Tic Tac Toe algorithm timing out on long cases

I've encountered a programming problem where there are multiple games of tic tac toe being played and you have to deduce, when making a specific move, who will win if you continue making the best move possible. I've been tearing my hair out at what exactly is causing this problem; the program seems to work fine for one single case but with 15+ games it becomes near impossible. Any help would be appreciated.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#include <iostream>
#include <assert.h>
#include <cstddef>
#include <string>
#include <vector>
#include <sstream>
#include <deque>
using namespace std;

// A board struct for the game tic-tac-toe
struct BoardXO
{
    int8_t state[9]; //where player markers are on the board (player can be -1 or 1; 0 for unoccupied)
    uint8_t moves[9];  //sequence of moves that led to current game state (i.e., which marker position players selected)
    int8_t turn[9]; //which player made the corresponding move (either -1, 1, etc., or, -1, 1, etc.)
    uint8_t numMoves; //total number of moves that have been made in the game to this point

   BoardXO()
    {
        for (int i=0; i<9; i++)
        {
            state[i] = 0;
        }
        numMoves = 0;
    }
 const int8_t& operator[](std::uint8_t idx) const
    {
        if (idx<0 || idx > 8)
        {
            throw std::range_error("Error: Subscript out of bounds.");
        }
        return state[idx];
    }
};

class XO
{
private:
  BoardXO b; //a board that stores the current state of the game
  bool gameOver(const BoardXO& brd); //1 draw or game won, 0 available moves
  int8_t winner(const BoardXO& brd); //-1 for player -1 or 1 for player 1 or 0 for no winner (draw)
  bool makeMove(const int8_t& play, const uint8_t& pos, BoardXO& brd);

public:
    XO();
    XO(const int8_t* play, const uint8_t* pos, const uint8_t& n);
    void show();
    bool makeMove(const int8_t& play, const uint8_t& pos); //place player marker play (-1 or 1) at position pos; increment number of moves counter (numMoves)
    bool makeMoves(const int8_t* play, const uint8_t* pos, const uint8_t& n);
    bool makeOptimalMove(); // pre-condition: board has one marker set (i.e., one player has placed a marker); post condition: game state updated to reflect optimal move for active player
    BoardXO getBoard(); //return current board (this is mainly for testing/grading)
    bool gameOver(); //1 draw or game won, 0 available moves
    int8_t winner(); //-1 for player -1 or 1 for player 1 or 0 for no winner (draw)
};

XO::XO() {}
/* create game where moves have already been played. For example, a game could be initialized using XO({-1,1,-1,1,-1,1}, {2,0,3,7,6,8}, 6)*/
XO::XO(const int8_t* play, const uint8_t* pos, const uint8_t& n){} // moves won't be invalid for codeabbey.

//place player marker play (-1 or 1) at position pos; increment number of moves counter (b.numMoves)
bool XO::makeMove(const int8_t& play, const uint8_t& pos, BoardXO& brd)
{  //moves made will not be invalid for this problem.
    brd.state[(int)pos] = play;
    brd.moves[(int)brd.numMoves] = pos;
    brd.turn[(int)brd.numMoves] = play;
    brd.numMoves = brd.numMoves + 1;
    return true;
}

bool XO::makeMove(const int8_t& play, const uint8_t& pos){    return makeMove(play, pos, b);}
bool XO::makeMoves(const int8_t* play, const uint8_t* pos, const uint8_t& n)
{
    if ((int)n< 0 || (int)n > 9)
    {
        std::cout<<"At move " << (int)n << " Tried to make more moves that would breach legal amount"<<std::endl;
        return false;
    }
    for (int i=0; i<(int)n; i++)
    {
        if(!makeMove(*(play+i), *(pos+i)))
        {
            return false;
        }
    }
    return true;
}

