Compare commits

..

No commits in common. "0145664567267316475899f3a37ed6ac8138d872" and "04134f013c5495ada1538002e22fd48fe14e2c7a" have entirely different histories.

29 changed files with 197 additions and 825 deletions

View File

@ -1,5 +1,4 @@
CXXFLAGS += -O3 -Wall
# CXXFLAGS += -pg
# Add .d to Make's recognized suffixes.
SUFFIXES += .d
@ -29,7 +28,7 @@ obj/%.o:
$(CXX) $(CXXFLAGS) -o $@ -c $<
main: $(OBJFILES)
$(CXX) $(CXXFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main -lsfml-graphics -lsfml-window -lsfml-system
$(CXX) $(LDFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main -lsfml-graphics -lsfml-window -lsfml-system
clean:
rm -rf obj/* $(DEPFILES) test_bin/
@ -47,7 +46,7 @@ LIBS := $(filter-out obj/main.o,$(OBJFILES))
test_bin/%: tests/%.cpp $(LIBS)
@echo $(LIBS)
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS) -lsfml-graphics -lsfml-window -lsfml-system
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS)
# The 'test' target builds all tests and then runs each one.
.PHONY: test

View File

@ -14,8 +14,7 @@ void AIvsAIController::start() {
ai::AI* current_player;
while (!board.is_terminal()) {
current_player = board.white_to_play ? &p1 : &p2;
std::cout << typeid(*current_player).name() << " to play" << std::endl;
Move move = current_player->search(board);
Move move = current_player->search(board, board.white_to_play);
make_move(move);
}
@ -24,14 +23,13 @@ void AIvsAIController::start() {
void AIvsAIController::make_move(Move move) {
board = board.make_move(move);
std::cout << "Made move: " << move << std::endl;
view.update_board(board, -1, {});
Colour current_colour = board.white_to_play ? White : Black;
if (board.is_checkmate())
if (board.is_checkmate_for(current_colour))
view.notify_checkmate(current_colour);
if (board.is_stalemate())
if (board.is_stalemate_for(current_colour))
view.notify_stalemate(current_colour);
}

View File

@ -9,7 +9,6 @@ ManualController::ManualController(Board b, View& v): Controller(b, v) {
}
void ManualController::start() {
reset_selection();
view.show();
}
@ -51,24 +50,13 @@ void ManualController::show_legal_moves(Coords xy) {
}
void ManualController::make_move(Move move) {
// handle promotion before making the move
Colour colour = board.white_to_play ? White : Black;
Coords source = Coords::from_index(move.source_square);
if (board.piece_at(move.source_square) == Piece::Pawn
&& board.colour_at(move.source_square) == White && source.y == 6) {
Piece promotion_piece = (Piece) (colour | view.ask_about_promotion());
move.promoting_to = promotion_piece;
}
std::cout << "Move made: " << move << std::endl;
board = board.make_move(move);
reset_selection();
Colour current_colour = board.white_to_play ? White : Black;
if (board.is_checkmate())
if (board.is_checkmate_for(current_colour))
view.notify_checkmate(current_colour);
if (board.is_stalemate())
if (board.is_stalemate_for(current_colour))
view.notify_stalemate(current_colour);
}

View File

@ -2,8 +2,6 @@
#include "controller/controller.hpp"
#include "controller/human_vs_ai.hpp"
#include "controller/manual.hpp"
#include "model/ais/ai.hpp"
#include "model/perft/perft.hpp"
#include "view/gui.hpp"
#include "view/noop.hpp"
#include "view/view.hpp"
@ -11,31 +9,26 @@
#include <chrono>
int main(int argc, char* argv[]) {
// std::string pos =
// "r2qkb1r/2p1pppp/p1n1b3/1p6/B2P4/2P1P3/P4PPP/R1BQK1NR w KQkq - 0 9 ";
// std::string pos = "8/6K1/5P2/8/1k6/8/8/8 w - - 0 1";
// pos for ai timing<
std::string pos =
"r3k2r/p1ppqpb1/Bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPB1PPP/R3K2R b KQkq - 0 3";
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
Board b = Board::setup_fen_position(pos);
ai::v0_random p1(true, std::chrono::milliseconds(1000));
// ai::v1_pure_minimax p2(false, std::chrono::milliseconds(20000));
// ai::v2_alpha_beta p2(false, std::chrono::milliseconds(20000));
ai::v3_AB_ordering p2(false, std::chrono::milliseconds(20000));
// ai::v4_search_captures p2(false, std::chrono::milliseconds(20000));
ai::v0_random p1(std::chrono::milliseconds(500));
ai::v0_random p2(std::chrono::milliseconds(500));
// GUI gui;
NoOpView gui;
GUI gui;
AIvsAIController manual(b, gui, p1, p2);
// HumanVsAIController manual(b, gui, p2);
Controller& controller = manual;
controller.start();
// perft();
// ai::v1_simple ai;
//
// Board b = Board::setup_fen_position(pos);
// Move move = ai.search(b, true);
// std::cout << move << std::endl;
return 0;
}

View File

@ -1,44 +1,17 @@
#include "ai.hpp"
#include <condition_variable>
#include <ostream>
#include <thread>
Move ai::AI::search(const Board& b) {
Move ai::AI::search(const Board& b, bool am_white) {
Move result;
std::condition_variable cv;
std::mutex cv_mutex;
double elapsed;
stop_computation = false;
// Start computation in a separate thread
std::thread computation_thread([&]() {
auto start = std::chrono::steady_clock::now();
result = _search(b);
auto end = std::chrono::steady_clock::now();
elapsed =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
.count();
// Notify the timer thread that computation is done
{
std::lock_guard<std::mutex> lock(cv_mutex);
stop_computation = true;
cv.notify_one();
}
});
std::thread computation_thread([&]() { result = _search(b, am_white); });
// Start a timer thread to stop computation after given time
std::thread timer_thread([&]() {
std::unique_lock<std::mutex> lock(cv_mutex);
if (!cv.wait_for(lock, thinking_time, [&] {
return stop_computation.load();
})) {
// Timeout reached; set stop flag
std::this_thread::sleep_for(thinking_time);
stop_computation = true;
}
});
// Wait for computation thread to finish
@ -46,7 +19,5 @@ Move ai::AI::search(const Board& b) {
// Ensure timer thread is also stopped
timer_thread.join();
std::cout << "Took " << elapsed << " ms" << std::endl;
return result;
}

View File

@ -8,72 +8,36 @@
namespace ai {
class AI {
protected:
bool am_white;
std::chrono::milliseconds thinking_time;
virtual Move _search(const Board&) = 0;
virtual Move _search(const Board&, bool = false) = 0;
public:
AI(bool am_white, std::chrono::milliseconds tt)
: am_white(am_white),
thinking_time(tt) {}
AI(std::chrono::milliseconds tt): thinking_time(tt) {}
std::atomic<bool> stop_computation = false;
Move search(const Board& b);
Move search(const Board& b, bool am_white = false);
virtual int eval(const Board&) = 0;
};
struct v0_random : public AI {
v0_random(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
v0_random(std::chrono::milliseconds tt): AI(tt) {}
Move _search(const Board&) override;
Move _search(const Board&, bool) override;
int eval(const Board&) override {
return 0;
};
};
class v1_pure_minimax : public AI { // looks two moves ahead
class v1_simple : public AI {
int _search(const Board&, int);
public:
v1_pure_minimax(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
v1_simple(std::chrono::milliseconds tt): AI(tt) {}
Move _search(const Board&) override;
Move _search(const Board&, bool) override;
int eval(const Board&) override;
};
class v2_alpha_beta : public AI {
// looks two moves ahead, with alpha-beta pruning (no move ordering)
int _search(const Board&, int, int, int);
public:
v2_alpha_beta(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&) override;
int eval(const Board&) override;
};
class v3_AB_ordering : public AI {
// looks two moves ahead, with alpha-beta pruning, with move ordering
virtual int _search(const Board&, int, int, int);
public:
v3_AB_ordering(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
Move _search(const Board&) override;
int eval(const Board&) override;
};
class v4_search_captures : public v3_AB_ordering {
// same as v3, but looking at only at captures when leaf is reached,
// until no captures are left
int _search(const Board&, int, int, int) override;
int _search_captures(const Board&, int, int);
public:
v4_search_captures(bool w, std::chrono::milliseconds tt)
: v3_AB_ordering(w, tt) {}
};
} // namespace ai

View File

@ -2,12 +2,8 @@
#include <thread>
Move ai::v0_random::_search(const Board& b) {
Move ai::v0_random::_search(const Board& b, bool) {
std::vector<Move> moves = b.all_legal_moves();
std::this_thread::sleep_for(std::chrono::milliseconds(thinking_time)
); // Simulate work
return moves[rand() % moves.size()];
}

View File

@ -1,91 +0,0 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
#define MULTITHREADED 1
static int position_counter = 0;
Move ai::v1_pure_minimax::_search(const Board& b) {
position_counter = 0;
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
std::cout << "Looked at " << position_counter << " positions" << std::endl;
return best_move;
}
int ai::v1_pure_minimax::_search(const Board& b, int depth) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
int best_evaluation = -INFINITY;
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1);
best_evaluation = std::max(best_evaluation, tmp_eval);
}
return best_evaluation;
}
int ai::v1_pure_minimax::eval(const Board& b) {
position_counter++;
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -0,0 +1,95 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "ai.hpp"
#include <future>
#include <map>
static int INFINITY = std::numeric_limits<int>::max();
Move ai::v1_simple::_search(const Board& b, bool am_white) {
ThreadPool pool(std::thread::hardware_concurrency());
std::vector<Move> moves = b.all_legal_moves();
std::map<Move, std::future<int>> futures;
for (int depth = 1; !stop_computation; depth++) {
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&]() {
return _search(tmp_board, depth - 1);
})});
}
}
Move best_move;
int best_eval = -INFINITY;
for (auto& [move, future] : futures) {
int eval = future.get();
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
return best_move;
}
int ai::v1_simple::_search(const Board& b, int depth) {
if (b.is_checkmate_for(b.white_to_play ? White : Black))
return -INFINITY;
if (depth == 0 || stop_computation)
return eval(b);
std::vector<Move> moves = b.all_legal_moves();
int best_evaluation = 0;
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1);
best_evaluation = std::max(best_evaluation, tmp_eval);
}
return best_evaluation;
}
static int PawnValue = 100;
static int KnightValue = 300;
static int BishopValue = 320;
static int RookValue = 500;
static int QueenValue = 900;
int count_material(const Board& b, int8_t colour) {
int ret = 0;
for (int i = 0; i < 64; i++) {
if (b.colour_at(i) == colour)
switch (b.piece_at(i)) {
case Piece::Pawn:
ret += PawnValue;
break;
case Piece::Knigt:
ret += KnightValue;
break;
case Piece::Bishop:
ret += BishopValue;
break;
case Piece::Rook:
ret += RookValue;
break;
case Piece::Queen:
ret += QueenValue;
break;
}
}
return ret;
}
int ai::v1_simple::eval(const Board& b) {
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -1,92 +0,0 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <map>
#define MULTITHREADED 1
static int position_counter = 0;
Move ai::v2_alpha_beta::_search(const Board& b) {
position_counter = 0;
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3, -INFINITY, INFINITY);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
std::cout << "Looked at " << position_counter << " positions" << std::endl;
return best_move;
}
int ai::v2_alpha_beta::_search(const Board& b, int depth, int alpha, int beta) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v2_alpha_beta::eval(const Board& b) {
position_counter++;
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -1,99 +0,0 @@
#include "../pieces/piece.hpp"
#include "../utils/threadpool.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
#include <map>
#define MULTITHREADED 1
static int position_counter;
Move ai::v3_AB_ordering::_search(const Board& b) {
position_counter = 0;
std::vector<Move> moves = b.all_legal_moves();
Move best_move;
int best_eval = -INFINITY;
#if MULTITHREADED
ThreadPool pool(std::thread::hardware_concurrency());
std::cout << "Have to look at " << moves.size() << " moves" << std::endl;
std::map<Move, std::future<int>> futures;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
futures.insert({move, pool.enqueue([&, tmp_board]() {
return _search(tmp_board, 3, -INFINITY, INFINITY);
})});
}
int counter = 0;
for (auto& [move, future] : futures) {
int eval = future.get();
counter++;
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#else
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
std::cout << "Looking at " << move << std::endl;
int eval = _search(tmp_board, 3);
if (!am_white)
eval *= -1;
if (eval > best_eval) {
best_eval = eval;
best_move = move;
}
}
#endif
std::cout << "Looked at " << position_counter << " positions" << std::endl;
return best_move;
}
int ai::v3_AB_ordering::_search(
const Board& b, int depth, int alpha, int beta
) {
if (depth == 0 || stop_computation)
return eval(b);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v3_AB_ordering::eval(const Board& b) {
position_counter++;
int white_eval = count_material(b, Colour::White);
int black_eval = count_material(b, Colour::Black);
int evaluation = white_eval - black_eval;
int perspective = b.white_to_play ? 1 : -1;
return perspective * evaluation;
}

View File

@ -1,61 +0,0 @@
#include "../pieces/piece.hpp"
#include "../utils/utils.hpp"
#include "ai.hpp"
#include <algorithm>
#define MULTITHREADED 1
static int position_counter;
int ai::v4_search_captures::_search(
const Board& b, int depth, int alpha, int beta
) {
if (depth == 0 || stop_computation)
return _search_captures(b, alpha, beta);
if (b.no_legal_moves()) {
if (b.is_check())
return -INFINITY;
return 0;
}
std::vector<Move> moves = b.all_legal_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
Move best_move;
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search(tmp_board, depth - 1, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}
int ai::v4_search_captures::_search_captures(
const Board& b, int alpha, int beta
) {
int evaluation = eval(b);
if (evaluation >= beta)
return beta;
alpha = std::max(evaluation, alpha);
std::vector<Move> moves = b.all_capturing_moves();
std::sort(moves.begin(), moves.end(), [&](Move& m1, Move& m2) {
return m1.score_guess(b) > m2.score_guess(b);
});
for (const Move& move : moves) {
Board tmp_board = b.make_move(move);
int tmp_eval = -_search_captures(tmp_board, -beta, -alpha);
if (tmp_eval >= beta)
return beta;
alpha = std::max(alpha, tmp_eval);
}
return alpha;
}

View File

@ -5,11 +5,9 @@
#include "../utils/move.hpp"
#include "../utils/utils.hpp"
#include <SFML/Graphics/BlendMode.hpp>
#include <algorithm>
#include <cctype>
#include <map>
#include <sstream>
#include <stdexcept>
Board Board::setup_fen_position(std::string fen) {
@ -94,8 +92,6 @@ Board Board::setup_fen_position(std::string fen) {
index = fen.find(' ', index) + 1;
board.n_full_moves = std::stoi(fen.substr(index));
board.check = board._is_check_for(board.white_to_play ? White : Black);
board.nlm = board._no_legal_moves_for(board.white_to_play ? White : Black);
return board;
}
@ -115,7 +111,7 @@ std::string Board::to_fen() const {
for (int rank = 7; rank >= 0; rank--) {
int empty_cell_counter = 0;
for (int file = 0; file < 8; file++) {
if (piece_at({file, rank}) == Piece::None) {
if (squares[rank * 8 + file] == Piece::None) {
empty_cell_counter++;
continue;
}
@ -173,14 +169,7 @@ std::string Board::to_fen() const {
return ret;
}
Board Board::skip_turn() const {
Board ret = *this;
ret.white_to_play = !ret.white_to_play;
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
return ret;
}
Board Board::make_move(Move move, bool recurse_call) const {
Board Board::make_move(Move move) const {
Board ret;
std::copy(
std::begin(this->squares),
@ -277,64 +266,24 @@ Board Board::make_move(Move move, bool recurse_call) const {
}
}
ret.n_half_moves = n_half_moves + 1;
if (is_capturing || piece_at(move.source_square) == Piece::Pawn)
ret.n_half_moves = 0;
if (!white_to_play)
ret.n_full_moves = n_full_moves + 1;
if (ret.n_half_moves > 150) {
std::cerr << "too many recursions" << std::endl;
exit(1);
}
if (recurse_call) {
ret.check = ret._is_check_for(ret.white_to_play ? White : Black);
ret.nlm = ret._no_legal_moves_for(ret.white_to_play ? White : Black);
}
return ret;
}
bool Board::insufficient_material_for(Colour current_colour) const {
int n_bishop = 0, n_knight = 0;
for (int i = 0; i < 64; i++) {
Colour colour = colour_at(i);
if (colour != current_colour)
continue;
Piece piece = piece_at(i);
if (piece == Piece::Pawn || piece == Piece::Queen
|| piece == Piece::Rook)
return false;
if (piece == Piece::Bishop)
n_bishop++;
if (piece == Piece::Knigt && colour == Colour::White)
n_knight++;
}
return (n_bishop == 0 && n_knight == 0) || (n_bishop == 1 && n_knight == 0)
|| (n_bishop == 0 && n_knight == 1);
}
int8_t Board::get_king_of(int8_t colour) const {
for (int i = 0; i < 64; i++)
if (squares[i] == (colour | Piece::King))
return i;
std::stringstream ss;
ss << "Apparently there no kings of the such color in this board: "
<< std::endl;
ss << to_fen();
throw std::domain_error(ss.str());
throw std::domain_error(
"Apparently there no kings of the such color in this board"
);
}
bool Board::_is_check_for(Colour colour) const {
bool Board::is_check_for(int8_t colour) const {
int8_t king_idx = this->get_king_of(colour);
std::vector<Move> all_moves;
all_moves.reserve(50);
for (int8_t i = 0; i < 64; i++) {
for (int i = 0; i < 64; i++) {
if (this->squares[i] == Piece::None || colour_at(i) == colour)
continue;
std::vector<int8_t> targets;
if (piece_at(i) == King) {
// special case for the king, because it creates infinite recursion
// (since he looks if he's walking into a check)
@ -343,7 +292,7 @@ bool Board::_is_check_for(Colour colour) const {
for (int dy = -1; dy <= 1; dy++) {
Coords c{king_pos.x + dx, king_pos.y + dy};
if (c.is_within_bounds())
all_moves.push_back(Move{i, c.to_index()});
targets.push_back(c.to_index());
}
}
} else {
@ -353,51 +302,27 @@ bool Board::_is_check_for(Colour colour) const {
Coords::from_index(i),
true
);
all_moves.insert(all_moves.end(), moves.begin(), moves.end());
targets = to_target_square(moves);
}
for (const Move& move : all_moves)
if (move.target_square == king_idx)
if (std::find(targets.begin(), targets.end(), king_idx)
!= targets.end())
return true;
all_moves.clear();
}
return false;
}
bool Board::_no_legal_moves_for(Colour colour) const {
bool Board::no_legal_moves_for(int8_t colour) const {
for (int i = 0; i < 64; i++) {
if (squares[i] == Piece::None || colour_at(i) != colour)
continue;
std::vector<Move> moves;
moves = legal_moves(squares[i], *this, Coords::from_index(i));
if (colour_at(i) == colour) {
std::vector<Move> moves =
legal_moves(squares[i], *this, Coords::from_index(i));
if (moves.size() > 0)
return false;
}
}
return true;
}
bool Board::no_legal_moves() const {
return nlm;
}
bool Board::is_check() const {
return check;
}
bool Board::is_checkmate() const {
return check && nlm;
}
bool Board::is_stalemate() const {
return !check && nlm;
}
bool Board::is_terminal() const {
return n_half_moves == 100 || insufficient_material() || is_checkmate()
|| is_stalemate();
}
std::vector<Move> Board::all_legal_moves() const {
std::vector<Move> ret;
for (int i = 0; i < 64; i++) {
@ -410,28 +335,3 @@ std::vector<Move> Board::all_legal_moves() const {
}
return ret;
}
std::vector<Move> Board::all_capturing_moves() const {
std::vector<Move> moves = all_legal_moves();
std::vector<Move> ret;
ret.reserve(moves.size());
for (const Move& move : moves)
if (piece_at(move.target_square) != Piece::None)
ret.push_back(move);
return ret;
}
std::vector<int8_t> Board::opponent_pawn_attack_map() const {
std::vector<int8_t> ret;
for (int i = 0; i < 64; i++) {
if (piece_at(i) == Piece::Pawn
&& ((colour_at(i) == White && !white_to_play)
|| (colour_at(i) == Black && white_to_play))) {
std::vector<int8_t> attack_map =
pawn_attack_map(*this, Coords::from_index(i));
ret.insert(ret.end(), attack_map.begin(), attack_map.end());
}
}
return ret;
}

View File

@ -9,9 +9,7 @@
struct Board {
private:
int8_t get_king_of(int8_t) const;
bool _no_legal_moves_for(Colour) const;
bool _is_check_for(Colour) const;
bool nlm = false, check = false;
bool no_legal_moves_for(int8_t) const;
public:
int8_t squares[64] = {Piece::None};
@ -19,47 +17,43 @@ struct Board {
int8_t w_castle_rights = CastleSide::NeitherSide;
int8_t b_castle_rights = CastleSide::NeitherSide;
int8_t en_passant_target = -1;
int n_half_moves = 0;
int n_full_moves = 0;
uint8_t n_half_moves = 0;
uint8_t n_full_moves = 0;
static Board setup_fen_position(std::string fen);
Board skip_turn() const;
Board make_move(Move, bool = true) const;
Board make_move(Move) const;
std::string to_fen() const;
bool no_legal_moves() const;
bool is_check() const;
bool insufficient_material_for(Colour) const;
bool insufficient_material() const {
return insufficient_material_for(White)
&& insufficient_material_for(Black);
};
bool is_check_for(int8_t) const;
std::vector<Move> all_legal_moves() const;
std::vector<Move> all_capturing_moves() const;
std::vector<int8_t> opponent_pawn_attack_map() const;
bool is_checkmate() const;
bool is_checkmate_for(int8_t colour) const {
return is_check_for(colour) && no_legal_moves_for(colour);
}
bool is_stalemate() const;
bool is_stalemate_for(int8_t colour) const {
return !is_check_for(colour) && no_legal_moves_for(colour);
}
bool is_terminal() const;
bool is_terminal() const {
return is_checkmate_for(White) || is_checkmate_for(Black)
|| is_stalemate_for(White) || is_stalemate_for(Black);
}
inline Piece piece_at(int8_t idx) const {
Piece piece_at(int8_t idx) const {
return (Piece) (squares[idx] & 0b00111);
}
inline Piece piece_at(Coords xy) const {
Piece piece_at(Coords xy) const {
return piece_at(xy.to_index());
}
inline Colour colour_at(int8_t idx) const {
Colour colour_at(int8_t idx) const {
return (Colour) (squares[idx] & 0b11000);
}
inline Colour colour_at(Coords xy) const {
Colour colour_at(Coords xy) const {
return colour_at(xy.to_index());
}
};

View File

@ -47,7 +47,7 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{3, 2812}, // 11
{4, 43238}, // 157
{5, 674624}, // 2199
{6, 11030083},
// {6, 11030083},
},
},
@ -59,7 +59,7 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{2, 264}, // 1
{3, 9467}, // 69
{4, 422333}, // 3085
{5, 15833292}, // 124452
// {5, 15833292}, // 124452
},
},
@ -71,7 +71,7 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{2, 264}, // 2
{3, 9467}, // 104
{4, 422333}, // 3742
{5, 15833292}, // 136784
// {5, 15833292}, // 136784
},
},
@ -82,7 +82,7 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{1, 44}, // 0
{2, 1486}, // 12
{3, 62379}, // 357
{4, 2103487}, // 13804
// {4, 2103487}, // 13804
// {5, 89941194}, // 1230428
},
},
@ -95,7 +95,7 @@ static std::map<std::string, std::map<int, int>> pos2expected{
{1, 46}, // 0
{2, 2079}, // 16
{3, 89890}, // 602
{4, 3894594}, // 26612
// {4, 3894594}, // 26612
// {5, 164075551}, // 1230428
},
},
@ -141,7 +141,6 @@ int move_generation_test(
} else {
// Regular sequential execution
for (const Move& move : moves) {
// std::cout << "Looking at " << move << std::endl;
Board tmp_board = b.make_move(move);
int n = move_generation_test(tmp_board, depth - 1, max_depth, pool);
if (depth == max_depth)

View File

@ -10,9 +10,8 @@ static bool is_clear_king_side(const Board& b, const Coords xy) {
return false;
std::optional<Move> move = move_for_position(b, xy, c);
Board board_after_move = b.make_move(move.value(), false);
board_after_move = board_after_move.skip_turn();
if (board_after_move.is_check())
Board board_after_move = b.make_move(move.value());
if (board_after_move.is_check_for(b.colour_at(xy)))
return false;
}
return true;
@ -25,9 +24,8 @@ static bool is_clear_queen_side(const Board& b, const Coords xy) {
return false;
std::optional<Move> move = move_for_position(b, xy, c);
Board board_after_move = b.make_move(move.value(), false);
board_after_move = board_after_move.skip_turn();
if (dx < 3 && board_after_move.is_check())
Board board_after_move = b.make_move(move.value());
if (dx < 3 && board_after_move.is_check_for(b.colour_at(xy)))
return false;
}
return true;
@ -48,8 +46,7 @@ std::vector<Move> king_moves(const Board& b, const Coords xy) {
}
}
if (b.is_check())
if (b.is_check_for(b.colour_at(xy)))
return keep_only_blocking(ret, b);
// -- Castles

View File

@ -19,7 +19,7 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
ret.push_back(Move{
xy.to_index(),
left.to_index(),
(Piece) (my_colour | piece)
(int8_t) (my_colour | piece)
});
else
ret.push_back(Move{xy.to_index(), left.to_index()});
@ -39,7 +39,7 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
ret.push_back(Move{
xy.to_index(),
right.to_index(),
(Piece) (my_colour | piece)
(int8_t) (my_colour | piece)
});
else
ret.push_back(Move{xy.to_index(), right.to_index()});
@ -68,7 +68,7 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
ret.push_back(Move{
xy.to_index(),
new_xy.to_index(),
(Piece) (my_colour | piece)
.promoting_to = (int8_t) (my_colour | piece)
});
else
ret.push_back(Move{
@ -79,24 +79,3 @@ std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
return ret;
}
std::vector<int8_t> pawn_attack_map(const Board& b, Coords xy) {
std::vector<int8_t> ret{};
Colour my_colour = b.colour_at(xy);
// -- Capture to the left
if (xy.x > 0) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords left{xy.x - 1, xy.y + dy};
ret.push_back(left.to_index());
}
// -- Capture to the right
if (xy.x < 7) {
int dy = my_colour == Colour::White ? 1 : -1;
Coords right{xy.x + 1, xy.y + dy};
ret.push_back(right.to_index());
}
return ret;
}

View File

@ -9,12 +9,11 @@ keep_only_blocking(const std::vector<Move> candidates, const Board& board) {
if (candidates.size() == 0)
return {};
Colour my_colour = board.colour_at(candidates[0].source_square);
std::vector<Move> ret;
for (Move move : candidates) {
Board board_after_move = board.make_move(move, false);
board_after_move = board_after_move.skip_turn();
if (!board_after_move.is_check())
Board board_after_move = board.make_move(move);
if (!board_after_move.is_check_for(my_colour))
ret.push_back(move);
}
return ret;

View File

@ -50,12 +50,7 @@ inline const char* to_string(Piece c) {
}
}
inline std::ostream& operator<<(std::ostream& os, const Colour& i) {
os << std::to_string(i);
return os;
}
inline std::ostream& operator<<(std::ostream& os, const Piece& i) {
inline std::ostream& operator<<(std::ostream& os, const int8_t& i) {
os << std::to_string(i);
return os;
}
@ -69,7 +64,6 @@ std::vector<Move> keep_only_blocking(const std::vector<Move>, const Board&);
std::optional<Move> move_for_position(const Board&, const Coords, const Coords);
std::vector<Move> look_direction(const Board&, const Coords, int, int);
std::vector<int8_t> pawn_attack_map(const Board&, const Coords);
std::vector<Move> pawn_moves(const Board&, const Coords);
std::vector<Move> rook_moves(const Board&, const Coords);
std::vector<Move> knight_moves(const Board&, const Coords);

View File

@ -1,26 +0,0 @@
#include "move.hpp"
#include "../board/board.hpp"
#include "utils.hpp"
#include <algorithm>
int Move::score_guess(const Board& b) const {
int ret = 0;
Piece me_piece = b.piece_at(source_square);
Piece captured_piece = b.piece_at(target_square);
if (captured_piece != Piece::None)
ret += 10 * piece_value(captured_piece) - piece_value(me_piece);
if (promoting_to != Piece::None)
ret += piece_value(promoting_to);
std::vector<int8_t> pawn_attack_map = b.opponent_pawn_attack_map();
if (std::find(pawn_attack_map.begin(), pawn_attack_map.end(), target_square)
!= pawn_attack_map.end())
ret -= me_piece;
return ret;
}

View File

@ -11,9 +11,7 @@ struct Move {
int8_t source_square;
int8_t target_square;
Piece promoting_to = Piece::None;
int score_guess(const Board&) const;
int8_t promoting_to = Piece::None;
std::string to_string() const {
std::stringstream ss;

View File

@ -1,35 +1,8 @@
#include "utils.hpp"
#include "../board/board.hpp"
std::vector<int8_t> to_target_square(std::vector<Move> moves) {
std::vector<int8_t> ret;
for (Move move : moves)
ret.push_back(move.target_square);
return ret;
}
int piece_value(Piece p) {
switch (p) {
case Piece::Pawn:
return PawnValue;
case Piece::Knigt:
return KnightValue;
case Piece::Bishop:
return BishopValue;
case Piece::Rook:
return RookValue;
case Piece::Queen:
return QueenValue;
default:
return 0;
}
}
int count_material(const Board& b, int8_t colour) {
int ret = 0;
for (int i = 0; i < 64; i++)
if (b.colour_at(i) == colour)
ret += piece_value(b.piece_at(i));
return ret;
}

View File

@ -3,17 +3,6 @@
#include "move.hpp"
#include <cstdint>
#include <limits>
#include <vector>
std::vector<int8_t> to_target_square(std::vector<Move>);
int count_material(const Board&, int8_t);
int piece_value(Piece);
const int INFINITY = std::numeric_limits<int>::max();
const int PawnValue = 100;
const int KnightValue = 300;
const int BishopValue = 320;
const int RookValue = 500;
const int QueenValue = 900;

View File

@ -134,81 +134,6 @@ void GUI::draw_pieces(const Board& board) {
}
}
int GUI::show_popup(
const std::string& message, const std::vector<std::string>& options
) {
sf::RenderWindow popup(sf::VideoMode(300, 200), "Choice");
sf::Font font;
if (!font.loadFromFile("res/arial.ttf")) {
std::cerr << "Error: Could not load font!" << std::endl;
return -1;
}
sf::Text text(message, font, 20);
text.setPosition(20, 20);
text.setFillColor(sf::Color::Black);
std::vector<sf::RectangleShape> buttonShapes;
std::vector<sf::Text> buttonTexts;
for (size_t i = 0; i < options.size(); ++i) {
sf::RectangleShape button(sf::Vector2f(200, 30));
button.setPosition(50, 70 + i * 40);
button.setFillColor(sf::Color(150, 150, 150));
buttonShapes.push_back(button);
sf::Text buttonText(options[i], font, 18);
buttonText.setPosition(60, 75 + i * 40);
buttonText.setFillColor(sf::Color::Black);
buttonTexts.push_back(buttonText);
}
while (popup.isOpen()) {
sf::Event event;
while (popup.pollEvent(event)) {
if (event.type == sf::Event::Closed)
popup.close();
else if (event.type == sf::Event::MouseButtonPressed) {
for (size_t i = 0; i < buttonShapes.size(); ++i) {
if (buttonShapes[i].getGlobalBounds().contains(
event.mouseButton.x,
event.mouseButton.y
)) {
popup.close();
return i;
}
}
}
}
popup.clear(sf::Color::White);
popup.draw(text);
for (size_t i = 0; i < buttonShapes.size(); ++i) {
popup.draw(buttonShapes[i]);
popup.draw(buttonTexts[i]);
}
popup.display();
}
return -1;
}
Piece GUI::ask_about_promotion() {
std::vector<std::string> options = {"Queen", "Rook", "Bishop", "Knight"};
int idx = show_popup("Please choose a promotion for your pawn", options);
switch (idx) {
case 0:
return Queen;
case 1:
return Rook;
case 2:
return Bishop;
case 3:
return Knigt;
};
return Piece::None;
}
void GUI::show() {
while (window.isOpen())
handle_events();

View File

@ -15,7 +15,6 @@ class GUI : public View {
GUI();
void show() override;
Piece ask_about_promotion();
void update_board(const Board&, int8_t, std::vector<int8_t>) override;
void notify_checkmate(Colour) override;
void notify_stalemate(Colour) override;
@ -28,10 +27,6 @@ class GUI : public View {
sf::Color colours[2] = {sf::Color(0xB88762FF), sf::Color(0xEDD6B0FF)};
sf::Color alt_colours[2] = {sf::Color(0xDCC34BFF), sf::Color(0xF6EB72FF)};
int show_popup(
const std::string& message, const std::vector<std::string>& options
);
void load_textures();
void handle_events();
void handle_click(int, int);

View File

@ -9,8 +9,4 @@ class NoOpView : public View {
void update_board(const Board&, int8_t, std::vector<int8_t>) override {};
void notify_checkmate(Colour) override{};
void notify_stalemate(Colour) override{};
Piece ask_about_promotion() override {
return Queen;
};
};

View File

@ -15,7 +15,6 @@ class View {
}
virtual void show() = 0;
virtual Piece ask_about_promotion() = 0;
virtual void update_board(const Board&, int8_t, std::vector<int8_t>) = 0;
virtual void notify_checkmate(Colour) = 0;
virtual void notify_stalemate(Colour) = 0;

View File

@ -1,4 +1,4 @@
#include "../src/model/board/board.hpp"
#include "../src/board.hpp"
#include "lib.hpp"
int main() {

View File

@ -1,4 +1,4 @@
#include "../src/model/utils/coords.hpp"
#include "../src/coords.hpp"
#include "lib.hpp"
int main() {