<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P098-washing-machine/ | updated: 2026-10-15 -->

# P098 Washing machine

Washing programs of timed stages run minute by minute, with pause and resume, a door that stays locked while there is water or the drum is turning, and power cuts that make the stage under way start again - the finish accounted for minute by minute.

EML-P project `projects/washing-machine` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/washing-machine` runs it and `eml project verify projects/washing-machine` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: washing-machine-cycle-simulator (https://efficientnewlanguage.org/cases/079-washing-machine-cycle-simulator/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
2
lizard
2

3
abc
3
5 dance
3
10 pause 5 resume
3
12
3
300 pause
3
0 open 1 open 2 open 3 open 4 open 5 open 6 open 7 open 8 open 9 open 10 open 11 open 12 open 13 open 14 open 15 open 16 open
3

3
0 resume 0 close 0 on 2 pause 3 pause 4 cut 5 cut 6 pause 7 resume 8 on 9 resume 10 resume
2
quick
3
5 pause
2
delicate
4
```

Screen:

```text
== Washing machine ==
Programs of timed stages, minute by minute - with pause, a door that stays locked while it is unsafe, and power cuts.

1) programs  2) run  3) events  4) quit
choice> 0
Pick a number from 1 to 4.

1) programs  2) run  3) events  4) quit
choice> x
Pick a number from 1 to 4.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> lizard
Type cotton, quick, delicate or spin.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> 
Cancelled.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> abc
Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 5 dance
Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 10 pause 5 resume
Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 12
Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 300 pause
Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 0 open 1 open 2 open 3 open 4 open 5 open 6 open 7 open 8 open 9 open 10 open 11 open 12 open 13 open 14 open 15 open 16 open
Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 
Cancelled.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 0 resume 0 close 0 on 2 pause 3 pause 4 cut 5 cut 6 pause 7 resume 8 on 9 resume 10 resume
Events (minute and action): 0 resume, 0 close, 0 on, 2 pause, 3 pause, 4 cut, 5 cut, 6 pause, 7 resume, 8 on, 9 resume, 10 resume.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> quick
The quick program, 31 minutes.
Events (minute and action): 0 resume, 0 close, 0 on, 2 pause, 3 pause, 4 cut, 5 cut, 6 pause, 7 resume, 8 on, 9 resume, 10 resume.
  minutes   stage                       water  door
    0-1     fill                        yes    locked
    2-3     paused in fill              yes    locked
    4-7     no power in fill            yes    locked
    8       paused in fill              yes    locked
    9-11    fill                        yes    locked
   12-23    wash                        yes    locked
   24-25    drain                       yes    locked
   26-28    rinse 1: fill               yes    locked
   29-32    rinse 1                     yes    locked
   33-34    rinse 1: drain              yes    locked
   35-39    spin                        no     locked
   40       finished                    no     locked
   41       finished                    no     closed
Minute 0, resume: not paused.
Minute 0, close: already closed.
Minute 0, on: the power is on.
Minute 2, pause: paused in fill.
Minute 3, pause: already paused.
Minute 4, cut: power off in fill: the 2 minutes done in it are lost.
Minute 5, cut: already off.
Minute 6, pause: the power is off.
Minute 7, resume: the power is off.
Minute 8, on: power back, still paused: fill will start again.
Minute 9, resume: fill starts, 3 minutes.
Minute 10, resume: not paused.
Finished at minute 40 = the program's 31 + 3 paused + 4 without power + 2 done again after a power cut.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 5 pause
Events (minute and action): 5 pause.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> delicate
The delicate program, 50 minutes.
Events (minute and action): 5 pause.
  minutes   stage                       water  door
    0-3     fill                        yes    locked
    4       wash                        yes    locked
    5-299   paused in wash              yes    locked
Minute 5, pause: paused in wash.
Not finished by minute 300.

1) programs  2) run  3) events  4) quit
choice> 4
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
2
quick
3
10 pause 11 open 14 resume 20 cut 23 on 36 pause 36 open 37 open 38 resume 38 close 39 resume 42 open 43 open
2
quick
3
30 cut 60 on
2
cotton
3
none
2
spin
4
```

