How can i extract my best move from Min Max in TicTacToe? - artificial-intelligence

int minmax(Board game, int depth)
{
if (game.IsFinished() || depth < 0)
return game.Score(game.Turn);
int alpha = int.MinValue + 1;
foreach (Point move in game.Generate_Moves())
{
Board currentBoard = game;
currentBoard.Do_Move(move);
alpha = max(alpha, -minmax(currentBoard, depth-1));
currentBoard.Undo_Move(move);
}
return alpha;
}
The thing is that this little function tells me if the game is a win, a lose or a draw, but how can i get the move that will led me to a win? My Point class is a simple Class With 2 coordinates X, Y and i want to get the answer as a point so i can latter say something like game.Do_Move(myPoint).
In case some functions aren't obvious:
game.IsFinished() - returns true if win/lose/draw else otherwise
game.Score(turn) - returns -1/0/1 in case is a lose/draw/win for the player with the next move
game.Generate_Moves() - returns a List with available moves
game.Do_Move() - void that applies the move to game
game.Undo_Move() - talks for itself

It would be enough if the minimax function which gets called on the root node of the game tree returns both, the choosen move and the score. For all other nodes of the game tree, the function needs only to return the score. Thus the usual way is to implement two slightly different minimax functions – Look at Note #2 in the description to this NegaMax Framework.
Applied to your minimax interface you would have following additional function:
int minimaxWithMove(Board game, int depth, Point& choosen)
{
assert (!game.IsFinished() && depth > 0); // not possible at root node
int alpha = int.MinValue + 1;
foreach (Point move in game.Generate_Moves())
{
Board currentBoard = game;
currentBoard.Do_Move(move);
int score = -minmax(currentBoard, depth-1);
if (score > alpha)
{
alpha = score;
choosen = move;
}
}
return alpha;
}
Note that I have removed the call to Undo_Move as it is not needed because you make a copy of game in each iteration.

You need to apply the minimax theorem.
You basically have to make a game tree, where each node in the tree is a board position, and each child is the result of a legal move. The leaf nodes (where the game is ended) will have scores according to game.score(), and one player is trying to pick moves down a path leading to a high score, while the other is trying to pick moves that force a low score. The theorem will help you see how to apply that idea, rigorously.

Related

How to improve Alpha-beta pruning performance

Here is my code for gomoku AI. So now my AI is currently run over 5 seconds but the time limit is 5 seconds. I am trying to improve the performance so I try move ordering but it seems not works. I calculate the score first in getChildStates(int player) function and then sort the vector into a descending order. But it just not work. Can some body help me?
Also, my depth is two. transpotation table seems not help, so I haven't try it.
int minimax(int depth, GameState state, bool maximizingPlayer, int alpha, int beta)
{
if (depth == 2)
return state.score;
if (maximizingPlayer)
{
vector<GameState> children = state.getChildStates(1);
sort(children.begin(), children.end(), greaterA());
int best = MIN;
for (auto& value : children) {
int val = minimax(depth + 1, value,
false, alpha, beta);
int oldBest = best;
best = max(best, val);
alpha = max(alpha, best);
if (depth == 0 && oldBest != best){
bestMoveX = value.lastMove.x;
bestMoveY = value.lastMove.y;
}
// Alpha Beta Pruning
if (beta <= alpha)
break;
}
return best;
}
else
{
vector<GameState> children = state.getChildStates(2);
sort(children.begin(), children.end(),greaterA());
int best = MAX;
// Recur for left and right children
for (auto& value : children) {
int val = minimax(depth + 1, value,
true, alpha, beta);
best = min(best, val);
beta = min(beta, best);
// Alpha Beta Pruning
if (beta <= alpha)
break;
}
return best;
}
}
I won't recommend sorting the game states to prioritize the states thereby enabling force move as per set timeout. Even with alpha-beta pruning, the minimax tree may be just too big. For reference you can have a look in the GNU Chess at github. Here are some options to reduce the best move search time:
1) Reduce depth of search.
2) Weed out redundant moves from the possible moves.
3) Use multi-threading in the first ply to gain speed
4) Allow quiescence search mode, so that minimax tree branches could continue generating in the background when the human opponent is still thinking.
5) Instead of generating the minimax tree for every move, you can think about reusable minimax tree where you only pruned moves already made and only continue generating one ply every iteration ( instead of the whole tree, see this article ).

Computing a move score in a Minimax Tree of a certain depth

