Negamax implementation doesn't appear to work with tic-tac-toe - c++

I've implemented Negamax as it can be found on wikipedia, which includes alpha/beta pruning.
However, it seems to favor a losing move, which is an invalid result to my knowledge.
The game is Tic-Tac-Toe, I've abstracted most of the game play so it should be rather easy to spot an error within the algorithm.
#include <list>
#include <climits>
#include <iostream>
//#define DEBUG 1
using namespace std;
struct Move {
int row, col;
Move(int row, int col) : row(row), col(col) { }
Move(const Move& m) { row = m.row; col = m.col; }
};
struct Board {
char player;
char opponent;
char board[3][3];
Board() { }
void read(istream& stream) {
stream >> player;
opponent = player == 'X' ? 'O' : 'X';
for(int row = 0; row < 3; row++) {
for(int col = 0; col < 3; col++) {
char playa;
stream >> playa;
board[row][col] = playa == '_' ? 0 : playa == player ? 1 : -1;
}
}
}
void print(ostream& stream) {
for(int row = 0; row < 3; row++) {
for(int col = 0; col < 3; col++) {
switch(board[row][col]) {
case -1:
stream << opponent;
break;
case 0:
stream << '_';
break;
case 1:
stream << player;
break;
}
}
stream << endl;
}
}
void do_move(const Move& move, int player) {
board[move.row][move.col] = player;
}
void undo_move(const Move& move) {
board[move.row][move.col] = 0;
}
bool isWon() {
if (board[0][0] != 0) {
if (board[0][0] == board[0][1] &&
board[0][1] == board[0][2])
return true;
if (board[0][0] == board[1][0] &&
board[1][0] == board[2][0])
return true;
}
if (board[2][2] != 0) {
if (board[2][0] == board[2][1] &&
board[2][1] == board[2][2])
return true;
if (board[0][2] == board[1][2] &&
board[1][2] == board[2][2])
return true;
}
if (board[1][1] != 0) {
if (board[0][1] == board[1][1] &&
board[1][1] == board[2][1])
return true;
if (board[1][0] == board[1][1] &&
board[1][1] == board[1][2])
return true;
if (board[0][0] == board[1][1] &&
board[1][1] == board[2][2])
return true;
if (board[0][2] == board [1][1] &&
board[1][1] == board[2][0])
return true;
}
return false;
}
list<Move> getMoves() {
list<Move> moveList;
for(int row = 0; row < 3; row++)
for(int col = 0; col < 3; col++)
if (board[row][col] == 0)
moveList.push_back(Move(row, col));
return moveList;
}
};
ostream& operator<< (ostream& stream, Board& board) {
board.print(stream);
return stream;
}
istream& operator>> (istream& stream, Board& board) {
board.read(stream);
return stream;
}
int evaluate(Board& board) {
int score = board.isWon() ? 100 : 0;
for(int row = 0; row < 3; row++)
for(int col = 0; col < 3; col++)
if (board.board[row][col] == 0)
score += 1;
return score;
}
int negamax(Board& board, int depth, int player, int alpha, int beta) {
if (board.isWon() || depth <= 0) {
#if DEBUG > 1
cout << "Found winner board at depth " << depth << endl;
cout << board << endl;
#endif
return player * evaluate(board);
}
list<Move> allMoves = board.getMoves();
if (allMoves.size() == 0)
return player * evaluate(board);
for(list<Move>::iterator it = allMoves.begin(); it != allMoves.end(); it++) {
board.do_move(*it, -player);
int val = -negamax(board, depth - 1, -player, -beta, -alpha);
board.undo_move(*it);
if (val >= beta)
return val;
if (val > alpha)
alpha = val;
}
return alpha;
}
void nextMove(Board& board) {
list<Move> allMoves = board.getMoves();
Move* bestMove = NULL;
int bestScore = INT_MIN;
for(list<Move>::iterator it = allMoves.begin(); it != allMoves.end(); it++) {
board.do_move(*it, 1);
int score = -negamax(board, 100, 1, INT_MIN + 1, INT_MAX);
board.undo_move(*it);
#if DEBUG
cout << it->row << ' ' << it->col << " = " << score << endl;
#endif
if (score > bestScore) {
bestMove = &*it;
bestScore = score;
}
}
if (!bestMove)
return;
cout << bestMove->row << ' ' << bestMove->col << endl;
#if DEBUG
board.do_move(*bestMove, 1);
cout << board;
#endif
}
int main() {
Board board;
cin >> board;
#if DEBUG
cout << "Starting board:" << endl;
cout << board;
#endif
nextMove(board);
return 0;
}
Giving this input:
O
X__
___
___
The algorithm chooses to place a piece at 0, 1, causing a guaranteed loss, do to this trap(nothing can be done to win or end in a draw):
XO_
X__
___
I'm pretty sure the game implementation is correct, but the algorithm should be aswell.
EDIT: Updated evaluate and nextMove.
EDIT2: Fixed first problem, there still seem to be bugs though

