Project P035

Pig (dice game)

The dice game Pig for two players or against the computer: roll as often as you like, every roll adds to the turn, a 1 loses the whole turn, and holding banks it - first to the target wins. The computer holds at 20; the dice come from a random generator written in EML, so a seed always gives the same game, and a tally shows how often each face came up.

4 modules · 2 recorded sessionstext-menu UI in the terminalupdated 2026-10-10

Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.

About

The dice game Pig, for two players or one against the computer. On a turn you roll a die as often as you like: every roll adds to the turn, but a 1 loses the whole turn, and holding banks it. The first to the target - 100 unless it is changed - wins; a turn that reaches the target is banked at once. The dice come from a random generator written in EML, so a seed always gives the same rolls, and a tally shows how often each face has come up.

  • main.eml - the menu, the names, the target and the seed, a turn at the keyboard and the game
  • pig.eml - the computer's strategy and its turn
  • rng.eml - the random generator of P008 (number guessing), with a die
  • tally.eml - how often each face came up, with a bar for each

How each part works:

  • The computer holds at 20, or as soon as its turn would win. A roll at turn total t loses t one time in six and otherwise adds 4 on average ((2 + 3 + 4 + 5 + 6) / 5), so it is worth (20 - t) / 6 points on average: rolling pays while the turn is below 20. Worked out exactly over a whole turn, holding at 20 gives the most points per turn, 8.14 on average (so does 21: at 20 one more roll gains nothing on average). That is the best rule for points per turn; winning a game is a different question - near the end, the score still needed matters more - and the computer looks at that only to stop when its turn would win.
  • The generator is a linear congruential one with the constants of the C standard's example rand(), its state below 2^31; a die is 1 plus the high bits of the state (the state divided by 65536) modulo 6. The constants meet the conditions for the full period, so in one period every state comes once and each face comes up exactly as often as its share of the 32,768 high-bit values: 5,462 for faces 1 and 2 and 5,461 for the others - a sixth, to within 0.012%.
  • Python's random module is not used: a session is checked byte for byte in CPython and in the EML interpreter, and both must see the same rolls.
  • The tally counts every roll of the run, the computer's too, as in the corpus case dice-roll-tally: a count for each face, the face that came up most often (the lowest on a tie) and the share a fair die would give.

What is checked: names of 1 to 12 letters, different in any case, and not Computer when playing the computer; r, h or q on a turn, in either case, and no hold before the first roll; a target from 10 to 500; a seed of up to 9 digits. An empty answer cancels.

Sessions: sessions/basic.in sets the target to 40. Ana and Bo play: Bo holds once his turn reaches 12 and Ana at 15; Ana loses four turns to a 1 before her first hold, and Bo wins 43 to 32 with a single 4 that takes him past 40. Then Cy plays the computer, which holds once with 21 and loses three turns to a 1; Cy wins 41 to 21. The tally of the 57 rolls ends the session. sessions/bad-input.in gives menu choices 0 and x, the tally before any roll, targets 5, 501 and x, seeds x and 1234567890, a name with a digit, a name of 13 letters, the same name in capitals, a hold before rolling, x and roll as moves, R in capitals, q to stop the game, Computer as a name and two cancelled games; then it sets seed 7 twice, and both games start with 5, 3 and 3 - the same seed gives the same rolls.

Built on the verified corpus case dice-roll-tally (a tally of dice faces, with the most frequent face found by comparison).

Recorded sessions

What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.

bad-input

interpreter: byte-equal

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 0
Pick a number from 1 to 6.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> x
Pick a number from 1 to 6.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 5
No dice rolled yet.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 3
target (10 to 500)> 5
The target stays 100: type a number from 10 to 500.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 3
target (10 to 500)> 501
The target stays 100: type a number from 10 to 500.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 3
target (10 to 500)> x
The target stays 100: type a number from 10 to 500.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 3
target (10 to 500)> 
Cancelled.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 4
seed> x
A seed is a whole number of up to 9 digits.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 4
seed> 1234567890
A seed is a whole number of up to 9 digits.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 4
seed> 
Cancelled.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 1
player 1> Al1
A name is 1 to 12 letters.
player 1> Abcdefghijklm
A name is 1 to 12 letters.
player 1> Ann
player 2> ANN
Ann is already playing.
player 2> Ben
Type r to roll, h to hold, q to stop the game.
Ann 0, Ben 0 - Ann to play.
Ann (turn 0)> h
Roll at least once before holding.
Ann (turn 0)> x
Type r to roll, h to hold or q to stop the game.
Ann (turn 0)> roll
Type r to roll, h to hold or q to stop the game.
Ann (turn 0)> R
Ann rolls 5 - turn 5.
Ann (turn 5)> r
Ann rolls 1 - the turn is lost.
Ann 0, Ben 0 - Ben to play.
Ben (turn 0)> q
Game stopped.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 2
your name> computer
Computer is already playing.
your name> 
Cancelled.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 1
player 1> Ann
player 2> 
Cancelled.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 5
2 rolls so far:
  1:  1  #
  2:  0
  3:  0
  4:  0
  5:  1  #
  6:  0
