Implementing Alpha Beta into Minimax - c++

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.

Related

Minimax algorithm for tic tac toe doesnt give optimal solution

I am writing 3D tic tac toe game using minimax algorithm with alpha beta pruning, but the algorithm doesnt give optimal solution, it goes and chooses next possible solution from winning states, not taking into concern what is on the board, meaning it wont block my moves.
This is the code:
`
int Game::miniMax(char marker, int depth, int alpha, int beta){
// Initialize best move
bestMove = std::make_tuple(-1, -1, -1);
// If we hit a terminal state (leaf node), return the best score and move
if (isBoardFull() || getBoardState('O')!=0 || depth>5)
return getBoardState('O');
auto allowedMoves = getAllowedMoves();
for (int i = 0; i < allowedMoves.size(); i++){
auto move = allowedMoves[i];
board[std::get<0>(move)][std::get<1>(move)][std::get<2>(move)] = marker;
// Maximizing player's turn
if (marker == 'O'){
int bestScore = INT32_MIN;
int score = miniMax('X', depth + 1, alpha, beta);
// Get the best scoring move
if (bestScore <= score){
bestScore = score - depth * 10;
bestMove = move;
// Check if this branch's best move is worse than the best
// option of a previously search branch. If it is, skip it
alpha = std::max(alpha, bestScore);
board[std::get<0>(move)][std::get<1>(move)][std::get<2>(move)] = '-';
if (beta <= alpha){
break;
}
}
} // Minimizing opponent's turn
else{
int bestScore = INT32_MAX;
int score = miniMax('O', depth + 1, alpha, beta);
if (bestScore >= score){
bestScore = score + depth * 10;
bestMove = move;
// Check if this branch's best move is worse than the best
// option of a previously search branch. If it is, skip it
beta = std::min(beta, bestScore);
board[std::get<0>(move)][std::get<1>(move)][std::get<2>(move)] = '-';
if (beta <= alpha){
break;
}
}
}
board[std::get<0>(move)][std::get<1>(move)][std::get<2>(move)] = '-'; // Undo move
}
if(marker== 'O')
return INT32_MIN;
else
return INT32_MAX;
}
`
What do I need to change to make it work?
I tried other ways to implement minimax, but it doesnt give optimal, or any, solution.
The limit on depth is because it is too slow for bigger depths, but still not giving solution, also increasing the value of the constant that multiplies the depth in the score part is only slowing the program

How to respond to a limitless possibility of outcomes?

