How to build a world-class rock-paper-scissors bot
@notthepoint
@notthepoint
dorothy wingrove
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
@notthepoint
Greedy
@notthepoint
content
@notthepoint
sly
@notthepoint
@notthepoint
Crystal ball
@notthepoint
Difficulty:
tattoo play
@notthepoint
Difficulty:
Ishikawa Watanabe Laboratory, the University of Tokyo (25th June 2012) Janken (rock-paper-scissors) Robot with 100% winning rate Retrieved from: https://www.youtube.com/watch?v=3nxjjztQKtY&feature=youtu.be
Full rights to Ishikawa Watanabe Laboratory
@notthepoint
imperfect information
@notthepoint
zero sum
@notthepoint
@notthepoint
19
99
# random bot
my_move_history = []
their_move_history = []
def next_turn(their_last_move)
['r','p','s'].sample
end
@notthepoint
# always rock bot
my_move_history = []
their_move_history = []
def next_turn(their_last_move)
'r'
end
@notthepoint
# frequency counting bot
their_move_history = {
'r' => 0,
'p' => 0,
's' => 0
}
counter_moves = { 'r' => 'p', 'p' => 's', 's' => 'r' }
def next_turn(their_last_move)
their_move_history[their_last_move] += 1
their_move = their_move_history.max_by{ |_,v| v }[0]
counter_moves[their_move]
end
@notthepoint
# Fixed string bot
string = 'rrpspprss'
current_position = -1
def next_move(opp_prev_move)
current_position =
(current_position + 1) % string.length
string[current_position]
end
@notthepoint
De Bruijn
a de Bruijn sequence of order n on a size-k alphabet A is a cyclic sequence in which every possible length-n string on A occurs exactly once as a substring
@notthepoint
rrpspprss
@notthepoint
A = {r, p, s} B = (3, 2)
our set
set length
substring length
A = {0, 1} B = (2, 3)
00010111
11101000
@notthepoint
A = {1,2,3,4} B = (4, 4)
@notthepoint
@notthepoint
0000100020003001100120013002100220023003100320033010102010301110112011301210122012301310132013302020302110212021302210222022302310232023303031103120313032103220323033103320333111121113112211231132113312121312221223123212331313221323133213332222322332323333
A = {r,p,s} B = (3, 6)
@notthepoint
@notthepoint
rrrrrrprrrrrsrrrrpprrrrpsrrrrsprrrrssrrrprprrrprsrrrppprrrppsrrrpsprrrpssrrrsrprrrsrsrrrspprrrspsrrrssprrrsssrrprrprrsrrprpprrprpsrrprsprrprssrrpprprrpprsrrpppprrpppsrrppsprrppssrrpsrprrpsrsrrpspprrpspsrrpssprrpsssrrsrrsrpprrsrpsrrsrsprrsrssrrsprprrsprsrrsppprrsppsrrspsprrspssrrssrprrssrsrrsspprrsspsrrsssprrssssrprprprsrprppprprppsrprpsprprpssrprsrsrprspprprspsrprssprprsssrpprpprpsrpprsprpprssrppprsrppppprppppsrpppsprpppssrppsrsrppspprppspsrppssprppsssrpsrpsrsprpsrssrpsprsrpsppprpsppsrpspsprpspssrpssrsrpsspprpsspsrpsssprpssssrsrsrspprsrspsrsrssprsrsssrsprsprssrspppprspppsrsppsprsppssrspspprspspsrspssprspsssrssrssppprssppsrsspsprsspssrssspprssspsrssssprsssssppppppsppppsspppspspppsssppsppspssppsspsppsssspspspssspsspssssss
### de Bruijn sequence for alphabet k and subsequences of length n.
def de_bruijn(k, n)
@k, @n = k, n
if @k.is_a?(String) # if k is a string, take k as the full alphabet
@length = @k.length
alphabet = @k
else
@length = @k
alphabet = [*0..@length-1]
end
@a = [0] * @length * @n
@sequence = []
def db(t, p)
if t > @n
@sequence = *(@sequence + @a[1..p]) if @n % p == 0
else
@a[t] = @a[t - p]
db(t + 1, p)
((@a[t - p] + 1)..@length-1).each do |j|
@a[t] = j
db(t + 1, t)
end
end
end
db(1, 1)
@sequence.map { |i| alphabet[i].to_s }.join('')
end
@notthepoint
r | r | r | p | s | p | p | r | s | s |
---|
Count: 1
s | s | p | p | s | p | r | s | s | r |
---|
s | r | r | r | r | p | s | p | p | r |
---|
first move
latest move
@notthepoint
Count: 1
first move
latest move
s | r | r | r | r | p | s | p | p | r |
---|
s | s | p | p | s | p | r | s | s | r |
---|
r | r | r | p | s | p | p | r | s | s |
---|
@notthepoint
Count: 2
first move
latest move
s | r | r | r | r | p | s | p | p | r |
---|
s | s | p | p | s | p | r | s | s | r |
---|
r | r | r | p | s | p | p | r | s | s |
---|
@notthepoint
Count: 2
first move
latest move
s | r | r | r | r | p | s | p | p | r |
---|
s | s | p | p | s | p | r | s | s | r |
---|
r | r | r | p | s | p | p | r | s | s |
---|
@notthepoint
WINDOW SIZE | COUNT | FOLLOWING MOVES |
---|---|---|
10 | 1 | p |
9 | 1 | p |
8 | 1 | p |
7 | 1 | p |
6 | 1 | p |
5 | 1 | p |
4 | 2 | p, r |
3 | 2 | p, r |
2 | 2 | p, r |
@notthepoint
# basic direct history bot
their_move_history = ""
counter_moves = { 'r' => 'p', 'p' => 's', 's' => 'r' }
def next_turn(their_last_move)
their_move_history << their_last_move
regexp = /([rps]{3})([rps])[rps]*\1$/
matches = regexp.match(their_move_str)
their_move = matches.captures[1]
counter_moves[their_move]
end
@notthepoint
end of the line
get first capture
spsrrrspssssrrr
([rps]{3})([rps])[rps]*\1$
@notthepoint
([rps]{3})([rps])[rps]*\1$
exactly 3
spsrrrspssssrrr
@notthepoint
spsrrrspssssrrr
([rps]{3})([rps])[rps]*\1$
@notthepoint
0 or more
([rps]{3})([rps])[rps]*\1$
spsrrrspssssrrr
@notthepoint
biggest window size
@notthepoint
# direct history bot, biggest window size
@their_move_history = "ssrssrpssprrps"
@counter_moves = { 'r' => 'p', 'p' => 's', 's' => 'r' }
def next_turn(their_last_move)
@their_move_history << their_last_move
their_move = nil
5.downto(2).each do |i|
regexp = /([rps]{#{i}})([rps])[rps]*\1$/
matches = regexp.match(@their_move_history)
(their_move = matches.captures[1] and break) if matches
end
their_move = fallback_method unless their_move
@counter_moves[their_move]
end
@notthepoint
greatest number of matches
@notthepoint
# direct history bot, greatest number of matches
@their_move_history = "ssrssrpssprrps"
@counter_moves = { 'r' => 'p', 'p' => 's', 's' => 'r' }
def next_turn(their_last_move)
@their_move_history << their_last_move
possible_moves = {}
5.downto(2).each do |x|
str = @their_move_history[-x..-1]
matches = @their_move_history.scan(/(?=#{str}([rps]))/)
match_counts = matches.count
if match_counts > 0
possible_moves[i] = { count: match_counts, following_move: matches[0][0] }
end
end
if possible_moves.length > 0
their_move = possible_moves.max_by { |k, v| v[:count] }[1][:following_move]
else
their_move = fallback_method
end
@counter_moves[their_move]
end
@notthepoint
frequency of following move
@notthepoint
# direct history bot, frequency of following move
@their_move_history = "ssrssrpssprrps"
@counter_moves = { 'r' => 'p', 'p' => 's', 's' => 'r' }
def next_turn(their_last_move)
@their_move_history << their_last_move
possible_moves = { 'r' => 0, 'p' => 0, 's' => 0 }
(2..5).each do |i|
regexp = /([rps]{#{i}})([rps])[rps]*\1$/
matches = regexp.match(@their_move_history)
possible_moves[matches.captures[1]] += 1 if matches
end
their_move = possible_moves.max_by{ |k,v| v }[0]
@counter_moves[their_move]
end
@notthepoint
@notthepoint
STRATEGIES |
---|
Random |
Frequency counting |
Direct history - fixed 'window' size |
Direct history - highest frequency of matches |
Direct history - highest frequency of following move |
Direct history - longest repeating pattern |
STRATEGIES | |
---|---|
Random | 0 |
Frequency counting | 0 |
Direct history - fixed 'window' size | 0 |
Direct history - highest frequency of matches | 0 |
Direct history - highest frequency of following move | 0 |
Direct history - longest repeating pattern | 0 |
@notthepoint
@notthepoint
@notthepoint
A
B
@notthepoint
@notthepoint
A
B
A'
@notthepoint
@notthepoint
A
B
A'
B'
@notthepoint
@notthepoint
A'
B'
@notthepoint
@notthepoint
A
B
A'
B'
A''
@notthepoint
A
B
A'
B'
A''
B''
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
|||
Frequency counter |
|||
Direct history - fixed window size | |||
Direct history - highest frequency of matches | |||
Direct history - highest frequency of following move | |||
Direct history - longest repeating pattern |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
0 | 0 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 0 | 0 | 0 |
Direct history - highest frequency of matches | 0 | 0 | 0 |
Direct history - highest frequency of following move | 0 | 0 | 0 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
0 | 0 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 1 | 0 | 0 |
Direct history - highest frequency of matches | 0 | 0 | 0 |
Direct history - highest frequency of following move | 0 | 0 | 0 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
0 | 1 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 1 | 0 | 0 |
Direct history - highest frequency of matches | 0 | 0 | 1 |
Direct history - highest frequency of following move | 0 | 1 | 0 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
0 | 1 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 2 | 0 | 0 |
Direct history - highest frequency of matches | 0 | 0 | 1 |
Direct history - highest frequency of following move | 0 | 2 | 1 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
1 | 1 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 2 | 0 | 0 |
Direct history - highest frequency of matches | 1 | 0 | 1 |
Direct history - highest frequency of following move | 0 | 3 | 2 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
1 | 1 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 1 | -1 | 0 |
Direct history - highest frequency of matches | 0 | 0 | 1 |
Direct history - highest frequency of following move | 0 | 3 | 2 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
1 | 1 | 0 |
Frequency counter |
0 | 0 | 0 |
Direct history - fixed window size | 1 | -1 | 0 |
Direct history - highest frequency of matches | 0 | 0 | 1 |
Direct history - highest frequency of following move | 0 | 3 | 2 |
Direct history - longest repeating pattern | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
345 | 300 | 285 |
Frequency counter |
-80 | 449 | 287 |
Direct history - fixed window size | 175 | -274 | 332 |
Direct history - highest frequency of matches | 221 | -10 | 373 |
Direct history - highest frequency of following move | 45 | 134 | -90 |
Direct history - longest repeating pattern | 442 | -56 | 149 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
344 | 301 | 285 |
Frequency counter |
-81 | 450 | 287 |
Direct history - fixed window size | 175 | -275 | 333 |
Direct history - highest frequency of matches | 220 | -9 | 373 |
Direct history - highest frequency of following move | 46 | 134 | -91 |
Direct history - longest repeating pattern | 443 | -56 | 148 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
343 | 302 | 285 |
Frequency counter |
-82 | 451 | 287 |
Direct history - fixed window size | 174 | -274 | 333 |
Direct history - highest frequency of matches | 220 | -10 | 374 |
Direct history - highest frequency of following move | 45 | 135 | -91 |
Direct history - longest repeating pattern | 443 | -57 | 149 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
343 | 301 | 286 |
Frequency counter |
-83 | 452 | 287 |
Direct history - fixed window size | 175 | -274 | 332 |
Direct history - highest frequency of matches | 221 | -10 | 373 |
Direct history - highest frequency of following move | 44 | 136 | -91 |
Direct history - longest repeating pattern | 443 | -58 | 150 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
343 | 301 | 286 |
Frequency counter |
-83 | 407 | 288 |
Direct history - fixed window size | 176 | -274 | 300 |
Direct history - highest frequency of matches | 200 | -9 | 373 |
Direct history - highest frequency of following move | 44 | 107 | -90 |
Direct history - longest repeating pattern | 400 | -57 | 150 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
310 | 302 | 286 |
Frequency counter |
-83 | 360 | 289 |
Direct history - fixed window size | 177 | -274 | 270 |
Direct history - highest frequency of matches | 201 | -9 | 346 |
Direct history - highest frequency of following move | 44 | 97 | -89 |
Direct history - longest repeating pattern | 400 | -63 | 151 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
310 | 260 | 287 |
Frequency counter |
-83 | 323 | 290 |
Direct history - fixed window size | 160 | -273 | 270 |
Direct history - highest frequency of matches | 202 | -9 | 330 |
Direct history - highest frequency of following move | 40 | 98 | -89 |
Direct history - longest repeating pattern | 360 | -62 | 151 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
280 | 262 | 287 |
Frequency counter |
-83 | 291 | 291 |
Direct history - fixed window size | 144 | -272 | 270 |
Direct history - highest frequency of matches | 182 | -8 | 330 |
Direct history - highest frequency of following move | 32 | 100 | -89 |
Direct history - longest repeating pattern | 324 | -61 | 151 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random |
280 | 238 | 288 |
Frequency counter |
-83 | 260 | 292 |
Direct history - fixed window size | 144 | -271 | 233 |
Direct history - highest frequency of matches | 182 | -9 | 331 |
Direct history - highest frequency of following move | 36 | 99 | -89 |
Direct history - longest repeating pattern | 293 | -60 | 151 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random, over last 10 rounds |
0 | 0 | 0 |
Frequency counter, last 10 rounds |
0 | 0 | 0 |
Direct history - fixed window size, over last 10 rounds | 0 | 0 | 0 |
Direct history - highest frequency of matches, over last 10 rounds | 0 | 0 | 0 |
Direct history - highest frequency of following move, over last 10 rounds | 0 | 0 | 0 |
Direct history - longest repeating pattern, over last 10 rounds | 0 | 0 | 0 |
Random, over last 100 rounds | 0 | 0 | 0 |
Frequency counter, last 100 rounds | 0 | 0 | 0 |
Direct history - fixed window size, over last 100 rounds | 0 | 0 | 0 |
Direct history - highest frequency of matches, over last 100 rounds | 0 | 0 | 0 |
Direct history - highest frequency of following move, over last 100 rounds | 0 | 0 | 0 |
Direct history - longest repeating pattern, over last 100 rounds | 0 | 0 | 0 |
@notthepoint
No bluff | Bluff | Double bluff | |
---|---|---|---|
Random, over last 10 rounds |
0 | 0 | 0 |
Frequency counter, last 10 rounds |
0 | 0 | 0 |
Direct history - fixed window size, over last 10 rounds | 0 | 0 | 0 |
Direct history - highest frequency of matches, over last 10 rounds | 0 | 0 | 0 |
Direct history - highest frequency of following move, over last 10 rounds | 0 | 0 | 0 |
Direct history - longest repeating pattern, over last 10 rounds | 0 | 0 | 0 |
Random, over last 100 rounds | 0 | 0 | 0 |
Frequency counter, last 100 rounds | 0 | 0 | 0 |
Direct history - fixed window size, over last 100 rounds | 0 | 0 | 0 |
Direct history - highest frequency of matches, over last 100 rounds | 0 | 0 | 0 |
Direct history - highest frequency of following move, over last 100 rounds | 0 | 0 | 0 |
Direct history - longest repeating pattern, over last 100 rounds | 0 | 0 | 0 |
Iocaine powder
@notthepoint
@notthepoint
@notthepoint
https://rps-slackbot.herokuapp.com/
https://github.com/notthepoint/rps_slackbot
thank you
@notthepoint
https://webdocs.cs.ualberta.ca/~darse/rsbpc1.html
http://web.archive.org/web/20061202004212/http://www.ofb.net/~egnor/iocaine.html
https://pdfs.semanticscholar.org/5bae/79f4eb6fab8c8106736cf94a255d1535bf34.pdf
http://www.ijiris.com/volumes/volume1/issue5/11.NVIS10093.pdf
http://www.samkass.com/theories/RPSSL.html
https://cs.stanford.edu/people/eroberts/courses/soco/projects/1998-99/game-theory/psr.html
https://en.wikipedia.org/wiki/Side-blotched_lizard
Darse Billings (2000). Thoughts on RoShamBo. ICGA Journal Vol. 23, No. 1
Ruby Paper Scissors - Round Two
By dorothyjane
Ruby Paper Scissors - Round Two
- 1,585