Most often: 1 (1 time). A fair die would give each face 0.3 of them.

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 4
seed> 7
Seed set to 7: the same seed gives the same rolls.

== Pig (first to 100, seed 7) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 1
player 1> Ann
player 2> Ben
Type r to roll, h to hold, q to stop the game.
Ann 0, Ben 0 - Ann to play.
Ann (turn 0)> r
Ann rolls 5 - turn 5.
Ann (turn 5)> r
Ann rolls 3 - turn 8.
Ann (turn 8)> r
Ann rolls 3 - turn 11.
Ann (turn 11)> q
Game stopped.

== Pig (first to 100, seed 7) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 4
seed> 7
Seed set to 7: the same seed gives the same rolls.

== Pig (first to 100, seed 7) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 1
player 1> Ann
player 2> Ben
Type r to roll, h to hold, q to stop the game.
Ann 0, Ben 0 - Ann to play.
Ann (turn 0)> r
Ann rolls 5 - turn 5.
Ann (turn 5)> r
Ann rolls 3 - turn 8.
Ann (turn 8)> r
Ann rolls 3 - turn 11.
Ann (turn 11)> q
Game stopped.

== Pig (first to 100, seed 7) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 5
8 rolls so far:
  1:  1  #
  2:  0
  3:  4  ####
  4:  0
  5:  3  ###
  6:  0
Most often: 3 (4 times). A fair die would give each face 1.3 of them.

== Pig (first to 100, seed 7) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 6
Bye.
What was typed (56 lines)
0
x
5
3
5
3
501
3
x
3

4
x
4
1234567890
4

1
Al1
Abcdefghijklm
Ann
ANN
Ben
h
x
roll
R
r
q
2
computer

1
Ann

5
4
7
1
Ann
Ben
r
r
r
q
4
7
1
Ann
Ben
r
r
r
q
5
6

basic

interpreter: byte-equal

