Question

I wrote a basic tic-tac-toe game based on multidimensional arrays. g[3][3]. In my program I have about 9 conditions like the one I am about to show you:

if((g[0][0] == X && g[0][1] == X && g[0][2] == X) || (g[0][0] == O && g[0][1] == O && g[0][2] == O))

This is quite insane. I am probably doing something wrong but this is why I am addressing this question. Is there an easier way of representing long and complicated conditions like this? For example couldn't I somehow do:

if(grid.hasXes)
Était-ce utile?

La solution

You're probably going about it the wrong way. There are only 3^9, or 19683 possible combinations, so you can convert your grid to an int, even on a 16 bit machine:

int
asInt( char const (&grid)[3][3] )
{
    int results = 0;
    for ( int i = 0; i != 3; ++ i ) {
        for ( int j = 0; j != 3; ++ j ) {
            results *= 3;
            switch ( grid[i][j] ) {
            case 'X':
                results += 1;
                break;

            case 'Y':
                results += 2;
                break;

            case ' ':
                break;

            default:
                assert(0);
            }
        }
    }
    return results;
}

Afterwards, you can use the int to index into a table indicating who won (if anyone). Alternatively, you can convert just one or the other player's position into a 9 bit int:

int
asInt( char const (&grid)[3][3], char who )
{
    int results = 0;
    for ( int i = 0; i != 3; ++ i ) {
        for ( int j = 0; j != 3; ++ j ) {
            results *= 2;
            if ( grid[i][j] == who ) {
                ++ results;
            }
        }
    }
    return results;
}

You can then use a simple linear search into a table, verifying that the necessary bits are set:

static int const wins[] =
{
    0007, 0070, 0700,       //  rows
    0111, 0222, 0444,       //  columns
    0124, 0421              //  diagonals
};

class Wins
{
    int myToMatch;
public:
    Wins( char const (&grid)[3][3], char who )
        : myToMatch( asInt( grid, who ) )
    {
    }
    bool operator()( int entry ) const
    {
        return (entry & myToMatch) == entry;
    }
};

Then:

if ( std::find_if( begin( wins ), end( wins ), Wins( grid, 'X' ) )
            != end( wins ) {
    //  X wins
else if ( std::find_if( begin( wins ), end( wins ), Wins( grid, 'O' ) )
            != end( wins ) {
    //  O wins
else
    //  play another turn.

You could even consider keeping the grid as two ints, one per player. The bit number for a position would be 3 * i + j, and to test if a move is legal:

bool
isLegal( int gridX, int gridY, int i, int j )
{
    return ((gridX | gridY) & (1 << (3 * i + j))) == 0;
}

Autres conseils

The simplest -- and most powerful -- way to deal with this kind of issue is simply by extracting the ugly code into a function. That function can be a member of a class, if it's convenient, or simply a free function. In your case, the quick fix could be

bool hasXes(char[3][3] g) {
    return (g[0][0] == X && g[0][1] == X && g[0][2] == X) || (g[0][0] == O && g[0][1] == O && g[0][2] == O)
}

Then you can simply write:

if (hasXes(g)) ...

now I got it...

bool check(char *g, int x, int y, int moveX, int moveY, char ch)
{
    for (int i(0); i<3; ++i)
    {
        if ((g+(y*3)+x) != ch) return false;
        x += moveX;
        y += moveY;
    }
    return true;
}

you use it like that:

if (check(g, 0, 0, 0, 1, 'O')) //checking O in the first row.
if (check(g, 0, 0, 0, 1, 'X')) //checking X in the first row.
if (check(g, 0, 0, 1, 0, 'O')) //checking O in the first column.
if (check(g, 0, 0, 1, 0, 'X')) //checking X in the first column.

You could write functions to hide the complexity and enhance the readability of your main driver function. For instance, you could check a row or column to see if it it's all equal to X or O.

This should work:

bool found = false;
int i, j;
for(i = 0; i < 3; i++)
{
    for(j = 0; j < 3; j++)
    {
        if(g[i][j] == X)
        {
            found = true;
            break;
        }
    }
    if(found == true)
    {
        break;
    }
}
if(found == true)
{
    // do something because one of them had X.  i, j have the co-ordinates of the first find of it
}
else
{
    // none of them had X
}

There may be a way to use a goto as well, though those are heavily discouraged in c++. If you only want a row at a time, only use 1 loop.

One more option to choose from. You can use memcmp if the storage is contiguous

if(!memcmp(g[0],"XXX",3) || !memcmp(g[0],"OOO",3))

In this special case there is also the somewhat simpler comparison:

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

At least assuming there are only X and O possible. Otherwise this will become

if(g[0][0] == g[0][1] && g[0][1] == g[0][2] && ( g[0][1] == X || g[0][1] == O ) )

Which still is a lot simpler IMHO.

If you cannot simplify like this, use a loop as other have pointed out.

Typesafe comments!

const bool first_is_xful = g[0][0] == X && g[0][1] == X && g[0][2] == X,
           second_is_xful = ...;

if (first_is_xful || second_is_xful || ...) ...

Or functions functions:

bool is_xful (int row, ...) ...

...

if (is_ixful(0) || ...

You could count the Xs or try to find them:

Assuming g is a 3 x 3 array, containing characters X or O:

char* end = g + 9;
std::count(g, end, 'X') > 0;

or more efficiently:

char* end = g + 9;
std::find(g, end, 'X') != end;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top