Skip to main content
Tweeted twitter.com/StackCodeReview/status/1379947099102535684
edited body
Source Link
Offtkp
  • 371
  • 2
  • 11

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 squaredsquares (and be captured next turn). Castling is not implemented yet.

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 squared (and be captured next turn). Castling is not implemented yet.

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.

Became Hot Network Question
Source Link
Offtkp
  • 371
  • 2
  • 11

Chess engine for chess without checks in C++

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 squared (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;
}