Your evaluate function counts the empty spaces, and does not recognize a winning board.
EDIT:
There's also a relatively minor problem in nextMove. The line should be
int score = -negamax(board, 0, -1, INT_MIN + 1, INT_MAX);
Fix that (and evaluate), and the code chooses the right move.
EDIT:
This fixes it:
if (board.isWon() || depth <= 0) {
#if DEBUG > 1
cout << "Found winner board at depth " << depth << endl;
cout << board << endl;
#endif
return -100;
}
Almost all of these problems stem from not being clear about the meaning of score. It is from the point of view of player. If negamax is evaluating the position of player 1, and player 1 cannot win, the score should be low (e.g. -100); if negamax is evaluating the position of player -1, and player -1 cannot win, the score should be low (e.g. -100). If evaluate() can't distinguish the players, then returning a score of player * evaluate(board) is just wrong.

isWon returns true for both a win or a loss of the player. That can't be helping.

There seems to be something funny with the use of player.
Your toplevel loop calls "board.do_move(*it, 1);" then calls negamax with player=1.
Then negamax will call "board.do_move(*it, player);", so it looks like the first player is effectively getting 2 moves.

Related

C++ Tic tac toe minimax

I am trying to create a c++ unbeatable tic tac toe AI. after watching several videos on the topic i thought I had it all figured out. An error pops up on the screen saying "Expression: vector subscript out of range". I believe the error is coming from the availableMoves() function. however I do not know why.
The game itself works fine. any help would be appreciated.
#include <iostream>
#include <vector>
#include <ctime>
bool in(std::vector<int> v, int element)
{
for (int i = 0; i < v.size(); i++)
{
if (element == v[i])
{
return true;
}
}
return false;
}
class Board
{
private:
char board[3][3] = { {'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '9'} };
public:
void displayBoard()
{
std::cout << "___________________" << std::endl;
for (int i = 0; i < 3; i++)
{
std::cout << "| ";
for (int j = 0; j < 3; j++)
{
std::cout << board[i][j] << " | ";
}
std::cout << std::endl;
}
std::cout << "___________________" << std::endl;
}
std::vector<int> availableMoves()
{
std::vector<int> moves;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (board[i][j] != 'X' && board[i][j] != 'O')
{
moves.push_back(i * 3 + j);
}
}
}
return moves;
}
void move(int choice, char mark)
{
int y = choice / 3;
int x = choice - y * 3;
board[y][x] = mark;
}
void revert(int choice)
{
int y = choice / 3;
int x = choice - y * 3;
board[y][x] = (char)choice + 48;
}
int checkWin()
{
for (int i = 0; i < 3; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2])
{
if (board[i][0] == 'X')
{
return 1;
}
else if (board[i][0] == 'O')
{
return -1;
}
}
}
for (int i = 0; i < 3; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i])
{
if (board[0][i] == 'X')
{
return 1;
}
else if (board[0][i] == 'O')
{
return -1;
}
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2])
{
if (board[0][0] == 'X')
{
return 1;
}
else if (board[0][0] == 'O')
{
return -1;
}
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0])
{
if (board[0][2] == 'X')
{
return 1;
}
else if (board[0][2] == 'O')
{
return -1;
}
}
return 0;
}
int evaluate()
{
return (checkWin() * -1) * (availableMoves().size() + 1);
}
Board& operator=(Board& b)
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
board[i][j] = b.board[i][j];
}
}
return (*this);
}
};
class TicTacToe
{
private:
Board board;
int turn;
int searches = 0;
public:
TicTacToe()
{
std::srand(time(0));
turn = std::rand() % 2;
}
int minimax(int depth, Board curBoard, bool is_max)
{
searches++;
if (depth == 0 || curBoard.checkWin() != 0)
{
return board.evaluate();
}
if (is_max)
{
int max_eval = -2147483647;
for (int i = 0; i < curBoard.availableMoves().size(); i++)
{
curBoard.move(curBoard.availableMoves()[i], 'O');
depth -= 1;
int eval = minimax(depth, curBoard, false);
curBoard.revert(curBoard.availableMoves()[i]);
if (eval > max_eval)
{
max_eval = eval;
}
}
return max_eval;
}
if (!is_max)
{
int min_eval = 2147483647;
for (int i = 0; i < curBoard.availableMoves().size(); i++)
{
curBoard.move(curBoard.availableMoves()[i], 'X');
depth -= 1;
int eval = minimax(depth, curBoard, true);
curBoard.revert(curBoard.availableMoves()[i]);
if (eval < min_eval)
{
min_eval = eval;
}
}
return min_eval;
}
}
void game()
{
while (board.checkWin() == 0 && board.availableMoves().size() != 0)
{
board.displayBoard();
if (turn % 2 == 0)
{
std::cout << std::endl;
int choice;
std::cout << "Enter Your Move: ";
std::cin >> choice;
choice -= 1;
while (!in(board.availableMoves(), choice))
{
std::cout << "Enter A Valid Move: ";
std::cin >> choice;
}
board.move(choice, 'X');
std::cout << std::endl;
turn++;
}
board.displayBoard();
if (board.checkWin() != 0)
{
break;
}
if (turn % 2 == 1)
{
int ai = minimax(9 - (turn % 2), board, true);
std::cout << searches;
std::cin.get();
turn++;
}
}
if (board.checkWin() == 1)
{
std::cout << "You Won" << std::endl;
}
else if (board.checkWin() == -1)
{
std::cout << "You Lost" << std::endl;
}
else
{
std::cout << "Tie" << std::endl;
}
std::cout << "Would You Like To Play Again Y/N: ";
char playAgain;
std::cin >> playAgain;
if (playAgain == 'Y')
{
Board newBoard;
board = newBoard;
game();
}
}
};
int main()
{
TicTacToe ticTacToe;
ticTacToe.game();
}
Do you know how to debug? If not, you should definitely learn this, it's pretty helpful. But here's some things I found out.
The problem is not in availableMoves(), but in minimax(), more precisely in line 215, where the program calls curBoard. revert(curBoard. availableMoves()[i]).
void revert(int choice)
{
int y = choice / 3;
int x = choice - y * 3;
board[y][x] = (char)choice + 48;
}
for (int i = 0; i < curBoard.availableMoves().size(); i++)
{
curBoard.move(curBoard.availableMoves()[i], 'X');
depth -= 1;
int eval = minimax(depth, curBoard, true);
curBoard.revert(curBoard.availableMoves()[i]);
if (eval < min_eval)
{
min_eval = eval;
}
}
The error happens in the function revert, but I am not sure why. Maybe availableMoves also returns something wrong. Variable i is permanently 0 in the for-loop. So it is possible that there is something wrong at position 0 of the vector moves, which revert cannot handle. Try debugging yourself, maybe you'll find the problem.

