37 Commits

Author SHA1 Message Date
472f9e4c7c made CastleSide a normal enum again to use &
Some checks failed
tagged-release / Tagged Release (push) Has been cancelled
2025-02-02 21:30:37 +01:00
f5292fa6d7 added small comment 2025-02-02 21:22:38 +01:00
8a3a92f80f minor change 2025-02-02 21:14:31 +01:00
b7ad7b3111 implemented king legal moves 2025-02-02 21:14:14 +01:00
b329c41bea got rid of magic numbers 2025-02-02 21:13:30 +01:00
1625345f08 fixed board.cpp 2025-02-02 20:28:50 +01:00
a790677bb7 implemented queen legal moves 2025-02-02 20:28:41 +01:00
703dcef59f implemented knight legal moves 2025-02-02 20:24:42 +01:00
d74222c2f4 implemented rook legal moves 2025-02-02 20:19:07 +01:00
01c912435b implemented bishop legal moves 2025-02-02 20:17:36 +01:00
8bf164cb05 extracted coords to its own hpp 2025-02-02 20:17:27 +01:00
e08dbb913e fixed board and pawn moves 2025-02-02 19:49:21 +01:00
fea3f6c98a fixed signature 2025-02-02 18:24:23 +01:00
da55f0085f implemented (partially) the pawn move 2025-02-02 18:17:37 +01:00
95327ec653 cleaned up main 2025-02-02 18:17:19 +01:00
1a4e33201e when loading a fen ref, i return the object and
not a pointer
2025-02-02 18:17:02 +01:00
d6baf1ee53 added main to gitignore 2025-02-02 18:16:45 +01:00
67e377bfde made some seperation of concerns 2025-02-02 17:16:37 +01:00
29453fbb14 implemented some stuff 2025-02-02 17:13:55 +01:00
585e392b6a created move struct 2025-02-02 16:49:24 +01:00
d436c5a032 made stuff a tiny bit more compact 2025-02-02 16:49:10 +01:00
08c0a3b50b added test support to the makefile 2025-02-02 16:37:59 +01:00
0ae37a3eba did some stuff to make makefile work... it was
painful
2025-02-02 16:26:46 +01:00
c83129a0d5 added cpp gitignore 2025-02-02 15:31:45 +01:00
acfa27c83e removed binary files 2025-02-02 15:21:04 +01:00
85a5bfa328 moved some stuff around 2025-02-02 15:15:14 +01:00
139a5c7d8f fixed missing imports
Some checks are pending
pre-release / Pre Release (push) Waiting to run
2025-02-02 14:54:17 +01:00
3a2988d351 fixed permissions of fields 2025-02-02 14:53:17 +01:00
c758d1854f removed useless imports 2025-02-02 14:52:47 +01:00
53a0755547 extracted the assert equals to another file to
have mulitple test files
2025-02-02 14:52:13 +01:00
4792daf127 cpp board can go back and forth from FEN
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
2025-02-02 14:50:44 +01:00
947114877b moved everything related to python in the python
Some checks failed
pre-release / Pre Release (push) Waiting to run
tagged-release / Tagged Release (push) Has been cancelled
folder
2025-02-02 13:20:59 +01:00
166e1c7664 added some positions to test the move generation 2025-02-02 13:18:25 +01:00
84d73511d2 fixed a bunch of issues with the move generation 2025-02-02 12:58:05 +01:00
4bb068b2a5 added complete FEN support both for reading and writing 2025-02-01 18:34:41 +01:00
92e1ff26fc added pawn promotion 2025-02-01 16:43:02 +01:00
c7884e227b made the initial board a memeber of the board
Some checks failed
pre-release / Pre Release (push) Has been cancelled
module
2025-02-01 10:37:02 +01:00
52 changed files with 1135 additions and 35 deletions

34
cpp/.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.appobj/
test_bin/
main

56
cpp/Makefile Normal file
View File