So let's say that there is an imaginary 2 by 2 grid comprised for 4 numbers ...
1 2
3 4
You can either flip the grid horizontally or vertically down the middle by imputing either H or V respectively. You can also flip the grid as many times as you wish, with the previous choice affecting your future outcome.
For example, you could flip the grid horizontally down the middle, and then vertically.
While solving this problem, I got enough code written down so that the program works, except for the part where the "flipping" happens. Since you can enter as many H's and V's as you would like, I have some trouble writing code that would support this action.
Since the program input could contain as many horizontal or vertical flips as the user would prefer, that prevents me from manually using if-statements; in other words, I can't say "if the 1st letter is H, flip horizontally, if the 2nd letter is V, flip vertically, etc.".
This is just a short snippet of what I have figured out so far...
void flipGrid(string str, int letterPlace)
{
while (letterPlace < str.length())
{
if (str.at(letterPlace) == 'H')
{
// flip grid horizontally
}
else if (str.at(letterPlace) == 'V')
{
// flip grid vertically
}
letterPlace += 1;
}
}
int main()
{
int increment = 0;
string userInput;
cin >> userInput;
flipGrid(userInput, increment);
return 0;
}
As you can probably tell, I need help with the parts specified by the comments. If the code were to run as planned, it should look something like this...
Input (example 1)
H
Output
3 4
1 2
Input (example 2)
HVVH
Output (the two H's and the two V's cancel out, leaving us with the original)
1 2
3 4
I feel like there should be an easier way to solve this problem, or is the method I'm currently working on the right way to approach this problem? Please let me know if I'm on the right track or not. Thanks!
I would do a few things. First, I would simply count the H's and V's and, when done, modulo 2 each count. This will leave you flipCountH and flipCountV each having 0 or 1. There's no need to do multiple flips, right? Then you'll at most do each action once.
void flipCounts(string str, int &flipCountH, int &flipCountY)
{
for (char c: str) {
if (c == 'H')
{
++flipCountH;
}
else if (c == 'V')
{
++clipCountY
}
}
}
Use that method, then:
flipCountH %= 2;
flipCountY %= 2;
if (flipCountH > 0) {
performHorizontalFlip();
}
if (flipCountV > 0) {
performVerticalFlip();
}
Now, HOW you flip is based on how you store the data. For this very specific problem, I would store it in an int[2][2].
void performVerticalFlip() {
int[2] topLine;
topLine[0] = grid[0][0];
topLine[1] = grid[0][1];
grid[0][0] = grid[1][0];
grid[0][1] = grid[1][1];
grid[1][0] = topLine[0];
grid[1][1] = topLine[1];
}
Now, you can probably make use of C++ move semantics, but that's an advanced topic. You could also make a swap method that swaps two integers. That's not so advanced.
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
Then the code above is simpler:
swap(grid[0][0], grid[1][0]);
swap(grid[0][1], grid[1][1]);
Horizontal flip is similar.
From the comments:
I don't know how to flip it in each statement
So, flipping a 2x2 grid vertically is simple:
int tmp = grid[0][0];
grid[0][0] = grid[1][0];
grid[1][0] = tmp;
tmp = grid[0][1];
grid[0][1] = grid[1][1];
grid[1][1] = tmp;
If you have a grid bigger than a 2x2, this will work as well:
// for half the height of the grid
for(unsigned int i = 0;i<Height/2;i++) {
// for the width of the grid
for(unsigned int j =0; j<Width) {
// store a copy of the old value
int tmp = grid[i][j];
// put the new value in
grid[i][j] = grid[Height-1-i][j]; // note, we are flipping this vertically,
// so we want something an equal distance away
// from the other end as us
// replace the value we were grabbing from with the saved value
grid[Height-1-i][j] = tmp;
}
}
In case this is homework, I'm going to leave a horizontal flip for you to figure out (hint, it's the same thing, but with the width and height reversed).

Collision detection in voxel world

I am kinda stuck with my basic voxel physics right now. It's very, very choppy and I am pretty sure my maths is broken somewhere, but let's see what you have to say:
// SOMEWHERE AT CLASS LEVEL (so not being reinstantiated every frame, but persisted instead!)
glm::vec3 oldPos;
// ACTUAL IMPL
glm::vec3 distanceToGravityCenter =
this->entity->getPosition() -
((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
if (!entity->grounded) {
glm::vec3 entityPosition = entity->getPosition();
if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) {
glm::vec3 dir = entityPosition - oldPos; // Actually no need to normalize as we check for lesser, bigger or equal to 0
std::cout << "falling dir: " << glm::to_string(dir) << std::endl;
// Calculate offset (where to put after hit)
int x = dir.x;
int y = dir.y;
int z = dir.z;
if (dir.x >= 0) {
x = -1;
} else if (dir.x < 0) {
x = 1;
}
if (dir.y >= 0) {
y = -1;
} else if (dir.y < 0) {
y = 1;
}
if (dir.z >= 0) {
z = -1;
} else if (dir.z < 0) {
z = 1;
}
glm::vec3 newPos = oldPos + glm::vec3(x, y, z);
this->entity->setPosition(newPos);
entity->grounded = true; // If some update happens, grounded needs to be changed
} else {
oldPos = entity->getPosition();
this->entity->setPosition(distanceToGravityCenter);
}
}
Basic idea was to determine from which direction entityt would hit the surface and then just position it one "unit" back into that direction. But obviously I am doing something wrong as that will always move entity back to the point where it came from, effectively holding it at the spawn point.
Also this could probably be much easier and I am overthinking it.
As #CompuChip already pointed out, your ifs could be further simplified.
But what is more important is one logical issue that would explain the "choppiness" you describe (Sadly you did not provide any footage, so this is my best guess)
From the code you posted:
First you check if entity is grounded. If so you continue with checking if there is a collision and lastly, if there is not, you set the position.
You have to invert that a bit.
Save old position
Check if grounded
Set the position already to the new one!
Do collision detection
Reset to old position IF you registered a collision!
So basically:
glm::vec3 distanceToGravityCenter =
this->entity->getPosition() -
((this->entity->getPosition() - gravityCenter) * 0.005d); // TODO multiply by time
oldPos = entity->getPosition(); // 1.
if (!entity->grounded) { // 2.
this->fallingStar->setPosition(distanceToGravityPoint); // 3
glm::vec3 entityPosition = entity->getPosition();
if (getBlock(floorf(entityPosition.x), floorf(entityPosition.y), floorf(entityPosition.z))) { // 4, 5
this->entity->setPosition(oldPos);
entity->grounded = true; // If some update happens, grounded needs to be changed
}
}
This should get you started :)
I want to elaborate a bit more:
If you check for collision first and then set position you create an "infinite loop" upon first collision/hit as you collide, then if there is a collision (which there is) you set back to the old position. Basically just mathematic inaccuracy will make you move, as on every check you are set back to the old position.
Consider the if-statements for one of your coordinates:
if (dir.x >= 0) {
x = -1;
}
if (dir.x < 0) {
x = 1;
}
Suppose that dir.x < 0. Then you will skip the first if, enter the second, and x will be set to 1.
If dir.x >= 0, you will enter the first if and x will be set to -1. Now x < 0 is true, so you will enter the second if as well, and x gets set to 1 again.
Probably what you want is to either set x to 1 or to -1, depending on dir.x. You should only execute the second if when the first one was not entered, so you need an else if:
if (dir.x >= 0) {
x = -1;
} else if (dir.x < 0) {
x = 1;
}
which can be condensed, if you so please, into
x = (dir.x >= 0) ? -1 : 1;

