Suggestion on Tic Tac Toe
I am designing my implementation strategy for Tic-Tac-Toe game. Since this is my 1st game implementation, I am a bit confused and need some general pointers.
Now, the total number of winning combinations in a Tic-Tac-Toe are 8. Currently, I plan to store these winning combinations in an array. Once the end user has made at least 3 moves, I would start checking if the Player has won the game by comparing the current positions used by a Player against this array. However, I am sure开发者_运维问答 this is not an efficient way to check if the player has a winning combination.
Can anyone please suggest me on how to go about with design the logic for the game?
Don't worry about efficiency. I wrote a backtracking solution and there are only 549,945 possible game states. It takes less than 0.25 seconds to run through these on my laptop. Here was my logic to see if the game was over - clearly not very efficient, but it doesn't matter:
private boolean isWinningMove(int row, int col) {
int piece = board[row][col];
// check current row
boolean w = true;
for (int x = 0; x < 3; x++) { w = w && (board[row][x] == piece); }
if (w) { return true; }
// check current column
w = true;
for (int x = 0; x < 3; x++) { w = w && (board[x][col] == piece); }
if (w) { return true; }
// check 0,0 diagonal
w = true;
for (int x = 0; x < 3; x++) { w = w && (board[x][x] == piece); }
if (w) { return true; }
// check 0,2 diagonal
w = true;
for (int x = 0; x < 3; x++) { w = w && (board[x][2 - x] == piece); }
return w;
}
Here were my results, which match data on the Wikipedia page for tic-tac-toe:
Moves Simulated: 549945
Draws=46080 Player1-Wins=131184 Player2-Wins=77904
Perfect Strategy Implies: Always a tie.
Games won in 0 moves? 0
Games won in 1 moves? 0
Games won in 2 moves? 0
Games won in 3 moves? 0
Games won in 4 moves? 0
Games won in 5 moves? 1440
Games won in 6 moves? 5328
Games won in 7 moves? 47952
Games won in 8 moves? 72576
Games won in 9 moves? 81792
Since the state space for tic-tac-toe is so small, you could store all the possible end game positions, and use rotations, but I think you're overthinking it a little.
Instead of storing a 3x3 array for the game board, use a 7x7 array, with the inner-most 3x3 for the game board. You should have at least three values that each square can represent -- something like PLAYER_1
, PLAYER_2
and NONE
. Initially, all values should be set to NONE
. Then, after every player's move, check all around the square that was chosen for for 3-in-a-row; 2 above, 2 below, 2 left, 2 right, 2 upper left, 2 lower right, 2 upper right, 2 lower left.
Why the 7x7 array? With a 7x7 array, you can safely search in all directions from any square in the 3x3 area without requiring if
statements to see if you're walking off the edge of the array. The board will look like this:
0 1 2 3 4 5 6 0 * * * * * * * 1 * * * * * * * 2 * * * * * * * 3 * * * * * * * 4 * * * * * * * 5 * * * * * * * 6 * * * * * * *
For example, if the first player moves to, 0,0 on the tic-tac-toe board, that is the same as moving to 2,2 on the 7x7 board. When the move is made, you run a check all around the 2,2 square to see if there are three squares in a row that have the same value
- above: 2,0 and 2,1 and 2,2
- below: 2,2 and 2,3 and 2,4
- left: 0,2 and 1,2 and 2,2
- right: 2,2, and 2,3 and 2,4
- upper-left: 0,0 and 1,1 and 2,2
- upper-right: 2,2 and 3,1 and 4,0
- lower-left: 0,4 and 1,3 and 2,2
- lower-right: 2,2 and 3,3 and 4,4
Since the band of squares around the 3x3 board will always have the value NONE
, they can never trigger a winning condition.
If any of those all match the same player value (e.g. PLAYER_1 for the first player), then the game is over with a win. Else, if all squares are taken, the game is a draw.
I've used this for other similar games in the past and it works quite well.
Consider representing the board by integers.
-1 = X
0 = empty
1 = O
now, add up the value of the squares for each of the 8 possibilities (3 up and down, 3 left and right, 2 diagionals).
if the sum is 3, O wins if the sum is -3, X wins
if the sum is 2, then O has a winning move in one of those positions if the sum i -2, then X has a winning move in one of those positions.
The AI can use that as a basis for making decisions. A one move look ahead is sufficient to never lose.
If the AI starts the game, the best move is a corner. If the opponent fails to take the center, the AI wins. If he does take the center, then either the AI wins, or draws.
Instead of iterating over something I just wrote the 8 combinations.
My evaluation func does the following: if A is side to move & if there is two A elements and a 0 (empty) in one of all combinations then it's a win:
boolean player_can_win(int value) { //value is side's element*2
return board[0] + board[1] + board[2] == value
|| board[3] + board[4] + board[5] == value
|| board[6] + board[7] + board[8] == value
|| board[0] + board[3] + board[6] == value
|| board[1] + board[4] + board[7] == value
|| board[2] + board[5] + board[8] == value
|| board[0] + board[4] + board[8] == value
|| board[2] + board[4] + board[6] == value;
}
Explicitly storing and comparing to the solutions isnt the most efficient if you were playing generalized N X N tic-tac-toe, but since it's such as small board and there are only 8 such combos there is nothing wrong with explicitly storing solutions like this.
The bigger issue is that depending on storage style, spaces that aren't relevant to the solution might be an issue.
O - - - O -
X X X vs. X X X
O - O O - O
comparing 3x3 state arrays these are different and as such this method would require well over 8 end states
I presume you keep something like a gameState 3x3 array with blank=0, X=1, O=2 ?
Besides those explicit comparisons, you can do something like
win = false
// rows/columns
for i in 0,1,2
if (state[i][0] != BLANK && state[i][0] == state[i][1] == state[i][2]) win = true
#extensible to NxN - all(j == state[i][0] for j in state[i])
if (state[0][i] != BLANK && state[0][i] == state[1][i] == state[2][i]) win = true
#extensible to NxN - all(j == state[0][i] for j in zip(*state)[i])
//diagonals
if (state[0][0] != BLANK && state[0][0] == state[1][1] == state[2][2]) win = true
#extensible to NxN - all(state[j][j] == state[0][0] for j in range(len(state))
if (state [2][0] != BLANK && state[2][0] == state[1][1] == state[0][2]) win = true
If you want win to store the winner rather than flag, then make win = BLANK up top, and set to the value of any of the involved squares. Shouldn't be necessary tho, winner is obviously the most recent move!
I think the part of writing tic-tac-toe that you may find most challenging, but not too hard, would the AI. It is not too difficult, but not exactly trivial, to write an AI that wont lose (can always at least force a tie). If you want a relatively good AI that is capable of losing occasionally, youll need to add some randomness or something.
Implementing the Tic Tac Toe game is probably the simplest problem to solve in terms of AI and search space.
The key is to approach the problem with Minimax, Iterative deepening Depth-first search and Alpha-beta pruning algorithms.
Here's my implementation of the game in Python, which is only ~200 lines of code and has the capability to play a game as Human vs. Human
, Human vs. Computer
, and Computer vs. Computer
. It also keeps the statistics on depths and number of nodes reached/pruned leading up to the best move.
I highly recommend edX.org
Artificial Intelligence course, which gives the the fundamental knowledge on current AI topics and solutions.
精彩评论