Pousse (Game)
#! /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