mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:17:44 +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:
parent
b000a884c8
commit
cf8fce368a
5 changed files with 85 additions and 0 deletions
|
@ -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
|
bool ChessWidget::export_pgn(const StringView& export_path) const
|
||||||
{
|
{
|
||||||
auto file_or_error = Core::File::open(export_path, Core::File::WriteOnly);
|
auto file_or_error = Core::File::open(export_path, Core::File::WriteOnly);
|
||||||
|
|
|
@ -62,6 +62,7 @@ public:
|
||||||
void set_drag_enabled(bool e) { m_drag_enabled = e; }
|
void set_drag_enabled(bool e) { m_drag_enabled = e; }
|
||||||
RefPtr<Gfx::Bitmap> get_piece_graphic(const Chess::Piece& piece) const;
|
RefPtr<Gfx::Bitmap> get_piece_graphic(const Chess::Piece& piece) const;
|
||||||
|
|
||||||
|
String get_fen() const;
|
||||||
bool export_pgn(const StringView& export_path) const;
|
bool export_pgn(const StringView& export_path) const;
|
||||||
|
|
||||||
void resign();
|
void resign();
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <LibGUI/AboutDialog.h>
|
#include <LibGUI/AboutDialog.h>
|
||||||
#include <LibGUI/ActionGroup.h>
|
#include <LibGUI/ActionGroup.h>
|
||||||
#include <LibGUI/Application.h>
|
#include <LibGUI/Application.h>
|
||||||
|
#include <LibGUI/Clipboard.h>
|
||||||
#include <LibGUI/FilePicker.h>
|
#include <LibGUI/FilePicker.h>
|
||||||
#include <LibGUI/Icon.h>
|
#include <LibGUI/Icon.h>
|
||||||
#include <LibGUI/Menu.h>
|
#include <LibGUI/Menu.h>
|
||||||
|
@ -121,6 +122,10 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
dbgln("Exported PGN file to {}", export_path.value());
|
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_separator();
|
||||||
|
|
||||||
app_menu.add_action(GUI::Action::create("New game", { Mod_None, Key_F2 }, [&](auto&) {
|
app_menu.add_action(GUI::Action::create("New game", { Mod_None, Key_F2 }, [&](auto&) {
|
||||||
|
|
|
@ -205,6 +205,73 @@ Board::Board()
|
||||||
set_piece(Square("h8"), { Colour::Black, Type::Rook });
|
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
|
Piece Board::get_piece(const Square& square) const
|
||||||
{
|
{
|
||||||
ASSERT(square.rank < 8);
|
ASSERT(square.rank < 8);
|
||||||
|
@ -474,6 +541,7 @@ bool Board::apply_illegal_move(const Move& move, Colour colour)
|
||||||
|
|
||||||
m_last_move = move;
|
m_last_move = move;
|
||||||
m_moves_since_capture++;
|
m_moves_since_capture++;
|
||||||
|
m_moves_since_pawn_advance++;
|
||||||
|
|
||||||
if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
|
if (move.from == Square("a1") || move.to == Square("a1") || move.from == Square("e1"))
|
||||||
m_white_can_castle_queenside = false;
|
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) {
|
if (get_piece(move.to).colour != Colour::None) {
|
||||||
const_cast<Move&>(move).is_capture = true;
|
const_cast<Move&>(move).is_capture = true;
|
||||||
m_moves_since_capture = 0;
|
m_moves_since_capture = 0;
|
||||||
|
|
|
@ -139,6 +139,8 @@ public:
|
||||||
bool apply_move(const Move&, Colour colour = Colour::None);
|
bool apply_move(const Move&, Colour colour = Colour::None);
|
||||||
const Optional<Move>& last_move() const { return m_last_move; }
|
const Optional<Move>& last_move() const { return m_last_move; }
|
||||||
|
|
||||||
|
String to_fen() const;
|
||||||
|
|
||||||
enum class Result {
|
enum class Result {
|
||||||
CheckMate,
|
CheckMate,
|
||||||
StaleMate,
|
StaleMate,
|
||||||
|
@ -180,6 +182,7 @@ private:
|
||||||
Colour m_resigned { Colour::None };
|
Colour m_resigned { Colour::None };
|
||||||
Optional<Move> m_last_move;
|
Optional<Move> m_last_move;
|
||||||
int m_moves_since_capture { 0 };
|
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_kingside { true };
|
||||||
bool m_white_can_castle_queenside { true };
|
bool m_white_can_castle_queenside { true };
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue