From: Matt Hulse Date: 2007-04-17T00:20:05+09:00 Subject: Re: Magic Fingers (#120) I took the OO approach and implemented a strategy pattern. To see the game play out, pass in the -v flag, and -d will show the actual command given for each move. The output of the test file contains a frequency distribution to show how each matchup plays out. Here is some sample output for the second matchup in the test over 200 games: _____________________________________ Game 2: Aggressive vs Random 3: ---------------------------------33 4: --------------------------------------------------------------------68 5: -------------------------------------------43 6: ------------------------24 7: ---------------15 8: -----5 9: -------7 10: ----4 11: -1 Player 1: 166 wins Player 2: 34 wins ______________________________________ In an Aggressive vs Aggressive game, the first player wins in 3 rounds every time. Obviously there are better strategies that I didn't take the time to explore. Again, I'm happy to receive any comments. I feel like my solutions are so clunky compared to what you guys submit. I am here to learn! Here's my code: #file: game.rb #author: Matt Hulse - www.matt-hulse.com require 'player' class Game attr_reader :p1, :p2, :winner, :rounds def initialize(p1_strategy = "random", p2_strategy = "random") @p1 = Player.new(1,p1_strategy, self) @p2 = Player.new(2,p2_strategy, self) @rounds = 0 end def get_opponent(player) return @p1 if @p2 == player return @p2 if @p1 == player end def loop #keep going until one person has no hands left #after 100 rounds we're calling a draw! puts self if $VERBOSE until(game_over) do @rounds += 1 p1.move puts self if $VERBOSE unless game_over then p2.move puts self if $VERBOSE end break if @rounds >= 100 end if(lost?(p1)) then @winner = p2 elsif(lost?(p2)) @winner = p1 else puts "Draw" end end def game_over lost?(p1) || lost?(p2) end def lost?(player) player.left.finger_count <= 0 and player.right.finger_count <= 0 end def to_s "#{p1}\n#{p2}\n" end end #file: hand.rb #author: Matt Hulse - www.matt-hulse.com class Hand attr_reader :right_or_left, :finger_count def initialize(right_or_left, count = 1) @right_or_left = right_or_left @finger_count = count end def touch(hand) hand.add_fingers(self.finger_count) end def add_fingers(num) @finger_count += num if @finger_count >= 5 then @finger_count = 0 end end def clap(hand,num) hand.add_fingers(num) #num must be <= self.finger_count @finger_count -= num end def to_s result = "" #print left to right i = 0 (1..5).each{ i += 1 if(i <= @finger_count) then result += "|" else result += "-" end } return result if @right_or_left == :right return result.reverse end end #file: player.rb #author: Matt Hulse - www.matt-hulse.com require 'hand' class Player attr_reader :player_num, :right, :left, :strategy, :game def initialize(player_num, strategy, game) @player_num = player_num @strategy = strategy @game = game @right = Hand.new(:right,1) @left = Hand.new(:left,1) end def move begin eval(@strategy) rescue NameError => e random #default to random end end def get_opponent @game.get_opponent(self) end def get_larger_hand return @right if @right.finger_count > @left.finger_count return @left end def min(a,b) return a if a <= b return b end def random #all possible moves, choose randomly among them opponent = get_opponent valid_moves = Array.new opp_rt_count = opponent.right.finger_count opp_lt_count = opponent.left.finger_count if(@right.finger_count > 0) then valid_moves << "@right.touch(opponent.right)" if opp_rt_count > 0 valid_moves << "@right.touch(opponent.left)" if opp_lt_count > 0 #total on hand transferred to cannot be more than 5 #random number is between 1 and the minimum of what left can receive and right can give rand_max = min(5-@left.finger_count,@right.finger_count-1) valid_moves << "@right.clap(@left, #{rand(rand_max) + 1})" if rand_max > 0 end if(@left.finger_count > 0) then valid_moves << "@left.touch(opponent.right)" if opp_rt_count > 0 valid_moves << "@left.touch(opponent.left)" if opp_lt_count > 0 #total on hand transferred to cannot be more than 5 #random number is between 1 and the minimum of what right can receive and left can give rand_max = min(5-@right.finger_count,@left.finger_count-1) valid_moves << "@left.clap(@right, #{rand(rand_max) + 1})" if rand_max > 0 end move = valid_moves[rand(valid_moves.size)] eval(move) puts "Player #{player_num}: #{move}" if $DEBUG end def aggressive opponent = get_opponent #every move is to touch the opponents largest hand with the local largest hand move = "get_larger_hand.touch(opponent.get_larger_hand)" eval(move) puts "Player #{player_num}: #{move}" if $DEBUG end def to_s "#{player_num}: #{@left} #{@right}" end end #file: test.rb #author: Matt Hulse - www.matt-hulse.com require 'game' puts "Game 1: Random vs Random" num_rounds = Hash.new(0) wins = Hash.new(0) (1..200).each{ game = Game.new("random","random") game.loop if(game.winner) then puts "Player #{game.winner.player_num} won in #{game.rounds} rounds using #{game.winner.strategy} strategy." if $VERBOSE num_rounds[game.rounds] += 1 wins[game.winner.player_num] +=1 end } num_rounds.keys.sort{|a,b| a<=>b}.each{|i| puts "#{i}: #{'-'*num_rounds[i]}#{num_rounds[i]}" } wins.each_pair{|key,value| puts "Player #{key}: #{value} wins" } puts puts "Game 2: Aggressive vs Random" num_rounds = Hash.new(0) wins = Hash.new(0) (1..200).each{ game = Game.new("aggressive", "random") game.loop if(game.winner) then puts "Player #{game.winner.player_num} won in #{game.rounds} rounds using #{game.winner.strategy} strategy." if $VERBOSE num_rounds[game.rounds] += 1 wins[game.winner.player_num] +=1 end } num_rounds.keys.sort{|a,b| a<=>b}.each{|i| puts "#{i}: #{'-'*num_rounds[i]}#{num_rounds[i]}" } wins.each_pair{|key,value| puts "Player #{key}: #{value} wins" } puts puts "Game 3: Aggressive vs Aggressive" num_rounds = Hash.new(0) wins = Hash.new(0) (1..200).each{ game = Game.new("aggressive","aggressive") game.loop if(game.winner) then puts "Player #{game.winner.player_num} won in #{game.rounds} rounds using #{game.winner.strategy} strategy." if $VERBOSE num_rounds[game.rounds] += 1 wins[game.winner.player_num] +=1 end } num_rounds.keys.sort{|a,b| a<=>b}.each{|i| puts "#{i}: #{'-'*num_rounds[i]}#{num_rounds[i]}" } wins.each_pair{|key,value| puts "Player #{key}: #{value} wins" } Thanks, __________ Matt Hulse matt.hulse@gmail.com http://www.matt-hulse.com On Apr 13, 5:48 am, Ruby Quiz wrote: > 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.rubyquiz.com/ > > 3. Enjoy! > > Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone > on Ruby Talk follow the discussion. Please reply to the original quiz message, > if you can. > > -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= > > We have a foreign exchange student from Korea staying with us. The best part of > that is the intended exchange of cultures. For example, to kill time on a > recent plane trip, the student taught us a common finger game children play in > Korea. > > The rules are very easy: > > 1. Both players start by holding up one finger on each hand. > 2. On each turn a player must do one of the following: > a. Touch one of their hands to an opponent's hand, adding the finger > count on their hand to the touched hand. The player keeps the same > number of fingers, but the opponent must add the player's finger > count in addition to the fingers already on that hand. > b. Clap their hands together to "transfer" one or more fingers from > one hand to the other. You cannot transfer all the fingers off of > a hand. > 3. A hand with five or more fingers is eliminated, which is shown by > making a fist. An opponent can not add fingers to an eliminated > hand and an it cannot be used in touches, but players may transfer > fingers to it, "reviving" it. The first player with two eliminated > hands loses the game. > > For example, here is how a quick game might play out: > > 1: ----| |---- Starting fingers. > 2: ----| |---- > > 1: ----| |---- Player 1's left hand touches player 2's right hand. > 2: ----| ||--- > > 1: ----| |||-- 2's right touches 1's right hand. > 2: ----| ||--- > > 1: ----| |||-- 1's right touches 2's right hand. > 2: ----| ----- > > 1: ----| ||||- 2's left touches 1's right hand. > 2: ----| ----- > > 1: ----| |||-- 1's right touches 2's left hand. > 2: ----- ----- > > Of course, as a programmer, I immediately tried to solve this game. I suck the > fun right out of everything, but at least it gave us another quiz topic. > > This week's Ruby Quiz is to programmatically solve Magic Fingers. Is it a win > for the first or second player with perfect play, or can you always force a draw > with repeating counts? Have your program print some output that would convince > anyone beyond the shadow of a doubt what the game's outcome will be.