minimax c++ implementation for tic tac toe - c++

void generate_moves(int gameBoard[9], list<int> &moves)
{
for (int i = 0; i < 9; i++)
{
if (gameBoard[i] == 0){
moves.push_back(i);
}
}
}
int evaluate_position(int gameBoard[9], int playerTurn)
{
state currentGameState = checkWin(gameBoard);
if (currentGameState != PLAYING)
{
if ((playerTurn == 1 && currentGameState == XWIN) || (playerTurn == -1 && currentGameState == OWIN))
return +infinity;
else if ((playerTurn == -1 && currentGameState == XWIN) || (playerTurn == 1 && currentGameState == OWIN))
return -infinity;
else if (currentGameState == DRAW)
return 0;
}
return -1;
}
int MinMove(int gameBoard[9], int playerTurn)
{
//if (checkWin(gameBoard) != PLAYING) { return evaluate_board(gameBoard); };
int pos_val = evaluate_position(gameBoard, playerTurn);
if (pos_val != -1) return pos_val;
int bestScore = +infinity;
list<int> movesList;
generate_moves(gameBoard, movesList);
while (!movesList.empty())
{
gameBoard[movesList.front()] = playerTurn;
int score = MaxMove(gameBoard, playerTurn*-1);
if (score < bestScore)
{
bestScore = score;
}
gameBoard[movesList.front()] = 0;
movesList.pop_front();
}
return bestScore;
}
int MaxMove(int gameBoard[9], int playerTurn)
{
//if (checkWin(gameBoard) != PLAYING) { return evaluate_board(gameBoard); };
int pos_val = evaluate_position(gameBoard, playerTurn);
if (pos_val != -1) return pos_val;
int bestScore = -infinity;
list<int> movesList;
generate_moves(gameBoard, movesList);
while (!movesList.empty())
{
gameBoard[movesList.front()] = playerTurn;
int score = MinMove(gameBoard, playerTurn*-1);
if (score > bestScore)
{
bestScore = score;
}
gameBoard[movesList.front()] = 0;
movesList.pop_front();
}
return bestScore;
}
int MiniMax(int gameBoard[9], int playerTurn)
{
int bestScore = -infinity;
int index = 0;
list<int> movesList;
vector<int> bestMoves;
generate_moves(gameBoard, movesList);
while (!movesList.empty())
{
gameBoard[movesList.front()] = playerTurn;
int score = MinMove(gameBoard, playerTurn);
if (score > bestScore)
{
bestScore = score;
bestMoves.clear();
bestMoves.push_back(movesList.front());
}
else if (score == bestScore)
{
bestMoves.push_back(movesList.front());
}
gameBoard[movesList.front()] = 0;
movesList.pop_front();
}
index = bestMoves.size();
if (index > 0) {
time_t secs;
time(&secs);
srand((uint32_t)secs);
index = rand() % index;
}
return bestMoves[index];
}
I created a tic tac toe program in C++ and tried to implement a MiniMax algorithm with exhaustive search tree.
These are the functions I have written using wiki and with the help of some websites. But the AI just doesn't work right and at times doesn't play its turn at all.
Could someone have a look and please point out if there is anything wrong with the logic?
This is how I think it works:
Minimax : This function starts with very large -ve number as best score and goal is to maximize that number. It calls minMove function. If new score > best score, then best score = new score...
MinMove : This function evaluates game board. If game over then it returns -infinity or +infinity depending on who won. If game is going on this function starts with max +infinity value as best score and goal is to minimize it as much possible. It calls MaxMove with opponent player's turn. (since players alternate turns).
If score < best score then best score = score. ...
MaxMove : This function evaluates game board. If game over then it returns -infinity or +infinity depending on who won. If game is going on this function starts with least -infinity value as best score and goal is to maximize it as much possible. It calls MinMove with opponent player's turn. (since players alternate turns).
If score > best score then best score = score. ...
Minmove and MaxMove call each other mutually recursively, MaxMove maximizing the value and MinMove minimizing it. Finally it returns the best possible moves list.
If there are more than 1 best moves, then a random of them is picked as the computer's move.

