Find path in grid if possible using Recursion - c++

Given a grid of size R X C and startPosition and endPosition of a person in a grid composed of zero and one. Now I want to find path from start position to end position, and also trace the marked path on grid by labelling them 2. If path is not possible, I need to tell that path is not possible. So I had written this logic :
vector<vector<int>> solution;
void printSolution(vector<vector<int>> grid)
{
for (int i=0;i<grid.size();i++)
{
for (int j=0;j<grid[i].size();j++)
cout<<grid[i][j]<<" ";
cout<<endl;
}
}
bool isSafe(vector<vector<int>> grid, pair<int,int> position)
{
if(position.first >= 0 && position.first < grid.size() &&
position.second >= 0 && position.second < grid[0].size() &&
grid[position.first][position.second] == 0)
return true;
return false;
}
bool checkPath(vector<vector<int>> grid,pair<int,int> currentPosition,pair<int,int> endPosition){
if(currentPosition == endPosition)
{
solution[currentPosition.first][currentPosition.second] = 2;
return true;
}
if(isSafe(grid,currentPosition) == true)
{
solution[currentPosition.first][currentPosition.second] = 2;
if (checkPath(grid, make_pair(currentPosition.first+1,currentPosition.second),endPosition) == true)
return true;
if (checkPath(grid, make_pair(currentPosition.first-1,currentPosition.second),endPosition) == true)
return true;
if (checkPath(grid, make_pair(currentPosition.first,currentPosition.second+1),endPosition) == true)
return true;
if (checkPath(grid, make_pair(currentPosition.first,currentPosition.second-1),endPosition) == true)
return true;
solution[currentPosition.first][currentPosition.second] = 0;
return false;
}
return false;
}
bool solver(vector<vector<int>> grid,pair<int,int> startPosition,pair<int,int> endPosition,int R,int C){
solution.resize(R,vector<int>(C));
bool isPath = checkPath(grid,startPosition,endPosition);
printSolution(solution);
if(isPath==false){
cout<<"No Path Found"<<endl;
return false;
}
else{
cout<<"Path Found"<<endl;
return true;
}
}
This code is giving segmentation error. Please help to find it. I am stuck for almost a whole day to find where it is present.
So help me correct the logic of code. Assume I have following fields :
int R,C;
int grid[R][C];
pair<int,int> startPosition;
pair<int,int> endPosition;
This recursion is running infinite. i Checked for a simple case with startPosition as (1,0) and endPosition as (2,2) , R=3 and C=3 and grid as :
1 1 1
0 0 1
1 0 0
First, I tried with BFS, then start to make recursive solution.