Screen:

```text
== Washing machine ==
Programs of timed stages, minute by minute - with pause, a door that stays locked while it is unsafe, and power cuts.

1) programs  2) run  3) events  4) quit
choice> 1
Programs, in minutes:
  cotton     89 minutes: fill 4, wash 40, drain 2, 3 rinses (fill 3, rinse 6, drain 2 each), spin 10
  quick      31 minutes: fill 3, wash 12, drain 2, 1 rinse (fill 3, rinse 4, drain 2), spin 5
  delicate   50 minutes: fill 4, wash 20, drain 2, 2 rinses (fill 3, rinse 5, drain 2 each), spin 4
  spin       10 minutes: spin 10
The door stays locked while the program runs, while there is water in the drum, and for a minute after the drum stops.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> quick
The quick program, 31 minutes.
No events: the program runs straight through.
  minutes   stage                       water  door
    0-2     fill                        yes    locked
    3-14    wash                        yes    locked
   15-16    drain                       yes    locked
   17-19    rinse 1: fill               yes    locked
   20-23    rinse 1                     yes    locked
   24-25    rinse 1: drain              yes    locked
   26-30    spin                        no     locked
   31       finished                    no     locked
   32       finished                    no     closed
Finished at minute 31, the program's 31.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 10 pause 11 open 14 resume 20 cut 23 on 36 pause 36 open 37 open 38 resume 38 close 39 resume 42 open 43 open
Events (minute and action): 10 pause, 11 open, 14 resume, 20 cut, 23 on, 36 pause, 36 open, 37 open, 38 resume, 38 close, 39 resume, 42 open, 43 open.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> quick
The quick program, 31 minutes.
Events (minute and action): 10 pause, 11 open, 14 resume, 20 cut, 23 on, 36 pause, 36 open, 37 open, 38 resume, 38 close, 39 resume, 42 open, 43 open.
  minutes   stage                       water  door
    0-2     fill                        yes    locked
    3-9     wash                        yes    locked
   10-13    paused in wash              yes    locked
   14-18    wash                        yes    locked
   19       drain                       yes    locked
   20-22    no power in drain           yes    locked
   23-24    drain                       yes    locked
   25-27    rinse 1: fill               yes    locked
   28-31    rinse 1                     yes    locked
   32-33    rinse 1: drain              yes    locked
   34-35    spin                        no     locked
   36       paused in spin              no     locked
   37       paused in spin              no     open
   38       paused in spin              no     closed
   39-41    spin                        no     locked
   42       finished                    no     locked
   43       finished                    no     open
Minute 10, pause: paused in wash.
Minute 11, open: stays locked - water in the drum.
Minute 14, resume: wash goes on, 5 minutes left in it.
Minute 20, cut: power off in drain: the 1 minute done in it is lost.
Minute 23, on: power back: drain starts again.
Minute 36, pause: paused in spin.
Minute 36, open: stays locked - the drum is still turning.
Minute 37, open: the door opens.
Minute 38, resume: close the door first.
Minute 38, close: the door closes.
Minute 39, resume: spin goes on, 3 minutes left in it.
Minute 42, open: stays locked - the drum is still turning.
Minute 43, open: the door opens.
Finished at minute 42 = the program's 31 + 7 paused + 3 without power + 1 done again after a power cut.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> 30 cut 60 on
Events (minute and action): 30 cut, 60 on.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> cotton
The cotton program, 89 minutes.
Events (minute and action): 30 cut, 60 on.
  minutes   stage                       water  door
    0-3     fill                        yes    locked
    4-29    wash                        yes    locked
   30-59    no power in wash            yes    locked
   60-99    wash                        yes    locked
  100-101   drain                       yes    locked
  102-104   rinse 1: fill               yes    locked
  105-110   rinse 1                     yes    locked
  111-112   rinse 1: drain              yes    locked
  113-115   rinse 2: fill               yes    locked
  116-121   rinse 2                     yes    locked
  122-123   rinse 2: drain              yes    locked
  124-126   rinse 3: fill               yes    locked
  127-132   rinse 3                     yes    locked
  133-134   rinse 3: drain              yes    locked
  135-144   spin                        no     locked
  145       finished                    no     locked
  146       finished                    no     closed
Minute 30, cut: power off in wash: the 26 minutes done in it are lost.
Minute 60, on: power back: wash starts again.
Finished at minute 145 = the program's 89 + 30 without power + 26 done again after a power cut.

1) programs  2) run  3) events  4) quit
choice> 3
Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.
events> none
No events.

1) programs  2) run  3) events  4) quit
choice> 2
program (cotton, quick, delicate or spin)> spin
The spin program, 10 minutes.
No events: the program runs straight through.
  minutes   stage                       water  door
    0-9     spin                        no     locked
   10       finished                    no     locked
   11       finished                    no     closed
Finished at minute 10, the program's 10.

1) programs  2) run  3) events  4) quit
choice> 4
Bye.
```

