REM >frrepcal
:
DIM gregorian$(11)
DIM gregorian%(11)
DIM republican$(11)
DIM sansculottides$(5)
gregorian$() = "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
gregorian%() = 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
REM 7-bit ASCII encoding, so no accents on French words
republican$() = "Vendemiaire", "Brumaire", "Frimaire", "Nivose", "Pluviose", "Ventose", "Germinal", "Floreal", "Prairial", "Messidor", "Thermidor", "Fructidor"
sansculottides$() = "Fete de la vertu", "Fete du genie", "Fete du travail", "Fete de l'opinion", "Fete des recompenses", "Fete de la Revolution"
:
PRINT "*** French  Republican ***"
PRINT "*** calendar converter ***"
PRINT "Enter a date to convert, in the format 'day month year'"
PRINT "e.g.: 1 Prairial 3,"
PRINT "      20 May 1795."
PRINT "For Sansculottides, use 'day year'"
PRINT "e.g.: Fete de l'opinion 9."
PRINT "Or just press 'RETURN' to exit the program."
PRINT
REPEAT
  INPUT LINE "> " src$
  IF src$ <> "" THEN
    PROC_split(src$, day%, month%, year%)
    REM for simplicity, we assume that years up to 1791 are Republican
    REM and years from 1792 onwards are Gregorian
    IF year% < 1792 THEN
      REM convert Republican date to number of days elapsed
      REM since 21 September 1792, then convert that number
      REM to the Gregorian date
      PROC_day_to_gre(FN_rep_to_day(day%, month%, year%), day%, month%, year%)
      PRINT; day%; " "; gregorian$(month% - 1); " " year%
    ELSE
      REM convert Gregorian date to Republican, via
      REM number of days elapsed since 21 September 1792
      PROC_day_to_rep(FN_gre_to_day(day%, month%, year%), day%, month%, year%)
      IF month% = 13 THEN
        PRINT sansculottides$(day% - 1); " "; year%
      ELSE
        PRINT; day%; " "; republican$(month% - 1); " "; year%
      ENDIF
    ENDIF
  ENDIF
UNTIL src$ = ""
END
:
DEF PROC_split(s$, RETURN d%, RETURN m%, RETURN y%)
LOCAL month_and_year$, month$, months$(), i%
DIM months$(11)
IF LEFT$(s$, 4) = "Fete" THEN
  m% = 13
  FOR i% = 0 TO 5
    IF LEFT$(s$, LEN sansculottides$(i%)) = sansculottides$(i%) THEN
      d% = i% + 1
      y% = VAL(RIGHT$(s$, LEN s$ - LEN sansculottides$(i%) - 1))
    ENDIF
  NEXT
ELSE
  d% = VAL(LEFT$(s$, INSTR(s$, " ") - 1))
  month_and_year$ = MID$(s$, INSTR(s$, " ") + 1)
  month$ = LEFT$(month_and_year$, INSTR(month_and_year$, " ") - 1)
  y% = VAL(MID$(month_and_year$, INSTR(month_and_year$, " ") + 1))
  IF y% < 1792 THEN months$() = republican$() ELSE months$() = gregorian$()
  FOR i% = 0 TO 11
    IF months$(i%) = month$ THEN m% = i% + 1
  NEXT
ENDIF
ENDPROC
:
DEF FN_gre_to_day(d%, m%, y%)
REM modified & repurposed from code given at
REM https://www.staff.science.uu.nl/~gent0113/calendar/isocalendar_text5.htm
IF m% < 3 THEN
  y% -= 1
  m% += 12
ENDIF
= INT(365.25 * y%) - INT(y% / 100) + INT(y% / 400) + INT(30.6 * (m% + 1)) + d% - 654842
:
DEF FN_rep_to_day(d%, m%, y%)
REM assume that a year is a leap year iff the _following_ year is
REM divisible by 4, but not by 100 unless also by 400
REM
REM other methods for computing Republican leap years exist
IF m% = 13 THEN
  m% -= 1
  d% += 30
ENDIF
IF FN_rep_leap(y%) THEN d% -= 1
= 365 * y% + INT((y% + 1) / 4) - INT((y% + 1) / 100) + INT((y% + 1) / 400) + 30 * m% + d% - 395
:
DEF PROC_day_to_gre(day%, RETURN d%, RETURN m%, RETURN y%)
y% = INT(day% / 365.25)
d% = day% - INT(365.25 * y%) + 21
y% += 1792
d% += INT(y% / 100) - INT(y% / 400) - 13
m% = 8
WHILE d% > gregorian%(m%)
  d% -= gregorian%(m%)
  m% += 1
  IF m% = 12 THEN
    m% = 0
    y% += 1
    IF FN_gre_leap(y%) THEN gregorian%(1) = 29 ELSE gregorian%(1) = 28
  ENDIF
ENDWHILE
m% += 1
ENDPROC
:
DEF PROC_day_to_rep(day%, RETURN d%, RETURN m%, RETURN y%)
LOCAL sansculottides%
y% = INT(day% / 365.25)
IF FN_rep_leap(y%) THEN y% -= 1
d% = day% - INT(365.25 * y%) + INT((y% + 1) / 100) - INT((y% + 1) / 400)
y% += 1
m% = 1
IF FN_rep_leap(y%) THEN sansculottides% = 6 ELSE sansculottides% = 5
WHILE d% > 30
  d% -= 30
  m% += 1
  IF m% = 13 THEN
    IF d% > sansculottides% THEN
      d% -= sansculottides%
      m% = 1
      y% += 1
      IF FN_rep_leap(y%) THEN sansculottides% = 6 ELSE sansculottides% = 5
    ENDIF
  ENDIF
ENDWHILE
ENDPROC
:
DEF FN_rep_leap(year%)
REM see comment at the beginning of FN_rep_to_day
= ((year% + 1) MOD 4 = 0 AND ((year% + 1) MOD 100 <> 0 OR (year% + 1) MOD 400 = 0))
:
DEF FN_gre_leap(year%)
= (year% MOD 4 = 0 AND (year% MOD 100 <> 0 OR year% MOD 400 = 0))


' version 18 Pluviose 227
' compile with: fbc -s console
' retained the original comments for then BBC BASIC entry

#Macro rep_leap (_year)
    ' see comment at the beginning of rep_to_day
    ((_year +1) Mod 4 = 0 And ((_year +1) Mod 100 <> 0 Or (_year +1) Mod 400 = 0))
#EndMacro

#Macro gre_leap (_year)
    (_year Mod 4 = 0 And (_year Mod 100 <> 0 Or _year Mod 400 = 0))
#EndMacro