Snake game boundary checking

I am writing a simple console snake game,when the 'O' snake hits the wall '#' it's game over but for some reason it ends one before the wall from left to right,top and bottom works fine.
I tried debugging by printing y(I know should be the other way around not good design by me) and y displays it's position as 0 just before the wall so does that mean the wall is -1? but it can't possibly be -1 because I set i = 0 not -1,
so flustered as to whats going wrong also note my code is quite messy but I am pretty new and still learning good style
void draw(){
system("cls");
bool notFirst = false;
bool notLast = false;
for(int i = 0; i <= HEIGHT+2; i++){
for(int j = 0; j <= WIDTH+2; j++){
if(i > 0){
notFirst = false;
}
if(i < HEIGHT+2){
notLast = false;
}
if(i == 0 && j <= WIDTH-1){
cout << "#"; // top wall
notFirst = true;
}
if(i == HEIGHT+2 && j <= WIDTH-2){
cout << "#"; // bottom wall
notLast = true;
}
if(j == 0 && i != 0){
cout << "#"; // #
}
if(j == WIDTH-1){
cout << "#";
}
if(x == i && y == j){
if(x == 0 || x == HEIGHT+2){
gameOver = true;
}
if(y == 0){
gameOver = true;
}
cout << y; // DEBUG
cout << "O";
}
else if(fruitX == i && fruitY == j){
cout << "F";
}
else{
if(!notFirst && !notLast){
cout << " ";
}
}
}
cout << endl;
}
}
int main()
{
setUp();
while(!gameOver){
draw();
input();
logic();
}
}

