Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic engine to Chess app #29

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/Chess/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
58 changes: 36 additions & 22 deletions apps/Chess/src/chess.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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)
{
Expand Down
22 changes: 17 additions & 5 deletions apps/Chess/src/chess.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ eColor;

typedef struct
{
eColor color;
ePiece piece;
eColor color : 2;
ePiece piece : 6;
}
BoardPiece;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -160,4 +167,9 @@ void ClearFlashingTiles();

void ChessUpdateMoveList();

void PerformBestMove();

// Engine
BoardMove FindBestMove(BoardState* pState);

#endif//CHESS_H
250 changes: 250 additions & 0 deletions apps/Chess/src/engine.c
Original file line number Diff line number Diff line change
@@ -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;
}


Loading