5
Oct
0

[C++] MinMax algorithm with noughts and crosses game (understand min-max heuristic)

Here is an example of min-max algorithm.

The code has been taken from:
Algorithms in a Nutshell, George T. Heineman, Gary Pollice, Stanley Selkow
L’algorithme min-max – SiteDuZeros (French tutorial website)

The Code

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <time.h>

// Board game dimensions
const int BoardWidth  = 4;
const int BoardHeight = 4;
const int OccurencesToWin = 3;
const int AlgorithmDeep = 6;

// Case & Win Types
enum Case {
    Empty = 0,
    Player1,
    Player2,
    Tie,
};

#define PrintScore
//#define PrintScore(X) X

/** Count number of lines
  * This function return the number of lines that are composed
  * by at least nbOccurences
    You can test it with:
    int board[BoardHeight][BoardWidth] = { {0,0,0,0} ,
                                           {0,0,0,0} ,
                                           {0,0,0,0} ,
                                           {1,0,0,0} };
    int player1Series = 0;
    int player2Series = 0;
    countLines(board, &player1Series, &player2Series, 2);
    printf("player1Series %d, player2Series %d \n", player1Series, player2Series);
  */

void countLines(const int board[BoardHeight][BoardWidth],int* const counterPlayer1, int* const counterPlayer2,
                const int nbOccurences){
    // init variables
     *counterPlayer1 = 0;
     *counterPlayer2 = 0;

     int verticalCounter[BoardWidth];
     memset(verticalCounter, 0, BoardWidth * sizeof(int));

     int topLeftBottomRight[BoardWidth];
     memset(topLeftBottomRight, 0, BoardWidth * sizeof(int));
     int topRightBottomLeft[BoardWidth];
     memset(topRightBottomLeft, 0, BoardWidth * sizeof(int));

     int tlbrOffset = 0;
     int trblOffset = 0;

     // go on the board in the height way
     for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
         if(topLeftBottomRight[tlbrOffset%BoardWidth] >= nbOccurences){
             if(board[idxHeight-1][BoardWidth-1] == Player1){
                 ++(*counterPlayer1);
             }
             else if(board[idxHeight-1][BoardWidth-1] == Player2){
                ++(*counterPlayer2);
             }
         }

         if(topRightBottomLeft[(trblOffset + BoardHeight - 1)%BoardWidth] >= nbOccurences){
             if(board[idxHeight-1][0] == Player1){
                 ++(*counterPlayer1);
             }
             else if(board[idxHeight-1][0] == Player2){
                ++(*counterPlayer2);
             }
         }

        // reset diagonal count
         topLeftBottomRight[(tlbrOffset)%BoardWidth] = 1;
         topRightBottomLeft[(trblOffset + BoardHeight - 1)%BoardWidth] = 1;

         int horizontalCounter = 1;
         // Go on the width way
         for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
             // Horitontal line
             if(idxWidth && board[idxHeight][idxWidth-1] == board[idxHeight][idxWidth]){
                 ++horizontalCounter;
             }
             else {
                 if(horizontalCounter >= nbOccurences){
                     if(board[idxHeight][idxWidth-1] == Player1){
                         ++(*counterPlayer1);
                     }
                     else if(board[idxHeight][idxWidth-1] == Player2){
                        ++(*counterPlayer2);
                     }
                 }
                 horizontalCounter = 1;
             }
             // Vertical line
             if(idxHeight && board[idxHeight-1][idxWidth] == board[idxHeight][idxWidth]){
                 ++verticalCounter[idxWidth];
             }
             else {
                 if(verticalCounter[idxWidth] >= nbOccurences){
                     if(board[idxHeight-1][idxWidth] == Player1){
                         ++(*counterPlayer1);
                     }
                     else if(board[idxHeight-1][idxWidth] == Player2){
                         ++(*counterPlayer2);
                     }
                 }
                 verticalCounter[idxWidth] = 1;
             }
             // Top Left Bottom Right
             if(idxHeight && idxWidth && board[idxHeight-1][idxWidth-1] == board[idxHeight][idxWidth]){
                 ++topLeftBottomRight[(idxWidth + tlbrOffset)%BoardWidth];
             }
             else {
                 if(topLeftBottomRight[(idxWidth + tlbrOffset)%BoardWidth] >= nbOccurences){
                     if(board[idxHeight-1][idxWidth-1] == Player1){
                         ++(*counterPlayer1);
                     }
                     else if(board[idxHeight-1][idxWidth-1] == Player2){
                        ++(*counterPlayer2);
                     }
                 }
                 topLeftBottomRight[(idxWidth + tlbrOffset)%BoardWidth] = 1;
             }
             // Top Right Bottom Left
             if(idxHeight && idxWidth != BoardWidth -1 && board[idxHeight-1][idxWidth+1] == board[idxHeight][idxWidth]){
                 ++topRightBottomLeft[(idxWidth + trblOffset)%BoardWidth];
             }
             else {
                 if(topRightBottomLeft[(idxWidth + trblOffset)%BoardWidth] >= nbOccurences ){
                     if(board[idxHeight-1][idxWidth+1] == Player1){
                         ++(*counterPlayer1);
                     }
                     else if(board[idxHeight-1][idxWidth+1] == Player2){
                        ++(*counterPlayer2);
                     }
                 }
                 topRightBottomLeft[(idxWidth + trblOffset)%BoardWidth] = 1;
             }
         }

         if(horizontalCounter >= nbOccurences){
             if(board[idxHeight][BoardWidth-1] == Player1){
                 ++(*counterPlayer1);
             }
             else if(board[idxHeight][BoardWidth-1] == Player2){
                ++(*counterPlayer2);
             }
         }

         tlbrOffset = ((tlbrOffset - 1) + BoardWidth) % BoardWidth;
         trblOffset = (trblOffset + 1) % BoardWidth;
     }
     // Count last line
     const int heightLimit = BoardHeight - 1;

     for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
         if(verticalCounter[idxWidth] >= nbOccurences){
             if(board[heightLimit][idxWidth] == Player1){
                 ++(*counterPlayer1);
             }
             else if(board[heightLimit][idxWidth] == Player2){
                ++(*counterPlayer2);
             }
         }

         if(topLeftBottomRight[(1 + idxWidth + tlbrOffset)%BoardWidth] >= nbOccurences ){
             if(board[heightLimit][idxWidth] == Player1){
                ++(*counterPlayer1);
             }
             else if(board[heightLimit][idxWidth] == Player2){
                ++(*counterPlayer2);
             }
         }

         if(topRightBottomLeft[(idxWidth + trblOffset - 1 + BoardWidth)%BoardWidth] >= nbOccurences ){
             if(board[heightLimit][idxWidth] == Player1){
                ++(*counterPlayer1);
             }
             else if(board[heightLimit][idxWidth] == Player2){
                ++(*counterPlayer2);
             }
         }
     }
}

