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

Chess: Add ability to export game as PGN file

This patch adds an option to the menubar for exporting the current
game as a PGN file. This file can then be read by other chess
programs (and ours eventually) to replay the game or analyze it.
The implementation is mostly PGN spec compliant, however the code
could use some more work. Particularly the `const_cast`s...
But it's a start. :^)

Fixup: Chess: Fixed hard-coded home path in unveil() call

Fixup: Chess: Removed castling flags from Move struct

The castling detection logic is done inside Move::to_algebraic()
now, removing the need for is_castle_short and is_castle_long flags
inside of the Move struct.
This commit is contained in:
AnicJov 2020-12-04 14:26:26 +01:00 committed by Andreas Kling
parent 01b62cc7f4
commit fe1628746c
5 changed files with 205 additions and 11 deletions

View file

@ -27,9 +27,12 @@
#include "ChessWidget.h"
#include "PromotionDialog.h"
#include <AK/String.h>
#include <LibCore/DateTime.h>
#include <LibCore/File.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Font.h>
#include <unistd.h>
ChessWidget::ChessWidget(const StringView& set)
{
@ -289,6 +292,57 @@ void ChessWidget::maybe_input_engine_move()
});
}
bool ChessWidget::export_pgn(const StringView& export_path) const
{
auto file_or_error = Core::File::open(export_path, Core::File::WriteOnly);
if (file_or_error.is_error()) {
warnln("Couldn't open '{}': {}", export_path, file_or_error.error());
return false;
}
auto& file = *file_or_error.value();
// Tag Pair Section
file.write("[Event \"Casual Game\"]\n");
file.write("[Site \"SerenityOS Chess\"]\n");
file.write(String::formatted("[Date \"{}\"]\n", Core::DateTime::now().to_string("%Y.%m.%d")));
file.write("[Round \"1\"]\n");
String username(getlogin());
const String player1 = (!username.is_empty() ? username : "?");
const String player2 = (!m_engine.is_null() ? "SerenityOS ChessEngine" : "?");
file.write(String::formatted("[White \"{}\"]\n", m_side == Chess::Colour::White ? player1 : player2));
file.write(String::formatted("[Black \"{}\"]\n", m_side == Chess::Colour::Black ? player1 : player2));
file.write(String::formatted("[Result \"{}\"]\n", Chess::Board::result_to_points(m_board.game_result(), m_board.turn())));
file.write("[WhiteElo \"?\"]\n");
file.write("[BlackElo \"?\"]\n");
file.write("[Variant \"Standard\"]\n");
file.write("[TimeControl \"-\"]\n");
file.write("[Annotator \"SerenityOS Chess\"]\n");
file.write("\n");
// Movetext Section
for (size_t i = 0, move_no = 1; i < m_board.moves().size(); i += 2, move_no++) {
const String white = m_board.moves().at(i).to_algebraic();
if (i + 1 < m_board.moves().size()) {
const String black = m_board.moves().at(i + 1).to_algebraic();
file.write(String::formatted("{}. {} {} ", move_no, white, black));
} else {
file.write(String::formatted("{}. {} ", move_no, white));
}
}
file.write("{ ");
file.write(Chess::Board::result_to_string(m_board.game_result(), m_board.turn()));
file.write(" } ");
file.write(Chess::Board::result_to_points(m_board.game_result(), m_board.turn()));
file.write("\n");
file.close();
return true;
}
void ChessWidget::flip_board()
{
m_side = Chess::opposing_colour(m_side);
@ -306,6 +360,6 @@ void ChessWidget::resign()
set_drag_enabled(false);
update();
const String msg = m_board.result_to_string(m_board.game_result());
const String msg = Chess::Board::result_to_string(m_board.game_result(), m_board.turn());
GUI::MessageBox::show(window(), msg, "Game Over", GUI::MessageBox::Type::Information);
}