Skip to content

Instantly share code, notes, and snippets.

@bridgpal
Forked from sjgjohnston/tic-tac-toe.rb
Created October 1, 2013 05:39
Show Gist options
  • Save bridgpal/6774295 to your computer and use it in GitHub Desktop.
Save bridgpal/6774295 to your computer and use it in GitHub Desktop.
require "Rumoji" # See; https://github.com/mwunsch/rumoji
# Customize grid space and player icons as emoji charcters from: http://www.emoji-cheat-sheet.com/
require "Rainbow"
class Grid
attr_accessor :production_run, :player_x_moves, :player_o_moves
def initialize(production_run, player_x_moves, player_o_moves)
@is_debug_mode = false # set to true to invoke debugging statements if needed...
@production_run = production_run #
@player_x_moves = player_x_moves # the set of grid positions filled by player x
@player_o_moves = player_o_moves # the set of grid positions filled by player o
@player_x = Rumoji.decode(":negative_squared_cross_mark:") # "X"
@player_o = Rumoji.decode(":large_blue_circle:") # "O"
@box1 = Rumoji.decode(":one:") # "1"
@box2 = Rumoji.decode(":two:") # "2"
@box3 = Rumoji.decode(":three:") # "3"
@box4 = Rumoji.decode(":four:") # "4"
@box5 = Rumoji.decode(":five:") # "5"
@box6 = Rumoji.decode(":six:") # "6"
@box7 = Rumoji.decode(":seven:") # "7"
@box8 = Rumoji.decode(":eight:") # "8"
@box9 = Rumoji.decode(":nine:") # "9"
# Needed to keep the base number grid value settings useable again despite player move updates (x and o insertions) to the game grid boxes...
@box1_base = @box1.dup
@box2_base = @box2.dup
@box3_base = @box3.dup
@box4_base = @box4.dup
@box5_base = @box5.dup
@box6_base = @box6.dup
@box7_base = @box7.dup
@box8_base = @box8.dup
@box9_base = @box9.dup
# Create start grid state...
update_grid()
end # method initialize
def print_grid()
print "\n . . "
print "\n #{@box1} | #{@box2} | #{@box3} "
print "\n----+----+----"
print "\n #{@box4} | #{@box5} | #{@box6} "
print "\n----+----+----"
print "\n #{@box7} | #{@box8} | #{@box9} "
print "\n ' ' "
end # method print_grid
def reset_grid()
initialize(@production_run, [], [])
end # method reset_grid
def update_grid()
debug("\nlocation : at start of 'upgrade_grid' method.") if @is_debug_mode
#update visual grid boxes for player_x_moves..
@player_x_moves.each do |x_move|
print "\nDEBUG: player_x_moves.each instance: #{get_icon(x_move)} \n" if @is_debug_mode
case x_move
when "1"
@box1 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box1_base} to #{@player_x}\n" if @is_debug_mode
when "2"
@box2 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box2_base} to #{@player_x}\n" if @is_debug_mode
when "3"
@box3 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box3_base} to #{@player_x}\n" if @is_debug_mode
when "4"
@box4 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box4_base} to #{@player_x}\n" if @is_debug_mode
when "5"
@box5 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box5_base} to #{@player_x}\n" if @is_debug_mode
when "6"
@box6 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box6_base} to #{@player_x}\n" if @is_debug_mode
when "7"
@box7 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box7_base} to #{@player_x}\n" if @is_debug_mode
when "8"
@box8 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box8_base} to #{@player_x}\n" if @is_debug_mode
when "9"
@box9 = @player_x.dup #"X"
print "\nDEBUG: setting #{@box9_base} to #{@player_x}\n" if @is_debug_mode
end # case
end # player_x_moves.each
#update visual grid boxes for player_o_moves
@player_o_moves.each do |o_move|
print "\nDEBUG: player_o_moves.each instance: #{get_icon(o_move)} \n" if @is_debug_mode
case o_move
when "1"
@box1 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box1_base} to #{@player_o}\n" if @is_debug_mode
when "2"
@box2 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box2_base} to #{@player_o}\n" if @is_debug_mode
when "3"
@box3 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box3_base} to #{@player_o}\n" if @is_debug_mode
when "4"
@box4 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box4_base} to #{@player_o}\n" if @is_debug_mode
when "5"
@box5 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box5_base} to #{@player_o}\n" if @is_debug_mode
when "6"
@box6 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box6_base} to #{@player_o}\n" if @is_debug_mode
when "7"
@box7 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box7_base} to #{@player_o}\n" if @is_debug_mode
when "8"
@box8 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box8_base} to #{@player_o}\n" if @is_debug_mode
when "9"
@box9 = @player_o.dup #"O"
print "\nDEBUG: setting #{@box9_base} to #{@player_o}\n" if @is_debug_mode
end # case
end # player_o_moves.each
# Print an updated display grid...
print "\n updated grid....\n" if @is_debug_mode
print_grid()
# Check if we have a winner yet...
if is_winner?("X")
print "\n\nPlayer #{@player_x} wins!\n\n"
if @production_run # continue to new game if in prodution_run mode...
print "\n--------------\n** New game **\n".color("#00CCCC")
reset_grid()
process_move() # start new game
end
elsif is_winner?("O")
print "\n\nPlayer #{@player_o} wins!\n\n"
if @production_run # continue to new gaae if in production_run mode...
print "\n--------------\n** New game **\n".color("#00CCCC")
reset_grid()
process_move() # start new game
end
elsif ( (@player_x_moves + @player_o_moves).length == 9) # no more moves left and no winner...
print "\n\nGame ended in a tie.\n\n"
if @production_run # contnue to new game if in production_run mode...
print "\n--------------\n** New game **\n".color("#00CCCC")
reset_grid()
process_move() # start new game
end
else
print "\n\nDEBUG: no winner yet...." if @is_debug_mode
end
end # method update_grid()
def is_winner?(player)
#check if each single array of player_moves contains any of the subsets the finite single array winning states
debug("\nlocation : at start of 'is_winner?' method.") if @is_debug_mode
winner = false
winning_grids = [ ["1","2","3"], ["4","5","6"], ["7","8","9"], ["1","4","7"], ["2","5","8"], ["3","6","9"], ["1","5","9"], ["3","5","7"] ]
(player == "X") ? (player_moves = @player_x_moves; player_icon = @player_x) : (player_moves = @player_o_moves; player_icon = @player_o)
print "\n\tDEBUG: player_moves passed into 'is_winner?': #{player_moves}" if @is_debug_mode
winning_grids.each do |win_pattern|
print "\n\tDEBUG: Checking against win_pattern #{win_pattern}" if @is_debug_mode
win_pattern_temp = win_pattern.dup #needed becauyse 'contains_all?' called in the next line destroys the second array passed-in
if ( contains_all?(player_moves, win_pattern_temp) )
winner = true
end
end # winning_grids.each
print "\n\nDEBUG: winner state check: #{player_icon}\n" if @is_debug_mode
winner
end # method is_winner?
def is_odd?(number)
( (number % 2) == 0 ) ? false : true
end # method is_odd?
def contains_all? (first_array, second_array)
#method to return true if all the elements of second_array are contained within first_array
debug("\nlocation : at start of 'contains_all?' method.") if @is_debug_mode
#first_array.each { |e| if i = second_array.index(e) then second_array.delete_at(i) end}
first_array.each do |e|
print "\n\t\tDEBUG: first_array[e] = #{e}" if @is_debug_mode
if i = second_array.index(e)
second_array.delete_at(i)
print "\n\t\t\tDEBUG: second_array[#{e}] is at index #{i} and is being deleted." if @is_debug_mode
end
end
print "\n\t\tDEBUG: second_array.empty? = #{second_array.empty?}" if @is_debug_mode
second_array.empty? #if true then
end # method contains_all?
def is_valid_move?(player_move)
#puts "Player move = #{player_move}"
all_moves = @player_x_moves + @player_o_moves
player_move_temp = player_move.dup #needed becauyse 'contains_all?' called in the next line destroys the second array passed-in
not contains_all?(all_moves, player_move_temp.chars.to_a) #need the preceeding .chars here to get the .to_a to work
end # method is_valid_move?
def debug(message)
print "\n---------------------"
print "\nDEBUG:\n#{message}"
print "\n\tplayer_x_moves = #{@player_x_moves}"
print "\n\tplayer_o_moves = #{@player_o_moves}\n"
print_grid()
print "\n---------------------"
end # method debug(message)
def process_move()
all_moves = @player_x_moves + @player_o_moves
move_count = all_moves.length + 1
debug("move count : #{move_count}\nlocation : at start of 'process_move' method.") if @is_debug_mode
if ( move_count <= 9 ) # there are only nine boxes on the grid...
is_odd?(move_count) ? ( player=@player_x ) : ( player=@player_o )
print "\n\n#{move_count}. Player #{player} make your move [ #{@box1_base} - #{@box9_base} , q ]: "
player_move = gets.chomp #todo: validate input
if (player_move.upcase != "Q")
if ( ("1".."9").to_a.include? (player_move) )
begin # begin..end section needed around the following exception code because there is a trailing 'else' from the above 'if' that needs to be clearly excluded from the ensure section below
raise DuplicateMoveError if ( not is_valid_move?(player_move) )
(player == @player_x) ? ( @player_x_moves.push(player_move) ) : ( @player_o_moves.push(player_move) )
rescue Exception => e
print "\n\tInvalid move: #{get_icon("base", player_move)} is already filled by player #{get_icon("move", player_move)} \n"
( print "\tDEBUG: " ; p e.backtrace.inspect if ( e.class == DuplicateMoveError ) ) if is_debug_mode
ensure
update_grid()
process_move() #continue the game
end # begin
# Original if-else-end logic replaced by begin..end section above.
# if ( is_valid_move?(player_move) )
# (player == @player_x) ? ( @player_x_moves.push(player_move) ) : ( @player_o_moves.push(player_move) )
# update_grid()
# process_move() #continue the game
# else
# print "\n\tInvalid move: #{get_icon("base", player_move)} is already filled by player #{get_icon("move", player_move)} \n"
# ( print "\tDEBUG: " ; p e.backtrace.inspect if ( e.class == DuplicateMoveError ) ) if is_debug_mode
# update_grid()
# process_move() #continue the game
# end
else
print "\n\tInvalid selection. Pick one of [ #{@box1_base} - #{@box9_base} , q ]\n"
update_grid()
process_move() #continue the game
end # if ( ("1".."9").to_a.include? (player_move) )
else
print "\nQuiting game.\n\n"
exit
end # if (player_move.upcase != "Q")
else
#print "\n\nGame ended in a tie.\n\n"
update_grid()
end # if ( move_count <= 9 )
end # method process_move()
def get_icon(type, box_selection)
case box_selection
when "1"
(type == "base") ? (return @box1_base) : (return @box1)
when "2"
(type == "base") ? (return @box2_base) : (return @box2)
when "3"
(type == "base") ? (return @box3_base) : (return @box3)
when "4"
(type == "base") ? (return @box4_base) : (return @box4)
when "5"
(type == "base") ? (return @box5_base) : (return @box5)
when "6"
(type == "base") ? (return @box6_base) : (return @box6)
when "7"
(type == "base") ? (return @box7_base) : (return @box7)
when "8"
(type == "base") ? (return @box8_base) : (return @box8)
when "9"
(type == "base") ? (return @box9_base) : (return @box9)
end
end # method get_icon
end # class Grid
class DuplicateMoveError < StandardError
# Creating my own exception...
end # class DuplicateMoveError
# RUN
production_run = true # set to true to have the main tic-tac-toe game run invoked... set to false to have the test runs (below) invoked...
# Main two player game...
if production_run
# Setup start empty game grid state..
#grid = Grid.new(production_run, [], []) # so you can start the game in any state and continue...
# Test in-progress game state...
print "\n\nTest game in progress..."
player_x_moves = ["2", "4", "6", "7"]
player_o_moves = ["1", "3", "5"]
grid = Grid.new(production_run, player_x_moves, player_o_moves) # so you can start the game in any state and continue...
# Start or continue game...
grid.process_move()
end
#Test game grid states...
if ( not production_run )
# Test player X wins...
print "\n\nTest player X wins..."
player_x_moves = ["1", "2", "3"]
player_o_moves = ["4", "5"]
grid = Grid.new(production_run, player_x_moves, player_o_moves) # so you can start the game in any state and continue...
print "--------------\n"
# Test player O wins...
print "\n\nTest player O wins..."
player_x_moves = ["2", "4", "6"]
player_o_moves = ["1", "5", "9"]
grid = Grid.new(production_run, player_x_moves, player_o_moves) # so you can start the game in any state and continue...
print "--------------\n"
# Test game tie...
print "\n\nTest game tie..."
player_x_moves = ["2", "4", "6", "7", "9"]
player_o_moves = ["1", "3", "5", "8"]
grid = Grid.new(production_run, player_x_moves, player_o_moves) # so you can start the game in any state and continue...
print "--------------\n"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment