Rock, Paper, Scissors, Lizard, Spock: about RPSLS
RPSLS is an extention of the popular game Rock, Paper, Scissors (RPS). RPS is a game of chance where each match players signal for rock with a closed fist, paper with an open hand or scissors with with pointer and middle fingers extended. By conventional rules: rock smashes scissors, paper covers rock and scissors cuts paper. RPS and similar games have a long and interesting history. In fact, variants are reported to have been played in China as early as the Han dynasty (206BB - 220AD) (1)
RPSLS improves on it’s more popular predecessor by adding lizard and spock as two extra response options for each player. Each response beats 2 other options but loses to the remaining two. The original rules state “Scissors cuts paper covers rock crushes lizard poisons spock smashes scissors decaipates lizard eats paper disproves spock vaporizes rock crushes scissors” (2) and were popularized in the sitcom Big Bang Theory:
The rules are summarized here:
player | wins against | loses to |
---|---|---|
Rock | Lizard & Scissors | Paper & Spock |
Paper | Rock & Spock | Scissors & Lizard |
Scissors | Paper & Lizard | Spock & Rock |
Lizard | Sock & Paper | Scissors & Rock |
Spock | Scissors & Rock | Lizard & Paper |
The addition of Spock & Lizard improves upon RPS by reducing the the chance of games ending in a draw. RPS works pretty well for 2 players competing in a best of three game. However, when more players are added the chances of a tie are \(\frac{1}{n}\) therefore, adding two more options adds variety and improved playability to the game.
Getting started
This R markdown notebook will be running python and there are a few steps necessary to get this set up:
Will need to include the R reticulate library & will have to point to the right python version to use:
#load reticulate library
library( reticulate )
#point to the python you'd like to use
use_python("/home/bonzilla/anaconda3/bin/python3", required = T)
#import the sys python module & see which version of python is being used:
sys <- import("sys")
sys$version
## [1] "3.7.6 (default, Jan 8 2020, 20:02:53) \n[GCC 7.3.0]"
Now to import the python libraries that will be used:
import random
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pandas as pd
import numpy as np
print( 'We are calling python from R in this R chunk' )
## We are calling python from R in this R chunk
RPSLS Helper functions & accesories
Great! Now that the environment is set up, we will begin by defining a few helper functions for the RPSLS simulation as well as a few accesories.
The following code builds a few lists that will be referenced to help visualize the simulation and to make things a little more fun/creative. The lists hold multi-line strings of ascii art text generated by a Text to Ascii Art Generator. Here, the font from ‘Doom’ was used, because, frankly, it would be wrong not to.
stringPlayers = (
"""
______ _
| ___ \ | |
| |_/ /___ ___| | __
| // _ \ / __| |/ /
| |\ \ (_) | (__| <
\_| \_\___/ \___|_|\_\ """,
"""
_____ _
/ ___| | |
\ `--. _ __ ___ ___| | __
`--. \ '_ \ / _ \ / __| |/ /
/\__/ / |_) | (_) | (__| <
\____/| .__/ \___/ \___|_|\_\\
| |
|_|
""",
"""
______
| ___ \
| |_/ /_ _ _ __ ___ _ __
| __/ _` | '_ \ / _ \ '__|
| | | (_| | |_) | __/ |
\_| \__,_| .__/ \___|_|
| |
|_|
""",
"""
_ _ _
| | (_) | |
| | _ __________ _ _ __ __| |
| | | |_ /_ / _` | '__/ _` |
| |___| |/ / / / (_| | | | (_| |
\_____/_/___/___\__,_|_| \__,_|
""",
"""
_____ _
/ ___| (_)
\ `--. ___ _ ___ ___ ___ _ __ ___
`--. \/ __| / __/ __|/ _ \| '__/ __|
/\__/ / (__| \__ \__ \ (_) | | \__ \\
\____/ \___|_|___/___/\___/|_| |___/
""")
stringOutcomes = (
"""
_____ ________ _________ _ _ _____ ___________
/ __ \ _ | \/ || ___ \ | | |_ _| ___| ___ \\
| / \/ | | | . . || |_/ / | | | | | | |__ | |_/ /
| | | | | | |\/| || __/| | | | | | | __|| /
| \__/\ \_/ / | | || | | |_| | | | | |___| |\ \
\____/\___/\_| |_/\_| \___/ \_/ \____/\_| \_|
_ _ _____ _ _ _____ _ _ _
| | | |_ _| \ | |/ ___| | | | |
| | | | | | | \| |\ `--. | | | |
| |/\| | | | | . ` | `--. \ | | | |
\ /\ /_| |_| |\ |/\__/ / |_|_|_|
\/ \/ \___/\_| \_/\____/ (_|_|_)
""",
"""
_ _ _ ____ ___ ___ _ _
| | | | | | | \/ | / _ \ | \ | |
| |_| | | | | . . |/ /_\ \| \| |
| _ | | | | |\/| || _ || . ` |
| | | | |_| | | | || | | || |\ |
\_| |_/\___/\_| |_/\_| |_/\_| \_/
_ _ _____ _ _ _____ _ _ _
| | | |_ _| \ | |/ ___| | | | |
| | | | | | | \| |\ `--. | | | |
| |/\| | | | | . ` | `--. \ | | | |
\ /\ /_| |_| |\ |/\__/ / |_|_|_|
\/ \/ \___/\_| \_/\____/ (_|_|_)
__
/ _|
| |_ ___ _ __ _ __ _____ __
| _/ _ \| '__| | '_ \ / _ \ \ /\ / /
_ _ _| || (_) | | | | | | (_) \ V V / _ _ _
(_|_|_)_| \___/|_| |_| |_|\___/ \_/\_/ (_|_|_)
""",
"""
_ _ _
| | (_) | |
__ _ | |_ _ ___ __| | __ _ __ _ _ __ ___ ___
/ _` | | __| |/ _ \/ _` | / _` |/ _` | '_ ` _ \ / _ \\
| (_| | | |_| | __/ (_| | | (_| | (_| | | | | | | __/
\__,_| \__|_|\___|\__,_| \__, |\__,_|_| |_| |_|\___|
__/ |
|___/
___ _ _ __
/ / | | | (_) \ \\
| || |__ _____ __ | |__ ___ _ __ _ _ __ __ _ | |
| || '_ \ / _ \ \ /\ / / | '_ \ / _ \| '__| | '_ \ / _` || |
| || | | | (_) \ V V / | |_) | (_) | | | | | | | (_| || |
| ||_| |_|\___/ \_/\_/ |_.__/ \___/|_| |_|_| |_|\__, || |
\_\ __/ /_/
|___/ """
)
The following code estaplishes a pandas
dataframe structure that will relate the proper action verbs with paired options (e.g. Spock vaporizes Rock).
outcomeActions = [['scissors', 'paper', 'cuts'],
['paper', 'rock', 'covers'], ['rock', 'lizard', 'crushes'], ['lizard', 'spock', 'poisons'],
['spock', 'scissors', 'smashes'], ['scissors', 'lizard', 'decapitates'], ['lizard', 'paper', 'eats'], ['paper', 'spock', 'disproves'], ['spock', 'rock', 'vaporizes'], ['rock', 'scissors', 'crushes']]
outcomeActions_df = pd.DataFrame( outcomeActions, columns = ['W_Choice', 'L_Choice', 'Action'])
print( outcomeActions_df )
## W_Choice L_Choice Action
## 0 scissors paper cuts
## 1 paper rock covers
## 2 rock lizard crushes
## 3 lizard spock poisons
## 4 spock scissors smashes
## 5 scissors lizard decapitates
## 6 lizard paper eats
## 7 paper spock disproves
## 8 spock rock vaporizes
## 9 rock scissors crushes
These helper functions will associate a number to a name and the same name to the corresponding number. This is necessary to relate the user input to the randomly generated ‘computer’ result.
#define helper functions for our RPSLS game:
def name_to_number( name ):
"""convert the player's choice to player_number
name: string """
name = str.lower( name )
if( name == 'rock' ):
return 0
elif( name == 'spock' ):
return 1
elif( name == 'paper' ):
return 2
elif( name == 'lizard' ):
return 3
elif( name == 'scissors' ):
return 4
else:
print( 'ERROR: Name\nPlease enter either Rock, Spock, Paper, Lizard or Scissors' )
def number_to_name( number ):
"""convert the computer's random number to a choice
number: int """
if( number == 0 ):
return 'rock';
elif( number == 1 ):
return 'spock';
elif( number == 2 ):
return 'paper';
elif( number == 3 ):
return 'lizard';
elif( number == 4 ):
return 'scissors';
else:
print( 'ERROR: Number' )
## spock
## 1
The next function controls the logic of a RPSLS game:
def rpsls( player_choice, display ):
"""
Takes a name as input, generates a random selection, calculates the game outcome. Takes a second parameter to indicate if the results should be displayed
player_choice: string
display: bool. True to display game outcome
"""
player_choice = str.lower( player_choice )
player_number = name_to_number( player_choice )
# compute random result to simulate the input's competition using random.randrange()
comp_number = random.randrange( 0, 4 )
#convert comp_number to comp_choice using number_to_name()
comp_choice = number_to_name( comp_number );
#compute difference of comp_number and player_number modulo five
difference = (comp_number - player_number) % 5
#determine winner
if( difference == 1 or difference == 2 ):
gameOutcome = 0
action = outcomeActions_df.query("L_Choice==@player_choice and W_Choice==@comp_choice")['Action']
gameAction = comp_choice + ' ' + action.values[0] + ' ' + player_choice + ' therefore: '
gameOutcome = -1
elif ( difference == 4 or difference == 3 ):
gameOutcome = 1
action = outcomeActions_df.query("W_Choice==@player_choice and L_Choice==@comp_choice")['Action']
gameAction = player_choice + ' ' + action.values[0] + ' ' + comp_choice + ' therefore: '
gameOutcome = 1
elif( difference == 0 ):
gameOutcome = 2
gameAction = ' == '
gameOutcome = 0
if display:
print( "\n" )
print( "Player chooses: " )
print( stringPlayers[player_number])
print( "Computer chooses: ")
print( stringPlayers[comp_number], '\n')
print( gameAction, '\n' )
print( stringOutcomes[gameOutcome] )
print( '\n' )
return gameOutcome
RPSLS Simulations:
##
##
## Player chooses:
##
## _____ _
## / ___| | |
## \ `--. _ __ ___ ___| | __
## `--. \ '_ \ / _ \ / __| |/ /
## /\__/ / |_) | (_) | (__| <
## \____/| .__/ \___/ \___|_|\_\
## | |
## |_|
##
## Computer chooses:
##
## _____ _
## / ___| | |
## \ `--. _ __ ___ ___| | __
## `--. \ '_ \ / _ \ / __| |/ /
## /\__/ / |_) | (_) | (__| <
## \____/| .__/ \___/ \___|_|\_\
## | |
## |_|
##
##
## ==
##
##
## _____ ________ _________ _ _ _____ ___________
## / __ \ _ | \/ || ___ \ | | |_ _| ___| ___ \
## | / \/ | | | . . || |_/ / | | | | | | |__ | |_/ /
## | | | | | | |\/| || __/| | | | | | | __|| /
## | \__/\ \_/ / | | || | | |_| | | | | |___| |\ \
## \____/\___/\_| |_/\_| \___/ \_/ \____/\_| \_|
##
##
## _ _ _____ _ _ _____ _ _ _
## | | | |_ _| \ | |/ ___| | | | |
## | | | | | | | \| |\ `--. | | | |
## | |/\| | | | | . ` | `--. \ | | | |
## \ /\ /_| |_| |\ |/\__/ / |_|_|_|
## \/ \/ \___/\_| \_/\____/ (_|_|_)
##
##
##
##
## 0
2.
##
##
## Player chooses:
##
## ______ _
## | ___ \ | |
## | |_/ /___ ___| | __
## | // _ \ / __| |/ /
## | |\ \ (_) | (__| <
## \_| \_\___/ \___|_|\_\
## Computer chooses:
##
## ______
## | ___ \
## | |_/ /_ _ _ __ ___ _ __
## | __/ _` | '_ \ / _ \ '__|
## | | | (_| | |_) | __/ |
## \_| \__,_| .__/ \___|_|
## | |
## |_|
##
##
## paper covers rock therefore:
##
##
## _ _ _
## | | (_) | |
## __ _ | |_ _ ___ __| | __ _ __ _ _ __ ___ ___
## / _` | | __| |/ _ \/ _` | / _` |/ _` | '_ ` _ \ / _ \
## | (_| | | |_| | __/ (_| | | (_| | (_| | | | | | | __/
## \__,_| \__|_|\___|\__,_| \__, |\__,_|_| |_| |_|\___|
## __/ |
## |___/
## ___ _ _ __
## / / | | | (_) \ \
## | || |__ _____ __ | |__ ___ _ __ _ _ __ __ _ | |
## | || '_ \ / _ \ \ /\ / / | '_ \ / _ \| '__| | '_ \ / _` || |
## | || | | | (_) \ V V / | |_) | (_) | | | | | | | (_| || |
## | ||_| |_|\___/ \_/\_/ |_.__/ \___/|_| |_|_| |_|\__, || |
## \_\ __/ /_/
## |___/
##
##
## -1
3.
## -1
1000 RPSLS Simulations
numRuns = 1000
gameResults = np.linspace( 0, numRuns, numRuns )
for aGame in range( numRuns ):
aresult = rpsls( 'Lizard', 0 )
gameResults[ aGame ] = aresult
print a summary of the simulation outcome
#percentage of tied games
numtied = len(gameResults[gameResults==0])/numRuns*100
#proportion of games won by the 'player'
numPWins = len(gameResults[gameResults==1])/numRuns*100
#proportion of games won by the matrix
numCWins = len(gameResults[gameResults==-1])/numRuns*100
print( 'There were ', numtied, '% tied games', "\nthe 'player' won ", numPWins, '% of games\nthe computer won ', numCWins, '% of games')
## There were 24.8 % tied games
## the 'player' won 50.3 % of games
## the computer won 24.9 % of games
Next we will run the same simulation however, for the previous run, the the ‘player’ always selected ‘Lizard’. This simulation will functionalize the for
loop and have the player now randomly chosing an option as well.
def runRPSLSsims( numRuns):
"""
Runs 'numRuns' of RPSLS simulations and returns to game outcome
numRuns: number of simulations to run (int)
"""
runSteps = np.linspace( 0, numRuns, (numRuns), endpoint=False)
gameResults = pd.Series(np.zeros(len(runSteps)), index=runSteps)
for aGame in runSteps:
randomNum = random.randrange( 0, 4 )
randomName = number_to_name( randomNum )
aresult = rpsls( randomName, 0 )
gameResults[int(aGame)] = aresult
return gameResults
#run simulation
numRuns = 1000
all_1000_runs = runRPSLSsims( numRuns )
runsSummary = all_1000_runs.value_counts()/numRuns*100
print( 'There were ', runsSummary.loc[0], '% tied games', "\nthe 'player' won ", runsSummary.loc[1.0], '% of games\nthe computer won ', runsSummary.loc[-1.0], '% of games')
## There were 24.5 % tied games
## the 'player' won 37.5 % of games
## the computer won 38.0 % of games
Conclusions
The theoretical number of ties predicted for this game is \(\frac{1}{n}\) where n
is the number of options available to the player. RPSLS has 5 possible outcomes, so the predicted number of ties is 20%. The last simulation approximates 20% \(\pm\) a few percents (variable across simulations). Noteably, the percent of games won by the ‘player’ and the computer are much more equivalent in the second simulation compared to the first simulation where the ‘player’ selection was fixed to ‘Lizard’.
Future directions
- Move to a GUI
- incorporate pictures
- Is there a war to display python code inline in R Markdown? Hmmmmm