6

I've got this 3 x 3 array of char that's supposed to represent a tic-tac-toe board, and before, I would use a bunch of "if" statements to see if there were 3 in a row.

... if ((board[0][0] == board[0][1]) && (board[0][1] == board[0][2])) { ... } ...

I realized that this is a lot of typing, and quite error-prone, so is there a better way to do this?

Enrico Tuvera Jr
  • 2,739
  • 6
  • 35
  • 51
  • 6
    9 values, three states each, thus two bits per cell = 18 bits. That fits into an int on any decent machine. Then you can either do a one big switch or do bitwise operations with pre-defined masks. – Nikolai Fetissov Feb 20 '10 at 05:19
  • Much better idea, Nikolai. +1 – Billy ONeal Feb 20 '10 at 05:26
  • We had a tic-tac-toe shortest-program competition not long ago: http://stackoverflow.com/questions/2245801/code-golf-tic-tac-toe/2256299#2256299 – Potatoswatter Feb 20 '10 at 05:38
  • 1
    If obscure things like bitwise check is allowed we may just take any code from http://stackoverflow.com/questions/2245801/code-golf-tic-tac-toe. – kennytm Feb 20 '10 at 05:38

8 Answers8

3

You could change it to check from only where the last move was made.

//lr = lastMoveRow
//lc = lastMoveCol

// no need to check blank with last move known
if (board[0][lc] == board[1][lc] && board[0][lc] == board[2][lc] ||
    board[lr][0] == board[lr][1] && board[lr][0] == board[lr][2]){
      // Game over
}

// Check diagonals
if (board[1][1] != blank &&
   (board[0][0] == board[1][1] && board[0][0] == board[2][2] ||
    board[2][0] == board[1][1] && board[2][0] == board[0][2])){
    // Game over
}

Or - Checking all states:

//Check horizontals and verticals at once
for (int i = 0; i < 3; ++i){
    // Check column and row at once
    if (board[0][i] != blank && board[0][i] == board[1][i] && board[0][i] == board[2][i] ||
        board[i][0] != blank && board[i][0] == board[i][1] && board[i][0] == board[i][2]){
      // Game over
    }
}

// Check diagonals
if (board[1][1] != blank &&
   (board[0][0] == board[1][1] && board[0][0] == board[2][2] ||
    board[2][0] == board[1][1] && board[2][0] == board[0][2])){
    // Game over
}

Or if you do turn it into a bit by bit system - keep separate X and O boards for ease of updating. Then you only need 9 bits for x, 9 bits for O, and your winning boards matches are much simpler. (To find open spaces in this case, just bitwise or the x and o boards)

// winning 9 bit boards
// int winningBoards[8]
000000111
000111000
111000000
001001001
010010010
100100100
100010001
001010100

//xBoard and yBoard can be ints
for (int i = 0; i < 8; ++i){
  if (xBoard & winningBoards[i] == winningBoards[i]){
    //Game over
  }
}
Jeff
  • 175
  • 6
  • The only problem with this answer is that it does not handle cases where a square is empty. If a row is entirely empty it will report success, even though the game is not over. Otherwise, +1. – Billy ONeal Feb 20 '10 at 05:40
1

You could loop it. For instance to check all of the rows you might do:

for(int i = 0; i < 3; i++){
    if((board[i][0]==board[i][1]) && (board[i][1]==board[i][2])){
        ....
    }
}

And do something similar for the columns. Then you just need to check the diagonals separately.

Justin Peel
  • 46,722
  • 6
  • 58
  • 80
1

You can remove the paranthesis because && has lower priority than ==

if (board[0][0] == board[0][1] && board[0][1] == board[0][2])

You can also define an inline function (or macro) to factor out equality

inline bool are_equal(int a, int b, int c) {
  return a == b && b == c;
}
...
if (are_equal(board[0][0], board[0][1], board[0][2]))

Note that a==b==c does not return what you need. For instance, 0==0==1 is true in many C-derived languages.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
1

I don't know about "better", but you could break things up serially:

//Set empty to whatever value you're using for an empty square.
#define empty '\0'

bool thereIsALine(char matrix[3][3])
{
    char c;
    //Check all columns:
    for(int i = 0; i < 3; i++)
    {
        c = matrix[i][0];
        if (c == empty)
            break;
        if (c == matrix[i][1] && c == matrix[i][2])
            return true;
    }
    //Check all rows:
    for(int i = 0; i < 3; i++)
    {
        c = matrix[0][i];
        if (c == empty)
            break;
        if (c == matrix[1][i] && c == matrix[2][i])
            return true;
    }
    //Check diagonals
    c = matrix[1][1];
    if (c == empty) return false;
    if (c == matrix[0][2] && c == matrix[2][0] )
        return true;
    if (c == matrix[0][0] && c == matrix[2][2] )
        return true;
    return false;
}
Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
1

Adapted from last week's Code Golf competition. Note that linear patterns along the board matrix begin at a given index and progress along equal intervals.

And if you represent player 1 with a 1, and player 2 with a 2, then those are independent bits and you can test for 3 in a row with bitwise AND.

char check_1row( char *board, int base, int stride ) {
    return board[ base ] & board[ base + stride ] & board[ base + 2 * stride ];
}