/** Is the game over (tie or one player won)
  */
Case gameIsOver(const int board[BoardHeight][BoardWidth]){
     int player1Won = 0;
     int player2Won = 0;

     countLines(board, &player1Won, &player2Won, OccurencesToWin);

     if(player1Won){
          return Player1;
     }
     else if(player2Won){
          return Player2;
     }
     else{
          //Si le jeu n'est pas fini et que personne n'a gagné, on renvoie 0
         for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
              for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
                    if(board[idxHeight][idxWidth] == Empty){
                         return Empty;
                    }
               }
          }
     }
     //Si le jeu est fini et que personne n'a gagné, on renvoie 3
     return Tie;
}

// Eval a board to give a score (to compare two board for example)
// the higher => the better for the computer
int eval(const Case me, const int board[BoardHeight][BoardWidth]){
     int casesCounterMe = 0;
     int casesCounterOther = 0;
     //Count the number of pions
     for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
          for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
               if(board[idxHeight][idxWidth] == me){
                    ++casesCounterMe;
               }
               else if(board[idxHeight][idxWidth] != Empty){
                   ++casesCounterOther;
              }
          }
     }

     const Case winner = gameIsOver(board);
     if( winner == me){
            return 1000 - casesCounterMe;
     }
     if( winner == Tie){
         return 0;
     }
     if( winner != Empty){
            return casesCounterOther - 1000;
     }

     //Small computation to eval the bord
    int player1Series = 0;
    int player2Series = 0;

    countLines(board, &player1Series, &player2Series, 2);

    if( me == Player1){
        return player1Series - player2Series;
    }
    else{
        return player2Series - player1Series;
    }
}

// Max function has to know Min
int Min(const Case me, int board[BoardHeight][BoardWidth],const int deeper);

// Max function, simulates other play
int Max(const Case me, int board[BoardHeight][BoardWidth],const int deeper){
    const Case other = (me == Player1? Player2 : Player1);
     if(deeper == 0 || gameIsOver(board) != Empty ){
          return eval(other, board);
     }

     int maxScore = INT_MIN;

     for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
          for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
                if(board[idxHeight][idxWidth] == Empty){
                      board[idxHeight][idxWidth] = other;
                      const int score = Min( me, board, deeper-1 );

                      if(score > maxScore  || ( (score == maxScore) && (rand()%2 == 0) ) ){
                            maxScore = score;
                      }
                      board[idxHeight][idxWidth] = Empty;
                }
          }
     }

     return maxScore;
}

