Jump to content

Tic-tac-toe: Difference between revisions

2,558 bytes added ,  10 years ago
→‎{{header|Ruby}}: improve AI’s defense; refactor a lot; make more idiomatic; make board printout look nicer
(→‎{{header|Ruby}}: improve AI’s defense; refactor a lot; make more idiomatic; make board printout look nicer)
Line 5,245:
 
=={{header|Ruby}}==
<lang ruby>modulerequire TicTacToe'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
def initialize(player1Classplayer_1_class, player2Classplayer_2_class)
@board = Array.new(10) # we ignore index 0 for convenience
@free_positions = Set.new(1..9).to_a
@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[@turncurrent_player_id].marker = "X"
@players[nextTurnother_player_id].marker = "O"
puts "#{@players[@current_player_id]} goes first."
end
attr_reader :free_positionsboard, :boardfree_positions, :turncurrent_player_id
def play
loop do
player = @players[@turncurrent_player_id]
idx = player.select
puts "#{player} selects #{player.marker} position #{idx}"
@board[idx] = player.marker
@free_positions.delete(idx)
# check for a winnerplace_player_marker(player)
ROWS.each do |row|
if row.all? {|idx| @board[idx] == player.marker}
puts "#{player} wins!"
print_board
return
end
end
#if no winner, is board fullplayer_has_won?(player)
if @free_positions.empty? puts "#{player} wins!"
print_board
return
elsif board_full?
puts "It's a draw."
print_board
Line 5,284 ⟶ 5,279:
end
nextTurnswitch_players!
end
end
def nextTurnplace_player_marker(player)
1position -= @turnplayer.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
def nextTurnswitch_players!
@turncurrent_player_id = nextTurnother_player_id
end
def opponent
@players[nextTurnother_player_id]
end
def turn_num
10 - @free_positions.size
end
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
Line 5,315 ⟶ 5,337:
class HumanPlayer < Player
def selectselect_position
@game.print_board
loop do
Line 5,331 ⟶ 5,353:
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
end
def selectselect_position
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
if markers[self.marker].length == 2
log_debug "winning on line #{line.join}"
return markers[nil].first
elsif markers[opponent_marker].length == 2
idxlog_debug ="could markers[nil]block on line #{line.firstjoin}"
blocking_position = markers[nil].first
end
end
if blocking_position
log_debug "blocking at #{blocking_position}"
# look for opponent's winning rows to block
return idx ifreturn idxblocking_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|
@game.free_positions.include?(pos)
end
end
def log_debug(message)
puts "#{self}: #{message}" if DEBUG
end
def to_s
"Computer#{@game.turncurrent_player_id}"
end
end
Line 5,372 ⟶ 5,430:
Game.new(ComputerPlayer, ComputerPlayer).play
puts
Game.new(players_with_human = [HumanPlayer, ComputerPlayer)].play</lang>shuffle
Game.new(*players_with_human).play</lang>
 
sample output
<pre>
Computer1Computer0 goes first.
Computer1Computer0 selects X position 5
Computer0Computer1 selects O position 39
Computer1Computer0 selects X position 73
Computer0Computer1 selects O position 17
Computer1Computer0 selects X position 28
Computer0Computer1 selects O position 82
Computer1Computer0 selects X position 91
Computer0Computer1 selects O position 64
Computer1Computer0 selects X position 46
It's a draw.
O|X | O | X
--+---+--
XO | X |O X
--+---+--
X|O | X | O
 
Human goes first.
1 | 2 | 3
--+---+--
4 | 5 | 6
--+---+--
7 | 8 | 9
Select your X position: 13
Human selects X position 13
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
X1 | 2 |3 X
--+---+--
4 | O |6 O
--+---+--
7X | 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
Human selects X position 9
Computer1 selects O position 38
XO | 2 |O X
--+---+--
4X | O |6 O
--+---+--
7X |8 O | X
Select your X position: 72
Human selects X position 72
It's a draw.
Computer1 selects O position 4
O | X | X
X|2|O
--+---+--
OX | O |6 O
--+---+--
X |8 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}}==
21

edits

Cookies help us deliver our services. By using our services, you agree to our use of cookies.