Rock paper scissors
Rock paper scissors against a computer that learns how often you play each move and what you play after each, guessing your next move and playing what beats it; fixed strategies played for hundreds of rounds; and what it has learned about you.
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
Rock paper scissors against a computer that learns how you play. You can play it yourself, or let a fixed strategy play it for up to 300 rounds, and see what it has learned about you.
main.eml- the menu, playing rounds, the strategies, and what it has learnedrps.eml- the rules, the computer's memory and guess, one round, and the fixed strategies
How each part works:
- The rules, as in the corpus case
rock-paper-scissors-simulator, here with moves as 0 rock, 1 paper, 2 scissors: (m + 1) % 3 beats m, and (you - computer + 3) % 3 says who won. - The computer counts how often you play each move, and what you play after each move. To guess your next move it looks at what you have played after your last one; with nothing there, at what you play most; with nothing at all, it picks at random. Then it plays what beats the guess. It chooses before it sees your move. A tie between equally common moves is broken at random, from a linear congruential generator's top 16 bits, so the order of the moves cannot steer it.
- What it can and cannot learn: a cycle (rock, paper, scissors, ...) or always the same move, it learns within a few rounds and then wins almost every time. Against random moves it can do no better than a third. It only remembers your own last move, so a strategy built on ITS last move - playing what beats it, or copying it - is something it cannot see. Over 300 rounds it wins only 41% and 43% of those.
What is checked: menu choices 1 to 5; moves as rock, paper, scissors or r, p, s, an empty answer stopping; a strategy of cycle, rock, copy, beat-last or random; 10 to 300 rounds.
Sessions: sessions/basic.in gives: - nine rounds of rock, paper, scissors over and over: the computer wins the first by luck, draws three while it learns, then wins every round; - what it learned from them; - 300 rounds each of cycle (99.0% to the computer), random (30.7%), beat-last (41.3%) and copy (43.0%), and 30 of rock (100%); - forgetting, then nothing learned.
sessions/bad-input.in gives: - menu choices 0 and x; - the moves rk and lizard, then stopping after no rounds; - the strategy mirror, then 9, 301 and abc rounds, and two cancels.
Built on the verified corpus case rock-paper-scissors-simulator (who beats whom, and keeping score).
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== Rock paper scissors ==
Against a computer that learns how often you play each move, and what you play after each.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 0
Pick a number from 1 to 5.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> x
Pick a number from 1 to 5.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 1
your move (rock, paper, scissors, or r, p, s)> rk
Type rock, paper or scissors (or r, p, s); an empty answer stops.
your move (rock, paper, scissors, or r, p, s)> lizard
Type rock, paper or scissors (or r, p, s); an empty answer stops.
your move (rock, paper, scissors, or r, p, s)>
Stopped after 0 rounds. You 0, computer 0, draws 0.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)> mirror
Type cycle, rock, copy, beat-last or random.
strategy (cycle, rock, copy, beat-last or random)> cycle
rounds (10 to 300)> 9
Type a whole number from 10 to 300.
rounds (10 to 300)> 301
Type a whole number from 10 to 300.
rounds (10 to 300)> abc
Type a whole number from 10 to 300.
rounds (10 to 300)>
Cancelled.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)>
Cancelled.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 5
Bye.
What was typed (16 lines)
0
x
1
rk
lizard
2
mirror
cycle
9
301
abc
2
5
basic
interpreter: byte-equal== Rock paper scissors ==
Against a computer that learns how often you play each move, and what you play after each.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 1
your move (rock, paper, scissors, or r, p, s)> r
You rock, computer paper - it guessed rock at random, with nothing to go on. The computer wins.
You 0, computer 1, draws 0.
your move (rock, paper, scissors, or r, p, s)> p
You paper, computer paper - it guessed rock from what you play most. A draw.
You 0, computer 1, draws 1.
your move (rock, paper, scissors, or r, p, s)> s
You scissors, computer scissors - it guessed paper from what you play most. A draw.
You 0, computer 1, draws 2.
your move (rock, paper, scissors, or r, p, s)> r
You rock, computer rock - it guessed scissors from what you play most. A draw.
You 0, computer 1, draws 3.
your move (rock, paper, scissors, or r, p, s)> p
You paper, computer scissors - it guessed paper from what you played after rock. The computer wins.
You 0, computer 2, draws 3.
your move (rock, paper, scissors, or r, p, s)> s
You scissors, computer rock - it guessed scissors from what you played after paper. The computer wins.
You 0, computer 3, draws 3.
your move (rock, paper, scissors, or r, p, s)> rock
You rock, computer paper - it guessed rock from what you played after scissors. The computer wins.
You 0, computer 4, draws 3.
your move (rock, paper, scissors, or r, p, s)> paper
You paper, computer scissors - it guessed paper from what you played after rock. The computer wins.
You 0, computer 5, draws 3.
your move (rock, paper, scissors, or r, p, s)> scissors
You scissors, computer rock - it guessed scissors from what you played after paper. The computer wins.
You 0, computer 6, draws 3.
your move (rock, paper, scissors, or r, p, s)>
Stopped after 9 rounds. You 0, computer 6, draws 3.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 3
After 9 rounds:
rock paper scissors
you played 3 3 3
after rock 0 3 0
after paper 0 0 3
after scissors 2 0 0
Next it guesses rock from what you played after scissors, so it will play paper.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)> cycle
rounds (10 to 300)> 300
300 rounds of cycle against a fresh computer: strategy 0, computer 297, draws 3.
The computer won 99.0% of the rounds - against moves chosen at random it could expect a third.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)> rock
rounds (10 to 300)> 30
30 rounds of rock against a fresh computer: strategy 0, computer 30, draws 0.
The computer won 100.0% of the rounds - against moves chosen at random it could expect a third.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)> random
rounds (10 to 300)> 300
300 rounds of random against a fresh computer: strategy 112, computer 92, draws 96.
The computer won 30.7% of the rounds - against moves chosen at random it could expect a third.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)> beat-last
rounds (10 to 300)> 300
300 rounds of beat-last against a fresh computer: strategy 135, computer 124, draws 41.
The computer won 41.3% of the rounds - against moves chosen at random it could expect a third.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 2
strategy (cycle, rock, copy, beat-last or random)> copy
rounds (10 to 300)> 300
300 rounds of copy against a fresh computer: strategy 31, computer 129, draws 140.
The computer won 43.0% of the rounds - against moves chosen at random it could expect a third.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 4
It has forgotten everything.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 3
Nothing yet: no rounds played.
1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit
choice> 5
Bye.
What was typed (30 lines)
1
r
p
s
r
p
s
rock
paper
scissors
3
2
cycle
300
2
rock
30
2
random
300
2
beat-last
300
2
copy
300
4
3
5
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# P080 rock paper scissors against a computer that learns: play it
# yourself, or let a fixed strategy play it for many rounds, and see what
# it has learned about you.
import rps
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):
if len(s) == 0 or len(s) > 3:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def lpad(s, n):
while len(s) < n:
" " + s => s
return s
def pad(s, n):
while len(s) < n:
s + " " => s
return s
["rock", "paper", "scissors"] => NAMES
def move_of(s):
if s == "rock" or s == "r":
return 0
if s == "paper" or s == "p":
return 1
if s == "scissors" or s == "s":
return 2
return -1
def percent(a, b):
# a / b as a percentage to one decimal, rounded half up.
2000 * a + b => n
2 * b => d
0 => q
while n >= d:
n - d => n
q + 1 => q
0 => whole
q => r
while r >= 10:
r - 10 => r
whole + 1 => whole
return str(whole) + "." + str(r) + "%"
def score(mem):
return "You " + str(mem[5]) + ", computer " + str(mem[6]) + ", draws " + str(mem[7]) + "."
def why(how, last):
if how == "after":
return "from what you played after " + NAMES[last]
if how == "often":
return "from what you play most"
return "at random, with nothing to go on"
def play(mem):
0 => k
while True:
trim(input("your move (rock, paper, scissors, or r, p, s)> ")) => s
if s == "":
("Stopped after " + str(k) + " rounds. " + score(mem)) ^0
return mem
move_of(s) => you
if you == -1:
"Type rock, paper or scissors (or r, p, s); an empty answer stops." ^0
else:
mem[2] => last
rps.play(mem, you) => r
r[0] => mem
k + 1 => k
["A draw.", "You win.", "The computer wins."] => said
("You " + NAMES[you] + ", computer " + NAMES[r[1]] + " - it guessed " + NAMES[r[2]] + " " + why(r[3], last) + ". " + said[r[4]]) ^0
score(mem) ^0
def auto(mem):
while True:
trim(input("strategy (cycle, rock, copy, beat-last or random)> ")) => name
if name == "":
"Cancelled." ^0
return mem
if name == "cycle" or name == "rock" or name == "copy" or name == "beat-last" or name == "random":
while True:
trim(input("rounds (10 to 300)> ")) => a
if a == "":
"Cancelled." ^0
return mem
number(a) => n
if n >= 10 and n <= 300:
rps.new_memory(2026) => m
-1 => last_cpu
99 => x
for k in [0:n - 1]:
rps.strategy_move(name, k, last_cpu, x) => sm
sm[1] => x
rps.play(m, sm[0]) => r
r[0] => m
r[1] => last_cpu
(str(n) + " rounds of " + name + " against a fresh computer: strategy " + str(m[5]) + ", computer " + str(m[6]) + ", draws " + str(m[7]) + ".") ^0
("The computer won " + percent(m[6], n) + " of the rounds - against moves chosen at random it could expect a third.") ^0
return mem
"Type a whole number from 10 to 300." ^0
"Type cycle, rock, copy, beat-last or random." ^0
def learned(mem):
if mem[4] == 0:
"Nothing yet: no rounds played." ^0
return
("After " + str(mem[4]) + " rounds:") ^0
(" " + pad("", 16) + lpad("rock", 6) + lpad("paper", 7) + lpad("scissors", 10)) ^0
(" " + pad("you played", 16) + lpad(str(mem[0][0]), 6) + lpad(str(mem[0][1]), 7) + lpad(str(mem[0][2]), 10)) ^0
for m in [0:2]:
mem[1][m] => row
(" " + pad("after " + NAMES[m], 16) + lpad(str(row[0]), 6) + lpad(str(row[1]), 7) + lpad(str(row[2]), 10)) ^0
rps.guess(mem) => g
("Next it guesses " + NAMES[g[0]] + " " + why(g[1], mem[2]) + ", so it will play " + NAMES[rps.beats(g[0])] + ".") ^0
rps.new_memory(2026) => memory
"== Rock paper scissors ==" ^0
"Against a computer that learns how often you play each move, and what you play after each." ^0
True => running
while running:
"" ^0
"1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
play(memory) => memory
elif choice == "2":
auto(memory) => memory
elif choice == "3":
learned(memory)
elif choice == "4":
rps.new_memory(2026) => memory
"It has forgotten everything." ^0
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import rps
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 len(s) == 0 or len(s) > 3:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def lpad(s, n):
while len(s) < n:
s = " " + s
return s
def pad(s, n):
while len(s) < n:
s = s + " "
return s
NAMES = ["rock", "paper", "scissors"]
def move_of(s):
if s == "rock" or s == "r":
return 0
if s == "paper" or s == "p":
return 1
if s == "scissors" or s == "s":
return 2
return -1
def percent(a, b):
n = 2000 * a + b
d = 2 * b
q = 0
while n >= d:
n = n - d
q = q + 1
whole = 0
r = q
while r >= 10:
r = r - 10
whole = whole + 1
return str(whole) + "." + str(r) + "%"
def score(mem):
return "You " + str(mem[5]) + ", computer " + str(mem[6]) + ", draws " + str(mem[7]) + "."
def why(how, last):
if how == "after":
return "from what you played after " + NAMES[last]
if how == "often":
return "from what you play most"
return "at random, with nothing to go on"
def play(mem):
k = 0
while True:
s = trim(input("your move (rock, paper, scissors, or r, p, s)> "))
if s == "":
print("Stopped after " + str(k) + " rounds. " + score(mem))
return mem
you = move_of(s)
if you == -1:
print("Type rock, paper or scissors (or r, p, s); an empty answer stops.")
else:
last = mem[2]
r = rps.play(mem, you)
mem = r[0]
k = k + 1
said = ["A draw.", "You win.", "The computer wins."]
print("You " + NAMES[you] + ", computer " + NAMES[r[1]] + " - it guessed " + NAMES[r[2]] + " " + why(r[3], last) + ". " + said[r[4]])
print(score(mem))
def auto(mem):
while True:
name = trim(input("strategy (cycle, rock, copy, beat-last or random)> "))
if name == "":
print("Cancelled.")
return mem
if name == "cycle" or name == "rock" or name == "copy" or name == "beat-last" or name == "random":
while True:
a = trim(input("rounds (10 to 300)> "))
if a == "":
print("Cancelled.")
return mem
n = number(a)
if n >= 10 and n <= 300:
m = rps.new_memory(2026)
last_cpu = -1
x = 99
for k in range(0, n):
sm = rps.strategy_move(name, k, last_cpu, x)
x = sm[1]
r = rps.play(m, sm[0])
m = r[0]
last_cpu = r[1]
print(str(n) + " rounds of " + name + " against a fresh computer: strategy " + str(m[5]) + ", computer " + str(m[6]) + ", draws " + str(m[7]) + ".")
print("The computer won " + percent(m[6], n) + " of the rounds - against moves chosen at random it could expect a third.")
return mem
print("Type a whole number from 10 to 300.")
print("Type cycle, rock, copy, beat-last or random.")
def learned(mem):
if mem[4] == 0:
print("Nothing yet: no rounds played.")
return
print("After " + str(mem[4]) + " rounds:")
print(" " + pad("", 16) + lpad("rock", 6) + lpad("paper", 7) + lpad("scissors", 10))
print(" " + pad("you played", 16) + lpad(str(mem[0][0]), 6) + lpad(str(mem[0][1]), 7) + lpad(str(mem[0][2]), 10))
for m in range(0, 3):
row = mem[1][m]
print(" " + pad("after " + NAMES[m], 16) + lpad(str(row[0]), 6) + lpad(str(row[1]), 7) + lpad(str(row[2]), 10))
g = rps.guess(mem)
print("Next it guesses " + NAMES[g[0]] + " " + why(g[1], mem[2]) + ", so it will play " + NAMES[rps.beats(g[0])] + ".")
memory = rps.new_memory(2026)
print("== Rock paper scissors ==")
print("Against a computer that learns how often you play each move, and what you play after each.")
running = True
while running:
print("")
print("1) play 2) let a strategy play 3) what it has learned 4) make it forget 5) quit")
choice = trim(input("choice> "))
if choice == "1":
memory = play(memory)
elif choice == "2":
memory = auto(memory)
elif choice == "3":
learned(memory)
elif choice == "4":
memory = rps.new_memory(2026)
print("It has forgotten everything.")
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
rps.eml
eml# P080 rock paper scissors - a computer that learns how you play. It counts
# how often you play each move, and what you play after each move; it
# guesses your next move from what you played last (or, with nothing to go
# on, from how often you play each), and plays what beats it. Moves are 0
# rock, 1 paper, 2 scissors; (m + 1) % 3 beats m.
def beats(m):
return (m + 1) % 3
def result(you, cpu):
# 0 a draw, 1 you win, 2 the computer wins.
return (you - cpu + 3) % 3
def draw(x):
# One step of a linear congruential generator (Numerical Recipes'
# constants, mod 2^32): [next state, its top 16 bits]; x / 65536 is a
# division by a power of two below 2^53, so it is exact.
(1664525 * x + 1013904223) % 4294967296 => x
return [x, int(x / 65536)]
def new_memory(seed):
# [how often each move, after each move how often each next move, your
# last move (-1 none), the random state, rounds, your wins, its wins,
# draws, right guesses].
return [[0, 0, 0], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], -1, seed, 0, 0, 0, 0, 0]
def most(counts, x):
# The move with the highest count; a tie is broken at random, so the
# guess cannot be steered by the order of the moves. [move, state].
0 => top
for c in counts:
if c > top:
c => top
[] => best
for m in [0:2]:
if counts[m] == top:
best + [m] => best
if len(best) == 1:
return [best[0], x]
draw(x) => d
return [best[d[1] % len(best)], d[0]]
def guess(mem):
# The guess and why: [move, "after" | "often" | "random", state].
mem[3] => x
if mem[2] >= 0:
mem[1][mem[2]] => after
if after[0] + after[1] + after[2] > 0:
most(after, x) => g
return [g[0], "after", g[1]]
if mem[0][0] + mem[0][1] + mem[0][2] > 0:
most(mem[0], x) => g
return [g[0], "often", g[1]]
draw(x) => d
return [d[1] % 3, "random", d[0]]
def play(mem, you):
# One round: the computer commits to its move before seeing yours,
# then learns from yours. Returns [memory, its move, its guess, why,
# result].
guess(mem) => g
beats(g[0]) => cpu
result(you, cpu) => r
[mem[0][0], mem[0][1], mem[0][2]] => often
often[you] + 1 => often[you]
[[mem[1][0][0], mem[1][0][1], mem[1][0][2]], [mem[1][1][0], mem[1][1][1], mem[1][1][2]], [mem[1][2][0], mem[1][2][1], mem[1][2][2]]] => after
if mem[2] >= 0:
after[mem[2]][you] + 1 => after[mem[2]][you]
[often, after, you, g[2], mem[4] + 1, mem[5], mem[6], mem[7], mem[8]] => out
if r == 1:
out[5] + 1 => out[5]
elif r == 2:
out[6] + 1 => out[6]
else:
out[7] + 1 => out[7]
if g[0] == you:
out[8] + 1 => out[8]
return [out, cpu, g[0], g[1], r]
def strategy_move(name, k, last_cpu, x):
# What a fixed strategy plays in round k (from 0): [move, state].
if name == "cycle":
return [k % 3, x]
if name == "rock":
return [0, x]
if name == "copy":
if last_cpu == -1:
return [0, x]
return [last_cpu, x]
if name == "beat-last":
if last_cpu == -1:
return [0, x]
return [beats(last_cpu), x]
draw(x) => d
return [d[1] % 3, d[0]]
Python projection (rps.py)
def beats(m):
return (m + 1) % 3
def result(you, cpu):
return (you - cpu + 3) % 3
def draw(x):
x = (1664525 * x + 1013904223) % 4294967296
return [x, int(x / 65536)]
def new_memory(seed):
return [[0, 0, 0], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], -1, seed, 0, 0, 0, 0, 0]
def most(counts, x):
top = 0
for c in counts:
if c > top:
top = c
best = []
for m in range(0, 3):
if counts[m] == top:
best = best + [m]
if len(best) == 1:
return [best[0], x]
d = draw(x)
return [best[d[1] % len(best)], d[0]]
def guess(mem):
x = mem[3]
if mem[2] >= 0:
after = mem[1][mem[2]]
if after[0] + after[1] + after[2] > 0:
g = most(after, x)
return [g[0], "after", g[1]]
if mem[0][0] + mem[0][1] + mem[0][2] > 0:
g = most(mem[0], x)
return [g[0], "often", g[1]]
d = draw(x)
return [d[1] % 3, "random", d[0]]
def play(mem, you):
g = guess(mem)
cpu = beats(g[0])
r = result(you, cpu)
often = [mem[0][0], mem[0][1], mem[0][2]]
often[you] = often[you] + 1
after = [[mem[1][0][0], mem[1][0][1], mem[1][0][2]], [mem[1][1][0], mem[1][1][1], mem[1][1][2]], [mem[1][2][0], mem[1][2][1], mem[1][2][2]]]
if mem[2] >= 0:
after[mem[2]][you] = after[mem[2]][you] + 1
out = [often, after, you, g[2], mem[4] + 1, mem[5], mem[6], mem[7], mem[8]]
if r == 1:
out[5] = out[5] + 1
elif r == 2:
out[6] = out[6] + 1
else:
out[7] = out[7] + 1
if g[0] == you:
out[8] = out[8] + 1
return [out, cpu, g[0], g[1], r]
def strategy_move(name, k, last_cpu, x):
if name == "cycle":
return [k % 3, x]
if name == "rock":
return [0, x]
if name == "copy":
if last_cpu == -1:
return [0, x]
return [last_cpu, x]
if name == "beat-last":
if last_cpu == -1:
return [0, x]
return [beats(last_cpu), x]
d = draw(x)
return [d[1] % 3, d[0]]