Project P051

Hangman

Guess a secret word from a list of over a hundred, a letter at a time or the whole word at once, before six wrong guesses finish the drawing. A hint counts, among the words that still fit what you know, how many hold each letter you have not tried. Words come from a seed, none twice until the list is used up.

3 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

Guess the secret word before the drawing is finished. Guess one letter at a time, or the whole word at once; each wrong guess adds a part to the drawing - head, body, two arms, two legs - and the sixth one hangs you. The words come from a list of 105, picked from a seed, none twice until the whole list has been played.

  • main.eml - the menu, one word from the first guess to the last, the drawing and the score
  • words.eml - the word list, which words still fit what you know, and the letter counts for a hint
  • rng.eml - random numbers written in EML, the generator of P008

How each part works:

  • A guessed letter shows in every place it stands in the word. Upper case is read as lower case, and a letter you have tried already costs nothing.
  • A whole word of the wrong length costs nothing, only a reminder of the length. A wrong word of the right length counts as a wrong guess.
  • A hint looks at the words in the list that still fit what you know: the right length, the shown letters in their places, none of the missed letters, no shown letter hiding behind a blank - the game would have shown it there too - and none of the whole words you guessed wrong. For each letter you have not tried, it counts in how many of those words the letter stands, once a word, and names the three highest, a tie going to the letter first in the alphabet. The count is the corpus case char-frequency-table's tally: check whether the letter is already in the table, then add one or start at one. A hint costs nothing.

What is checked: menu choices 1 to 4; a guess of letters only, a to z, after spaces at the ends are dropped; a seed from 0 to 999999999. An empty guess gives the word up, and an empty seed cancels.

Sessions: sessions/basic.in plays three words from the default seed 2026. - spoon: one miss, then a hint, where 2 words still fit and s stands in both. Then s and o, and the whole word. - trumpet: four hints along the way, from 10 words that fit down to one, and the word typed in capitals. - pebble: six misses in a row, which show the drawing grow, and the word is lost. Then the score: 2 won, 1 lost.

sessions/bad-input.in gives: - menu choices 0 and x; - seeds abc, -1 and 1234567890, and an empty seed that cancels; - seed 7, then the guesses 1, "a b" and ab!; - E in capitals, and then e again; - zebra, which has the wrong length, and bottle, which has the right length but is wrong. After that the hint counts 14 words, with bottle left out; - an empty guess, which gives the word up.

Built on the verified corpus case char-frequency-table (counting each character in a dict, in the order the characters first appear).

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
== Hangman ==
Guess the word a letter at a time; six wrong guesses and you are hanged.

1) new word  2) seed  3) score  4) quit
choice> 0
Pick a number from 1 to 4.

1) new word  2) seed  3) score  4) quit
choice> x
Pick a number from 1 to 4.

1) new word  2) seed  3) score  4) quit
choice> 2
seed (0 to 999999999)> abc
Type a number from 0 to 999999999.
seed (0 to 999999999)> -1
Type a number from 0 to 999999999.
seed (0 to 999999999)> 1234567890
Type a number from 0 to 999999999.
seed (0 to 999999999)> 
Cancelled.

1) new word  2) seed  3) score  4) quit
choice> 2
seed (0 to 999999999)> 7
Words now come from seed 7.

1) new word  2) seed  3) score  4) quit
choice> 1
A word of 6 letters.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> 1
Type a letter from a to z, the whole word, or ?.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> a b
Type a letter from a to z, the whole word, or ?.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> ab!
Type a letter from a to z, the whole word, or ?.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> E
Yes - e is in it 1 time.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ e    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> e
You have tried e already.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ e    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> zebra
The word has 6 letters - that guess costs nothing.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ e    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> bottle
Not bottle.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _ e    missed: none    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> ?
14 words in the list still fit. The letters in the most of them: l (11), a (7), d (5).
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _ e    missed: none    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> 
You give up - the word was fiddle.

1) new word  2) seed  3) score  4) quit
choice> 3
Won 0, lost 1.

1) new word  2) seed  3) score  4) quit
choice> 4
Bye.
What was typed (21 lines)
0
x
2
abc
-1
1234567890

2
7
1
1
a b
ab!
E
e
zebra
bottle
?

3
4

