Although a player-versus-player version of tic-tac-toe is nice, chances are you will not have a friend who wants to spend time playing computerized tic-tac-toe with you for long. Perhaps it will be more satisfying if you create a version of tic-tac-toe that will play against you. This is what we will do in this section.
First, let’s understand the change we are trying to make. Instead of always having the player input the position on the board to fill, we want the computer to take one of the turns. Therefore, we will make a clearer distinction between the human’s move and the computer’s move. The code in Example 12-13 illustrates this change.
1
def
ask_player_for_move
(
current_player
)
2
if
current_player
==
COMPUTER_PLAYER
3
computer_move
(
current_player
)
4
else
5
human_move
(
current_player
)
6
end
7
end
The purpose of the ask_player_for_move
method is to switch between
player input and computer AI (artificial intelligence) based on the
current player. If the current player is the computer (line 2), let the
computer take its move (line 3); otherwise (line 4), let the user take her
or his move (line 5). Make sure to define the constant COMPUTER_PLAYER
. It may be set equal to
X
or O
. This would also be a good time to define the
constant HUMAN_PLAYER
. This should be
set equal to O
if COMPUTER_PLAYER
is equal to X
, or X
if
COMPUTER_PLAYER
is equal to O
.
If you have been reading carefully, you will have noticed that
ask_player_for_move
was already defined
pages ago, and now we have changed its definition here. Previously, the
method prompted either player one or player two for which turn she or he
wanted to make, and then took the turn if it was valid. That code was
relocated to the method human_move
. The
code for human_move
is identical to our
old ask_player_for_move
method.
At this point, we have a mechanism for changing between the human’s
and the computer’s turn, and we have slightly changed the definition of
the human’s turn. From here it should be clear that the next logical step
is to define the computer_move
method,
and it is defined in Example 12-14.
1
def
computer_move
(
current_player
)
2
row
=
-
1
3
col
=
-
1
4
found
=
"F"
5
6
check_rows
(
COMPUTER_PLAYER
,
found
)
7
check_cols
(
COMPUTER_PLAYER
,
found
)
8
check_diagonals
(
COMPUTER_PLAYER
,
found
)
9
10
check_rows
(
HUMAN_PLAYER
,
found
)
11
check_cols
(
HUMAN_PLAYER
,
found
)
12
check_diagonals
(
HUMAN_PLAYER
,
found
)
13
14
if
found
==
"F"
15
if
@board
[
1
][
1
]
==
EMPTY_POS
16
row
=
1
17
col
=
1
18
@board
[
row
][
col
]
=
current_player
19
elsif
available_corner
()
20
pick_corner
(
current_player
)
21
else
22
until
validate_position
(
row
,
col
)
23
row
=
rand
(
@board
.
size
)
24
col
=
rand
(
@board
.
size
)
25
end
26
@board
[
row
][
col
]
=
current_player
27
end
28
end
29
end
If this method looks complicated, don’t worry. First, let’s walk
through the code at an algorithmic level. The computer_move
method does the following in
order, picking the first rule it can successfully complete:
Check the rows, columns, and diagonals to see if either the computer can win or the human can win. If such a spot exists, take it to either win the game or prevent the human from winning. Note that we intentionally do not include the code for those methods, as their implementation is straightforward and their details are unnecessary for the purposes of our discussion.
If the middle cell is unoccupied, take the middle cell.
If there is an available corner, take any of the available corner spots.
If none of the prior conditions are true, pick a random cell.
For simplicity, we did not include all the necessary code to guarantee at least a draw for the AI. However, in tic-tac-toe, correct play guarantees at least a draw. To guarantee at least a draw, the computer’s corner selection option should be modified as follows:
If a corner spot is available, then select a corner spot, with the following corner selection preference. If the computer went first, randomly choose among the available corners that are not adjacent to the human’s noncenter spot(s). If the human went first, then check for one exception condition; otherwise, randomly choose among the available corners that are adjacent to the human’s noncenter spot(s). The exception condition is one in which only three squares are occupied, the computer has the center square, and the human has both corners on the same diagonal; in such a case, override the corner selection option and randomly choose a noncorner spot. If none of the aforementioned conditions are met, then choose any available corner.
This description defines a high-level algorithm for the game of tic-tac-toe. You now have all the needed skills to design, develop, and debug all the implementation specifics. Please do so and enjoy playing your computerized opponent.
3.147.75.221