C++ TicTacToe problems

I am trying to learn C++ and I have made litte tictactoe game but somethings wrong. I've tried to make the winner class both a void and a bool. But when I type in one coordinate it preforms the class. For making it simple you can only win if the 3 on top is O. Whats wrong?
SO if I input: 0 0 it says winner
Here's the code:
#include <iostream>
const int rows = 3;
const int elements = 3;
const char Ochar = 'O';
char board[rows][elements];
void Clear()
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < elements; j++)
{
board[i][j] = 0;
}
}
}
void Show()
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < elements; j++)
{
std::cout << " " << board[i][j] << " |";
}
std::cout << std::endl;
std::cout << "------------" << std::endl;
}
}
bool PlayerAttack(int x, int y)
{
if (board[x][y] == 0)
{
board[x][y] = Ochar;
return true;
}
return false;
}
void Winner()
{
if (board[0][0], board[0][1], board[0][2] = 'O')
{
std::cout << "Winner";
}
}
int main()
{
Clear();
Show();
int pos1 = 0;
int pos2 = 0;
while (1)
{
std::cout << "Please input a coordinate: "; std::cin >> pos1 >> pos2; std::cout << std::endl;
PlayerAttack(pos1, pos2);
Show();
Winner();
}
}
This line does not do what you think it does
if (board[0][0], board[0][1], board[0][2] = 'O')
You'd have to do
if (board[0][0] == 'O' && board[0][1] == 'O' && board[0][2] == 'O')
To use your Winner function to break your loop
bool Winner()
{
// You'll obviously have to check more than just this row
if (board[0][0] == 'O' && board[0][1] == 'O' && board[0][2] == 'O')
{
std::cout << "Winner";
return true;
}
return false;
}
Then in main
int main()
{
Clear();
Show();
int pos1 = 0;
int pos2 = 0;
bool winner = false;
while (!winner)
{
std::cout << "Please input a coordinate: "; std::cin >> pos1 >> pos2; std::cout << std::endl;
PlayerAttack(pos1, pos2);
Show();
winner = Winner(); // Use the returned bool
}
}

Tic tac toe game code

