Is there an easier way of representing conditions in C++?
-
31-05-2021 - |
Pergunta
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)
Solução
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 int
s, 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;
}
Outras dicas
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;