@ -0,0 +1,56 @@
# Add .d to Make's recognized suffixes.
SUFFIXES += .d
#We don't need to clean up when we're making these targets
NODEPS:=clean tags svn
#Find all the C++ files in the src/ directory
SOURCES:=$(shell find src/ -name "*.cpp")
OBJFILES := $(patsubst src/%.cpp,obj/%.o,$(SOURCES))
#These are the dependency files, which make will clean up after it creates them
DEPFILES:=$(patsubst %.cpp,%.d,$(SOURCES))
#Don't create dependencies when we're cleaning, for instance
ifeq (0, $(words $(findstring $(MAKECMDGOALS), $(NODEPS))))
#Chances are, these files don't exist. GMake will create them and
#clean up automatically afterwards
-include $(DEPFILES)
endif
#This is the rule for creating the dependency files
src/%.d: src/%.cpp
$(CXX) $(CXXFLAGS) -MM -MT '$(patsubst src/%.cpp,obj/%.o,$<)' $< -MF $@
#This rule does the compilation
obj/%.o:
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ -c $<
main: $(OBJFILES)
$(CXX) $(LDFLAGS) $(OBJFILES) $(LOADLIBES) $(LDLIBS) -o main
clean:
rm -rf obj/* $(DEPFILES) test_bin/
# --- Test Support ---
# Find all test source files in the tests directory
TESTS := $(shell find tests -name "*.cpp")
# Define corresponding test executable names, e.g. tests/foo.cpp -> test_bin/foo
TEST_BIN := $(patsubst tests/%.cpp,test_bin/%,$(TESTS))
LIBS := $(filter-out obj/main.o,$(OBJFILES))
# Pattern rule: how to build a test executable from a test source file.
# You can adjust CXXFLAGS or add include directories if needed.
test_bin/%: tests/%.cpp $(LIBS)
@echo $(LIBS)
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -o $@ $< $(LIBS)
# The 'test' target builds all tests and then runs each one.
.PHONY: test
test: $(TEST_BIN)
@echo "Running all tests..."
@for t in $(TEST_BIN); do \
echo "---- Running $$t ----"; \
./$$t || exit 1; \
done

131
cpp/src/board.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "board.hpp"
#include "coords.hpp"
#include "pieces/piece.hpp"
#include <algorithm>
#include <cctype>
#include <map>
#include <stdexcept>
Board Board::setup_fen_position(std::string fen) {
Board board;
std::map<char, Piece> c2p{
{'k', Piece::King},
{'p', Piece::Pawn},
{'n', Piece::Knigt},
{'b', Piece::Bishop},
{'r', Piece::Rook},
{'q', Piece::Queen},
};
std::string fen_board = fen.substr(0, fen.find(' '));
int rank = 7, file = 0;
for (char symbol : fen_board) {
if (symbol == '/') {
file = 0;
rank--;
continue;
}
if (std::isdigit(symbol))
file += symbol - '0';
else {
Colour colour =
std::isupper(symbol) ? Colour::White : Colour::Black;
Piece piece = c2p[std::tolower(symbol)];
board.squares[rank * 8 + file] = colour | piece;
file++;
}
}
return board;
}
std::string Board::to_fen() const {
std::map<int, char> p2c{
{Piece::King, 'k'},
{Piece::Pawn, 'p'},
{Piece::Knigt, 'n'},
{Piece::Bishop, 'b'},
{Piece::Rook, 'r'},
{Piece::Queen, 'q'},
};
std::string ret;
for (int rank = 7; rank >= 0; rank--) {
int empty_cell_counter = 0;
for (int file = 0; file < 8; file++) {
if (this->squares[rank * 8 + file] == Piece::None) {
empty_cell_counter++;
continue;
}
int full_piece = this->squares[rank * 8 + file];
char piece = p2c[full_piece & 0b111];
int8_t colour = colour_at({file, rank});
if (empty_cell_counter > 0) {
ret += std::to_string(empty_cell_counter);
empty_cell_counter = 0;
}
ret += colour == Colour::White ? std::toupper(piece) : piece;
}
if (empty_cell_counter > 0)
ret += std::to_string(empty_cell_counter);
if (rank > 0)
ret += "/";
}
return ret;
}
Board Board::make_move(Move move) const {
int8_t dest_piece = this->squares[move.target_square];
Board ret;
std::copy(
std::begin(this->squares),
std::end(this->squares),
std::begin(ret.squares)
);
ret.white_to_play = !this->white_to_play;
// -- Actually make the move
ret.squares[move.source_square] = Piece::None;
ret.squares[move.target_square] = this->squares[move.source_square];
// -- Handle en passant target being eaten
if (move.en_passant)
ret.squares[move.target_square - 8] = Piece::None;
return ret;
}
int8_t Board::get_king_of(int8_t colour) const {
for (int i = 0; i < 64; i++)
if (this->squares[i] == (colour | Piece::King))
return i;
throw std::domain_error(
"Apparently there no kings of the such color in this board"
);
}
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;
}
bool Board::is_check_for(int8_t colour) const {
int8_t king_idx = this->get_king_of(colour);
for (int i = 0; i < 64; i++) {
if (this->squares[i] == Piece::None)
continue;
std::vector<Move> moves =
legal_moves(this->squares[i], *this, Coords::from_index(i), true);
std::vector<int8_t> targets = to_target_square(moves);
if (std::find(targets.begin(), targets.end(), i) != targets.end())
return true;
}
return false;
}

32
cpp/src/board.hpp Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include "coords.hpp"
#include "move.hpp"
#include "pieces/piece.hpp"
#include <string>
struct Board {
private:
int8_t get_king_of(int8_t colour) const;
public:
int8_t squares[64] = {Piece::None};
bool white_to_play;
CastleSide w_castle_rights;
CastleSide b_castle_rights;
static Board setup_fen_position(std::string fen);
Board make_move(Move) const;
std::string to_fen() const;
bool is_check_for(int8_t) const;
int8_t colour_at(int8_t idx) const {
return squares[idx] & 0b11000;
}
int8_t colour_at(Coords xy) const {
return colour_at(xy.to_index());
}
};

7
cpp/src/castle_side.hpp Normal file
View File

@ -0,0 +1,7 @@
#include <cstdint>
enum CastleSide : int8_t {
NeitherSide = 0,
KingSide = 1,
QueenSide = 2,
};

19
cpp/src/coords.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
struct Coords {
int x, y;
int8_t to_index() const {
return this->y * 8 + this->x;
}
static Coords from_index(int idx) {
return {idx % 8, idx / 8};
}
bool is_within_bounds() const {
return this->to_index() < 64;
}
};

10
cpp/src/main.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "board.hpp"
#include <iostream>
int main(int argc, char* argv[]) {
std::string pos =
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
Board b = Board::setup_fen_position(pos);
return 0;
}

15
cpp/src/move.hpp Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include "castle_side.hpp"
#include <cstdint>
struct Move {
int8_t source_square;
int8_t target_square;
bool is_capturing = false;
CastleSide castle_side = CastleSide::NeitherSide;
bool en_passant = false;
int8_t promoting_to = 0;
};

23
cpp/src/pieces/bishop.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "../board.hpp"
#include "../coords.hpp"
#include "../move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> bishop_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
auto ne = look_direction(b, xy, 1, 1);
ret.insert(ret.end(), ne.begin(), ne.end());
auto se = look_direction(b, xy, 1, -1);
ret.insert(ret.end(), se.begin(), se.end());
auto sw = look_direction(b, xy, -1, -1);
ret.insert(ret.end(), sw.begin(), sw.end());
auto nw = look_direction(b, xy, -1, 1);
ret.insert(ret.end(), nw.begin(), nw.end());
return ret;
}

76
cpp/src/pieces/king.cpp Normal file
View File

@ -0,0 +1,76 @@
#include "../board.hpp"
#include "../coords.hpp"
#include "piece.hpp"
static bool is_clear_king_side(const Board& b, const Coords xy) {
for (int dx = 1; dx < 3; dx++) {
Coords c{xy.x + dx, xy.y};
if (b.squares[c.to_index()] != Piece::None)
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)))
return false;
}
return true;
}
static bool is_clear_queen_side(const Board& b, const Coords xy) {
for (int dx = 1; dx < 4; dx++) {
Coords c{xy.x - dx, xy.y};
if (b.squares[c.to_index()] != Piece::None)
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)))
return false;
}
return true;
}
std::vector<Move> king_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
// -- Regular moves
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) // skip staying in the same position
continue;
Coords c{xy.x + dx, xy.y + dy};
std::optional<Move> move = move_for_position(b, xy, c);
if (move.has_value())
ret.push_back(move.value());
}
}
if (b.is_check_for(b.colour_at(xy)))
return ret;
// -- Castles
CastleSide castling_rights = b.colour_at(xy) == Colour::White
? b.w_castle_rights
: b.b_castle_rights;
if (castling_rights == CastleSide::NeitherSide)
return ret;
if (castling_rights & CastleSide::KingSide && is_clear_king_side(b, xy)) {
ret.push_back(Move{
xy.to_index(),
Coords{6, xy.y}.to_index(),
.castle_side = CastleSide::KingSide
});
}
if (castling_rights & CastleSide::QueenSide && is_clear_queen_side(b, xy)) {
ret.push_back(Move{
xy.to_index(),
Coords{2, xy.y}.to_index(),
.castle_side = CastleSide::QueenSide
});
}
return ret;
}

28
cpp/src/pieces/knight.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "../board.hpp"
#include "../coords.hpp"
#include "../move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> knight_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
std::vector<std::pair<int, int>> moves = {
{+2, +1},
{+1, +2}, // north east
{+2, -1},
{+1, -2}, // south east
{-2, -1},
{-1, -2}, // south west
{-2, +1},
{-1, +2} // north west
};
for (const auto& [dx, dy] : moves) {
std::optional<Move> move =
move_for_position(b, xy, Coords{xy.x + dx, xy.y + dy});
if (move.has_value())
ret.push_back(move.value());
}
return ret;
}

51
cpp/src/pieces/pawn.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "../board.hpp"
#include "../coords.hpp"
#include "../move.hpp"
#include "piece.hpp"
std::vector<Move> pawn_moves(const Board& b, const Coords xy) {
std::vector<Move> ret{};
int8_t 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};
int8_t capturable_piece = b.squares[left.to_index()];
if (capturable_piece != 0) {
if (my_colour != b.colour_at(left))
ret.push_back(Move{xy.to_index(), 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};
int8_t capturable_piece = b.squares[right.to_index()];
if (capturable_piece != 0) {
if (my_colour != b.colour_at(right))
ret.push_back(Move{xy.to_index(), right.to_index()});
}
}
// -- Promotion
bool is_on_starting_rank =
my_colour == Colour::White ? xy.y == 1 : xy.y == 6;
int max_dy = is_on_starting_rank ? 3 : 2;
for (int dy = 1; dy < max_dy; dy++) {
int actual_dy = my_colour == Colour::White ? dy : -dy;
Coords new_xy{xy.x, xy.y + actual_dy};
if (b.squares[new_xy.to_index()] != Piece::None)
break;
if (new_xy.y == 7 || new_xy.y == 0)
for (auto piece : {Queen, Knigt, Bishop, Rook})
ret.push_back(Move{
xy.to_index(),
new_xy.to_index(),
.promoting_to = piece
});
}
return ret;
}

72
cpp/src/pieces/piece.cpp Normal file
View File

@ -0,0 +1,72 @@
#include "piece.hpp"
#include "../board.hpp"
#include "../coords.hpp"
std::vector<Move>
keep_only_blocking(const std::vector<Move> candidates, const Board& board) {
if (candidates.size() == 0)
return {};
int8_t 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))
ret.push_back(move);
}
return ret;
}
std::vector<Move> legal_moves(
int8_t p, const Board& b, const Coords xy, bool looking_for_check = false
) {
std::vector<Move> ret;
switch (p) {
case Piece::Pawn:
ret = pawn_moves(b, xy);
case Piece::Bishop:
break;
default:
break;
}
if (!looking_for_check)
return keep_only_blocking(ret, b);
return {};
}
std::optional<Move>
move_for_position(const Board& board, const Coords source, const Coords dest) {
if (dest.is_within_bounds())
return {};
int8_t piece = board.squares[dest.to_index()];
if (piece == Piece::None)
return Move{source.to_index(), dest.to_index()};
int8_t source_colour = board.colour_at(source);
int8_t dest_colour = board.colour_at(dest);
if (source_colour != dest_colour)
return Move{source.to_index(), dest.to_index(), true};
return {};
}
std::vector<Move>
look_direction(const Board& board, const Coords xy, int mult_dx, int mult_dy) {
std::vector<Move> ret;
for (int d = 0; d < 8; d++) {
int dx = mult_dx * d;
int dy = mult_dy * d;
std::optional<Move> move =
move_for_position(board, xy, Coords{xy.x + dx, xy.y + dy});
if (move.has_value()) {
ret.push_back(move.value());
if (move.value().is_capturing)
break;
}
}
return ret;
}

37
cpp/src/pieces/piece.hpp Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include "../move.hpp"
#include <cstdint>
#include <optional>
#include <vector>
enum Piece : int8_t {
None = 0,
King = 1,
Pawn = 2,
Knigt = 3,
Bishop = 4,
Rook = 5,
Queen = 6,
};
enum Colour : int8_t {
White = 8,
Black = 16,
};
class Board;
struct Coords;
std::vector<Move> legal_moves(int8_t, const Board&, const Coords, bool);
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<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);
std::vector<Move> bishop_moves(const Board&, const Coords);
std::vector<Move> queen_moves(const Board&, const Coords);
std::vector<Move> king_moves(const Board&, const Coords);

35
cpp/src/pieces/queen.cpp Normal file
View File

@ -0,0 +1,35 @@
#include "../board.hpp"
#include "../coords.hpp"
#include "../move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> rook_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
auto e = look_direction(b, xy, 1, 0);
ret.insert(ret.end(), e.begin(), e.end());
auto s = look_direction(b, xy, 0, -1);
ret.insert(ret.end(), s.begin(), s.end());
auto w = look_direction(b, xy, -1, 0);
ret.insert(ret.end(), w.begin(), w.end());
auto n = look_direction(b, xy, 0, 1);
ret.insert(ret.end(), n.begin(), n.end());
auto ne = look_direction(b, xy, 1, 1);
ret.insert(ret.end(), ne.begin(), ne.end());
auto se = look_direction(b, xy, 1, -1);
ret.insert(ret.end(), se.begin(), se.end());
auto sw = look_direction(b, xy, -1, -1);
ret.insert(ret.end(), sw.begin(), sw.end());
auto nw = look_direction(b, xy, -1, 1);
ret.insert(ret.end(), nw.begin(), nw.end());
return ret;
}

23
cpp/src/pieces/rook.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "../board.hpp"
#include "../coords.hpp"
#include "../move.hpp"
#include "piece.hpp"
#include <vector>
std::vector<Move> rook_moves(const Board& b, const Coords xy) {
std::vector<Move> ret;
auto e = look_direction(b, xy, 1, 0);
ret.insert(ret.end(), e.begin(), e.end());
auto s = look_direction(b, xy, 0, -1);
ret.insert(ret.end(), s.begin(), s.end());
auto w = look_direction(b, xy, -1, 0);
ret.insert(ret.end(), w.begin(), w.end());
auto n = look_direction(b, xy, 0, 1);
ret.insert(ret.end(), n.begin(), n.end());
return ret;
}

0
cpp/src/stickfosh.cpp Normal file
View File

22
cpp/tests/fen.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "../src/board.hpp"
#include "lib.hpp"
int main() {
std::string pos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos)->to_fen());
pos = "r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos)->to_fen());
pos = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos)->to_fen());
pos = "4k2r/6r1/8/8/8/8/3R4/R3K3";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos)->to_fen());
pos = "8/8/8/4p1K1/2k1P3/8/8/8";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos)->to_fen());
pos = "8/5k2/3p4/1p1Pp2p/pP2Pp1P/P4P1K/8/8";
ASSERT_EQUALS(pos, Board::setup_fen_position(pos)->to_fen());
}

12
cpp/tests/lib.hpp Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <iostream>
#define ASSERT_EQUALS(expected, actual) \
{ \
if (expected != actual) \
std::cout << "Expected: " << std::endl \
<< '\t' << expected << std::endl \
<< "Got: " << std::endl \
<< '\t' << actual << std::endl; \
}

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 734 B

After

Width:  |  Height:  |  Size: 734 B

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1014 B

After

Width:  |  Height:  |  Size: 1014 B

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

169
python/src/ai/ai.py Normal file
View File

@ -0,0 +1,169 @@
from collections import defaultdict
import time
from tqdm import tqdm
from logic.board import INITIAL_BOARD, Board
from logic.move import Move
from logic.pieces.piece import Colour
pos2expected = {
# -- Position 1
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": {
1: 20,
2: 400,
3: 8_902,
4: 197_281,
},
# -- Position 2
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1": {
1: 48,
2: 2_039,
3: 97_862,
4: 4_085_603,
},
# -- Position 3
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1": {
1: 14,
2: 191,
3: 2_812,
4: 43_238,
5: 674_624,
},
# -- Position 4
"r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1": {
1: 6,
2: 264,
3: 9467,
4: 422_333,
},
# -- Position 5
"rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8": {
1: 44,
2: 1486,
3: 62379,
4: 2103487,
},
# -- Position 6
"r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10": {
1: 46,
2: 2079,
3: 89890,
4: 3894594,
},
}
tick = "\033[92m✔\033[0m" # Green Tick
cross = "\033[91m❌\033[0m" # Red Cross
res = defaultdict(lambda : 0)
def peft(pos: str):
global res
expected = pos2expected[pos]
board = Board.setup_FEN_position(pos)
for depth in expected:
with tqdm(total=expected[depth], desc=f"Depth: {depth}") as bar:
start = time.process_time()
moves = move_generation_test(bar, board, depth, depth)
bar.close()
elapsed = time.process_time() - start
elapsed *= 1_000
print("Depth:", depth, end=" ")
print("Result:", moves, end=" ")
if moves == expected[depth]:
print(f"{tick}", end=" ")
else:
print(f"{cross} (expected {expected[depth]})", end=" ")
print("positions Time:", int(elapsed), "milliseconds")
if moves != expected[depth]:
print()
for key, value in res.items():
print(f"{key}: {value}")
def move_generation_test(bar, board: Board, depth: int, max_depth, first = None):
global res
if first is None:
res = defaultdict(lambda : 0)
if board.is_terminal():
# bar.update(1)
# res[first] += 1
return 0
if depth == 0:
res[first] += 1
bar.update(1)
return 1
moves = board.legal_moves()
if depth == 1:
res[first] += len(moves)
bar.update(len(moves))
return len(moves)
num_pos = 0
for move in moves:
tmp_board = board.make_move(move)
if first is None:
first = move.piece.pos.to_algebraic() + move.pos.to_algebraic()
if first == "f7h8":
print(tmp_board.legal_moves())
num_pos += move_generation_test(bar, tmp_board, depth - 1, max_depth, first = first)
if depth == max_depth:
first = None
return num_pos
def play_game(board: Board, strategies: dict, verbose: bool = False) -> Board:
"""Play a turn-taking game. `strategies` is a {player_name: function} dict,
where function(state) is used to get the player's move."""
state = board
move_counter = 1
while not (board.is_checkmate_for(board._turn) or board.is_stalemate_for(board._turn)):
player = board._turn
move = strategies[player](state)
state = board.make_move(move)
if verbose:
if player == Colour.WHITE:
print(str(move_counter) + ".", move, end=" ")
else:
print(move)
return state
def minmax_search(state: Board) -> tuple[float, Move]:
"""Search game tree to determine best move; return (value, move) pair."""
return _max_value(state) if state._turn == Colour.WHITE else _min_value(state)
def _max_value(state: Board) -> tuple[float, Move]:
if state.is_terminal():
return state.utility(), None
v, move = -float("inf"), None
for a in state.legal_moves():
v2, _ = _min_value(state.make_move(a))
if v2 > v:
v, move = v2, a
return v, move
def _min_value(state: Board) -> tuple[float, Move]:
if state.is_terminal():
return state.utility(), None
v, move = float("inf"), None
for a in state.legal_moves():
v2, _ = _min_value(state.make_move(a))
if v2 < v:
v, move = v2, a
return v, move

View File

@ -18,6 +18,8 @@ class Board:
self._white_castling_rights = set() self._white_castling_rights = set()
self._black_castling_rights = set() self._black_castling_rights = set()
self._en_passant_target = None self._en_passant_target = None
self._n_moves = 0
self._n_half_moves = 0
@staticmethod @staticmethod
def _piece_class_from_char(c: str) -> Type[Piece]: def _piece_class_from_char(c: str) -> Type[Piece]:
@ -83,6 +85,8 @@ class Board:
for c in position[index:]: for c in position[index:]:
index += 1 index += 1
if c == "-" or c == " ": if c == "-" or c == " ":
if c == "-":
index += 1
break break
sides = "kq" sides = "kq"
@ -98,7 +102,24 @@ class Board:
# -- En passant target # -- En passant target
if position[index] != "-": if position[index] != "-":
ret._en_passant_target = position[index:index+2] pos = Position.from_algebraic(position[index:index+2])
index += 2
if pos.y == 2:
pos.y += 1
assert pos in ret._white, "En passant target is not in the position"
ret._en_passant_target = ret._white[pos]
elif pos.y == 5:
pos.y -= 1
assert pos in ret._black, "En passant target is not in the position"
ret._en_passant_target = ret._black[pos]
else:
raise ValueError("You can't have a en passant target that is not on the third or sixth rank")
else:
index += 1
index += 1
ret._n_half_moves = int(position[index:position.find(" ", index + 1)])
ret._n_moves = int(position[position.find(" ", index)+1:])
return ret return ret
@ -180,10 +201,22 @@ class Board:
if move.pos in other_pieces: if move.pos in other_pieces:
del other_pieces[move.pos] del other_pieces[move.pos]
if piece.colour == Colour.BLACK:
ret._n_moves = self._n_moves + 1
if move.is_capturing or type(piece) == Pawn:
ret._n_half_moves = 0
else:
ret._n_half_moves = self._n_half_moves + 1
if move.en_passant: if move.en_passant:
pos_to_remove = Position(move.pos.x, move.pos.y + (1 if self._turn == Colour.BLACK else -1)) pos_to_remove = Position(move.pos.x, move.pos.y + (1 if self._turn == Colour.BLACK else -1))
del other_pieces[pos_to_remove] del other_pieces[pos_to_remove]
if move.promotes_to is not None:
assert type(piece) == Pawn, "Trying to promote something that is not a pawn: not good!"
pieces_moving[move.pos] = move.promotes_to(move.pos, piece.colour)
# -- Set en passant target if needed # -- Set en passant target if needed
if move.becomes_en_passant_target: if move.becomes_en_passant_target:
ret._en_passant_target = pieces_moving[move.pos] ret._en_passant_target = pieces_moving[move.pos]
@ -215,6 +248,12 @@ class Board:
ret._white_castling_rights.remove(CastleSide.Queen) ret._white_castling_rights.remove(CastleSide.Queen)
elif piece.pos.x == 7 and CastleSide.King in ret._white_castling_rights: elif piece.pos.x == 7 and CastleSide.King in ret._white_castling_rights:
ret._white_castling_rights.remove(CastleSide.King) ret._white_castling_rights.remove(CastleSide.King)
if move.is_capturing and move.pos.y == 7 and move.pos in self._black and type(self._black[move.pos]) == Rook:
if move.pos.x == 0 and CastleSide.Queen in ret._black_castling_rights:
ret._black_castling_rights.remove(CastleSide.Queen)
elif move.pos.x == 7 and CastleSide.King in ret._black_castling_rights:
ret._black_castling_rights.remove(CastleSide.King)
else: else:
if type(piece) == King: if type(piece) == King:
ret._black_castling_rights = set() ret._black_castling_rights = set()
@ -225,5 +264,97 @@ class Board:
elif piece.pos.x == 7 and CastleSide.King in ret._black_castling_rights: elif piece.pos.x == 7 and CastleSide.King in ret._black_castling_rights:
ret._black_castling_rights.remove(CastleSide.King) ret._black_castling_rights.remove(CastleSide.King)
if move.is_capturing and move.pos.y == 0 and move.pos in self._white and type(self._white[move.pos]) == Rook:
if move.pos.x == 0 and CastleSide.Queen in ret._white_castling_rights:
ret._white_castling_rights.remove(CastleSide.Queen)
elif move.pos.x == 7 and CastleSide.King in ret._white_castling_rights:
ret._white_castling_rights.remove(CastleSide.King)
return ret return ret
def to_fen_string(self):
ret = ""
for y in range(7, -1, -1):
empty_cell_counter = 0
for x in range(8):
pos = Position(x, y)
piece = None
if pos in self._white:
piece = self._white[pos]
elif pos in self._black:
piece = self._black[pos]
if piece is None:
empty_cell_counter += 1
continue
if empty_cell_counter > 0:
ret += str(empty_cell_counter)
empty_cell_counter = 0
letter = piece.letter()
ret += letter.lower() if piece.colour == Colour.BLACK else letter.upper()
if empty_cell_counter > 0:
ret += str(empty_cell_counter)
if y > 0:
ret += "/"
ret += " "
ret += "w" if self._turn == Colour.WHITE else "b"
ret += " "
if len(self._white_castling_rights) == 0 and len(self._black_castling_rights) == 0:
ret += "-"
else:
if CastleSide.King in self._white_castling_rights:
ret += "K"
if CastleSide.Queen in self._white_castling_rights:
ret += "Q"
if CastleSide.King in self._black_castling_rights:
ret += "k"
if CastleSide.Queen in self._black_castling_rights:
ret += "q"
ret += " "
if self._en_passant_target is not None:
pos = Position(self._en_passant_target.pos.x, self._en_passant_target.pos.y)
pos.y += -1 if self._en_passant_target.colour == Colour.WHITE else 1
ret += pos.to_algebraic()
else:
ret += "-"
ret += " "
ret += str(self._n_half_moves)
ret += " "
ret += str(self._n_moves)
return ret
def legal_moves(self) -> list[Move]:
ret = []
pieces = self._white if self._turn == Colour.WHITE else self._black
for piece in pieces.values():
ret += piece.legal_moves(self)
return ret
def is_terminal(self) -> bool:
return self.is_stalemate_for(Colour.WHITE) or self.is_stalemate_for(Colour.BLACK) or self.is_checkmate_for(Colour.WHITE) or self.is_checkmate_for(Colour.BLACK)
def utility(self) -> int:
if self.is_stalemate_for(Colour.WHITE) or self.is_stalemate_for(Colour.BLACK):
return 0
if self.is_checkmate_for(Colour.WHITE):
return 1
if self.is_checkmate_for(Colour.BLACK):
return -1
raise ValueError("Cannot determine the utility of board become it neither checkmate nor stalemate for either players")
_fen_pos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
INITIAL_BOARD = Board.setup_FEN_position(_fen_pos)

View File

@ -1,4 +1,5 @@
# from logic.pieces.piece import Piece # from logic.pieces.piece import Piece
from typing import Type
from logic.position import Position from logic.position import Position
from enum import Enum from enum import Enum
@ -8,13 +9,14 @@ class CastleSide(Enum):
Queen = "O-O-O" Queen = "O-O-O"
class Move: class Move:
def __init__(self, piece: "Piece", pos: Position,/, is_capturing: bool = False, castle_side: CastleSide = CastleSide.Neither, en_passant: bool = False, becomes_en_passant_target: bool = False) -> None: def __init__(self, piece: "Piece", pos: Position,/, is_capturing: bool = False, castle_side: CastleSide = CastleSide.Neither, en_passant: bool = False, becomes_en_passant_target: bool = False, promotes_to: Type["Piece"] = None) -> None:
self.piece = piece self.piece = piece
self.pos = pos self.pos = pos
self.is_capturing = is_capturing self.is_capturing = is_capturing
self.castle_side = castle_side self.castle_side = castle_side
self.becomes_en_passant_target = becomes_en_passant_target self.becomes_en_passant_target = becomes_en_passant_target
self.en_passant = en_passant self.en_passant = en_passant
self.promotes_to = promotes_to
def to_algebraic(self) -> str: def to_algebraic(self) -> str:
raise NotImplementedError("The move can't be translated to algbraic notation, as it was not implemented") raise NotImplementedError("The move can't be translated to algbraic notation, as it was not implemented")
@ -30,10 +32,19 @@ class Move:
return "O-O-O" return "O-O-O"
ret = "" ret = ""
if type(self.piece).__name__ != "Pawn": if type(self.piece).__name__ == "Pawn":
if self.is_capturing:
ret += self.piece.pos.to_algebraic()[0]
ret += "x"
ret += self.pos.to_algebraic()
else:
ret += self.pos.to_algebraic()
else:
ret += self.piece.letter().upper() ret += self.piece.letter().upper()
if self.is_capturing:
ret += "x"
ret += str(self.pos) ret += str(self.pos)
return ret return ret
def __repr__(self) -> str: def __repr__(self) -> str:

View File

@ -17,7 +17,7 @@ class Bishop(Piece):
# looking north west # looking north west
ret.extend(self._look_direction(board, -1, 1)) ret.extend(self._look_direction(board, -1, 1))
if not looking_for_check and board.is_check_for(self.colour): if not looking_for_check:# and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board) return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -47,7 +47,7 @@ class King(Piece):
if CastleSide.Queen in castling_rights: if CastleSide.Queen in castling_rights:
clear = True clear = True
for dx in range(1, 3): for dx in range(1, 4):
x = self.pos.x - dx x = self.pos.x - dx
y = self.pos.y y = self.pos.y
@ -57,7 +57,7 @@ class King(Piece):
move = self._move_for_position(board, x, y) move = self._move_for_position(board, x, y)
board_after_move = board.make_move(move) board_after_move = board.make_move(move)
if board_after_move.is_check_for(self.colour): if dx < 3 and board_after_move.is_check_for(self.colour):
clear = False clear = False
break break

View File

@ -16,7 +16,7 @@ class Knight(Piece):
if move is not None: if move is not None:
ret.append(move) ret.append(move)
if not looking_for_check and board.is_check_for(self.colour): if not looking_for_check:# and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board) return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -1,5 +1,9 @@
from logic.move import Move from logic.move import Move
from logic.pieces.bishop import Bishop
from logic.pieces.knight import Knight
from logic.pieces.piece import Colour, Piece from logic.pieces.piece import Colour, Piece
from logic.pieces.queen import Queen
from logic.pieces.rook import Rook
from logic.position import Position from logic.position import Position
class Pawn(Piece): class Pawn(Piece):
@ -13,6 +17,10 @@ class Pawn(Piece):
(self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x - 1, self.pos.y - 1))) (self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x - 1, self.pos.y - 1)))
): ):
if capturable_piece.colour != self.colour: if capturable_piece.colour != self.colour:
if (self.colour == Colour.WHITE and capturable_piece.pos.y == 7) or (self.colour == Colour.BLACK and capturable_piece.pos.y == 0):
for piece in [Queen, Knight, Bishop, Rook]:
ret.append(Move(self, capturable_piece.pos, is_capturing=True, promotes_to=piece))
else:
ret.append(Move(self, capturable_piece.pos, is_capturing = True)) ret.append(Move(self, capturable_piece.pos, is_capturing = True))
# can we capture to the right? # can we capture to the right?
@ -22,6 +30,10 @@ class Pawn(Piece):
(self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x + 1, self.pos.y - 1))) (self.colour == Colour.BLACK and (capturable_piece := board.piece_at(self.pos.x + 1, self.pos.y - 1)))
): ):
if capturable_piece.colour != self.colour: if capturable_piece.colour != self.colour:
if (self.colour == Colour.WHITE and capturable_piece.pos.y == 7) or (self.colour == Colour.BLACK and capturable_piece.pos.y == 0):
for piece in [Queen, Knight, Bishop, Rook]:
ret.append(Move(self, capturable_piece.pos, is_capturing=True, promotes_to=piece))
else:
ret.append(Move(self, capturable_piece.pos, is_capturing = True)) ret.append(Move(self, capturable_piece.pos, is_capturing = True))
# -- Can we capture en passant? # -- Can we capture en passant?
@ -38,18 +50,31 @@ class Pawn(Piece):
# -- Normal moves # -- Normal moves
if self.colour == Colour.WHITE: if self.colour == Colour.WHITE:
for dy in range(1, 3 if self.pos.y == 1 else 2): for dy in range(1, 3 if self.pos.y == 1 else 2):
if self.pos.y + dy > 7 or board.piece_at(self.pos.x, self.pos.y + dy): y = self.pos.y + dy
if y > 7 or board.piece_at(self.pos.x, y):
break break
pos = Position(self.pos.x, self.pos.y + dy) pos = Position(self.pos.x, y)
if y == 7:
for piece in [Queen, Knight, Bishop, Rook]:
ret.append(Move(self, pos, promotes_to=piece))
else:
ret.append(Move(self, pos, becomes_en_passant_target=dy==2)) ret.append(Move(self, pos, becomes_en_passant_target=dy==2))
else: else:
for dy in range(1, 3 if self.pos.y == 6 else 2): for dy in range(1, 3 if self.pos.y == 6 else 2):
if self.pos.y - dy < 0 or board.piece_at(self.pos.x, self.pos.y - dy): y = self.pos.y - dy
if y < 0 or board.piece_at(self.pos.x, y):
break break
pos = Position(self.pos.x, self.pos.y - dy) pos = Position(self.pos.x, y)
if y == 0:
for piece in [Queen, Knight, Bishop, Rook]:
ret.append(Move(self, pos, promotes_to=piece))
else:
ret.append(Move(self, pos, becomes_en_passant_target=dy==2)) ret.append(Move(self, pos, becomes_en_passant_target=dy==2))
if not looking_for_check and board.is_check_for(self.colour): if not looking_for_check:# and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board) return self.keep_only_blocking(ret, board)
return ret return ret
def letter(self):
return "p"

View File

@ -29,7 +29,7 @@ class Queen(Piece):
# looking north # looking north
ret.extend(self._look_direction(board, 0, 1)) ret.extend(self._look_direction(board, 0, 1))
if not looking_for_check and board.is_check_for(self.colour): if not looking_for_check:# and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board) return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -17,7 +17,7 @@ class Rook(Piece):
# looking north # looking north
ret.extend(self._look_direction(board, 0, 1)) ret.extend(self._look_direction(board, 0, 1))
if not looking_for_check and board.is_check_for(self.colour): if not looking_for_check:# and board.is_check_for(self.colour):
return self.keep_only_blocking(ret, board) return self.keep_only_blocking(ret, board)
return ret return ret

View File

@ -17,6 +17,14 @@ class Position:
return x >= Position._MIN_POS and x <= Position._MAX_POS \ return x >= Position._MIN_POS and x <= Position._MAX_POS \
and y >= Position._MIN_POS and y <= Position._MAX_POS and y >= Position._MIN_POS and y <= Position._MAX_POS
@staticmethod
def from_algebraic(square: str) -> "Position":
assert len(square) == 2, f"'{square}' is malformed"
x = Position._FILES.index(square[0])
y = Position._RANKS.index(int(square[1]))
return Position(x, y)
def to_algebraic(self) -> str: def to_algebraic(self) -> str:
return f"{Position._FILES[self.x]}{Position._RANKS[self.y]}" return f"{Position._FILES[self.x]}{Position._RANKS[self.y]}"

27
python/src/main.py Normal file
View File

@ -0,0 +1,27 @@
import time
from pprint import pprint
from tqdm import tqdm
from ai.ai import move_generation_test
from controller.controller import Controller
from logic.board import INITIAL_BOARD, Board
from logic.position import Position
from view.gui import GUI
from view.tui import TUI
from ai.ai import peft
if __name__ == "__main__":
board = INITIAL_BOARD
pos = "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"
board = Board.setup_FEN_position(pos)
view = GUI()
controller = Controller(board, view)
# view.show()
# exit()
peft(pos)

30
python/tests/fen.py Normal file
View File

@ -0,0 +1,30 @@
import unittest
import sys
sys.path.append('src') # you must execute pytest from the stickfosh dir for this to work
from logic.board import Board
class FENTests(unittest.TestCase):
def testInitialPosition(self):
pos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string())
def testRandomPositions(self):
pos = "r1bk3r/p2pBpNp/n4n2/1p1NP2P/6P1/3P4/P1P1K3/q5b1 b Qk - 0 1"
self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string())
pos = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string())
pos = "4k2r/6r1/8/8/8/8/3R4/R3K3 w Qk - 0 1"
self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string())
pos = "8/8/8/4p1K1/2k1P3/8/8/8 b - - 0 1"
self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string())
pos = "8/5k2/3p4/1p1Pp2p/pP2Pp1P/P4P1K/8/8 b - - 99 50"
self.assertEqual(pos, Board.setup_FEN_position(pos).to_fen_string())

30
python/tests/position.py Normal file
View File

@ -0,0 +1,30 @@
import unittest
import sys
sys.path.append('src') # you must execute pytest from the stickfosh dir for this to work
from logic.position import Position
class PositionTests(unittest.TestCase):
def testXY2Algebraic(self):
self.assertEqual(Position(0, 0).to_algebraic(), "a1")
self.assertEqual(Position(1, 0).to_algebraic(), "b1")
self.assertEqual(Position(2, 1).to_algebraic(), "c2")
self.assertEqual(Position(4, 2).to_algebraic(), "e3")
self.assertEqual(Position(7, 7).to_algebraic(), "h8")
def testAlgebraic2XY(self):
self.assertEqual(Position.from_algebraic("a1"), Position(0, 0))
self.assertEqual(Position.from_algebraic("b1"), Position(1, 0))
self.assertEqual(Position.from_algebraic("c2"), Position(2, 1))
self.assertEqual(Position.from_algebraic("e3"), Position(4, 2))
self.assertEqual(Position.from_algebraic("h8"), Position(7, 7))
self.assertRaises(AssertionError, lambda : Position.from_algebraic("a11"))
self.assertRaises(ValueError, lambda : Position.from_algebraic("j1"))
self.assertRaises(ValueError, lambda : Position.from_algebraic("a9"))

View File

@ -1,14 +0,0 @@
from controller.controller import Controller
from logic.board import Board
from view.gui import GUI
from view.tui import TUI
if __name__ == "__main__":
initial_board_position = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
board = Board.setup_FEN_position(initial_board_position)
view = GUI()
controller = Controller(board, view)
view.show()