CasinoShakespeare - excluded because it requires an active internet connection

Original Posted Question

You've swung around to your friends house for the most epic showdown Battle ever of Rock, Paper, Scissors, Lizard, Spock. In true BigBang nerd-tastic style, none of the players are playing themselves but have created console bots to play on their behalf. You whip out your USB key and hand it over to the Sheldor the Conqueror for inclusion in the showdown. Penny swoons. Or perhaps Howard swoons. We don't judge here at Leonard's apartment.

Rules

Standard Rock, Paper, Scissors, Lizard, Spock rules apply.

Scissors cut Paper

Paper covers Rock

Rock crushes Lizard

Lizard poisons Spock

Spock smashes Scissors

Scissors decapitate Lizard

Lizard eats Paper

Paper disproves Spock

Spock vaporizes Rock

Rock crushes Scissors

Each player's bot will play one Match against each other bot in the tournament.

Each Match will consist of 100 iterations of an RPSLV game.

After each match, the winner is the player who has won the most number of games/hands out of 100.

If you win a match, you will be assigned 1 point in the league table. In the result of a draw-match, neither player will gain a point.

Bot Requirements

Your bot must be runnable from the command line.

Sheldor's *nix box has died, so we're running it off his windows 8 Gaming Laptop so make sure your provided solution can run on windows. Sheldor has graciously offered to install any required runtimes (within reason) to be able to run your solution. (.NET, Java, Php, Python, Ruby, Powershell ...)

Inputs

In the first game of each match no arguments are supplied to your bot.
In each subsequent game of each match:
- Arg1 will contain the history of your bots hands/decisions in this match.
- Arg2 will contain the history of your opponents hands/decisions in this match.

History will be represented by a sequence of single capital letters representing the possible hands you can play.

