From: Ruby Quiz Date: 2005-01-21T23:19:28+09:00 Subject: [QUIZ] Paper Rock Scissors (#16) The three rules of Ruby Quiz: 1. Please do not post any solutions or spoiler discussion for this quiz until 48 hours have passed from the time on this message. 2. Support Ruby Quiz by submitting ideas as often as you can: http://www.grayproductions.net/ruby_quiz/ 3. Enjoy! -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Alright generals, break out you copies of "The Art of War" and let's get a little competition going! Your task is to build a strategy for playing the game of Paper Rock Scissors against all manner of opponents. The question here is if you can adapt to an opponent's strategy and seize the advantage, while he is doing the same to you of course. If you're not familiar with this childhood game, it's very simple. Both players choose one of three items at the same time: Paper, a Rock, or Scissors. A "winner" is chosen by the following rules: Paper covers a Rock. (Paper beats a Rock.) Scissors cut Paper. (Scissors beat Paper.) A Rock smashes Scissors. (A Rock beats Scissors.) Anything else is a "draw". Defining a player for straight forward. I'm providing a class you can just inherit from: class YourPlayer < Player def initialize( opponent ) # optional # # called at the start of a match verses opponent # opponent = String of opponent's class name # # Player's constructor sets @opponent end def choose # required # # return your choice of :paper, :rock or :scissors end def result( you, them, win_lose_or_draw ) # optional # # called after each choice you make to give feedback # you = your choice # them = opponent's choice # win_lose_or_draw = :win, :lose or :draw, your result end end We'll need some rules for defining players, to make it easy for all our strategies to play against each other: * send in one file for each strategy * a file should contain exactly one subclass of Player * start the name of your subclass with your initials * start the name of your files with your initials * start any data files you write to disk with your initials Those rules should help with testing how different algorithms perform against each other. Here are two dumb Players to practice with: class JEGPaperPlayer < Player def choose :paper end end class JEGQueuePlayer < Player QUEUE = [ :rock, :scissors, :scissors ] def initialize( opponent ) super @index = 0 end def choose choice = QUEUE[@index] @index += 1 @index = 0 if @index == QUEUE.size choice end end Here's how those two do against each other in a 1,000 game match: JEGPaperPlayer vs. JEGQueuePlayer JEGPaperPlayer: 334 JEGQueuePlayer: 666 JEGQueuePlayer Wins Finally, here's the game engine that supports the players: #!/usr/bin/env ruby class Player @@players = [ ] def self.inherited( player ) @@players << player end def self.each_pair (0...(@@players.size - 1)).each do |i| ((i + 1)...@@players.size).each do |j| yield @@players[i], @@players[j] end end end def initialize( opponent ) @opponent = opponent end def choose raise NoMethodError, "Player subclasses must override choose()." end def result( you, them, win_lose_or_draw ) # do nothing--sublcasses can override as needed end end class Game def initialize( player1, player2 ) @player1 = player1.new(player2.to_s) @player2 = player2.new(player1.to_s) @score1 = 0 @score2 = 0 end def play( match ) match.times do hand1 = @player1.choose hand2 = @player2.choose case hand1 when :paper case hand2 when :paper draw hand1, hand2 when :rock win @player1, hand1, hand2 when :scissors win @player2, hand1, hand2 else raise "Invalid choice by #{@player2.class}." end when :rock case hand2 when :paper win @player2, hand1, hand2 when :rock draw hand1, hand2 when :scissors win @player1, hand1, hand2 else raise "Invalid choice by #{@player2.class}." end when :scissors case hand2 when :paper win @player1, hand1, hand2 when :rock win @player2, hand1, hand2 when :scissors draw hand1, hand2 else raise "Invalid choice by #{@player2.class}." end else raise "Invalid choice by #{@player1.class}." end end end def results match = "#{@player1.class} vs. #{@player2.class}\n" + "\t#{@player1.class}: #{@score1}\n" + "\t#{@player2.class}: #{@score2}\n" if @score1 == @score2 match + "\tDraw\n" elsif @score1 > @score2 match + "\t#{@player1.class} Wins\n" else match + "\t#{@player2.class} Wins\n" end end private def draw( hand1, hand2 ) @score1 += 0.5 @score2 += 0.5 @player1.result(hand1, hand2, :draw) @player2.result(hand2, hand1, :draw) end def win( winner, hand1, hand2 ) if winner == @player1 @score1 += 1 @player1.result(hand1, hand2, :win) @player2.result(hand2, hand1, :lose) else @score2 += 1 @player1.result(hand1, hand2, :lose) @player2.result(hand2, hand1, :win) end end end match_game_count = 1000 if ARGV.size > 2 and ARGV[0] == "-m" and ARGV[1] =~ /^[1-9]\d*$/ ARGV.shift match_game_count = ARGV.shift.to_i end ARGV.each do |p| if test(?d, p) Dir.foreach(p) do |file| next if file =~ /^\./ next unless file =~ /\.rb$/ require File.join(p, file) end else require p end end Player.each_pair do |one, two| game = Game.new one, two game.play match_game_count puts game.results end To use: paper_rock_scissors.rb jeg_paper_player.rb jeg_queue_player.rb Or you can point it at a directory and it will treat all the ".rb" files in there as Players: paper_rock_scissors.rb players/ You can also change the match game count: paper_rock_scissors.rb -m 10000 players/