## Modules

### main.eml

```eml
# P098 washing machine: programs of timed stages, run minute by minute,
# with pause and resume, a door that stays locked while it is unsafe, and
# power cuts.
import washer

def plain(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def pad(s, n):
    while len(s) < n:
        s + " " => s
    return s

def lpad(s, n):
    while len(s) < n:
        " " + s => s
    return s

def describe(name):
    washer.program(name) => p
    washer.stages(p) => st
    "" => line
    if p[0] > 0:
        "fill " + str(p[0]) + ", wash " + str(p[1]) + ", drain " + str(p[2]) + ", " => line
    if p[3] == 1:
        line + "1 rinse (fill " + str(p[4]) + ", rinse " + str(p[5]) + ", drain " + str(p[6]) + "), " => line
    elif p[3] > 1:
        line + str(p[3]) + " rinses (fill " + str(p[4]) + ", rinse " + str(p[5]) + ", drain " + str(p[6]) + " each), " => line
    line + "spin " + str(p[7]) => line
    ("  " + pad(name, 10) + lpad(str(washer.total(st)), 3) + " minutes: " + line) ^0

def show_programs():
    "Programs, in minutes:" ^0
    for name in ["cotton", "quick", "delicate", "spin"]:
        describe(name)
    "The door stays locked while the program runs, while there is water in the drum, and for a minute after the drum stops." ^0

def show_events(events):
    if len(events) == 0:
        "No events: the program runs straight through." ^0
        return
    "" => line
    for ev in events:
        if line != "":
            line + ", " => line
        line + str(ev[0]) + " " + ev[1] => line
    ("Events (minute and action): " + line + ".") ^0

def run(events):
    plain(input("program (cotton, quick, delicate or spin)> ")) => name
    if name == "":
        "Cancelled." ^0
        return
    washer.program(name) => p
    if len(p) == 0:
        "Type cotton, quick, delicate or spin." ^0
        return
    washer.stages(p) => st
    washer.total(st) => need
    washer.simulate(st, events, 300) => r
    ("The " + name + " program, " + washer.minutes(need) + ".") ^0
    show_events(events)
    ("  " + pad("minutes", 10) + pad("stage", 28) + pad("water", 7) + "door") ^0
    0 => first
    r[0] => tl
    for t in [1:len(tl)]:
        if t == len(tl) or tl[t][0] != tl[first][0] or tl[t][1] != tl[first][1] or tl[t][2] != tl[first][2]:
            "     " => span
            if t - 1 > first:
                "-" + pad(str(t - 1), 4) => span
            "no" => water
            if tl[first][1]:
                "yes" => water
            ("  " + lpad(str(first), 3) + span + "  " + pad(tl[first][0], 28) + pad(water, 7) + tl[first][2]) ^0
            t => first
    for n in r[1]:
        ("Minute " + str(n[0]) + ", " + n[1] + ": " + n[2] + ".") ^0
    if r[2] == -1:
        ("Not finished by minute 300.") ^0
        return
    "" => extra
    if r[3] > 0:
        extra + " + " + str(r[3]) + " paused" => extra
    if r[4] > 0:
        extra + " + " + str(r[4]) + " without power" => extra
    if r[5] > 0:
        extra + " + " + str(r[5]) + " done again after a power cut" => extra
    if extra == "":
        ("Finished at minute " + str(r[2]) + ", the program's " + str(need) + ".") ^0
    else:
        ("Finished at minute " + str(r[2]) + " = the program's " + str(need) + extra + ".") ^0

def set_events(events):
    "Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none." ^0
    plain(input("events> ")) => t
    if t == "":
        "Cancelled." ^0
        return events
    if t == "none":
        "No events." ^0
        return []
    washer.parse_events(t) => out
    if len(out) == 0:
        "Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order." ^0
        return events
    show_events(out)
    return out

[] => events

"== Washing machine ==" ^0
"Programs of timed stages, minute by minute - with pause, a door that stays locked while it is unsafe, and power cuts." ^0
True => running
while running:
    "" ^0
    "1) programs  2) run  3) events  4) quit" ^0
    plain(input("choice> ")) => choice
    if choice == "1":
        show_programs()
    elif choice == "2":
        run(events)
    elif choice == "3":
        set_events(events) => events
    elif choice == "4":
        False => running
    else:
        "Pick a number from 1 to 4." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import washer

def plain(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def pad(s, n):
    while len(s) < n:
        s = s + " "
    return s

def lpad(s, n):
    while len(s) < n:
        s = " " + s
    return s

def describe(name):
    p = washer.program(name)
    st = washer.stages(p)
    line = ""
    if p[0] > 0:
        line = "fill " + str(p[0]) + ", wash " + str(p[1]) + ", drain " + str(p[2]) + ", "
    if p[3] == 1:
        line = line + "1 rinse (fill " + str(p[4]) + ", rinse " + str(p[5]) + ", drain " + str(p[6]) + "), "
    elif p[3] > 1:
        line = line + str(p[3]) + " rinses (fill " + str(p[4]) + ", rinse " + str(p[5]) + ", drain " + str(p[6]) + " each), "
    line = line + "spin " + str(p[7])
    print("  " + pad(name, 10) + lpad(str(washer.total(st)), 3) + " minutes: " + line)

def show_programs():
    print("Programs, in minutes:")
    for name in ["cotton", "quick", "delicate", "spin"]:
        describe(name)
    print("The door stays locked while the program runs, while there is water in the drum, and for a minute after the drum stops.")

def show_events(events):
    if len(events) == 0:
        print("No events: the program runs straight through.")
        return
    line = ""
    for ev in events:
        if line != "":
            line = line + ", "
        line = line + str(ev[0]) + " " + ev[1]
    print("Events (minute and action): " + line + ".")

def run(events):
    name = plain(input("program (cotton, quick, delicate or spin)> "))
    if name == "":
        print("Cancelled.")
        return
    p = washer.program(name)
    if len(p) == 0:
        print("Type cotton, quick, delicate or spin.")
        return
    st = washer.stages(p)
    need = washer.total(st)
    r = washer.simulate(st, events, 300)
    print("The " + name + " program, " + washer.minutes(need) + ".")
    show_events(events)
    print("  " + pad("minutes", 10) + pad("stage", 28) + pad("water", 7) + "door")
    first = 0
    tl = r[0]
    for t in range(1, len(tl)+1):
        if t == len(tl) or tl[t][0] != tl[first][0] or tl[t][1] != tl[first][1] or tl[t][2] != tl[first][2]:
            span = "     "
            if t - 1 > first:
                span = "-" + pad(str(t - 1), 4)
            water = "no"
            if tl[first][1]:
                water = "yes"
            print("  " + lpad(str(first), 3) + span + "  " + pad(tl[first][0], 28) + pad(water, 7) + tl[first][2])
            first = t
    for n in r[1]:
        print("Minute " + str(n[0]) + ", " + n[1] + ": " + n[2] + ".")
    if r[2] == -1:
        print("Not finished by minute 300.")
        return
    extra = ""
    if r[3] > 0:
        extra = extra + " + " + str(r[3]) + " paused"
    if r[4] > 0:
        extra = extra + " + " + str(r[4]) + " without power"
    if r[5] > 0:
        extra = extra + " + " + str(r[5]) + " done again after a power cut"
    if extra == "":
        print("Finished at minute " + str(r[2]) + ", the program's " + str(need) + ".")
    else:
        print("Finished at minute " + str(r[2]) + " = the program's " + str(need) + extra + ".")

def set_events(events):
    print("Events as minute and action, in order: pause, resume, open, close, cut (the power) or on. For example: 10 pause 12 open 14 resume. Or none.")
    t = plain(input("events> "))
    if t == "":
        print("Cancelled.")
        return events
    if t == "none":
        print("No events.")
        return []
    out = washer.parse_events(t)
    if len(out) == 0:
        print("Type up to 16 pairs: a minute from 0 to 299, then pause, resume, open, close, cut or on; minutes in order.")
        return events
    show_events(out)
    return out

events = []
print("== Washing machine ==")
print("Programs of timed stages, minute by minute - with pause, a door that stays locked while it is unsafe, and power cuts.")
running = True
while running:
    print("")
    print("1) programs  2) run  3) events  4) quit")
    choice = plain(input("choice> "))
    if choice == "1":
        show_programs()
    elif choice == "2":
        run(events)
    elif choice == "3":
        events = set_events(events)
    elif choice == "4":
        running = False
    else:
        print("Pick a number from 1 to 4.")
print("Bye.")
```