In MiniMax, MinMove(gameBoard, playerTurn) should be MinMove(gameBoard, -playerTurn) as you do in MaxMove.
As you use MinMove and MaxMove, your evaluation function should be absolute. I mean +infinity for XWIN
and -infinity for OWIN. And so MinMove can only be use when player == -1 and MaxMove when player == 1, thus the parameter become useless. And so MiniMax can only be used by player == 1.
I have done some changes in your code and it works (https://ideone.com/Ihy1SR).

Related

How to store the first step of best value in a minimax tree?

I have a minimax tree and an evaluation function.The minimax function return only an integer(best value).How can i store the first move of the way of the founded best value ?
here's my code :
int Brain::MiniMax(GameBoard gb, int depth,int Turn,int lastcount) //0->Max 1->Min
{
if (depth == 5)
return Evaluation(lastcount, gb);
int bestval = 0;
if (Turn == 0)
{
bestval = -100;
vector<pair<int, pair<int, int>>> possibleFences = this->PossibleFences(gb);
for (int i = 0; i < possibleFences.size(); i++)//ForFences
{
int cnt = 0;
NextShortestPathMove(cnt, gb.OpponentPawn,gb);
if (gb.CanPutFence(possibleFences[i].first, possibleFences[i].second.first, possibleFences[i].second.second) == 0)
continue;
gb.PutFence(possibleFences[i].second.first, possibleFences[i].second.second, possibleFences[i].first);
int value = MiniMax(gb, depth + 1,1, cnt);
if (value > bestval)
{
bestval = value;
move = possibleFences[i];
}
}
return bestval;
}
else if (Turn == 1)
{
bestval = +100;
int** possibleMoves = this->PossibleMoves(gb.OpponentPawn.Row, gb.OpponentPawn.Column, gb.OpponentPawn,gb);
for (int i = 0; i < 6; i++)
{
if (possibleMoves[i][0] == -1)
continue;
int cnt = 0;
NextShortestPathMove(cnt, gb.OpponentPawn,gb);
gb.MoveOpponentPlayer(possibleMoves[i][0], possibleMoves[i][1]);
int value = MiniMax(gb, depth + 1, 0,cnt);
bestval = min(value, bestval);
}
return bestval;
}
}
for example at the end if the bestval = 10, i want the first move of the selection of this bestval. now i store the move in the 'move' variable but it doesn't work correctly.
In a practical minimax algorithm implementation, a hash table is used to enter evaluated scores and moves including hashkey, a value unique for each position of the players and pieces. This is also useful during implementation of "force move".
During move evaluation, hashkey, score and move position are recorded in a struct and the hash table as well. So, after successful search, the entire struct is returned to enable update of the graphics and game status.
A typical hash entry looks like so:
struct HashEntry {
int move;
int score;
int depth;
uint64_t posKey;
int flags;
};

Tic Tac Toe: Evaluating Heuristic Value of a Node

Pardon me if this question already exists, I've searched a lot but I haven't gotten the answer to the question I want to ask. So, basically, I'm trying to implement a Tic-Tac-Toe AI that uses the Minimax algorithm to make moves.
However, one thing I don't get is, that when Minimax is used on an empty board, the value returned is always 0 (which makes sense because the game always ends in a draw if both players play optimally).
So Minimax always chooses the first tile as the best move when AI is X (since all moves return 0 as value). Same happens for the second move and it always chooses the second tile instead. How can I fix this problem to make my AI pick the move with the higher probability of winning? Here is the evaluation and Minimax function I use (with Alpha-Beta pruning):
int evaluate(char board[3][3], char AI)
{
for (int row = 0; row<3; row++)
{
if (board[row][0] != '_' && board[row][0] == board[row][1] && board[row][1] == board[row][2])
{
if (board[row][0]==AI)
{
return +10;
}
else
{
return -10;
}
}
}
for (int col = 0; col<3; col++)
{
if (board[0][col] != '_' && board[0][col] == board[1][col] && board[1][col] == board[2][col])
{
if (board[0][col]==AI)
{
return +10;
}
else
{
return -10;
}
}
}
if (board[1][1] != '_' && ((board[0][0]==board[1][1] && board[1][1]==board[2][2]) || (board[0][2]==board[1][1] && board[1][1]==board[2][0])))
{
if (board[1][1]==AI)
{
return +10;
}
else
{
return -10;
}
}
return 0;
}
int Minimax(char board[3][3], bool AITurn, char AI, char Player, int depth, int alpha, int beta)
{
bool breakout = false;
int score = evaluate(board, AI);
if(score == 10)
{
return score - depth;
}
else if(score == -10)
{
return score + depth;
}
else if(NoTilesEmpty(board))
{
return 0;
}
if(AITurn == true)
{
int bestvalue = -1024;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j<3; j++)
{
if(board[i][j] == '_')
{
board[i][j] = AI;
bestvalue = max(bestvalue, Minimax(board, false, AI, Player, depth+1, alpha, beta));
alpha = max(bestvalue, alpha);
board[i][j] = '_';
if(beta <= alpha)
{
breakout = true;
break;
}
}
}
if(breakout == true)
{
break;
}
}
return bestvalue;
}
else if(AITurn == false)
{
int bestvalue = +1024;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j<3; j++)
{
if(board[i][j] == '_')
{
board[i][j] = Player;
bestvalue = min(bestvalue, Minimax(board, true, AI, Player, depth+1, alpha, beta));
beta = min(bestvalue, beta);
board[i][j] = '_';
if(beta <= alpha)
{
breakout = true;
break;
}
}
}
if(breakout == true)
{
break;
}
}
return bestvalue;
}
}
Minimax assumes optimal play, so maximizing "probability of winning" is not a meaningful notion: Since the other player can force a draw but cannot force a win, they will always force a draw. If you want to play optimally against a player who is not perfectly rational (which, of course, is one of the only two ways to win*), you'll need to assume some probability distribution over the opponent's moves and use something like ExpectMinimax, where with some probability the opponent's move is overridden by a random mistake. Alternatively, you can deliberately restrict the ply of the minimax search, using a heuristic for the opponent's play beyond a certain depth (but still searching the game tree for your own moves.)
* The other one is not to play.
Organize your code into smaller routines so that it looks tidier and easier to debug. Apart from the recursive minimax function, an all-possible-valid-move generation function and a robust evaluation sub-routine are essential ( which seems lacking here).
For example, at the beginning of the game, the evaluation algorithm should return a non-zero score, every position should have a relative scoring index ( eg middle position may have slightly higher weightage than the corners).
Your minimax boundary condition - return if there is no empty cell positions ; is flawed as it will evaluate even when a winning/losing move occurred in the preceding ply. Such conditions will aggravate in more complex AI games.
If you are new to minimax, you can find plenty of ready to compile sample codes on CodeReview