// pre-condition: board has one marker set (i.e., one player has placed a marker); post condition: game state updated to reflect optimal move for active player
/* blah blah blah. */
bool XO::makeOptimalMove()
{
    if(gameOver())
    {
        return false;
    }
    deque<BoardXO> q;
    q.push_back(b);
    int scores[9] = {-10,-10,-10,-10,-10,-10,-10,-10,-10};
    for (int i=0 ; i<9; i++)
    {
        if((int)b[i]==0)
        {
            scores[i]= 1;
        }
    }
    while (!q.empty())
    {
        BoardXO c = q.front();
        q.pop_front();

        int nextPlay = 0;
        if((int)c.turn[(int)c.numMoves - 1] == 1)
        {
            nextPlay = -1;
        }
        else
        {
            nextPlay = 1;
        }
        if (!gameOver(c))
        {
            for (int i=0 ; i<9; i++)
            {
                BoardXO temp = c;
                if ((int)c.state[i] == 0)
                {
                    assert(makeMove((int8_t)nextPlay, (uint8_t)i, temp));
                    q.push_back(temp);
                }
            }
        }
        else
        {
            int initmove = (int)c.moves[(int)b.numMoves];
            int8_t whichPlay = c.turn[(int)b.numMoves];
           if (winner(c) == whichPlay)
            {
                 int newscore = 10 - c.numMoves;
              if(scores[initmove]> 0 && newscore > scores[initmove])
                {
                    scores[initmove] = newscore;
                }
            }
            else if ((int)winner(c) == 0)
            {
                if (scores[initmove]>0)
                {
                    scores[initmove] = 0;
                }
            }
            else
            {
                int newscore = c.numMoves - 10;
                if(newscore<scores[initmove])
                {
                    scores[initmove] = newscore;
                }
            }
        }
    }
    int nextPlay = (int)b.turn[(int)b.numMoves-1]*-1;
    int maxscore = -11;
     int index = 0;
    for (int i=0; i<9; i++)
    {
       
        if(scores[i] > maxscore && (int)b.state[i] == 0)
        {
            maxscore = scores[i];
            index = i;
        }
    }
    makeMove((int8_t)nextPlay, (uint8_t)index);
    return true;
}

BoardXO XO::getBoard(){    return b;  }

bool XO::gameOver(const BoardXO& brd)
{
    if (brd.numMoves == 9)
    {  return true;}    
    if (brd[0]!= 0)
    {
        if (brd[0] == brd[1] && brd[1] == brd[2])
            return true;

        if( brd[0] == brd[3] && brd[3] == brd[6])
            return true;

        if( brd[0] == brd[4] && brd[4] == brd[8])
            return true;
    }
    if (brd[3]!= 0 && brd[3] == brd[4] && brd[4] == brd[5])
    {
        return true;
    }
    if (brd[6]!= 0 && brd[6] == brd[7] && brd[7] == brd[8])
    {
        return true;
    }
    if (brd[2]!= 0)
    {
        if (brd[2] == brd[4] && brd[4] == brd[6])
            return true;
        if(brd[2]== brd[5] && brd[5]==brd[8])
            return true;
    }
    if (brd[1]!=0 && brd[1]== brd[4] && brd[4]==brd[7])
    {
        return true;
    }
    return false;
} //1 draw or game won, 0 available moves
int8_t XO::winner(const BoardXO& brd)
{
    if (brd[0]!= 0)
    {
        if (brd[0] == brd[1] && brd[1] == brd[2])
            return brd[0];

        if( brd[0] == brd[3] && brd[3] == brd[6])
            return brd[0];

        if( brd[0] == brd[4] && brd[4] == brd[8])
            return brd[0];
    }
    if (brd[3]!= 0 && brd[3] == brd[4] && brd[4] == brd[5])
    {
        return brd[3];
    }
    if (brd[6]!= 0 && brd[6] == brd[7] && brd[7] == brd[8])
    {
        return brd[6];
    }
    if (brd[2]!= 0)
    {
        if (brd[2] == brd[4] && brd[4] == brd[6])
            return brd[2];
        if(brd[2]== brd[5] && brd[5]==brd[8])
            return brd[2];
    }
    if (brd[1]!=0 && brd[1]== brd[4] && brd[4]==brd[7])
    {
        return brd[1];
    }
    return 0;
} //-1 for player -1, or 1 for player 1, or 0 for no winner (draw)

bool XO::gameOver(){    return gameOver(b); }