basic

interpreter: byte-equal
== Hangman ==
Guess the word a letter at a time; six wrong guesses and you are hanged.

1) new word  2) seed  3) score  4) quit
choice> 1
A word of 5 letters.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> e
No e.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _    missed: e    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> ?
2 words in the list still fit. The letters in the most of them: s (2), a (1), g (1).
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _    missed: e    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> s
Yes - s is in it 1 time.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: s _ _ _ _    missed: e    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> o
Yes - o is in it 2 times.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: s _ o o _    missed: e    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> spoon
You got it: spoon, with 1 wrong guess.

1) new word  2) seed  3) score  4) quit
choice> 1
A word of 7 letters.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> ?
10 words in the list still fit. The letters in the most of them: e (8), a (6), i (5).
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> e
Yes - e is in it 1 time.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ e _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> ?
4 words in the list still fit. The letters in the most of them: a (2), c (2), i (2).
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ e _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> a
No a.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _ e _    missed: a    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> ?
2 words in the list still fit. The letters in the most of them: m (2), c (1), h (1).
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _ e _    missed: a    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> m
Yes - m is in it 1 time.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ m _ e _    missed: a    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> c
No c.
  +---+
  |   |
  O   |
  |   |
      |
      |
=========
Word: _ _ _ m _ e _    missed: a, c    wrong guesses left: 4
letter, the whole word, ? for a hint, or nothing to give up> ?
Only one word in the list still fits.
  +---+
  |   |
  O   |
  |   |
      |
      |
=========
Word: _ _ _ m _ e _    missed: a, c    wrong guesses left: 4
letter, the whole word, ? for a hint, or nothing to give up> TRUMPET
You got it: trumpet, with 2 wrong guesses.

1) new word  2) seed  3) score  4) quit
choice> 1
A word of 6 letters.
  +---+
  |   |
      |
      |
      |
      |
=========
Word: _ _ _ _ _ _    missed: none    wrong guesses left: 6
letter, the whole word, ? for a hint, or nothing to give up> a
No a.
  +---+
  |   |
  O   |
      |
      |
      |
=========
Word: _ _ _ _ _ _    missed: a    wrong guesses left: 5
letter, the whole word, ? for a hint, or nothing to give up> i
No i.
  +---+
  |   |
  O   |
  |   |
      |
      |
=========
Word: _ _ _ _ _ _    missed: a, i    wrong guesses left: 4
letter, the whole word, ? for a hint, or nothing to give up> o
No o.
  +---+
  |   |
  O   |
 /|   |
      |
      |
=========
Word: _ _ _ _ _ _    missed: a, i, o    wrong guesses left: 3
letter, the whole word, ? for a hint, or nothing to give up> u
No u.
  +---+
  |   |
  O   |
 /|\  |
      |
      |
=========
Word: _ _ _ _ _ _    missed: a, i, o, u    wrong guesses left: 2
letter, the whole word, ? for a hint, or nothing to give up> y
No y.
  +---+
  |   |
  O   |
 /|\  |
 /    |
      |
=========
Word: _ _ _ _ _ _    missed: a, i, o, u, y    wrong guesses left: 1
letter, the whole word, ? for a hint, or nothing to give up> s
No s.
  +---+
  |   |
  O   |
 /|\  |
 / \  |
      |
=========
Hanged - the word was pebble.

1) new word  2) seed  3) score  4) quit
choice> 3
Won 2, lost 1.

1) new word  2) seed  3) score  4) quit
choice> 4
Bye.
What was typed (25 lines)
1
e
?
s
o
spoon
1
?
e
?
a
?
m
c
?
TRUMPET
1
a
i
o
u
y
s
3
4

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
# P051 hangman: guess the secret word a letter at a time before the drawing
# is finished - six wrong guesses hang you. A hint counts, among the words
# that still fit, how many hold each letter you have not tried.
import words
import rng

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 lowered(s):
    # Capital letters to small ones; anything else unchanged.
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" => big
    "abcdefghijklmnopqrstuvwxyz" => small
    "" => out
    for ch in s:
        ch => c
        for k in [0:25]:
            if ch == big[k]:
                small[k] => c
        out + c => out
    return out