### washer.eml

```eml
# P098 washing machine - a program as stages with fixed times, run minute
# by minute with a running total, as in the corpus case
# washing-machine-cycle-simulator; and the things that happen to a real
# machine: pause and resume, a door that stays locked while it is unsafe,
# and a power cut, after which the stage under way starts again.

def program(name):
    # [fill, wash, drain, rinses, rinse fill, rinse, rinse drain, spin]
    {"cotton": [4, 40, 2, 3, 3, 6, 2, 10], "quick": [3, 12, 2, 1, 3, 4, 2, 5], "delicate": [4, 20, 2, 2, 3, 5, 2, 4], "spin": [0, 0, 0, 0, 0, 0, 0, 10]} => programs
    if name in programs:
        return programs[name]
    return []

def stages(p):
    # [label, minutes, water in the drum]
    [] => out
    if p[0] > 0:
        out + [["fill", p[0], True], ["wash", p[1], True], ["drain", p[2], True]] => out
    if p[3] > 0:
        for r in [1:p[3]]:
            "rinse " + str(r) => n
            out + [[n + ": fill", p[4], True], [n, p[5], True], [n + ": drain", p[6], True]] => out
    out + [["spin", p[7], False]] => out
    return out

def total(st):
    0 => n
    for s in st:
        n + s[1] => n
    return n

def minutes(n):
    if n == 1:
        return "1 minute"
    return str(n) + " minutes"

def parse_events(text):
    # "12 pause 15 resume": minute then action, minutes 0 to 299 in order,
    # 1 to 16 events; [] if anything is wrong.
    {"pause": 1, "resume": 1, "open": 1, "close": 1, "cut": 1, "on": 1} => actions
    [] => words
    "" => w
    for c in text + " ":
        if c == " ":
            if w != "":
                words + [w] => words
            "" => w
        else:
            w + c => w
    if len(words) == 0 or len(words) % 2 == 1 or len(words) > 32:
        return []
    [] => out
    0 => i
    while i < len(words):
        -1 => m
        if len(words[i]) <= 3:
            0 => m
            for c in words[i]:
                if m >= 0 and c >= "0" and c <= "9":
                    m * 10 + int(c) => m
                else:
                    -1 => m
        if m < 0 or m > 299 or not (words[i + 1] in actions):
            return []
        if len(out) > 0 and m < out[len(out) - 1][0]:
            return []
        out + [[m, words[i + 1]]] => out
        i + 2 => i
    return out

def simulate(st, events, cap):
    # Minute by minute. Events at a minute happen before it is counted.
    # Returns [timeline, notes, finish, paused, off, lost]: timeline holds
    # [label, water, door] for each minute; finish is the minute the last
    # stage is done (-1 if not by `cap`); lost is the minutes done in
    # stages that a power cut made start again.
    0 => s
    0 => done_in
    "running" => status
    "" => before
    False => door_open
    0 => stopped
    -1 => finish
    0 => paused
    0 => off
    0 => lost
    [] => timeline
    [] => notes
    0 => e
    -1 => last
    if len(events) > 0:
        events[len(events) - 1][0] => last
    0 => m
    while m < cap and (finish == -1 or m <= finish + 1 or m <= last):
        while e < len(events) and events[e][0] == m:
            events[e][1] => a
            "" => label
            if status != "done":
                st[s][0] => label
            "" => text
            if a == "pause":
                if status == "running":
                    "paused" => status
                    m => stopped
                    "paused in " + label => text
                elif status == "paused":
                    "already paused" => text
                elif status == "off":
                    "the power is off" => text
                else:
                    "already finished" => text
            elif a == "resume":
                if status == "paused":
                    if door_open:
                        "close the door first" => text
                    else:
                        "running" => status
                        if done_in == 0:
                            label + " starts, " + minutes(st[s][1]) => text
                        else:
                            label + " goes on, " + minutes(st[s][1] - done_in) + " left in it" => text
                elif status == "running":
                    "not paused" => text
                elif status == "off":
                    "the power is off" => text
                else:
                    "already finished" => text
            elif a == "open":
                if door_open:
                    "already open" => text
                elif status == "running":
                    "stays locked - the program is running" => text
                elif status != "done" and st[s][2]:
                    "stays locked - water in the drum" => text
                elif m - stopped < 1:
                    "stays locked - the drum is still turning" => text
                else:
                    True => door_open
                    "the door opens" => text
            elif a == "close":
                if door_open:
                    False => door_open
                    "the door closes" => text
                else:
                    "already closed" => text
            elif a == "cut":
                if status == "off":
                    "already off" => text
                elif status == "done":
                    "done" => before
                    "off" => status
                    "power off; the program had finished" => text
                else:
                    status => before
                    if status == "running":
                        m => stopped
                    "off" => status
                    if done_in == 0:
                        "power off in " + label + ", none of it done yet" => text
                    elif done_in == 1:
                        "power off in " + label + ": the 1 minute done in it is lost" => text
                    else:
                        "power off in " + label + ": the " + minutes(done_in) + " done in it are lost" => text
                    lost + done_in => lost
                    0 => done_in
            else:
                if status != "off":
                    "the power is on" => text
                elif before == "done":
                    "done" => status
                    "power back" => text
                elif before == "running" and not door_open:
                    "running" => status
                    "power back: " + label + " starts again" => text
                else:
                    "paused" => status
                    if before == "running":
                        "power back; the door is open, so it waits, paused: " + label + " will start again" => text
                    else:
                        "power back, still paused: " + label + " will start again" => text
            notes + [[m, a, text]] => notes
            e + 1 => e
        "finished" => label
        False => water
        if status != "done" and not (status == "off" and before == "done"):
            st[s][0] => label
            st[s][2] => water
            if status == "paused":
                "paused in " + label => label
            elif status == "off":
                "no power in " + label => label
        elif status == "off":
            "no power, finished" => label
        "closed" => door
        if door_open:
            "open" => door
        elif status == "running" or water or m - stopped < 1:
            "locked" => door
        timeline + [[label, water, door]] => timeline
        if status == "running":
            done_in + 1 => done_in
            if done_in == st[s][1]:
                s + 1 => s
                0 => done_in
                if s == len(st):
                    "done" => status
                    m + 1 => finish
                    m + 1 => stopped
                    len(st) - 1 => s
        elif finish == -1:
            if status == "paused":
                paused + 1 => paused
            else:
                off + 1 => off
        m + 1 => m
    return [timeline, notes, finish, paused, off, lost]
```