int8_t XO::winner(){    return winner(b);}
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
int main(){
    int inputs;
    cin>>inputs;
    cin.ignore();
    for (int i=0; i<inputs; i++){
        string moves;
        getline (cin, moves); 
       // cout<<"Got here ";
        stringstream stream(moves);
        vector <uint8_t> movevec;
        int n;
        while (stream>>n){
        //  cout<<"Creating movevec ";
       movevec.push_back(n);
        }
        uint8_t movearr [movevec.size()];
        int8_t turnarr [movevec.size()];
        int8_t nextplayer = 1;
        for (int j=0; j< movevec.size(); j++ ){
            movearr[j] = movevec[j];
            if(j%2==0){
                turnarr[j]=-1;
                nextplayer = 1;
            }
            else{
                turnarr[j]=1;
                nextplayer = -1;
            }
        }
        // create the game, then output who wins with each move
        int8_t* turnptr = turnarr;
        uint8_t* movptr = movearr;
        uint8_t a= movevec.size();
        XO thisGame(turnptr, movptr, a);
        XO temp [10];
        uint8_t count =0;
        int cpy = 0; //keep it separate
        for (int m = 0; m < 10; m++){ //make each possible move to output the winner.
            temp[cpy] = thisGame;
            //cout<<"move with m = "<< m<<" and i= "<<i<< " ";
            
            temp[cpy].makeMove(nextplayer, count);
            cpy++;
            count++;
              
            while(!temp[m].gameOver()){
                temp[m].makeOptimalMove();
               // BoardXO test = temp[m].getBoard();
               // cout<<"Changing the board "<<test<<" ";
            }
           //cout<<"Trying to display...";
            if(temp[m].winner()==-1){
            cout<<2;
            }
            else if (temp[m].winner() == 1){
                cout<<1;
            }
            else{
                cout<<0;
            }
            if (m ==9) {break;}
        }
        cout<<" ";
        }
    return 0;
}
}
Last edited on
Put your code in code tags so it's readable.
Post some input and corresponding output.
If it's from a website, post a link to the problem.
problem: https://www.codeabbey.com/index/task_view/tic-tac-toe-minimax-algorithm

input data:
3
1 8 7
7 1 5 3 4 8
7 2 1 3

answer:
000020000 101000100 000021202

my output: [nothing]