I've implemented a Chess game in C, with the following structs:
move - which represents a move from (a,b) to (c,d) on a char board[8][8] (Chess board)
moves - which is a linked list of moves with head and tail.
Variables:
playing_color is 'W' or 'B'.
minimax_depth is a minimax depth that was set before.
Here is my code of the Minimax function with alpha-beta pruning and the getMoveScore function which should return the score of the move in Minimax Tree of a certain minimax_depth that was set before.
As well I'm using the getBestMoves function which I will also list here, it basicly find the best moves during the Minimax algorithm and saves them into a global variable so that I will be able to use them later.
I must add that all the functions that are listed within the three functions that I will add here are working properly and were tested, so the problem is either a logic problem of the alphabetaMax algorithm or the implementation of
getBestMoves/getMoveScore.
The problem mainly is that when I get my best moves at depth N (which are also not computed right somewhy) and then check their score on the same depth with getMoveScore function, I'm getting different scores that don't match the score of those actual best moves. I've spent hours on debugging this and couldn't see the error, I hope maybe anyone could give me a tip on finding the problem.
Here is the code:
/*
* Getting best possible moves for the playing color with the minimax algorithm
*/
moves* getBestMoves(char playing_color){
//Allocate memory for the best_moves which is a global variable to fill it in a minimax algorithm//
best_moves = calloc(1, sizeof(moves));
//Call an alpha-beta pruned minimax to compute the best moves//
alphabeta(playing_color, board, minimax_depth, INT_MIN, INT_MAX, 1);
return best_moves;
}
/*
* Getting the score of a given move for a current player
*/
int getMoveScore(char playing_color, move* curr_move){
//Allocate memory for best_moves although its not used so its just freed later//
best_moves = calloc(1, sizeof(moves));
int score;
char board_cpy[BOARD_SIZE][BOARD_SIZE];
//Copying a a current board and making a move on that board which score I want to compute//
boardCopy(board, board_cpy);
actualBoardUpdate(curr_move, board_cpy, playing_color);
//Calling the alphabeta Minimax now with the opposite color , a board after a given move and as a minimizing player, because basicly I made my move so its now the opponents turn and he is the minimizing player//
score = alphabeta(OppositeColor(playing_color), board_cpy, minimax_depth, INT_MIN, INT_MAX, 0);
freeMoves(best_moves->head);
free(best_moves);
return score;
}
/*
* Minimax function - finding the score of the best move possible from the input board
*/
int alphabeta(char playing_color, char curr_board[BOARD_SIZE][BOARD_SIZE], int depth,int alpha,int beta, int maximizing) {
if (depth == 0){
//If I'm at depth 0 I'm evaluating the current board with my scoring function//
return scoringFunc(curr_board, playing_color);
}
int score;
int max_score;
char board_cpy[BOARD_SIZE][BOARD_SIZE];
//I'm getting all the possible legal moves for the playing color//
moves * all_moves = getMoves(playing_color, curr_board);
move* curr_move = all_moves->head;
//If its terminating move I'm evaluating board as well, its separate from depth == 0 because only here I want to free memory//
if (curr_move == NULL){
free(all_moves);
return scoringFunc(curr_board,playing_color);
}
//If maximizing player is playing//
if (maximizing) {
score = INT_MIN;
max_score = score;
while (curr_move != NULL){
//Make the move and call alphabeta with the current board after the move for opposite color and !maximizing player//
boardCopy(curr_board, board_cpy);
actualBoardUpdate(curr_move, board_cpy, playing_color);
score = alphabeta(OppositeColor(playing_color), board_cpy, depth - 1,alpha,beta, !maximizing);
alpha = MAX(alpha, score);
if (beta <= alpha){
break;
}
//If I'm at the maximum depth I want to get current player best moves//
if (depth == minimax_depth){
move* best_move;
//If I found a move with a score that is bigger then the max score, I will free all previous moves and append him, and update the max_score//
if (score > max_score){
max_score = score;
freeMoves(best_moves->head);
free(best_moves);
best_moves = calloc(1, sizeof(moves));
best_move = copyMove(curr_move);
concatMoves(best_moves, best_move);
}
//If I have found a move with the same score and want to concatenate it to a list of best moves//
else if (score == max_score){
best_move = copyMove(curr_move);
concatMoves(best_moves, best_move);
}
}
//Move to the next move//
curr_move = curr_move->next;
}
freeMoves(all_moves->head);
free(all_moves);
return alpha;
}
else {
//The same as maximizing just for a minimizing player and I dont want to look for best moves here because I dont want to minimize my outcome//
score = INT_MAX;
while (curr_move != NULL){
boardCopy(curr_board, board_cpy);
actualBoardUpdate(curr_move, board_cpy, playing_color);
score = alphabeta(OppositeColor(playing_color), board_cpy, depth - 1,alpha,beta, !maximizing);
beta = MIN(beta, score);
if (beta <= alpha){
break;
}
curr_move = curr_move->next;
}
freeMoves(all_moves->head);
free(all_moves);
return beta;
}
}
As Eugene has pointed out-I'm adding an example here:
http://imageshack.com/a/img910/4643/fmQvlm.png
I'm currently the white player, i got only king-k and queen-q, the opposite color has king-K and rook-R. Obviously my best move here is to eat a rook or cause a check at least. Moves of the pieces are tested and they work fine. Although when i call get_best_moves function at depth 3, I'm getting lots of unnecessary moves and negative scores for them at that depth. Maybe now it's a little more clear. Thanks!
Without debugging your whole code, at least ONE of the problems is the fact that your scoreverification might work with a minimax algorithm, but not with a Alpha-Beta. Following problem:
The getMoveScore() function has to start with an open AB Window.
The getBestMoves() however call getMoveScore() with an already closed AB Window.
So in the case of getBestMoves, there can be branches pruned that are not being pruned in getMoveScore(), therefore the score not being exact, and thats the reason (or at least ONE of them) why these valued can differ.

