mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:37:37 +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
|
||||
{
|
||||
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; }
|
||||
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();
|
||||
|
|
|
@ -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&) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue