writing rock-paper-scissors bots in ruby
The game is trivial from a game-theory point of view. The optimal mixed strategy is to choose an action uniformly at random (one-third probability of each). This will ensure a break-even result in the long run, regardless of how strong (or how weak!) the opponent is.
However, against predictable opponents, a player can attempt to detect patterns in the opponent's play, and exploit those weaknesses with an appropriate counter-strategy.
A RubyShamBo tournament pits ruby programs, each implimenting different strategies, against each other. RubyShamBo was inspired by the International RoShamBo Programmming Competition. Much of this summary has been shamelessly stolen from the original announcement of that competition. After each turn, the winning bot recieves 1 point, and the losing bot losses 1 point. If the game ends in a tie, no points are awarded. At the end of the tournament, the bots are ranked by the total number of points they have gained in all matches against all bots.
Some of the programs in a RubyShamBo tournament will use sub-optimal strategies, and will be vulnerable to a perceptive and adaptive opponent. Of course, there is always a risk associated with such a prediction, as that player may be attempting to trap its opponent by anticipating the reaction to previous plays.
The most successful programs will recognize a variety of patterns and relationships, and use that information to gain an advantage over each opponent, without being susceptible to similar attacks.
At this stage there are no firm plans to host a public RubyShamBo tournament, but if you'd like to enter (or even help organise) such an event, please send an email to jonno at jamtronix dot com expressing your interest.
running a tournament
First, you will need to have ruby installed. Windows users should get the latest version of the One-Click Ruby Installer. Users of other platforms can consult the Ruby downloads site for a suitable version.Once ruby is set up on your machine, download the zip file containing latest RubyShamBo release and unzip to somewhere on to your local drive. The zip file will include the following folders :
- lib/ which contains 'rubyshambo.rb' which has all of the code for hosting a tournament
- bots/ which has a number of files each containing the code for a single bot
- test/ which contains 'test_tournament.rb', which will set up and run a tournament that pits every bot defined in the bots/ directory against each other, and then print out the results.
C:\src\rubyshambo\test>ruby test_tournament.rb Wagner 1 : 6027 YoungGun 1 : 2925 CivilWarBuff 1 : 2596 Bot 1 : 1 OldGeneral 1 : -442 SensitiveLefty 1 : -586 SportsFan 1 : -2608 BrokenRecord 1 : -7913This shows the most successful bot in this tournament is 'Wagner 1' which won 6027 more turns than it lost. The least succesful bot was 'BrokenRecord 1' which lost 7913 more turns than it won.
creating your own bot
Creating a new RubyShamBo bot requires some knowledge of programming in ruby. You should at least be able to complete the introductory browser-based tutorial at the Try Ruby! website. The rules that must be followed by each bot competing in a RubyShamBo tournament are:- The bot must be a subclass of Bot (which is defined in rubyshambo.rb)
- The bot must impliment a method called 'get_throw' which takes a single parameter, which is a MatchHistory object (also defined in rubyshambo.rb), and returns a symbol, which must be either :rock, :paper, or :scissors.
- If you define an 'initialize' method for the bot, it must not have any mandatory parameters (else the tournament framework can't autoload it)
The MatchHistory object that gets passed in to each call contains the following fields:
- turns_played will be 0 on the first turn against a new opponent
- my_throws is an array recording every throw you've made in the current match. Since ruby arrays are zero-indexed, match_history.my_throws[0] will have the throw you selected on the first turn in this match, match_history.my_throws[6] will have the move you made on the seventh turn, and match_history.my_throws.last will have the last throw you made.
- opponent_throws is an array recording every throw the current opponent has made in this match. The throw the opponent made last turn is in match_history.opponent_throws.last
- my_score is the score for the current match. The opponents score for the current match will always be the inverse of your score. i.e. if match_history.my_score is 5, that means you have won 5 more turns than you have lost in the current match (and thus the opponent's score must be -5).
- VALID_THROWS is an array containing [:paper,:scissors,:rock], i.e. VALID_THROWS[1] will return :scissors
- WINNING_THROW is a hash where the keys are possible throws and the values are the throw that beats the key. i.e. WINNING_THROW[:rock] returns :paper.
- Turn.random_throw will return a random throw.
an example bot
Here is the bot called 'Old General' (found in bots/oldgeneral.rb). He's called that because he's always planning to fight the last war. On the first turn he throws at random. On every subsequent turn, he throws whatever would have won the last turnclass OldGeneral<Bot def get_throw(match_history) if (match_history.turns_played==0) then return Turn.random_throw end return WINNING_THROW[match_history.opponent_throws.last] end end