When implementing a BFS path finding algorithm, it helps to have a shadow array that keeps track of the distance from the origin for all visited nodes.
int distance[R][C];
Initially, set all of the distances to -1 to indicate that the location has not been visited. Then set the distance at the origin to 0, and push the origin onto the stack.
While the stack is not empty, pop an item from the stack. For each adjacent location (that hasn't been visited) set the distance for that location, and push that location.
When you reach the end position, you can find the path by working backwards. Knowing the distance at the end position, the previous point on the path is an adjacent position that has been visited, and has a lower distance.

Related

Water in a falling sand simulation

I am currently working on a very simple 'Falling Sand' simulation game in C++ and SDL2, and am having problems with getting water to flow in a more realistic manner. I basically have a grid of cells that I iterate through bottom-to-top, left-to-right and if I find a water cell, I just check below, down to left, down to the right, left then right for empty cells and it moves into the first one its finds (it makes a random choice if both diagonal cells or both horizontal cells are free). I then mark the cell it moved into as processed so that it is not checked again for the rest of that loop.
My problem is a sort of 'left-bias' in how the particles move; if I spawn a square of water cells above a barrier, they will basically all shift to left without moving once the particles begin to reach the barrier, while the cells on the right will run down in the proper way. So instead of forming a nice triangular shape flowing out evenly to both sides, the whole shape will just move to the left. This effect is reversed whenever I iterate left-to-right, so I know it's something to do with that but so far I've been stumped trying to fix it. I initially thought it was a problem with how I marked the cells as processed but I've found no obvious bugs with that system in many hours of testing. Has anyone faced any similar challeneges in developing a simulation like this, or knows something that I'm missing? Any help would be very much appreciated.
EDIT:
Ok so I've made a little progress, however I've ran into another bug that seems to be unrelated to iteration, since now I save a copy of the old cells and read from that to decide an update, then update the original cells and display that. This already made the sand work better, however water, which checks horizontally for free cells, now 'disappears' when it does move horizontally. I've been testing it all morning and have yet to find a solution, I thought it might've been someting to do with how I was copying the arrays over, but it seems to work as far as I can tell.
New snippets:
Simulation.cpp
void Simulation::update()
{
copyStates(m_cells, m_oldCells); // so now oldcells is the last new state
for(int y = m_height - 1; y>= 0; y--)
for(int x = 0; x < m_width; x++)
{
Cell* c = getOldCell(x, y); // check in the old state for possible updates
switch(c->m_type)
{
case EMPTY:
break;
case SAND:
if(c->m_visited == false) update_sand(x, y);
break;
case WATER:
if(c->m_visited == false) update_water(x, y);
break;
default:
break;
}
}
}
void Simulation::update_water(int x, int y)
{
bool down = (getOldCell(x, y+1)->m_type == EMPTY) && checkBounds(x, y+1) && !getOldCell(x, y+1)->m_visited;
bool d_left = (getOldCell(x-1, y+1)->m_type == EMPTY) && checkBounds(x-1, y+1) && !getOldCell(x-1, y+1)->m_visited;
bool d_right = (getOldCell(x+1, y+1)->m_type == EMPTY) && checkBounds(x+1, y+1) && !getOldCell(x+1, y+1)->m_visited ;
bool left = (getOldCell(x-1, y)->m_type == EMPTY) && checkBounds(x-1, y) && !getOldCell(x-1, y)->m_visited ;
bool right = (getOldCell(x+1, y)->m_type == EMPTY) && checkBounds(x+1, y) && !getOldCell(x+1, y)->m_visited ;
// choose random dir if both are possible
if(d_left && d_right)
{
int r = rand() % 2;
if(r) d_right = false;
else d_left = false;
}
if(left && right)
{
int r = rand() % 2;
if(r) right = false;
else left = false;
}
if(down)
{
getCell(x, y+1)->m_type = WATER; // we now update the new state
getOldCell(x, y+1)->m_visited = true; // mark as visited so it will not be checked again in update()
} else if(d_left)
{
getCell(x-1, y+1)->m_type = WATER;
getOldCell(x-1, y+1)->m_visited = true;
} else if(d_right)
{
getCell(x+1, y+1)->m_type = WATER;
getOldCell(x+1, y+1)->m_visited = true;
} else if(left)
{
getCell(x-1, y)->m_type = WATER;
getOldCell(x-1, y)->m_visited = true;
} else if(right)
{
getCell(x+1, y)->m_type = WATER;
getOldCell(x+1, y)->m_visited = true;
}
if(down || d_right || d_left || left || right) // the original cell is now empty; update the new state
{
getCell(x, y)->m_type = EMPTY;
}
}
void Simulation::copyStates(Cell* from, Cell* to)
{
for(int x = 0; x < m_width; x++)
for(int y = 0; y < m_height; y++)
{
to[x + y * m_width].m_type = from[x + y * m_width].m_type;
to[x + y * m_width].m_visited = from[x + y * m_width].m_visited;
}
}
Main.cpp
sim.update();
Uint32 c_sand = 0xedec9a00;
for(int y = 0; y < sim.m_height; y++)
for(int x = 0; x < sim.m_width; x++)
{
sim.getCell(x, y)->m_visited = false;
if(sim.getCell(x, y)->m_type == 0) screen.setPixel(x, y, 0);
if(sim.getCell(x, y)->m_type == 1) screen.setPixel(x, y, c_sand);
if(sim.getCell(x, y)->m_type == 2) screen.setPixel(x, y, 0x0000cc00);
}
screen.render();
I've attached a gif showing the problem, hopefully this might help make it a little clearer. You can see the sand being placed normally, then the water and the strange patterns it makes after being placed (notice how it moves off to the left when it's spawned, unlike the sand)
You also have to mark the destination postion as visited to stop multiple cells moving in to the same place.

Error reconstructing path in a grid using BFS