animation from 3DS to OpenGL

I am trying to export a 3ds animation to OpenGL and I want to go to the next frame little by little. To do that I use 3ds file with 100 keys so if I do not make mistakes it is ok.
To run my animation I use the lib3ds_file_eval statement but it seems I am doing a mistake
Here is how I do that :
void animationTimer(int value) {
if (g_haltAnimation != 0) {
lib3ds_file_eval(g_scenes3DS[ANIMATED_KART_ID].lib3dsfile, g_currentFrame);
g_currentFrame = (g_currentFrame + 1) % g_scenes3DS[ANIMATED_KART_ID].lib3dsfile->frames;
glutTimerFunc(10, animationTimer, 0);
}
}
So it is quite simple. I put the lib3dsfile of my scene in parameter and the number of the next frame. And when I check the transformation matrix in nodes, it does not change and I can not find why.
I noticed that current_frame in lib3dsFile does not change too, I do not know if it is normal or not.
It is normal behaviour that current_frame in the file does not change. However the matrices of at least some nodes should change for a non-trivial animation.
Did you check the nodes by doing the following?
for (Lib3dsNode* p = g_scenes3DS[ANIMATED_KART_ID].lib3dsfile->nodes; p != 0; p = p->next )
{
// check p->matrix here
}
Make sure to check every matrix because some (most?) nodes probably won't move in a kart animation.

Print doesn't show in printed array although specified

