Tic-tac-toe: Difference between revisions

Content added Content deleted
(→‎{{header|Ruby}}: improve AI’s defense; refactor a lot; make more idiomatic; make board printout look nicer)
Line 5,245: Line 5,245:


=={{header|Ruby}}==
=={{header|Ruby}}==
<lang ruby>module TicTacToe
<lang ruby>require 'set'

ROWS = [[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
module TicTacToe
LINES = [[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]
class Game
class Game
def initialize(player1Class, player2Class)
def initialize(player_1_class, player_2_class)
@board = Array.new(10)
@board = Array.new(10) # we ignore index 0 for convenience
@free_positions = (1..9).to_a
@free_positions = Set.new(1..9)
@players = [player1Class.new(self), player2Class.new(self)]
@players = [player_1_class.new(self), player_2_class.new(self)]
@turn = rand(2)
@current_player_id = 0
puts "#{@players[@turn]} goes first."
@players[@turn].marker = "X"
@players[@current_player_id].marker = "X"
@players[nextTurn].marker = "O"
@players[other_player_id].marker = "O"
puts "#{@players[@current_player_id]} goes first."
end
end
attr_reader :free_positions, :board, :turn
attr_reader :board, :free_positions, :current_player_id
def play
def play
loop do
loop do
player = @players[@turn]
player = @players[@current_player_id]
idx = player.select
puts "#{player} selects #{player.marker} position #{idx}"
@board[idx] = player.marker
@free_positions.delete(idx)
# check for a winner
place_player_marker(player)
ROWS.each do |row|
if row.all? {|idx| @board[idx] == player.marker}
puts "#{player} wins!"
print_board
return
end
end
# no winner, is board full?
if player_has_won?(player)
if @free_positions.empty?
puts "#{player} wins!"
print_board
return
elsif board_full?
puts "It's a draw."
puts "It's a draw."
print_board
print_board
Line 5,284: Line 5,279:
end
end
nextTurn!
switch_players!
end
end
end
end
def nextTurn
def place_player_marker(player)
1 - @turn
position = player.select_position
puts "#{player} selects #{player.marker} position #{position}"
@board[position] = player.marker
@free_positions.delete(position)
end
def player_has_won?(player)
LINES.any? do |line|
line.all? {|position| @board[position] == player.marker}
end
end
def board_full?
@free_positions.empty?
end
def other_player_id
1 - @current_player_id
end
end
def nextTurn!
def switch_players!
@turn = nextTurn
@current_player_id = other_player_id
end
end
def opponent
def opponent
@players[nextTurn]
@players[other_player_id]
end
def turn_num
10 - @free_positions.size
end
end
def print_board
def print_board
col_separator, row_separator = " | ", "--+---+--"
display =lambda{|row| row.map {|i| @board[i] ? @board[i] : i}.join("|")}
label_for_position = lambda{|position| @board[position] ? @board[position] : position}
puts display[[1,2,3]], "-+-+-", display[[4,5,6]], "-+-+-", display[[7,8,9]]
row_for_display = lambda{|row| row.map(&label_for_position).join(col_separator)}
row_positions = [[1,2,3], [4,5,6], [7,8,9]]
rows_for_display = row_positions.map(&row_for_display)
puts rows_for_display.join("\n" + row_separator + "\n")
end
end
end
end
Line 5,315: Line 5,337:
class HumanPlayer < Player
class HumanPlayer < Player
def select
def select_position
@game.print_board
@game.print_board
loop do
loop do
Line 5,331: Line 5,353:
class ComputerPlayer < Player
class ComputerPlayer < Player
DEBUG = false # edit this line if necessary
def group_row(row)
markers = row.group_by {|idx| @game.board[idx]}
def group_positions_by_markers(line)
markers = line.group_by {|position| @game.board[position]}
markers.default = []
markers.default = []
markers
markers
end
end
def select
def select_position
opponent_marker = @game.opponent.marker
opponent_marker = @game.opponent.marker
winning_or_blocking_position = look_for_winning_or_blocking_position(opponent_marker)
# look for winning rows
return winning_or_blocking_position if winning_or_blocking_position
for row in ROWS
markers = group_row(row)
if corner_trap_defense_needed?
return corner_trap_defense_position(opponent_marker)
end
# could make this smarter by sometimes doing corner trap offense
return random_prioritized_position
end
def look_for_winning_or_blocking_position(opponent_marker)
for line in LINES
markers = group_positions_by_markers(line)
next if markers[nil].length != 1
next if markers[nil].length != 1
if markers[self.marker].length == 2
if markers[self.marker].length == 2
log_debug "winning on line #{line.join}"
return markers[nil].first
return markers[nil].first
elsif markers[opponent_marker].length == 2
elsif markers[opponent_marker].length == 2
idx = markers[nil].first
log_debug "could block on line #{line.join}"
blocking_position = markers[nil].first
end
end
end
end
if blocking_position
log_debug "blocking at #{blocking_position}"
# look for opponent's winning rows to block
return idx if idx
return blocking_position
end
end
# need some logic here to get the computer to pick a smarter position
def corner_trap_defense_needed?
# simply pick a position in order of preference
corner_positions = [1, 3, 7, 9]
opponent_chose_a_corner = corner_positions.any?{|pos| @game.board[pos] != nil}
return @game.turn_num == 2 && opponent_chose_a_corner
end
def corner_trap_defense_position(opponent_marker)
# if you respond in the center or the opposite corner, the opponent can force you to lose
log_debug "defending against corner start by playing adjacent"
# playing in an adjacent corner could also be safe, but would require more logic later on
opponent_position = @game.board.find_index {|marker| marker == opponent_marker}
safe_responses = {1=>[2,4], 3=>[2,6], 7=>[4,8], 9=>[6,8]}
return safe_responses[opponent_position].sample
end
def random_prioritized_position
log_debug "picking random position, favoring center and then corners"
([5] + [1,3,7,9].shuffle + [2,4,6,8].shuffle).find do |pos|
([5] + [1,3,7,9].shuffle + [2,4,6,8].shuffle).find do |pos|
@game.free_positions.include?(pos)
@game.free_positions.include?(pos)
end
end
end
def log_debug(message)
puts "#{self}: #{message}" if DEBUG
end
end
def to_s
def to_s
"Computer#{@game.turn}"
"Computer#{@game.current_player_id}"
end
end
end
end
Line 5,372: Line 5,430:
Game.new(ComputerPlayer, ComputerPlayer).play
Game.new(ComputerPlayer, ComputerPlayer).play
puts
puts
Game.new(HumanPlayer,ComputerPlayer).play</lang>
players_with_human = [HumanPlayer, ComputerPlayer].shuffle
Game.new(*players_with_human).play</lang>


sample output
sample output
<pre>
<pre>
Computer1 goes first.
Computer0 goes first.
Computer1 selects X position 5
Computer0 selects X position 5
Computer0 selects O position 3
Computer1 selects O position 9
Computer1 selects X position 7
Computer0 selects X position 3
Computer0 selects O position 1
Computer1 selects O position 7
Computer1 selects X position 2
Computer0 selects X position 8
Computer0 selects O position 8
Computer1 selects O position 2
Computer1 selects X position 9
Computer0 selects X position 1
Computer0 selects O position 6
Computer1 selects O position 4
Computer1 selects X position 4
Computer0 selects X position 6
It's a draw.
It's a draw.
O|X|O
X | O | X
-+-+-
--+---+--
X|X|O
O | X | X
-+-+-
--+---+--
X|O|X
O | X | O


Human goes first.
Human goes first.
1|2|3
1 | 2 | 3
-+-+-
--+---+--
4|5|6
4 | 5 | 6
-+-+-
--+---+--
7|8|9
7 | 8 | 9
Select your X position: 1
Select your X position: 3
Human selects X position 1
Human selects X position 3
Computer1 selects O position 6
1 | 2 | X
--+---+--
4 | 5 | O
--+---+--
7 | 8 | 9
Select your X position: 7
Human selects X position 7
Computer1 selects O position 5
Computer1 selects O position 5
X|2|3
1 | 2 | X
-+-+-
--+---+--
4|O|6
4 | O | O
-+-+-
--+---+--
7|8|9
X | 8 | 9
Select your X position: 4
Human selects X position 4
Computer1 selects O position 1
O | 2 | X
--+---+--
X | O | O
--+---+--
X | 8 | 9
Select your X position: 9
Select your X position: 9
Human selects X position 9
Human selects X position 9
Computer1 selects O position 3
Computer1 selects O position 8
X|2|O
O | 2 | X
-+-+-
--+---+--
4|O|6
X | O | O
-+-+-
--+---+--
7|8|X
X | O | X
Select your X position: 7
Select your X position: 2
Human selects X position 7
Human selects X position 2
It's a draw.
Computer1 selects O position 4
O | X | X
X|2|O
-+-+-
--+---+--
O|O|6
X | O | O
-+-+-
--+---+--
X|8|X
X | O | X</pre>
Select your X position: 8
Human selects X position 8
Human wins!
X|2|O
-+-+-
O|O|6
-+-+-
X|X|X
</pre>


=={{header|Run BASIC}}==
=={{header|Run BASIC}}==