def letters_only(s):
    if s == "":
        return False
    for ch in s:
        if not (ch >= "a" and ch <= "z"):
            return False
    return True

def has(s, ch):
    for x in s:
        if x == ch:
            return True
    return False

def counted(n, one, many):
    if n == 1:
        return "1 " + one
    return str(n) + " " + many

def gallows(wrong):
    # The drawing after `wrong` wrong guesses: head, body, two arms, two legs.
    " " => head
    "   " => body
    "   " => legs
    if wrong >= 1:
        "O" => head
    if wrong == 2:
        " | " => body
    elif wrong == 3:
        "/| " => body
    elif wrong >= 4:
        "/|\\" => body
    if wrong == 5:
        "/  " => legs
    elif wrong >= 6:
        "/ \\" => legs
    return ["  +---+", "  |   |", "  " + head + "   |", " " + body + "  |", " " + legs + "  |", "      |", "========="]

def pattern_of(secret, guessed):
    "" => p
    for ch in secret:
        if has(guessed, ch):
            p + ch => p
        else:
            p + "_" => p
    return p

def spaced(p):
    "" => out
    for ch in p:
        if out != "":
            out + " " => out
        out + ch => out
    return out

def listed(s):
    "" => out
    for ch in s:
        if out != "":
            out + ", " => out
        out + ch => out
    if out == "":
        return "none"
    return out

def pick(game):
    # A word not played since the list was last used up.
    words.word_list() => ws
    if len(game[3]) == len(ws):
        [] => game[3]
    while True:
        ws[game[0].below(len(ws))] => w
        False => played
        for p in game[3]:
            if p == w:
                True => played
        if not played:
            game[3] + [w] => game[3]
            return w

def hint(pattern, misses, guessed, ruled_out):
    words.candidates(pattern, misses, ruled_out) => fit
    if len(fit) == 1:
        "Only one word in the list still fits." ^0
        return
    words.best_letters(words.letter_table(fit, guessed), 3) => best
    "" => s
    for b in best:
        if s != "":
            s + ", " => s
        s + b[0] + " (" + str(b[1]) + ")" => s
    (str(len(fit)) + " words in the list still fit. The letters in the most of them: " + s + ".") ^0

def play(game):
    pick(game) => secret
    ("A word of " + str(len(secret)) + " letters.") ^0
    "" => guessed
    "" => misses
    [] => ruled_out
    0 => wrong
    while True:
        pattern_of(secret, guessed) => pattern
        for line in gallows(wrong):
            line ^0
        ("Word: " + spaced(pattern) + "    missed: " + listed(misses) + "    wrong guesses left: " + str(6 - wrong)) ^0
        lowered(trim(input("letter, the whole word, ? for a hint, or nothing to give up> "))) => answer
        if answer == "":
            ("You give up - the word was " + secret + ".") ^0
            return False
        if answer == "?":
            hint(pattern, misses, guessed, ruled_out)
        elif not letters_only(answer):
            "Type a letter from a to z, the whole word, or ?." ^0
        elif len(answer) == 1:
            if has(guessed, answer):
                ("You have tried " + answer + " already.") ^0
            else:
                guessed + answer => guessed
                0 => n
                for ch in secret:
                    if ch == answer:
                        n + 1 => n
                if n > 0:
                    ("Yes - " + answer + " is in it " + counted(n, "time", "times") + ".") ^0
                else:
                    misses + answer => misses
                    wrong + 1 => wrong
                    ("No " + answer + ".") ^0
        elif len(answer) != len(secret):
            ("The word has " + str(len(secret)) + " letters - that guess costs nothing.") ^0
        elif answer == secret:
            secret => guessed
        else:
            ruled_out + [answer] => ruled_out
            wrong + 1 => wrong
            ("Not " + answer + ".") ^0
        if pattern_of(secret, guessed) == secret:
            ("You got it: " + secret + ", with " + counted(wrong, "wrong guess", "wrong guesses") + ".") ^0
            return True
        if wrong == 6:
            for line in gallows(wrong):
                line ^0
            ("Hanged - the word was " + secret + ".") ^0
            return False