Python projection of washer.eml:

```python
def program(name):
    programs = {"cotton": [4, 40, 2, 3, 3, 6, 2, 10], "quick": [3, 12, 2, 1, 3, 4, 2, 5], "delicate": [4, 20, 2, 2, 3, 5, 2, 4], "spin": [0, 0, 0, 0, 0, 0, 0, 10]}
    if name in programs:
        return programs[name]
    return []

def stages(p):
    out = []
    if p[0] > 0:
        out = out + [["fill", p[0], True], ["wash", p[1], True], ["drain", p[2], True]]
    if p[3] > 0:
        for r in range(1, p[3]+1):
            n = "rinse " + str(r)
            out = out + [[n + ": fill", p[4], True], [n, p[5], True], [n + ": drain", p[6], True]]
    out = out + [["spin", p[7], False]]
    return out

def total(st):
    n = 0
    for s in st:
        n = n + s[1]
    return n

def minutes(n):
    if n == 1:
        return "1 minute"
    return str(n) + " minutes"

def parse_events(text):
    actions = {"pause": 1, "resume": 1, "open": 1, "close": 1, "cut": 1, "on": 1}
    words = []
    w = ""
    for c in text + " ":
        if c == " ":
            if w != "":
                words = words + [w]
            w = ""
        else:
            w = w + c
    if len(words) == 0 or len(words) % 2 == 1 or len(words) > 32:
        return []
    out = []
    i = 0
    while i < len(words):
        m = -1
        if len(words[i]) <= 3:
            m = 0
            for c in words[i]:
                if m >= 0 and c >= "0" and c <= "9":
                    m = m * 10 + int(c)
                else:
                    m = -1
        if m < 0 or m > 299 or not words[i + 1] in actions:
            return []
        if len(out) > 0 and m < out[len(out) - 1][0]:
            return []
        out = out + [[m, words[i + 1]]]
        i = i + 2
    return out

def simulate(st, events, cap):
    s = 0
    done_in = 0
    status = "running"
    before = ""
    door_open = False
    stopped = 0
    finish = -1
    paused = 0
    off = 0
    lost = 0
    timeline = []
    notes = []
    e = 0
    last = -1
    if len(events) > 0:
        last = events[len(events) - 1][0]
    m = 0
    while m < cap and (finish == -1 or m <= finish + 1 or m <= last):
        while e < len(events) and events[e][0] == m:
            a = events[e][1]
            label = ""
            if status != "done":
                label = st[s][0]
            text = ""
            if a == "pause":
                if status == "running":
                    status = "paused"
                    stopped = m
                    text = "paused in " + label
                elif status == "paused":
                    text = "already paused"
                elif status == "off":
                    text = "the power is off"
                else:
                    text = "already finished"
            elif a == "resume":
                if status == "paused":
                    if door_open:
                        text = "close the door first"
                    else:
                        status = "running"
                        if done_in == 0:
                            text = label + " starts, " + minutes(st[s][1])
                        else:
                            text = label + " goes on, " + minutes(st[s][1] - done_in) + " left in it"
                elif status == "running":
                    text = "not paused"
                elif status == "off":
                    text = "the power is off"
                else:
                    text = "already finished"
            elif a == "open":
                if door_open:
                    text = "already open"
                elif status == "running":
                    text = "stays locked - the program is running"
                elif status != "done" and st[s][2]:
                    text = "stays locked - water in the drum"
                elif m - stopped < 1:
                    text = "stays locked - the drum is still turning"
                else:
                    door_open = True
                    text = "the door opens"
            elif a == "close":
                if door_open:
                    door_open = False
                    text = "the door closes"
                else:
                    text = "already closed"
            elif a == "cut":
                if status == "off":
                    text = "already off"
                elif status == "done":
                    before = "done"
                    status = "off"
                    text = "power off; the program had finished"
                else:
                    before = status
                    if status == "running":
                        stopped = m
                    status = "off"
                    if done_in == 0:
                        text = "power off in " + label + ", none of it done yet"
                    elif done_in == 1:
                        text = "power off in " + label + ": the 1 minute done in it is lost"
                    else:
                        text = "power off in " + label + ": the " + minutes(done_in) + " done in it are lost"
                    lost = lost + done_in
                    done_in = 0
            elif status != "off":
                text = "the power is on"
            elif before == "done":
                status = "done"
                text = "power back"
            elif before == "running" and not door_open:
                status = "running"
                text = "power back: " + label + " starts again"
            else:
                status = "paused"
                if before == "running":
                    text = "power back; the door is open, so it waits, paused: " + label + " will start again"
                else:
                    text = "power back, still paused: " + label + " will start again"
            notes = notes + [[m, a, text]]
            e = e + 1
        label = "finished"
        water = False
        if status != "done" and not (status == "off" and before == "done"):
            label = st[s][0]
            water = st[s][2]
            if status == "paused":
                label = "paused in " + label
            elif status == "off":
                label = "no power in " + label
        elif status == "off":
            label = "no power, finished"
        door = "closed"
        if door_open:
            door = "open"
        elif status == "running" or water or m - stopped < 1:
            door = "locked"
        timeline = timeline + [[label, water, door]]
        if status == "running":
            done_in = done_in + 1
            if done_in == st[s][1]:
                s = s + 1
                done_in = 0
                if s == len(st):
                    status = "done"
                    finish = m + 1
                    stopped = m + 1
                    s = len(st) - 1
        elif finish == -1:
            if status == "paused":
                paused = paused + 1
            else:
                off = off + 1
        m = m + 1
    return [timeline, notes, finish, paused, off, lost]
```