Tallest tower with stacked boxes in the given order

Given N boxes. How can i find the tallest tower made with them in the given order ? (Given order means that the first box must be at the base of the tower and so on). All boxes must be used to make a valid tower.
It is possible to rotate the box on any axis in a way that any of its 6 faces gets parallel to the ground, however the perimeter of such face must be completely restrained inside the perimeter of the superior face of the box below it. In the case of the first box it is possible to choose any face, because the ground is big enough.
To solve this problem i've tried the following:
- Firstly the code generates the rotations for each rectangle (just a permutation of the dimensions)
- secondly constructing a dynamic programming solution for each box and each possible rotation
- finally search for the highest tower made (in the dp table)
But my algorithm is taking wrong answer in unknown test cases. What is wrong with it ? Dynamic programming is the best approach to solve this problem ?
Here is my code:
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstdlib>
#include <cstring>
struct rectangle{
int coords[3];
rectangle(){ coords[0] = coords[1] = coords[2] = 0; }
rectangle(int a, int b, int c){coords[0] = a; coords[1] = b; coords[2] = c; }
};
bool canStack(rectangle &current_rectangle, rectangle &last_rectangle){
for (int i = 0; i < 2; ++i)
if(current_rectangle.coords[i] > last_rectangle.coords[i])
return false;
return true;
}
//six is the number of rotations for each rectangle
int dp(std::vector< std::vector<rectangle> > &v){
int memoization[6][v.size()];
memset(memoization, -1, sizeof(memoization));
//all rotations of the first rectangle can be used
for (int i = 0; i < 6; ++i) {
memoization[i][0] = v[0][i].coords[2];
}
//for each rectangle
for (int i = 1; i < v.size(); ++i) {
//for each possible permutation of the current rectangle
for (int j = 0; j < 6; ++j) {
//for each permutation of the previous rectangle
for (int k = 0; k < 6; ++k) {
rectangle &prev = v[i - 1][k];
rectangle &curr = v[i][j];
//is possible to put the current rectangle with the previous rectangle ?
if( canStack(curr, prev) ) {
memoization[j][i] = std::max(memoization[j][i], curr.coords[2] + memoization[k][i-1]);
}
}
}
}
//what is the best solution ?
int ret = -1;
for (int i = 0; i < 6; ++i) {
ret = std::max(memoization[i][v.size()-1], ret);
}
return ret;
}
int main ( void ) {
int n;
scanf("%d", &n);
std::vector< std::vector<rectangle> > v(n);
for (int i = 0; i < n; ++i) {
rectangle r;
scanf("%d %d %d", &r.coords[0], &r.coords[1], &r.coords[2]);
//generate all rotations with the given rectangle (all combinations of the coordinates)
for (int j = 0; j < 3; ++j)
for (int k = 0; k < 3; ++k)
if(j != k) //micro optimization disease
for (int l = 0; l < 3; ++l)
if(l != j && l != k)
v[i].push_back( rectangle(r.coords[j], r.coords[k], r.coords[l]) );
}
printf("%d\n", dp(v));
}
Input Description
A test case starts with an integer N, representing the number of boxes (1 ≤ N ≤ 10^5).
Following there will be N rows, each containing three integers, A, B and C, representing the dimensions of the boxes (1 ≤ A, B, C ≤ 10^4).
Output Description
Print one row containing one integer, representing the maximum height of the stack if it’s possible to pile all the N boxes, or -1 otherwise.
Sample Input
2
5 2 2
1 3 4
Sample Output
6
Sample image for the given input and output.
Usually you're given the test case that made you fail. Otherwise, finding the problem is a lot harder.
You can always approach it from a different angle! I'm going to leave out the boring parts that are easily replicated.
struct Box { unsigned int dim[3]; };
Box will store the dimensions of each... box. When it comes time to read the dimensions, it needs to be sorted so that dim[0] >= dim[1] >= dim[2].
The idea is to loop and read the next box each iteration. It then compares the second largest dimension of the new box with the second largest dimension of the last box, and same with the third largest. If in either case the newer box is larger, it adjusts the older box to compare the first largest and third largest dimension. If that fails too, then the first and second largest. This way, it always prefers using a larger dimension as the vertical one.
If it had to rotate a box, it goes to the next box down and checks that the rotation doesn't need to be adjusted there too. It continues until there are no more boxes or it didn't need to rotate the next box. If at any time, all three rotations for a box failed to make it large enough, it stops because there is no solution.
Once all the boxes are in place, it just sums up each one's vertical dimension.
int main()
{
unsigned int size; //num boxes
std::cin >> size;
std::vector<Box> boxes(size); //all boxes
std::vector<unsigned char> pos(size, 0); //index of vertical dimension
//gets the index of dimension that isn't vertical
//largest indicates if it should pick the larger or smaller one
auto get = [](unsigned char x, bool largest) { if (largest) return x == 0 ? 1 : 0; return x == 2 ? 1 : 2; };
//check will compare the dimensions of two boxes and return true if the smaller one is under the larger one
auto check = [&boxes, &pos, &get](unsigned int x, bool largest) { return boxes[x - 1].dim[get(pos[x - 1], largest)] < boxes[x].dim[get(pos[x], largest)]; };
unsigned int x = 0, y; //indexing variables
unsigned char change; //detects box rotation change
bool fail = false; //if it cannot be solved
for (x = 0; x < size && !fail; ++x)
{
//read in the next three dimensions
//make sure dim[0] >= dim[1] >= dim[2]
//simple enough to write
//mine was too ugly and I didn't want to be embarrassed
y = x;
while (y && !fail) //when y == 0, no more boxes to check
{
change = pos[y - 1];
while (check(y, true) || check(y, false)) //while invalid rotation
{
if (++pos[y - 1] == 3) //rotate, when pos == 3, no solution
{
fail = true;
break;
}
}
if (change != pos[y - 1]) //if rotated box
--y;
else
break;
}
}
if (fail)
{
std::cout << -1;
}
else
{
unsigned long long max = 0;
for (x = 0; x < size; ++x)
max += boxes[x].dim[pos[x]];
std::cout << max;
}
return 0;
}
It works for the test cases I've written, but given that I don't know what caused yours to fail, I can't tell you what mine does differently (assuming it also doesn't fail your test conditions).
If you are allowed, this problem might benefit from a tree data structure.
First, define the three possible cases of block:
1) Cube - there is only one possible option for orientation, since every orientation results in the same height (applied toward total height) and the same footprint (applied to the restriction that the footprint of each block is completely contained by the block below it).
2) Square Rectangle - there are three possible orientations for this rectangle with two equal dimensions (for examples, a 4x4x1 or a 4x4x7 would both fit this).
3) All Different Dimensions - there are six possible orientations for this shape, where each side is different from the rest.
For the first box, choose how many orientations its shape allows, and create corresponding nodes at the first level (a root node with zero height will allow using simple binary trees, rather than requiring a more complicated type of tree that allows multiple elements within each node). Then, for each orientation, choose how many orientations the next box allows but only create nodes for those that are valid for the given orientation of the current box. If no orientations are possible given the orientation of the current box, remove that entire unique branch of orientations (the first parent node with multiple valid orientations will have one orientation removed by this pruning, but that parent node and all of its ancestors will be preserved otherwise).
By doing this, you can check for sets of boxes that have no solution by checking whether there are any elements below the root node, since an empty tree indicates that all possible orientations have been pruned away by invalid combinations.
If the tree is not empty, then just walk the tree to find the highest sum of heights within each branch of the tree, recursively up the tree to the root - the sum value is your maximum height, such as the following pseudocode:
std::size_t maximum_height() const{
if(leftnode == nullptr || rightnode == nullptr)
return this_node_box_height;
else{
auto leftheight = leftnode->maximum_height() + this_node_box_height;
auto rightheight = rightnode->maximum_height() + this_node_box_height;
if(leftheight >= rightheight)
return leftheight;
else
return rightheight;
}
}
The benefits of using a tree data structure are
1) You will greatly reduce the number of possible combinations you have to store and check, because in a tree, the invalid orientations will be eliminated at the earliest possible point - for example, using your 2x2x5 first box, with three possible orientations (as a Square Rectangle), only two orientations are possible because there is no possible way to orient it on its 2x2 end and still fit the 4x3x1 block on it. If on average only two orientations are possible for each block, you will need a much smaller number of nodes than if you compute every possible orientation and then filter them as a second step.
2) Detecting sets of blocks where there is no solution is much easier, because the data structure will only contain valid combinations.
3) Working with the finished tree will be much easier - for example, to find the sequence of orientations of the highest, rather than just the actual height, you could pass an empty std::vector to a modified highest() implementation, and let it append the actual orientation of each highest node as it walks the tree, in addition to returning the height.