The problem I am facing is the following:
I have a function based on the BFS search algorithm that I use in a NxM grid, the mission of this function is to return the following Direction from a set of possible Directions = {Up, Down, Left , Right} (No diagonal moves!)to which a player has to move, so that in each "round / frame" where there is a type of item of a game (For example, in this specific case, a bazooka) is closer to the item.
To address the problem, I have created a Map class made of vector <vector <Cell> > where vector is from the standard library and Cell is what the grid is made of and has some consulting methods on what is in one of the NxM cells (if there is a building, an enemy, a Bazooka, etc.)
So, for implementing a solution for this, I made a struct TrackingBFS to reconstruct the path of the BFS search:
struct TrackingBFS {
pair <int,int> anterior;
bool visited;
};
And this is the BFS search implementation:
//Pre: origen is a valid position on the grid where the player is
//Post:Returns a pair of bool and a direction to the closest bazooka. If we have access to a bazooka, then we will return a pair (true,Dir) where Dir is a direction to take to be closer to the bazooka else a pair (false, Dir) where dir is just the same direction as origen.
pair <bool,Dir> direction_to_closest_gun (const Pos& origen) {
//R = board_rows() C = board_cols()
//m = mapa
//sr,sc = origin positions
int sr = origen.i;
int sc = origen.j;
//Variables para mantener tracking de los pasos cogidos
queue <int> rq; //Queue of x-row coordinates
queue <int> cq; //Queue of y-row coordinates
int move_count = 0; //Number of steps
int nodes_left_in_layer = 1; //How many nodes we need to de-queue before doing a step
int nodes_in_next_layer = 0; //How many nodes we add to the expansio of the BFS so we can upgrade nodes_left_in_layer in the next iteration
bool arma_mejor_encontrada = false;
//Visited is a MxN matrix of TrackingBFS that for every i,j stores the following information:
//If we visited the cell at position visited [i][j], then the TrackingBFS struct will be visited = true and last_node = (k,l) where k and l are the components of the LAST cell on the grid we visited in the BFS search.
//Else if we didn't visited the cell at [i][j], the TrackingBFS struct will be visited = true and last_node (-1,-1).
TrackingBFS aux;
aux.last_node = make_pair(-1,-1);
aux.visited = false;
//We create a Matrix of TrackingBFS named visited of NxM filled with the unvisited cells
vector < vector <TrackingBFS> > visited (board_rows(), vector <TrackingBFS> (board_cols(),aux));
//--------------------------------SOLVE---------------------------------
rq.push(sr);
cq.push(sc);
visited[sr][sc].visited = true;
visited[sr][sc].last_node = make_pair(sr,sc);
int xfound;
int yfound;
while (!rq.empty()) {
int r = rq.front();
int c = cq.front();
rq.pop();
cq.pop();
if (mapa[r][c].weapon == Bazooka) {
arma_mejor_encontrada = true;
xfound = r;
yfound = c;
break;
}
//Explore neighbours
Pos p (r,c);
for (Dir d : dirs) {
Pos searching = p + d;
int rr = searching.i;
int cc = searching.j;
//If the position we are searching is out of range or it's been visited before or there is a obstacle then continue
if (!pos_ok(searching) or visited[rr][cc].visited or mapa[rr][cc].type == Building or mapa[rr][cc].resistance != -1 or mapa[rr][cc].id != -1) {
//NADA
}
//Else we add the visited node to the queue, and fill the information on visited[rr][cc] with the node we are coming and mark it as visited
else {
rq.push(rr);
cq.push(cc);
visited[rr][cc].visited = true;
visited[rr][cc].last_node = make_pair (r,c);
nodes_in_next_layer++;
}
}
nodes_left_in_layer--;
if (nodes_left_in_layer == 0) {
nodes_left_in_layer = nodes_in_next_layer;
nodes_in_next_layer = 0;
move_count++;
}
}
//If we found the Bazooka
if (arma_mejor_encontrada) {
//Return the pair (true,next direction of the player at position (sr,sc) to approach the bazooka)
return make_pair(arma_mejor_encontrada, reconstruir_camino(visited,xfound,yfound,sr,sc));
}
else {
//If there is no bazooka we return (false,Up (this second component is meaningless))
return make_pair(arma_mejor_encontrada, Up);
}
}
The reconstruir_camino (recosntruct_path in english) implementation:
//This function is made to reconstruct the path from where we found de bazooka (x,y) to where our player is (ox,oy), whe are only interested in the next position of because this is run each frame, so we are constantly refreshing the state of the map.
Dir reconstruir_camino(const vector < vector <TrackingBFS> >& visited, const int& x, const int& y, const int& ox, const int& oy) {
//In v we save the pair of coordinates of our path, this was done only for debuging and in debug_matriz_tracking(visited) is also only for debuging
vector <pair<int,int>> path;
debug_matriz_tracking(visited);
//
int i = visited[x][y].last_node.first;
int j = visited[x][y].last_node.second;
while (not (i == ox and j == oy)) { //While the next node is not iqual as the original node we started de search (The one where our player is)
path.push_back(make_pair(i,j)); //Just for debuging
i = visited[i][j].last_node.first;
j = visited[i][j].last_node.second;
}
//So now in path we have the start and end nodes of every edge on the path
int siguiente_x = path.back().first;
int siguiente_y = path.back().second;
debug_camino(path);
return direccion_contiguos(ox,oy,siguiente_x,siguiente_y);
}
And direccion_contiguos (contiguous direction / relative direction in english) implementation:
//Returns the RELATIVE direction to go if we want to go from (ox, oy) to (dx, dy) being these two contiguous positions, that is, (dx, dy) is in Up, Down, Left or Right with respect to (ox, oy) IT WORKS FINE, NOTHING WRONG WITH THIS DON'T TOUCH
Dir direccion_contiguos (const int& ox, const int& oy, const int& dx, const int& dy) {
Pos o (ox,oy);
Pos d (dx,dy);
if (o + Up == d) {
return Up;
}
else if (o + Down == d){
return Down;
}
else if (o + Left == d) {
return Left;
}
else if (o + Right == d) {
return Right;
}
return Down;
}
So now in visited, we have the information to reconstruct the path, in fact I debuged it (it's kinda messy i know, sorry), in a visual way so this is what I got for a Player in origen = (7,10) and bazooka at position = (4,11):
[Imgur link of the Visual representation of the Matrix for reconstructing the path from origin to bazooka][1]
To read this image, at the top and left there are the coordinates of every cell of the visited matrix, the ones with green font, are the ones that have been visited, and they store THE NEXT cell/vertex of the path, and the ones with (-1,-1) are the ones that have not been visited by the BFS algorithm and thus they don't have any previous node and are in white.
So, NICE! It seems to work, at least the visited matrix.
My problem is when I debug the vector of edges/directions of the graph/grid, this is what I used in the example of the image:
void debug_camino(const vector <pair<int,int>>& v) {
cerr << "----------------------CAMINO-------------------- DEBUG_CAMINO" << endl;
for (long unsigned int i = 0; i < v.size(); ++i) {
cerr << "(" << v[i].first << "," << v[i].second << "),";
}
cerr << endl;
}
And when I executed the program, this is the path that I got with debug_camino():
If you see the image attached you can see that that's almost the path but not quite yet.
(4,12),(4,13),(4,14),(3,15),(3,16),(4,16),(5,16),(6,16),(7,15),(7,14),(7,13),(7,12),(7,11)
These ones bolded are not real (even valid because they are diagonal moves) reconstructions of the path and I don't really know WHY this is happening, but it's provoking my player to not following right path, and I want to fix the error and I'm kinda desperate because I don't really know where the error is and I've been trying for days :( ! I hope somebody can help me with this. Thanks for reading all this and sorry if the code is in some parts in Spanish or if it's not all that readable.
[1]: https://i.stack.imgur.com/vZ2Go.png
Okay, I actually managed to fix this error, i was overwriting the i variable so that was causing the error.
//This function is made to reconstruct the path from where we found de bazooka (x,y) to where our player is (ox,oy), whe are only interested in the next position of because this is run each frame, so we are constantly refreshing the state of the map.
Dir reconstruir_camino(const vector < vector <TrackingBFS> >& visited, const int& x, const int& y, const int& ox, const int& oy) {
//In v we save the pair of coordinates of our path, this was done only for debuging and in debug_matriz_tracking(visited) is also only for debuging
vector <pair<int,int>> path;
debug_matriz_tracking(visited);
//
int i = visited[x][y].last_node.first;
int j = visited[x][y].last_node.second;
while (not (i == ox and j == oy)) { //While the next node is not iqual as the original node we started de search (The one where our player is)
path.push_back(make_pair(i,j)); //Just for debuging
**i** = visited[i][j].last_node.first;
j = visited[**i**][j].last_node.second;
}
//So now in path we have the start and end nodes of every edge on the path
int siguiente_x = path.back().first;
int siguiente_y = path.back().second;
debug_camino(path);
return direccion_contiguos(ox,oy,siguiente_x,siguiente_y);
}
It's already fixed

Chess engine threefold repetition detection leads to incorrect play

I am trying to implement threefold repetition detection in my chess engine, but the method I use leads to incorrect play. I check for one repetition of the position in the current search space or two repetitions "behind" the root. This approach is used by Stockfish too but it makes my engine weaker (ELO score drops significantly when testing).
Each position in the history of the game has a zobrist hash index.
Threefold repetition detection:
bool Eval::isThreefoldRepetition() const {
const std::deque<GameState>&history=internal_board.getHistory();
int repetitions=0;
uint64_t hash=internal_board.getGameState().zobrist_key;
for(int k=history.size()-2;k>=0;k-=2){
if(history[k].zobrist_key==hash){
if(k>=root)//found repetition in current search space
return true;
//else we need 2 repetitions that happen before the root
repetitions++;
if(repetitions==2)
return true;
}
}
return false;
}
Search:
int Eval::negamax(int depth, int alpha, int beta, Color color) {
if(isThreefoldRepetition())
return threefold_repetition;
uint64_t hash=internal_board.getGameState().zobrist_key;
int alphaOrig=alpha;
Transposition node=TranspositionTable::getInstance().getTransposition(hash);
if(node.getType()!=NodeType::Null &&node.getDepth()>=depth){
if(node.getType()==NodeType::Exact)
return node.getValue();
else if(node.getType()==NodeType::LowerBound)
alpha=std::max(alpha,node.getValue());
else if(node.getType()==NodeType::UpperBound)
beta=std::min(beta,node.getValue());
if(alpha>=beta)
return node.getValue();
}
if (depth == 0) {
return getHeuristicScore(color);
}
std::vector<Move> moves = movegen.getAllMoves();
if (moves.size() == 0) {
if (movegen.isInCheck(color))
return checkmate - depth;//try to delay the checkmate
return stalemate;
}
int best = -infinity;
Move best_move;
setRating(moves);
std::sort(moves.begin(), moves.end(), compare);
for (const Move &move:moves) {
if(!hasTimeLeft()){
premature_stop=true;
break;
}
internal_board.makeMove(move);
int down = -negamax(depth - 1, -beta, -alpha, getOpposite(color));
internal_board.undoLastMove();
if(down>best){
best=down;
best_move=move;
}
alpha = std::max(alpha, best);
if (alpha >= beta)
break;
}
if(!premature_stop) {
//save the position in the transposition table
NodeType node_type;
if (best <= alphaOrig)//did not affect the score
node_type = NodeType::UpperBound;
else if (best >= beta)
node_type = NodeType::LowerBound;
else node_type = NodeType::Exact;
TranspositionTable::getInstance().addEntry(Transposition(node_type, hash, depth, best, best_move));
}
return best;
}
Notice that I can't check for a 'real' threefold repetition because a position will not be repeated thrice in the search space when using transposition tables.
Is my method of evaluation wrong?

Can't find the correct startvalue for FloodFill implementation in C++

I am trying to fill everything that is outside a contour using a queue implementation of flood fill. However i have some problems deducing the starting point to add to the queue.
I define a new double of 400*400 named tmpSlice and fill it with zeroes. Then i map the contour with label value 1.0. This part works just fine.
The problem occurs when i have to push a starting point onto the queue the best scenario would be to push the top left corner or point 0.
However i can not push 0 as a starting point without getting a segmentation fault. I am currently pushing 160000 which is the size of tmpSlice this is the only number i have been able to insert without errors.
However when i am pushing 160000 onto the queue and after the flood fill has executed i run the loop(at bottom of code) that runs from 0-160000.
This loop should color everything which has value 0 and 1 as one and everything that has value 2 should be zero. But currently everything inside the tmpSlice has value 0 except the original contour that has value 1.
For clarification 2 represents the outside of the contour, 1 represents the border of the contour and 0 represents whats inside the contour
So basically the flood fill does nothing on my dataset tmpSlice. I think it is because of the starting point inserted onto the queue but i haven't been able to insert any values that will work.
Thoughts?
PS.
I am limited to use the new double because a vector::std doesn't work with some functions from the minc library that i am using.
/* Flood fill */
//Colour we are looking for as background
int TargetColour = 0.0;
//The new colour we will write
int NewColour = 2.0;
//create set to secure unique entries into the queue, otherwise we etc. insert the 9th element in a 3x3 array 6 times.
set < int > Set;
//Create queue
queue < int > MyQue;
//Insert first point into the queue
MyQue.push(sizes[1]*sizes[2]);
int Node;
//While loop for iterating over the nodes.
while (!MyQue.empty()){
//Set front element to Node, and pop the front element from queue
Node = MyQue.front();
MyQue.pop();
//Change the colour to newcolour
tmpSlice[Node] = NewColour;
//Define the Node directions
int WestNode = Node-1;
int EastNode = Node+1;
//sizes are the lengths x,y
int NorthNode = Node-sizes[1];
int SouthNode = Node+sizes[2];
//Boundary checks
EastNodeBoundaryCheck = floor((Node-sizes[1]*sizes[2]*floor(Node/(sizes[1]*sizes[2])))/sizes[1]) == floor((EastNode-sizes[1]*sizes[2]*floor(EastNode/(sizes[1]*sizes[2])))/sizes[1]);
SouthNodeBoundaryCheck = floor(Node / (sizes[1]*sizes[2])) == floor(SouthNode / (sizes[1]*sizes[2]));
WestNodeBoundaryCheck = floor((Node-sizes[1]*sizes[2]*floor(Node/(sizes[1]*sizes[2])))/sizes[1]) == floor((WestNode-sizes[1]*sizes[2]*floor(WestNode/(sizes[1]*sizes[2])))/sizes[1]);
NorthNodeBoundaryCheck = floor(Node / (sizes[1]*sizes[2])) == floor(NorthNode / (sizes[1]*sizes[2]));
//East Node
if (Set.insert(EastNode).second) {
if (tmpSlice[EastNode] == TargetColour && EastNodeBoundaryCheck == 1){
MyQue.push(EastNode);
}
}
//South Node
if (Set.insert(SouthNode).second) {
if (tmpSlice[SouthNode] == TargetColour && SouthNodeBoundaryCheck == 1){
MyQue.push(SouthNode);
}
}
//West Node
if (Set.insert(WestNode).second) {
if (tmpSlice[WestNode] == TargetColour && WestNodeBoundaryCheck == 1){
MyQue.push(WestNode);
}
}
//North Node
if (Set.insert(NorthNode).second) {
if (tmpSlice[NorthNode] == TargetColour && NorthNodeBoundaryCheck == 1){
MyQue.push(NorthNode);
}
}
}
// Insert the colored points as 0 and everything else as 1
for(i = 0; i < sizes[1]*sizes[2]; i++){
if(tmpSlice[i] == 0.0 || 1.0){
slab[first_voxel_at_slice + i] = 1.0;
}
if(tmpSlice[i] == 2.0){
slab[first_voxel_at_slice + i] = 0.0;
}
}
I found the answer myself when answering a comment :-)
I needed to check the NorthNode if it was bigger than 0 for the first row to avoid segmentation fault.
//North Node
if (Set.insert(NorthNode).second) {
if (NorthNode > 0){
if (tmpSlice[NorthNode] == TargetColour && NorthNodeBoundaryCheck == 1){
MyQue.push(NorthNode);
}
}
}