Minimax with alpha-beta pruning problems

I'm making a C++ program for the game chopsticks.
It's a really simple game with only 625 total game states (and it's even lower if you account for symmetry and unreachable states). I have read up minimax and alpha-beta algorithms, mostly for tic tac toe, but the problem I was having was that in tic tac toe it's impossible to loop back to a previous state while that can easily happen in chopsticks. So when running the code it would end up with a stack overflow.
I fixed this by adding flags for previously visited states (I don't know if that's the right way to do it.) so that they can be avoided, but now the problem I have is that the output is not symmetric as expected.
For example in the start state of the game each player has one finger so it's all symmetric. The program tells me that the best move is to hit my right hand with my left but not the opposite.
My source code is -
#include <iostream>
#include <array>
#include <vector>
#include <limits>
std::array<int, 625> t; //Flags for visited states.
std::array<int, 625> f; //Flags for visited states.
int no = 0; //Unused. For debugging.
class gamestate
{
public:
gamestate(int x, bool t) : turn(t) //Constructor.
{
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++) {
val[i][j] = x % 5;
x /= 5;
}
init();
}
void print() //Unused. For debugging.
{
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++)
std::cout << val[i][j] << "\t";
std::cout << "\n";
}
std::cout << "\n";
}
std::array<int, 6> canmove = {{ 1, 1, 1, 1, 1, 1 }}; //List of available moves.
bool isover() //Is the game over.
{
return ended;
}
bool won() //Who won the game.
{
return winner;
}
bool isturn() //Whose turn it is.
{
return turn;
}
std::vector<int> choosemoves() //Choose the best possible moves in the current state.
{
std::vector<int> bestmoves;
if(ended)
return bestmoves;
std::array<int, 6> scores;
int bestscore;
if(turn)
bestscore = std::numeric_limits<int>::min();
else
bestscore = std::numeric_limits<int>::max();
scores.fill(bestscore);
for (int i = 0; i < 6; i++)
if (canmove[i]) {
t.fill(0);
f.fill(0);
gamestate *play = new gamestate(this->playmove(i),!turn);
scores[i] = minimax(play, 0, std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
std::cout<<i<<": "<<scores[i]<<std::endl;
delete play;
if (turn) if (scores[i] > bestscore) bestscore = scores[i];
if (!turn) if (scores[i] < bestscore) bestscore = scores[i];
}
for (int i = 0; i < 6; i++)
if (scores[i] == bestscore)
bestmoves.push_back(i);
return bestmoves;
}
private:
std::array<std::array<int, 2>, 2 > val; //The values of the fingers.
bool turn; //Whose turn it is.
bool ended = false; //Has the game ended.
bool winner; //Who won the game.
void init() //Check if the game has ended and find the available moves.
{
if (!(val[turn][0]) && !(val[turn][1])) {
ended = true;
winner = !turn;
canmove.fill(0);
return;
}
if (!(val[!turn][0]) && !(val[!turn][1])) {
ended = true;
winner = turn;
canmove.fill(0);
return;
}
if (!val[turn][0]) {
canmove[0] = 0;
canmove[1] = 0;
canmove[2] = 0;
if (val[turn][1] % 2)
canmove[5] = 0;
}
if (!val[turn][1]) {
if (val[turn][0] % 2)
canmove[2] = 0;
canmove[3] = 0;
canmove[4] = 0;
canmove[5] = 0;
}
if (!val[!turn][0]) {
canmove[0] = 0;
canmove[3] = 0;
}
if (!val[!turn][1]) {
canmove[1] = 0;
canmove[4] = 0;
}
}
int playmove(int mov) //Play a move to get the next game state.
{
auto newval = val;
switch (mov) {
case 0:
newval[!turn][0] = (newval[turn][0] + newval[!turn][0]);
newval[!turn][0] = (5 > newval[!turn][0]) ? newval[!turn][0] : 0;
break;
case 1:
newval[!turn][1] = (newval[turn][0] + newval[!turn][1]);
newval[!turn][1] = (5 > newval[!turn][1]) ? newval[!turn][1] : 0;
break;
case 2:
if (newval[turn][1]) {
newval[turn][1] = (newval[turn][0] + newval[turn][1]);
newval[turn][1] = (5 > newval[turn][1]) ? newval[turn][1] : 0;
} else {
newval[turn][0] /= 2;
newval[turn][1] = newval[turn][0];
}
break;
case 3:
newval[!turn][0] = (newval[turn][1] + newval[!turn][0]);
newval[!turn][0] = (5 > newval[!turn][0]) ? newval[!turn][0] : 0;
break;
case 4:
newval[!turn][1] = (newval[turn][1] + newval[!turn][1]);
newval[!turn][1] = (5 > newval[!turn][1]) ? newval[!turn][1] : 0;
break;
case 5:
if (newval[turn][0]) {
newval[turn][0] = (newval[turn][1] + newval[turn][0]);
newval[turn][0] = (5 > newval[turn][0]) ? newval[turn][0] : 0;
} else {
newval[turn][1] /= 2;
newval[turn][0] = newval[turn][1];
}
break;
default:
std::cout << "\nInvalid move!\n";
}
int ret = 0;
for (int i = 1; i > -1; i--)
for (int j = 1; j > -1; j--) {
ret+=newval[i][j];
ret*=5;
}
ret/=5;
return ret;
}
static int minimax(gamestate *game, int depth, int alpha, int beta) //Minimax searching function with alpha beta pruning.
{
if (game->isover()) {
if (game->won())
return 1000 - depth;
else
return depth - 1000;
}
if (game->isturn()) {
for (int i = 0; i < 6; i++)
if (game->canmove[i]&&t[game->playmove(i)]!=-1) {
int score;
if(!t[game->playmove(i)]){
t[game->playmove(i)] = -1;
gamestate *play = new gamestate(game->playmove(i),!game->isturn());
score = minimax(play, depth + 1, alpha, beta);
delete play;
t[game->playmove(i)] = score;
}
else
score = t[game->playmove(i)];
if (score > alpha) alpha = score;
if (alpha >= beta) break;
}
return alpha;
} else {
for (int i = 0; i < 6; i++)
if (game->canmove[i]&&f[game->playmove(i)]!=-1) {
int score;
if(!f[game->playmove(i)]){
f[game->playmove(i)] = -1;
gamestate *play = new gamestate(game->playmove(i),!game->isturn());
score = minimax(play, depth + 1, alpha, beta);
delete play;
f[game->playmove(i)] = score;
}
else
score = f[game->playmove(i)];
if (score < beta) beta = score;
if (alpha >= beta) break;
}
return beta;
}
}
};
int main(void)
{
gamestate test(243, true);
auto movelist = test.choosemoves();
for(auto i: movelist)
std::cout<<i<<std::endl;
return 0;
}
I'm passing the moves in a sort of base-5 to decimal system as each hand can have values from 0 to 4.
In the code I have input the state -
3 3
4 1
The output says I should hit my right hand (1) to the opponent's right (3) but it does not say I should hit it to my opponent's left (also 3)
I think the problem is because of the way I handled infinite looping.
What would be the right way to do it? Or if that is the right way, then how do I fix the problem?
Also please let me know how I can improve my code.
Thanks a lot.
Edit:
I have changed my minimax function as follows to ensure that infinite loops are scored above losing but I'm still not getting symmetry. I also made a function to add depth to the score
static float minimax(gamestate *game, int depth, float alpha, float beta) //Minimax searching function with alpha beta pruning.
{
if (game->isover()) {
if (game->won())
return 1000 - std::atan(depth) * 2000 / std::acos(-1);
else
return std::atan(depth) * 2000 / std::acos(-1) - 1000;
}
if (game->isturn()) {
for (int i = 0; i < 6; i++)
if (game->canmove[i]) {
float score;
if(!t[game->playmove(i)]) {
t[game->playmove(i)] = -1001;
gamestate *play = new gamestate(game->playmove(i), !game->isturn());
score = minimax(play, depth + 1, alpha, beta);
delete play;
t[game->playmove(i)] = score;
} else if(t[game->playmove(i)] == -1001)
score = 0;
else
score = adddepth(t[game->playmove(i)], depth);
if (score > alpha) alpha = score;
if (alpha >= beta) break;
}
return alpha;
} else {
for (int i = 0; i < 6; i++)
if (game->canmove[i]) {
float score;
if(!f[game->playmove(i)]) {
f[game->playmove(i)] = -1001;
gamestate *play = new gamestate(game->playmove(i), !game->isturn());
score = minimax(play, depth + 1, alpha, beta);
delete play;
f[game->playmove(i)] = score;
} else if(f[game->playmove(i)] == -1001)
score = 0;
else
score = adddepth(f[game->playmove(i)], depth);
if (score < beta) beta = score;
if (alpha >= beta) break;
}
return beta;
}
}
This is the function to add depth -
float adddepth(float score, int depth) //Add depth to pre-calculated score.
{
int olddepth;
float newscore;
if(score > 0) {
olddepth = std::tan((1000 - score) * std::acos(-1) / 2000);
depth += olddepth;
newscore = 1000 - std::atan(depth) * 2000 / std::acos(-1);
} else {
olddepth = std::tan((1000 + score) * std::acos(-1) / 2000);
depth += olddepth;
newscore = std::atan(depth) * 2000 / std::acos(-1) - 1000;
}
return newscore;
}
Disclaimer: I don't know C++, and I frankly haven't bothered to read the game rules. I have now read the rules, and still stand by what I said...but I still don't know C++. Still, I can present some general knowledge of the algorithm which should set you off in the right direction.
Asymmetry is not in itself a bad thing. If two moves are exactly equivalent, it should choose one of them and not stand helpless like Buridan's ass. You should, in fact, be sure that any agent you write has some method of choosing arbitrarily between policies which it cannot distinguish.
You should think more carefully about the utility scheme implied by refusing to visit previous states. Pursuing an infinite loop is a valid policy, even if your current representation of it will crash the program; maybe the bug is the overflow, not the policy that caused it. If given the choice between losing the game, and refusing to let the game end, which do you want your agent to prefer?
Playing ad infinitum
If you want your agent to avoid losing at all costs -- that is, you want it to prefer indefinite play over loss -- then I would suggest treating any repeated state as a terminal state and assigning it a value somewhere between winning and losing. After all, in a sense it is terminal -- this is the loop the game will enter forever and ever and ever, and the definite result of it is that there is no winner. However, remember that if you are using simple minimax (one utility function, not two), then this implies that your opponent also regards eternal play as a middling result.
It may sound ridiculous, but maybe playing unto infinity is actually a reasonable policy. Remember that minimax assumes the worst case -- a perfectly rational foe whose interests are the exact opposite of yours. But if, for example, you're writing an agent to play against a human, then the human will either err logically, or will eventually decide they would rather end the game by losing -- so your agent will benefit from patiently staying in this Nash equilibrium loop!
Alright, let's end the game already
If you want your agent to prefer that the game end eventually, then I would suggest implementing a living penalty -- a modifier added to your utility which decreases as a function of time (be it asymptotic or without bound). Implemented carefully, this can guarantee that, eventually, any end is preferable to another turn. With this solution as well, you need to be careful about considering what preferences this implies for your opponent.
A third way
Another common solution is to depth-limit your search and implement an evaluation function. This takes the game state as its input and just spits out a utility value which is its best guess at the end result. Is this provably optimal? No, not unless your evaluation function is just completing the minimax, but it means your algorithm will finish within a reasonable time. By burying this rough estimate deep enough in the tree, you wind up with a pretty reasonable model. However, this produces an incomplete policy, which means that it is more useful for a replanning agent than for a standard planning agent. Minimax replanning is the usual approach for complex games (it is, if I'm not mistaken, the basic algorithm followed by Deep Blue), but since this is a very simple game you probably don't need to take this approach.
A side note on abstraction
Note that all of these solutions are conceptualized as either numeric changes to or estimations of the utility function. This is, in general, preferable to arbitrarily throwing away possible policies. After all, that's what your utility function is for -- any time you make a policy decision on the basis of anything except the numeric value of your utility, you are breaking your abstraction and making your code less robust.

Connect Four - Negamax AI evaluation function issue

I'm trying to implement NegaMax ai for Connect 4. The algorithm works well some of the time, and the ai can win. However, sometimes it completely fails to block opponent 3 in a rows, or doesn't take a winning shot when it has three in a row.
The evaluation function iterates through the grid (horizontally, vertically, diagonally up, diagonally down), and takes every set of four squares. It then checks within each of these sets and evaluates based on this.
I've based the function on the evaluation code provided here: http://blogs.skicelab.com/maurizio/connect-four.html
My function is as follows:
//All sets of four tiles are evaluated before this
//and values for the following variables are set.
if (redFoursInARow != 0)
{
redScore = INT_MAX;
}
else
{
redScore = (redThreesInARow * threeWeight) + (redTwosInARow * twoWeight);
}
int yellowScore = 0;
if (yellowFoursInARow != 0)
{
yellowScore = INT_MAX;
}
else
{
yellowScore = (yellowThreesInARow * threeWeight) + (yellowTwosInARow * twoWeight);
}
int finalScore = yellowScore - redScore;
return turn ? finalScore : -finalScore; //If this is an ai turn, return finalScore. Else return -finalScore.
My negamax function looks like this:
inline int NegaMax(char g[6][7], int depth, int &bestMove, int row, int col, bool aiTurn)
{
{
char c = CheckForWinner(g);
if ('E' != c || 0 == depth)
{
return EvaluatePosition(g, aiTurn);
}
}
int bestScore = INT_MIN;
for (int i = 0; i < 7; ++i)
{
if (CanMakeMove(g, i)) //If column i is not full...
{
{
//...then make a move in that column.
//Grid is a 2d char array.
//'E' = empty tile, 'Y' = yellow, 'R' = red.
char newPos[6][7];
memcpy(newPos, g, sizeof(char) * 6 * 7);
int newRow = GetNextEmptyInCol(g, i);
if (aiTurn)
{
UpdateGrid(newPos, i, 'Y');
}
else
{
UpdateGrid(newPos, i, 'R');
}
int newScore = 0; int newMove = 0;
newScore = NegaMax(newPos, depth - 1, newMove, newRow, i, !aiTurn);
newScore = -newScore;
if (newScore > bestScore)
{
bestMove = i;
bestScore = newScore;
}
}
}
}
return bestScore;
}
I'm aware that connect four has been solved are that there are definitely better ways to go about this, but any help or suggestions with fixing/improving this will be greatly appreciated. Thanks!

C++ Darts Game: Loop does not exit second time round

I am currently coding a simulator for a game (or multiple games) of 501 darts in C++ and have run into a problem in my code.
The purpose of this region is to simulate 10,000 matches of world championship final darts between two players. One match consists of two players playing the best of 13 sets (first to 7), a set being the best of 5 games (first to 3). In each game both players start at a score of 501 and try to reduce their score to 0 before the other player. I am simulating 10,000 of these matches in order to gain the frequency of each possible result between the two players.
My players are declared in a class with their skill attributes and score count etc, with a method throw_quick_set(); which simulates the player throwing a set of 3 darts for their turn before the other player steps up to do the same.
My problem is, after the first match is won (the program sees a player with 7 sets) - the second match goes on forever with a player winning an infinite amount of sets. I see this because of the output phrases I included when a player wins a game or set when I noticed something was going wrong.
Perhaps someone can help me spot the error in my code, I've been staring at it so long I probably wouldn't notice it even if it was staring me in the face.
Thanks for your time!
int results[14] = {0}; //Stores counters for each of 14 possible outcomes
for (int gameNum = 0; gameNum < 10000; gameNum++)
{
// Calculate results of 10,000 full world championship finals.
// Final is thirteen sets - each set is a best of 5 games.
int sidSetWins = 0, joeSetWins = 0;
int sidGWins = 0, joeGWins = 0;
do
{ // Play best of 13 sets
do
{ // Play best of 5 games
do
{ // Play single game
if (currTurn == sid)
{
Sid.throw_quick_set(); // Throws a set of three
currTurn = joe;
}
else if (currTurn == joe)
{
Joe.throw_quick_set(); // Throws a set of three
currTurn = sid;
}
}
while ((Sid.score != 0) && (Joe.score != 0));
if (Sid.score == 0)
{
sidGWins++;
cout << "Sid wins game" << endl;
Sid.score = 501;
Joe.score = 501;
}
else if (Joe.score == 0)
{
joeGWins++;
cout << "Joe wins game" << endl;
Sid.score = 501;
Joe.score = 501;
}
Sleep(1000);
}
while ((joeGWins < 3) && (sidGWins < 3));
if (joeGWins == 3)
{
joeSetWins++;
cout << "Joe wins set" << endl;
sidGWins = 0;
joeGWins = 0; // Reset for each set
}
else if (sidGWins == 3)
{
sidSetWins++;
cout << "Sid wins set" << endl;
sidGWins = 0;
joeGWins = 0; // Reset for each set
}
Sleep(1000);
}
while ((sidSetWins < 7) && (joeSetWins < 7));
if ((gameNum%1000) == 0)
{
cout << "SidSets " << sidSetWins << endl;
cout << "JoeSets " << joeSetWins << endl;
sidSetWins = 0;
joeSetWins = 0; // Reset for each match
}
EDIT: Here's the class method throw_live_set(); also, if that helps you.
int Darts_501::throw_quick_set()
{ // Used for quick games with no commentary - for average calculations
darts = 0;
int startScore = score;
quickGame = true; // Disable commentary
do
{
if (score>=100)
curr_state = fast;
else if (score > 50)
curr_state = slow;
else if ((score <= 50) && (score > 1))
curr_state = focus;
else if ((score == 1) || (score < 0))
curr_state = bust;
else
curr_state = out;
switch (curr_state)
{
case fast:
score -= throw_treble(20, trebPerc);
break;
case slow:
score -= throw_single(20);
break;
case focus:
if (score == 50)
score -= throw_bull(bullPerc);
else if ((score != 0) && (score%2 == 0))
score -= throw_double(score/2); // If score is even, look to hit the double of half that value
else if ((score != 0) && (score%2 != 0))
score -= throw_setup(score); // If odd, look to set-up a good double score (strategic)
break;
case bust:
score = startScore;
break;
case out: score = 0;;
}
darts++;
} while ((darts < 3) &&
(curr_state != out) &&
(curr_state != bust));
return 0;
}