def ask_seed():
    while True:
        trim(input("seed (0 to 999999999)> ")) => answer
        if answer == "":
            return -1
        True => ok
        if len(answer) > 9:
            False => ok
        for ch in answer:
            if not (ch >= "0" and ch <= "9"):
                False => ok
        if ok:
            return int(answer)
        "Type a number from 0 to 999999999." ^0

"== Hangman ==" ^0
"Guess the word a letter at a time; six wrong guesses and you are hanged." ^0
# [the generator, words won, words lost, words played since the list was used up]
[rng.Rng(2026), 0, 0, []] => game
True => running
while running:
    "" ^0
    "1) new word  2) seed  3) score  4) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        if play(game):
            game[1] + 1 => game[1]
        else:
            game[2] + 1 => game[2]
    elif choice == "2":
        ask_seed() => seed
        if seed == -1:
            "Cancelled." ^0
        else:
            rng.Rng(seed) => game[0]
            [] => game[3]
            ("Words now come from seed " + str(seed) + ".") ^0
    elif choice == "3":
        ("Won " + str(game[1]) + ", lost " + str(game[2]) + ".") ^0
    elif choice == "4":
        False => running
    else:
        "Pick a number from 1 to 4." ^0
"Bye." ^0
Python projection (main.py)
import words
import rng

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 lowered(s):
    big = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    small = "abcdefghijklmnopqrstuvwxyz"
    out = ""
    for ch in s:
        c = ch
        for k in range(0, 26):
            if ch == big[k]:
                c = small[k]
        out = out + c
    return out

def letters_only(s):
    if s == "":
        return False
    for ch in s:
        if not (ch >= "a" and ch <= "z"):
            return False
    return True

def has(s, ch):
    for x in s:
        if x == ch:
            return True
    return False

def counted(n, one, many):
    if n == 1:
        return "1 " + one
    return str(n) + " " + many

def gallows(wrong):
    head = " "
    body = "   "
    legs = "   "
    if wrong >= 1:
        head = "O"
    if wrong == 2:
        body = " | "
    elif wrong == 3:
        body = "/| "
    elif wrong >= 4:
        body = "/|\\"
    if wrong == 5:
        legs = "/  "
    elif wrong >= 6:
        legs = "/ \\"
    return ["  +---+", "  |   |", "  " + head + "   |", " " + body + "  |", " " + legs + "  |", "      |", "========="]

def pattern_of(secret, guessed):
    p = ""
    for ch in secret:
        if has(guessed, ch):
            p = p + ch
        else:
            p = p + "_"
    return p

def spaced(p):
    out = ""
    for ch in p:
        if out != "":
            out = out + " "
        out = out + ch
    return out

def listed(s):
    out = ""
    for ch in s:
        if out != "":
            out = out + ", "
        out = out + ch
    if out == "":
        return "none"
    return out

def pick(game):
    ws = words.word_list()
    if len(game[3]) == len(ws):
        game[3] = []
    while True:
        w = ws[game[0].below(len(ws))]
        played = False
        for p in game[3]:
            if p == w:
                played = True
        if not played:
            game[3] = game[3] + [w]
            return w

def hint(pattern, misses, guessed, ruled_out):
    fit = words.candidates(pattern, misses, ruled_out)
    if len(fit) == 1:
        print("Only one word in the list still fits.")
        return
    best = words.best_letters(words.letter_table(fit, guessed), 3)
    s = ""
    for b in best:
        if s != "":
            s = s + ", "
        s = s + b[0] + " (" + str(b[1]) + ")"
    print(str(len(fit)) + " words in the list still fit. The letters in the most of them: " + s + ".")

