Forum: Ruby One Die Game (#203)

33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-05-01 17:48
(Received via mailing list)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Quiz:

1.  Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have elapsed from the time this message was
sent.

2.  Support Ruby Quiz by submitting ideas and responses
as often as you can!
Visit: <http://rubyquiz.strd6.com/suggestions>

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.

RSS Feed: http://rubyquiz.strd6.com/quizzes.rss

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## One Die Game (#203)

Hej Rubyists,

This week's quiz was submitted by Siep Korteling through the
suggestions page <http://rubyquiz.strd6.com/suggestions>.

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[1] in one of Martin Gardner's books.

[1]: http://en.wikipedia.org/wiki/Nim

Have Fun!
1566d4066e11205ec3e3aaeeaf89348b?d=identicon&s=25 Luke Cowell (lcowell)
on 2009-05-06 23:43
Why do I feel like I'm doing someone's homework assignment ?

Here's my attempt at the quiz:
http://www.pastie.org/469635

I know Daniel asked that the computer sometimes make a mistake (for
fun), but my computer is not really into having fun. It's a type-a,
aggressive, winner takes all computer. Don't mess with it.

I'm throwing the gauntlet down. Write you own computer opponent and we
can have them battle it out!

Subclass Player:
class MyRadAI < Player

Create a method next move:
def next_move(dice, opponents)

opponents is an array of the other players and each opponent has name
and score accessors.
the dice class has some convenient methods available to you to figure
out which moves are available:
def available_nums
def valid?(choice) is also useful.

Luke
554efc0a02df8d447f163d1d5360f668?d=identicon&s=25 Chris Cacciatore (truce)
on 2009-05-07 20:44
My AI is simple, but I think it is still pretty playable.

-C

class Game
  class Player
    attr_reader :name
    def initialize(die,name)
      @die,@name = die,name
    end
    def choose_move(score)
      gets.to_i
    end
  end

  class AI < Player
    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
  end

  class Die
    attr_reader :current_side
    def initialize
      roll
      @opposites = [[1,6],[2,5],[3,4]]
    end
    def roll
      @current_side = rand(6)+1
    end
    def legal_move?(side)
      return false if side == @current_side
      return false if side > 6 or side < 1
      a = [@current_side,side].sort!
      return !@opposites.include?(a)
    end
    def flip(side)
      if legal_move?(side)
        @current_side = side
        0
      else
        1
      end
    end
    private :roll
  end
  def initialize
    print "enter your name: "
    name = gets.chomp
    @d = Die.new
    @bust, @score = 31,0
    @players = [AI.new(@d,"the computer"),Player.new(@d,name)]
    @players = rand(10) < 5 ? @players : @players.reverse
    puts "#{@players.last.name} rolled the die to
#{@d.current_side}\n\n********************"
    @score += @d.current_side
  end
  def update
    @players.each do |p|
      puts "current score = #{@score}"
      while @d.flip(p.choose_move(@score)) == 1 ? true : false
        puts "invalid move"
      end
      puts "#{p.name} flipped the die to
#{@d.current_side}\n****************"
      @score += @d.current_side
      if @score > @bust
        puts "#{p.name} lost!"
        return false
      end
      if @score == @bust
        puts "#{p.name} won!"
        return false
      end
    end
    return true
  end
  def play
    while update
    end
  end
  private :update
end

g = Game.new
g.play
33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-05-12 00:42
(Received via mailing list)
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!
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.