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

Icons made by Freepik from www.flaticon.com CC 3.0 BY

@notthepoint

@notthepoint

Greedy

@notthepoint

content

@notthepoint

sly

Icons made by Freepik from www.flaticon.com CC 3.0 BY

@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

Ruby Paper Scissors - Round Two

By dorothyjane

Ruby Paper Scissors - Round Two

  • 1,504