times out
my output: [nothing]
Your program doesn't try to generate any output except a single space at the end of main(). Should we uncomment that section at the end of the for loop in main?
Also:
1
2
3
4
    cin >> inputs;
    for (int i = 0; i < inputs; i++) {
        string moves;
        getline(cin, moves);

Inputs is on a line by itself. Line 1 reads the number, but NOT the newline. So the first time you hit line 4, it reads from after inputs, to the end of the line.

To consume the newline after inputs, add:
cin >> ws;
Right after line 1. This will consume any whitespace that follows inputs.
I consumed the whitespace, but the error's somewhere else. Good pointer though.
You aren't really gaining anything by using int8_t and uint8_t instead of int. Just make them ints and get rid of all the casts.

I don't see why you need to store the moves. Once they're on the board, they're done. You don't really need to refer to previous moves (do you?), except perhaps the last move, if that simplifies things.

You don't need an array for turn. Storing the one who went first is good enough, although in regular tic-tac-toe X always goes first, so maybe you don't need to store that at all (the turn number would tell you whose turn it is).

gameOver repeats the code of winner. It could just call winner instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Return true if game is over (draw or win), false otherwise.
bool XO::gameOver(const BoardXO& brd) {
    return brd.numMoves == 9 || winner(brd) != 0;
}

// Return true if the given 3 board positions have equal values other than zero.
inline bool eq(const BoardXO& brd, int a, int b, int c) {
    return brd[a] && brd[a] == brd[b] && brd[b] == brd[c];
}

// Return -1 for player -1, or 1 for player 1, or 0 for no winner (draw)
int XO::winner(const BoardXO& brd) {
    if (eq(brd, 0, 1, 2) ||
        eq(brd, 0, 3, 6) ||
        eq(brd, 0, 4, 8)) return brd[0];
    if (eq(brd, 1, 4, 7)) return brd[1];
    if (eq(brd, 2, 4, 6) ||
        eq(brd, 2, 5, 8)) return brd[2];
    if (eq(brd, 3, 4, 5)) return brd[3];
    if (eq(brd, 6, 7, 8)) return brd[6];
    return 0;
}


If they're expecting a function like the recursive one they show to work for their input then your TLE might be due to your code getting into an infinite loop with some inputs.
Last edited on
> for (int m = 0; m < 10; m++){ //make each possible move to output the winner.
¿there are 10 possible moves?


1
2
/* create game where moves have already been played. For example, a game could be initialized using XO({-1,1,-1,1,-1,1}, {2,0,3,7,6,8}, 6)*/
XO::XO(const int8_t* play, const uint8_t* pos, const uint8_t& n){} // moves won't be invalid for codeabbey. 

I would have guess that that should have to do something
Last edited on
ah, thanks for pointing out the error and simplification. Much appreciated.

I'm storing moves to see which play provides the optimal winner. There's still inexplicable logic error in the MakeOptimalMove that I probably have to fix (if it helps, I keep on getting the same output for whatever reason), but at least I got past the infinite loop problem
Last edited on
Does this pass?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <sstream>
#include <string>

const int SZ = 9;

bool win(const int *b, int p) {
    if (p % 2 == 0) {
        if (p % 4 == 0 && b[0] == b[4] && b[4] == b[8])
            return true;
        if ((p % 4 != 0 || p == 4) && b[2] == b[4] && b[4] == b[6])
            return true;
    }
    int i = p / 3 * 3;
    if (b[i] == b[i + 1] && b[i + 1] == b[i + 2])
        return true;
    i = p % 3;
    if (b[i] == b[i + 3] && b[i + 3] == b[i + 6])
        return true;
    return false;
}

int assess(int* board, int move_count, int last_move,
           std::string& value_str, bool top = true) {
    if (top) value_str = "000000000";
    int side = (move_count + 1) % 2 ? 1 : -1;
    if (move_count >= 3) {
        if (win(board, last_move)) return -side;
        if (move_count == SZ) return 0;
    }
    int best_value = -1;
    for (int m = 0; m < SZ; ++m) {
        if (board[m] == 0) {
            board[m] = side;
            int value = assess(board, move_count + 1, m, value_str, false)
                      * side;
            board[m] = 0;
            if (value > best_value) best_value = value;
            if (top) value_str[m] = '1' + value;
        }
    }
    return best_value * side;
}

int read_line(int* board, int& last_move) {
    int move_count = 0;
    std::string line;
    std::getline(std::cin, line);
    std::istringstream iss(line);
    for (int side = 1, m; iss >> m; side = -side) {
        board[m] = side;
        last_move = m;
        ++move_count;
    }
    return move_count;
}

int main() {
    int tests = 0;
    std::cin >> tests;
    std::cin.ignore(999, '\n');
    std::string value_str;
    while (tests--) {
        int board[SZ] {}; // inited to 0's
        int last_move = -1, move_count = read_line(board, last_move);
        assess(board, move_count, last_move, value_str);
        std::cout << value_str << '\n';
    }
}

One problem is that you're copying boards all the time. Each board is at least 27 bytes by my count. That isn't much but it would still be faster if you followed the algorithm given so you can use a single board. See the underlined parts below:
function assess(side):
    if some side have won:
        return this side
    endif

    bestMove = null
    bestValue = -1000000

    for each (move from allPossibleMoves):
        makeMove(move)
        value = assess(-side)
        takeBack(move)

        if value * side > bestValue:
            bestValue = value * side
            bestMove = move
        endif
    endfor

    return bestValue * side
oh, I'm stupid, you could calculate value by how many 1's you have on the board, haha. Thanks for the help man. I thought you needed a bazillion boards like my school tic tac toe project suggested (though if the problem statement was to output the board with X and O then maybe the class would work)
TTT is sometimes used to explain a concept of 'transposition tables' where you have every possible state of the game (rotated and mirrored boards are redundant so you only need a fraction of the # of states of possible board positions). And some positions are illegal and omitted (eg any board with 3 xs and 1 o is not legal).

this results in a bloated obnoxious program to illustrate the point (since TTT can be written in just a few lines of code). Kind of like using factorials to show recursion, when a lookup table of a few values is the better answer.
Last edited on
Topic archived. No new replies allowed.