== Pig (first to 100, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 3
target (10 to 500)> 40
The first to 40 wins.

== Pig (first to 40, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 1
player 1> Ana
player 2> Bo
Type r to roll, h to hold, q to stop the game.
Ana 0, Bo 0 - Ana to play.
Ana (turn 0)> r
Ana rolls 5 - turn 5.
Ana (turn 5)> r
Ana rolls 1 - the turn is lost.
Ana 0, Bo 0 - Bo to play.
Bo (turn 0)> r
Bo rolls 2 - turn 2.
Bo (turn 2)> r
Bo rolls 3 - turn 5.
Bo (turn 5)> r
Bo rolls 3 - turn 8.
Bo (turn 8)> r
Bo rolls 6 - turn 14.
Bo (turn 14)> h
Bo holds: 14, score 14.
Ana 0, Bo 14 - Ana to play.
Ana (turn 0)> r
Ana rolls 3 - turn 3.
Ana (turn 3)> r
Ana rolls 2 - turn 5.
Ana (turn 5)> r
Ana rolls 4 - turn 9.
Ana (turn 9)> r
Ana rolls 1 - the turn is lost.
Ana 0, Bo 14 - Bo to play.
Bo (turn 0)> r
Bo rolls 1 - the turn is lost.
Ana 0, Bo 14 - Ana to play.
Ana (turn 0)> r
Ana rolls 3 - turn 3.
Ana (turn 3)> r
Ana rolls 1 - the turn is lost.
Ana 0, Bo 14 - Bo to play.
Bo (turn 0)> r
Bo rolls 5 - turn 5.
Bo (turn 5)> r
Bo rolls 4 - turn 9.
Bo (turn 9)> r
Bo rolls 3 - turn 12.
Bo (turn 12)> h
Bo holds: 12, score 26.
Ana 0, Bo 26 - Ana to play.
Ana (turn 0)> r
Ana rolls 3 - turn 3.
Ana (turn 3)> r
Ana rolls 1 - the turn is lost.
Ana 0, Bo 26 - Bo to play.
Bo (turn 0)> r
Bo rolls 3 - turn 3.
Bo (turn 3)> r
Bo rolls 3 - turn 6.
Bo (turn 6)> r
Bo rolls 4 - turn 10.
Bo (turn 10)> r
Bo rolls 1 - the turn is lost.
Ana 0, Bo 26 - Ana to play.
Ana (turn 0)> r
Ana rolls 4 - turn 4.
Ana (turn 4)> r
Ana rolls 4 - turn 8.
Ana (turn 8)> r
Ana rolls 2 - turn 10.
Ana (turn 10)> r
Ana rolls 2 - turn 12.
Ana (turn 12)> r
Ana rolls 4 - turn 16.
Ana (turn 16)> h
Ana holds: 16, score 16.
Ana 16, Bo 26 - Bo to play.
Bo (turn 0)> r
Bo rolls 2 - turn 2.
Bo (turn 2)> r
Bo rolls 6 - turn 8.
Bo (turn 8)> r
Bo rolls 5 - turn 13.
Bo (turn 13)> h
Bo holds: 13, score 39.
Ana 16, Bo 39 - Ana to play.
Ana (turn 0)> r
Ana rolls 5 - turn 5.
Ana (turn 5)> r
Ana rolls 6 - turn 11.
Ana (turn 11)> r
Ana rolls 5 - turn 16.
Ana (turn 16)> h
Ana holds: 16, score 32.
Ana 32, Bo 39 - Bo to play.
Bo (turn 0)> r
Bo rolls 4 - turn 4, score 43.
Bo wins, 43 to 32!

== Pig (first to 40, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 2
your name> Cy
Type r to roll, h to hold, q to stop the game.
The computer holds at 20, or as soon as its turn would win.
Cy 0, Computer 0 - Cy to play.
Cy (turn 0)> r
Cy rolls 6 - turn 6.
Cy (turn 6)> r
Cy rolls 5 - turn 11.
Cy (turn 11)> r
Cy rolls 1 - the turn is lost.
Cy 0, Computer 0 - Computer to play.
Computer rolls 3, 2, 4, 5, 2, 5 and holds: 21, score 21.
Cy 0, Computer 21 - Cy to play.
Cy (turn 0)> r
Cy rolls 6 - turn 6.
Cy (turn 6)> r
Cy rolls 1 - the turn is lost.
Cy 0, Computer 21 - Computer to play.
Computer rolls 1 - the turn is lost.
Cy 0, Computer 21 - Cy to play.
Cy (turn 0)> r
Cy rolls 5 - turn 5.
Cy (turn 5)> r
Cy rolls 6 - turn 11.
Cy (turn 11)> r
Cy rolls 6 - turn 17.
Cy (turn 17)> h
Cy holds: 17, score 17.
Cy 17, Computer 21 - Computer to play.
Computer rolls 2, 1 - the turn is lost.
Cy 17, Computer 21 - Cy to play.
Cy (turn 0)> r
Cy rolls 4 - turn 4.
Cy (turn 4)> r
Cy rolls 5 - turn 9.
Cy (turn 9)> r
Cy rolls 5 - turn 14.
Cy (turn 14)> h
Cy holds: 14, score 31.
Cy 31, Computer 21 - Computer to play.
Computer rolls 1 - the turn is lost.
Cy 31, Computer 21 - Cy to play.
Cy (turn 0)> r
Cy rolls 6 - turn 6.
Cy (turn 6)> r
Cy rolls 4 - turn 10, score 41.
Cy wins, 41 to 21!

== Pig (first to 40, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 5
57 rolls so far:
  1: 11  ###########
  2:  8  ########
  3:  9  #########
  4: 10  ##########
  5: 11  ###########
  6:  8  ########
Most often: 1 (11 times). A fair die would give each face 9.5 of them.

== Pig (first to 40, seed 2026) ==
1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit
choice> 6
Bye.
What was typed (63 lines)
3
40
1
Ana
Bo
r
r
r
r
r
r
h
r
r
r
r
r
r
r
r
r
r
h
r
r
r
r
r
r
r
r
r
r
r
h
r
r
r
h
r
r
r
h
r
2
Cy
r
r
r
r
r
r
r
r
h
r
r
r
h
r
r
5
6

Modules

The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.

main.eml(entry)

eml
# P035 dice game Pig: two players, or one against the computer. On a turn
# you roll a die as often as you like - every roll adds to the turn, but a 1
# loses the whole turn - and holding banks it. The first to the target wins.
# The dice come from a random generator written in EML, so the same seed
# always gives the same rolls, and a tally shows how often each face came up.
import pig
import rng
import tally

12 => longest_name

def trim(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 lower(s):
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" => big
    "abcdefghijklmnopqrstuvwxyz" => small
    "" => out
    for c in s:
        0 => k
        while k < 26 and big[k] != c:
            k + 1 => k
        if k < 26:
            out + small[k] => out
        else:
            out + c => out
    return out

def number(s):
    # The value of 1 to 9 digits, otherwise -1.
    if s == "" or len(s) > 9:
        return -1
    0 => n
    for c in s:
        if not (c in "0123456789"):
            return -1
        n * 10 + int(c) => n
    return n

def ask_name(prompt, taken):
    # A name of 1 to 12 letters, not taken (in any case); "" when the
    # answer is empty.
    while True:
        trim(input(prompt)) => name
        if name == "":
            return ""
        True => letters
        for c in name:
            if not (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"):
                False => letters
        if not letters or len(name) > longest_name:
            ("A name is 1 to " + str(longest_name) + " letters.") ^0
        else:
            "" => clash
            for t in taken:
                if lower(t) == lower(name):
                    t => clash
            if clash == "":
                return name
            (clash + " is already playing.") ^0

def human_turn(r, name, score, target):
    # One turn at the keyboard. Returns [how it ended, points, rolls], where
    # it ends "lost", "held", "won" or "stopped".
    0 => turn
    [] => rolls
    while True:
        lower(trim(input(name + " (turn " + str(turn) + ")> "))) => answer
        if answer == "r":
            r.die() => d
            rolls + [d] => rolls
            if d == 1:
                (name + " rolls 1 - the turn is lost.") ^0
                return ["lost", 0, rolls]
            turn + d => turn
            if score + turn >= target:
                (name + " rolls " + str(d) + " - turn " + str(turn) + ", score " + str(score + turn) + ".") ^0
                return ["won", turn, rolls]
            (name + " rolls " + str(d) + " - turn " + str(turn) + ".") ^0
        elif answer == "h":
            if turn == 0:
                "Roll at least once before holding." ^0
            else:
                (name + " holds: " + str(turn) + ", score " + str(score + turn) + ".") ^0
                return ["held", turn, rolls]
        elif answer == "q":
            return ["stopped", 0, rolls]
        else:
            "Type r to roll, h to hold or q to stop the game." ^0

def computer_turn(r, score, target):
    # The computer's turn, shown on one line. Returns [how it ended, points,
    # rolls] like human_turn.
    pig.computer_turn(r, score, target) => rolls
    if rolls[len(rolls) - 1] == 1:
        ("Computer rolls " + pig.listed(rolls) + " - the turn is lost.") ^0
        return ["lost", 0, rolls]
    0 => turn
    for d in rolls:
        turn + d => turn
    if score + turn >= target:
        ("Computer rolls " + pig.listed(rolls) + " - turn " + str(turn) + ", score " + str(score + turn) + ".") ^0
        return ["won", turn, rolls]
    ("Computer rolls " + pig.listed(rolls) + " and holds: " + str(turn) + ", score " + str(score + turn) + ".") ^0
    return ["held", turn, rolls]

def play(r, names, computer, target, counts):
    # One game; the first name starts. Returns the tally with this game's
    # rolls added.
    [0, 0] => scores
    0 => p
    "Type r to roll, h to hold, q to stop the game." ^0
    if computer:
        "The computer holds at 20, or as soon as its turn would win." ^0
    while True:
        (names[0] + " " + str(scores[0]) + ", " + names[1] + " " + str(scores[1]) + " - " + names[p] + " to play.") ^0
        if computer and p == 1:
            computer_turn(r, scores[1], target) => t
        else:
            human_turn(r, names[p], scores[p], target) => t
        tally.counted(counts, t[2]) => counts
        if t[0] == "stopped":
            "Game stopped." ^0
            return counts
        scores[p] + t[1] => scores[p]
        if t[0] == "won":
            (names[p] + " wins, " + str(scores[p]) + " to " + str(scores[1 - p]) + "!") ^0
            return counts
        1 - p => p

def new_game(r, computer, target, counts):
    if computer:
        ask_name("your name> ", ["Computer"]) => a
        if a == "":
            "Cancelled." ^0
            return counts
        return play(r, [a, "Computer"], True, target, counts)
    ask_name("player 1> ", []) => a
    if a == "":
        "Cancelled." ^0
        return counts
    ask_name("player 2> ", [a]) => b
    if b == "":
        "Cancelled." ^0
        return counts
    return play(r, [a, b], False, target, counts)

100 => target
2026 => seed
rng.Rng(seed) => r
[0, 0, 0, 0, 0, 0] => counts
True => running
while running:
    "" ^0
    ("== Pig (first to " + str(target) + ", seed " + str(seed) + ") ==") ^0
    "1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        new_game(r, False, target, counts) => counts
    elif choice == "2":
        new_game(r, True, target, counts) => counts
    elif choice == "3":
        trim(input("target (10 to 500)> ")) => answer
        number(answer) => n
        if answer == "":
            "Cancelled." ^0
        elif n >= 10 and n <= 500:
            n => target
            ("The first to " + str(target) + " wins.") ^0
        else:
            ("The target stays " + str(target) + ": type a number from 10 to 500.") ^0
    elif choice == "4":
        trim(input("seed> ")) => answer
        number(answer) => s
        if answer == "":
            "Cancelled." ^0
        elif s < 0:
            "A seed is a whole number of up to 9 digits." ^0
        else:
            s => seed
            rng.Rng(seed) => r
            ("Seed set to " + str(seed) + ": the same seed gives the same rolls.") ^0
    elif choice == "5":
        tally.show(counts)
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import pig
import rng
import tally
longest_name = 12

def trim(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 lower(s):
    big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    small = "abcdefghijklmnopqrstuvwxyz"
    out = ""
    for c in s:
        k = 0
        while k < 26 and big[k] != c:
            k = k + 1
        if k < 26:
            out = out + small[k]
        else:
            out = out + c
    return out

def number(s):
    if s == "" or len(s) > 9:
        return -1
    n = 0
    for c in s:
        if not c in "0123456789":
            return -1
        n = n * 10 + int(c)
    return n

def ask_name(prompt, taken):
    while True:
        name = trim(input(prompt))
        if name == "":
            return ""
        letters = True
        for c in name:
            if not c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz":
                letters = False
        if not letters or len(name) > longest_name:
            print("A name is 1 to " + str(longest_name) + " letters.")
        else:
            clash = ""
            for t in taken:
                if lower(t) == lower(name):
                    clash = t
            if clash == "":
                return name
            print(clash + " is already playing.")

def human_turn(r, name, score, target):
    turn = 0
    rolls = []
    while True:
        answer = lower(trim(input(name + " (turn " + str(turn) + ")> ")))
        if answer == "r":
            d = r.die()
            rolls = rolls + [d]
            if d == 1:
                print(name + " rolls 1 - the turn is lost.")
                return ["lost", 0, rolls]
            turn = turn + d
            if score + turn >= target:
                print(name + " rolls " + str(d) + " - turn " + str(turn) + ", score " + str(score + turn) + ".")
                return ["won", turn, rolls]
            print(name + " rolls " + str(d) + " - turn " + str(turn) + ".")
        elif answer == "h":
            if turn == 0:
                print("Roll at least once before holding.")
            else:
                print(name + " holds: " + str(turn) + ", score " + str(score + turn) + ".")
                return ["held", turn, rolls]
        elif answer == "q":
            return ["stopped", 0, rolls]
        else:
            print("Type r to roll, h to hold or q to stop the game.")

def computer_turn(r, score, target):
    rolls = pig.computer_turn(r, score, target)
    if rolls[len(rolls) - 1] == 1:
        print("Computer rolls " + pig.listed(rolls) + " - the turn is lost.")
        return ["lost", 0, rolls]
    turn = 0
    for d in rolls:
        turn = turn + d
    if score + turn >= target:
        print("Computer rolls " + pig.listed(rolls) + " - turn " + str(turn) + ", score " + str(score + turn) + ".")
        return ["won", turn, rolls]
    print("Computer rolls " + pig.listed(rolls) + " and holds: " + str(turn) + ", score " + str(score + turn) + ".")
    return ["held", turn, rolls]

def play(r, names, computer, target, counts):
    scores = [0, 0]
    p = 0
    print("Type r to roll, h to hold, q to stop the game.")
    if computer:
        print("The computer holds at 20, or as soon as its turn would win.")
    while True:
        print(names[0] + " " + str(scores[0]) + ", " + names[1] + " " + str(scores[1]) + " - " + names[p] + " to play.")
        if computer and p == 1:
            t = computer_turn(r, scores[1], target)
        else:
            t = human_turn(r, names[p], scores[p], target)
        counts = tally.counted(counts, t[2])
        if t[0] == "stopped":
            print("Game stopped.")
            return counts
        scores[p] = scores[p] + t[1]
        if t[0] == "won":
            print(names[p] + " wins, " + str(scores[p]) + " to " + str(scores[1 - p]) + "!")
            return counts
        p = 1 - p

def new_game(r, computer, target, counts):
    if computer:
        a = ask_name("your name> ", ["Computer"])
        if a == "":
            print("Cancelled.")
            return counts
        return play(r, [a, "Computer"], True, target, counts)
    a = ask_name("player 1> ", [])
    if a == "":
        print("Cancelled.")
        return counts
    b = ask_name("player 2> ", [a])
    if b == "":
        print("Cancelled.")
        return counts
    return play(r, [a, b], False, target, counts)

target = 100
seed = 2026
r = rng.Rng(seed)
counts = [0, 0, 0, 0, 0, 0]
running = True
while running:
    print("")
    print("== Pig (first to " + str(target) + ", seed " + str(seed) + ") ==")
    print("1) two players  2) play the computer  3) target  4) seed  5) dice tally  6) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        counts = new_game(r, False, target, counts)
    elif choice == "2":
        counts = new_game(r, True, target, counts)
    elif choice == "3":
        answer = trim(input("target (10 to 500)> "))
        n = number(answer)
        if answer == "":
            print("Cancelled.")
        elif n >= 10 and n <= 500:
            target = n
            print("The first to " + str(target) + " wins.")
        else:
            print("The target stays " + str(target) + ": type a number from 10 to 500.")
    elif choice == "4":
        answer = trim(input("seed> "))
        s = number(answer)
        if answer == "":
            print("Cancelled.")
        elif s < 0:
            print("A seed is a whole number of up to 9 digits.")
        else:
            seed = s
            r = rng.Rng(seed)
            print("Seed set to " + str(seed) + ": the same seed gives the same rolls.")
    elif choice == "5":
        tally.show(counts)
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")

pig.eml

eml
# P035 dice game Pig - the computer's play. On a turn a player rolls as
# often as they like: every roll adds to the turn, a 1 loses the whole turn,
# and holding banks the turn. The first to the target wins; a turn that
# reaches the target is banked at once.

20 => hold_at

def should_hold(turn, score, target):
    # The computer's fixed strategy: hold at 20, or as soon as the turn wins.
    # A roll at turn total t loses t one time in six and otherwise adds 4 on
    # average ((2 + 3 + 4 + 5 + 6) / 5), so it is worth (20 - t) / 6 points
    # on average: rolling pays while the turn is below 20.
    return turn >= hold_at or score + turn >= target

def computer_turn(r, score, target):
    # The rolls of one turn, in order; the last one is 1 if the turn was lost.
    [] => rolls
    0 => turn
    while True:
        r.die() => d
        rolls + [d] => rolls
        if d == 1:
            return rolls
        turn + d => turn
        if should_hold(turn, score, target):
            return rolls

def listed(rolls):
    # [3, 5, 1] as "3, 5, 1".
    "" => out
    for d in rolls:
        if out != "":
            out + ", " => out
        out + str(d) => out
    return out
Python projection (pig.py)
hold_at = 20

def should_hold(turn, score, target):
    return turn >= hold_at or score + turn >= target

def computer_turn(r, score, target):
    rolls = []
    turn = 0
    while True:
        d = r.die()
        rolls = rolls + [d]
        if d == 1:
            return rolls
        turn = turn + d
        if should_hold(turn, score, target):
            return rolls

def listed(rolls):
    out = ""
    for d in rolls:
        if out != "":
            out = out + ", "
        out = out + str(d)
    return out

rng.eml

eml
# P035 dice game Pig - random numbers written in EML: the linear
# congruential generator of P008 (number guessing), with the constants of
# the C standard's example rand(). Python's random module is not used, so a
# seed gives the same rolls on every machine, and the interpreter can check
# a whole session byte for byte.

class Rng:
    def __init__(self, seed):
        seed % 2147483648 => self.state

    def step(self):
        (1103515245 * self.state + 12345) % 2147483648 => self.state
        return self.state

    def below(self, n):
        # A number from 0 to n - 1, taken from the high bits of the state: the
        # low bits of this generator repeat with short periods. Dividing by
        # 65536 is exact in a float for a state below 2^31.
        return int(self.step() / 65536) % n

    def die(self):
        return 1 + self.below(6)
Python projection (rng.py)
class Rng:
    def __init__(self, seed):
        self.state = seed % 2147483648
    def step(self):
        self.state = (1103515245 * self.state + 12345) % 2147483648
        return self.state
    def below(self, n):
        return int(self.step() / 65536) % n
    def die(self):
        return 1 + self.below(6)

tally.eml

eml
# P035 dice game Pig - the dice tally: how often each face has come up in
# this run, as in the corpus case dice-roll-tally, with a bar for each face.

def quotient(a, b):
    return int((a - a % b) / b)

def counted(counts, rolls):
    # counts is [how many 1s, ..., how many 6s]; returns it with rolls added.
    [] => out
    for face in [1:6]:
        counts[face - 1] => n
        for d in rolls:
            if d == face:
                n + 1 => n
        out + [n] => out
    return out

def show(counts):
    0 => total
    for n in counts:
        total + n => total
    if total == 0:
        "No dice rolled yet." ^0
        return 0
    if total == 1:
        "1 roll so far:" ^0
    else:
        (str(total) + " rolls so far:") ^0
    for face in [1:6]:
        str(counts[face - 1]) => n
        while len(n) < 3:
            " " + n => n
        ("  " + str(face) + ":" + n + "  " + "#" * counts[face - 1]) => line
        if counts[face - 1] == 0:
            ("  " + str(face) + ":" + n) => line
        line ^0
    # the most frequent face, the lowest on a tie
    1 => best
    for face in [2:6]:
        if counts[face - 1] > counts[best - 1]:
            face => best
    "times" => times
    if counts[best - 1] == 1:
        "time" => times
    # a fair die gives each face a sixth of the rolls; in tenths, halves up
    quotient(total * 20 + 6, 12) => tenths
    ("Most often: " + str(best) + " (" + str(counts[best - 1]) + " " + times + "). A fair die would give each face " + str(quotient(tenths, 10)) + "." + str(tenths % 10) + " of them.") ^0
    return 0
Python projection (tally.py)
def quotient(a, b):
    return int((a - a % b) / b)

def counted(counts, rolls):
    out = []
    for face in range(1, 7):
        n = counts[face - 1]
        for d in rolls:
            if d == face:
                n = n + 1
        out = out + [n]
    return out

def show(counts):
    total = 0
    for n in counts:
        total = total + n
    if total == 0:
        print("No dice rolled yet.")
        return 0
    if total == 1:
        print("1 roll so far:")
    else:
        print(str(total) + " rolls so far:")
    for face in range(1, 7):
        n = str(counts[face - 1])
        while len(n) < 3:
            n = " " + n
        line = "  " + str(face) + ":" + n + "  " + "#" * counts[face - 1]
        if counts[face - 1] == 0:
            line = "  " + str(face) + ":" + n
        print(line)
    best = 1
    for face in range(2, 7):
        if counts[face - 1] > counts[best - 1]:
            best = face
    times = "times"
    if counts[best - 1] == 1:
        times = "time"
    tenths = quotient(total * 20 + 6, 12)
    print("Most often: " + str(best) + " (" + str(counts[best - 1]) + " " + times + "). A fair die would give each face " + str(quotient(tenths, 10)) + "." + str(tenths % 10) + " of them.")
    return 0

Built on these corpus cases