DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Pousse (Game)

05.04.2008
| 2524 views |
  • submit to reddit
        
#! /usr/bin/ruby
# Pousse.rb   Ruby implementation of the game of Pousse.
class Hash
  #add a method to Hash that gets the next key in the hash, given a key.
  def next(key)
    keys = self.keys
    pos  = keys.index(key)
    if pos+1 == keys.length
      return keys[0]
    else
      return keys[pos+1]
    end
  end  
end
  
class Pousse
  def initialize(size = 4)
    @size       = size.to_i.freeze                      #our size can't change
    @board      = Array.new(@size){ Array.new(@size) }  #2d board array
    @previous   = []                                    #previous boards
    @players    = {:one => 'X', :two => 'O'}.freeze     #hash of players as:
                                                        # :name => piece
    run                                                 #run the game
  end
  
  #returns true if a victor is found, false if not
  def victory?
    grand_total = Hash.new(0)
    inv_players = @players.invert
    
    #add all the rows/cols representing a straight to grand_total
    @board.each_index do |x|
      r_sub, c_sub = Hash.new(0), Hash.new(0)
      @board[x].each_index do |y|
        r_sub[inv_players[@board[x][y]]] += 1 if @board[x][y]
        c_sub[inv_players[@board[y][x]]] += 1 if @board[y][x]
      end
      #add up subtotal vals
      [c_sub, r_sub].each do |a|
        a.each do |x|
          grand_total[x[0]] += 1 if x[1] == @size
        end
      end
    end
    
    #check for victory with straights
    if grand_total.values.inject{|sum, n| sum+n}
      if grand_total[@player] == grand_total[@players.next(@player)]
        puts "It's a tie!"
      elsif grand_total[@player] > grand_total[@players.next(@player)]
        puts "Player #{@player.to_s}, you win!"
        return true
      else
        puts "Player #{@players.next(@player)}, you win!"
        return true
      end
    end
    
    #check to see if this board is not a new one..
    @previous.each do |x|
      if x == @board
        puts "Player #{@player.to_s}, that board is a REPEAT! YOU LOSE!"
        return true
      end
    end
    #add a deep copy of the current board to the previous board list
    @previous << Marshal.load(Marshal.dump(@board))
    false
  end
  
  #print help messages
  def display_help
    puts "--------"
    puts "General commands:
    h - display this help dialog
    d - draw the board
    q - quit the program"
    
    puts "move? commands (i is an integer from 1 to #{@size})
    Li - Left
    Ri - Right
    Ti - Top
    Bi - Bottom"
    puts "--------"
  end
  
  #draw a grid representing current board status
  def draw_board
    #build numbers for top
    print "\n  "
    @size.times do |x|
      print " #{x+1}"
    end
    puts ""
    
    #main board section
    (@size * 2).times do |x|
      if (x % 2 == 0) #print a boundry
        print "  "
        @size.times { print "+-" }
        puts "+"
      else #print a data row
        print "#{x/2+1} "
        @size.times do |y|
          print "|"
          print @board[x/2][y] ? @board[x/2][y] : " "
        end
        puts "|"
      end
    end
    
    #bottom border
    print "  "
    @size.times { print "+-" }
     puts "+"
  end

  #execute a move? command. returns true if the move? was successful,
  #false if not
  def move?(command)
    direction, row = command.split(//)
    row = row.to_i
    
    if row > @size or row < 1 #we have a problem.
      puts "Invalid index value (#{row}) supplied."
      return false
    end
    
    row -= 1 #offset because of array index
    
    #setup values for shift_board call
    case direction.upcase
      when "L" then x = row;      y = 0;        dx = 0;   dy =  1
      when "R" then x = row;      y = @size-1;  dx = 0;   dy = -1
      when "T" then x = 0;        y = row;      dx = 1;   dy =  0
      when "B" then x = @size-1;  y = row ;     dx = -1;  dy =  0
    end
    shift_board(x, y, dx, dy, @players[@player])
    true
  end
  
  #game run loop. this method returns when the game is over
  def run
    puts "Welcome to Pousse!"
    display_help
    @player = :one
    loop do
      puts "Player #{@player.to_s}'s (#{@players[@player]}) turn."
      
      #get a value from the command line
      print "> "
      _input = $stdin.gets
      
      #process the input
      if    _input =~ /^[q|Q]/                  then break
      elsif _input =~ /^[h|H]/                  then display_help
      elsif _input =~ /^[d|D]/                  then draw_board
      elsif _input =~ /^[L|R|T|B|l|r|t|b][\d]/  then
        if move?(_input)
           draw_board
           break if victory? #exit the loop if we find a winner
           @player = @players.next(@player)
        end
      else puts "Command not recognized."
      end
    end
    puts "Thanks for playing Pousse!"
  end
  
  #shift cells in the board until our new data is appended and old data
  #is shifted into blank spaces or lost
  def shift_board(to_x, to_y, dx, dy, append)
    if to_y < @size and to_x < @size then #only handle existing cells
      if @board[to_x][to_y] != nil then
        mv = @board[to_x][to_y]
        @board[to_x][to_y] = nil
        shift_board(to_x+dx, to_y+dy, dx, dy, mv)
      end
      @board[to_x][to_y] = append
    end
  end
end

if ARGV[0]
  Pousse.new(ARGV[0]) 
else
  Pousse.new
end