I'm working a simple candy crush game for my year 1 assignment.
I am at this stage where I need to show my self-made simple marker( *box made of '|' and '_'* ) on the center of the board ( board[5][5] ) once the program is executed.
Here is the current code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//FUNCTION: Draw the Board
int drawBoard()
{
//Declare array size
int board[9][9];
//initialize variables
int rows, columns, randomNumber, flag;
//random number seed generator
srand(time(NULL));
for ( rows = 0 ; rows < 9 ; rows++ )
{
for ( columns = 0 ; columns < 9 ; columns++ )
{
flag = 0;
do
{
//generate random numbers from 2 - 8
randomNumber = rand() %7 + 2;
board[rows][columns] = randomNumber;
//Checks for 2 adjacent numbers.
if ( board[rows][columns] == board[rows - 1][columns] || board[rows][columns] == board[rows][columns - 1] )
{
flag = 0;
continue;
}
else
{
flag = 1;
printf( " %d ", board[rows][columns] );
}
} while ( flag == 0 );
}//end inner for-loop
printf("\n\n");
}//end outer for-loop
//call FUNCTION marker() to display marker around board[5][5]
marker( board[5][5] );
}//end FUNCTION drawBoard
//FUNCTION: Mark the surrounding of the number with "|" and "_" at board[5][5]
void marker( int a )
{
printf( " _ \n" );
printf( "|%c|\n", a );
printf( " _ \n" );
}
int main()
{
drawBoard();
}
At the end of function drawBoard(), I placed the code marker( board[5][5] ).
This should have printed the markers around the number printed at coordinate board[5][5]..but for some reason it displays right after the board has been printed.
So why doesn't it print at that coordinate although I specified it at board[5][5]?
What could be the problem here?
so in your marker function you need to pass the board and the coordinate you want to print at
void marker( int x, int y, int** board )
{
board[x][y-1]="_";
board[x-1][y]="|";
board[x+1][y]="|";
board[x][y+1]="_";
}
then after the call to marker(5,5,board), call drawboard again
my code's a bit off but that's the logic, except you need to check for the case that the marker is at the edge of the board
in other words, you need to keep board around, and any time you make a change to it, clear the screen and print the whole board out again.
There is no persistent drawing in the way that you are doing this. You are just printing straight to the shell/command prompt. The way that you trying to do things will not work. You can't edit something drawn to the prompt after you have drawn it, you need to basically clear the screen and then draw again but with your indicated maker.
I don't know if you are able to use libraries in your assignment, but a very good library that WILL let you do is ncurses
EDIT Full rewrite of answer
Drawing Things On Top of One Another In CMD
Alright, I had some downtime at work, so I wrote a project to do what you need and I'm going to post code and explain what it does and why you need it along the way.
First thin that you are going to need a basically a render buffer or a render context. Whenever you are programming in a graphics API such as OpenGL, you don't just render straight to the screen, you render each object that you have to a buffer that rasterizes your content and turns it into pixels. Once it's in that form, the API shoves the rendered picture onto the screen. We are going to take a similar approach where instead of drawing to a pixel buffer on the GPU, we are going to draw to a character buffer. Think about each character as a pixel on the screen.
Here is a pastebin of the complete source:
Complete Source of Project
RenderContext
Our class to do this will be the RenderContext class. It has fields to hold width and height as well as an array of chars and a special char that we fill our buffer with whenever we clear it.
This class simply holds an array and functions to let us render to it. It makes sure that when we draw to it, we are within bounds. It is possible for an object to try to draw outside of the clipping space (off screen). However, whatever is drawn there is discarded.
class RenderContext {
private:
int m_width, m_height; // Width and Height of this canvas
char* m_renderBuffer; // Array to hold "pixels" of canvas
char m_clearChar; // What to clear the array to
public:
RenderContext() : m_width(50), m_height(20), m_clearChar(' ') {
m_renderBuffer = new char[m_width * m_height];
}
RenderContext(int width, int height) : m_width(width), m_height(height), m_clearChar(' ') {
m_renderBuffer = new char[m_width * m_height];
}
~RenderContext();
char getContentAt(int x, int y);
void setContentAt(int x, int y, char val);
void setClearChar(char clearChar);
void render();
void clear();
};
The two most important functions of this class are setContentAt and render
setContentAt is what an object calls to fill in a "pixel" value. To make this a little more flexible, our class uses a pointer to an array of chars rather than a straight array (or even a two dimensional array). This lets us set the size of our canvas at runtime. Because of this, we access elements of this array with x + (y * m_width) which replaces a two dimensional dereference such as arr[i][j]
// Fill a specific "pixel" on the canvas
void RenderContext::setContentAt(int x, int y, char val) {
if (((0 <= x) && (x < m_width)) && ((0 <= y) && (y < m_height))) {
m_renderBuffer[(x + (y * m_width))] = val;
}
}
render is what actually draws to the prompt. All it does is iterate over all the "pixels" in it's buffer and place them on screen and then moves to the next line.
// Paint the canvas to the shell
void RenderContext::render() {
int row, column;
for (row = 0; row < m_height; row++) {
for (column = 0; column < m_width; column++) {
printf("%c", getContentAt(column, row));
}
printf("\n");
}
}
I_Drawable
Our next class is an Interface that lets us contract with objects that they can draw to our RenderContext. It is pure virtual because we don't want to actually be able to instantiate it, we only want to derive from it. It's only function is draw which accepts a RenderContext. Derived classes use this call to receive the RenderContext and then use RenderContext's setContentAt to put "pixels" into the buffer.
class I_Drawable {
public:
virtual void draw(RenderContext&) = 0;
};
GameBoard
The first class to implement the I_Drawable, thus being able to render to our RenderContext, is the GameBoard class. This is where a majority of the logic comes in. It has fields for width, height, and a integer array that holds the values of the elements on the board. It also has two other fields for spacing. Since when you draw your board using your code, you have spaces between each element. We don't need to incorporate this into the underlying structure of the board, we just need to use them when we draw.
class GameBoard : public I_Drawable {
private:
int m_width, m_height; // Width and height of the board
int m_verticalSpacing, m_horizontalSpacing; // Spaces between each element on the board
Marker m_marker; // The cursor that will draw on this board
int* m_board; // Array of elements on this board
void setAtPos(int x, int y, int val);
void generateBoard();
public:
GameBoard() : m_width(10), m_height(10), m_verticalSpacing(5), m_horizontalSpacing(3), m_marker(Marker()) {
m_board = new int[m_width * m_height];
generateBoard();
}
GameBoard(int width, int height) : m_width(width), m_height(height), m_verticalSpacing(5), m_horizontalSpacing(3), m_marker(Marker()) {
m_board = new int[m_width * m_height];
generateBoard();
}
~GameBoard();
int getAtPos(int x, int y);
void draw(RenderContext& renderTarget);
void handleInput(MoveDirection moveDirection);
int getWidth();
int getHeight();
};
It's key functions are generateBoard, handleInput, and the derived virtual function draw. However, do note that in its constructor it creates a new int array and gives it to its pointer. Then its destructor automatically removes the allocated memory whenever the board goes away.
generateBoard is what we use to actual create the board and fill it with numbers. It will iterate over each location on the board. Each time, it will look at the elements directly to the left and above and store them. Then it will generate a random number until the number it generates does not match either of the stored elements, then it stores the number in the array. I rewrote this to get rid of the flag usage. This function gets called during the construction of the class.
// Actually create the board
void GameBoard::generateBoard() {
int row, column, randomNumber, valToLeft, valToTop;
// Iterate over all rows and columns
for (row = 0; row < m_height; row++) {
for (column = 0; column < m_width; column++) {
// Get the previous elements
valToLeft = getAtPos(column - 1, row);
valToTop = getAtPos(column, row - 1);
// Generate random numbers until we have one
// that is not the same as an adjacent element
do {
randomNumber = (2 + (rand() % 7));
} while ((valToLeft == randomNumber) || (valToTop == randomNumber));
setAtPos(column, row, randomNumber);
}
}
}
handleInput is what deals with moving the cursor around on the board. It's basically a freebie and your next step after getting the cursor to draw over the board. I needed a way to test the drawing. It accepts an enumeration that we switch on to know where to move our cursor to next. If you maybe wanted to have your cursor wrap around the board whenever you reach an edge, you would want to do that here.
void GameBoard::handleInput(MoveDirection moveDirection) {
switch (moveDirection) {
case MD_UP:
if (m_marker.getYPos() > 0)
m_marker.setYPos(m_marker.getYPos() - 1);
break;
case MD_DOWN:
if (m_marker.getYPos() < m_height - 1)
m_marker.setYPos(m_marker.getYPos() + 1);
break;
case MD_LEFT:
if (m_marker.getXPos() > 0)
m_marker.setXPos(m_marker.getXPos() - 1);
break;
case MD_RIGHT:
if (m_marker.getXPos() < m_width - 1)
m_marker.setXPos(m_marker.getXPos() + 1);
break;
}
}
draw is very important because it's what gets the numbers into the RenderContext. To summarize, it iterates over every element on the board, and draws in the correct location on the canvas placing an element under the correct "pixel". This is where we incorporate the spacing. Also, take care and note that we render the cursor in this function.
It's a matter of choice, but you can either store a marker outside of the GameBoard class and render it yourself in the main loop (this would be a good choice because it loosens the coupling between the GameBoard class and the Marker class. However, since they are fairly coupled, I chose to let GameBoard render it. If we used a scene graph, as we probably would with a more complex scene/game, the Marker would probably be a child node of the GameBoard so it would be similar to this implementation but still more generic by not storing an explicit Marker in the GameBoard class.
// Function to draw to the canvas
void GameBoard::draw(RenderContext& renderTarget) {
int row, column;
char buffer[8];
// Iterate over every element
for (row = 0; row < m_height; row++) {
for (column = 0; column < m_width; column++) {
// Convert the integer to a char
sprintf(buffer, "%d", getAtPos(column, row));
// Set the canvas "pixel" to the char at the
// desired position including the padding
renderTarget.setContentAt(
((column * m_verticalSpacing) + 1),
((row * m_horizontalSpacing) + 1),
buffer[0]);
}
}
// Draw the marker
m_marker.draw(renderTarget);
}
Marker
Speaking of the Marker class, let's look at that now. The Marker class is actually very similar to the GameBoard class. However, it lacks a lot of the logic that GameBoard has since it doesn't need to worry about a bunch of elements on the board. The important thing is the draw function.
class Marker : public I_Drawable {
private:
int m_xPos, m_yPos; // Position of cursor
public:
Marker() : m_xPos(0), m_yPos(0) {
}
Marker(int xPos, int yPos) : m_xPos(xPos), m_yPos(yPos) {
}
void draw(RenderContext& renderTarget);
int getXPos();
int getYPos();
void setXPos(int xPos);
void setYPos(int yPos);
};
draw simply puts four symbols onto the RenderContext to outline the selected element on the board. Take note that Marker has no clue about the GameBoard class. It has no reference to it, it doesn't know how large it is, or what elements it holds. You should note though, that I got lazy and didn't take out the hard coded offsets that sort of depend on the padding that the GameBoard has. You should implement a better solution to this because if you change the padding in the GameBoard class, your cursor will be off.
Besides that, whenever the symbols get drawn, they overwrite whatever is in the ContextBuffer. This is important because the main point of your question was how to draw the cursor on top of the GameBoard. This also goes to the importance of draw order. Let's say that whenever we draw our GameBoard, we drew a '=' between each element. If we drew the cursor first and then the board, the GameBoard would draw over the cursor making it invisible.
If this were a more complex scene, we might have to do something fancy like use a depth buffer that would record the z-index of an element. Then whenever we drew, we would check and see if the z-index of the new element was closer or further away than whatever was already in the RenderContext's buffer. Depending on that, we might skip drawing the "pixel" altogether.
We don't though, so take care to order your draw calls!
// Draw the cursor to the canvas
void Marker::draw(RenderContext& renderTarget) {
// Adjust marker by board spacing
// (This is kind of a hack and should be changed)
int tmpX, tmpY;
tmpX = ((m_xPos * 5) + 1);
tmpY = ((m_yPos * 3) + 1);
// Set surrounding elements
renderTarget.setContentAt(tmpX - 0, tmpY - 1, '-');
renderTarget.setContentAt(tmpX - 1, tmpY - 0, '|');
renderTarget.setContentAt(tmpX - 0, tmpY + 1, '-');
renderTarget.setContentAt(tmpX + 1, tmpY - 0, '|');
}
CmdPromptHelper
The last class that I'm going to talk about is the CmdPromptHelper. You don't have anything like this in your original question. However, you will need to worry about it soon. This class is also only useful on Windows so if you are on linux/unix, you will need to worry about dealing with drawing to the shell yourself.
class CmdPromptHelper {
private:
DWORD inMode; // Attributes of std::in before we change them
DWORD outMode; // Attributes of std::out before we change them
HANDLE hstdin; // Handle to std::in
HANDLE hstdout; // Handle to std::out
public:
CmdPromptHelper();
void reset();
WORD getKeyPress();
void clearScreen();
};
Each one of the functions is important. The constructor gets handles to the std::in and std::out of the current command prompt. The getKeyPress function returns what key the user presses down (key-up events are ignored). And the clearScreen function clears the prompt (not really, it actually moves whatever is already in the prompt up).
getKeyPress just makes sure you have a handle and then reads what has been typed into the console. It makes sure that whatever it is, it is a key and that it is being pressed down. Then it returns the key code as a Windows specific enum usually prefaced by VK_.
// See what key is pressed by the user and return it
WORD CmdPromptHelper::getKeyPress() {
if (hstdin != INVALID_HANDLE_VALUE) {
DWORD count;
INPUT_RECORD inrec;
// Get Key Press
ReadConsoleInput(hstdin, &inrec, 1, &count);
// Return key only if it is key down
if (inrec.Event.KeyEvent.bKeyDown) {
return inrec.Event.KeyEvent.wVirtualKeyCode;
} else {
return 0;
}
// Flush input
FlushConsoleInputBuffer(hstdin);
} else {
return 0;
}
}
clearScreen is a little deceiving. You would think that it clears out the text in the prompt. As far as I know, it doesn't. I'm pretty sure it actually shifts all the content up and then writes a ton of characters to the prompt to make it look like the screen was cleared.
An important concept that this function brings up though is the idea of buffered rendering. Again, if this were a more robust system, we would want to implement the concept of double buffering which means rendering to an invisible buffer and waiting until all drawing is finished and then swap the invisible buffer with the visible one. This makes for a much cleaner view of the render because we don't see things while they are still getting drawn. The way we do things here, we see the rendering process happen right in front of us. It's not a major concern, it just looks ugly sometimes.
// Flood the console with empty space so that we can
// simulate single buffering (I have no idea how to double buffer this)
void CmdPromptHelper::clearScreen() {
if (hstdout != INVALID_HANDLE_VALUE) {
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD cellCount; // How many cells to paint
DWORD count; // How many we painted
COORD homeCoord = {0, 0}; // Where to put the cursor to clear
// Get console info
if (!GetConsoleScreenBufferInfo(hstdout, &csbi)) {
return;
}
// Get cell count
cellCount = csbi.dwSize.X * csbi.dwSize.Y;
// Fill the screen with spaces
FillConsoleOutputCharacter(
hstdout,
(TCHAR) ' ',
cellCount,
homeCoord,
&count
);
// Set cursor position
SetConsoleCursorPosition(hstdout, homeCoord);
}
}
main
The very last thing that you need to worry about is how to use all these things. That's where main comes in. You need a game loop. Game loops are probably the most important thing in any game. Any game that you look at will have a game loop.
The idea is:
Show something on screen
Read input
Handle the input
GOTO 1
This program is no different. The first thing it does is create a GameBoard and a RenderContext. It also makes a CmdPromptHelper which lets of interface with the command prompt. After that, it starts the loop and lets the loop continue until we hit the exit condition (for us that's pressing escape). We could have a separate class or function do dispatch input, but since we just dispatch the input to another input handler, I kept it in the main loop. After you get the input, you send if off to the GameBoard which alters itself accordingly. The next step is to clear the RenderContext and the screen/prompt. Then rerun the loop if escape wasn't pressed.
int main() {
WORD key;
GameBoard gb(5, 5);
RenderContext rc(25, 15);
CmdPromptHelper cph;
do {
gb.draw(rc);
rc.render();
key = cph.getKeyPress();
switch (key) {
case VK_UP:
gb.handleInput(MD_UP);
break;
case VK_DOWN:
gb.handleInput(MD_DOWN);
break;
case VK_LEFT:
gb.handleInput(MD_LEFT);
break;
case VK_RIGHT:
gb.handleInput(MD_RIGHT);
break;
}
rc.clear();
cph.clearScreen();
} while (key != VK_ESCAPE);
}
After you have taken into consideration all of these things, you understand why and where you need to be drawing your cursor. It's not a matter of calling a function after another, you need to composite your draws. You can't just draw the GameBoard and then draw the Marker. At least not with the command prompt. I hope this helps. It definitely alleviated the down time at work.

Using minimax search for card games with imperfect information

I want to use minimax search (with alpha-beta pruning), or rather negamax search, to make a computer program play a card game.
The card game actually consists of 4 players. So in order to be able to use minimax etc., I simplify the game to "me" against the "others". After each "move", you can objectively read the current state's evaluation from the game itself. When all 4 players have placed the card, the highest wins them all - and the cards' values count.
As you don't know how the distribution of cards between the other 3 players is exactly, I thought you must simulate all possible distributions ("worlds") with the cards that are not yours. You have 12 cards, the other 3 players have 36 cards in total.
So my approach is this algorithm, where player is a number between 1 and 3 symbolizing the three computer players that the program might need to find moves for. And -player stands for the opponents, namely all the other three players together.
private Card computerPickCard(GameState state, ArrayList<Card> cards) {
int bestScore = Integer.MIN_VALUE;
Card bestMove = null;
int nCards = cards.size();
for (int i = 0; i < nCards; i++) {
if (state.moveIsLegal(cards.get(i))) { // if you are allowed to place this card
int score;
GameState futureState = state.testMove(cards.get(i)); // a move is the placing of a card (which returns a new game state)
score = negamaxSearch(-state.getPlayersTurn(), futureState, 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
if (score > bestScore) {
bestScore = score;
bestMove = cards.get(i);
}
}
}
// now bestMove is the card to place
}
private int negamaxSearch(int player, GameState state, int depthLeft, int alpha, int beta) {
ArrayList<Card> cards;
if (player >= 1 && player <= 3) {
cards = state.getCards(player);
}
else {
if (player == -1) {
cards = state.getCards(0);
cards.addAll(state.getCards(2));
cards.addAll(state.getCards(3));
}
else if (player == -2) {
cards = state.getCards(0);
cards.addAll(state.getCards(1));
cards.addAll(state.getCards(3));
}
else {
cards = state.getCards(0);
cards.addAll(state.getCards(1));
cards.addAll(state.getCards(2));
}
}
if (depthLeft <= 0 || state.isEnd()) { // end of recursion as the game is finished or max depth is reached
if (player >= 1 && player <= 3) {
return state.getCurrentPoints(player); // player's points as a positive value (for self)
}
else {
return -state.getCurrentPoints(-player); // player's points as a negative value (for others)
}
}
else {
int score;
int nCards = cards.size();
if (player > 0) { // make one move (it's player's turn)
for (int i = 0; i < nCards; i++) {
GameState futureState = state.testMove(cards.get(i));
if (futureState != null) { // wenn Zug gültig ist
score = negamaxSuche(-player, futureState, depthLeft-1, -beta, -alpha);
if (score >= beta) {
return score;
}
if (score > alpha) {
alpha = score; // alpha acts like max
}
}
}
return alpha;
}
else { // make three moves (it's the others' turn)
for (int i = 0; i < nCards; i++) {
GameState futureState = state.testMove(cards.get(i));
if (futureState != null) { // if move is valid
for (int k = 0; k < nCards; k++) {
if (k != i) {
GameState futureStateLevel2 = futureState.testMove(cards.get(k));
if (futureStateLevel2 != null) { // if move is valid
for (int m = 0; m < nCards; m++) {
if (m != i && m != k) {
GameState futureStateLevel3 = futureStateLevel2.testMove(cards.get(m));
if (futureStateLevel3 != null) { // if move is valid
score = negamaxSuche(-player, futureStateLevel3, depthLeft-1, -beta, -alpha);
if (score >= beta) {
return score;
}
if (score > alpha) {
alpha = score; // alpha acts like max
}
}
}
}
}
}
}
}
}
return alpha;
}
}
}
This seems to work fine, but for a depth of 1 (depthLeft=1), the program already needs to calculate 50,000 moves (placed cards) on average. This is too much, of course!
So my questions are:
Is the implementation correct at all? Can you simulate a game like this? Regarding the imperfect information, especially?
How can you improve the algorithm in speed and work load?
Can I, for example, reduce the set of possible moves to a random set of 50% to improve speed, while keeping good results?
I found UCT algorithm to be a good solution (maybe). Do you know this algorithm? Can you help me implementing it?
I want to clarify details that the accepted answer doesn't really go into.
In many card games you can sample the unknown cards that your opponent could have instead of generating all of them. You can take into account information like short suits and the probability of holding certain cards given play so far when doing this sampling to weight the likelihood of each possible hand (each hand is a possible world that we'll solve independently). Then, you solve each hand using perfect information search. The best move over all of these worlds is often the best move overall - with some caveat.
In games like Poker this won't work very well -- the game is all about the hidden information. You have to precisely balance your actions to keep the information about your hand hidden.
But, in games like trick-based card games, this works pretty well - particularly since new information is being revealed all the time. Really good players have a good idea what everyone holds anyway. So, reasonably strong Skat and Bridge programs have been based on these ideas.
If you can completely solve the underlying world, that is best, but if you can't, you can use minimax or UCT to choose the best move in each world. There are also hybrid algorithms (ISMCTS) that try to mix this process together. Be careful about the claims here. Simple sampling approaches are easier to code -- you should try the simpler approach before a more complex one.
Here are some research papers that will give some more information on when the sampling approach to imperfect information has worked well:
Understanding the Success of Perfect Information Monte Carlo Sampling in Game Tree Search (This paper analyzes when the sampling approach is likely to work.)
Improving State Evaluation, Inference, and Search in Trick-Based Card Games (This paper describes the use of sampling in Skat)
Imperfect information in a computationally challenging game (This paper describes sampling in Bridge)
Information Set Monte Carlo Tree Search (This paper merges sampling and UCT/Monte Carlo Tree Search to avoid the issues in the first reference.)
The problem with rule-based approaches in the accepted answer is that they can't take advantage of computational resources beyond that required to create the initial rules. Furthermore, rule-based approaches will be limited by the power of the rules that you can write. Search-based approaches can use the power of combinatorial search to produce much stronger play than the author of the program.
Minimax search as you've implemented it is the wrong approach for games where there is so much uncertainty. Since you don't know the card distribution among the other players, your search will spend an exponential amount of time exploring games that could not happen given the actual distribution of the cards.
I think a better approach would be to start with good rules for play when you have little or no information about the other players' hands. Things like:
If you play first in a round, play your lowest card since you have little chance of winning the round.
If you play last in a round, play your lowest card that will win the round. If you can't win the round, then play your lowest card.
Have your program initially not bother with search and just play by these rules and have it assume that all the other players will use these heuristics as well. As the program observes what cards the first and last players of each round play it can build up a table of information about the cards each player likely holds. E.g. a 9 would have won this round, but player 3 didn't play it so he must not have any cards 9 or higher. As information is gathered about each player's hand the search space will eventually be constrained to the point where a minimax search of possible games could produce useful information about the next card to play.

Resources