Game

class Game

Represents a chess game.

Constructors

inline constexpr constexpr Game ()

Default constructor. Constructs a new game starting from the default position.

inline explicit constexpr constexpr Game (const Position &initial_position)

Constructs a new game with a specified initial position.

Comparison operators

inline constexpr bool operator==(const Game &other) const

Equality comparison operator.

Element access

inline const Position &initialPosition() const

Returns the initial game position.

inline bool startsFromDefaultPosition() const

Indicates whether the game started from the default position.

inline const Position &currentPosition() const

Returns the current game position.

inline const PiecePlacement &piecePlacement() const

Returns the piece placement of the current position.

inline const Color &activeColor() const

Returns the active color (the side to move).

inline std::optional<Square> enPassantTargetSquare() const

Returns the en passant target square, if any.

inline std::optional<Square> legalEnPassantTargetSquare() const

Returns the en passant target square if it’s legally capturable.

inline const uint32_t &halfmoveClock() const

Returns the number of halfmoves since the last capture or pawn move.

inline const uint32_t &fullmoveNumber() const

Returns the current fullmove number.

inline const CastlingRights &castlingRights() const

Returns the current castling rights.

inline const std::vector<UciMove> &uciMoves() const

Returns the list of moves in UCI notation played since the initial position.

inline const std::vector<SanMove> &sanMoves() const

Returns the list of moves in SAN notation played since the initial position.

inline const RepetitionTracker &repetitionTracker() const

Returns the repetition tracker.

inline std::optional<GameResult> result() const

Returns the result of the game if it is over.

inline std::optional<DrawReason> drawReason() const

Returns the draw reason if the game ended in a draw.

Modifiers

inline std::expected<void, MoveError> move(const UciMove &move)

Applies a move specified in UCI (Universal Chess Interface) format to the current game state, or returns an error if the move is illegal for the current position.

inline std::expected<void, MoveError> move(const SanMove &move)

Applies a move specified in SAN (Standard Algebraic Notation) format to the current game state, or returns an error if the move is illegal for the current position.

inline void undoMove()

Undoes the last move played, restoring the previous position.

inline void reset()

Resets the game to the initial position.

Public Types

using RepetitionTracker = std::unordered_map<Position, uint32_t, RepetitionHash, RepetitionEqual>

Type alias for mapping from positions to their repetition count, used to detect threefold repetition.

Helper classes

template<>
struct formatter : public chesscxx::internal::SpecDispatcher<chesscxx::internal::PgnSpec, chesscxx::internal::FenSpec, chesscxx::internal::AsciiSpec, chesscxx::internal::PieceListSpec, chesscxx::internal::RepetitionSpec>
#include <game_formatter.h>

formatting support for chesscxx::Game

template<>
struct hash
#include <game_hash.h>

hash support for chesscxx::Game

template<>
class Parser
#include <game_parser.h>

FEN parsing support for Game.

template<>
class Parser
#include <game_parser.h>

PGN parsing support for Game.

Subclassed by chesscxx::Parser< Game, const char *, parse_as::Default >

template<>
class Parser : public chesscxx::Parser<Game, const char*, parse_as::Pgn>
#include <game_parser.h>

default parsing support for Game

Examples

Full game

#include <chesscxx/game.h>
#include <chesscxx/game_result.h>
#include <chesscxx/parse.h>
#include <chesscxx/san_move.h>
#include <chesscxx/uci_move.h>

#include <cstdlib>
#include <optional>
#include <print>
#include <string_view>

namespace {
void verify(const auto& check) {
  if (!static_cast<bool>(check)) std::abort();
}

auto parseUciMove(std::string_view str) -> chesscxx::UciMove {
  auto parsed_uci_move = chesscxx::parse<chesscxx::UciMove>(str);
  verify(parsed_uci_move.has_value());
  return parsed_uci_move.value();
}

auto parseSanMove(std::string_view str) -> chesscxx::SanMove {
  auto parsed_san_move = chesscxx::parse<chesscxx::SanMove>(str);
  verify(parsed_san_move.has_value());
  return parsed_san_move.value();
}
}  // namespace

