203 One Die Game
Description
Hej Rubyists,
This week’s quiz was submitted by Siep Korteling through the suggestions page.
Imagine a two player game with the following rules:
- Playing material: 1 ordinary die; opposing sides sum up to 7.
- The first move consists of the first player rolling the die and initializing the total to the amount on the face shown.
- All further moves consist of turning the die one quarter and adding the face value of the new side to the current total. Example: If the die is facing 2 after the initial throw, then possible moves are 1,3,4 and 6, with totals 3,5,6 and 8.
- A player wins when that player’s move results in a total of 31.
- A player loses when that player overshoots 31.
Let’s say it’s your turn. The current total is 24 and the die is facing 3. You can win by turning the die to face 6, totaling 30. Your opponent cannot play 1 because it’s at the bottom of the die, so he is forced to overshoot the goal of 31.
Build a program which plays this game against a human opponent and blunders occasionally (to keep it fun).
P.S. I did not invent this game; I’m pretty sure I found this game in a chapter about nim-like games in one of Martin Gardner’s books.
Have Fun!
Summary
Luke Cowell and Chris Cacciatore both submitted solutions for this week’s quiz. There was some ambiguity in the way the quiz was posted, so the implementations were slightly different from one another. Luke had a running total for each player and Chris had a single total. Despite that difference, both solutions tackled the problem similarly and are both interesting.
The Dice classes in both solutions have methods for rolling a random side and validating moves by keeping track of opposites. Chris keeps an array of explicitly defined opposites: @opposites =
[[1,6],[2,5],[3,4]]. Luke’s method for determining available moves makes use of the fact that opposites are defined by the faces adding up to seven:
def available_nums
(1..@total_sides).reject {|n| (@top + n == (@total_sides + 1) || n == @top)}
end
Each of these implementations has a different strength when it comes to future changes: Chris’s is easier to rearrange the configuration of the dice and Luke’s is easier to expand to dice with a different number of faces. One caveat for dice of more faces is that additional adjacency information would need to be added (for example a dodecahedron or icosahedron would have different adjacencies).
Both solutions require the definition of only one method to create a new player or different AI. For Luke’s code the only method that needs to be implemented is next_move. Let’s take a look at the Human and Computer Player implementations:
class Human < Player
def next_move(dice, opponents = nil)
done = false
while(done == false)
choice = gets.to_i
if(dice.valid?(choice) == true)
done = true
else
print "!!!you can't choose #{choice}!!!"
end
puts
end
choice
end
end
For the human player it gets the choice from the prompt, prompting again if the player selects an invalid choice.
class Computer < Player
def next_move(dice, opponents = nil)
if(@score == 30)
return 1
elsif((@score % 6) == 0) && dice.available_nums.include?(6)
return 6
else
return dice.available_nums[rand(4)]
end
end
end
The computer player tries to set up a path for a win. If the score is a multiple of 6 and the computer is able to choose 6 on the die, then it will choose 6 each time until the score reaches 30, and then will choose 1 on the next turn to secure victory. If it can’t then it makes a random choice. It would be fairly easy to implement different computer AI by providing different ways to compute the next move.
Chris uses a similar technique for choosing the next move from the computer player. It checks for a winning move and takes it if available, otherwise it makes a random move.
def choose_move(score)
sleep(1)
if score < 25
while !@die.legal_move?( (r = rand(6)+1) )
end
return r
else
#simple win
return 31-score if @die.legal_move?(31-score)
return rand(6)+1
end
end
The play consists of players alternating moves. Both programs check for the validity of the move outside of the player’s implementation, so you are unable to cheat by having your player return a roll of (31
- current_value).
An interesting element of Luke’s program is that he allows you to set up a game between two human players or two computer players rather than just between a human and a computer.
Though the specific game mechanics were slightly different in each program, they both provided interesting solutions to the problem.
Thank you Luke and Chris for your submissions to this week’s quiz!