char check_win( char (&board)[3][3] ) {
    char winner = 0;
    winner |= check1row( board, 0, 4 ); // check NW/SE diagonal
    for ( int i = 0; i < 3; i ++ ) {
        winner |= check1row( board, i, 3 ); // check verticals
    }
    winner |= check1row( board, 2, 2 ); // check NE/SW diagonal
    for ( int i = 0; i < 9; i += 3 ) {
        winner |= check1row( board, i, 1 ); // check horizontals
    }
    return winner;
}
Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Rather nice! However, the question asks about a 2-dimensional array, not a 1-dimensional one. How can this approach be generalized? Maybe by parsing the 2D array into a 1D one first? – Morlock Feb 20 '10 at 06:05
  • @Morlock: The 2D and 1D arrays are guaranteed equivalent. Just cast, no parsing needed. Edited answer to fit better. – Potatoswatter Feb 20 '10 at 09:24
0

Yes, you could do this

if (board[0][0]==board[0][1]==board[0][2]) {...}

Maybe even write a function

inline boolean row_win(int row_num){
   return (board[row_num][0]==board[row_num][1]==board[row_num][2]);
}

It has a hidden side though, it wont work if board[0][0],board[0][0],board[0][1] are equal to 0.

An alternative is writing a for loop, but I guess thats even more typing.

Tom
  • 43,810
  • 29
  • 138
  • 169
  • i was hoping for something that involved less typing. I'd still need to make an 'if' for each valid triplet. – Enrico Tuvera Jr Feb 20 '10 at 05:11
  • I'm not entirely certain this works. Say the first `==` is evaluated first and evaluates to true, then you get something like `1 == board[0][2]` which is not what the OP intends. If it goes the other way, then you get `board[0][0]==1` which is also not what the OP intends. – Billy ONeal Feb 20 '10 at 05:15
  • 1
    You are right, did the test with int a,b,c = 50,50,50. a==b evaluates to 1, and 1<> 50. – Tom Feb 20 '10 at 05:21
0

Here is a complete solution, in the form of a check function that verifies if a player (1 or 2, standing for X and O) wins:

// tic tac toe win checker

#include<iostream>

using namespace std;

const int DIM = 3;

int check (int t[DIM][DIM]) {
    // 0 is empty, 1 is X, 2 is O
    // return 1 or 2 if there is a win from either
    for (int row=0; row<DIM; row++) {
        if (t[row][0] == t[row][1] && t[row][1] == t[row][2]) {
            if (t[row][0] != 0) return t[row][0];
        }
    }
    for (int col=0; col<DIM; col++) {
        if (t[0][col] == t[1][col] && t[0][col] == t[2][col]) {
            if (t[0][col] != 0) return t[0][col];
        }
    }
    if (t[0][0] == t[1][1] && t[1][1] == t[2][2]) {
        if (t[0][0] != 0) return t[0][0];
    }
    if (t[0][2] == t[1][1] && t[1][1] == t[2][0] != 0) {
        if (t[0][2] != 0) return t[0][2];
    }
    return 1;
}

int main() {
    int ttt[DIM][DIM];
    ttt[1][0] = 2; // Initialyzing row no. 2 to values "2" to test
    ttt[1][1] = 2;
    ttt[1][2] = 2;
    if (check(ttt) != 0) {
        cout << "Player " << check(ttt) << " wins\n";
    }
    else {
        cout << "No winner yet\n";
    }
}

EDIT: I have preferred this approach (returning the number of the winning player) rather than simply verifying if there was a winner, as it seemed more practical for use.

Hope it helps!

Morlock
  • 6,880
  • 16
  • 43
  • 50
  • Why bother defining a `DIM` macro if you're just going to hard-code the diagonal checks at the end? also, this is essentially the same answer I posted 39 mins before you. – Billy ONeal Feb 20 '10 at 06:04
  • Rather true, except that this solution returns the winner, instead of returning 'true' if there is a winner. I have no pretense whatsoever about this being the best possible answer, even more so since I started learning C++ last week. This may be why I didn't spot the similarity of approach to your solution, however they do resemble each other, indeed. Cheers – Morlock Feb 20 '10 at 06:12
  • Why did you make `ttt` global? – avakar Feb 20 '10 at 09:01
  • Indeed, I should have made ttt part of main. I changed the code. Thx – Morlock Feb 20 '10 at 17:03
0

You can store the indices that make up winning rows, and use a single loop:

int win_row[][3] = {{0, 0, 0}, {1, 1, 1}, {2, 2, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}};
int win_col[][3] = {{0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 0, 0}, {1, 1, 1}, {2, 2, 2}, {0, 1, 2}, {2, 1, 0}};

bool has_winner(char board[][3])
{
    //'\0' means unoccupied
    for (int i = 0; i != 8; ++i) {
        char c = board[win_row[i][0]][win_col[i][0]];
        if (c && c == board[win_row[i][1]][win_col[i][1]] && c == board[win_row[i][2]][win_col[i][2]]) {
            return true;
        }
    }
    return false;
}

I also support Jeff's suggestions of storing players' moves in separate values and using bitwise operations.

UncleBens
  • 40,819
  • 6
  • 57
  • 90