For this project, we chose to replicate the classic game of Rock, Paper, Scissors using the R program. The inspiration for this project came from an article in the New York Times that outlined how computers can essentially be trained to recognize human playing patterns in the game and can adjust itself accordingly in order to play moves that are more likely to win. We were interested in finding a way for R to simulate an opponent who is playing against a real human player and to look back at the previous moves that the human played to look for patterns which inevitably exist amongst the human’s moves although humans may not realize that their moves have a pattern. The article described two different settings for a computer opponent; novice and veteran. A novice computer opponent does not look back at the human’s history of previously played moves but instead plays a random move every time. We would expect the results of a game with a human versus a novice computer opponent to be relatively equal with the computer winning approximately 50% of the time and the human winning approximately 50% of the time. For a veteran computer player, the computer would notice the pattern of the human’s past moves and would take these into account when playing its next move. Here, we would expect the computer to win more and more as more games went on because the human’s history of moves would built up allowing the computer to recognize the patterns and therefore predict the human’s next move and subsequently play to beat the human. Using R, we imitated both of these settings. We started with the creation of a basic function to perform Rock, Paper, Scissors and then replicated this function 50 times in order to create a history for the computer to reference. We also created a code that with specific rules for the computer to follow everytime based on the current move that the human played. Finally, we created a function that could look back at the previous moves played by the human and use this information to hopefully be more successful by predicting the next move and playing to beat it.
The first function that we created was the basic Rock, Paper, Scissors Function using R to play human versus the computer. All the function requires of the human player is to select their move (either rock, paper or scissors). The computer than randomly generates one of the three moves to play back. For this function, the computer does not look at any of the human’s past plays but will simply always generate a random answer. The function works by taking the human move that is entered manually by the player and then randomly generating a move in response. Once this move is generated, the computer compares the human’s move with its move to determine which move constitutes a win and which constitutes a loss. The rules were as follows: rock wins over scissors, scissors wins over paper, and paper wins over rock. For example, if the human player decides to play scissors and the computer randomly generates a move of paper, the computer has lost that game. If both the human and the computer play the same move, the function will return a answer of “tie”. The majority of this function used if/else statements to inform the computer of what to return in possible situation.
rps = function(move){
options = c("rock", "paper", "scissors")
comp.move = sample(options, size = 1)
if(move == "rock" & comp.move == "rock"){
names(comp.move) = "tie"
}else
if(move == "rock" & comp.move == "scissors"){
names(comp.move) = "loss"
}else
if(move == "rock" & comp.move == "paper"){
names(comp.move) = "win"
}else
if(move == "paper" & comp.move == "paper"){
names(comp.move) = "tie"
}else
if(move == "paper" & comp.move == "scissors"){
names(comp.move) = "win"
}else
if(move == "paper" & comp.move == "rock"){
names(comp.move) = "loss"
}else
if(move == "scissors" & comp.move == "scissors"){
names(comp.move) = "tie"
}else
if(move == "scissors" & comp.move == "rock"){
names(comp.move) = "win"
}else
if(move == "scissors" & comp.move == "paper"){
names(comp.move) = "loss"
}
return(comp.move)
}
The second portion of this project involved taking the initial Rock, Paper, Scissors function we created and replicating it 50 times. The purpose of this was to build a vector containing the history of a human’s moves after 50 games. The history of moves was used later to locate patterns that would potentially allow the computer to accurately predict the next human play. It is important to note that the 50 human moves in this portion of the project were not random. They were moves that were chosen by a real human in hopes of possibly identifying a human pattern. j This series of 50 games was predicted to have the lowest winning percentage for the computer out of the three functions. This is because while we were obtaining the history of human moves, the computer was playing 50 random times. Nothing about the function had been added to assist the computer with its ability to predict the human moves so we expected the computer to win approximately 50% of the games or 25 games.
#Create vector with the human's 50 moves
human.move = c("rock", "paper", "scissors", "paper", "paper", "rock", "scissors", "rock", "rock", "paper", "paper", "scissors", "rock", "rock", "paper", "paper", "paper", "scissors", "paper", "rock", "paper", "rock", "rock", "scissors", "scissors", "paper", "rock", "paper", "scissors", "rock", "paper", "paper", "scissors", "rock", "paper", "rock", "paper", "paper", "scissors", "scissors", "paper", "rock", "rock", "scissors", "scissors", "rock", "paper", "scissors", "scissors", "rock")
#Using the basic rps function, this runs the function 50 times using the vector of human moves as the 50 moves
rps.random=function(move){
comp.moves = vector("character")
for(i in 1:length(move)){
comp.move=rps(move[i])
comp.moves = append(comp.moves,comp.move, after = length(comp.moves))
}
winrate.random = mean(names(comp.moves) == "win")
return(winrate.random)
#return(comp.moves)
}
results.random = rps.random(human.move)
After we replicated the game 50 times in order to obtain the human moves, we designed a function that would play Rock, Paper, Scissors using specifically designated rules. The rules that we chose were: 1. If the previous human move is scissors then play rock 2. If the previous human move is paper then play scissors. 3. If the previous human move is rock then play paper. After designating these rules we still had to distinguish to the program how to determine if the computer won or lost the round. The purpose of the guidlines was to present a baseline for assigning rules to the game. This function should have an outcome of better results than the original function because it is not random. However, this function was still not expected to cause the computer to get better as time progressed and more and more rounds were played.
# Rules for the second strategy. This is assigning the guidelines to the game
rps.rules = function(previousmove,currentmove){
if(previousmove == "scissors"){
comp.move.rules = "rock"
}else{
if(previousmove == "paper"){
comp.move.rules = "scissors"
}else{
if(previousmove == "rock")
comp.move.rules = "paper"
}}
#This is telling the computer whether it won or lost based on the human move and the computer move
if(currentmove == "scissors" & comp.move.rules == "rock"){
names(comp.move.rules) = "win"
}else{
if(currentmove == "rock" & comp.move.rules == "rock"){
names(comp.move.rules) = "tie"
}else{
if(currentmove == "paper" & comp.move.rules == "rock"){
names(comp.move.rules) = "loss"
}}}
if(currentmove == "paper" & comp.move.rules == "scissors"){
names(comp.move.rules) = "win"
}else{
if(currentmove == "scissors" & comp.move.rules == "scissors"){
names(comp.move.rules) = "tie"
}else{
if(currentmove == "rock" & comp.move.rules == "scissors"){
names(comp.move.rules) = "loss"
}}}
if(currentmove == "rock" & comp.move.rules == "paper"){
names(comp.move.rules) = "win"
}else{
if(currentmove == "paper" & comp.move.rules == "paper"){
names(comp.move.rules) = "tie"
}else{
if(currentmove == "scissors" & comp.move.rules == "paper"){
names(comp.move.rules) = "loss"
}}}
return(comp.move.rules)
}
results.random = rps.random(human.move)
# Strategy 2. This runs the above function 50 times with the vector of human moves
rps.guide=function(move){
comp.moves.rules = vector("character")
comp.moves.rules[1]=rps(move)
for(i in 2:length(move)){
comp.move.rules=rps.rules(move[i-1],move[i])
comp.moves.rules = append(comp.moves.rules,comp.move.rules, after = length(comp.moves.rules))
}
winrate.guide = mean(names(comp.moves.rules) == "win")
return(winrate.guide)
#return(comp.moves.rules)
}
results.guide = rps.guide(human.move)
For the final function in our project, we created a code that was designed to look at the human’s previous two moves. Using these moves, the function scanned the history of moves (from the 50 trials previously) and searched for patterns that contained these two moves. The most recent time that these two moves were used consecutively was identified and the function also identified the next move, after these two, that were played. According to this, the computer would then play the move that would win against that move in hopes that the human would follow their same pattern so their next move would be predicted. For example, if the last two moves played were rock and scissors, the function will look throughout the history of human plays and identify the most recent time that the human played rock followed by scissors. Say the next move played was rock. The computer will take this into account and will then play paper in order to beat the assumed next move of rock. Using this strategy, it was predicted that over time, as the history of human moves built up, the computer would become better and better at predicting the human’s next move based on patterns. It is important to note for this code that when the function is looking for the most recent two moves, it must look for them before these most recent two, otherwise the function would return an answer of null because there isnt a move yet after the previous two moves.
#Function needed for the third strategy. Win expecting that the human will play a certain move. Telling us which move would actually return a win
rps.win = function(previousmove){
if(previousmove == "rock"){
comp.move.win = "paper"
}else
if(previousmove == "paper"){
comp.move.win = "scissors"
}else
if(previousmove == "scissors"){
comp.move.win = "rock"
}
return(comp.move.win)
}
#Just giving us the results of the game for all combinations of the move
rps.smart.name = function(comp.move.smart,move){
if(identical(comp.move.smart,move)){
result.smart = "tie"
}else
if(comp.move.smart == "rock" & move == "paper" | comp.move.smart == "paper" & move == "scissors" | comp.move.smart == "scissors" & move == "rock"){
result.smart = "loss"
}else{
result.smart = "win"
}
return(result.smart)
}
#Implementing strategy #3
rps.smart = function(move){
#Creating blank vector which will store all moves
comp.moves.smart = vector("character")
thelength = length(move)
#Playing a random move first 10 times
for(i in 1:10){
comp.move.smart = rps(move[i])
comp.moves.smart = append(comp.moves.smart,comp.move.smart, after = length(comp.moves.smart))
}
for(i in 11:thelength){
#Storing last two moves played by human in a separate vector
last2human = move[(i-2):(i-1)]
#Intiializing objects
showedup = F
move.to.beat = ""
#Looping through entire history of moves already played by human
for(j in 1:(i-3)){
#Vector that stores two consecutive moves i.e. 1,2; 2,3; 3,4
testmoves = move[j:j+1]
#Checking to see if pattern exists
if(testmoves == last2human){
showedup = T
#Store move that happened after the same pattern
move.to.beat = move[j+2]
}
}
#If pattern existed in vector
if(showedup == T){
#Play the move that would beat move.to.beat
comp.move.smart = rps.win(move.to.beat)
names(comp.move.smart) = rps.smart.name(comp.move.smart,move[i])
}else{
#Play random move
comp.move.smart = rps(move[i])
names(comp.move.smart) = rps.smart.name(comp.move.smart,move[i])
}
comp.moves.smart = append(comp.moves.smart,comp.move.smart, after = length(comp.moves.smart))
}
winrate.smart = mean(names(comp.moves.smart) == "win")
return(winrate.smart)
#return(comp.moves.smart)
}
The results of our Rock, Paper, Scissors were inconclusive in terms of our hypothesis that the computer would have a better percentage of wins over time when looking for patterns played by the human to determine its next move. Sometimes the best winning percentage for the computer came from the original function which involved simply playing Rock, Paper, Scissors 50 times against the vector of 50 human plays. Other times, the smart function, that involved using the computer’s memory, was the most successful in terms of winning percentages. Finally, sometimes the function that required the computer to follow 3 guidlines regardless of the past history of the human moves ended up having the highest winning percentage. For this reason, it is impossible to determine which function was the most successful in helping the computer gain more wins.
There were most likely several limitations in this game that prevented the most successful function from being determined. The biggest problem was the fact that the results varied each time the function was run. This was due to the fact that when using the memory function, the first ten moves the computer made were always random because there was not enough of a history of human moves to look for patterns yet. This may have been because of such a small sample size. It is possible that a larger sample size would have seen different results. For this reason, the winning percentage for the first ten rounds varied each time the code was run and this therefore affected the outcome once the total 50 trials had been run. Finally, the vector of human outcomes that was used was gathered from a real person. However, these were simply moves that someone wrote down and they were not actually playing Rock, Paper, Scissors with someone another person at the time. This may have altered their moves and made them less realistic and therefore less likely to have patterns of moves that the computer could recognize with the memory function. If changes were made to this game such as using a much larger sample size and a different vector of human responses, the chances of pattern recognition by the computer may increase and the hypothesis may have be supported.
results.smart = rps.smart(human.move)
all.results = c(results.random,results.guide,results.smart)
barplot(all.results,col = c("blue","white","red"),names.arg = c("Random","Guidelines","Smart"),xlab = "Strategy",ylab = "Win Rate", main = "Results Across Strategies")