In the case where your bot does not return a valid hand (i.e. 1 of the above 5 single capital letters, then you automatically forfeit that hand and the match continues.

In the case where both bots do not return a valid hand, then the game is considered a draw and the match continues.

Match Format

Each submitted bot will play one match against each other bot in the tournament.

Each match will last exactly 100 games.

Matches will be played anonymously, you will not have an advanced knowledge of the specific bot you are playing against, however you may use any and all information you can garner from his decision making during the history of the current match to alter your strategy against your opponent. You may also track history of your previous games to build up patterns/heuristics etc... (See rules below)

During a single game, the orchestration engine will run your bot and your opponents bot 100 milliseconds apart and then compare the results in order to avoid any PRNG collisions in the same language/runtime. (this actually happened me during testing).

Judging & Constraints

Dr. Sheldon Cooper in the guise of Sheldor the Conqueror has kindly offered to oversee the running of the tournament.
Sheldor the Conqueror is a fair and just overseer (mostly). All decisions by Sheldor are final.

Gaming will be conducted in a fair and proper manner:

Your bot script/program will be stored in the orchestration engine under a subfolder Players\[YourBotName]\

You may use the subfolder Players\[YourBotName]\data to log any data or game history from the current tournament as it proceeds. Data directories will be purged at the start of each tournament run.

You may not access the Player directory of another player in the tournament

Each player may submit more than one bot to play so long as they do not interact or assist one another.

Edit - Additional Constraints

Regarding forfeits, they won't be supported. Your bot must play one of the 5 valid hands. I'll test each bot outside of the tournament with some random data to make sure that they behave. Any bots that throw errors (i.e. forfeits errors) will be excluded from the tourney til they're bug fixed.

Bots may be derivative so long as they are succinctly different in their behaviour. Bots (including in other languages) that perform exactly the same behaviour as an existing bot will be disqualified

There are already spam bots for the following so please don't resubmit

Rock - BartSimpson

Paper - LisaSimpson

Scissor - EdwardScissorhands

Spock - Vulcan

Lizard - Khaleesi

Pseudo Random - SimpleRandomBot & FairBot

Psuedo Random RPS - ConservativeBot

Psuedo Random LV - Barney Stinson

Bots may not call out to 3rd party services or web resources (or anything else which significantly slows down the speed/decision making time of the matches). CasinoShakespeare is the only exception as that bot was submitted prior to this constraint being added.

Sheldor will update this question as often as he can with Tournament results, as more bots are submitted.

Orchestration / Control Program

The orchestration program, along with source code for each bot is available on github.

What does the history look like when a player has forfeited a hand?
–
histocratJul 25 '14 at 17:41

1

I was going to go all-out with an analytic approach, but most of the bots here are stupid enough to defeat smart AI.
–
fluffyJul 25 '14 at 21:23

1

Just because I never am in the lead for any KotH challenge I've competed in, I've taken a screenshot as a memento.
–
Kyle KanosJul 26 '14 at 2:55

3

I'll run another tourney tonight and post the full match results on pastebin... next batch will have about 450 games but should be a bit quicker to run as I've implemented some parallelization stuff in the control prog
–
Eoin CampbellJul 28 '14 at 7:55

3

If I'm not mistaken, there seems to be a serious bug in the orchestration script: The histories of player 1 and 2 are always passed to the bots as first and second argument respectively, while according to the rules the bots should always get their own history first. Now player 2 is effectively trying to beat itself. (I got a bit suspicious because my bot won every single match where it was player 1 while losing half of the other matches.)
–
EmilJul 29 '14 at 7:07

75 Answers
75

Pony (Python 2)

This is based on a rock-paper-scissors bot I wrote some time ago for a programming challenge at the end of a Udacity online class. I changed it to include Spock and lizard and made some improvements.

The program has 11 different simple strategies, each with 5 variants. It chooses from among these based on how well they would have performed over the last rounds.

I removed a fallback strategy that just played random against stronger opponents. I guess it's more fun like this.

import sys
# just play Spock for the first two rounds
if len(sys.argv)<2 or len(sys.argv[1])<2: print 'V'; sys.exit()
# initialize and translate moves to numbers for better handling:
my_moves, opp_moves = sys.argv[1], sys.argv[2]
moves = ('R', 'P', 'S', 'V', 'L')
history = zip([moves.index(i) for i in my_moves],
[moves.index(i) for i in opp_moves])
# predict possible next moves based on history
def prediction(hist):
N = len(hist)
# find longest match of the preceding moves in the earlier history
cand_m = cand_o = cand_b = range(N-1)
for l in xrange(1,min(N, 20)):
ref = hist[N-l]
cand_m = ([c for c in cand_m if c>=l and hist[c-l+1][0]==ref[0]]
or cand_m[-1:])
cand_o = ([c for c in cand_o if c>=l and hist[c-l+1][1]==ref[1]]
or cand_o[-1:])
cand_b = ([c for c in cand_b if c>=l and hist[c-l+1]==ref]
or cand_b[-1:])
# analyze which moves were used how often
freq_m, freq_o = [0]*5, [0]*5
for m in hist:
freq_m[m[0]] += 1
freq_o[m[1]] += 1
# return predictions
return ([hist[-i][p] for i in 1,2 for p in 0,1]+ # repeat last moves
[hist[cand_m[-1]+1][0], # history matching of my own moves
hist[cand_o[-1]+1][1], # history matching of opponent's moves
hist[cand_b[-1]+1][0], # history matching of both
hist[cand_b[-1]+1][1],
freq_m.index(max(freq_m)), # my most frequent move
freq_o.index(max(freq_o)), # opponent's most frequent move
0]) # good old rock (and friends)
# what would have been predicted in the last rounds?
pred_hist = [prediction(history[:i]) for i in xrange(2,len(history)+1)]
# how would the different predictions have scored?
n_pred = len(pred_hist[0])
scores = [[0]*5 for i in xrange(n_pred)]
for pred, real in zip(pred_hist[:-1], history[2:]):
for i in xrange(n_pred):
scores[i][(real[1]-pred[i]+1)%5] += 1
scores[i][(real[1]-pred[i]+3)%5] += 1
scores[i][(real[1]-pred[i]+2)%5] -= 1
scores[i][(real[1]-pred[i]+4)%5] -= 1
# return best counter move
best_scores = [list(max(enumerate(s), key=lambda x: x[1])) for s in scores]
best_scores[-1][1] *= 1.001 # bias towards the simplest strategy
if best_scores[-1][1]<0.4*len(history): best_scores[-1][1] *= 1.4
strat, (shift, score) = max(enumerate(best_scores), key=lambda x: x[1][1])
print moves[(pred_hist[-1][strat]+shift)%5]

Run as:

python Pony.py

Edit:
I made a small change by putting in a bias towards the simplest strategy (i.e. always play the same move) in unsure cases. This helps a bit to not try to find overly complicated patterns where there are none, e.g. in bots like ConservativeBot.

Note: I tried to explain the basic history matching strategy that this bot uses in the post for my other bot Dienstag.

@WChargin, of course. :) When I wrote my original code, I had read about Iocaine Powder some years earlier and vaguely remembered the general idea. So, Pony is indeed inspired by it, if not very directly. As it turns out, they are very similar. I think mine has a wider repertoire of strategies while Iocaine Powder has a clever level of meta-meta reasoning that I did not include.
–
EmilJul 31 '14 at 6:00

