I recently took a stab at the A* search algorithm. I've tried it before to no avail, but I've had a level of success this time. It always finds a path, unless it can't (obviously) and it's USUALLY close to the shortest one. Other times it acts really screwy as in adds one too many times, goes in a zig zag pattern, moves in the wrong direction randomly. It's very strange. Screenshot here.
Code below:
int manhattan( Coord a, Coord b )
{
int x = abs(b.x-a.x);
int y = abs(b.y-a.y);
return x+y;
}
std::vector<Coord> AStar( std::vector< std::vector< int > > grid, Point start, Point end )
{
//The current 'focal' point.
Point *cur;
//The open and closed lists.
std::vector< Point* > closed;
std::vector< Point* > open;
//Start by adding the starting position to the list.
open.push_back( &start );
//Just so it knows whether or not to try and reconstruct a path.
bool error = true;
while( open.size() > 0 )
{
//The current point is the first entry in the open list.
cur = open.at(0);
if( cur->getPos() == end.getPos() )
{
error = false;
break;
}
//Add in all the neighbors of the current point.
for( int y = -1; y <= 1; y++ )
{
for( int x = -1; x <= 1; x++ )
{
int curX = cur->getPos().x+x;
int curY = cur->getPos().y+y;
int movCost = 10;
//If it is a diagonal, make it cost 14 instead of 10.
if( (y == -1 && x == -1)||
(y == 1 && x == -1)||
(y == -1 && x == 1)||
(y == 1 && x == 1))
{
movCost = 14;
//continue;
}
Coord temp( curX, curY );
bool make = true;
//If it is outside the range of the map, continue.
if( curY >= grid.size() ||
curX >= grid.size() )
{
continue;
}
/*
These two loops are to check whether or not the point's neighbors already exist.
This feels really sloppy to me. Please tell me if there is a better way.
*/
for( int i = 0; i < open.size(); i++ )
{
if( temp == open.at(i)->getPos() )
{
make = false;
break;
}
}
for( int i = 0; i < closed.size(); i++ )
{
if( temp == closed.at(i)->getPos() )
{
make = false;
break;
}
}
//If the point in the map is a zero, then it is a wall. Continue.
if( (grid.at(temp.x).at(temp.y) == 0 ) ||
( temp.x<0 || temp.y < 0 ) )
{
continue;
}
//If it is allowed to make a new point, it adds it to the open list.
if( make )
{
int gScore = manhattan( start.getPos(), Coord( curX, curY ) );
int hScore = manhattan( end.getPos(), Coord( curX, curY ) );
int tileCost = grid[curX][curY];
int fScore = gScore+hScore+tileCost;
open.push_back( new Point( curX, curY, fScore, cur ) );
}
}
}
//It then pushes back the current into the closed set as well as erasing it from the open set.
closed.push_back( cur );
open.erase( open.begin() );
//Heapsort works, guranteed. Not sure if it's a stable sort, though. From what I can tell that shouldn't matter, though.
open = heapsort( open );
}
std::vector<Coord> path;
if( error )
{
return path;
}
//Reconstruct a path by tracing through the parents.
while( cur->getParent() != nullptr )
{
path.push_back( cur->getPos() );
cur = cur->getParent();
}
path.push_back( cur->getPos() );
return path;
}
Anyway! Thanks for any help ahead of time! If you want to give me some helpful tips or any other help that would be awesome! Thanks very much! :^)
I can see that you're trying to make diagonals more expensive here:
int movCost = 10;
//If it is a diagonal, make it cost 14 instead of 10.
if( (y == -1 && x == -1)||
(y == 1 && x == -1)||
(y == -1 && x == 1)||
(y == 1 && x == 1))
{
movCost = 14;
//continue;
}
But you don't actually use movCost elsewhere in your code.
Instead, your cost function only uses Manhattan distance:
int gScore = manhattan( start.getPos(), Coord( curX, curY ) );
int hScore = manhattan( end.getPos(), Coord( curX, curY ) );
int tileCost = grid[curX][curY];
int fScore = gScore+hScore+tileCost;
Which explains the diagonally zig-zagging paths:
By the way, there is one more logical error in your code: in A*, the g-cost should be calculated as the actual cost from the start to the current node, not estimated like you have using your manhattan() function. You should be saving the cost along with your points in your open and closed sets.
In future, you should turn on all compiler warnings and don't ignore them. This will catch mistakes that are easy to miss, like unused variables.
Related
I've been making a attempt to work out a maze generator using a recursive depth first search style algorithm. I know it's been done many times in the past but for my own understanding I've been trying to implement it from scratch myself in order to better understand it.
So, this is my attempt so far.
#include <iostream>
#include <vector>
#include <algorithm>
#include <unistd.h>
struct vertex {
int x;
int y;
};
int main(int argc, char **argv)
{
// Init the map
int map_size = 24;
int half_point = map_size / 2;
int map[map_size+1][map_size+1];
for(int y = 0; y < map_size+1; y++) {
for(int x = 0; x < map_size+1; x++) {
map[x][y] = 0;
}
}
vertex start;
start.x = half_point;
start.y = half_point;
std::vector<vertex> history;
// Set start point as visited
history.push_back(start);
while( !history.empty() ) {
vertex v = history.back();
map[v.x][v.y] = 1;
// Calculate the directions for each vertex
vertex N; N.x = v.x+1; N.y = v.y;
vertex S; S.x = v.x-1; S.y = v.y;
vertex E; E.x = v.x; E.y = v.y-1;
vertex W; W.x = v.x; W.y = v.y + 1;
bool can_north = false; bool can_south = false; bool can_east = false; bool can_west = false;
// Check north and add if relevant
if( N.x >= 0 && N.x <= map_size && N.y >= 0 && N.y <= map_size && map[N.x][N.y] == 0 ) { can_north = true; }
// Check south and add if relevant
if( S.x >= 0 && S.x <= map_size && S.y >= 0 && S.y <= map_size && map[S.x][S.y] == 0 ) { can_south = true; }
// Check east and add if relevant
if( E.x >= 0 && E.x <= map_size && E.y >= 0 && E.y <= map_size && map[E.x][E.y] == 0 ) { can_east = true; }
// Check west and add if relevant
if( W.x >= 0 && W.x <= map_size && W.y >= 0 && W.y <= map_size && map[W.x][W.y] == 0 ) { can_west = true; }
std::vector<vertex> available;
if(can_north) { available.push_back(N); }
if(can_south) { available.push_back(S); }
if(can_east) { available.push_back(E); }
if(can_west) { available.push_back(W); }
// Select random element from availables
if( !available.empty() )
{
std::random_shuffle( available.begin(), available.end() );
vertex aV = available.back();
history.push_back(aV);
available.clear();
} else {
if( !history.empty() ) {
history.pop_back();
}
}
// Animate the output to the console
system("clear");
for(int y = 0; y < map_size+1; y++) {
for(int x = 0; x < map_size+1; x++) {
std::cout << map[x][y] << ", ";
}
std::cout << std::endl;
}
std::cout << std::endl;
usleep(5000);
}
return 0;
}
It's using a Linux system call in there to clear the terminal each time before displaying the update to have a basic animation of the paths it takes.
What I don't know and I trying to understand is...
1) Is this actually a recursive depth-first search algorithm?
If so, then at least I have understood the concept correctly.
2) How would I go about drawing this to an image file?
I will answer your first question
Is this actually a recursive depth-first search algorithm?
At the Recursive depth-first search algorithm each cell has on of three states (Unvisitied, Visit In Progress and Visitied). The idea is to traverse through a graph and visit each node. If a node has 'Unvisited' condition it becomes 'Visit In Progress' and each neighbor will be visited. After each neighbor was visited the node becomes 'Visited'. To do so you have to remember each cell in your maze you visited once and never travers it again. Each member of history full fill condition 'Visit In Progress', and each cell which is set in map full fill condition 'Visited'. My answer is: Yes.
Comment to your second question:
How would I go about drawing this to an image file?
To draw an image I recommend you The CImg library. See more at question The easiest way to draw an image?
If you like to draw the traversed path you need a second stack or similar container where you record all the steps you have done. Perhaps you can draw a raster and arrows from one to an other cell for each step. If you draw arrows from north to south right shifted to them form south to north and those from east to west down shifted to them from west to east you won't have any overlapping.
I'm trying to solve this puzzle using backtrack algorithm. In c++
I face the problem that I couldn't apply the backtrack
void try1(int x,int y)
{
int k=-1;
do{
k++ ;
if(check_color(c[k],x,y))
{
h[x][y]=c[k];// c[k]= r or b;
if ( check_full() && check_array() && check_equal() )
{
cout<<"coloring had finished"; cout<<" \n ";
print(h); getch();
}
else
{
if(y==9&&x<9)
{
y = -1; x++;
}
while(y<9 )
{
y=y+1;
if(h[x][y]==' ')
try1(x,y);
/* here should be the backtrack I think
if(q=='n')
{ x--;cout<<h[x][y];
if(h[x][y]=='b'){h[x][y]='r';}
else {h[x][y]='b';}}*/
else if ( y==9 && x<9 ){
y=-1 ;x++ ;
}
}
}
}
} while ( k<1 ) ;
}
could anyone help me???
Ineed all possible solution back tracking
Backtrack algorithm is quite simple. You just pick a move. Check if that move is okay. And then you go to next position. Here is what I think you should write.
void try1(int x,int y)
{
for ( int k = 0; k < 2; ++k ){
h[x][y] = c[k];
if ( is it okay to put this color in this position ){
if ( x == 9 && y == 9 ){ // table is full
// we have found the solution
// print the table
return;
}
// assuming x is the number of current row
// assuming y is the number of current column
// assuming we are filling the matrix from left to right
// and top to bottom
int next_x, next_y;
if ( y == 9 ){
next_y = 0;
next_x = x+1;
} else {
next_y = y+1;
next_x = x;
}
try1(next_x, next_y);
}
h[x][y] = ' '; // clear this place
}
}
I'm trying to figure out my problem for an hour.
I'm going to draw what is happening.
char trap = 'Q';
char character = 'L';
....
.Q..
..L.
....
when L moves up and Q moves to right they collide and the program ends. but:
....
.QL.
....
....
when L moves to left and Q moves to right they dont collide the same way as the example above instead, whats happening is:
....
..Q.
....
....
here's my code. sorry for my bad english :(
test if the move is 'w' , 'a' , 's' or 'd' :
void cave::move(int& x, int& y, char m, char unit)
{
if ( m == 'W' || m == 'w' ) // if moves up
{
floor[x][y] = tile;
x -= 1;
for ( unsigned short int x = 0; x < 3; x++ )
{
if ( floor[x][y] == wall && floor[x][y] == trap[x] )
{
x += 1;
trapsMove();
}
}
floor[x][y] = unit;
}
else if ( m == 'A' || m == 'a' ) // if moves to left
{
floor[x][y] = tile;
y -= 1;
for ( unsigned short int x = 0; x < 3; x++ )
{
if ( floor[x][y] == wall && floor[x][y] == trap [x] )
{
y += 1;
trapsMove();
}
}
floor[x][y] = unit;
}
else if ( m == 'S' || m == 's' ) // if moves down
{
floor[x][y] = tile;
x += 1;
for ( unsigned short int x = 0; x < 3; x++ )
{
if ( floor[x][y] == wall && floor[x][y] == trap[x] )
{
x -= 1;
trapsMove();
}
}
floor[x][y] = unit;
}
else if ( m == 'D' || m == 'd' ) // if moves to right
{
floor[x][y] = tile;
y += 1;
for ( unsigned short int x = 0; x < 3; x++ )
{
if ( floor[x][y] == wall && floor[x][y] == trapx] )
{
y -= 1;
trapsMove();
}
}
floor[x][y] = unit;
}
else
control();
return;
}
ai moves function
void cave::trapsMove()
{
int r[3]; // each index will hold the movement of traps
for ( unsigned short int x = 0; x < 3; x++ )
{
r[x] = rand() % 4 + 1;
if ( r[x] == 1 ) // moves up
move(traps_positionX[x],traps_positionY[x],'w',trap[x]);
else if ( r[x] == 2 ) // moves to left
move(traps_positionX[x],traps_positionY[x],'a',trap[x]);
else if ( r[x] == 3 ) // moves down
move(traps_positionX[x],traps_positionY[x],'s',trap[x]);
else if ( r[x] == 4 ) // moves to right
move(traps_positionX[x],traps_positionY[x],'d',trap[x]);
}
return;
}
check if collide
bool cave::collision()
{
for ( unsigned short int x = 0; x < 3; x++ )
{
if ( floor[character_positionX][character_positionY] == trap[x] )
return true;
}
return false;
}
Well it seems from your code that the Trap and Player switch their position.
However the position switch from your Trap (Q) overwrites the Player (L) with a floor tile.
Essentially this happens:
(1) .QL.
(2) .L.. // L and Q inhabit the same tile
(3) ..Q. // Q overwrites L with a '.' tile
Your code suffers from convolution, because you try to either do too much in one function or do it in several different places. This leads to you missing for instance the necessary collision check in the above example.
A better strategy would be to try to structure your program flow. For instance you currently have:
(1) Prompt for a direction
(2) Move player tile
(2a) If player hits a tile which is a Trap or a Wall (typo in your code, must be || instead of &&) move the traps
<-- (2a) is another pitfall where traps can do two moves in a row.
(3) Move the traps
(4) check for collision
(5) repeat from (1)
I think what you wanted was:
(1) Prompt for a direction
(2) Move player tile
(2a) check for collision
(3) Move traps
(3a) check for collision
(6) repeat from (1)
As for the refactoring try to recycle your code. For instance in your move() function the char 'w' (...) only influences the 'x' or 'y' variable. Thus you could write it also as
move(...) {
if((m=='w')||(m=='W')) { y = y+1 }
else if((m=='a')||(m=='A')) { x = x-1 } // same for SD
if(floor[x][y] != wall) {
// set new position to object, if it can't move, just don't set it
}
}
This way you don't have to copy&paste your for-loop with minimal alterations.
I need to place numbers within a grid such that it doesn't collide with each other. This number placement should be random and can be horizontal or vertical. The numbers basically indicate the locations of the ships. So the points for the ships should be together and need to be random and should not collide.
I have tried it:
int main()
{
srand(time(NULL));
int Grid[64];
int battleShips;
bool battleShipFilled;
for(int i = 0; i < 64; i++)
Grid[i]=0;
for(int i = 1; i <= 5; i++)
{
battleShips = 1;
while(battleShips != 5)
{
int horizontal = rand()%2;
if(horizontal == 0)
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != j)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row+k)*8+(column)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row+k)*8+(column)] = 1;
battleShipFilled = true;
}
battleShips++;
}
else
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row)*8+(column+k)] = 1;
battleShipFilled = true;
}
battleShips++;
}
}
}
}
But the code i have written is not able to generate the numbers randomly in the 8x8 grid.
Need some guidance on how to solve this. If there is any better way of doing it, please tell me...
How it should look:
What My code is doing:
Basically, I am placing 5 ships, each of different size on a grid. For each, I check whether I want to place it horizontally or vertically randomly. After that, I check whether the surrounding is filled up or not. If not, I place them there. Or I repeat the process.
Important Point: I need to use just while, for loops..
You are much better of using recursion for that problem. This will give your algorithm unwind possibility. What I mean is that you can deploy each ship and place next part at random end of the ship, then check the new placed ship part has adjacent tiles empty and progress to the next one. if it happens that its touches another ship it will due to recursive nature it will remove the placed tile and try on the other end. If the position of the ship is not valid it should place the ship in different place and start over.
I have used this solution in a word search game, where the board had to be populated with words to look for. Worked perfect.
This is a code from my word search game:
bool generate ( std::string word, BuzzLevel &level, CCPoint position, std::vector<CCPoint> &placed, CCSize lSize )
{
std::string cPiece;
if ( word.size() == 0 ) return true;
if ( !level.inBounds ( position ) ) return false;
cPiece += level.getPiece(position)->getLetter();
int l = cPiece.size();
if ( (cPiece != " ") && (word[0] != cPiece[0]) ) return false;
if ( pointInVec (position, placed) ) return false;
if ( position.x >= lSize.width || position.y >= lSize.height || position.x < 0 || position.y < 0 ) return false;
placed.push_back(position);
bool used[6];
for ( int t = 0; t < 6; t++ ) used[t] = false;
int adj;
while ( (adj = HexCoord::getRandomAdjacentUnique(used)) != -1 )
{
CCPoint nextPosition = HexCoord::getAdjacentGridPositionInDirection((eDirection) adj, position);
if ( generate ( word.substr(1, word.size()), level, nextPosition, placed, lSize ) ) return true;
}
placed.pop_back();
return false;
}
CCPoint getRandPoint ( CCSize size )
{
return CCPoint ( rand() % (int)size.width, rand() % (int)size.height);
}
void generateWholeLevel ( BuzzLevel &level,
blockInfo* info,
const CCSize &levelSize,
vector<CCLabelBMFont*> wordList
)
{
for ( vector<CCLabelBMFont*>::iterator iter = wordList.begin();
iter != wordList.end(); iter++ )
{
std::string cWord = (*iter)->getString();
// CCLog("Curront word %s", cWord.c_str() );
vector<CCPoint> wordPositions;
int iterations = 0;
while ( true )
{
iterations++;
//CCLog("iteration %i", iterations );
CCPoint cPoint = getRandPoint(levelSize);
if ( generate (cWord, level, cPoint, wordPositions, levelSize ) )
{
//Place pieces here
for ( int t = 0; t < cWord.size(); t++ )
{
level.getPiece(wordPositions[t])->addLetter(cWord[t]);
}
break;
}
if ( iterations > 1500 )
{
level.clear();
generateWholeLevel(level, info, levelSize, wordList);
return;
}
}
}
}
I might add that shaped used in the game was a honeycomb. Letter could wind in any direction, so the code above is way more complex then what you are looking for I guess, but will provide a starting point.
I will provide something more suitable when I get back home as I don't have enough time now.
I can see a potential infinite loop in your code
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
Here, nothing prevents row from being 0, as it was assignd rand%8 earlier, and k can be assigned a negative value (since j can be positive). Once that happens nothing will end the while loop.
Also, I would recommend re-approaching this problem in a more object oriented way (or at the very least breaking up the code in main() into multiple, shorter functions). Personally I found the code a little difficult to follow.
A very quick and probably buggy example of how you could really clean your solution up and make it more flexible by using some OOP:
enum Orientation {
Horizontal,
Vertical
};
struct Ship {
Ship(unsigned l = 1, bool o = Horizontal) : length(l), orientation(o) {}
unsigned char length;
bool orientation;
};
class Grid {
public:
Grid(const unsigned w = 8, const unsigned h = 8) : _w(w), _h(h) {
grid.resize(w * h);
foreach (Ship * sp, grid) {
sp = nullptr;
}
}
bool addShip(Ship * s, unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) { // if in valid range
if (s->orientation == Horizontal) {
if ((x + s->length) <= _w) { // if not too big
int p = 0; //check if occupied
for (int c1 = 0; c1 < s->length; ++c1) if (grid[y * _w + x + p++]) return false;
p = 0; // occupy if not
for (int c1 = 0; c1 < s->length; ++c1) grid[y * _w + x + p++] = s;
return true;
} else return false;
} else {
if ((y + s->length) <= _h) {
int p = 0; // check
for (int c1 = 0; c1 < s->length; ++c1) {
if (grid[y * _w + x + p]) return false;
p += _w;
}
p = 0; // occupy
for (int c1 = 0; c1 < s->length; ++c1) {
grid[y * _w + x + p] = s;
p += _w;
}
return true;
} else return false;
}
} else return false;
}
void drawGrid() {
for (int y = 0; y < _h; ++y) {
for (int x = 0; x < _w; ++x) {
if (grid.at(y * w + x)) cout << "|S";
else cout << "|_";
}
cout << "|" << endl;
}
cout << endl;
}
void hitXY(unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) {
if (grid[y * _w + x]) cout << "You sunk my battleship" << endl;
else cout << "Nothing..." << endl;
}
}
private:
QVector<Ship *> grid;
unsigned _w, _h;
};
The basic idea is create a grid of arbitrary size and give it the ability to "load" ships of arbitrary length at arbitrary coordinates. You need to check if the size is not too much and if the tiles aren't already occupied, that's pretty much it, the other thing is orientation - if horizontal then increment is +1, if vertical increment is + width.
This gives flexibility to use the methods to quickly populate the grid with random data:
int main() {
Grid g(20, 20);
g.drawGrid();
unsigned shipCount = 20;
while (shipCount) {
Ship * s = new Ship(qrand() % 8 + 2, qrand() %2);
if (g.addShip(s, qrand() % 20, qrand() % 20)) --shipCount;
else delete s;
}
cout << endl;
g.drawGrid();
for (int i = 0; i < 20; ++i) g.hitXY(qrand() % 20, qrand() % 20);
}
Naturally, you can extend it further, make hit ships sink and disappear from the grid, make it possible to move ships around and flip their orientation. You can even use diagonal orientation. A lot of flexibility and potential to harness by refining an OOP based solution.
Obviously, you will put some limits in production code, as currently you can create grids of 0x0 and ships of length 0. It's just a quick example anyway. I am using Qt and therefore Qt containers, but its just the same with std containers.
I tried to rewrite your program in Java, it works as required. Feel free to ask anything that is not clearly coded. I didn't rechecked it so it may have errors of its own. It can be further optimized and cleaned but as it is past midnight around here, I would rather not do that at the moment :)
public static void main(String[] args) {
Random generator = new Random();
int Grid[][] = new int[8][8];
for (int battleShips = 0; battleShips < 5; battleShips++) {
boolean isHorizontal = generator.nextInt(2) == 0 ? true : false;
boolean battleShipFilled = false;
while (!battleShipFilled) {
// Select a random row and column for trial
int row = generator.nextInt(8);
int column = generator.nextInt(8);
while (Grid[row][column] == 1) {
row = generator.nextInt(8);
column = generator.nextInt(8);
}
int lengthOfBattleship = 0;
if (battleShips == 0) // Smallest ship should be of length 2
lengthOfBattleship = (battleShips + 2);
else // Other 4 ships has the length of 2, 3, 4 & 5
lengthOfBattleship = battleShips + 1;
int numberOfCorrectLocation = 0;
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal && row + k > 0 && row + k < 8) {
if (Grid[row + k][column] == 1)
break;
} else if (!isHorizontal && column + k > 0 && column + k < 8) {
if (Grid[row][column + k] == 1)
break;
} else {
break;
}
numberOfCorrectLocation++;
}
if (numberOfCorrectLocation == lengthOfBattleship) {
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal)
Grid[row + k][column] = 1;
else
Grid[row][column + k] = 1;
}
battleShipFilled = true;
}
}
}
}
Some important points.
As #Kindread said in an another answer, the code has an infinite loop condition which must be eliminated.
This algorithm will use too much resources to find a solution, it should be optimized.
Code duplications should be avoided as it will result in more maintenance cost (which might not be a problem for this specific case), and possible bugs.
Hope this answer helps...
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)
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;
}
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;