Implementing Alpha Beta into Minimax

I'm trying to add Alpha Beta pruning into my minimax, but I can't understand where I'm going wrong.
At the moment I'm going through 5,000 iterations, where I should be going through approximately 16,000 according to a friend. When choosing the first position, it is returning -1 (a loss) whereas it should be able to definitely return a 0 at this point (a draw) as it should be able to draw from an empty board, however I can't see where I'm going wrong as I follow my code it seems to be fine
Strangely if I switch returning Alpha and Beta inside my checks (to achieve returning 0) the computer will attempt to draw but never initiate any winning moves, only blocks
My logical flow
If we are looking for alpha:
If the score > alpha, change alpha. if alpha and beta are overlapping, return alpha
If we are looking for beta:
If the score < beta, change beta. if alpha and beta are overlapping, return beta
Here is my
Recursive call
int MinimaxAB(TGameBoard* GameBoard, int iPlayer, bool _bFindAlpha, int _iAlpha, int _iBeta)
{
//How is the position like for player (their turn) on iGameBoard?
int iWinner = CheckForWin(GameBoard);
bool bFull = CheckForFullBoard(GameBoard);
//If the board is full or there is a winner on this board, return the winner
if(iWinner != NONE || bFull == true)
{
//Will return 1 or -1 depending on winner
return iWinner*iPlayer;
}
//Initial invalid move (just follows i in for loop)
int iMove = -1;
//Set the score to be instantly beaten
int iScore = INVALID_SCORE;
for(int i = 0; i < 9; ++i)
{
//Check if the move is possible
if(GameBoard->iBoard[i] == 0)
{
//Put the move in
GameBoard->iBoard[i] = iPlayer;
//Recall function
int iBestPositionSoFar = -MinimaxAB(GameBoard, Switch(iPlayer), !_bFindAlpha, _iAlpha, _iBeta);
//Replace Alpha and Beta variables if they fit the conditions - stops checking for situations that will never happen
if (_bFindAlpha == false)
{
if (iBestPositionSoFar < _iBeta)
{
//If the beta is larger, make the beta smaller
_iBeta = iBestPositionSoFar;
iMove = i;
if (_iAlpha >= _iBeta)
{
GameBoard->iBoard[i] = EMPTY;
//If alpha and beta are overlapping, exit the loop
++g_iIterations;
return _iBeta;
}
}
}
else
{
if (iBestPositionSoFar > _iAlpha)
{
//If the alpha is smaller, make the alpha bigger
_iAlpha = iBestPositionSoFar;
iMove = i;
if (_iAlpha >= _iBeta)
{
GameBoard->iBoard[i] = EMPTY;
//If alpha and beta are overlapping, exit the loop
++g_iIterations;
return _iAlpha;
}
}
}
//Remove the move you just placed
GameBoard->iBoard[i] = EMPTY;
}
}
++g_iIterations;
if (_bFindAlpha == true)
{
return _iAlpha;
}
else
{
return _iBeta;
}
}
Initial call (when computer should choose a position)
int iMove = -1; //Invalid
int iScore = INVALID_SCORE;
for(int i = 0; i < 9; ++i)
{
if(GameBoard->iBoard[i] == EMPTY)
{
GameBoard->iBoard[i] = CROSS;
int tempScore = -MinimaxAB(GameBoard, NAUGHT, true, -1000000, 1000000);
GameBoard->iBoard[i] = EMPTY;
//Choosing best value here
if (tempScore > iScore)
{
iScore = tempScore;
iMove = i;
}
}
}
//returns a score based on Minimax tree at a given node.
GameBoard->iBoard[iMove] = CROSS;
Any help regarding my logical flow that would make the computer return the correct results and make intelligent moves would be appreciated
Does your algorithm work perfectly without alpha-beta pruning? Your initial call should be given with false for _bFindAlpha as the root node behaves like an alpha node, but it doesn't look like this will make a difference:
int tempScore = -MinimaxAB(GameBoard, NAUGHT, false, -1000000, 1000000);
Thus I will recommend for you to abandon this _bFindAlpha nonsense and convert your algorithm to negamax. It behaves identically to minimax but makes your code shorter and clearer. Instead of checking whether to maximize alpha or minimize beta, you can just swap and negate when recursively invoking (this is the same reason you can return the negated value of the function right now). Here's a slightly edited version of the Wikipedia pseudocode:
function negamax(node, α, β, player)
if node is a terminal node
return color * the heuristic value of node
else
foreach child of node
val := -negamax(child, -β, -α, -player)
if val ≥ β
return val
if val > α
α := val
return α
Unless you love stepping through search trees, I think that you will find it easier to just write a clean, correct version of negamax than debug your current implementation.