Markov, Ruby

Looks at the opponent's last two moves and determines the possible (and most likely) follow ups. If the combination hasn't been picked before, he just uses all of the opponent's moves (so far) instead. Then he collects all the possible responses for these and picks a random one.

And then I use this program to determine the most possible move that I will do next then find out what you will do and finally find a way to beat what you will do and infinite-loop the whole thing again, again and again.
–
JamieJul 29 '14 at 0:35

@EoinCampbell I don't think it's necessary to inform everyone in a comment. It just clutters the page. I suggest that you instead just mention which bots didn't make it into a leaderboard, although they were already posted, like Doorknob does here.
–
Martin BüttnerJul 25 '14 at 18:09

HuddleWolfTheConqueror - C#

HuddleWolf is back and better than ever. He will beat Sheldor the Conqueror at his own silly game. HuddleWolf is smart enough to identify and counter spammerbots. For more intelligent opponents, HuddleWolf uses his knowledge of basic 5th grade statistics and utilizes a weighted dice roll based on the opposition's history of plays.

ToddlerProof

This fairly stupid bot assumes it is playing a toddler who will "chase" its moves, always trying to beat whatever was last thrown. If the bot is beaten several times in a row, it jumps to a new point in the pattern. It is based on my strategy for always beating my much younger brother. :)

EDIT:: Changed the length of a loss streak required to jump into random throws. Also fixed a major bug with the random jump.

Save as ToddlerProof.java, compile, then run with java ToddlerProof [me] [them]

Should we be using print or println?... I wasn't certain.
–
kaineJul 25 '14 at 20:31

Hmmm. I would imagine both would work, but I could see println messing up if the control program grabbed the newline instead of the character. Thanks for pointing that out, I'll edit my code just in case
–
StranjyrJul 25 '14 at 20:34

@Stranjyr there were some bugs in your last run. It didn't bomb the control program but if you search the history for "ToddlerProof plays n" it looks like your bot was returning null for certain hands and then autolosing the hand. Example game is "Echo & ToddlerProof" where Echo played "LVSPRLV" before your bot started to crap out.
–
Eoin CampbellJul 30 '14 at 13:09

@Eion Campbell Thanks for mentioning it. I saw that earlier when you posted the logs from the failed tourney, and I think I have it fixed. It was running into an error where if it lost more than 5 straight, instead of jumping to a random play it just threw an invalid value. And then, because that made it lose, it threw another invalid value. A vicious cycle.
–
StranjyrJul 30 '14 at 13:32

Cool. Have it updated in the control prog now.
–
Eoin CampbellJul 31 '14 at 17:09

Bart Simpson

"Good old rock! Nothing beats rock!"

puts 'R'

Run as

ruby DoTheBartman.rb

Lisa Simpson

"Poor, predictable Bart. Always chooses rock."

puts 'P'

Run as

ruby LisaSimpson.rb

Better Lisa Simpson

I felt bad about making Lisa quite so stupid, so I allowed her to randomly choose between either of the hands that will beat rock. Still stupid, but she is a Simpson after all. Maybe a crayon got stuck in her brain?

@MartinBüttner Damn, didn't notice that. The programs still seem to do different things though - and at least Lisa here can feel more superior by beating two different versions of her brother.
–
Dr R DizzleJul 28 '14 at 14:17

1