def play(game):
    secret = pick(game)
    print("A word of " + str(len(secret)) + " letters.")
    guessed = ""
    misses = ""
    ruled_out = []
    wrong = 0
    while True:
        pattern = pattern_of(secret, guessed)
        for line in gallows(wrong):
            print(line)
        print("Word: " + spaced(pattern) + "    missed: " + listed(misses) + "    wrong guesses left: " + str(6 - wrong))
        answer = lowered(trim(input("letter, the whole word, ? for a hint, or nothing to give up> ")))
        if answer == "":
            print("You give up - the word was " + secret + ".")
            return False
        if answer == "?":
            hint(pattern, misses, guessed, ruled_out)
        elif not letters_only(answer):
            print("Type a letter from a to z, the whole word, or ?.")
        elif len(answer) == 1:
            if has(guessed, answer):
                print("You have tried " + answer + " already.")
            else:
                guessed = guessed + answer
                n = 0
                for ch in secret:
                    if ch == answer:
                        n = n + 1
                if n > 0:
                    print("Yes - " + answer + " is in it " + counted(n, "time", "times") + ".")
                else:
                    misses = misses + answer
                    wrong = wrong + 1
                    print("No " + answer + ".")
        elif len(answer) != len(secret):
            print("The word has " + str(len(secret)) + " letters - that guess costs nothing.")
        elif answer == secret:
            guessed = secret
        else:
            ruled_out = ruled_out + [answer]
            wrong = wrong + 1
            print("Not " + answer + ".")
        if pattern_of(secret, guessed) == secret:
            print("You got it: " + secret + ", with " + counted(wrong, "wrong guess", "wrong guesses") + ".")
            return True
        if wrong == 6:
            for line in gallows(wrong):
                print(line)
            print("Hanged - the word was " + secret + ".")
            return False

def ask_seed():
    while True:
        answer = trim(input("seed (0 to 999999999)> "))
        if answer == "":
            return -1
        ok = True
        if len(answer) > 9:
            ok = False
        for ch in answer:
            if not (ch >= "0" and ch <= "9"):
                ok = False
        if ok:
            return int(answer)
        print("Type a number from 0 to 999999999.")

