Skip to content

Instantly share code, notes, and snippets.

@bhauman
Last active December 16, 2015 20:38
Show Gist options
  • Select an option

  • Save bhauman/5493451 to your computer and use it in GitHub Desktop.

Select an option

Save bhauman/5493451 to your computer and use it in GitHub Desktop.
My entry for the asheville coders league event: http://www.meetup.com/Asheville-Coders-League/events/113701462/ I added a simple minmax based player as well.
class TicTacToe
attr_accessor :move_pos, :prev_ttt
@@winning_patterns = nil
def initialize(_move_pos = nil, _prev_ttt = nil)
@move_pos, @prev_ttt = _move_pos, _prev_ttt
end
def game_over?
valid_moves.empty?
end
def valid_moves
@valid_moves ||= (0..8).find_all { |mv| valid_move? mv}
end
def valid_move?(position)
position > -1 && position < 9 && board[position].nil?
end
def next_turn
!turn ? "X" : "O"
end
def move(position)
TicTacToe.new(position, self) if valid_move?(position)
end
def turn
@turn ||= prev_ttt.nil? ? true : !prev_ttt.turn
end
def board
@board ||= prev_ttt.nil? ? ([nil] * 9) : prev_ttt.board.clone.tap { |b| b[move_pos] = (turn ? "X" : "O") }
end
# win detection
def winning_patterns
@@winning_patterns ||=
(0..8).each_slice(3).to_a.tap { |s| (s.transpose + [[0,4,8], [6,4,2]]).each { |p| s.push(p) } }
end
def pattern_wins?(pattern)
pattern.collect { |p| board[p] }.join =~ /^(XXX|OOO)$/
end
def winner?
relevant_patterns = winning_patterns.select {|p| p.index(move_pos) }
if winning_pat = relevant_patterns.detect { |pat| pattern_wins?(pat) }
board[winning_pat.first]
else
false
end
end
def to_s
(0..8).to_a.each_slice(3).collect do |row|
row.collect { |p| board[p] || p.next}.join(" | ") + "\n"
end.join("---------\n")
end
def self.from_array(moves)
moves.inject(TicTacToe.new) { |accum, mv| accum.move(mv) }
end
end
class Game
def initialize(game = TicTacToe, ai_player = MinMaxPlayer.new)
@game = game
@ai_player = ai_player
end
def start(game = @game.new)
puts "\nWould you like to play a game?\n" if game.valid_moves.length == 9
puts "\n" + game.to_s + "\n"
if winner = game.winner?
puts "The winner is #{winner}!"
return
end
if game.game_over?
puts "It's a tie"
return
end
position = get_valid_move(game)
start(game.move(position))
end
def get_valid_move(game)
if @ai_player && game.next_turn == @ai_player.player
return @ai_player.best_move(game)
end
puts "Choose next move for #{game.next_turn} #{(game.valid_moves.map &:next).inspect}:"
move = gets().to_i - 1
if game.valid_move? move
move
else
puts "Invalid move!"
get_valid_move(game)
end
end
end
class MinMaxPlayer
attr_accessor :player
def initialize(player = "X")
@player = player
@memoizer = { }
end
def best_move(game)
score_moves = Hash[game.valid_moves.map { |mv| [score_game(game.move(mv)), mv]}]
score_moves[score_moves.keys.max]
end
def score_game(game)
@memoizer[game.board] ||= if game.winner?
(@player == game.next_turn ? -1 : 1) * 10
elsif game.game_over?
0
else
scores = game.valid_moves.collect { |m| score_game(game.move(m)) }
game.next_turn == @player ? scores.max : scores.min
end
end
end
def tests
def assert(phrase, statement)
if statement
puts "#{phrase}: Passed"
else
puts "##### #{phrase}: Failed"
end
end
ttt = TicTacToe.new
assert "has empty state", ttt.prev_ttt.nil?
ttt = ttt.move 4
assert "moving adds position to state", ttt.move_pos == 4
ttt = ttt.move 5
assert "moving adds second position to state after first", ttt.move_pos == 5 && ttt.prev_ttt.move_pos == 4
assert "valid_moves returns all posible moves", ttt.valid_moves == ((0..8).to_a - [4,5])
assert "valid_move? returns true if position isn't in state", ttt.valid_move?(6)
assert "valid_move? returns false if position is in state", !ttt.valid_move?(5)
assert "valid_move? returns false if position is < 0", !ttt.valid_move?(-1)
assert "valid_move? returns false if position is > 8", !ttt.valid_move?(9)
assert "pos return X if position is taken and its turn is odd", ttt.board[4] == "O"
assert "pos return O if position is taken and its turn is even", ttt.board[5] == "X"
assert "pos returns nil if position is not taken", ttt.board[7].nil?
assert "winner return X for X win", TicTacToe.from_array([0,3,1,4,6,5]).winner? == "X"
assert "winner return O for O win", TicTacToe.from_array([0,3,1,4,2]).winner? == "O"
assert "winner return O for diagonal O win", TicTacToe.from_array([0,3,4,2,8]).winner? == "O"
assert "winner return false for no win", TicTacToe.from_array([0,3,4,2,7]).winner? == false
end
tests()
Game.new.start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment