# rock_paper_scissors
#
# script designed to win repeated games of rock/paper/scissors by
# using probability lookup techniques.
from __future__ import division
import cPickle
import random
class RockPaperScissors:
# just set up the defaults
def __init__(self):
self.moves = ["" for x in range(3)]
self.possible_moves = ["rock", "paper", "scissors"]
self.possible_sequences = self._possible_sequences()
self.frequencies = self._frequencies()
self.prediction_matrix = self._prediction_matrix()
# load data from pickle format
def load(self):
database = open("rps2_data.txt", "r")
try:
# loading the data throws an EOFError the first time the script
# runs, since the data won't have been written yet
self.frequencies = cPickle.load(database)
self.prediction_matrix = cPickle.load(database)
except EOFError:
pass
database.close()
# save data to pickle format
def store(self):
database = open("rps2_data.txt", "w")
cPickle.dump(self.frequencies, database)
cPickle.dump(self.prediction_matrix, database)
database.close()
# main method; play the game
def play(self):
self.load()
user_move = ""
self.moves[0] = self.user_move(user_move)
self.moves[1] = self.user_move(user_move)
prediction = self.predict(self.moves)
self.moves[2] = self.user_move(user_move)
print "predicted third move " + prediction
if prediction == self.moves[2]:
print "rawk!"
self.learn(self.moves)
self.store()
# acquire moves interactively in Windows
def user_move(self, user_move):
while user_move != "rock" and user_move != "paper" and user_move != "scissors":
user_move = raw_input("rock/paper/scissors: ")
return user_move
# internal only; list possible sequences.
def _possible_sequences(self):
# the data structure here is a list of tuples. por ejemplo:
# "rock_paper", "scissors"
# "rock_paper", "rock"
# ...for every permutation of the rock/paper/scissors set, which obviously has 27
# possible versions, given that it is a 3x3x3 matrix. possible_sequences is basically
# the data structure which represents all possible permutations. a more sophisticated
# version would use a three-d data structure and model probabilities for the second
# given the first, but that's not planned at the moment. this is based on the kung fu
# fighting moves C++ prediction code in O'Reilly's "AI for Game Developers" Bayesian
# networks chapter, which is really pretty simplistic. the network has only three nodes,
# here representing the three moves in a rock-paper-scissors game, and the first two
# moves simply feed the frequency matrices for the third node's probability calculations.
# really the network only has two nodes, since the second blocks the first.
_ps = []
possible_seq = ()
for x in self.possible_moves:
for y in self.possible_moves:
for z in self.possible_moves:
possible_seq = x + "_" + y, z
_ps.append(possible_seq)
return _ps
# internal only; define default frequencies
def _frequencies(self):
# these are the frequencies for each two-move sequence. ie:
# {"rock_paper": 23}
# ...which would mean that the first move had been rock and the second move had been
# paper 24 distinct times so far. this data is needed for the probability calculations.
_frqs = {}
for x in self.possible_sequences:
_frqs[x[0]] = 0
return _frqs
# internal only; default (empty) matrix
def _prediction_matrix(self):
# this records the frequency with which initial two-move sequences are followed by a
# given third move. ie:
# {("rock_paper", "scissors"): 23}
# which would mean that 24 times, when rock was followed by paper, scissors was the
# third move. this data also needed for probability calculations.
_cfm = {}
for x in self.possible_sequences:
_cfm[x] = 0
return _cfm
# populate frequency table/prediction matrix
def learn(self, moves):
# all we do here is increment the frequency numbers stored in the frequencies dict and
# the prediction_matrix dict (which is indexed by a tuple). you'll need to check out
# the methods which create those data structures to make sense of them, but it's pretty
# straightforward.
index = moves[0] + "_" + moves[1]
self.frequencies[index] = self.frequencies[index] + 1
self.prediction_matrix[index, moves[2]] = self.prediction_matrix[index, moves[2]] + 1
# based on first two moves, predict third
def predict(self, moves):
# the probability of any given move comes from an equation; divide the frequency with
# which a two-move sequence is followed by a given move by the overall frequency of
# that two-move sequence. if either frequency is equal to zero, the probability table
# is still in its infancy, and we resort to a random guess. this method is the reason
# the script imports both random (for the guessing) and __future__.division (to ensure
# floating-point probability values).
two_move_seq = moves[0] + "_" + moves[1]
overall_frequency = self.frequencies[two_move_seq]
most_probable_move = ()
move_probability = 0
seq_followed_by_move = 0
for move in self.possible_moves:
seq_followed_by_move = self.prediction_matrix[(two_move_seq, move)]
if seq_followed_by_move != 0 and overall_frequency != 0:
move_probability = seq_followed_by_move / overall_frequency
else:
move_probability = random.random()
if not most_probable_move or most_probable_move[1] < move_probability:
# the highest move probability replaces any other, even if the probability is
# randomly generated; this favors populating the prediction matrix at the
# expense of certainty.
most_probable_move = (move, move_probability)
return most_probable_move[0]