Number guessing
Guess the number at three levels, with higher/lower hints that narrow to a range, a limited number of guesses and a best score per level; the secret numbers come from a random generator written in EML - from a text menu.
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 number the program is thinking of, at three levels: easy (1 to 20, 5 guesses), normal (1 to 100, 7 guesses) and hard (1 to 1000, 10 guesses). Every miss says higher or lower, and from the third miss on the program also names the range the number must be in, narrowing it as you go. The fewest guesses of each level is kept as the best score. A text menu; the scores live while the program runs.
main.eml- the menu, the best scores and the seedgame.eml- one game: the levels, the guesses and their checks, the hints and the narrowing rangerng.eml- random numbers written in EML: a linear congruential generator (the constants of the C standard's examplerand()), taking a number from the high bits of its state, because the low bits of such a generator repeat with short periods
Python's random module is not used. A seed (2026 unless you set another) always gives the same games on every machine, which is also what lets the interpreter check a whole session against CPython byte for byte.
What is checked: a guess must be a whole number in the level's range - one that is not does not use up a guess; a seed must be a whole number.
Sessions: sessions/basic.in wins a normal game in five guesses with the range hint appearing, an easy game in three, loses a normal game, wins a hard game by halving, and shows the best scores; sessions/bad-input.in types an unknown choice, shows the empty best scores, types a seed that is a word, sets seed 7, types guesses that are words, 0, 21, empty and 5.5 (none of them counts), wins, and then wins in a single guess.
Built on the verified corpus case mvp-number-guessing-game (guesses answered with higher or lower, and the number of attempts counted).
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
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 9
Pick a number from 1 to 6.
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 4
-- best scores --
easy -
normal -
hard -
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 5
seed> abc
A seed is a whole number.
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 5
seed> 7
Seed set to 7.
== Number guessing (seed 7) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 1
I am thinking of a number from 1 to 20. You have 5 guesses.
guess 1/5> abc
Guess a whole number from 1 to 20.
guess 1/5> 0
Guess a whole number from 1 to 20.
guess 1/5> 21
Guess a whole number from 1 to 20.
guess 1/5>
Guess a whole number from 1 to 20.
guess 1/5> 5.5
Guess a whole number from 1 to 20.
guess 1/5> 10
Too high.
guess 2/5> 3
Too low.
guess 3/5> 5
Right! 5 in 3 guesses.
New best for easy: 3 guesses.
== Number guessing (seed 7) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 1
I am thinking of a number from 1 to 20. You have 5 guesses.
guess 1/5> 7
Right! 7 in 1 guess.
New best for easy: 1 guess.
== Number guessing (seed 7) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 4
-- best scores --
easy 1 guess
normal -
hard -
== Number guessing (seed 7) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 6
Bye.
What was typed (19 lines)
9
4
5
abc
5
7
1
abc
0
21
5.5
10
3
5
1
7
4
6
basic
interpreter: byte-equal
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 2
I am thinking of a number from 1 to 100. You have 7 guesses.
guess 1/7> 50
Too high.
guess 2/7> 25
Too high.
guess 3/7> 12
Too low. It is between 13 and 24.
guess 4/7> 18
Too low. It is between 19 and 24.
guess 5/7> 21
Right! 21 in 5 guesses.
New best for normal: 5 guesses.
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 1
I am thinking of a number from 1 to 20. You have 5 guesses.
guess 1/5> 10
Too low.
guess 2/5> 15
Too low.
guess 3/5> 19
Right! 19 in 3 guesses.
New best for easy: 3 guesses.
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 2
I am thinking of a number from 1 to 100. You have 7 guesses.
guess 1/7> 10
Too low.
guess 2/7> 20
Too low.
guess 3/7> 30
Too low. It is between 31 and 100.
guess 4/7> 40
Too low. It is between 41 and 100.
guess 5/7> 50
Too low. It is between 51 and 100.
guess 6/7> 60
Too low. It is between 61 and 100.
guess 7/7> 70
Too low. It is between 71 and 100.
Out of guesses - it was 88.
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 3
I am thinking of a number from 1 to 1000. You have 10 guesses.
guess 1/10> 500
Too low.
guess 2/10> 750
Too high.
guess 3/10> 625
Too high. It is between 501 and 624.
guess 4/10> 562
Too low. It is between 563 and 624.
guess 5/10> 593
Too high. It is between 563 and 592.
guess 6/10> 577
Too low. It is between 578 and 592.
guess 7/10> 585
Too high. It is between 578 and 584.
guess 8/10> 581
Too low. It is between 582 and 584.
guess 9/10> 583
Right! 583 in 9 guesses.
New best for hard: 9 guesses.
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 4
-- best scores --
easy 3 guesses
normal 5 guesses
hard 9 guesses
== Number guessing (seed 2026) ==
1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit
choice> 6
Bye.
What was typed (30 lines)
2
50
25
12
18
21
1
10
15
19
2
10
20
30
40
50
60
70
3
500
750
625
562
593
577
585
581
583
4
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# P008 number guessing: pick a level and guess the number. The best score of
# each level is kept while the program runs, and the seed of the random
# generator can be changed - the same seed always gives the same games.
import game
import rng
def plural(n):
if n == 1:
return "1 guess"
return str(n) + " guesses"
def show_best(best):
"" ^0
"-- best scores --" ^0
for i in [0:len(game.levels) - 1]:
(" " + ("%-8s" % game.levels[i][0])) => line
if best[i] == 0:
line + "-" => line
else:
line + plural(best[i]) => line
line ^0
2026 => seed
rng.Rng(seed) => r
[0 for level in game.levels] => best
True => running
while running:
"" ^0
("== Number guessing (seed " + str(seed) + ") ==") ^0
"1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit" ^0
game.trim(input("choice> ")) => choice
if choice == "1" or choice == "2" or choice == "3":
int(choice) - 1 => i
game.play(r, game.levels[i]) => used
if used > 0 and (best[i] == 0 or used < best[i]):
used => best[i]
("New best for " + game.levels[i][0] + ": " + plural(used) + ".") ^0
elif choice == "4":
show_best(best)
elif choice == "5":
game.number(game.trim(input("seed> "))) => s
if s < 0:
"A seed is a whole number." ^0
else:
s => seed
rng.Rng(seed) => r
("Seed set to " + str(seed) + ".") ^0
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import game
import rng
def plural(n):
if n == 1:
return "1 guess"
return str(n) + " guesses"
def show_best(best):
print("")
print("-- best scores --")
for i in range(0, len(game.levels)):
line = " " + "%-8s" % game.levels[i][0]
if best[i] == 0:
line = line + "-"
else:
line = line + plural(best[i])
print(line)
seed = 2026
r = rng.Rng(seed)
best = [0 for level in game.levels]
running = True
while running:
print("")
print("== Number guessing (seed " + str(seed) + ") ==")
print("1) easy 1-20 2) normal 1-100 3) hard 1-1000 4) best scores 5) new seed 6) quit")
choice = game.trim(input("choice> "))
if choice == "1" or choice == "2" or choice == "3":
i = int(choice) - 1
used = game.play(r, game.levels[i])
if used > 0 and (best[i] == 0 or used < best[i]):
best[i] = used
print("New best for " + game.levels[i][0] + ": " + plural(used) + ".")
elif choice == "4":
show_best(best)
elif choice == "5":
s = game.number(game.trim(input("seed> ")))
if s < 0:
print("A seed is a whole number.")
else:
seed = s
r = rng.Rng(seed)
print("Seed set to " + str(seed) + ".")
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
game.eml
eml# P008 number guessing - one game. The player has a limited number of
# guesses; a guess that is not a whole number in range does not use one.
# After every miss the game says higher or lower, and from the third miss on
# it also names the range the number must be in, which it keeps narrowing.
# [name, highest number, guesses allowed]
[
["easy", 20, 5],
["normal", 100, 7],
["hard", 1000, 10],
] => levels
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 number(s):
# The value of s if it is digits only (at least one), otherwise -1.
if s == "":
return -1
0 => n
for c in s:
if not (c in "0123456789"):
return -1
n * 10 + int(c) => n
return n
def play(rng, level):
# One game at this level. Returns the number of guesses it took, or 0 if
# the guesses ran out.
level[1] => top
level[2] => allowed
1 + rng.below(top) => secret
("I am thinking of a number from 1 to " + str(top) + ". You have " + str(allowed) + " guesses.") ^0
1 => low
top => high
0 => used
while used < allowed:
number(trim(input("guess " + str(used + 1) + "/" + str(allowed) + "> "))) => g
if g < 1 or g > top:
("Guess a whole number from 1 to " + str(top) + ".") ^0
else:
used + 1 => used
if g == secret:
if used == 1:
("Right! " + str(secret) + " in 1 guess.") ^0
else:
("Right! " + str(secret) + " in " + str(used) + " guesses.") ^0
return used
if g < secret:
"Too low." => hint
if g + 1 > low:
g + 1 => low
else:
"Too high." => hint
if g - 1 < high:
g - 1 => high
if used >= 3:
hint + " It is between " + str(low) + " and " + str(high) + "." => hint
hint ^0
("Out of guesses - it was " + str(secret) + ".") ^0
return 0
Python projection (game.py)
levels = [["easy", 20, 5], ["normal", 100, 7], ["hard", 1000, 10]]
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 number(s):
if s == "":
return -1
n = 0
for c in s:
if not c in "0123456789":
return -1
n = n * 10 + int(c)
return n
def play(rng, level):
top = level[1]
allowed = level[2]
secret = 1 + rng.below(top)
print("I am thinking of a number from 1 to " + str(top) + ". You have " + str(allowed) + " guesses.")
low = 1
high = top
used = 0
while used < allowed:
g = number(trim(input("guess " + str(used + 1) + "/" + str(allowed) + "> ")))
if g < 1 or g > top:
print("Guess a whole number from 1 to " + str(top) + ".")
else:
used = used + 1
if g == secret:
if used == 1:
print("Right! " + str(secret) + " in 1 guess.")
else:
print("Right! " + str(secret) + " in " + str(used) + " guesses.")
return used
if g < secret:
hint = "Too low."
if g + 1 > low:
low = g + 1
else:
hint = "Too high."
if g - 1 < high:
high = g - 1
if used >= 3:
hint = hint + " It is between " + str(low) + " and " + str(high) + "."
print(hint)
print("Out of guesses - it was " + str(secret) + ".")
return 0
rng.eml
eml# P008 number guessing - random numbers written in EML: a linear
# congruential generator with the constants of the C standard's example
# rand(). Python's random module is not used, so a seed gives the same games
# 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