print("== Hangman ==")
print("Guess the word a letter at a time; six wrong guesses and you are hanged.")
game = [rng.Rng(2026), 0, 0, []]
running = True
while running:
    print("")
    print("1) new word  2) seed  3) score  4) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        if play(game):
            game[1] = game[1] + 1
        else:
            game[2] = game[2] + 1
    elif choice == "2":
        seed = ask_seed()
        if seed == -1:
            print("Cancelled.")
        else:
            game[0] = rng.Rng(seed)
            game[3] = []
            print("Words now come from seed " + str(seed) + ".")
    elif choice == "3":
        print("Won " + str(game[1]) + ", lost " + str(game[2]) + ".")
    elif choice == "4":
        running = False
    else:
        print("Pick a number from 1 to 4.")
print("Bye.")

words.eml

eml
# P051 hangman - the word list, and what the letters of the words that still
# fit say about the next guess.

def word_list():
    return ["anchor", "apple", "badger", "basket", "beacon", "bicycle", "blanket", "bottle", "bridge", "bucket",
            "butter", "cactus", "camera", "candle", "canyon", "carpet", "castle", "cherry", "chimney", "circus",
            "cobweb", "comet", "compass", "cookie", "cotton", "crayon", "dolphin", "donkey", "dragon", "drawer",
            "eagle", "engine", "falcon", "feather", "fiddle", "forest", "fossil", "garden", "giraffe", "glacier",
            "goblet", "guitar", "hammer", "harbor", "helmet", "hockey", "island", "jacket", "jigsaw", "jungle",
            "kettle", "kitten", "ladder", "lantern", "lemon", "lizard", "magnet", "marble", "meadow", "mitten",
            "monkey", "muffin", "needle", "nugget", "oyster", "paddle", "parrot", "pebble", "pepper", "pickle",
            "pillow", "pirate", "planet", "pocket", "puzzle", "rabbit", "ribbon", "rocket", "saddle", "salmon",
            "sandal", "shadow", "silver", "socket", "spider", "spoon", "squirrel", "stable", "string", "sugar",
            "teapot", "ticket", "tomato", "trumpet", "tunnel", "turtle", "umbrella", "valley", "velvet", "violin",
            "walnut", "window", "wizard", "yogurt", "zipper"]

def fits(word, pattern, misses):
    # Could word be the secret? pattern has a letter where one is known and
    # "_" elsewhere; misses are letters known not to be in it. A known
    # letter also cannot hide behind a "_": the game shows every place a
    # guessed letter stands.
    if len(word) != len(pattern):
        return False
    for i in [0:len(word) - 1]:
        word[i] => ch
        if pattern[i] != "_":
            if ch != pattern[i]:
                return False
        else:
            for m in misses:
                if ch == m:
                    return False
            for p in pattern:
                if ch == p:
                    return False
    return True

def candidates(pattern, misses, ruled_out):
    # The words that fit, leaving out whole words already guessed wrong.
    [] => out
    for w in word_list():
        if fits(w, pattern, misses):
            False => wrong
            for r in ruled_out:
                if r == w:
                    True => wrong
            if not wrong:
                out + [w] => out
    return out

def letter_table(words, guessed):
    # For every letter not guessed yet, in how many of the words it stands -
    # counted once a word, kept in the order letters first appear (the
    # check-then-insert tally of the corpus case char-frequency-table).
    {} => counts
    for w in words:
        "" => seen
        for ch in w:
            0 => known
            for g in guessed + seen:
                if g == ch:
                    1 => known
            if known == 0:
                seen + ch => seen
                if ch in counts:
                    counts[ch] + 1 => counts[ch]
                else:
                    1 => counts[ch]
    return counts

def best_letters(counts, k):
    # The k letters in the most words, most first; a tie goes to the letter
    # that comes first in the alphabet.
    [] => out
    "" => taken
    while len(out) < k and len(out) < len(counts):
        "" => pick
        0 => most
        for ch in counts:
            0 => used
            for t in taken:
                if t == ch:
                    1 => used
            if used == 0:
                if counts[ch] > most or (counts[ch] == most and ch < pick):
                    ch => pick
                    counts[ch] => most
        taken + pick => taken
        out + [[pick, most]] => out
    return out
Python projection (words.py)
def word_list():
    return ["anchor", "apple", "badger", "basket", "beacon", "bicycle", "blanket", "bottle", "bridge", "bucket", "butter", "cactus", "camera", "candle", "canyon", "carpet", "castle", "cherry", "chimney", "circus", "cobweb", "comet", "compass", "cookie", "cotton", "crayon", "dolphin", "donkey", "dragon", "drawer", "eagle", "engine", "falcon", "feather", "fiddle", "forest", "fossil", "garden", "giraffe", "glacier", "goblet", "guitar", "hammer", "harbor", "helmet", "hockey", "island", "jacket", "jigsaw", "jungle", "kettle", "kitten", "ladder", "lantern", "lemon", "lizard", "magnet", "marble", "meadow", "mitten", "monkey", "muffin", "needle", "nugget", "oyster", "paddle", "parrot", "pebble", "pepper", "pickle", "pillow", "pirate", "planet", "pocket", "puzzle", "rabbit", "ribbon", "rocket", "saddle", "salmon", "sandal", "shadow", "silver", "socket", "spider", "spoon", "squirrel", "stable", "string", "sugar", "teapot", "ticket", "tomato", "trumpet", "tunnel", "turtle", "umbrella", "valley", "velvet", "violin", "walnut", "window", "wizard", "yogurt", "zipper"]

def fits(word, pattern, misses):
    if len(word) != len(pattern):
        return False
    for i in range(0, len(word)):
        ch = word[i]
        if pattern[i] != "_":
            if ch != pattern[i]:
                return False
        else:
            for m in misses:
                if ch == m:
                    return False
            for p in pattern:
                if ch == p:
                    return False
    return True

def candidates(pattern, misses, ruled_out):
    out = []
    for w in word_list():
        if fits(w, pattern, misses):
            wrong = False
            for r in ruled_out:
                if r == w:
                    wrong = True
            if not wrong:
                out = out + [w]
    return out

def letter_table(words, guessed):
    counts = {}
    for w in words:
        seen = ""
        for ch in w:
            known = 0
            for g in guessed + seen:
                if g == ch:
                    known = 1
            if known == 0:
                seen = seen + ch
                if ch in counts:
                    counts[ch] = counts[ch] + 1
                else:
                    counts[ch] = 1
    return counts

def best_letters(counts, k):
    out = []
    taken = ""
    while len(out) < k and len(out) < len(counts):
        pick = ""
        most = 0
        for ch in counts:
            used = 0
            for t in taken:
                if t == ch:
                    used = 1
            if used == 0:
                if counts[ch] > most or counts[ch] == most and ch < pick:
                    pick = ch
                    most = counts[ch]
        taken = taken + pick
        out = out + [[pick, most]]
    return out

rng.eml

eml
# P051 hangman - 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 words 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
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

Built on these corpus cases