Dim Shared As UInteger gregorian(11) => {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
Dim Shared As String gregorian_s(11), republican(11), sanscolottides(5)
' 7-bit ASCII encoding, so no accents on French words
Data "January", "February", "March", "April", "May", "June"
Data "July", "August", "September", "October", "November", "December"
Data "Vendemiaire", "Brumaire", "Frimaire","Nivose", "Pluviose", "Ventose"
Data "Germinal", "Floreal", "Prairial", "Messidor", "Thermidor", "Fructidor"
Data "Fete de la Vertu", "Fete du Genie", "Fete du Travail", "Fete de l'Opinion"
Data "Fete des Recompenses","Fete de la Revolution"
Restore
For i As UInteger = 0 To 11
    Read gregorian_s(i)
Next
For i As UInteger = 0 To 11
    Read republican(i)
Next
For i As UInteger = 0 To 5
    Read sanscolottides(i)
Next

Sub split(s As String, ByRef d As UInteger, ByRef m As UInteger, ByRef y As UInteger)

    Dim As String month_and_year, Month
    Dim As UInteger i

    s = LCase(Trim(s)) : d = 0 : m = 0 : y = 0
    If Left(s,4) = "fete" Then
        m = 13
        For i = 0 To 5
            If Left(s, Len(sanscolottides(i))) = LCase(sanscolottides(i)) Then
                d = i +1
                y = Val(Right(s, Len(s) - Len(sanscolottides(i)) -1))
            End If
        Next
    Else
        d = Val(Left(s, InStr(s, " ") -1))
        month_and_year = Mid(s, InStr(s, " ") +1)
        Month = Left(month_and_year, InStr(month_and_year, " ") -1)
        y = Val(Mid(month_and_year, InStr(month_and_year, " ") +1))
        If y < 1792 Then
            For i = 0 To 11
                If LCase(republican(i)) = Month Then m = i +1
            Next
        Else
            For i = 0 To 11
                If LCase(gregorian_s(i)) = Month Then m = i +1
            Next
        End If
    End If

End Sub

Sub day_to_gre(Day As UInteger, ByRef d As UInteger, ByRef m As UInteger, ByRef y As UInteger)

    y = Fix(Day / 365.25)
    d = Day - Fix(365.25 * y) + 21
    y += 1792
    d += y \ 100 - y \ 400 - 13
    m = 8

    While d > gregorian(m)
        d -= gregorian(m)
        m += 1
        If m = 12 Then
            m = 0
            y += 1
            If gre_leap(y) Then gregorian(1) = 29 Else gregorian(1) = 28
        End If
    Wend
    gregorian(1) = 28
    m += 1

End Sub

Function gre_to_day(d As UInteger, m As UInteger, y As UInteger) As UInteger

    ' modified & repurposed from code given at
    ' https://www.staff.science.uu.nl/~gent0113/calendar/isocalendar_text5.htm

    If m < 3 Then
        y -= 1
        m += 12
    End If
    Return Fix(365.25 * y) - y \ 100 + y \ 400 + Fix(30.6 * (m +1)) + d - 654842

End Function

Function rep_to_day(d As UInteger, m As UInteger, y As UInteger) As UInteger

    ' assume that a year is a leap year iff the _following_ year is
    ' divisible by 4, but not by 100 unless also by 400
    '
    ' other methods for computing republican leap years exist

    If m = 13 Then
        m -= 1
        d += 30
    End If
    If rep_leap(y) Then d -= 1

    Return 365 * y + (y +1) \ 4 - (y +1) \ 100 + (y +1) \ 400 + 30 * m + d - 395

End Function

Sub day_to_rep(Day As UInteger, ByRef d As UInteger, ByRef m As UInteger, ByRef y As UInteger)

    Dim As UInteger sansculottides = 5

    y = Fix(Day / 365.25)
    If rep_leap(y) Then y -= 1
    d = Day - Fix(365.25 * y) + (y +1) \ 100 - (y +1) \ 400
    y += 1
    m = 1
    If rep_leap(y) Then sansculottides = 6
    While d > 30
        d -= 30
        m += 1
        If m = 13 Then
            If d > sansculottides Then
                d -= sansculottides
                m = 1
                y += 1
                If rep_leap(y) Then sansculottides = 6 Else sansculottides = 5
            End If
        End If
    Wend

End Sub

' ------=< main >=------

Dim As UInteger Day, Month, Year
Dim As String src

Print "*** French  Republican ***"
Print "*** calendar converter ***"
Print "Enter a date to convert, in the format 'day month year'"
Print "e.g.: 1 Prairial 3,"
Print "      20 May 1795."
Print "For Sansculottides, use 'day year'"
Print "e.g.: Fete de l'opinion 9."
Print "Or just press 'RETURN' to exit the program."
Print

Do
    Line Input "> ", src
    If src <> "" Then
        split(src, Day, Month, Year)
        If Day = 0 Or Month = 0 Or Year <= 0 Then
            Print "Error in input"
            Continue Do
        End If
        ' for simplicity, we assume that years up to 1791 are republican
        ' and years from 1792 onwards are gregorian
        If Year < 1792 Then
            ' convert republican date to number of days elapsed
            ' since 21 september 1792, then convert that number
            ' to the gregorian date
            day_to_gre(rep_to_day(Day, Month, Year),Day, Month, Year)
            Print; Day; " "; gregorian_s(Month -1); " "; Year
        Else
            ' convert gregorian date to republican, via
            ' number of days elapsed since 21 september 1792
            day_to_rep(gre_to_day(Day, Month, Year), Day, Month, Year)
            If Month = 13 Then
                Print sanscolottides(Day -1); " "; Year
            Else
                Print ; Day; " "; republican(Month -1); " "; Year
            End If
        End If
    End If
Loop Until src = ""

End


// French Republican Calander: Nigel Galloway. April 16th., 2021
let firstDay=System.DateTime.Parse("22/9/1792")
type monthsFRC= Vendemiaire =   0
               |Brumaire    =  30
               |Frimaire    =  60
               |Nivose      =  90
               |Pluviose    = 120
               |Ventose     = 150
               |Germinal    = 180
               |Floral      = 210
               |Prairial    = 240
               |Messidor    = 270
               |Thermidor   = 300
               |Fructidor   = 330
               |Virtue      = 360
               |Talent      = 361
               |Labour      = 362
               |Opinion     = 363
               |Honours     = 364
               |Revolution  = 365
type months= January    =  1
            |February   =  2
            |March      =  3
            |April      =  4
            |May        =  5
            |June       =  6
            |July       =  7
            |August     =  8
            |September  =  9
            |October    = 10
            |November   = 11
            |December   = 12
let frc2Greg n (g:monthsFRC) l=firstDay+System.TimeSpan.FromDays(float((l-1)*365+l/4+(int g)+n-1))
let rec fG n g=let i=match g with 3 |7 |11->366 |_->365 in if n<i then (n,g) else fG(n-i)(g+1)
let Greg2FRC n=let n,g=fG((n-firstDay).Days) 1
               match n/30,n%30 with (12,n)->(1,enum<monthsFRC>(360+n),g) |(n,l)->(l+1,enum<monthsFRC>(n*30),g)
let n=(frc2Greg 1 monthsFRC.Vendemiaire 1) in printfn "%d %s %d -> %d %A %d" 1 "Vendemiaire" 1 n.Day (enum<months> n.Month) n.Year
let n=(frc2Greg 27 monthsFRC.Messidor 7) in printfn "%d %s %d -> %d %A %d" 27 "Messidor" 7 n.Day (enum<months> n.Month) n.Year
let n=(frc2Greg 1 monthsFRC.Revolution 11) in printfn "%d %s %d -> %d %A %d" 1 "Revolution" 11 n.Day (enum<months> n.Month) n.Year
let n=(frc2Greg 10 monthsFRC.Nivose 14) in printfn "%d %s %d -> %d %A %d" 10 "Nivose" 14 n.Day (enum<months> n.Month) n.Year
let n,g,l=Greg2FRC(System.DateTime(1792,9,22)) in printfn "%d %s %d -> %d %A %d" 22 "September" 1792 n g l
let n,g,l=Greg2FRC(System.DateTime(1799,7,15)) in printfn "%d %s %d -> %d %A %d" 15 "July" 1799 n g l
let n,g,l=Greg2FRC(System.DateTime(1803,9,23)) in printfn "%d %s %d -> %d %A %d" 23 "September" 1803 n g l
let n,g,l=Greg2FRC(System.DateTime(1805,12,31)) in printfn "%d %s %d -> %d %A %d" 31 "December" 1805 n g l


package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

var (
    gregorianStr = []string{"January", "February", "March",
        "April", "May", "June",
        "July", "August", "September",
        "October", "November", "December"}
    gregorian     = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
    republicanStr = []string{"Vendemiaire", "Brumaire", "Frimaire",
        "Nivose", "Pluviose", "Ventose",
        "Germinal", "Floreal", "Prairial",
        "Messidor", "Thermidor", "Fructidor"}
    sansculottidesStr = []string{"Fete de la vertu", "Fete du genie",
        "Fete du travail", "Fete de l'opinion",
        "Fete des recompenses", "Fete de la Revolution"}
)

func main() {
    fmt.Println("*** French  Republican ***")
    fmt.Println("*** calendar converter ***")
    fmt.Println("Enter a date to convert, in the format 'day month year'")
    fmt.Println("e.g.: 1 Prairial 3,")
    fmt.Println("      20 May 1795.")
    fmt.Println("For Sansculottides, use 'day year'")
    fmt.Println("e.g.: Fete de l'opinion 9.")
    fmt.Println("Or just press 'RETURN' to exit the program.")
    fmt.Println()
    for sc := bufio.NewScanner(os.Stdin); ; {
        fmt.Print("> ")
        sc.Scan()
        src := sc.Text()
        if src == "" {
            return
        }
        day, month, year := split(src)
        if year < 1792 {
            day, month, year = dayToGre(repToDay(day, month, year))
            fmt.Println(day, gregorianStr[month-1], year)
        } else {
            day, month, year := dayToRep(greToDay(day, month, year))
            if month == 13 {
                fmt.Println(sansculottidesStr[day-1], year)
            } else {
                fmt.Println(day, republicanStr[month-1], year)
            }
        }
    }
}

func split(s string) (d, m, y int) {
    if strings.HasPrefix(s, "Fete") {
        m = 13
        for i, sc := range sansculottidesStr {
            if strings.HasPrefix(s, sc) {
                d = i + 1
                y, _ = strconv.Atoi(s[len(sc)+1:])
            }
        }
    } else {
        d, _ = strconv.Atoi(s[:strings.Index(s, " ")])
        my := s[strings.Index(s, " ")+1:]
        mStr := my[:strings.Index(my, " ")]
        y, _ = strconv.Atoi(my[strings.Index(my, " ")+1:])
        months := gregorianStr
        if y < 1792 {
            months = republicanStr
        }
        for i, mn := range months {
            if mn == mStr {
                m = i + 1
            }
        }
    }
    return
}

func greToDay(d, m, y int) int {
    if m < 3 {
        y--
        m += 12
    }
    return y*36525/100 - y/100 + y/400 + 306*(m+1)/10 + d - 654842
}

func repToDay(d, m, y int) int {
    if m == 13 {
        m--
        d += 30
    }
    if repLeap(y) {
        d--
    }
    return 365*y + (y+1)/4 - (y+1)/100 + (y+1)/400 + 30*m + d - 395
}

func dayToGre(day int) (d, m, y int) {
    y = day * 100 / 36525
    d = day - y*36525/100 + 21
    y += 1792
    d += y/100 - y/400 - 13
    m = 8
    for d > gregorian[m] {
        d -= gregorian[m]
        m++
        if m == 12 {
            m = 0
            y++
            if greLeap(y) {
                gregorian[1] = 29
            } else {
                gregorian[1] = 28
            }
        }
    }
    m++
    return
}

func dayToRep(day int) (d, m, y int) {
    y = (day-1) * 100 / 36525
    if repLeap(y) {
        y--
    }
    d = day - (y+1)*36525/100 + 365 + (y+1)/100 - (y+1)/400
    y++
    m = 1
    sansculottides := 5
    if repLeap(y) {
        sansculottides = 6
    }
    for d > 30 {
        d -= 30
        m += 1
        if m == 13 {
            if d > sansculottides {
                d -= sansculottides
                m = 1
                y++
                sansculottides = 5
                if repLeap(y) {
                    sansculottides = 6
                }
            }
        }
    }
    return
}

func repLeap(year int) bool {
    return (year+1)%4 == 0 && ((year+1)%100 != 0 || (year+1)%400 == 0)
}

func greLeap(year int) bool {
    return year%4 == 0 && (year%100 != 0 || year%400 == 0)
}


using Dates

const GC_FORMAT = DateFormat("d U y")

const RC_FIRST_DAY = Date(1792, 9, 22)

const MAX_RC_DATE = Date(1805, 12, 31)

const RC_MONTHS = [
    "Vendémiaire", "Brumaire", "Frimaire", "Nivôse", "Pluviôse", "Ventôse",
    "Germinal", "Floréal", "Prairial", "Messidor", "Thermidor", "Fructidor"
]

const RC_DAYS_IN_MONTH = 30

const RC_SANSCULOTTIDES = [
    "Fête de la vertu", "Fête du génie", "Fête du travail",
    "Fête de l'opinion", "Fête des récompenses", "Fête de la Révolution"
]

additionaldaysforyear(yr) = yr > 11 ? 3 : yr > 7 ? 2 : yr > 3 ? 1 : 0
additionaldaysformonth(mo) = 30 * (mo - 1)
daysforFete(s) = findfirst(x -> x == s, RC_SANSCULOTTIDES) + 359

function togregorian(rc::String)
    yearstring, firstpart = reverse.(split(reverse(strip(rc)), r"\s+", limit=2))
    rcyear = parse(Int, yearstring)
    pastyeardays = (rcyear - 1) * 365 + additionaldaysforyear(rcyear)
    if isnumeric(firstpart[1])
        daystring, monthstring = split(firstpart, r"\s+", limit=2)
        nmonth = findfirst(x -> x == monthstring, RC_MONTHS)
        pastmonthdays = 30 * (nmonth - 1)
        furtherdays = parse(Int, daystring) + pastmonthdays + pastyeardays - 1
    else
        furtherdays = daysforFete(firstpart) + pastyeardays
    end
    gregorian = RC_FIRST_DAY + Day(furtherdays)
    if furtherdays < 0 || gregorian > MAX_RC_DATE
        throw(DomainError("French Republican Calendar date out of range"))
    end
    return Day(gregorian).value, monthname(Month(gregorian).value), Year(gregorian).value
end

function torepublican(gc::String)
    date = Date(DateTime(gc, GC_FORMAT))
    if date < RC_FIRST_DAY || date > MAX_RC_DATE
        throw(DomainError("French Republican Calendar date out of range"))
    end
    rcyear, rcdays = divrem(((date - RC_FIRST_DAY).value + 366), 365)
    rcdays -= additionaldaysforyear(rcyear)
    if rcdays < 1
        rcyear -= 1
        rcdays += 366
    end
    if rcdays < 361
        nmonth, rcday = divrem(rcdays, 30)
        return rcday, RC_MONTHS[nmonth + 1], rcyear
    else
        return RC_SANSCULOTTIDES[rcdays - 360], rcyear
    end
end

const republican = [
    "1 Vendémiaire 1", "1 Prairial 3", "27 Messidor 7",
    "Fête de la Révolution 11", "10 Nivôse 14"
]

const gregorian = [
    "22 September 1792", "20 May 1795", "15 July 1799",
    "23 September 1803", "31 December 1805"
]

function testrepublicancalendar()
    println("French Republican to Gregorian")
    for s in republican
        println(lpad(s, 24), " => ", togregorian(s))
    end
    println("Gregorian to French Republican")
    for s in gregorian
        println(lpad(s, 24), " => ", torepublican(s))
    end
end

testrepublicancalendar()


// version 1.1.4-3

import java.time.format.DateTimeFormatter
import java.time.LocalDate
import java.time.temporal.ChronoUnit.DAYS

/* year = 1..  month = 1..13  day = 1..30 */
class FrenchRCDate(val year: Int, val month: Int, val day: Int) {

    init {
        require (year > 0 && month in 1..13)
        if (month < 13) require (day in 1..30)
        else {
            val leap = isLeapYear(year)
            require (day in (if (leap) 1..6 else 1..5))
        }
    }

    override fun toString() =
        if (month < 13) "$day ${months[month - 1]} $year"
        else "${intercal[day - 1]} $year"

    fun toLocalDate(): LocalDate {
        var sumDays = 0L
        for (i in 1 until year) sumDays += if (isLeapYear(i)) 366 else 365
        val dayInYear = (month - 1) * 30 + day - 1
        return introductionDate.plusDays(sumDays + dayInYear)
    }

    companion object {
        /* uses the 'continuous method' for years after 1805 */
        fun isLeapYear(y: Int): Boolean {
            val yy = y + 1
            return (yy % 4 == 0) && (yy % 100 != 0 || yy % 400 == 0)
        }

        fun parse(frcDate: String): FrenchRCDate {
            val splits = frcDate.trim().split(' ')
            if (splits.size == 3) {
                val month = months.indexOf(splits[1]) + 1
                require(month in 1..13)
                val year = splits[2].toIntOrNull() ?: 0
                require(year > 0)
                val monthLength = if (month < 13) 30 else if (isLeapYear(year)) 6 else 5
                val day = splits[0].toIntOrNull() ?: 0
                require(day in 1..monthLength)
                return FrenchRCDate(year, month, day)
            }
            else if (splits.size in 4..5) {
                val yearStr = splits[splits.lastIndex]
                val year = yearStr.toIntOrNull() ?: 0
                require(year > 0)
                val scDay = frcDate.trim().dropLast(yearStr.length + 1)
                val day = intercal.indexOf(scDay) + 1
                val maxDay = if (isLeapYear(year)) 6 else 5
                require (day in 1..maxDay)
                return FrenchRCDate(year, 13, day)
            }
            else throw IllegalArgumentException("Invalid French Republican date")
        }

        /* for convenience we treat 'Sansculottide' as an extra month with 5 or 6 days */
        val months = arrayOf(
            "Vendémiaire", "Brumaire", "Frimaire", "Nivôse", "Pluviôse", "Ventôse", "Germinal",
            "Floréal", "Prairial", "Messidor", "Thermidor", "Fructidor", "Sansculottide"
        )

        val intercal = arrayOf(
            "Fête de la vertu", "Fête du génie", "Fête du travail",
            "Fête de l'opinion", "Fête des récompenses", "Fête de la Révolution"
        )

        val introductionDate = LocalDate.of(1792, 9, 22)
    }
}

fun LocalDate.toFrenchRCDate(): FrenchRCDate {
    val daysDiff  = DAYS.between(FrenchRCDate.introductionDate, this).toInt() + 1
    if (daysDiff <= 0) throw IllegalArgumentException("Date can't be before 22 September 1792")
    var year = 1
    var startDay = 1
    while (true) {
        val endDay = startDay + if (FrenchRCDate.isLeapYear(year)) 365 else 364
        if (daysDiff in startDay..endDay) break
        year++
        startDay = endDay + 1
    }
    val remDays = daysDiff - startDay
    val month  = remDays / 30
    val day = remDays - month * 30
    return FrenchRCDate(year, month + 1, day + 1)
}

fun main(args: Array<String>) {
    val formatter = DateTimeFormatter.ofPattern("d MMMM yyyy")
    val dates = arrayOf("22 September 1792", "20 May 1795", "15 July 1799", "23 September 1803",
                        "31 December 1805", "18 March 1871", "25 August 1944", "19 September 2016",
                        "22 September 2017", "28 September 2017")
    val frcDates = Array<String>(dates.size) { "" }
    for ((i, date) in dates.withIndex()) {
        val thisDate = LocalDate.parse(date, formatter)
        val frcd = thisDate.toFrenchRCDate()
        frcDates[i] = frcd.toString()
        println("${date.padEnd(25)} => $frcd")
    }

    // now process the other way around
    println()
    for (frcDate in frcDates) {
        val thisDate = FrenchRCDate.parse(frcDate)
        val lds = formatter.format(thisDate.toLocalDate())
        println("${frcDate.padEnd(25)} => $lds")
    }
}


import strformat, strscans, strutils, times

const

  RcMonths = ["Vendémiaire", "Brumaire", "Frimaire", "Nivôse", "Pluviôse", "Ventôse",
              "Germinal", "Floréal", "Prairial", "Messidor", "Thermidor", "Fructidor"]

  SansCulottides = ["Fête de la vertu", "Fête du génie", "Fête du travail",
                    "Fête de l’opinion", "Fête des récompenses", "Fête de la Révolution"]

let
  # First and last dates of republican calendar expressed in gregorian calendar.
  FirstRcDate = initDateTime(22, mSep, 1792, 0, 0, 0)
  LastRcDate = initDateTime(31, mDec, 1805, 0, 0, 0)

type
  # French republican date representation.
  RcDayRange = 1..30
  RcMonthRange = 1..13
  RcYearRange = 1..14
  RepublicanDate = tuple[year: RcYearRange, month: RcMonthRange, day: RcDayRange]

# Last dates of republican calendar expressed in republican calendar.
const RcLastDate: RepublicanDate = (RcYearRange(14), RcMonthRange(4), RcDayRange(10))

proc notnum(input: string; str: var string; start: int): int =
  # Parsing procedure to extract non numerical part of a date.
  var i = start
  while i <= input.high:
    if input[i] in '0'..'9': break
    str.add input[i]
    inc i
  if str.len == 0 or str[^1] != ' ': return -1   # Not terminated by a space.
  str.setLen(str.len - 1)   # Back before the space.
  result = str.len

proc parseRepublicanDate(rdate: string): RepublicanDate =
  ## Parse a French republican date and return its representation.

  let date = rdate.strip()
  var day, month, year: int
  var monthString, dayString: string

  if date.scanf("$i $+ $i", day, monthString, year):
    # Normal day.
    if day notin 1..30:
      raise newException(ValueError, "wrong day number: $1.".format(day))
    month = RcMonths.find(monthString) + 1
    if month == 0:
      raise newException(ValueError, "unknown French republican month: $1." % monthString)

  elif date.scanf("${notnum} $i", dayString, year):
    # Sans-culottide day (also known as “jour complémentaire”).
    month = 13  # Value used for sans-culottide days.
    day = SansCulottides.find(dayString) + 1
    if day == 0:
      raise newException(ValueError, "wrong “sans-culottide” day: « $1 »." % dayString)
    if day == 6 and year mod 4 != 3:
      raise newException(ValueError, "republican year $1 is not a leap year".format(year))

  else:
    raise newException(ValueError, "invalid French republican date: « $1 »." % date)

  result = (RcYearRange(year), RcMonthRange(month), RcDayRange(day))
  if result > RcLastDate:
    raise newException(ValueError, "republican date out of range: « $1 »." % date)

proc `$`(date: RepublicanDate): string =
  ## Return the string representation of a French republican date.

  if date.month != 13:
    # Normal day.
    result = "$1 $2 $3".format(date.day, RcMonths[date.month - 1], date.year)
  else:
    # Supplementary day.
    result = "$1 $2".format(SansCulottides[date.day - 1], date.year)

proc toGregorian(rdate: RepublicanDate): DateTime =
  ## Convert a republican date tuple to a gregorian date (DateTime object).
  let day = (rdate.day - 1) + (rdate.month - 1) * 30 + (rdate.year - 1) * 365 + rdate.year div 4
  result = FirstRcDate + initTimeInterval(days = day)

proc toGregorian(rdate: string): string =
  ## Convert a republican date string to a gregorian date string.
  let date = rdate.parseRepublicanDate()
  result = date.toGregorian().format("dd MMMM yyyy")

proc toRepublican(gdate: DateTime): RepublicanDate =
  ## Convert a gregorian date (DateTime object) to a republican date tuple.

  if gdate notin FirstRcDate..LastRcDate:
    raise newException(ValueError, "impossible conversion to republican date.")
  let d = gdate - FirstRcDate

  # Add a dummy year before year 1 in order to use a four years period.
  let dayNumber = d.inDays + 365
  let periodNum = dayNumber div 1461
  let dayInPeriod = dayNumber mod 1461

  # Compute year and day in year.
  let yearInPeriod = min(dayInPeriod div 365, 3)
  result.year = periodNum * 4 + yearInPeriod
  let dayInYear = dayInPeriod - yearInPeriod * 365

  # Compute month and day.
  result.month = dayInYear div 30 + 1
  result.day = dayInYear mod 30 + 1

proc toRepublican(gdate: string): string =
  ## Convert a gregorian date string to a republican date string.
  let date = gdate.parse("d MMMM yyyy")
  result = $(date.toRepublican())

when isMainModule:

  const
    RepublicanDates = ["1 Vendémiaire 1", "1 Prairial 3", "27 Messidor 7",
                      "Fête de la Révolution 11", "10 Nivôse 14"]
    GregorianDates = ["22 September 1792", "20 May 1795", "15 July 1799",
                      "23 September 1803", "31 December 1805"]

  echo "From French republican dates to gregorian dates:"
  for rdate in RepublicanDates:
    echo &"{rdate:>24} → {rdate.toGregorian()}"
  echo()

  echo "From gregorian dates to French republican dates:"
  for gdate in GregorianDates:
    echo &"{gdate:>24} → {gdate.toRepublican()}"


use feature 'state';
use DateTime;
my @month_names = qw{
    Vendémiaire Brumaire Frimaire  Nivôse   Pluviôse  Ventôse
    Germinal    Floréal  Prairial  Messidor Thermidor Fructidor
};
my @intercalary = (
    'Fête de la vertu',  'Fête du génie',        'Fête du travail',
    "Fête de l'opinion", 'Fête des récompenses', 'Fête de la Révolution',
);
my %month_nums  = map { $month_names[$_] => $_+1 } 0 .. $#month_names;
my %i_cal_nums  = map { $intercalary[$_] => $_+1 } 0 .. $#intercalary;
my $i_cal_month = 13;
my $epoch       = DateTime->new( year => 1792, month => 9, day => 22 );

sub is_republican_leap_year {
    my $y = $_[0] + 1;
    return !!( ($y % 4)==0 and (($y % 100)!=0 or ($y % 400)==0) );
}

sub Republican_to_Gregorian {
    my ($rep_date) = @_;
    state $months   = join '|', map { quotemeta } @month_names;
    state $intercal = join '|', map { quotemeta } @intercalary;
    state $re = qr{
      \A
        \s* (?:
                (?<ic> $intercal)
              | (?<day> \d+) \s+ (?<month> $months)
            )
        \s+ (?<year> \d+)
        \s*
      \z
    }msx;

    $rep_date =~ /$re/
        or die "Republican date not recognized: '$rep_date'";

    my $day1   = $+{ic}    ? $i_cal_nums{$+{ic}}    : $+{day};
    my $month1 = $+{month} ? $month_nums{$+{month}} : $i_cal_month;
    my $year1  = $+{year};

    my $days_since_epoch = ($year1-1) * 365 + ($month1-1) * 30 + ($day1-1);

    my $leap_days = grep { is_republican_leap_year($_) } 1 .. $year1-1;
    return $epoch->clone->add( days => ($days_since_epoch + $leap_days) );
}

sub Gregorian_to_Republican {
    my ($greg_date) = @_;

    my $days_since_epoch = $epoch->delta_days($greg_date)->in_units('days');
    die if $days_since_epoch < 0;
    my ( $year, $days ) = ( 1, $days_since_epoch );
    while (1) {
        my $year_length = 365 + ( is_republican_leap_year($year) ? 1 : 0 );
        last if $days < $year_length;
        $days -= $year_length;
        $year += 1;
    }
    my $day0   = $days % 30;
    my $month0 = ($days - $day0) / 30;

    my ( $day1, $month1 ) = ( $day0 + 1, $month0 + 1 );

    return $month1 == $i_cal_month
        ?       "$intercalary[$day0  ] $year"
        : "$day1 $month_names[$month0] $year";
}

while (<DATA>) {
    s{\s*\#.+\n?\z}{};
    /^(\d{4})-(\d{2})-(\d{2})\s+(\S.+?\S)\s*$/ or die;

    my $g = DateTime->new( year => $1, month => $2, day => $3 );
    my $r = $4;

    die if Republican_to_Gregorian($r) != $g
        or Gregorian_to_Republican($g) ne $r;

    die if Gregorian_to_Republican(Republican_to_Gregorian($r)) ne $r
        or Republican_to_Gregorian(Gregorian_to_Republican($g)) != $g;
}
say 'All tests successful.';

__DATA__
1792-09-22  1 Vendémiaire 1
1795-05-20  1 Prairial 3
1799-07-15  27 Messidor 7
1803-09-23  Fête de la Révolution 11
1805-12-31  10 Nivôse 14
1871-03-18  27 Ventôse 79
1944-08-25  7 Fructidor 152
2016-09-19  Fête du travail 224
1871-05-06  16 Floréal 79   # Paris Commune begins
1871-05-23  3 Prairial 79   # Paris Commune ends
1799-11-09  18 Brumaire 8   # Revolution ends by Napoléon coup
1804-12-02  11 Frimaire 13  # Republic   ends by Napoléon coronation
1794-10-30  9 Brumaire 3    # École Normale Supérieure established
1794-07-27  9 Thermidor 2   # Robespierre falls
1799-05-27  8 Prairial 7    # Fromental Halévy born
1792-09-22  1 Vendémiaire 1
1793-09-22  1 Vendémiaire 2
1794-09-22  1 Vendémiaire 3
1795-09-23  1 Vendémiaire 4
1796-09-22  1 Vendémiaire 5
1797-09-22  1 Vendémiaire 6
1798-09-22  1 Vendémiaire 7
1799-09-23  1 Vendémiaire 8
1800-09-23  1 Vendémiaire 9
1801-09-23  1 Vendémiaire 10
1802-09-23  1 Vendémiaire 11
1803-09-24  1 Vendémiaire 12
1804-09-23  1 Vendémiaire 13
1805-09-23  1 Vendémiaire 14
1806-09-23  1 Vendémiaire 15
1807-09-24  1 Vendémiaire 16
1808-09-23  1 Vendémiaire 17
1809-09-23  1 Vendémiaire 18
1810-09-23  1 Vendémiaire 19
1811-09-24  1 Vendémiaire 20
2015-09-23  1 Vendémiaire 224
2016-09-22  1 Vendémiaire 225
2017-09-22  1 Vendémiaire 226


with javascript_semantics
constant gregorians = {"January", "February", "March", "April", "May", "June", "July",
                       "August", "September", "October", "November", "December"},
         gregorian = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
         republicans = {"Vendémiaire", "Brumaire", "Frimaire", "Nivôse", "Pluviôse",
                        "Ventôse", "Germinal", "Floréal", "Prairial", "Messidor",
                        "Thermidor", "Fructidor"},
         sansculottides = {"Fête de la vertu", "Fête du génie", "Fête du travail",
                           "Fête de l'opinion", "Fête des récompenses",
                           "Fête de la Révolution"}

function rep_leap(integer year)
    return mod(year+1,4)==0 and (mod(year+1,100)!=0 or mod(year+1,400)==0)
end function

function rep_to_day(sequence dmy)
    integer {d, m, y} = dmy
    if m == 13 then
        m -= 1
        d += 30
    end if
    if rep_leap(y) then
        d -= 1
    end if
    integer res = 365*(y-1) + floor((y+1)/4) - floor((y+1)/100) + floor((y+1)/400) + 30*(m-1) + d
    return res
end function

function gre_leap(integer year)
    return mod(year,4)==0 and (mod(year,100)!=0 or mod(year,400)==0)
end function

function gre_to_day(sequence dmy)
    integer {d, m, y} = dmy
    if m < 3 then
        y -= 1
        m += 12
    end if
    integer res =  floor(y*365.25) - floor(y/100) + floor(y/400)
                 + floor(30.6*(m+1)) + d - 654842
    return res
end function

function day_to_rep(integer day)
    integer y = floor((day-1)/365.25)
    if not rep_leap(y) then
        y += 1
    end if
    integer d = day - floor(y*365.25) + 365 + floor(y/100) - floor(y/400),
            m = 1,
            sansculottide = 5+rep_leap(y)
    while d>30 do
        d -= 30
        m += 1
        if m == 13 then
            if d > sansculottide then
                d -= sansculottide
                m = 1
                y += 1
                sansculottide = 5 + rep_leap(y)
            end if
        end if
    end while
    return {d,m,y}
end function

function day_to_gre(integer day)
    integer y = floor(day/365.25),
            d = day - floor(y*365.25) + 21,
            m = 9
    y += 1792
    d += floor(y/100) - floor(y/400) - 13
    sequence gregoriam = deep_copy(gregorian)  -- (modifiable copy)
    while d>gregoriam[m] do
        d -= gregoriam[m]
        m += 1
        if m == 13 then
            m = 1
            y += 1
            gregoriam[2] = 28 + gre_leap(y)
        end if
    end while
    return {d,m,y}
end function

function greg_to_frep(string greg)
    {integer day, string months, integer year} = scanf(greg,"%d %s %d")[1]
    integer month = find(months,gregorians)
    {day,month,year} = day_to_rep(gre_to_day({day,month,year}))
    string frep = iff(month=13?sprintf("%s %d",{sansculottides[day], year})
                              :sprintf("%d %s %d",{day, republicans[month], year}))
    return frep
end function

function frep_to_greg(string frep)
    integer day, month, year
    string months, days
    if frep[1]<='9' then
        {day, months, year} = scanf(frep,"%d %s %d")[1]
        month = find(months,republicans)
    else
        {days, year} = scanf(frep,"%s %d")[1]
        day = find(days,sansculottides)
        month = 13
    end if
    {day,month,year} = day_to_gre(rep_to_day({day, month, year}))
    string greg = sprintf("%02d %s %d",{day, gregorians[month], year})
    return greg
end function

constant test_data = {
                      { "22 September 1792", "1 Vendémiaire 1" },
                      { "20 May 1795", "1 Prairial 3" },
                      { "15 July 1799", "27 Messidor 7" },
                      { "23 September 1803", "Fête de la Révolution 11" },
                      { "31 December 1805", "10 Nivôse 14" },

                      { "18 March 1871", "27 Ventôse 79" },
                      { "25 August 1944", "7 Fructidor 152" },
                      { "19 September 2016", "Fête du travail 224" },

                      { "06 May 1871", "16 Floréal 79" },       -- Paris Commune begins
                      { "23 May 1871", "3 Prairial 79" },       -- Paris Commune ends
                      { "09 November 1799", "18 Brumaire 8" },  -- Revolution ends by Napoléon coup
                      { "02 December 1804", "11 Frimaire 13" }, -- Republic ends by Napoléon coronation
                      { "30 October 1794", "9 Brumaire 3" },    -- École Normale Supérieure established
                      { "27 July 1794", "9 Thermidor 2" },      -- Robespierre falls
                      { "27 May 1799", "8 Prairial 7" },        -- Fromental Halévy born

                      { "22 September 1792", "1 Vendémiaire 1" },
                      { "22 September 1793", "1 Vendémiaire 2" },
                      { "22 September 1794", "1 Vendémiaire 3" },
                      { "23 September 1795", "1 Vendémiaire 4" },
                      { "22 September 1796", "1 Vendémiaire 5" },
                      { "22 September 1797", "1 Vendémiaire 6" },
                      { "22 September 1798", "1 Vendémiaire 7" },
                      { "23 September 1799", "1 Vendémiaire 8" },
                      { "23 September 1800", "1 Vendémiaire 9" },
                      { "23 September 1801", "1 Vendémiaire 10" },
                      { "23 September 1802", "1 Vendémiaire 11" },
                      { "24 September 1803", "1 Vendémiaire 12" },
                      { "23 September 1804", "1 Vendémiaire 13" },
                      { "23 September 1805", "1 Vendémiaire 14" },
                      { "23 September 1806", "1 Vendémiaire 15" },
                      { "24 September 1807", "1 Vendémiaire 16" },
                      { "23 September 1808", "1 Vendémiaire 17" },
                      { "23 September 1809", "1 Vendémiaire 18" },
                      { "23 September 1810", "1 Vendémiaire 19" },
                      { "24 September 1811", "1 Vendémiaire 20" },
                      { "23 September 2015", "1 Vendémiaire 224" },
                      { "21 September 2016", "Fête des récompenses 224" },
                      { "22 September 2016", "1 Vendémiaire 225" },
                      { "23 September 2016", "2 Vendémiaire 225" },
                      { "22 September 2017", "1 Vendémiaire 226" },
                      { "28 September 2017", "7 Vendémiaire 226" } }

for i=1 to length(test_data) do
    string {greg, frep} = test_data[i],
           frep2 = greg_to_frep(greg),
           greg2 = frep_to_greg(frep),
           ok = iff(frep=frep2 and greg=greg2?"ok":"**** ERROR ****")
    if platform()=WINDOWS then
        -- the windows console does not handle
        -- non-basic-latin-ascii characters well...
        frep = substitute_all(frep,"éêéô","eeeo")
    end if
    printf(1,"%18s <==> %-25s %s\n",{greg,frep,ok})
end for

--sanity test:
for i=1 to 150000 do -- (years 1792..~2203)
    if rep_to_day(day_to_rep(i))!=i then ?9/0 end if
    if gre_to_day(day_to_gre(i))!=i then ?9/0 end if
end for


use v6;
constant @month_names = <
    Vendémiaire Brumaire Frimaire  Nivôse   Pluviôse  Ventôse
    Germinal    Floréal  Prairial  Messidor Thermidor Fructidor
>;
constant @intercalary =
    'Fête de la vertu',  'Fête du génie',        'Fête du travail',
    "Fête de l'opinion", 'Fête des récompenses', 'Fête de la Révolution',
;
constant %month_nums  = %( @month_names Z=> 1..12 );
constant %i_cal_nums  = %( @intercalary Z=> 1.. 6 );
constant $i_cal_month = 13;
constant $epoch       = Date.new: '1792-09-22';

sub is_republican_leap_year ( Int:D $year --> Bool ) {
    my $y := $year + 1;
    return ?( $y %% 4 and ($y !%% 100 or $y %% 400) );
}

sub Republican_to_Gregorian ( Str:D $rep_date --> Date ) {
    grammar Republican_date_text {
        token day   { \d+ }
        token year  { \d+ }
        token ic    { @intercalary }
        token month { @month_names }
        rule TOP    { ^ [ <ic> | <day> <month> ] <year> $ }
    }

    Republican_date_text.parse($rep_date)
        orelse die "Republican date not recognized: '$rep_date'";

    my $day1   := $/<ic>    ?? %i_cal_nums{~$/<ic>}    !! +$/<day>;
    my $month1 := $/<month> ?? %month_nums{~$/<month>} !! $i_cal_month;

    my @ymd0 := ($/<year>, $month1, $day1) »-» 1;
    my $days_since_epoch := [+] @ymd0 Z* (365, 30, 1);

    my $leap_days := +grep &is_republican_leap_year, 1 ..^ $/<year>;
    return $epoch + $days_since_epoch + $leap_days;
}

sub Gregorian_to_Republican ( Date:D $greg_date --> Str ) {
    my $days_since_epoch := $greg_date - $epoch;
    die if $days_since_epoch < 0;

    my ( $year, $days ) = 1, $days_since_epoch;
    loop {
        my $year_length = 365 + (1 if $year.&is_republican_leap_year);
        last if $days < $year_length;
        $days -= $year_length;
        $year += 1;
    }

    my ( $day0, $month0 ) = $days.polymod( 30 );
    my ( $day1, $month1 ) = ($day0, $month0) X+ 1;

    return $month1 == $i_cal_month
        ??       "@intercalary[$day0  ] $year"
        !! "$day1 @month_names[$month0] $year";
}

my @test_data =
    ( '1792-09-22', '1 Vendémiaire 1' ),
    ( '1795-05-20', '1 Prairial 3' ),
    ( '1799-07-15', '27 Messidor 7' ),
    ( '1803-09-23', 'Fête de la Révolution 11' ),
    ( '1805-12-31', '10 Nivôse 14' ),

    ( '1871-03-18', '27 Ventôse 79' ),
    ( '1944-08-25', '7 Fructidor 152' ),
    ( '2016-09-19', 'Fête du travail 224' ),

    ( '1871-05-06', '16 Floréal 79'  ), # Paris Commune begins
    ( '1871-05-23', '3 Prairial 79'  ), # Paris Commune ends
    ( '1799-11-09', '18 Brumaire 8'  ), # Revolution ends by Napoléon coup
    ( '1804-12-02', '11 Frimaire 13' ), # Republic   ends by Napoléon coronation
    ( '1794-10-30', '9 Brumaire 3'   ), # École Normale Supérieure established
    ( '1794-07-27', '9 Thermidor 2'  ), # Robespierre falls
    ( '1799-05-27', '8 Prairial 7'   ), # Fromental Halévy born

    ( '1792-09-22', '1 Vendémiaire 1'   ),
    ( '1793-09-22', '1 Vendémiaire 2'   ),
    ( '1794-09-22', '1 Vendémiaire 3'   ),
    ( '1795-09-23', '1 Vendémiaire 4'   ),
    ( '1796-09-22', '1 Vendémiaire 5'   ),
    ( '1797-09-22', '1 Vendémiaire 6'   ),
    ( '1798-09-22', '1 Vendémiaire 7'   ),
    ( '1799-09-23', '1 Vendémiaire 8'   ),
    ( '1800-09-23', '1 Vendémiaire 9'   ),
    ( '1801-09-23', '1 Vendémiaire 10'  ),
    ( '1802-09-23', '1 Vendémiaire 11'  ),
    ( '1803-09-24', '1 Vendémiaire 12'  ),
    ( '1804-09-23', '1 Vendémiaire 13'  ),
    ( '1805-09-23', '1 Vendémiaire 14'  ),
    ( '1806-09-23', '1 Vendémiaire 15'  ),
    ( '1807-09-24', '1 Vendémiaire 16'  ),
    ( '1808-09-23', '1 Vendémiaire 17'  ),
    ( '1809-09-23', '1 Vendémiaire 18'  ),
    ( '1810-09-23', '1 Vendémiaire 19'  ),
    ( '1811-09-24', '1 Vendémiaire 20'  ),
    ( '2015-09-23', '1 Vendémiaire 224' ),
    ( '2016-09-22', '1 Vendémiaire 225' ),
    ( '2017-09-22', '1 Vendémiaire 226' ),
;

for @test_data -> ( $g_text, $r ) {
    my $g = Date.new: $g_text;

    die if Republican_to_Gregorian($r) != $g
        or Gregorian_to_Republican($g) ne $r;

    die if Gregorian_to_Republican(Republican_to_Gregorian($r)) ne $r
        or Republican_to_Gregorian(Gregorian_to_Republican($g)) != $g;
}
say 'All tests successful.';


require('DateTime')

var month_names = %w(
  Vendémiaire Brumaire Frimaire  Nivôse   Pluviôse  Ventôse
  Germinal    Floréal  Prairial  Messidor Thermidor Fructidor
)

var intercalary = [
                   'Fête de la vertu',
                   'Fête du génie',
                   'Fête du travail',
                   "Fête de l'opinion",
                   'Fête des récompenses',
                   'Fête de la Révolution',
                  ]

var i_cal_month = 13
var epoch = %O<DateTime>.new(year => 1792, month => 9, day => 22)

var month_nums = Hash(month_names.kv.map {|p| [p[1], p[0]+1] }.flat...)
var i_cal_nums = Hash(intercalary.kv.map {|p| [p[1], p[0]+1] }.flat...)

func is_republican_leap_year(Number year) -> Bool {
    var y = (year + 1)
    !!(4.divides(y) && (!100.divides(y) || 400.divides(y)))
}

func Republican_to_Gregorian(String rep_date) -> String {
    static months   = month_names.map { .escape }.join('|')
    static intercal = intercalary.map { .escape }.join('|')
    static re       = Regex("^
        \\s* (?:
                (?<ic> #{intercal})
              | (?<day> \\d+) \\s+ (?<month> #{months})
            )
        \\s+ (?<year> \\d+)
        \\s*
      \\z", 'x')

    var m = (rep_date =~ re)
    m || die "Republican date not recognized: '#{rep_date}'"

    var ncap = m.named_captures

    var day1   = Number(ncap{:ic}    ? i_cal_nums{ncap{:ic}}    : ncap{:day})
    var month1 = Number(ncap{:month} ? month_nums{ncap{:month}} : i_cal_month)
    var year1  = Number(ncap{:year})

    var days_since_epoch = (365*(year1 - 1) + 30*(month1 - 1) + (day1 - 1))

    var leap_days = (1 ..^ year1 -> grep { is_republican_leap_year(_) })
    epoch.clone.add(days => (days_since_epoch + leap_days)).strftime("%Y-%m-%d")
}

func Gregorian_to_Republican(String greg_date) -> String {
    var m = (greg_date =~ /^(\d{4})-(\d{2})-(\d{2})\z/)
    m || die "Gregorian date not recognized: '#{greg_date}'"

    var g = %O<DateTime>.new(year => m[0], month => m[1], day => m[2])
    var days_since_epoch = epoch.delta_days(g).in_units('days')
    days_since_epoch < 0 && die "unexpected error"
    var (year, days) = (1, days_since_epoch)

    loop {
        var year_length = (365 + (is_republican_leap_year(year) ? 1 : 0))
        days < year_length && break
        days -= year_length
        year += 1;
    }

    var day0   = (days % 30)
    var month0 = (days - day0)/30

    var (day1, month1) = (day0 + 1, month0 + 1)

    (month1 == i_cal_month
        ? "#{intercalary[day0]} #{year}"
        : "#{day1} #{month_names[month0]} #{year}")
}

for line in DATA {

    line.sub!(/\s*\#.+\R?\z/, '')

    var m = (line =~ /^(\d{4})-(\d{2})-(\d{2})\s+(\S.+?\S)\s*$/)
    m || die "error for: #{line.dump}"

    var g = "#{m[0]}-#{m[1]}-#{m[2]}"
    var r = m[3]

    var r2g = Republican_to_Gregorian(r)
    var g2r = Gregorian_to_Republican(g)

    #say "#{r} -> #{r2g}"
    #say "#{g} -> #{g2r}"

    if ((g2r != r) || (r2g != g)) {
        die "1-way error"
    }

    if ((Gregorian_to_Republican(r2g) != r) ||
        (Republican_to_Gregorian(g2r) != g)
    ) {
        die "2-way error"
    }
}

say 'All tests successful.'

__DATA__
1792-09-22  1 Vendémiaire 1
1795-05-20  1 Prairial 3
1799-07-15  27 Messidor 7
1803-09-23  Fête de la Révolution 11
1805-12-31  10 Nivôse 14
1871-03-18  27 Ventôse 79
1944-08-25  7 Fructidor 152
2016-09-19  Fête du travail 224
1871-05-06  16 Floréal 79   # Paris Commune begins
1871-05-23  3 Prairial 79   # Paris Commune ends
1799-11-09  18 Brumaire 8   # Revolution ends by Napoléon coup
1804-12-02  11 Frimaire 13  # Republic   ends by Napoléon coronation
1794-10-30  9 Brumaire 3    # École Normale Supérieure established
1794-07-27  9 Thermidor 2   # Robespierre falls
1799-05-27  8 Prairial 7    # Fromental Halévy born
1792-09-22  1 Vendémiaire 1
1793-09-22  1 Vendémiaire 2
1794-09-22  1 Vendémiaire 3
1795-09-23  1 Vendémiaire 4
1796-09-22  1 Vendémiaire 5
1797-09-22  1 Vendémiaire 6
1798-09-22  1 Vendémiaire 7
1799-09-23  1 Vendémiaire 8
1800-09-23  1 Vendémiaire 9
1801-09-23  1 Vendémiaire 10
1802-09-23  1 Vendémiaire 11
1803-09-24  1 Vendémiaire 12
1804-09-23  1 Vendémiaire 13
1805-09-23  1 Vendémiaire 14
1806-09-23  1 Vendémiaire 15
1807-09-24  1 Vendémiaire 16
1808-09-23  1 Vendémiaire 17
1809-09-23  1 Vendémiaire 18
1810-09-23  1 Vendémiaire 19
1811-09-24  1 Vendémiaire 20
2015-09-23  1 Vendémiaire 224
2016-09-22  1 Vendémiaire 225
2017-09-22  1 Vendémiaire 226


import "/date" for Date
import "/seq" for Lst
import "/fmt" for Fmt

class FrenchRCDate {
    /* uses the 'continuous method' for years after 1805 */
    static isLeapYear(y) {
        var yy = y + 1
         return (yy % 4 == 0) && (yy % 100 != 0 || yy % 400 == 0)
    }

    static parse(frcDate) {
        var splits = frcDate.trim().split(" ")
        if (splits.count == 3) {
            var month = Lst.indexOf(months, splits[1]) + 1
            if (month < 1 || month > 13) Fiber.abort("Invalid month.")
            var year = Num.fromString(splits[2])
            if (year < 1) Fiber.abort("Invalid year.")
            var monthLength = (month < 13) ? 30 : (isLeapYear(year) ? 6 : 5)
            var day = Num.fromString(splits[0])
            if (day < 1 || day > monthLength) Fiber.abort("Invalid day.")
            return FrenchRCDate.new(year, month, day)
        } else if (splits.count == 4 || splits.count == 5) {
            var yearStr = splits[-1]
            var year = Num.fromString(yearStr)
            if (year < 1) Fiber.abort("Invalid year.")
            var scDay = frcDate.trim()[0...-(yearStr.count + 1)]
            var day = Lst.indexOf(intercal, scDay) + 1
            var maxDay = isLeapYear(year) ? 6 : 5
            if (day < 1 || day > maxDay) Fiber.abort("Invalid day.")
            return FrenchRCDate.new(year, 13, day)
         } else Fiber.abort("Invalid French Republican date.")
    }

    /* for convenience we treat 'Sansculottide' as an extra month with 5 or 6 days */
    static months {
        return ["Vendémiaire", "Brumaire", "Frimaire", "Nivôse", "Pluviôse", "Ventôse", "Germinal",
                "Floréal", "Prairial", "Messidor", "Thermidor", "Fructidor", "Sansculottide"]
    }

    static intercal {
        return ["Fête de la vertu", "Fête du génie", "Fête du travail",
                "Fête de l'opinion", "Fête des récompenses", "Fête de la Révolution"]
    }

    static introductionDate { Date.new(1792, 9, 22) }

    /* year = 1..  month = 1..13  day = 1..30 */
    construct new(year, month, day) {
        if (year <= 0 || month < 1 || month > 13) Fiber.abort("Invalid date.")
        if (month < 13) {
            if (day < 1 || day > 30)  Fiber.abort("Invalid date.")
        } else {
            var leap = FrenchRCDate.isLeapYear(year)
            if (leap  && (day < 1 || day > 6)) Fiber.abort("Invalid date.")
            if (!leap && (day < 1 || day > 5)) Fiber.abort("Invalid date.")
        }
        _year = year
        _month = month
        _day = day
    }

    static fromLocalDate(ldate) {
        var daysDiff = (ldate - introductionDate).days + 1
        if (daysDiff <= 0) Fiber.abort("Date can't be before 22 September 1792.")
        var year = 1
        var startDay = 1
        while (true) {
            var endDay = startDay + (isLeapYear(year) ? 365 : 364)
            if (daysDiff >= startDay && daysDiff <= endDay) break
            year = year + 1
            startDay = endDay + 1
        }
        var remDays = daysDiff - startDay
        var month  = (remDays / 30).floor
        var day = remDays - month * 30
        return FrenchRCDate.new(year, month + 1, day + 1)
    }

    toString {
        if (_month < 13) return "%(_day) %(FrenchRCDate.months[_month - 1]) %(_year)"
        return "%(FrenchRCDate.intercal[_day - 1]) %(_year)"
    }

    toLocalDate {
        var sumDays = 0
        for (i in 1..._year) sumDays = sumDays + (FrenchRCDate.isLeapYear(i) ? 366 : 365)
        var dayInYear = (_month - 1) * 30 + _day - 1
        return FrenchRCDate.introductionDate.addDays(sumDays + dayInYear)
    }
}

var fmt = "d| |mmmm| |yyyy"
var dates = [
     "22 September 1792", "20 May 1795", "15 July 1799", "23 September 1803",
     "31 December 1805", "18 March 1871", "25 August 1944", "19 September 2016",
     "22 September 2017", "28 September 2017"
]
var frcDates = List.filled(dates.count, null)
var i = 0
for (date in dates) {
    var thisDate = Date.parse(date, fmt)
    var frcd = FrenchRCDate.fromLocalDate(thisDate)
    frcDates[i] = frcd.toString
    Fmt.print("$-25s => $s", date, frcd)
    i = i + 1
}

// now process the other way around
System.print()
for (frcDate in frcDates) {
    var thisDate = FrenchRCDate.parse(frcDate)
    var lds = thisDate.toLocalDate.format(fmt)
    Fmt.print("$-25s => $s", frcDate, lds)
}


AutoTrim Off
; User settings
bottompegs := 6
SleepTime  := 200
fallspace  := 30

; create the board
out := (pad2 := Space(bottompegs*2+1)) "`n"
Loop % bottompegs
{
	out .= Space(bottompegs-A_Index+1)
	Loop % A_Index
		out .= "* "
	out .= Space(bottompegs-A_Index+1) . "`n"
}
StringTrimRight, strboard, out, 1 ; remove last newline
Loop % fallspace-1
	strboard .= "`n" . pad2
strboard .= "`n"
Loop % bottompegs*2+1
	strboard .= "="

; Create Gui
Gui Font, , Consolas
Gui -Caption
Gui Margin, 0, 0
Gui, Add, edit, -VScroll vE, % strboard
Gui Show
Loop
{
	ballX := bottompegs+1, BallY := 1
	strboard := ChangeChar(strboard, BallX, ballY, "O")
	GuiControl,, E, % strboard
	sleep SleepTime
	; Make ball fall and bounce
	Loop % bottompegs
	{
		strboard := ChangeChar(strboard, BallX, BallY, " ")
		ballY += 1
		ballX += RandAdd()
		; MsgBox % ballX ", " ballY
		GuiControl,, E, % strboard := ChangeChar(strboard, ballX, ballY, "O")
		sleep SleepTime
	}
	; now fall to the bottom
	While GetChar(strboard, BallX, BallY+1) = A_Space
	{
		strboard := ChangeChar(strboard, BallX, BallY, " ")
		BallY += 1
		strboard := ChangeChar(strboard, BallX, BallY, "O")
		GuiControl,, E, % strboard
		sleep SleepTime
	}
}
~Esc::
GuiClose:
ExitApp

Space(n){
	If n
		return " " Space(n-1)
	return ""
}
RandAdd(){
	Random, n, 3, 4
	return (n=3 ? -1 : 1)
}

GetChar(s, x, y){
	Loop Parse, s, `n
		if (A_Index = y)
			return SubStr(A_LoopField, x, 1)
}
ChangeChar(s, x, y, c){
	Loop Parse, s, `n
	{
		If (A_Index = y)
		{
			Loop Parse, A_LoopField
				If (A_Index = x)
					out .= c
				else    out .= A_LoopField
		}
		else out .= A_LoopField
		     out .= "`n"
	}
	StringTrimRight, out, out, 1 ; removes the last newline
	return out
}


graphsize 150,125
fastgraphics
color black
rect 0,0,graphwidth,graphheight
refresh

N = 10		# number of balls
M = 5		# number of pins in last row
dim ball(N,5)	# (pos_x to center, level, x, y, direction}
dim cnt(M+1)

rad = 6
slow = 0.3
diamond = {0,rad,rad,0,0,-rad,-rad,0}
stepx = {rad/sqr(2),rad/2,rad/2,(1-1/sqr(2))*rad,0}
stepy = {(1-1/sqr(2))*rad,rad/2,rad/2,rad/sqr(2),rad}
CX = graphwidth/2 : CY = graphheight/2
iters = 0

# Draw pins
for i = 1 to M
	y = 3*rad*i
	for j = 1 to i
		dx = (j-i\2-1)*4*rad + ((i-1)%2)*2*rad
		color purple
		stamp CX+dx,y,1.0,diamond
		color darkpurple
		stamp CX+dx,y,0.6,diamond
	next j
next i
gosub saverefresh

R = 0 : C = 0
font "Tahoma",10,50
do
	# Release ball
	if R<N then
		R = R + 1
		ball[R-1,2] = CX : ball[R-1,3] = rad*(1-stepx[?]) : ball[R-1,4] = 0
		# How many balls are released
		color black
		text 5,5,(R-1)+" balls"
		color green
		text 5,5,(R)+" balls"
	end if
	# Animate balls on this step
	for it = 0 to stepx[?]-1
		for b = 0 to R-1
			gosub moveball
		next b
		gosub saverefresh
		pause slow/stepx[?]
	next it
	# Where to go on the next step?
	for b = 0 to R-1
		ball[b,1] = ball[b,1] + 1
		if ball[b,1]<=M then
			if rand>=0.5 then
				ball[b,4] = 1
			else
				ball[b,4] = -1
			end if
			ball[b,0] = ball[b,0] + ball[b,4]
		else
			if ball[b,4]<>0 then
				gosub eraseball
				i = (ball[b,0]+M)/2
				cnt[i] = cnt[i] + 1
				ball[b,4] = 0
				C = C + 1
			end if
		end if
	next b
	# Draw counter
	color green
	y = 3*rad*(M+1)
	for j = 0 to M
		dx = (j-(M+1)\2)*4*rad + (M%2)*2*rad
		stamp CX+dx,y,{-1.2*rad,0,1.2*rad,0,1.2*rad,2*cnt[j],-1.2*rad,2*cnt[j]}
	next j
	gosub saverefresh
until C >= N
end

moveball:
	if ball[b,1]>M then return
	gosub eraseball
	if ball[b,4]<>0.0 then
		ball[b,2] = ball[b,2]+ball[b,4]*stepx[it]
		ball[b,3] = ball[b,3]+stepy[it]
	else
		ball[b,3] = ball[b,3]+rad
	end if
	gosub drawball

drawball:
	color darkgreen
	circle ball[b,2],ball[b,3],rad-1
	color green
	circle ball[b,2],ball[b,3],rad-2
	return

eraseball:
	color black
	circle ball[b,2],ball[b,3],rad-1
	return

saverefresh:
	num$ = string(iters)
	for k = 1 to 4-length(num$)
		num$ = "0"+num$
	next k
	imgsave num$+"-Galton_box_BASIC-256.png", "PNG"
	iters = iters + 1
	refresh
	return


      maxBalls% = 10
      DIM ballX%(maxBalls%), ballY%(maxBalls%)

      VDU 23,22,180;400;8,16,16,128
      ORIGIN 180,0
      OFF

      REM Draw the pins:
      GCOL 9
      FOR row% = 1 TO 7
        FOR col% = 1 TO row%
          CIRCLE FILL 40*col% - 20*row% - 20, 800 - 40*row%, 12
        NEXT
      NEXT row%

      REM Animate
      last% = 0
      tick% = 0
      GCOL 3,3
      REPEAT
        IF RND(10) = 5 IF (tick% - last%) > 10 THEN
          FOR ball% = 1 TO maxBalls%
            IF ballY%(ball%) = 0 THEN
              ballX%(ball%) = 0
              ballY%(ball%) = 800
              last% = tick%
              EXIT FOR
            ENDIF
          NEXT
        ENDIF
        FOR ball% = 1 TO maxBalls%
          IF ballY%(ball%) CIRCLE FILL ballX%(ball%), ballY%(ball%), 12
          IF POINT(ballX%(ball%),ballY%(ball%)-10) = 12 OR ballY%(ball%) < 12 THEN
            IF ballY%(ball%) > 500 END
            ballY%(ball%) = 0
          ENDIF
        NEXT
        WAIT 2
        FOR ball% = 1 TO maxBalls%
          IF ballY%(ball%) THEN
            CIRCLE FILL ballX%(ball%), ballY%(ball%), 12
            ballY%(ball%) -= 4
            IF POINT(ballX%(ball%),ballY%(ball%)-10) = 9 THEN
              ballX%(ball%) += 40 * (RND(2) - 1.5)
            ENDIF
          ENDIF
        NEXT
        tick% += 1
      UNTIL FALSE


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BALLS 1024
int n, w, h = 45, *x, *y, cnt = 0;
char *b;

#define B(y, x) b[(y)*w + x]
#define C(y, x) ' ' == b[(y)*w + x]
#define V(i) B(y[i], x[i])
inline int rnd(int a) { return (rand()/(RAND_MAX/a))%a; }

void show_board()
{
	int i, j;
	for (puts("\033[H"), i = 0; i < h; i++, putchar('\n'))
		for (j = 0; j < w; j++, putchar(' '))
			printf(B(i, j) == '*' ?
				C(i - 1, j) ? "\033[32m%c\033[m" :
				"\033[31m%c\033[m" : "%c", B(i, j));
}

void init()
{
	int i, j;
	puts("\033[H\033[J");
	b = malloc(w * h);
	memset(b, ' ', w * h);

	x = malloc(sizeof(int) * BALLS * 2);
	y = x + BALLS;

	for (i = 0; i < n; i++)
		for (j = -i; j <= i; j += 2)
			B(2 * i+2, j + w/2) = '*';
	srand(time(0));
}

void move(int idx)
{
	int xx = x[idx], yy = y[idx], c, kill = 0, sl = 3, o = 0;

	if (yy < 0) return;
	if (yy == h - 1) { y[idx] = -1; return; }

	switch(c = B(yy + 1, xx)) {
	case ' ':	yy++; break;
	case '*':	sl = 1;
	default:	if (xx < w - 1 && C(yy, xx + 1) && C(yy + 1, xx + 1))
				if (!rnd(sl++)) o = 1;
			if (xx && C(yy, xx - 1) && C(yy + 1, xx - 1))
				if (!rnd(sl++)) o = -1;
			if (!o) kill = 1;
			xx += o;
	}

	c = V(idx); V(idx) = ' ';
	idx[y] = yy, idx[x] = xx;
	B(yy, xx) = c;
	if (kill) idx[y] = -1;
}

int run(void)
{
	static int step = 0;
	int i;
	for (i = 0; i < cnt; i++) move(i);
	if (2 == ++step && cnt < BALLS) {
		step = 0;
		x[cnt] = w/2;
		y[cnt] = 0;
		if (V(cnt) != ' ') return 0;
		V(cnt) = rnd(80) + 43;
		cnt++;
	}
	return 1;
}

int main(int c, char **v)
{
	if (c < 2 || (n = atoi(v[1])) <= 3) n = 5;
	if (n >= 20) n = 20;
	w = n * 2 + 1;
	init();

	do { show_board(), usleep(60000); } while (run());

	return 0;
}


#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

const int BMP_WID = 410, BMP_HEI = 230, MAX_BALLS = 120;

class myBitmap {
public:
    myBitmap() : pen( NULL ), brush( NULL ), clr( 0 ), wid( 1 ) {}
    ~myBitmap() {
        DeleteObject( pen ); DeleteObject( brush );
        DeleteDC( hdc ); DeleteObject( bmp );
    }
    bool create( int w, int h ) {
        BITMAPINFO bi;
        ZeroMemory( &bi, sizeof( bi ) );
        bi.bmiHeader.biSize        = sizeof( bi.bmiHeader );
        bi.bmiHeader.biBitCount    = sizeof( DWORD ) * 8;
        bi.bmiHeader.biCompression = BI_RGB;
        bi.bmiHeader.biPlanes      = 1;
        bi.bmiHeader.biWidth       =  w;
        bi.bmiHeader.biHeight      = -h;

        HDC dc = GetDC( GetConsoleWindow() );
        bmp = CreateDIBSection( dc, &bi, DIB_RGB_COLORS, &pBits, NULL, 0 );
        if( !bmp ) return false;
        hdc = CreateCompatibleDC( dc );
        SelectObject( hdc, bmp );
        ReleaseDC( GetConsoleWindow(), dc );
        width = w; height = h;
        return true;
    }
    void clear( BYTE clr = 0 ) {
        memset( pBits, clr, width * height * sizeof( DWORD ) );
    }
    void setBrushColor( DWORD bClr ) {
        if( brush ) DeleteObject( brush );
        brush = CreateSolidBrush( bClr );
        SelectObject( hdc, brush );
    }
    void setPenColor( DWORD c ) {
        clr = c; createPen();
    }
    void setPenWidth( int w ) {
        wid = w; createPen();
    }
    HDC getDC() const     { return hdc; }
    int getWidth() const  { return width; }
    int getHeight() const { return height; }
private:
    void createPen() {
        if( pen ) DeleteObject( pen );
        pen = CreatePen( PS_SOLID, wid, clr );
        SelectObject( hdc, pen );
    }
    HBITMAP bmp;
    HDC     hdc;
    HPEN    pen;
    HBRUSH  brush;
    void    *pBits;
    int     width, height, wid;
    DWORD   clr;
};
class point {
public:
    int x; float y;
    void set( int a, float b ) { x = a; y = b; }
};
typedef struct {
    point position, offset;
    bool alive, start;
}ball;
class galton {
public :
    galton() {
        bmp.create( BMP_WID, BMP_HEI );
        initialize();
    }
    void setHWND( HWND hwnd ) { _hwnd = hwnd; }
    void simulate() {
        draw(); update(); Sleep( 1 );
    }
private:
    void draw() {
        bmp.clear();
        bmp.setPenColor( RGB( 0, 255, 0 ) );
        bmp.setBrushColor( RGB( 0, 255, 0 ) );
        int xx, yy;
        for( int y = 3; y < 14; y++ ) {
            yy = 10 * y;
            for( int x = 0; x < 41; x++ ) {
                xx = 10 * x;
                if( pins[y][x] )
                    Rectangle( bmp.getDC(), xx - 3, yy - 3, xx + 3, yy + 3 );
            }
        }
        bmp.setPenColor( RGB( 255, 0, 0 ) );
        bmp.setBrushColor( RGB( 255, 0, 0 ) );
        ball* b;
        for( int x = 0; x < MAX_BALLS; x++ ) {
            b = &balls[x];
            if( b->alive )
                Rectangle( bmp.getDC(), static_cast<int>( b->position.x - 3 ), static_cast<int>( b->position.y - 3 ),
                                        static_cast<int>( b->position.x + 3 ), static_cast<int>( b->position.y + 3 ) );
        }
        for( int x = 0; x < 70; x++ ) {
            if( cols[x] > 0 ) {
                xx = 10 * x;
                Rectangle( bmp.getDC(), xx - 3, 160, xx + 3, 160 + cols[x] );
            }
        }
        HDC dc = GetDC( _hwnd );
        BitBlt( dc, 0, 0, BMP_WID, BMP_HEI, bmp.getDC(), 0, 0, SRCCOPY );
        ReleaseDC( _hwnd, dc );
    }
    void update() {
        ball* b;
        for( int x = 0; x < MAX_BALLS; x++ ) {
            b = &balls[x];
            if( b->alive ) {
                b->position.x += b->offset.x; b->position.y += b->offset.y;
                if( x < MAX_BALLS - 1 && !b->start && b->position.y > 50.0f ) {
                    b->start = true;
                    balls[x + 1].alive = true;
                }
                int c = ( int )b->position.x, d = ( int )b->position.y + 6;
                if( d > 10 || d < 41 ) {
                    if( pins[d / 10][c / 10] ) {
                        if( rand() % 30 < 15 ) b->position.x -= 10;
                        else b->position.x += 10;
                    }
                }
                if( b->position.y > 160 ) {
                    b->alive = false;
                    cols[c / 10] += 1;
                }
            }
        }
    }
    void initialize() {
        for( int x = 0; x < MAX_BALLS; x++ ) {
            balls[x].position.set( 200, -10 );
            balls[x].offset.set( 0, 0.5f );
            balls[x].alive = balls[x].start = false;
        }
        balls[0].alive = true;
        for( int x = 0; x < 70; x++ )
            cols[x] = 0;
        for( int y = 0; y < 70; y++ )
            for( int x = 0; x < 41; x++ )
                pins[x][y] = false;
        int p;
        for( int y = 0; y < 11; y++ ) {
            p = ( 41 / 2 ) - y;
            for( int z = 0; z < y + 1; z++ ) {
                pins[3 + y][p] = true;
                p += 2;
            }
        }
    }
    myBitmap bmp;
    HWND _hwnd;
    bool pins[70][40];
    ball balls[MAX_BALLS];
    int cols[70];
};
class wnd {
public:
    int wnd::Run( HINSTANCE hInst ) {
        _hInst = hInst;
        _hwnd = InitAll();
        _gtn.setHWND( _hwnd );
        ShowWindow( _hwnd, SW_SHOW );
        UpdateWindow( _hwnd );
        MSG msg;
        ZeroMemory( &msg, sizeof( msg ) );
        while( msg.message != WM_QUIT ) {
            if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) != 0 ) {
                TranslateMessage( &msg );
                DispatchMessage( &msg );
            } else _gtn.simulate();
        }
        return UnregisterClass( "_GALTON_", _hInst );
    }
private:
    static int WINAPI wnd::WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
        switch( msg ) {
            case WM_DESTROY: PostQuitMessage( 0 ); break;
            default:
                return static_cast<int>( DefWindowProc( hWnd, msg, wParam, lParam ) );
        }
        return 0;
    }
    HWND InitAll() {
        WNDCLASSEX wcex;
        ZeroMemory( &wcex, sizeof( wcex ) );
        wcex.cbSize           = sizeof( WNDCLASSEX );
        wcex.style           = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc   = ( WNDPROC )WndProc;
        wcex.hInstance     = _hInst;
        wcex.hCursor       = LoadCursor( NULL, IDC_ARROW );
        wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
        wcex.lpszClassName = "_GALTON_";
        RegisterClassEx( &wcex );
        RECT rc;
        SetRect( &rc, 0, 0, BMP_WID, BMP_HEI );
        AdjustWindowRect( &rc, WS_CAPTION, FALSE );
        return CreateWindow( "_GALTON_", ".: Galton Box -- PJorente :.", WS_SYSMENU, CW_USEDEFAULT, 0, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, _hInst, NULL );
    }
    HINSTANCE _hInst;
    HWND      _hwnd;
    galton    _gtn;
};
int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow ) {
    srand( GetTickCount() );
    wnd myWnd;
    return myWnd.Run( hInstance );
}


import std.stdio, std.algorithm, std.random, std.array;

enum int boxW = 41, boxH = 37; // Galton box width and height.
enum int pinsBaseW = 19;       // Pins triangle base size.
enum int nMaxBalls = 55;       // Number of balls.

static assert(boxW >= 2 && boxH >= 2);
static assert((boxW - 4) >= (pinsBaseW * 2 - 1));
static assert((boxH - 3) >= pinsBaseW);
enum centerH = pinsBaseW + (boxW - (pinsBaseW * 2 - 1)) / 2 - 1;

enum Cell : char { empty  = ' ',
                   ball   = 'o',
                   wall   = '|',
                   corner = '+',
                   floor  = '-',
                   pin    = '.' }

Cell[boxW][boxH] box; // Galton box. Will be printed upside-down.

struct Ball {
    int x, y; // Position.

    this(in int x_, in int y_) nothrow @safe @nogc
    in {
        assert(box[y_][x_] == Cell.empty);
    } body {
        this.x = x_;
        this.y = y_;
        box[y][x] = Cell.ball;
    }

    nothrow const @safe @nogc invariant {
        assert(x >= 0 && x < boxW && y >= 0 && y < boxH);
        assert(box[y][x] == Cell.ball);
    }

    void doStep() {
        if (y <= 0)
            return; // Reached the bottom of the box.

        final switch (box[y - 1][x]) with (Cell) {
            case empty:
                box[y][x] = Cell.empty;
                y--;
                box[y][x] = Cell.ball;
                break;
            case ball, wall, corner, floor:
                // It's frozen. (It always piles on other balls).
                break;
            case pin:
                box[y][x] = Cell.empty;
                y--;
                if (box[y][x - 1] == Cell.empty && box[y][x + 1] == Cell.empty) {
                    x += uniform(0, 2) * 2 - 1;
                    box[y][x] = Cell.ball;
                    return;
                } else if (box[y][x - 1] == Cell.empty) {
                    x++;
                } else {
                    x--;
                }
                box[y][x] = Cell.ball;
                break;
        }
    }
}

void initializeBox() {
    // Set ceiling and floor:
    box[0][] = Cell.corner ~ [Cell.floor].replicate(boxW - 2) ~ Cell.corner;
    box[$ - 1][] = box[0][];

    // Set walls:
    foreach (immutable r; 1 .. boxH - 1)
        box[r][0] = box[r][$ - 1] = Cell.wall;

    // Set pins:
    foreach (immutable nPins; 1 .. pinsBaseW + 1)
        foreach (pin; 0 .. nPins)
            box[boxH - 2 - nPins][centerH + 1 - nPins + pin * 2] = Cell.pin;
}

void drawBox() {
    foreach_reverse (const ref row; box)
        writefln("%(%c%)", row);
}

void main() {
    initializeBox;
    Ball[] balls;

    foreach (const i; 0 .. nMaxBalls + boxH) {
        writefln("\nStep %d:", i);
        if (i < nMaxBalls)
            balls ~= Ball(centerH, boxH - 2); // Add ball.
        drawBox;

        // Next step for the simulation.
        // Frozen balls are kept in balls array for simplicity.
        foreach (ref b; balls)
            b.doStep;
    }
}


import Html.App exposing (program)
import Time exposing (Time, every, millisecond)
import Color exposing (Color, black, red, blue, green)
import Collage exposing (collage)
import Collage exposing (collage,polygon, filled, move, Form, circle)
import Element exposing (toHtml)
import Html exposing (Attribute, Html, text, div, input, button)
import Html.Attributes as A exposing (type', min, placeholder, value, style, disabled)
import Html.Events exposing (onInput, targetValue, onClick)
import Dict exposing (Dict, get, insert)
import String exposing (toInt)
import Result exposing (withDefault)
import Random.Pcg as Random exposing (Seed, bool, initialSeed, independentSeed, step, map)

width = 500
height = 600
hscale = 10.0
vscale = hscale * 2
margin = 30
levelCount = 12
radius = hscale/ 2.0

type State = InBox Int Int Seed | Falling Int Float Float Float | Landed Int Float

type Coin = Coin State Color

colorCycle : Int -> Color
colorCycle i =
  case i % 3 of
    0 -> red
    1 -> blue
    _ -> green

initCoin : Int -> Seed -> Coin
initCoin indx seed = Coin (InBox 0 0 seed) (colorCycle indx)

drawCoin : Coin -> Form
drawCoin (Coin state color) =
  let dropLevel = toFloat (height//2 - margin)
      (level, shift, distance) =
        case state of
          InBox level shift seed -> (level, shift, 0)
          Falling shift distance _ _-> (levelCount, shift, distance)
          Landed shift distance -> (levelCount, shift, distance)
      position =
        (             hscale * toFloat shift
        , dropLevel - vscale * (toFloat level) - distance + radius / 2.0)

  in radius |> circle |> filled color |> move position

drawGaltonBox : List Form
drawGaltonBox =
  let levels = [0..levelCount-1]

      -- doubles :
      -- [0,2,4,6,8...]
      doubles = List.map (\n -> 2 * n) levels

      -- sequences :
      -- [[0], [0,2], [0,2,4], [0,2,4,6], [0,2,4,6,8],...]
      sequences = case List.tail (List.scanl (::) [] (doubles)) of
        Nothing -> []
        Just ls -> ls

      -- galtonCoords :
      -- [                            (0,0),
      --                       (-1,1),      (1,1),
      --                (-2,2),       (0,2),      (2,2),
      --         (-3,3),       (-1,3),      (1,3),      (3,3),
      --  (-4,4),       (-2,4),       (0,4),      (2,4),      (4,4), ...]
      galtonCoords =
        List.map2
          (\ls level -> List.map (\n -> (n - level, level)) ls)
          sequences
          levels
        |> List.concat

      peg = polygon [(0,0), (-4, -8), (4, -8)] |> filled black

      apex = toFloat (height//2 - margin)

  in List.map (\(x,y) -> move (hscale*toFloat x,  apex - vscale*toFloat y) peg) galtonCoords

coinsInBin : Int -> Dict Int Int -> Int
coinsInBin binNumber bins =
  case get binNumber bins of
    Nothing -> 0
    Just n -> n

addToBins : Int -> Dict Int Int -> Dict Int Int
addToBins binNumber bins =
  insert binNumber (coinsInBin binNumber bins + 1) bins

updateCoin : (Coin, Dict Int Int) -> (Coin, Dict Int Int)
updateCoin (Coin state color as coin, bins) =
  case state of
    InBox level shift seed ->
      let deltaShift = map (\b -> if b then 1 else -1) bool
          (delta, newSeed) = step deltaShift seed
          newShift = shift+delta
          newLevel = (level)+1
      in if (newLevel < levelCount) then
           (Coin (InBox newLevel newShift newSeed) color, bins)
         else -- transition to falling
           let maxDrop = toFloat (height - 2 * margin) - toFloat (levelCount) * vscale
               floor = maxDrop - toFloat (coinsInBin newShift bins) * (radius*2 + 1)
           in (Coin (Falling newShift -((vscale)/2.0) 10 floor) color, addToBins newShift bins)

    Falling shift distance velocity floor ->
      let newDistance = distance + velocity
      in if (newDistance < floor) then
           (Coin (Falling shift newDistance (velocity + 1) floor) color, bins)
         else -- transtion to landed
           (Coin (Landed shift floor) color, bins)

    Landed _ _ -> (coin, bins) -- unchanged

type alias Model =
  { coins : List Coin
  , bins : Dict Int Int
  , count : Int
  , started : Bool
  , seedInitialized : Bool
  , seed : Seed
  }

init : (Model, Cmd Msg)
init =
  ( { coins = []
    , bins = Dict.empty
    , count = 0
    , started = False
    , seedInitialized = False
    , seed = initialSeed 45 -- This will not get used.  Actual seed used is time dependent and set when the first coin drops.
    }, Cmd.none)

type Msg = Drop Time | Tick Time | SetCount String | Go

update : Msg -> Model -> (Model, Cmd Msg)
update action model =
  case action of
    Go ->
      ({model | started = model.count > 0}, Cmd.none)

    SetCount countString ->
      ({ model | count = toInt countString |> withDefault 0 }, Cmd.none)

    Drop t ->
      if (model.started && model.count > 0) then
          let newcount = model.count - 1
              seed' =  if model.seedInitialized then model.seed else initialSeed (truncate t)
              (seed'', coinSeed) = step independentSeed seed'
          in ({ model
              | coins = initCoin (truncate t) coinSeed :: model.coins
              , count = newcount
              , started = newcount > 0
              , seedInitialized = True
              , seed = seed''}, Cmd.none)
      else
         (model, Cmd.none)

    Tick _ ->
      -- foldr to execute update, append to coins, replace bins
      let (updatedCoins, updatedBins) =
        List.foldr (\coin (coinList, bins) ->
                       let (updatedCoin, updatedBins) = updateCoin (coin, bins)
                       in (updatedCoin :: coinList, updatedBins))
                   ([], model.bins)
                   model.coins
      in ({ model | coins = updatedCoins, bins = updatedBins}, Cmd.none)

view : Model -> Html Msg
view model =
  div []
    [ input
        [ placeholder "How many?"
        , let showString = if model.count > 0 then model.count |> toString else ""
          in value showString
        , onInput SetCount
        , disabled model.started
        , style [ ("height", "20px") ]
        , type' "number"
        , A.min "1"
        ]
        []

     , button
        [ onClick Go
        , disabled model.started
        , style [ ("height", "20px") ]
        ]
        [ Html.text "GO!" ]

     , let coinForms = (List.map (drawCoin) model.coins)
       in collage width height (coinForms ++ drawGaltonBox) |> toHtml
    ]

subscriptions model =
    Sub.batch
        [ every (40*millisecond) Tick
        , every (200*millisecond) Drop
        ]

main =
  program
      { init = init
      , view = view
      , update = update
      , subscriptions = subscriptions
      }


USING: accessors arrays calendar colors combinators
combinators.short-circuit fonts fry generalizations kernel
literals locals math math.ranges math.vectors namespaces opengl
random sequences timers ui ui.commands ui.gadgets
ui.gadgets.worlds ui.gestures ui.pens.solid ui.render ui.text ;
IN: rosetta-code.galton-box-animation

CONSTANT: pegs $[ 20 300 40 <range> ]
CONSTANT: speed 90
CONSTANT: balls 140
CONSTANT: peg-color  T{ rgba f 0.60 0.4 0.60 1.0 }
CONSTANT: ball-color T{ rgba f 0.80 1.0 0.20 1.0 }
CONSTANT: slot-color T{ rgba f 0.00 0.2 0.40 1.0 }
CONSTANT: bg-color   T{ rgba f 0.02 0.0 0.02 1.0 }

CONSTANT: font $[
    monospace-font
    t >>bold?
    T{ rgba f 0.80 1.0 0.20 1.0 } >>foreground
    T{ rgba f 0.02 0.0 0.02 1.0 } >>background
]

TUPLE: galton < gadget balls { frame initial: 1 } ;

DEFER: on-tick

: <galton-gadget> ( -- gadget )
    galton new bg-color <solid> >>interior V{ } clone >>balls
    dup [ on-tick ] curry f speed milliseconds <timer>
    start-timer ;

: add-ball ( gadget -- )
    dup frame>> balls <
    [ { 250 -20 } swap balls>> [ push ] keep ] when drop ;

: draw-msg ( -- )
    { 10 10 }
    [ font "Press <space> for new animation" draw-text ]
    with-translation ;

: draw-slots ( -- )
    slot-color gl-color { 70 350 } { 70 871 }
    10 [ 2dup gl-line [ { 40 0 } v+ ] [email protected] ] times 2drop
    { 70 871 } { 430 871 } gl-line ;

: diamond-side ( loc1 loc2 loc3 -- )
    [ v+ dup ] [ v+ gl-line ] bi* ;

: draw-diamond ( loc color -- )
    gl-color {
        [ { 0 -10 } { 10 10 } ]
        [ { 10 0 } { -10 10 } ]
        [ { 0 10 } { -10 -10 } ]
        [ { -10 0 } { 10 -10 } ]
    } [ diamond-side ] map-compose cleave ;

: draw-peg-row ( loc n -- )
    <iota> [ 40 * 0 2array v+ peg-color draw-diamond ] with
    each ;

: draw-peg-triangle ( -- )
    { 250 40 } 1
    8 [ 2dup draw-peg-row [ { -20 40 } v+ ] dip 1 + ] times
    2drop ;

: draw-balls ( gadget -- )
    balls>> [ ball-color draw-diamond ] each ;

: rand-side ( loc -- loc' ) { { 20 20 } { -20 20 } } random v+ ;

:: collide? ( GADGET BALL -- ? )
    BALL second :> y
    BALL { 0 20 } v+ :> tentative
    { [ y 860 = ] [ tentative GADGET balls>> member? ] } 0|| ;

:: update-ball ( GADGET BALL -- BALL' )
    {
        { [ BALL second pegs member? ] [ BALL rand-side ] }
        { [ GADGET BALL collide? ] [ BALL ] }
        [ BALL { 0 20 } v+ ]
    } cond ;

: update-balls ( gadget -- )
    dup '[ [ _ swap update-ball ] map ] change-balls drop ;

: on-tick ( gadget -- )
    {
        [ dup frame>> odd? [ add-ball ] [ drop ] if ]
        [ relayout-1 ] [ update-balls ]
        [ [ 1 + ] change-frame drop ]
    } cleave ;

M: galton pref-dim* drop { 500 900 } ;

M: galton draw-gadget*
    draw-peg-triangle draw-msg draw-slots draw-balls ;

: com-new ( gadget -- ) V{ } clone >>balls 1 >>frame drop ;

galton "gestures" f {
    { T{ key-down { sym " " } } com-new }
} define-command-map

MAIN-WINDOW: galton-box-animation
    {
        { title "Galton Box Animation" }
        { window-controls
            { normal-title-bar close-button minimize-button } }
    } <galton-gadget> >>gadgets ;


package main

import (
    "fmt"
    "math/rand"
    "time"
)

const boxW = 41      // Galton box width
const boxH = 37      // Galton box height.
const pinsBaseW = 19 // Pins triangle base.
const nMaxBalls = 55 // Number of balls.

const centerH = pinsBaseW + (boxW-pinsBaseW*2+1)/2 - 1

const (
    empty  = ' '
    ball   = 'o'
    wall   = '|'
    corner = '+'
    floor  = '-'
    pin    = '.'
)

type Ball struct{ x, y int }

func newBall(x, y int) *Ball {
    if box[y][x] != empty {
        panic("Tried to create a new ball in a non-empty cell. Program terminated.")
    }
    b := Ball{x, y}
    box[y][x] = ball
    return &b
}

func (b *Ball) doStep() {
    if b.y <= 0 {
        return // Reached the bottom of the box.
    }
    cell := box[b.y-1][b.x]
    switch cell {
    case empty:
        box[b.y][b.x] = empty
        b.y--
        box[b.y][b.x] = ball
    case pin:
        box[b.y][b.x] = empty
        b.y--
        if box[b.y][b.x-1] == empty && box[b.y][b.x+1] == empty {
            b.x += rand.Intn(2)*2 - 1
            box[b.y][b.x] = ball
            return
        } else if box[b.y][b.x-1] == empty {
            b.x++
        } else {
            b.x--
        }
        box[b.y][b.x] = ball
    default:
        // It's frozen - it always piles on other balls.
    }
}

type Cell = byte

/* Galton box. Will be printed upside down. */
var box [boxH][boxW]Cell

func initializeBox() {
    // Set ceiling and floor
    box[0][0] = corner
    box[0][boxW-1] = corner
    for i := 1; i < boxW-1; i++ {
        box[0][i] = floor
    }
    for i := 0; i < boxW; i++ {
        box[boxH-1][i] = box[0][i]
    }

    // Set walls
    for r := 1; r < boxH-1; r++ {
        box[r][0] = wall
        box[r][boxW-1] = wall
    }

    // Set rest to empty initially
    for i := 1; i < boxH-1; i++ {
        for j := 1; j < boxW-1; j++ {
            box[i][j] = empty
        }
    }

    // Set pins
    for nPins := 1; nPins <= pinsBaseW; nPins++ {
        for p := 0; p < nPins; p++ {
            box[boxH-2-nPins][centerH+1-nPins+p*2] = pin
        }
    }
}

func drawBox() {
    for r := boxH - 1; r >= 0; r-- {
        for c := 0; c < boxW; c++ {
            fmt.Printf("%c", box[r][c])
        }
        fmt.Println()
    }
}

func main() {
    rand.Seed(time.Now().UnixNano())
    initializeBox()
    var balls []*Ball
    for i := 0; i < nMaxBalls+boxH; i++ {
        fmt.Println("\nStep", i, ":")
        if i < nMaxBalls {
            balls = append(balls, newBall(centerH, boxH-2)) // add ball
        }
        drawBox()

        // Next step for the simulation.
        // Frozen balls are kept in balls slice for simplicity
        for _, b := range balls {
            b.doStep()
        }
    }
}


import Data.Map hiding (map, filter)
import Graphics.Gloss
import Control.Monad.Random

data Ball = Ball { position :: (Int, Int), turns :: [Int] }

type World = ( Int           -- number of rows of pins
             , [Ball]        -- sequence of balls
             , Map Int Int ) -- counting bins

updateWorld :: World -> World
updateWorld (nRows, balls, bins)
  | y < -nRows-5  = (nRows, map update bs, bins <+> x)
  | otherwise     = (nRows, map update balls, bins)
  where
    (Ball (x,y) _) : bs = balls

    b <+> x = unionWith (+) b (singleton x 1)

    update (Ball (x,y) turns)
      | -nRows <= y && y < 0 = Ball (x + head turns, y - 1) (tail turns)
      | otherwise            = Ball (x, y - 1) turns

drawWorld :: World -> Picture
drawWorld (nRows, balls, bins) = pictures [ color red ballsP
                                          , color black binsP
                                          , color blue pinsP ]
  where ballsP = foldMap (disk 1) $ takeWhile ((3 >).snd) $ map position balls
        binsP  = foldMapWithKey drawBin bins
        pinsP  = foldMap (disk 0.2) $ [1..nRows] >>= \i ->
                                          [1..i] >>= \j -> [(2*j-i-1, -i-1)]

        disk r pos = trans pos $ circleSolid (r*10)
        drawBin x h = trans (x,-nRows-7)
                    $ rectangleUpperSolid 20 (-fromIntegral h)
        trans (x,y) = Translate (20 * fromIntegral x) (20 * fromIntegral y)

startSimulation :: Int -> [Ball] -> IO ()
startSimulation nRows balls = simulate display white 50 world drawWorld update
  where display = InWindow "Galton box" (400, 400) (0, 0)
        world = (nRows, balls, empty)
        update _ _ = updateWorld

main = evalRandIO balls >>= startSimulation 10
  where balls = mapM makeBall [1..]
        makeBall y = Ball (0, y) <$> randomTurns
        randomTurns = filter (/=0) <$> getRandomRs (-1, 1)


link graphics

global pegsize, pegsize2, height, width, delay

procedure main(args)    # galton box simulation from Unicon book
   pegsize2 := (pegsize := 10) * 2    # pegs & steps
   delay := 2                         # ms delay
   setup_galtonwindow(pegsize)
   n := integer(args[1]) | 100        # balls to drop
   every 1 to n do galton(pegsize)
   WDone()
end

procedure setup_galtonwindow(n)  # Draw n levels of pegs,
local xpos, ypos, i, j
   # Pegboard size is 2n-1 square
   # Expected max value of histogram is (n, n/2)/2^n
   # ... approximate with something simpler?

   height := n*n/2*pegsize + (width := (2*n+1)*pegsize)
   Window("size=" || width || "," || height, "fg=grayish-white")
   WAttrib("fg=dark-grey")
   every ypos := (i := 1 to n) * pegsize2 do {
      xpos := width/2 - (i - 1) * pegsize - pegsize/2 - pegsize2
      every 1 to i do
         FillArc(xpos +:= pegsize2, ypos, pegsize, pegsize)
      }
   WAttrib("fg=black","drawop=reverse")      # set drawing mode for balls
end

procedure galton(n)                          # drop a ball into the galton box
local xpos, ypos, oldx, oldy
   xpos := oldx := width/2 - pegsize/2
   ypos := oldy := pegsize
   every 1 to n do {                         # For every ball...
      xpos +:= ((?2 = 1) | -1) * pegsize     # +/- pegsize
      animate(.oldx, .oldy, oldx := xpos, oldy := ypos +:= pegsize2)
      }
   animate(xpos, ypos, xpos, ypos + 40)      # Now the ball falls ...
   animate(xpos, ypos+40, xpos, ypos + 200)  # ... to the floor
   draw_ball(xpos)                           # Record this ball
end

procedure animate(xfrom, yfrom, xto, yto)
   animate_actual(xfrom, yfrom, xto, yfrom, 4)
   animate_actual(xto, yfrom, xto, yto, 10)
end

procedure animate_actual(xfrom, yfrom, xto, yto, steps) # attribs already set
local x, y, xstep, ystep, lastx, lasty
   x -:= xstep := (xto - (x := xfrom))/steps
   y -:= ystep := (yto - (y := yfrom))/steps
   every 1 to steps do {
      FillArc(lastx := x +:= xstep, lasty := y +:= ystep, pegsize, pegsize)
      WDelay(delay)      # wait in ms
      FillArc(x, y, pegsize, pegsize)
      }
end

procedure draw_ball(x)
static ballcounts
initial ballcounts := table(0)
   FillArc(x, height-(ballcounts[x] +:= 1)*pegsize, pegsize, pegsize)
end


import java.util.Random;
import java.util.List;
import java.util.ArrayList;

public class GaltonBox {
    public static void main( final String[] args ) {
        new GaltonBox( 8, 200 ).run();
    }

    private final int        m_pinRows;
    private final int        m_startRow;
    private final Position[] m_balls;
    private final Random     m_random = new Random();

    public GaltonBox( final int pinRows, final int ballCount ) {
        m_pinRows  = pinRows;
        m_startRow = pinRows + 1;
        m_balls    = new Position[ ballCount ];

        for ( int ball = 0; ball < ballCount; ball++ )
            m_balls[ ball ] = new Position( m_startRow, 0, 'o' );
    }

    private static class Position {
        int  m_row;
        int  m_col;
        char m_char;

        Position( final int row, final int col, final char ch ) {
            m_row  = row;
            m_col  = col;
            m_char = ch;
        }
    }

    public void run() {
        for ( int ballsInPlay = m_balls.length; ballsInPlay > 0;  ) {
            ballsInPlay = dropBalls();
            print();
        }
    }

    private int dropBalls() {
        int ballsInPlay = 0;
        int ballToStart = -1;

        // Pick a ball to start dropping
        for ( int ball = 0; ball < m_balls.length; ball++ )
            if ( m_balls[ ball ].m_row == m_startRow )
                ballToStart = ball;

        // Drop balls that are already in play
        for ( int ball = 0; ball < m_balls.length; ball++ )
            if ( ball == ballToStart ) {
                m_balls[ ball ].m_row = m_pinRows;
                ballsInPlay++;
            }
            else if ( m_balls[ ball ].m_row > 0 && m_balls[ ball ].m_row != m_startRow ) {
                m_balls[ ball ].m_row -= 1;
                m_balls[ ball ].m_col += m_random.nextInt( 2 );
                if ( 0 != m_balls[ ball ].m_row )
                    ballsInPlay++;
            }

        return ballsInPlay;
    }

    private void print() {
        for ( int row = m_startRow; row --> 1;  ) {
            for ( int ball = 0; ball < m_balls.length; ball++ )
                if ( m_balls[ ball ].m_row == row )
                    printBall( m_balls[ ball ] );
            System.out.println();
            printPins( row );
        }
        printCollectors();
        System.out.println();
    }

    private static void printBall( final Position pos ) {
        for ( int col = pos.m_row + 1; col --> 0;  )
            System.out.print( ' ' );
        for ( int col = 0; col < pos.m_col; col++ )
            System.out.print( "  " );
        System.out.print( pos.m_char );
    }

    private void printPins( final int row ) {
        for ( int col = row + 1; col --> 0;  )
            System.out.print( ' ' );
        for ( int col = m_startRow - row; col --> 0;  )
            System.out.print( ". " );
        System.out.println();
    }

    private void printCollectors() {
        final List<List<Position>> collectors = new ArrayList<List<Position>>();

        for ( int col = 0; col < m_startRow; col++ ) {
            final List<Position> collector = new ArrayList<Position>();

            collectors.add( collector );
            for ( int ball = 0; ball < m_balls.length; ball++ )
                if ( m_balls[ ball ].m_row == 0 && m_balls[ ball ].m_col == col )
                    collector.add( m_balls[ ball ] );
        }

        for ( int row = 0, rows = longest( collectors ); row < rows; row++ ) {
            for ( int col = 0; col < m_startRow; col++ ) {
                final List<Position> collector = collectors.get( col );
                final int            pos       = row + collector.size() - rows;

                System.out.print( '|' );
                if ( pos >= 0 )
                    System.out.print( collector.get( pos ).m_char );
                else
                    System.out.print( ' ' );
            }
            System.out.println( '|' );
        }
    }

    private static final int longest( final List<List<Position>> collectors ) {
        int result = 0;

        for ( final List<Position> collector : collectors )
            result = Math.max( collector.size(), result );

        return result;
    }
}


const readline = require('readline');

/**
 * Galton Box animation
 * @param {number} layers The number of layers in the board
 * @param {number} balls The number of balls to pass through
 */
const galtonBox = (layers, balls) => {
  const speed = 100;
  const ball = 'o';
  const peg = '.';
  const result = [];

  const sleep = ms => new Promise(resolve => {
    setTimeout(resolve,ms)
  });

  /**
   * The board is represented as a 2D array.
   * @type {Array<Array<string>>}
   */
  const board = [...Array(layers)]
      .map((e, i) => {
        const sides = Array(layers - i).fill(' ');
        const a = Array(i + 1).fill(peg).join(' ').split('');
        return [...sides, ...a, ...sides];
      });

  /**
   * @return {Array<string>}
   */
  const emptyRow = () => Array(board[0].length).fill(' ');

  /**
   * @param {number} i
   * @returns {number}
   */
  const bounce = i => Math.round(Math.random()) ? i - 1 : i + 1;

  /**
   * Prints the current state of the board and the collector
   */
  const show = () => {
    readline.cursorTo(process.stdout, 0, 0);
    readline.clearScreenDown(process.stdout);
    board.forEach(e => console.log(e.join('')));
    result.reverse();
    result.forEach(e => console.log(e.join('')));
    result.reverse();
  };

  /**
   * Collect the result.
   * @param {number} idx
   */
  const appendToResult = idx => {
    const row = result.find(e => e[idx] === ' ');
    if (row) {
      row[idx] = ball;
    } else {
      const newRow = emptyRow();
      newRow[idx] = ball;
      result.push(newRow);
    }
  };

  /**
   * Move the balls through the board
   * @returns {boolean} True if the there are balls in the board.
   */
  const iter = () => {
    let hasNext = false;
    [...Array(bordSize)].forEach((e, i) => {
      const rowIdx = (bordSize - 1) - i;
      const idx = board[rowIdx].indexOf(ball);
      if (idx > -1) {
        board[rowIdx][idx] = ' ';
        const nextRowIdx = rowIdx + 1;
        if (nextRowIdx < bordSize) {
          hasNext = true;
          const nextRow = board[nextRowIdx];
          nextRow[bounce(idx)] = ball;
        } else {
          appendToResult(idx);
        }
      }
    });
    return hasNext;
  };

  /**
   * Add a ball to the board.
   * @returns {number} The number of balls left to add.
   */
  const addBall = () => {
    board[0][apex] = ball;
    balls = balls - 1;
    return balls;
  };

  board.unshift(emptyRow());
  result.unshift(emptyRow());

  const bordSize = board.length;
  const apex = board[1].indexOf(peg);

  /**
   * Run the animation
   */
  (async () => {
    while (addBall()) {
      await sleep(speed).then(show);
      iter();
    }
    await sleep(speed).then(show);
    while (iter()) {
      await sleep(speed).then(show);
    }
    await sleep(speed).then(show);
  })();

};

galtonBox(12, 50);


using Random
function drawball(timer)
    global r, c, d
    print("\e[$r;$(c)H ")       # clear last ball position (r,c)
    if (r+=1) > 14
        close(timer)
        b = (bin[(c+2)>>2] += 1)# update count in bin
        print("\e[$b;$(c)Ho")   # lengthen bar of balls in bin
    else
        r in 3:2:13 && c in 17-r:4:11+r && (d = 2bitrand()-1)
        print("\e[$r;$(c+=d)Ho")# show ball moving in direction d
    end
end

print("\e[2J")                  # clear screen
for r = 3:2:13, c = 17-r:4:11+r # 6 pins in 6 rows
    print("\e[$r;$(c)H^")       # draw pins
end
print("\e[15;2H-------------------------")

bin = fill(15,7)                # positions of top of bins
while "x" != readline() >= ""   # x-Enter: exit, {keys..}Enter: next ball
    global r,c,d = 0,14,0
    t = Timer(drawball, 0, interval=0.1)
    while r < 15 sleep(0.01) end
    print("\e[40;1H")           # move cursor far down
end


// version 1.2.10

import java.util.Random

val boxW = 41       // Galton box width.
val boxH = 37       // Galton box height.
val pinsBaseW = 19  // Pins triangle base.
val nMaxBalls = 55  // Number of balls.

val centerH = pinsBaseW + (boxW - pinsBaseW * 2 + 1) / 2 - 1
val rand = Random()

enum class Cell(val c: Char) {
    EMPTY(' '),
    BALL('o'),
    WALL('|'),
    CORNER('+'),
    FLOOR('-'),
    PIN('.')
}

/* Galton box. Will be printed upside down. */
val box = List(boxH) { Array<Cell>(boxW) { Cell.EMPTY } }

class Ball(var x: Int, var y: Int) {

    init {
        require(box[y][x] == Cell.EMPTY)
        box[y][x] = Cell.BALL
    }

    fun doStep() {
        if (y <= 0) return  // Reached the bottom of the box.
        val cell = box[y - 1][x]
        when (cell) {
            Cell.EMPTY -> {
                box[y][x] = Cell.EMPTY
                y--
                box[y][x] = Cell.BALL
            }

            Cell.PIN -> {
                box[y][x] = Cell.EMPTY
                y--
                if (box[y][x - 1] == Cell.EMPTY && box[y][x + 1] == Cell.EMPTY) {
                    x += rand.nextInt(2) * 2 - 1
                    box[y][x] = Cell.BALL
                    return
                }
                else if (box[y][x - 1] == Cell.EMPTY) x++
                else x--
                box[y][x] = Cell.BALL
            }

            else -> {
                // It's frozen - it always piles on other balls.
            }
        }
    }
}

fun initializeBox() {
    // Set ceiling and floor:
    box[0][0] = Cell.CORNER
    box[0][boxW - 1] = Cell.CORNER
    for (i in 1 until boxW - 1) box[0][i] = Cell.FLOOR
    for (i in 0 until boxW) box[boxH - 1][i] = box[0][i]

    // Set walls:
    for (r in 1 until boxH - 1) {
        box[r][0] = Cell.WALL
        box[r][boxW - 1] = Cell.WALL
    }

    // Set pins:
    for (nPins in 1..pinsBaseW) {
        for (pin in 0 until nPins) {
            box[boxH - 2 - nPins][centerH + 1 - nPins + pin * 2] = Cell.PIN
        }
    }
}

fun drawBox() {
    for (row in box.reversed()) {
        for (i in row.indices) print(row[i].c)
        println()
    }
}

fun main(args: Array<String>) {
    initializeBox()
    val balls = mutableListOf<Ball>()
    for (i in 0 until nMaxBalls + boxH) {
        println("\nStep $i:")
        if (i < nMaxBalls) balls.add(Ball(centerH, boxH - 2))  // Add ball.
        drawBox()

        // Next step for the simulation.
        // Frozen balls are kept in balls list for simplicity
        for (b in balls) b.doStep()
    }
}