1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 22:17:45 +00:00

Chess: Added ability to copy board state as FEN

You can now copy the board state as Forsyth-Edwards Notation. You can
then paste this into other chess programs/games, or into ours when
it gets implemented.
This commit is contained in:
AnicJov 2020-12-10 16:44:38 +01:00 committed by Andreas Kling
parent b000a884c8
commit cf8fce368a
5 changed files with 85 additions and 0 deletions

View file

@ -292,6 +292,11 @@ void ChessWidget::maybe_input_engine_move()
});
}
String ChessWidget::get_fen() const
{
return m_board.to_fen();
}
bool ChessWidget::export_pgn(const StringView& export_path) const
{
auto file_or_error = Core::File::open(export_path, Core::File::WriteOnly);

View file

@ -62,6 +62,7 @@ public:
void set_drag_enabled(bool e) { m_drag_enabled = e; }
RefPtr<Gfx::Bitmap> get_piece_graphic(const Chess::Piece& piece) const;
String get_fen() const;
bool export_pgn(const StringView& export_path) const;
void resign();

View file

@ -30,6 +30,7 @@
#include <LibGUI/AboutDialog.h>
#include <LibGUI/ActionGroup.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Menu.h>
@ -121,6 +122,10 @@ int main(int argc, char** argv)
dbgln("Exported PGN file to {}", export_path.value());
}));
app_menu.add_action(GUI::Action::create("Copy FEN", { Mod_Ctrl, Key_C }, [&](auto&) {
GUI::Clipboard::the().set_data(widget.get_fen().bytes());
GUI::MessageBox::show(window, "Board state copied to clipboard as FEN.", "Copy FEN", GUI::MessageBox::Type::Information);
}));
app_menu.add_separator();
app_menu.add_action(GUI::Action::create("New game", { Mod_None, Key_F2 }, [&](auto&) {

View file

@ -205,6 +205,73 @@ Board::Board()
set_piece(Square("h8"), { Colour::Black, Type::Rook });
}
String Board::to_fen() const
{
StringBuilder builder;
// 1. Piece placement
int empty = 0;
for (unsigned rank = 0; rank < 8; rank++) {
for (unsigned file = 0; file < 8; file++) {
const Piece p(get_piece({ 7 - rank, file }));
if (p.type == Type::None) {
empty++;
continue;
}
if (empty > 0) {
builder.append(String::number(empty));
empty = 0;
}
String piece = char_for_piece(p.type);
if (piece == "")
piece = "P";
builder.append(p.colour == Colour::Black ? piece.to_lowercase() : piece);
}
if (empty > 0) {
builder.append(String::number(empty));
empty = 0;
}
if (rank < 7)
builder.append("/");
}
// 2. Active color
ASSERT(m_turn != Colour::None);
builder.append(m_turn == Colour::White ? " w " : " b ");
// 3. Castling availability
builder.append(m_white_can_castle_kingside ? "K" : "");
builder.append(m_white_can_castle_queenside ? "Q" : "");
builder.append(m_black_can_castle_kingside ? "k" : "");
builder.append(m_black_can_castle_queenside ? "q" : "");
builder.append(" ");
// 4. En passant target square
if (!m_last_move.has_value())
builder.append("-");
else if (m_last_move.value().piece.type == Type::Pawn) {
if (m_last_move.value().from.rank == 1 && m_last_move.value().to.rank == 3)
builder.append(Square(m_last_move.value().to.rank - 1, m_last_move.value().to.file).to_algebraic());
else if (m_last_move.value().from.rank == 6 && m_last_move.value().to.rank == 4)
builder.append(Square(m_last_move.value().to.rank + 1, m_last_move.value().to.file).to_algebraic());
else
builder.append("-");
} else {
builder.append("-");
}
builder.append(" ");
// 5. Halfmove clock
builder.append(String::number(min(m_moves_since_capture, m_moves_since_pawn_advance)));
builder.append(" ");
// 6. Fullmove number
builder.append(String::number(1 + m_moves.size() / 2));
return builder.to_string();
}
Piece Board::get_piece(const Square& square) const
{
ASSERT(square.rank < 8);
@ -474,6 +541,7 @@ bool Board::apply_illegal_move(const Move& move, Colour colour)
m_last_move = move;
m_moves_since_capture++;
m_moves_since_pawn_advance++;
if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
m_white_can_castle_queenside = false;
@ -514,6 +582,9 @@ bool Board::apply_illegal_move(const Move& move, Colour colour)
}
}
if (move.piece.type == Type::Pawn)
m_moves_since_pawn_advance = 0;
if (get_piece(move.to).colour != Colour::None) {
const_cast<Move&>(move).is_capture = true;
m_moves_since_capture = 0;

View file

@ -139,6 +139,8 @@ public:
bool apply_move(const Move&, Colour colour = Colour::None);
const Optional<Move>& last_move() const { return m_last_move; }
String to_fen() const;
enum class Result {
CheckMate,
StaleMate,
@ -180,6 +182,7 @@ private:
Colour m_resigned { Colour::None };
Optional<Move> m_last_move;
int m_moves_since_capture { 0 };
int m_moves_since_pawn_advance { 0 };
bool m_white_can_castle_kingside { true };
bool m_white_can_castle_queenside { true };