C++ MiniMax Algorithm getting stuck in an infinite loop for Tic Tac Toe Board

I'm currently implementing a MiniMax Algorithm with Alpha Beta Pruning for a Tic Tac Toe game.
My algorithm takes in an empty board and, at the end has that board contain the same state as the current board, along with the next move made. Then, I simply make *this (the current board) equal to the returned board.
However, for some reason, my algorithm is getting stuck in an infinite loop. Here's my miniMax function:
int board::miniMax(int alpha, int beta, board & childWithMaximum)
{
if (checkDone())
return boardScore();
vector<board> children = getChildren();
while (!children.empty())
{
board curr = children.back();
board dummyBoard;
int score = curr.miniMax(alpha, beta, dummyBoard);
if (computerTurn && (beta > score)) {
beta = score;
childWithMaximum = *this;
if (alpha >= beta)
break;
} else if (alpha < score) {
alpha = score;
childWithMaximum = *this;
if (alpha >= beta)
break;
}
}
return computerTurn ? alpha : beta;
}
I've done some print-statement debugging, and it appears that this getChildren() helper function is working. I had it print out a couple of children and there were other board states in the tree:
vector<board> board::getChildren()
{
vector<board> children;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (getPosition(i, j) == '*') {
//move not made here
board moveMade(*this);
moveMade.setPosition(i, j);
children.push_back(moveMade);
}
}
}
return children;
}
However, my miniMax() function is not making the return board equal to the next move.
The instructions in your while-loop never modify children, but will only stop if children.empty() is true. Therefore the loop inner is either never executed or executed infinitely.
Also here:
int score = curr.miniMax(alpha, beta, dummyBoard);
you call the function recursively with same parameters (except the third one which however is unused up to that point). Since the state of this, alpha and beta seems to be unchanged up to this point (except maybe if checkDone() or getPosition() changes it) this will also result in an infinite recursion.
Then, I simply make *this (the current board) equal to the returned board.
No, you only make other boards equal to *this. I do not see *this = anywhere in your code.