// Min function, simulates computer play
int Min(const Case me, int board[BoardHeight][BoardWidth],const int deeper){
    if(deeper == 0 || gameIsOver(board) != Empty ){
         return eval(me, board);
    }

     int minScore = INT_MAX;

     for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
          for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
                if(board[idxHeight][idxWidth] == Empty){
                      board[idxHeight][idxWidth] = me;
                      const int score = Max(me, board, deeper-1 );

                      if(score < minScore  || ( (score == minScore) && (rand()%2 == 0) )  ){
                            minScore = score;
                      }

                      board[idxHeight][idxWidth] = Empty;
                }
          }
     }

     return minScore;

}

// Ask the computer to play
void IAPlays(const Case me, int board[BoardHeight][BoardWidth], const int deeper){
     int maxScore = INT_MIN;
     int maxWidthPosition  = -1;
     int maxHeightPosition = -1;

     //printf("Current Stat:\n");

     for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
        for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
                if(board[idxHeight][idxWidth] == Empty){

                      board[idxHeight][idxWidth] = me;
                      const int score = Min( me, board, deeper - 1 );
                      //printf("|%5d", score);

                      if(score > maxScore){
                            maxScore = score;
                            maxWidthPosition = idxWidth;
                            maxHeightPosition = idxHeight;
                      }

                      board[idxHeight][idxWidth] = Empty;
                }
                //else printf("|%5d", 0);
          }
          //printf("|\n");
     }

     board[maxHeightPosition][maxWidthPosition] = me;
}

// Print the board
void PrintBoard(const int board[BoardHeight][BoardWidth]){
    printf("Board:\n");

    for(int idxHeight = 0 ; idxHeight < BoardHeight ; ++idxHeight){
        printf("\t");
        for(int idxWidth = 0 ; idxWidth < BoardWidth ; ++idxWidth){
            switch(board[idxHeight][idxWidth]){
            case Player1: printf("|O");
            break;
            case Player2: printf("|X");
            break;
            case Empty: printf("| ");
            case Tie:;
            }
        }
        printf("|\n");
    }
}

int main(){
    int board[BoardHeight][BoardWidth];
    memset(board, Empty, sizeof(int) * BoardWidth * BoardHeight);

    srand(time(NULL));
    board[int((float(rand())/RAND_MAX)*BoardHeight)][int((float(rand())/RAND_MAX)*BoardWidth)] = Player2;
    Case currentPlayer = Player1;
    while( gameIsOver(board) == Empty ){
        IAPlays(currentPlayer, board, AlgorithmDeep);

        switch(currentPlayer){
        case Player1: currentPlayer = Player2;
        break;
        case Player2: currentPlayer = Player1;
        break;
        case Empty:;
        case Tie:;
        }

        PrintBoard(board);
    }

    switch(gameIsOver(board)){
    case Player1: printf("Player 1 wins\n");
    break;
    case Player2: printf("Player 2 wins\n");
    break;
    case Empty:;
    case Tie: printf("Tie!\n");;
    }

    return 0;
}

Output

Board:
        | | | |
        |X| |O|
        | | | |
Board:
        | |X| |
        |X| |O|
        | | | |
Board:
        |O|X| |
        |X| |O|
        | | | |
Board:
        |O|X|X|
        |X| |O|
        | | | |
Board:
        |O|X|X|
        |X| |O|
        |O| | |
Board:
        |O|X|X|
        |X| |O|
        |O| |X|
Board:
        |O|X|X|
        |X|O|O|
        |O| |X|
Board:
        |O|X|X|
        |X|O|O|
        |O|X|X|
Tie!
Board:
        |O| | | |
        | |X| | |
        | | | | |
        | | | | |
Board:
        |O| | |X|
        | |X| | |
        | | | | |
        | | | | |
Board:
        |O| | |X|
        | |X| |O|
        | | | | |
        | | | | |
Board:
        |O| | |X|
        | |X| |O|
        | | | |X|
        | | | | |
Board:
        |O| | |X|
        | |X|O|O|
        | | | |X|
        | | | | |
Board:
        |O| | |X|
        |X|X|O|O|
        | | | |X|
        | | | | |
Board:
        |O| | |X|
        |X|X|O|O|
        |O| | |X|
        | | | | |
Board:
        |O| | |X|
        |X|X|O|O|
        |O| | |X|
        |X| | | |
Board:
        |O| | |X|
        |X|X|O|O|
        |O| | |X|
        |X| | |O|
Board:
        |O|X| |X|
        |X|X|O|O|
        |O| | |X|
        |X| | |O|
Board:
        |O|X|O|X|
        |X|X|O|O|
        |O| | |X|
        |X| | |O|
Board:
        |O|X|O|X|
        |X|X|O|O|
        |O|X| |X|
        |X| | |O|
Player 2 wins
Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.

Comments are closed.

Celadon theme by the Themes Boutique