diff --git a/apps/Chess/Makefile b/apps/Chess/Makefile index d362617..fe16903 100644 --- a/apps/Chess/Makefile +++ b/apps/Chess/Makefile @@ -5,5 +5,6 @@ SRC_DIR=src APP_C_FILES=$(shell find $(SRC_DIR) -type f -name '*.c') APP_S_FILES=$(shell find $(SRC_DIR) -type f -name '*.asm') USERCFLAGS=-Wno-switch +OPTIMIZATIONS = -O3 include ../CommonMakefile diff --git a/apps/Chess/src/chess.c b/apps/Chess/src/chess.c index a8d9451..0f8fd40 100644 --- a/apps/Chess/src/chess.c +++ b/apps/Chess/src/chess.c @@ -862,10 +862,8 @@ void ChessGeneratePGN(char* pgnOut, BoardState* pState, int rowSrc, int colSrc, strcpy(pgnOut, moveList); } -eErrorCode ChessCommitMove(int rowSrc, int colSrc, int rowDst, int colDst) +eErrorCode ChessCommitMove(BoardState* pState, int rowSrc, int colSrc, int rowDst, int colDst) { - BoardState* pState = g_CurrentState; - BoardPiece* pcSrc = GetPiece(pState, rowSrc, colSrc); eColor col = pcSrc->color; @@ -881,29 +879,31 @@ eErrorCode ChessCommitMove(int rowSrc, int colSrc, int rowDst, int colDst) if (errCode != ERROR_SUCCESS) return errCode; - if (g_CurrentState != &g_History[g_HistorySize - 1]) + if (g_CurrentState == pState) { - if (g_CurrentState < g_History || g_History + g_HistorySize <= g_CurrentState) - { - return ERROR_CANT_OVERWRITE_HISTORY; - } - // Show a message box asking whether the user wants to overwrite the history. - if (ChessMessageBox("Performing this move will overwrite the move history.\n\nDo you wish to perform the move?", "Chess", MB_YESNO | ICON_WARNING << 16) != MBID_YES) + if (g_CurrentState != &g_History[g_HistorySize - 1]) { - return ERROR_CANT_OVERWRITE_HISTORY; + if (g_CurrentState < g_History || g_History + g_HistorySize <= g_CurrentState) + { + return ERROR_CANT_OVERWRITE_HISTORY; + } + // Show a message box asking whether the user wants to overwrite the history. + if (ChessMessageBox("Performing this move will overwrite the move history.\n\nDo you wish to perform the move?", "Chess", MB_YESNO | ICON_WARNING << 16) != MBID_YES) + { + return ERROR_CANT_OVERWRITE_HISTORY; + } + + // this ptr diff should be fine, since we already check the bounds... + g_HistorySize = (int)(g_CurrentState - g_History) + 1; } - // this ptr diff should be fine, since we already check the bounds... - g_HistorySize = (int)(g_CurrentState - g_History) + 1; + // commit the move!! + AddHistoryFrame(); + + pState = g_CurrentState; } - // commit the move!! - AddHistoryFrame(); - - pState = g_CurrentState; - pState->m_PlayerState[pcSrc->color].m_nEnPassantColumn = -1; - pState->m_PlayerState[pcSrc->color].m_bKingInCheck = false; bool bCapture = false; @@ -933,9 +933,11 @@ eErrorCode ChessCommitMove(int rowSrc, int colSrc, int rowDst, int colDst) // fill in the move struct MoveInfo* pmi = &pState->m_MoveInfo; - ChessGeneratePGN(pmi->pgn, pState, rowSrc, colSrc, rowDst, colDst, castleType, bCheck, bCapture, bEnPassant, mateType); - - ChessUpdateMoveList(); + if (g_CurrentState == pState) + { + ChessGeneratePGN(pmi->pgn, pState, rowSrc, colSrc, rowDst, colDst, castleType, bCheck, bCapture, bEnPassant, mateType); + ChessUpdateMoveList(); + } pState->m_Player = GetNextPlayer(pState->m_Player); @@ -947,6 +949,18 @@ eErrorCode ChessCommitMove(int rowSrc, int colSrc, int rowDst, int colDst) } } +void PerformBestMove() +{ + BoardMove bm = FindBestMove(g_CurrentState); + + if (bm.rowSrc < 0) + return; + + eErrorCode ec = ChessCommitMove(g_CurrentState, bm.rowSrc, bm.colSrc, bm.rowDst, bm.colDst); + if (ec != ERROR_SUCCESS) + LogMsg("ERROR: couldn't perform move %d, %d -> %d, %d", bm.rowSrc, bm.colSrc, bm.rowDst, bm.colDst); +} + extern int g_nMoveNumber; void SetupBoard(BoardState* pState) { diff --git a/apps/Chess/src/chess.h b/apps/Chess/src/chess.h index b1a3b79..775bf16 100644 --- a/apps/Chess/src/chess.h +++ b/apps/Chess/src/chess.h @@ -78,8 +78,8 @@ eColor; typedef struct { - eColor color; - ePiece piece; + eColor color : 2; + ePiece piece : 6; } BoardPiece; @@ -125,13 +125,20 @@ typedef struct } BoardState; -extern BoardState* g_CurrentState; - +typedef struct +{ + int rowSrc; + int colSrc; + int rowDst; + int colDst; +} +BoardMove; +extern BoardState* g_CurrentState; extern BoardPiece g_pieces[BOARD_SIZE][BOARD_SIZE]; extern Window* g_pWindow; -eErrorCode ChessCommitMove(int rowSrc, int colSrc, int rowDst, int colDst); +eErrorCode ChessCommitMove(BoardState* pState, int rowSrc, int colSrc, int rowDst, int colDst); eErrorCode ChessCheckMove(BoardState* pState, int rowSrc, int colSrc, int rowDst, int colDst, eCastleType* castleType, bool * bWouldDoEP, bool bFlashTiles); // Check if a color is in checkmate or stalemate. @@ -160,4 +167,9 @@ void ClearFlashingTiles(); void ChessUpdateMoveList(); +void PerformBestMove(); + +// Engine +BoardMove FindBestMove(BoardState* pState); + #endif//CHESS_H diff --git a/apps/Chess/src/engine.c b/apps/Chess/src/engine.c new file mode 100644 index 0000000..d8f2d01 --- /dev/null +++ b/apps/Chess/src/engine.c @@ -0,0 +1,250 @@ +#include "chess.h" + +#define INF (1000000) + +const int g_InitialDepth = 4; + +const int nPieceValues[] = { + 0, // None + 99999, // King + 900, // Queen + 300, // Bishop + 300, // Knight + 500, // Rook + 100, // Pawn +}; + +// NOTE: I pulled these values out of my own ass. + +const char nKingValueMult[] = { + -50,-40,-30,-20,-20,-30,-40,-50, + -30,-20,-10, 0, 0,-10,-20,-30, + -30,-10, 20, 30, 30, 20,-10,-30, + -30,-10, 30, 40, 40, 30,-10,-30, + -30,-10, 30, 40, 40, 30,-10,-30, + -30,-10, 20, 30, 30, 20,-10,-30, + -30,-30, 0, 0, 0, 0,-30,-30, + -50,-30,-30,-30,-30,-30,-30,-50 +}; + +const char nRookValueMult[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 10, 10, 10, 10, 10, 10, 5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + -5, 0, 0, 0, 0, 0, 0, -5, + 0, 0, 0, 5, 5, 0, 0, 0 +}; + +const char nBishopValueMult[] = { + -20,-10,-10,-10,-10,-10,-10,-20, + -10, 0, 0, 0, 0, 0, 0,-10, + -10, 0, 5, 10, 10, 5, 0,-10, + -10, 5, 5, 10, 10, 5, 5,-10, + -10, 0, 10, 10, 10, 10, 0,-10, + -10, 10, 10, 10, 10, 10, 10,-10, + -10, 5, 0, 0, 0, 0, 5,-10, + -20,-10,-10,-10,-10,-10,-10,-20 +}; + +const char nKnightValueMult[] = { + -50,-40,-30,-30,-30,-30,-40,-50, + -40,-20, 0, 0, 0, 0,-20,-40, + -30, 0, 10, 15, 15, 10, 0,-30, + -30, 5, 15, 20, 20, 15, 5,-30, + -30, 0, 15, 20, 20, 15, 0,-30, + -30, 5, 10, 15, 15, 10, 5,-30, + -40,-20, 0, 5, 5, 0,-20,-40, + -50,-40,-30,-30,-30,-30,-40,-50 +}; + +const char nQueenValueMult[] = { + -20,-10,-10, -5, -5,-10,-10,-20, + -10, 0, 0, 0, 0, 0, 0,-10, + -10, 0, 5, 5, 5, 5, 0,-10, + -5, 0, 5, 5, 5, 5, 0, -5, + 0, 0, 5, 5, 5, 5, 0, -5, + -10, 5, 5, 5, 5, 5, 0,-10, + -10, 0, 5, 0, 0, 0, 0,-10, + -20,-10,-10, -5, -5,-10,-10,-20 +}; + +const char nPawnValueMult[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 50, 50, 50, 50, 50, 50, 50, 50, + 10, 10, 20, 30, 30, 20, 10, 10, + 5, 5, 10, 25, 25, 10, 5, 5, + 0, 0, 0, 20, 20, 0, 0, 0, + 5, -5,-10, 0, 0,-10, -5, 5, + 5, 10, 10,-20,-20, 10, 10, 5, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +const char* const nValueMultPiece[] = { + NULL, // None + nKingValueMult, // King, + nQueenValueMult, // Queen + nBishopValueMult, // Bishop + nKnightValueMult, // Knight + nRookValueMult, // Rook + nPawnValueMult, // Pawn +}; + +int EvaluateBoardState(BoardState* pState) +{ + int eval = 0; + + for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) + { + int iRow = i % BOARD_SIZE, iCol = i / BOARD_SIZE; + BoardPiece* pPiece = GetPiece(pState, iRow, iCol); + if (pPiece->piece == PIECE_NONE) + continue; + + bool isBlack = pPiece->color == BLACK; + int index = isBlack ? i : (BOARD_SIZE * BOARD_SIZE - 1 - i); + + int value = nPieceValues[pPiece->piece] + nValueMultPiece[pPiece->piece][index]; + if (isBlack) + value = -value; + + eval += value; + } + + return eval; +} + +#define MAX_MOVES 256 +BoardMove* GetAllMoves(BoardState* pState, int* outMoveCount) +{ +#define ADD_MOVE(a, b, c, d) do { \ + if (nMoves == MAX_MOVES) { \ + LogMsg("ERROR: too many moves!"); \ + } else { \ + BoardMove move = { a, b, c, d }; \ + pMoves[nMoves++] = move; \ + } \ +} while (0) + + int nMoves = 0; + BoardMove* pMoves = calloc(MAX_MOVES, sizeof(BoardMove)); + + // Enumerate every possible move that the current player can make. + for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) + { + int iRow = i % BOARD_SIZE, iCol = i / BOARD_SIZE; + BoardPiece* pPiece = GetPiece(pState, iRow, iCol); + if (pPiece->color != pState->m_Player) + continue; + + // TODO: Add moves depending on the piece that's being moved. Don't try every move ever, in a dumb way. + for (int j = 0; j < BOARD_SIZE * BOARD_SIZE; j++) + { + int jRow = j % BOARD_SIZE, jCol = j / BOARD_SIZE; + if (i == j) continue; + + // there's a legal move. + // The explanation for this is: + // If the king is in check, then ChessCheckMove only returns true if the move would take the king out of check. + + UNUSED eCastleType castleType; + UNUSED bool bEnPassant; + if (ChessCheckMove(pState, iRow, iCol, jRow, jCol, &castleType, &bEnPassant, false) == ERROR_SUCCESS) + ADD_MOVE(iRow, iCol, jRow, jCol); + } + } + + *outMoveCount = nMoves; + return pMoves; +} + +int MinMax(BoardState* pState, int Depth, BoardMove* winningMoveOut, bool isMaxing, int alpha, int beta) +{ + BoardMove winningMove = { -1, -1, -1, -1 }; + + if (Depth == 0) + return EvaluateBoardState(pState); + + BoardState state; + int nMoves = 0; + BoardMove* pMoves = GetAllMoves(pState, &nMoves); + + int max = -INF, min = INF; + + for (int i = 0; i < nMoves; i++) + { + BoardMove bm = pMoves[i]; + + // copy the state + state = *pState; + + // mutate the state to perform the move + eErrorCode ec = ChessCommitMove(&state, bm.rowSrc, bm.colSrc, bm.rowDst, bm.colDst); + + if (ec != ERROR_SUCCESS) + { + if (ec == ERROR_CHECKMATE) { + // checkmate!! don't hesitate!!!! + winningMove = pMoves[i]; + break; + } + + if (ec != ERROR_STALEMATE) { + LogMsg("ERROR: couldn't perform move - MinMax. %d,%d -> %d,%d ==> %d", bm.rowSrc, bm.colSrc, bm.rowDst, bm.colDst, ec); + continue; + } + } + + // the move has been performed. control was passed to the other player. call the MinMax again + + int score = MinMax(&state, Depth - 1, &bm, !isMaxing, alpha, beta); + + bm = pMoves[i]; + + // For debugging and tracking: + if (Depth == g_InitialDepth) + LogMsg(">> Eval predicted after %d,%d -> %d,%d ==> %d [%d/%d]", bm.rowSrc, bm.colSrc, bm.rowDst, bm.colDst, score, i+1, nMoves); + + if (isMaxing) + { + if (max < score) { + max = score; + winningMove = pMoves[i]; + } + if (alpha < score) + alpha = score; + } + else + { + if (min > score) { + min = score; + winningMove = pMoves[i]; + } + if (beta > score) + beta = score; + } + + if (beta <= alpha) + break; + } + + free(pMoves); + *winningMoveOut = winningMove; + return nMoves == 0 ? 0 : (isMaxing ? max : min); +} + +BoardMove FindBestMove(BoardState* pState) +{ + BoardMove bm = { -1, -1, -1, -1 }; + + BoardState bs = *pState; + int eval = MinMax(&bs, g_InitialDepth, &bm, bs.m_Player == WHITE, -INF, INF); + + LogMsg("Eval after %d,%d -> %d,%d ==> %d", bm.rowSrc, bm.colSrc, bm.rowDst, bm.colDst, eval); + + return bm; +} + + diff --git a/apps/Chess/src/main.c b/apps/Chess/src/main.c index 7d83074..a587006 100644 --- a/apps/Chess/src/main.c +++ b/apps/Chess/src/main.c @@ -385,7 +385,7 @@ void ChessReleaseCursor(int x, int y) //bPromotePawn = true; col = pPc->color; - err = ChessCommitMove(g_DraggedPieceRow, g_DraggedPieceCol, boardRow, boardCol); + err = ChessCommitMove(g_CurrentState, g_DraggedPieceRow, g_DraggedPieceCol, boardRow, boardCol); } } @@ -496,15 +496,17 @@ void CALLBACK ChessWndProc (Window* pWindow, int messageType, long parm1, long p RECT(rect, 10, g_BoardY + (BOARD_SIZE * PIECE_SIZE / 2) + 5, 40, (BOARD_SIZE * PIECE_SIZE / 2) - 5); AddControl(pWindow, CONTROL_LISTVIEW, rect, NULL, CHESS_CAPTURES_BLACK, 0, 0); - int btnWidth = RIGHT_BAR_WIDTH / 2; + int btnWidth = RIGHT_BAR_WIDTH; RECT(rect, CHESS_WIDTH - btnWidth - 5, 10, btnWidth - 5, 20); - AddControl(pWindow, CONTROL_BUTTON, rect, "Resign", CHESS_RESIGN_BLACK, ICON_RESIGN, 0); + //AddControl(pWindow, CONTROL_BUTTON, rect, "Resign", CHESS_RESIGN_BLACK, ICON_RESIGN, 0); + AddControl(pWindow, CONTROL_BUTTON, rect, "Find Best Move", CHESS_RESIGN_BLACK, ICON_RESIGN, 0); RECT(rect, CHESS_WIDTH - btnWidth * 2 - 10, 10, btnWidth - 5, 20); AddControl(pWindow, CONTROL_BUTTON, rect, "Draw", CHESS_DRAW_BLACK, ICON_SCALE16, 0); SetControlDisabled(pWindow, CHESS_DRAW_BLACK, true); RECT(rect, CHESS_WIDTH - btnWidth - 5, CHESS_HEIGHT - 30, btnWidth - 5, 20); - AddControl(pWindow, CONTROL_BUTTON, rect, "Resign", CHESS_RESIGN_WHITE, ICON_RESIGN, 0); + //AddControl(pWindow, CONTROL_BUTTON, rect, "Resign", CHESS_RESIGN_WHITE, ICON_RESIGN, 0); + AddControl(pWindow, CONTROL_BUTTON, rect, "Find Best Move", CHESS_RESIGN_WHITE, ICON_RESIGN, 0); RECT(rect, CHESS_WIDTH - btnWidth * 2 - 10, CHESS_HEIGHT - 30, btnWidth - 5, 20); AddControl(pWindow, CONTROL_BUTTON, rect, "Draw", CHESS_DRAW_WHITE, ICON_SCALE16, 0); SetControlDisabled(pWindow, CHESS_DRAW_WHITE, true); @@ -542,10 +544,13 @@ void CALLBACK ChessWndProc (Window* pWindow, int messageType, long parm1, long p break; } + /* if (MessageBox(pWindow, "Are you sure you want to resign as white?", "Chess", ICON_QUES << 16 | MB_YESNO) == MBID_YES) { ChessOnGameEnd(ERROR_RESIGNATION_WHITE, BLACK); } + */ + PerformBestMove(); break; } case CHESS_RESIGN_BLACK: @@ -556,10 +561,13 @@ void CALLBACK ChessWndProc (Window* pWindow, int messageType, long parm1, long p break; } + /* if (MessageBox(pWindow, "Are you sure you want to resign as black?", "Chess", ICON_QUES << 16 | MB_YESNO) == MBID_YES) { ChessOnGameEnd(ERROR_RESIGNATION_BLACK, WHITE); } + */ + PerformBestMove(); break; } } diff --git a/apps/CommonMakefile b/apps/CommonMakefile index ce30b02..c36ab95 100644 --- a/apps/CommonMakefile +++ b/apps/CommonMakefile @@ -7,6 +7,8 @@ BLD_DIR=build LIS_DIR=../../crt/src LII_DIR=../../crt/include +OPTIMIZATIONS ?= -O2 + LGCC=../../tools/libgcc-i686.a CC=clang @@ -14,7 +16,7 @@ LD=ld AS=nasm STRIP=strip -CFLAGS=-I $(INC_DIR) -I $(LII_DIR) -DNANOSHELL -ffreestanding -target i686-elf -O2 -nostdinc -fno-exceptions -Wall -Wextra -std=c99 -mno-sse2 -MMD $(USERCFLAGS) +CFLAGS=-I $(INC_DIR) -I $(LII_DIR) -DNANOSHELL -ffreestanding -target i686-elf -nostdinc -fno-exceptions -Wall -Wextra -std=c99 -mno-sse2 -MMD $(OPTIMIZATIONS) $(USERCFLAGS) LDFLAGS=-T link.ld -g -nostdlib -zmax-page-size=0x1000 ASFLAGS=-f elf32