Project Description:
Chess without checks: A chess variant where you can take the enemy king.
This engine implementation is for chess without checks. Since checks don't exist, expect the code to not account for pinned pieces. Kings can move to dangerous squares (and be captured next turn). Castling is not implemented yet.
The project is comprised of 3 .cpp
and 3 .h
files, and main.cpp
Most of the action happens in board.cpp
, which is the one I needed review for the most.
File explanation:
graphics.cpp
uses SDL2 library to draw the window, board and pieces, and handle events.
fen.cpp
declares the Type
and Color
enums for the pieces, and loads FEN codes into the board.
The board is an array of 64 ints. Color
and Type
int values are combined to create an int that represents a piece.
board.cpp
handles everything that happens on the board. When the user plays a move, it calculates a response with PlayComputerMove()
which in turn uses Minimax(int(&board)[64], int depth, bool isMaximizing)
to find the best move. I have big performance issues here.
Evaluate(int(&board)[64])
evaluates the position. Note that it only counts material. The computer doesn't understand strategy, so when no obvious moves exist (taking a piece, protecting a piece) it just moves pieces back and forth, which is to be expected.
Concerns:
1. Performance:
Even at a depth of 3, the algorithm seems to be taking multiple seconds to finish. You may notice std::chrono::duration<double,std::milli> total;
which counts the milliseconds that GetLegalMoves(int position)
took to execute (in sum for all the times it was called) each move. In my system, it seems to take ~1000ms, and PlayComputerMove()
takes about 5-6 thousand ms.
2. Design:
Currently, the algorithm searches the board for the black/white pieces in a for loop. I could use a std::map
to keep track of the pieces at all times, but I believe it wouldn't positively impact performance by a lot.
GetLegalMoves(int position)
returns a std::list<int>*
. This works, but it seems ugly to me. I do delete it after I don't need the list anymore. Returning a reference to a list seems to throw exceptions. I am only doing this to avoid copying the list.
Comments on bad practices and bad style in my code are also greatly appreciated.
Note: Implementing strategy (accounting for doubled pawns, center control, pawns close to promotion) is not my priority for now.
Code:
graphics.h
#ifndef GRAPHICS_H
#define GRAPHICS_H
#include <memory>
#include <chrono>
#include <thread>
#include "board.h"
#include "SDL.h"
#define SCREEN_X 0
#define SCREEN_Y 0
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 640
#define SQUARE_SIZE 64
class Graphics {
std::unique_ptr<SDL_Window, decltype(&SDL_DestroyWindow)> window;
std::unique_ptr<SDL_Renderer, decltype(&SDL_DestroyRenderer)> renderer;
std::chrono::system_clock::time_point a = std::chrono::system_clock::now();
std::chrono::system_clock::time_point b = std::chrono::system_clock::now();
SDL_Texture* boardTexture;
SDL_Texture* piecesTexture;
Board* board;
SDL_Event ev;
int mouse_x, mouse_y;
bool Init();
void InitBoard();
void InitSpritesheet();
void FillPiece(int piece, int xx, int yy);
public:
Graphics();
~Graphics();
void PreRender();
void Render();
void UpdatePieces();
void LimitFPS();
bool InputCheck();
void SetBoard(Board* board);
};
#endif
graphics.cpp
#include <iostream>
#include "graphics.h"
Graphics::Graphics() : window(nullptr, SDL_DestroyWindow), renderer(nullptr, SDL_DestroyRenderer) {
Init();
InitBoard();
InitSpritesheet();
}
Graphics::~Graphics() {
SDL_DestroyTexture(boardTexture);
SDL_Quit();
}
void Graphics::PreRender() {
SDL_RenderClear(renderer.get());
SDL_RenderCopy(renderer.get(), boardTexture, 0, 0);
}
bool Graphics::Init(){
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL failed to initialize. Error:" << SDL_GetError() << std::endl;
return false;
}
else {
window.reset(SDL_CreateWindow("Chess AI", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN));
renderer.reset(SDL_CreateRenderer(window.get(), -1, SDL_RENDERER_ACCELERATED));
return true;
}
}
void Graphics::InitBoard() {
SDL_Rect r;
r.x = SCREEN_X;
r.y = SCREEN_Y;
r.w = SQUARE_SIZE;
r.h = SQUARE_SIZE;
SDL_Surface* surf = SDL_GetWindowSurface(window.get());
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j += 2) {
SDL_FillRect(surf, &r, 0xAFC3C7FF);
r.x += SQUARE_SIZE * 2;
}
r.y += SQUARE_SIZE;
r.x = (r.x == SQUARE_SIZE * 9 ? 0 : SQUARE_SIZE);
}
r.y = SCREEN_Y;
r.x = SQUARE_SIZE;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j += 2) {
SDL_FillRect(surf, &r, 0x618187FF);
r.x += SQUARE_SIZE * 2;
}
r.y += SQUARE_SIZE;
r.x = (r.x == SQUARE_SIZE * 9 ? 0 : SQUARE_SIZE);
}
boardTexture = SDL_CreateTextureFromSurface(renderer.get(), surf);
SDL_FreeSurface(surf);
}
void Graphics::InitSpritesheet() {
SDL_Surface* surf = SDL_LoadBMP("pieces.bmp");
if (surf == NULL) {
std::cerr << "Unable to load spritesheet. Error:" << SDL_GetError() << std::endl;
}
piecesTexture = SDL_CreateTextureFromSurface(renderer.get(), surf);
SDL_FreeSurface(surf);
}
void Graphics::LimitFPS()
{
a = std::chrono::system_clock::now();
std::chrono::duration<double, std::milli> work_time = a - b;
if (work_time.count() < 16.75) {
std::chrono::duration<double, std::milli> delta_ms(16.75 - work_time.count());
auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
}
b = std::chrono::system_clock::now();
std::chrono::duration<double, std::milli> sleep_time = b - a;
}
bool Graphics::InputCheck() {
while (SDL_PollEvent(&ev) != 0) {
SDL_GetMouseState(&mouse_x, &mouse_y);
switch(ev.type){
case SDL_QUIT:
return true;
break;
case SDL_MOUSEBUTTONDOWN:
int index = ((mouse_x - SCREEN_X) / SQUARE_SIZE) % 8 + ((mouse_y - SCREEN_Y) / SQUARE_SIZE) * 8;
board->Click(index);
break;
}
}
return false;
}
void Graphics::SetBoard(Board* board) {
Graphics::board = board;
}
void Graphics::Render() {
SDL_RenderPresent(renderer.get());
}
void Graphics::FillPiece(int piece, int xx, int yy) {
Type t = GetType(piece);
int y = (GetColor(piece) == Color::White ? 0 : SQUARE_SIZE);
int x = 0;
switch (t) {
case Type::King:
x = 0;
break;
case Type::Queen:
x = SQUARE_SIZE;
break;
case Type::Bishop:
x = 2 * SQUARE_SIZE;
break;
case Type::Knight:
x = 3 * SQUARE_SIZE;
break;
case Type::Rook:
x = 4 * SQUARE_SIZE;
break;
case Type::Pawn:
x = 5 * SQUARE_SIZE;
break;
}
SDL_Rect srcRect;
SDL_Rect dstRect;
srcRect.x = x;
srcRect.y = y;
srcRect.w = SQUARE_SIZE;
srcRect.h = SQUARE_SIZE;
dstRect.x = xx;
dstRect.y = yy;
dstRect.w = SQUARE_SIZE;
dstRect.h = SQUARE_SIZE;
SDL_RenderCopy(renderer.get(), piecesTexture, &srcRect, &dstRect);
}
void Graphics::UpdatePieces() {
for (int i = 0; i < 64; i++) {
int piece = board->GetPiece(i);
if (piece != 0) {
FillPiece(piece, (i % 8) * SQUARE_SIZE, (i >> 3) * SQUARE_SIZE);
}
}
int heldPiece = board->GetHeldPiece();
if (heldPiece != 0) {
FillPiece(heldPiece, mouse_x - SQUARE_SIZE / 2, mouse_y - SQUARE_SIZE / 2);
}
}
fen.h
#ifndef FEN_H
#define FEN_H
#include <string>
#include <vector>
#include <array>
#include <ostream>
#include <istream>
#include <sstream>
enum class Type {
Pawn = 1,
Knight = 2,
Bishop = 4,
Rook = 8,
Queen = 16,
King = 32
};
enum class Color {
Black = 64,
White = 128
};
inline Type GetType(int piece) {
return static_cast<Type>(piece & 0x3F);
}
inline Color GetColor(int piece) {
return static_cast<Color>((piece >> 6) << 6);
}
struct FEN {
std::string piecePlacement;
Color activeColor;
bool whiteCanCastleKingside;
bool whiteCanCastleQueenside;
bool blackCanCastleKingside;
bool blackCanCastleQueenside;
int enPassantSquare;
bool justMovedTwoSquares;
int halfmoveClock;
int fullmoveNumber;
int blackKingPos;
int whiteKingPos;
FEN(int (&board)[64], std::string& str);
};
inline std::ostream& operator<<(std::ostream& out, const FEN& fen) {
out << "FEN: {\n"
<< fen.piecePlacement << ", \n"
<< ((fen.activeColor == Color::White) ? "White to play" : "Black to play") << ", \n"
<< "Move: " << fen.fullmoveNumber << "\n"
<< "}"
<< std::endl;
return out;
}
#endif
fen.cpp
#include "fen.h"
// Syntactic sugar
int operator|(const Type& T, const Color& C) {
return static_cast<int>(T) | static_cast<int>(C);
}
bool operator&(int I, Type T) {
return I & static_cast<int>(T);
}
bool operator&(int I, Color C) {
return I & static_cast<int>(C);
}
template <typename Out>
void split(const std::string& s, char delim, Out result) {
std::istringstream iss(s);
std::string item;
while (std::getline(iss, item, delim)) {
*result++ = item;
}
};
std::vector<std::string> split(const std::string& s, char delim) {
std::vector<std::string> elems;
split(s, delim, std::back_inserter(elems));
return elems;
};
FEN::FEN(int(&board)[64], std::string& str) {
std::vector<std::string> fenParts = split(str, ' ');
piecePlacement = fenParts[0];
int i = 0;
for (char c : piecePlacement) {
if (c != '/') {
if (c >= '1' && c <= '8') {
i += c - '0';
}
else {
if (isupper(c)) {
board[i] += (int)Color::White;
}
else {
board[i] += (int)Color::Black;
}
switch (tolower(c)) {
case 'p':
board[i] += (int)Type::Pawn;
break;
case 'n':
board[i] += (int)Type::Knight;
break;
case 'b':
board[i] += (int)Type::Bishop;
break;
case 'r':
board[i] += (int)Type::Rook;
break;
case 'q':
board[i] += (int)Type::Queen;
break;
case 'k':
board[i] += (int)Type::King;
if (board[i] & Color::White) {
whiteKingPos = i;
}
else {
blackKingPos = i;
}
break;
}
i++;
}
}
}
activeColor = (fenParts[1])[0] == 'w' ? Color::White : Color::Black;
for (char c : fenParts[2]) {
switch (c) {
case 'K':
whiteCanCastleKingside = true;
break;
case 'Q':
whiteCanCastleQueenside = true;
break;
case 'k':
blackCanCastleKingside = true;
break;
case 'q':
blackCanCastleQueenside = true;
break;
}
}
if ((fenParts[3])[0] != '-') {
int letter = (fenParts[3])[0] - 'a';
int number = (fenParts[3])[0] - '0' * 8;
enPassantSquare = letter + number;
}
halfmoveClock = std::stoi(fenParts[4]);
fullmoveNumber = std::stoi(fenParts[5]);
}
board.h
#ifndef BOARD_H
#define BOARD_H
#include <memory>
#include <map>
#include <chrono>
#include <list>
#include "fen.h"
class Board {
int board[64];
std::unique_ptr<FEN> fen;
std::list<int>* heldLegalMoves;
int legalMoveCounter = 0;
std::chrono::duration<double,std::milli> total;
int heldPiece, heldSquare = -1;
bool toMove = true;
bool justMovedTwoSquares = false;
void PlayComputerMove();
int Evaluate(int(&board)[64]);
int Minimax(int(&board)[64], int depth, bool isMaximizing);
public:
Board(std::string& fenCode);
std::list<int>* GetLegalMoves(int position);
void Click(int index);
void UndoClick();
const int GetPiece(int index) const;
int GetHeldPiece();
};
#endif
board.cpp
#include "board.h"
#include <algorithm>
#include <iostream>
Board::Board(std::string& fenCode) {
fen = std::make_unique<FEN>(board, fenCode);
}
std::list<int>* Board::GetLegalMoves(int position) {
auto t1 = std::chrono::high_resolution_clock::now();
std::list<int>* ret = new std::list<int>;
int piece = board[position];
Type t = GetType(piece);
Color c = GetColor(piece);
Color e = ((c == Color::White) ? Color::Black : Color::White);
switch (t) {
case Type::Pawn:
{
if (c == Color::White) {
if (position >= 8) {
if (position >= 48 && position <= 55 && board[position - 16] == 0 && board[position - 8] == 0) {
ret->push_back(position - 16);
fen->enPassantSquare = position - 8;
justMovedTwoSquares = true;
}
if (board[position - 8] == 0) {
ret->push_back(position - 8);
}
if ((position % 8 != 0 && GetColor(board[position - 9]) == e) || fen->enPassantSquare == position - 9) {
ret->push_back(position - 9);
}
if ((position + 1) % 8 != 0 && GetColor(board[position - 7]) == e || fen->enPassantSquare == position - 7) {
ret->push_back(position - 7);
}
}
}
else {
if (position <= 55) {
if (position >= 8 && position <= 15 && board[position + 16] == 0 && board[position + 8] == 0) {
ret->push_back(position + 16);
fen->enPassantSquare = position + 8;
justMovedTwoSquares = true;
}
if (board[position + 8] == 0) {
ret->push_back(position + 8);
}
if ((position % 8 != 0 && GetColor(board[position + 7]) == e) || fen->enPassantSquare == position + 7) {
ret->push_back(position + 7);
}
if (((position + 1) % 8 != 0 && GetColor(board[position + 9]) == e) || fen->enPassantSquare == position + 9) {
ret->push_back(position + 9);
}
}
}
break;
}
case Type::Knight:
{
if (position >= 16 && position % 8 != 0) {
if (GetColor(board[position - 17]) != c) {
ret->push_back(position - 17);
}
}
if (position >= 16 && (position + 1) % 8 != 0) {
if (GetColor(board[position - 15]) != c) {
ret->push_back(position - 15);
}
}
if (position >= 8 && position % 8 != 0 && (position - 1) % 8 != 0) {
if (GetColor(board[position - 10]) != c) {
ret->push_back(position - 10);
}
}
if (position >= 8 && (position + 1) % 8 != 0 && (position + 2) % 8 != 0) {
if (GetColor(board[position - 6]) != c) {
ret->push_back(position - 6);
}
}
if (position <= 47 && (position + 1) % 8 != 0) {
if (GetColor(board[position + 17]) != c) {
ret->push_back(position + 17);
}
}
if (position <= 47 && position % 8 != 0) {
if (GetColor(board[position + 15]) != c) {
ret->push_back(position + 15);
}
}
if (position <= 55 && (position + 1) % 8 != 0 && (position + 2) % 8 != 0) {
if (GetColor(board[position + 10]) != c) {
ret->push_back(position + 10);
}
}
if (position <= 55 && position % 8 != 0 && (position - 1) % 8 != 0) {
if (GetColor(board[position + 6]) != c) {
ret->push_back(position + 6);
}
}
break;
}
case Type::Bishop:
{
int pTemp = position;
while (pTemp >= 9 && (pTemp % 8 != 0)) {
pTemp -= 9;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp >= 8 && ((pTemp + 1) % 8 != 0)) {
pTemp -= 7;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp <= 55 && (pTemp % 8 != 0)) {
pTemp += 7;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp <= 55 && ((pTemp + 1) % 8 != 0)) {
pTemp += 9;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
break;
}
case Type::Rook:
{
int pTemp = position;
while (((pTemp--)) % 8 != 0) {
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while ((++pTemp) % 8 != 0) {
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp >= 8) {
pTemp -= 8;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp <= 55 != 0) {
pTemp += 8;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
break;
}
case Type::Queen:
{
int pTemp = position;
while (pTemp >= 9 && (pTemp % 8 != 0)) {
pTemp -= 9;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp >= 8 && ((pTemp + 1) % 8 != 0)) {
pTemp -= 7;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp <= 55 && (pTemp % 8 != 0)) {
pTemp += 7;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp <= 55 && ((pTemp + 1) % 8 != 0)) {
pTemp += 9;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (((pTemp--)) % 8 != 0) {
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while ((++pTemp) % 8 != 0) {
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp >= 8) {
pTemp -= 8;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
pTemp = position;
while (pTemp <= 55 != 0) {
pTemp += 8;
if (board[pTemp] != 0) {
if (GetColor(board[pTemp]) == e)
ret->push_back(pTemp);
break;
}
ret->push_back(pTemp);
}
break;
}
case Type::King:
{
if (c == Color::White) {
if (position == 60) {
if (fen->whiteCanCastleKingside && board[62] == 0) {
ret->push_back(62);
}
if (fen->whiteCanCastleQueenside && board[58] == 0) {
ret->push_back(58);
}
}
}
else {
if (position == 4) {
if (fen->blackCanCastleKingside && board[6] == 0) {
ret->push_back(6);
}
if (fen->blackCanCastleQueenside && board[2] == 0) {
ret->push_back(2);
}
}
}
if (position % 8 != 0) {
if (position >= 9) {
if (GetColor(board[position - 9]) != c)
ret->push_back(position - 9);
}
if (GetColor(board[position - 1]) != c)
ret->push_back(position - 1);
if (position <= 55) {
if (GetColor(board[position + 7]) != c)
ret->push_back(position + 7);
}
}
if ((position + 1) % 8 != 0) {
if (position >= 7) {
if (GetColor(board[position - 7]) != c)
ret->push_back(position - 7);
}
if (GetColor(board[position + 1]) != c)
ret->push_back(position + 1);
if (position < 55) {
if (GetColor(board[position + 9]) != c)
ret->push_back(position + 9);
}
}
if (position >= 8) {
if (GetColor(board[position - 8]) != c)
ret->push_back(position - 8);
}
if (position <= 55) {
if (GetColor(board[position + 8]) != c)
ret->push_back(position + 8);
}
break;
}
}
auto t2 = std::chrono::high_resolution_clock::now();
total += t2 - t1;
legalMoveCounter += ret->size();
return ret;
}
int Board::Evaluate(int(&board)[64]) {
int eval = 0;
int whiteBishopCount = 0;
int blackBishopCount = 0;
for (int i = 0; i < 64; i++) {
int piece = board[i];
if (piece != 0) {
int sign = GetColor(piece) == Color::White ? -1 : 1;
Color c = GetColor(piece);
Type t = GetType(piece);
switch (t) {
case Type::Pawn: {
eval += 10 * sign;
break;
}
case Type::Knight: {
eval += 30 * sign;
break;
}
case Type::Bishop: {
if (c == Color::White) {
whiteBishopCount++;
if (whiteBishopCount > 1) {
eval += 5;
}
}
else {
blackBishopCount++;
if (blackBishopCount > 1) {
eval -= 5;
}
}
eval += 30 * sign;
break;
}
case Type::Rook: {
eval += 40 * sign;
break;
}
case Type::Queen: {
eval += 90 * sign;
break;
}
case Type::King: {
eval += INT16_MAX * sign;
break;
}
}
}
}
return eval;
}
void Board::Click(int index) {
if (heldSquare == index) {
UndoClick();
return;
}
if (heldPiece == 0) {
if (toMove) {
delete heldLegalMoves;
heldPiece = board[index];
heldLegalMoves = GetLegalMoves(index);
board[index] = 0;
heldSquare = index;
}
}
else {
if (std::find(heldLegalMoves->begin(), heldLegalMoves->end(), index) != heldLegalMoves->end()) {
if (index == fen->enPassantSquare && GetType(heldPiece) == Type::Pawn) {
if (fen->enPassantSquare < 32) {
board[index + 8] = 0;
}
else {
board[index - 8] = 0;
}
}
board[index] = heldPiece;
heldPiece = 0;
heldSquare = -1;
toMove = false;
PlayComputerMove();
if (!justMovedTwoSquares) {
fen->enPassantSquare = 0;
}
else {
justMovedTwoSquares = false;
}
}
else {
UndoClick();
}
}
}
void Board::UndoClick() {
if (heldPiece != 0) {
board[heldSquare] = heldPiece;
heldPiece = 0;
heldSquare = -1;
}
}
void Board::PlayComputerMove() {
std::vector<int> blackPieces;
for (int i = 0; i < 64; i++) {
if (board[i] != 0 && GetColor(board[i]) == Color::Black) {
blackPieces.push_back(i);
}
}
int bestScore = INT_MIN;
int bestMove = -1;
int bestMoveOld = -1;
for (int i : blackPieces) {
auto legalMoves = GetLegalMoves(i);
if (legalMoves->size() != 0) {
for (int move : *legalMoves) {
int oldP = board[move];
board[move] = board[i];
board[i] = 0;
int score = Minimax(board, 2, false);
board[i] = board[move];
board[move] = oldP;
if (score > bestScore) {
bestScore = score;
bestMove = move;
bestMoveOld = i;
}
}
}
delete legalMoves;
}
board[bestMove] = board[bestMoveOld];
board[bestMoveOld] = 0;
toMove = true;
std::cout << "Total time spent on legalmoves:" << total.count() << std::endl;
std::cout << "Total legalmoves:" << legalMoveCounter << std::endl;
legalMoveCounter = 0;
total -= total;
}
int Board::Minimax(int(&board)[64], int depth, bool isMaximizing) {
int result = Evaluate(board);
// An evaluation bigger than 400 means that a king is taken, since
// the evaluation of all the other pieces is less than 400 in total
if (depth == 0 || result > 400 || result < -400) {
return result;
}
if (isMaximizing) {
int bestScore = INT_MIN;
std::vector<int> blackPieces;
for (int i = 0; i < 64; i++) {
if (board[i] != 0 && GetColor(board[i]) == Color::Black) {
blackPieces.push_back(i);
}
}
for (int i : blackPieces) {
auto legalMoves = GetLegalMoves(i);
if (legalMoves->size() != 0) {
for (int move : *legalMoves) {
int oldP = board[move];
board[move] = board[i];
board[i] = 0;
int score = Minimax(board, depth - 1, false);
board[i] = board[move];
board[move] = oldP;
bestScore = std::max(score, bestScore);
}
}
delete legalMoves;
}
return bestScore;
}
else {
int bestScore = INT_MAX;
std::vector<int> whitePieces;
for (int i = 0; i < 64; i++) {
if (board[i] != 0 && GetColor(board[i]) == Color::White) {
whitePieces.push_back(i);
}
}
for (int i : whitePieces) {
auto legalMoves = GetLegalMoves(i);
if (legalMoves->size() != 0) {
for (int move : *legalMoves) {
int oldP = board[move];
board[move] = board[i];
board[i] = 0;
int score = Minimax(board, depth - 1, true);
board[i] = board[move];
board[move] = oldP;
bestScore = std::min(score, bestScore);
}
}
delete legalMoves;
}
return bestScore;
}
}
int Board::GetHeldPiece() {
return heldPiece;
}
const int Board::GetPiece(int index) const{
return board[index];
}
main.cpp
#define SDL_MAIN_HANDLED
#include <iostream>
#include "graphics.h"
#define VERSION "1.0.0"
int main() {
std::string fenCode = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
Graphics g;
Board b(fenCode);
g.SetBoard(&b);
bool quit = false;
while (!quit) {
g.LimitFPS();
quit = g.InputCheck();
g.PreRender();
g.UpdatePieces();
g.Render();
}
return 0;
}
new
in hot code (search), don't use vector or list (prefer c-arrays, you may pre-allocate 6 arrays for the 3 moves you search (6 ply). In general avoid any kind of allocation in hot code. Next, use alpha-beta instead of minimax. Will save you quite a bit. \$\endgroup\$std::list
is a premature pessimization; do not use it unless you are spicing an order of magnitude more often than you are iterating, or your node data is a large number of kilobytes in size, and then still probably don't use it. \$\endgroup\$std::array
(which is nothing more than a standard-library wrapper around a C-style array). The reason you want to avoidstd::vector
is the dynamic memory allocation. That problem doesn't plaguestd::array
, since it is statically allocated (with the obvious caveat that the size cannot grow dynamically and must be specified at compile time.) \$\endgroup\$