Compare commits
36 Commits
04134f013c
...
0145664567
Author | SHA1 | Date | |
---|---|---|---|
|
0145664567 | ||
|
959ecdeb7d | ||
|
adf21ee021 | ||
|
989225c2d7 | ||
|
aef6fc39e3 | ||
|
2260cd918a | ||
|
3bc5b75f1e | ||
|
377e5dfdcc | ||
|
10de9828be | ||
|
9942832b18 | ||
|
5b45fc32c0 | ||
|
d28008d913 | ||
|
86beb9cc85 | ||
|
8edde1a8b1 | ||
|
182d183247 | ||
|
1b14ed693d | ||
|
a80fc9482d | ||
|
88b2d01cf8 | ||
|
979d44fad0 | ||
|
db87c2ec8b | ||
|
f3e28d2646 | ||
|
7e888bba61 | ||
|
1d83c066f6 | ||
|
2764787e63 | ||
|
c71cabb3cd | ||
|
0525bd565e | ||
|
695cef33df | ||
|
75b6c35cc5 | ||
|
74fa99fe7b | ||
|
0274f44647 | ||
|
a5abe39aa4 | ||
|
e056ef0805 | ||
|
dc6631f79c | ||
|
d6aa977a15 | ||
|
6e567f2f11 | ||
|
fced9757c2 |
@ -1,4 +1,5 @@
|
||||
CXXFLAGS += -O3 -Wall
|
||||
# CXXFLAGS += -pg
|
||||
|
||||
# Add .d to Make's recognized suffixes.
|
||||
SUFFIXES += .d
|
||||
@ -28,7 +29,7 @@ obj/%.o:
|
||||
$(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
main: $(OBJFILES)
|
||||
$(CXX) $(LDFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main -lsfml-graphics -lsfml-window -lsfml-system
|
||||
$(CXX) $(CXXFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main -lsfml-graphics -lsfml-window -lsfml-system
|
||||
|
||||
clean:
|
||||
rm -rf obj/* $(DEPFILES) test_bin/
|
||||
@ -46,7 +47,7 @@ LIBS := $(filter-out obj/main.o,$(OBJFILES))
|
||||
test_bin/%: tests/%.cpp $(LIBS)
|
||||
@echo $(LIBS)
|
||||
@mkdir -p $(dir $@)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS) -lsfml-graphics -lsfml-window -lsfml-system
|
||||
|
||||
# The 'test' target builds all tests and then runs each one.
|
||||
.PHONY: test
|
||||
|
@ -14,7 +14,8 @@ void AIvsAIController::start() {
|
||||
ai::AI* current_player;
|
||||
while (!board.is_terminal()) {
|
||||
current_player = board.white_to_play ? &p1 : &p2;
|
||||
Move move = current_player->search(board, board.white_to_play);
|
||||
std::cout << typeid(*current_player).name() << " to play" << std::endl;
|
||||
Move move = current_player->search(board);
|
||||
make_move(move);
|
||||
}
|
||||
|
||||
@ -23,13 +24,14 @@ 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_for(current_colour))
|
||||
if (board.is_checkmate())
|
||||
view.notify_checkmate(current_colour);
|
||||
|
||||
if (board.is_stalemate_for(current_colour))
|
||||
if (board.is_stalemate())
|
||||
view.notify_stalemate(current_colour);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ ManualController::ManualController(Board b, View& v): Controller(b, v) {
|
||||
}
|
||||
|
||||
void ManualController::start() {
|
||||
reset_selection();
|
||||
view.show();
|
||||
}
|
||||
|
||||
@ -50,13 +51,24 @@ 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_for(current_colour))
|
||||
if (board.is_checkmate())
|
||||
view.notify_checkmate(current_colour);
|
||||
|
||||
if (board.is_stalemate_for(current_colour))
|
||||
if (board.is_stalemate())
|
||||
view.notify_stalemate(current_colour);
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
#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"
|
||||
@ -9,26 +11,31 @@
|
||||
#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 =
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
"r3k2r/p1ppqpb1/Bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPB1PPP/R3K2R b KQkq - 0 3";
|
||||
|
||||
Board b = Board::setup_fen_position(pos);
|
||||
|
||||
ai::v0_random p1(std::chrono::milliseconds(500));
|
||||
ai::v0_random p2(std::chrono::milliseconds(500));
|
||||
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));
|
||||
|
||||
GUI gui;
|
||||
// GUI gui;
|
||||
NoOpView 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;
|
||||
}
|
||||
|
@ -1,17 +1,44 @@
|
||||
#include "ai.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <ostream>
|
||||
#include <thread>
|
||||
|
||||
Move ai::AI::search(const Board& b, bool am_white) {
|
||||
Move ai::AI::search(const Board& b) {
|
||||
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([&]() { result = _search(b, am_white); });
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
// Start a timer thread to stop computation after given time
|
||||
std::thread timer_thread([&]() {
|
||||
std::this_thread::sleep_for(thinking_time);
|
||||
stop_computation = true;
|
||||
std::unique_lock<std::mutex> lock(cv_mutex);
|
||||
if (!cv.wait_for(lock, thinking_time, [&] {
|
||||
return stop_computation.load();
|
||||
})) {
|
||||
// Timeout reached; set stop flag
|
||||
stop_computation = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for computation thread to finish
|
||||
@ -19,5 +46,7 @@ Move ai::AI::search(const Board& b, bool am_white) {
|
||||
|
||||
// Ensure timer thread is also stopped
|
||||
timer_thread.join();
|
||||
|
||||
std::cout << "Took " << elapsed << " ms" << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
@ -8,36 +8,72 @@
|
||||
namespace ai {
|
||||
class AI {
|
||||
protected:
|
||||
bool am_white;
|
||||
std::chrono::milliseconds thinking_time;
|
||||
virtual Move _search(const Board&, bool = false) = 0;
|
||||
virtual Move _search(const Board&) = 0;
|
||||
|
||||
public:
|
||||
AI(std::chrono::milliseconds tt): thinking_time(tt) {}
|
||||
AI(bool am_white, std::chrono::milliseconds tt)
|
||||
: am_white(am_white),
|
||||
thinking_time(tt) {}
|
||||
|
||||
std::atomic<bool> stop_computation = false;
|
||||
|
||||
Move search(const Board& b, bool am_white = false);
|
||||
Move search(const Board& b);
|
||||
|
||||
virtual int eval(const Board&) = 0;
|
||||
};
|
||||
|
||||
struct v0_random : public AI {
|
||||
v0_random(std::chrono::milliseconds tt): AI(tt) {}
|
||||
v0_random(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
|
||||
|
||||
Move _search(const Board&, bool) override;
|
||||
Move _search(const Board&) override;
|
||||
|
||||
int eval(const Board&) override {
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
class v1_simple : public AI {
|
||||
class v1_pure_minimax : public AI { // looks two moves ahead
|
||||
int _search(const Board&, int);
|
||||
|
||||
public:
|
||||
v1_simple(std::chrono::milliseconds tt): AI(tt) {}
|
||||
v1_pure_minimax(bool w, std::chrono::milliseconds tt): AI(w, tt) {}
|
||||
|
||||
Move _search(const Board&, bool) override;
|
||||
Move _search(const Board&) 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
|
||||
|
@ -2,8 +2,12 @@
|
||||
|
||||
#include <thread>
|
||||
|
||||
Move ai::v0_random::_search(const Board& b, bool) {
|
||||
Move ai::v0_random::_search(const Board& b) {
|
||||
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()];
|
||||
}
|
||||
|
91
cpp/src/model/ais/v1_pure_minimax.cpp
Normal file
91
cpp/src/model/ais/v1_pure_minimax.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#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;
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
#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;
|
||||
}
|
92
cpp/src/model/ais/v2_alpha_beta.cpp
Normal file
92
cpp/src/model/ais/v2_alpha_beta.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
#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;
|
||||
}
|
99
cpp/src/model/ais/v3_AB_ordering.cpp
Normal file
99
cpp/src/model/ais/v3_AB_ordering.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
#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;
|
||||
}
|
61
cpp/src/model/ais/v4_search_captures.cpp
Normal file
61
cpp/src/model/ais/v4_search_captures.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#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;
|
||||
}
|
@ -5,9 +5,11 @@
|
||||
#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) {
|
||||
@ -92,6 +94,8 @@ 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;
|
||||
}
|
||||
@ -111,7 +115,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 (squares[rank * 8 + file] == Piece::None) {
|
||||
if (piece_at({file, rank}) == Piece::None) {
|
||||
empty_cell_counter++;
|
||||
continue;
|
||||
}
|
||||
@ -169,7 +173,14 @@ std::string Board::to_fen() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
Board Board::make_move(Move move) const {
|
||||
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 ret;
|
||||
std::copy(
|
||||
std::begin(this->squares),
|
||||
@ -266,24 +277,64 @@ Board Board::make_move(Move move) 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;
|
||||
throw std::domain_error(
|
||||
"Apparently there no kings of the such color in this board"
|
||||
);
|
||||
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());
|
||||
}
|
||||
|
||||
bool Board::is_check_for(int8_t colour) const {
|
||||
bool Board::_is_check_for(Colour colour) const {
|
||||
int8_t king_idx = this->get_king_of(colour);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
std::vector<Move> all_moves;
|
||||
all_moves.reserve(50);
|
||||
for (int8_t 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)
|
||||
@ -292,7 +343,7 @@ bool Board::is_check_for(int8_t colour) const {
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
Coords c{king_pos.x + dx, king_pos.y + dy};
|
||||
if (c.is_within_bounds())
|
||||
targets.push_back(c.to_index());
|
||||
all_moves.push_back(Move{i, c.to_index()});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -302,27 +353,51 @@ bool Board::is_check_for(int8_t colour) const {
|
||||
Coords::from_index(i),
|
||||
true
|
||||
);
|
||||
targets = to_target_square(moves);
|
||||
|
||||
all_moves.insert(all_moves.end(), moves.begin(), moves.end());
|
||||
}
|
||||
if (std::find(targets.begin(), targets.end(), king_idx)
|
||||
!= targets.end())
|
||||
return true;
|
||||
|
||||
for (const Move& move : all_moves)
|
||||
if (move.target_square == king_idx)
|
||||
return true;
|
||||
all_moves.clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Board::no_legal_moves_for(int8_t colour) const {
|
||||
bool Board::_no_legal_moves_for(Colour colour) const {
|
||||
for (int i = 0; i < 64; 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;
|
||||
}
|
||||
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 (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++) {
|
||||
@ -335,3 +410,28 @@ 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;
|
||||
}
|
||||
|
@ -9,7 +9,9 @@
|
||||
struct Board {
|
||||
private:
|
||||
int8_t get_king_of(int8_t) const;
|
||||
bool no_legal_moves_for(int8_t) const;
|
||||
bool _no_legal_moves_for(Colour) const;
|
||||
bool _is_check_for(Colour) const;
|
||||
bool nlm = false, check = false;
|
||||
|
||||
public:
|
||||
int8_t squares[64] = {Piece::None};
|
||||
@ -17,43 +19,47 @@ struct Board {
|
||||
int8_t w_castle_rights = CastleSide::NeitherSide;
|
||||
int8_t b_castle_rights = CastleSide::NeitherSide;
|
||||
int8_t en_passant_target = -1;
|
||||
uint8_t n_half_moves = 0;
|
||||
uint8_t n_full_moves = 0;
|
||||
int n_half_moves = 0;
|
||||
int n_full_moves = 0;
|
||||
|
||||
|
||||
static Board setup_fen_position(std::string fen);
|
||||
|
||||
Board make_move(Move) const;
|
||||
Board skip_turn() const;
|
||||
Board make_move(Move, bool = true) const;
|
||||
std::string to_fen() const;
|
||||
bool is_check_for(int8_t) 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);
|
||||
};
|
||||
|
||||
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_for(int8_t colour) const {
|
||||
return is_check_for(colour) && no_legal_moves_for(colour);
|
||||
}
|
||||
bool is_checkmate() const;
|
||||
|
||||
bool is_stalemate_for(int8_t colour) const {
|
||||
return !is_check_for(colour) && no_legal_moves_for(colour);
|
||||
}
|
||||
bool is_stalemate() const;
|
||||
|
||||
bool is_terminal() const {
|
||||
return is_checkmate_for(White) || is_checkmate_for(Black)
|
||||
|| is_stalemate_for(White) || is_stalemate_for(Black);
|
||||
}
|
||||
bool is_terminal() const;
|
||||
|
||||
Piece piece_at(int8_t idx) const {
|
||||
inline Piece piece_at(int8_t idx) const {
|
||||
return (Piece) (squares[idx] & 0b00111);
|
||||
}
|
||||
|
||||
Piece piece_at(Coords xy) const {
|
||||
inline Piece piece_at(Coords xy) const {
|
||||
return piece_at(xy.to_index());
|
||||
}
|
||||
|
||||
Colour colour_at(int8_t idx) const {
|
||||
inline Colour colour_at(int8_t idx) const {
|
||||
return (Colour) (squares[idx] & 0b11000);
|
||||
}
|
||||
|
||||
Colour colour_at(Coords xy) const {
|
||||
inline Colour colour_at(Coords xy) const {
|
||||
return colour_at(xy.to_index());
|
||||
}
|
||||
};
|
||||
|
@ -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},
|
||||
},
|
||||
},
|
||||
|
||||
@ -55,11 +55,11 @@ static std::map<std::string, std::map<int, int>> pos2expected{
|
||||
{
|
||||
"r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 4",
|
||||
{
|
||||
{1, 6}, // 0
|
||||
{2, 264}, // 1
|
||||
{3, 9467}, // 69
|
||||
{4, 422333}, // 3085
|
||||
// {5, 15833292}, // 124452
|
||||
{1, 6}, // 0
|
||||
{2, 264}, // 1
|
||||
{3, 9467}, // 69
|
||||
{4, 422333}, // 3085
|
||||
{5, 15833292}, // 124452
|
||||
},
|
||||
},
|
||||
|
||||
@ -67,11 +67,11 @@ static std::map<std::string, std::map<int, int>> pos2expected{
|
||||
{
|
||||
"r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 5",
|
||||
{
|
||||
{1, 6}, // 0
|
||||
{2, 264}, // 2
|
||||
{3, 9467}, // 104
|
||||
{4, 422333}, // 3742
|
||||
// {5, 15833292}, // 136784
|
||||
{1, 6}, // 0
|
||||
{2, 264}, // 2
|
||||
{3, 9467}, // 104
|
||||
{4, 422333}, // 3742
|
||||
{5, 15833292}, // 136784
|
||||
},
|
||||
},
|
||||
|
||||
@ -79,10 +79,10 @@ static std::map<std::string, std::map<int, int>> pos2expected{
|
||||
{
|
||||
"rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 6",
|
||||
{
|
||||
{1, 44}, // 0
|
||||
{2, 1486}, // 12
|
||||
{3, 62379}, // 357
|
||||
// {4, 2103487}, // 13804
|
||||
{1, 44}, // 0
|
||||
{2, 1486}, // 12
|
||||
{3, 62379}, // 357
|
||||
{4, 2103487}, // 13804
|
||||
// {5, 89941194}, // 1230428
|
||||
},
|
||||
},
|
||||
@ -92,10 +92,10 @@ static std::map<std::string, std::map<int, int>> pos2expected{
|
||||
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 "
|
||||
"7",
|
||||
{
|
||||
{1, 46}, // 0
|
||||
{2, 2079}, // 16
|
||||
{3, 89890}, // 602
|
||||
// {4, 3894594}, // 26612
|
||||
{1, 46}, // 0
|
||||
{2, 2079}, // 16
|
||||
{3, 89890}, // 602
|
||||
{4, 3894594}, // 26612
|
||||
// {5, 164075551}, // 1230428
|
||||
},
|
||||
},
|
||||
@ -141,6 +141,7 @@ 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)
|
||||
|
@ -10,8 +10,9 @@ 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());
|
||||
if (board_after_move.is_check_for(b.colour_at(xy)))
|
||||
Board board_after_move = b.make_move(move.value(), false);
|
||||
board_after_move = board_after_move.skip_turn();
|
||||
if (board_after_move.is_check())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -24,8 +25,9 @@ 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());
|
||||
if (dx < 3 && board_after_move.is_check_for(b.colour_at(xy)))
|
||||
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())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -46,7 +48,8 @@ std::vector<Move> king_moves(const Board& b, const Coords xy) {
|
||||
}
|
||||
}
|
||||
|
||||
if (b.is_check_for(b.colour_at(xy)))
|
||||
|
||||
if (b.is_check())
|
||||
return keep_only_blocking(ret, b);
|
||||
|
||||
// -- Castles
|
||||
|
@ -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(),
|
||||
(int8_t) (my_colour | piece)
|
||||
(Piece) (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(),
|
||||
(int8_t) (my_colour | piece)
|
||||
(Piece) (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(),
|
||||
.promoting_to = (int8_t) (my_colour | piece)
|
||||
(Piece) (my_colour | piece)
|
||||
});
|
||||
else
|
||||
ret.push_back(Move{
|
||||
@ -79,3 +79,24 @@ 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;
|
||||
}
|
||||
|
@ -9,11 +9,12 @@ 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);
|
||||
if (!board_after_move.is_check_for(my_colour))
|
||||
Board board_after_move = board.make_move(move, false);
|
||||
board_after_move = board_after_move.skip_turn();
|
||||
|
||||
if (!board_after_move.is_check())
|
||||
ret.push_back(move);
|
||||
}
|
||||
return ret;
|
||||
|
@ -50,7 +50,12 @@ inline const char* to_string(Piece c) {
|
||||
}
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& os, const int8_t& i) {
|
||||
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) {
|
||||
os << std::to_string(i);
|
||||
return os;
|
||||
}
|
||||
@ -64,6 +69,7 @@ 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);
|
||||
|
26
cpp/src/model/utils/move.cpp
Normal file
26
cpp/src/model/utils/move.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#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;
|
||||
}
|
@ -11,7 +11,9 @@ struct Move {
|
||||
int8_t source_square;
|
||||
int8_t target_square;
|
||||
|
||||
int8_t promoting_to = Piece::None;
|
||||
Piece promoting_to = Piece::None;
|
||||
|
||||
int score_guess(const Board&) const;
|
||||
|
||||
std::string to_string() const {
|
||||
std::stringstream ss;
|
||||
|
@ -1,8 +1,35 @@
|
||||
#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;
|
||||
}
|
||||
|
@ -3,6 +3,17 @@
|
||||
#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;
|
||||
|
@ -134,6 +134,81 @@ 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();
|
||||
|
@ -15,6 +15,7 @@ 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;
|
||||
@ -27,6 +28,10 @@ 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);
|
||||
|
@ -9,4 +9,8 @@ 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;
|
||||
};
|
||||
};
|
||||
|
@ -15,6 +15,7 @@ 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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "../src/board.hpp"
|
||||
#include "../src/model/board/board.hpp"
|
||||
#include "lib.hpp"
|
||||
|
||||
int main() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "../src/coords.hpp"
|
||||
#include "../src/model/utils/coords.hpp"
|
||||
#include "lib.hpp"
|
||||
|
||||
int main() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user