## README

# P098 - Washing machine

Washing programs of timed stages, run minute by minute, and what can
happen to a real machine along the way:
- pause and resume;
- a door that stays locked while it is unsafe;
- power cuts, after which the stage under way starts again.

The finish is accounted for minute by minute.

- `main.eml` - the menu, the programs, events and the run table
- `washer.eml` - the programs and their stages, reading events, and the
  minute-by-minute run

How each part works:

- A program is a list of stages, each with a fixed number of minutes, as
  in the corpus case `washing-machine-cycle-simulator`. Cotton is fill 4,
  wash 40 and drain 2, then 3 rinses (fill 3, rinse 6, drain 2), then
  spin 10, for 89 minutes. Quick takes 31 minutes, delicate 50, and spin
  only 10.
- Events happen at the start of their minute, in the order typed: pause,
  resume, open, close, cut (the power) or on.
- The door stays locked while the program runs and while there is water
  in the drum (from the first fill to the last drain). It also stays
  locked for a minute after the drum stops. The program cannot resume
  with the door open.
- A power cut keeps the stage but loses the minutes done in it. When the
  power comes back, that stage starts again, or waits paused if the
  machine was paused or the door is open.
- The finish is accounted for: the program's minutes, plus minutes
  paused, plus minutes without power, plus minutes done again after a
  power cut.

What is checked: menu choices 1 to 4; a program name; 1 to 16 events,
each a minute from 0 to 299 and an action, in order of minutes, or none.
An empty answer cancels. Events that make no sense at the time are
refused with the reason, such as resume when not paused or a second cut.
The run stops at minute 300.

Sessions: `sessions/basic.in` gives:
- the four programs;
- quick, straight through in 31 minutes;
- quick with two pauses, the door refused and then opened, a resume
  refused with the door open, and a power cut in a drain, finishing at
  42 = 31 + 7 + 3 + 1;
- cotton with a 30-minute power cut 26 minutes into the wash: 145 = 89 +
  30 + 26;
- spin only.

`sessions/bad-input.in` gives:
- menu choices 0 and x; the program lizard; a cancel;
- events abc, an unknown action, minutes out of order, an odd count,
  minute 300, and 17 events; a cancel;
- events at the wrong time, each refused with its reason, around a pause
  and a power cut in the fill;
- a pause never resumed: not finished by minute 300.

Built on the verified corpus case `washing-machine-cycle-simulator`
(stages with fixed times, run in order with a running total).