auto main() -> int {
  chesscxx::Game game;
  std::println("{}\n", game);
  std::println("{:pgn}\n", game);
  std::println("{:fen}\n", game);
  std::println("{:ascii}\n", game);
  std::println("{:lists}\n", game);
  std::println("{:rep}\n", game);

  verify(game.move(parseSanMove("xe4+")));

  verify(game.move(parseUciMove("e7e5")));

  verify(game.move(parseSanMove("Qh5")));

  verify(game.move(parseSanMove("Nc6")));

  verify(game.move(parseUciMove("f1c4")));

  verify(game.move(parseSanMove("Nf6")));

  verify(game.move(parseSanMove("Bf7")));
  verify(game.result() == std::nullopt);
  game.undoMove();

  verify(game.move(parseSanMove("Qh5f7")));

  verify(game.result() == chesscxx::GameResult::kWhiteWins);

  std::println("{}\n", game);
  std::println("{:pgn}\n", game);
  std::println("{:fen}\n", game);
  std::println("{:ascii}\n", game);
  std::println("{:lists}\n", game);
  std::println("{:rep}\n", game);
}

Output:

[Result "*"]

*

[Result "*"]

*

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

rnbqkbnr
pppppppp
........
........
........
........
PPPPPPPP
RNBQKBNR
Active color: white
Castling availability: KQkq
En passant target square: -
Halfmove clock: 0
Fullmove number: 1

{ white king: [e1], white queen: [d1], white bishops: [f1, c1], white knights: [g1, b1], white rooks: [h1, a1], white pawns: [h2, g2, f2, e2, d2, c2, b2, a2], black pawns: [h7, g7, f7, e7, d7, c7, b7, a7], black king: [e8], black queen: [d8], black bishops: [f8, c8], black knights: [g8, b8], black rooks: [h8, a8] }
Active color: white
Castling availability: KQkq
En passant target square: -
Halfmove clock: 0
Fullmove number: 1

{ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 1 }

[Result "1-0"]

1. e4 e5 2. Qh5 Nc6 3. Bc4 Nf6 4. Qxf7# 1-0

[Result "1-0"]

1. e4 e5 2. Qh5 Nc6 3. Bc4 Nf6 4. Qxf7# 1-0

r1bqkb1r/pppp1Qpp/2n2n2/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 4

r.bqkb.r
pppp.Qpp
..n..n..
....p...
..B.P...
........
PPPP.PPP
RNB.K.NR
Active color: black
Castling availability: KQkq
En passant target square: -
Halfmove clock: 0
Fullmove number: 4

{ white king: [e1], white queen: [f7], white bishops: [c4, c1], white knights: [g1, b1], white rooks: [h1, a1], white pawns: [h2, g2, f2, d2, c2, e4, b2, a2], black pawns: [e5, h7, g7, d7, c7, b7, a7], black king: [e8], black queen: [d8], black bishops: [f8, c8], black knights: [f6, c6], black rooks: [h8, a8] }
Active color: black
Castling availability: KQkq
En passant target square: -
Halfmove clock: 0
Fullmove number: 4