Sheldor agrees... there shall be a BartBot and a BartSimpson :)
–
Eoin CampbellJul 28 '14 at 15:36

IocainePowder, Ruby

Based off (shamelessly stolen from) the RPS strategy here.
The bot looks chooses a guess identical to the Markov bot, but then assumes the opponent has guessed what it's going to choose, and chooses a move to beat that one accordingly.

Note that I've just adapted the basic idea of the linked strategy, not followed it in detail.

You keep using that word. I do not think it means what you think it means.
–
JoshDMJul 28 '14 at 20:25

2

The real power of Iocaine Powder was that is switches between using the markov and beating-markov. It starts out as smart markov, but once it senses (starts losing) it jumps into beating-markov mode. Should be easy to add.
–
Roy van RijnJul 30 '14 at 14:39

Ahh, clever! Not gonna lie, I had only heard Iocaine described to me, not actually looked at it in detail. Feel free to modify my code if you'd like or submit your own and get the credit!
–
jmiteJul 30 '14 at 17:11

BayesianBot, Perl (now v2!)

Above everything else, this is a unique program. In it, you shall see the brilliant fusion of statistics and horrible programming form. Also, this bot probably breaks many rules of Bayesian statistics, but the name sounds cooler.

The core essence of this bot is its creation of 250 different predictive models. Each model takes the form of "Given that I played rock last turn and my opponent played scissors two turns ago, this is the probability distribution for my opponent's next move." Each probability distribution takes the form of a multi-dimensional Dirichlet distribution.

Each turn, the predictions of all applicable models (typically 10) are multiplied together to form an overall prediction, which is then used to determine which moves have the highest expected payoff.

Edit 1: In this version, I changed the prior distribution and made the bot more randomized when it is losing.

There are a few things which may be subject to improvement, such as the number of models (250 is only a 3 digit number), the choice of prior distribution (currently Dir(3,3,3,3,3)), and the method of fusing predictions. Also, I never bothered to normalize any of the probability distributions, which is okay for now because I'm multiplying them.

I don't have super high expectations, but I hope this bot will be able to do well.

Assumes the opposing bot is always reacting to my previous move, and either picking something that would beat it, something that would lose to it, or the same move, possibly from a restricted set of possible moves. It then picks the best move given that assumption.

Except that the first ten moves are hardcoded: first I pretend I only know lizard, then I assume my opponent always throws something to beat the last thing I threw until I have enough data for proper analysis.

Contrary to the name, the only time randomness is used in this program is on the first round, when there's no information. Instead, it's named for the gambler's fallacy, the belief that if a random event has happened less often in the past, it's more likely to happen in the future. For example, if you flip a fair coin 20 times, and the first 15 are heads, the gambler's fallacy states that the odds of the remaining flips being tails are increased. Of course, this is untrue; regardless of the previous flips, a fair coin's odds of coming up tails is always 50%.

This program analyzes the opponent's history, finds the 2 moves that it has used the least so far, and assumes that the opponent's move this time will be one of those two. Assigning 2 to a win, 1 to a draw and 0 to a loss, it finds the move with the maximum score against these two predicted moves and throws that.

By switching the MODE variable to 0, this program will operate based on a related fallacy, also sometimes referred to as the gambler's fallacy. It states that if a random event has been happened more often in the past, it's more likely to happen in the future. For example, if you flip a coin 20 times and the first 15 are heads, this fallacy states that the remaining flips are more likely to be heads, since there's currently a streak. In mode 0, this program operates the same way, except that it assumes the opponent will throw one of the two moves it's thrown most often so far.

Summary of this algorithm: look at what the opponent last played, and then randomly play one of the two moves that would lose against the opponent's last move if they played it again. So it's better against bots that don't play the same move twice in a row.
–
Rory O'KaneJul 26 '14 at 4:23

Haha. I don't really know if I did made it that way. If I am not wrong, it is actually just a convoluted way of randomly selecting any of the 5 moves. ;)
–
bitpwnerJul 26 '14 at 6:35

SuperMarkov, Python

An improved version of the current Markov bot. Makes chains of length 0, 1, and 2, using either only our moves, only his moves, or both, to predict. Weighs the chains and picks what it expects the best result to be. Takes the result less seriously if the sample size is small.