Good evening everyone, could anyone point me in the direction I need to go to get this code for a simple game of tic tac toe operational? I feel it is very close but I cannot get it to behave properly. Also, what could I use instead of break commands under the drawBoard command? Thanks in advance..
#include <iostream>
// This program allows user(s) to play a game of tic-tac-toe.
using namespace std;
// constants
const char SIGIL[3] = { '.', 'X', 'O' };
// prototypes
int winner(int board[3][3]);
void drawBoard(int board[3][3]);
bool isMoveLegal(int board[3][3]);
bool isGameOver(int board[3][3]);
// return 1 if player 1 'X' has won
// return 2 if player 2 'O' has won
// return 0 if neither player has won
int winner(int board[3][3]){
int win = 0;
// Checks for:
// X X X
for (int i = 0; i < 3; i++)
if (board[i][0] == board[i][1] && board[i][1] == board[i][2])
win = board[i][0];
// Checks for:
// X
// X
// X
for (int i = 0; i < 3; i++)
if (board[0][i] == board[1][i] && board[1][i] == board[2][i])
win = board[0][i];
// Checks for:
// X X
// X or X
// X X
if ((board[0][0] == board[1][1] && board[1][1] == board[2][2]) ||
(board[0][2] == board[1][1] && board[1][1] == board[2][0]))
win = board[1][1];
return win;
}
// using this board as a guide
// draw the board using "." for empty squares
// or 'X' or 'O' for player 1 or player 2
void drawBoard(int board[3][3]){
cout << " 0 1 2" << endl;
for (int i = 0; i < 3; i++){
cout << i ;
for (int j = 0; j < 3; j++){
cout.width(3);
switch (board[i][j])
{
case 0:{
cout << ".";
}break; case 1:{
cout << "X";
}break;
case 2:{
cout << "0";
}break;
default:
break;
}
} cout << endl;
}
}
// return false if row or column are out of bounds
// or if that spot on the board is already taken
// otherwise return true
bool isMoveLegal(int board[3][3], int row, int column){
if (row >= 0 && row <= 3 && column <= 3 && column >= 0) {
if (board[row][column] == 0)
return true;
else return false;
}
return false;
}
// if any player has three in a row or if the board is full
// return true otherwise return false
bool isGameOver(int board[3][3]){
if (winner(board) == 1 || winner(board) == 2) return true;
for (int r = 0; r <= 2; r++)
for (int c = 0; c <= 2; c++)
if (board[r][c] == 0)
return false;
return true;
}
int main(){
int board[3][3] = { { 0 } }; // 0 for empty square, 1 or 2 for taken squares
int player = 1;
int row, column, result;
bool legalMove;
// starting board
drawBoard(board);
while (!isGameOver(board)){
cout << "Player " << player << "(" << SIGIL[player] << "), your move?";
cin >> row >> column;
legalMove = isMoveLegal(board, row, column);
while (!legalMove){
cout << "Player " << player << "(" << SIGIL[player] << "), your move?";
cin >> row >> column;
legalMove = isMoveLegal(board, row, column);
}
board[row][column] = player;
drawBoard(board);
player = 3 - player;
}
// game over
result = winner(board);
if (result == 0){
cout << "Tie Game" << endl;
}
else {
cout << "Player " << result << "(" << SIGIL[result] << ") wins!" << endl;
}
return 0;
}
This piece of code will go out of bounds:
bool isMoveLegal(int board[3][3], int row, int column){
if (row >= 0 && row <= 3 && column <= 3 && column >= 0) {
if (board[row][column] == 0)
return true;
else return false;
}
Remember that arrays are 0-indexed, so it should be:
bool isMoveLegal(int board[3][3], int row, int column){
if (row >= 0 && row < 3 && column < 3 && column >= 0) {
if (board[row][column] == 0)
return true;
else return false;
}
Your break statements in drawBoard are for the switch statement, not a loop. They are okay, however, you should use board[i][j] as an index into SIGIL:
// constants
const char SIGIL[3] = { '.', 'X', 'O' };
// ...
void drawBoard(int board[3][3])
{
cout << " 0 1 2\n";
for (int i = 0; i < 3; ++i)
{
cout << i ;
for (int j = 0; j < 3; ++j)
{
cout << SIGIL[board[i][j]] << " ";
}
cout << endl;;
}
}

Tic Tac Toe Programming [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I am pretty new in C++ programming. I am coding for Tic Tac Toe game to understand how its algorithm works. My question is about I have two functions. One of them is clearBoard function and the other one is drawBoard function. I got little help about them but I couldnt figure it out the way those functions works. Can anyone simply explain me what is exactly what? I have been trying to solve it and understand it but I think I got more confused. It might be a piece of cake but I really want to understand what's exactly going on. Thanks for your time and attention...
Here clearBoard function:
void clearBoard(int board[])
{
int i;
for(i = 0; i < board_size; i++) {
board[i] = -i - 1;
}
}
And here is my drawBoard function.
void drawBoard(int board[])
{
int i, j;
int n = 0;
for(i = 0; i <= 6; i = i+3) {
for(j = 0; j < 3; ++j) {
if(board[i + j] == 2)
cout << "|X|";
else if(board[i + j] == 1)
cout << "|O|";
else
cout << "|" << n << "|";
n = n+1;
}
cout << endl;
}
}
This is my main.cpp file. I just wanted to post my entire work at least it will be easier to see complete picture.
#include <iostream>
#include <ctime>
#include<cstdlib>
#include "Define.h"
using namespace std;
int main()
{
int board[board_size];
int turn = 0;
int p = 0;
int move = 10;
srand(time(NULL));
clearBoard(board);
cout << "This turn is randomly chosen!" << endl;
p = random(2) + 1;
cout << "The first move goes to Player: " << p << endl;
turn = p;
do {
if(p== 2 && 2 == turn)
{
drawBoard(board);
move = getPlayerMove(turn);
}
else
move = makeRandMove(turn);
} while(!isMoveValid(board, move));
board[move] = turn;
while(!isWin(board, move)){
drawBoard(board);
if(2 == turn)
turn = 1;
else
turn = 2;
do {
if(2 == turn)
move = getPlayerMove(turn);
else
move = makeRandMove(turn);
} while(!isMoveValid(board, move));
board[move] = turn;
}
drawBoard(board);
cout << "Player " << turn << " wins." << endl;
return 0;
}
And this is the functions.cpp file that has the functions that I was talking about above.
#include <iostream>
#include<cstdlib>
using namespace std;
const int board_size = 9;
void clearBoard(int board[])
{
int i;
for(i = 0; i < board_size; i++) {
board[i] = -i - 1;
}
}
int random(int x)
{
return rand() % x;
}
void drawBoard(int board[])
{
int i, j;
int n = 0;
for(i = 0; i <= 6; i = i+3) {
for(j = 0; j < 3; ++j) {
if(board[i + j] == 2)
cout << "|X|";
else if(board[i + j] == 1)
cout << "|O|";
else
cout << "|" << n << "|";
n = n+1;
}
cout << endl;
}
}
int getPlayerMove(int player)
{
int move;
cout << "Player " << player << " enter move: ";
cin >> move;
return move;
}
int makeRandMove(int player)
{
cout << "Computer (player " << player << ") moving." << endl;
return rand() % board_size;
}
bool isMoveValid(int board[], int move)
{
if(board[move] < 0)
return true;
return false;
}
bool isWin(int board[], int move)
{
if((board[0] == board[1] && board[0] == board[2]) ||
(board[3] == board[4] && board[3] == board[5]) ||
(board[6] == board[7] && board[6] == board[8]) ||
(board[0] == board[3] && board[0] == board[6]) ||
(board[1] == board[4] && board[1] == board[7]) ||
(board[2] == board[5] && board[2] == board[8]) ||
(board[0] == board[4] && board[0] == board[8]) ||
(board[2] == board[4] && board[2] == board[6]))
return true;
return false;
}
And here is my define.h header file has all the protypes...
#ifndef formula
#define formula
const int board_size = 9;
int random(int x);
void clearBoard(int board[]);
void drawBoard(int board[]);
int getPlayerMove(int player);
int makeRandMove(int player);
bool isMoveValid(int boardp[], int move);
bool isWin(int board[], int move);
#endif
The key to understanding the two board functions you are having difficulty with, clearBoard and drawBoard, you need to understand the data structure that you're storing your game board in. This is, in fact, a pretty simple example. Your board is defined as an array of 9 items:
int board[board_size];
...
const int board_size = 9;
The best way to think about this is if you take your 3 x 3 tic-tac-toe game board, and lay each row side by side. So the first three elements are the first row, the next three are the second row, and the last three are the final row.
The drawBoard function is the more complex of the two functions, but if you understand what the array is and what it represents, it makes this function much more understandable. The outer for loop is looping through each "row" of the gameboard, while the inner for loop loops through each element in that row.