{ "r1bqkb1r/pppp1Qpp/2n2n2/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq -": 1, "r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq -": 1, "r1bqkbnr/pppp1ppp/2n5/4p2Q/4P3/8/PPPP1PPP/RNB1KBNR w KQkq -": 1, "r1bqkbnr/pppp1ppp/2n5/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq -": 1, "rnbqkbnr/pppp1ppp/8/4p2Q/4P3/8/PPPP1PPP/RNB1KBNR b KQkq -": 1, "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq -": 1, "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 1 }

General

#include <chesscxx/game.h>
#include <chesscxx/parse.h>
#include <chesscxx/position.h>
#include <chesscxx/san_move.h>

#include <cstdlib>
#include <print>
#include <string_view>

namespace {
void verify(const auto& check) {
  if (!static_cast<bool>(check)) std::abort();
}

auto parseSanMove(std::string_view str) -> chesscxx::SanMove {
  auto parsed_san_move = chesscxx::parse<chesscxx::SanMove>(str);
  verify(parsed_san_move);
  return parsed_san_move.value();
}

auto parsePosition(std::string_view str) -> chesscxx::Position {
  auto parsed_position = chesscxx::parse<chesscxx::Position>(str);
  verify(parsed_position);
  return parsed_position.value();
}
}  // namespace

auto main() -> int {
  chesscxx::Game game;
  verify(game.startsFromDefaultPosition());
  verify(game.initialPosition() == game.currentPosition());

  game = chesscxx::Game(parsePosition(
      "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"));
  verify(game.startsFromDefaultPosition());
  verify(game.initialPosition() == game.currentPosition());

  game = chesscxx::Game(parsePosition(
      "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"));
  verify(!game.startsFromDefaultPosition());
  verify(game.initialPosition() == game.currentPosition());

  verify(game.move(parseSanMove("Nf6")));
  verify(game.initialPosition() != game.currentPosition());

  verify(game.move(parseSanMove("e5")));

  std::println("uci moves: {}", game.uciMoves());

  std::println("san moves: {}", game.sanMoves());

  game.reset();
  verify(game.initialPosition() == game.currentPosition());
  std::println("{}", game);
}

Output:

uci moves: [g8f6, e4e5]
san moves: [Nf6, e5]
[Result "*"]
[FEN "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"]
[SetUp "1"]

*

Repetition

#include <chesscxx/draw_reason.h>
#include <chesscxx/game.h>
#include <chesscxx/game_result.h>
#include <chesscxx/parse.h>
#include <chesscxx/san_move.h>

#include <cstdlib>
#include <print>
#include <string_view>

namespace {
void verify(const auto& check) {
  if (!static_cast<bool>(check)) std::abort();
}
auto parseSanMove(std::string_view str) -> chesscxx::SanMove {
  auto parsed_san_move = chesscxx::parse<chesscxx::SanMove>(str);
  verify(parsed_san_move);
  return parsed_san_move.value();
}

void printRepetition(auto& game) { std::println("{:rep}\n", game); }

void move(auto& game, std::string_view str) {
  auto move_result = game.move(parseSanMove(str));
  verify(move_result);
}
}  // namespace

auto main() -> int {
  chesscxx::Game game;
  printRepetition(game);

  move(game, "Nc3");
  printRepetition(game);

  move(game, "Nc6");
  printRepetition(game);

  move(game, "Nb1");
  printRepetition(game);

  move(game, "Nb8");
  printRepetition(game);

  move(game, "Nc3");
  printRepetition(game);

  move(game, "Nc6");
  printRepetition(game);

  verify(game.repetitionTracker().at(game.currentPosition()) == 2);

  move(game, "Nb1");
  printRepetition(game);

  move(game, "Nb8");
  printRepetition(game);

  verify(game.result() == chesscxx::GameResult::kDraw);
  verify(game.drawReason() == chesscxx::DrawReason::kThreefoldRepetition);
}

Output:

{ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 1 }

{ "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 1 }

{ "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 1 }

{ "r1bqkbnr/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR b KQkq -": 1, "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 1 }

{ "r1bqkbnr/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR b KQkq -": 1, "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 2 }

{ "r1bqkbnr/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR b KQkq -": 1, "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 1, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 2 }

{ "r1bqkbnr/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR b KQkq -": 1, "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 2 }

{ "r1bqkbnr/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR b KQkq -": 2, "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 2 }

{ "r1bqkbnr/pppppppp/2n5/8/8/8/PPPPPPPP/RNBQKBNR b KQkq -": 2, "r1bqkbnr/pppppppp/2n5/8/8/2N5/PPPPPPPP/R1BQKBNR w KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/R1BQKBNR b KQkq -": 2, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": 3 }

Hashing

#include <chesscxx/game.h>
#include <chesscxx/parse.h>
#include <chesscxx/position.h>
#include <chesscxx/san_move.h>

#include <cstdlib>
#include <functional>
#include <string_view>

namespace {
void verify(const auto& check) {
  if (!static_cast<bool>(check)) std::abort();
}
auto parseSanMove(std::string_view str) -> chesscxx::SanMove {
  auto parsed_san_move = chesscxx::parse<chesscxx::SanMove>(str);
  verify(parsed_san_move);
  return parsed_san_move.value();
}

auto parsePosition(std::string_view str) -> chesscxx::Position {
  auto parsed_position = chesscxx::parse<chesscxx::Position>(str);
  verify(parsed_position);
  return parsed_position.value();
}

void assertEqual(const auto& lhs, const auto& rhs) {
  verify(lhs == rhs);
  verify(std::hash<chesscxx::Game>{}(lhs) == std::hash<chesscxx::Game>{}(rhs));
}

void assertNotEqual(const auto& lhs, const auto& rhs) {
  verify(lhs != rhs);
  // These hashes may collide but are likely different
  verify(std::hash<chesscxx::Game>{}(lhs) != std::hash<chesscxx::Game>{}(rhs));
}
}  // namespace

auto main() -> int {
  chesscxx::Game uninitialized;

  chesscxx::Game const standard = chesscxx::Game(parsePosition(
      "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"));
  assertEqual(uninitialized, standard);

  chesscxx::Game const custom = chesscxx::Game(parsePosition(
      "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"));
  assertNotEqual(uninitialized, custom);

  verify(uninitialized.move(parseSanMove("e4")));
  assertNotEqual(uninitialized, custom);

  assertNotEqual(uninitialized, standard);

  uninitialized.undoMove();
  assertEqual(uninitialized, standard);
}

Output: