2

I'm making a Tic-Tac-Toe program. I plan to use minimax with it. I made a tree with space for all possible game sequences and I'm looking for a way to populate it. I currently have this type:

typedef struct name
{
    char grid [3] [3];
    struct name * child [9];
} node;

and I'm looking for a way to fill grid just like it's shown here. How would I go filling the grid to make sure that all possible combinations are there? My plan is to have the game recognize every move player can take and then decide what steps to take in order to win (I still need to figure out the decision part, but I'm holding that until I can fill the grids in tree).

AndrejaKo
  • 1,721
  • 5
  • 25
  • 41

6 Answers6

8

Sounds like a prime candidate for recursion to me...

Amber
  • 507,862
  • 82
  • 626
  • 550
  • At this moment, I can't see how would recursion help me. Could you make a bit more explicit answer? – AndrejaKo Nov 03 '10 at 18:52
  • 2
    Find all of the empty squares on the board associated with the node you're currently looking at. Create child nodes for each empty square, with that particular square filled in with whichever symbol's turn it currently is. Then recurse to each child node with the opposite symbol. Stop recursing when either the board is full or there is a three-in-a-row on the board. – Amber Nov 03 '10 at 19:01
  • @AndrejaKo recursion is the most obvious solution (check the other post). – ruslik Nov 03 '10 at 19:01
  • OK. Thanks. It's a bit clearer now. I'll have to modify my tree creation, but I'll save some memory this way. – AndrejaKo Nov 03 '10 at 19:07
6

Encode the positions in base 3. Make a unused grid 0; a grid with "X" a 1; and a grid with "O" a 2. So the absolute maximum number of complete grids you can have is 3^9 = 19683 (some of those are invalid tic-tac-toe representations)

So let's say you are dealing with '020112021'. Its children are:

020112021 /* father */ (4759 base 10)
=========
020112022                 father + 1 (1 base 3)
0201120x1 /* invalid */   father + 3 (10 base 3)
020112121                 father + 9 (100 base 3)
02011x021 /* invalid */   father + 27 (1000 base 3)
020122021                 father + 81 (10000 base 3)
020212021                 father + 243
021112021                 father + 729
0x0112021 /* invalid */   father + 2187
120112021                 father + 6561 (100000000 base 3)

I think you can figure a way to go on from here.

pmg
  • 106,608
  • 13
  • 126
  • 198
2

Pseudocode because recursion is hard to make into a list:

function descend(X_or_O, board)
    for square in board
        If square isn't empty: continue
        new_board = Fill in square with X_or_O.
        Check for a winner (if yes, return)
        newer_board = descend(opposite of X_or_O, new_board)
        tack newer_board onto the tree.
        clear out square

You should be able to do that with a couple for loops and if statements.

nmichaels
  • 49,466
  • 12
  • 107
  • 135
  • You forgot 0.5) Check if there's a winner (and return). – ruslik Nov 03 '10 at 19:03
  • Looks interesting. However, I have 9 boards on first level. I'm looking for a way to populate them all in such manner that I have a level for X's turn and a level for O's turn. It could be that my design is bad or that I'm misunderstanding your algorithm, but this way I'd get all combinations for a single starting point. I'd be able to easily modify it for different starting points, but what after second level? I'll do some testing and see how this works out. – AndrejaKo Nov 03 '10 at 19:05
  • @ruslik You're right! @Andrej, Yes, you're also right. I'll change things around to be clearer. – nmichaels Nov 03 '10 at 20:17
2

Child's play.

EDIT: Just in case the above link breaks, it's a reference to a description of the Tinkertoy Computer from Scientific American October 1989, also compiled and published with other SA amusement articles by the same author as The Tinkertoy Computer and Other Machinations. The guys (and gals) who built this machine were clever enough to avoid any alpha-beta search as well as compress the board into something that could easily be computed. By Tinkertoys.

ldav1s
  • 15,885
  • 2
  • 53
  • 56
1

Here you have a working solution. It is implemented in Python but I think it may help you.

The game tree is build recursively by the build_tree function, and is implemented as a list of lists.

The build_tree function takes a board (a node of the tree) and the piece that has the turn to play as input parameters, and builds all the possible new boards resulting of applying the piece to the board. Then, for each of these new boards, it calls the build_tree function again, but this time changing the piece that has the turn to play. When the board is terminal (no empty squares) the recursion ends.

This is the resulting tree for a 1x3 board:

[(0, 0, 0), [[('x', 0, 0), [[('x', 'o', 0), [('x', 'o', 'x')]], [('x', 0, 'o'), [('x', 'x', 'o')]]]], [(0, 'x', 0), [[('o', 'x', 0), [('o', 'x', 'x')]], [(0, 'x', 'o'), [('x', 'x', 'o')]]]], [(0, 0, 'x'), [[('o', 0, 'x'), [('o', 'x', 'x')]], [(0, 'o', 'x'), [('x', 'o', 'x')]]]]]]

Empty squares are denoted by '0'.

For the tic tac toe game, please change blank_board = (0,0,0) to blank_board = (0,0,0,0,0,0,0,0,0) in order to have a 3x3 board.

def change_piece(piece):
    if piece == 'x': return 'o'
    return 'x'

def is_terminal(board):
    """Check if there are any empty square in the board"""
    for square in board:
        if square == 0:
            return False
    return True

def build_tree(node, piece):
    """Build the game tree recursively. 

    The tree is implemented as a list of lists.
    """
    child_nodes = []
    for index, value in enumerate(node):
        if value == 0:
            new_node = list(node)
            new_node[index] = piece
            new_node = tuple(new_node)
            if not is_terminal(new_node):
                child_nodes.append(build_tree(new_node,change_piece(piece)))
            else:
                child_nodes.append(new_node)
    if child_nodes:
        return [node,child_nodes]
    return

if __name__ == "__main__":
    blank_board = (0,0,0)
    game_tree = build_tree(blank_board,'x')
    print(game_tree)
ssoler
  • 4,884
  • 4
  • 32
  • 33
1

This is a classic case for recursion:

typedef struct name
{
    char grid [3] [3];
    struct name * child [9];
} node;

node * new_grid(node *parent) {
    node *n = calloc(1, sizeof(node));
    if (parent)
        memcpy(n->grid, parent->grid, sizeof(grid));
}

// is n a winner based on the move just made at x,y?
int winner(const node *n, int x, int y) {
    return (n->grid[x][0] == n->grid[x][1] == n->grid[x][2]) ||
           (n->grid[0][y] == n->grid[1][y] == n->grid[2][y]) ||
           ((x == y) && (n->grid[0][0] == n->grid[1][1] == n->grid[2][2])) ||
           ((2-x == y) && (n->grid[0][2] == n->grid[1][1] == n->grid[2][0]));
}

void fill(node *n, char c) {
    int x, y, children;
    node *child;

    for (x = 0; x < 3; x++) {
        for (y = 0; y < 3; y++) {
             if (n->grid[x][y])
                 continue;
             child = n->child[children++] = new_grid(n);
             child->grid[x][y] = c;

             // recurse unless this is a winning play
             if (!winner(child, x, y))
                 fill(child, c == 'x' ? 'y' : 'x');
        }
    }
}

int main(void) {
    node *start = new_grid(0);
    fill(start);
}
Frank Szczerba
  • 5,000
  • 3
  • 31
  • 31