scalachess

repository·master·Indexed 21 days ago

https://github.com/lichess-org/scalachess

A functional, immutable, and side-effect-free Chess API written in Scala for lichess.org. It provides tools for move validation and execution via the CanPlay typeclass, FEN parsing and generation through FenReader and FenWriter, PGN parsing and serialization, and Glicko rating calculations via GlickoCalculator.

Tokens
7.6K
Snippets
32
Records
39
Agent score
73%

What's inside scalachess

  1. Run tests and code formatting

    master

    Use the following commands in the sbt shell to verify the project or prepare code for contribution:

    • Run tests: testKit / test
    • Run scalafmt and scalafix: prepare (this prepares the code style and fixes)
    sbt
    # Inside sbt shell:
    testKit / test
    prepare
  2. Install and compile scalachess

    master

    To use scalachess, clone the repository and use sbt (Scala Build Tool) to manage the project. You can compile the project directly from the sbt shell.

    git clone https://github.com/lichess-org/scalachess
    cd scalachess
    sbt
    # Inside sbt shell:
    compile
  3. Run benchmarks with JMH

    master

    Scalachess includes benchmarks using JMH. These can be run from the sbt shell with varying levels of intensity and output formats. Note that full benchmarks can take over an hour.

    Available benchmark commands:

    • Full benchmarks: bench / Jmh / run
    • JSON output: bench / Jmh / run -rf json
    • Quick benchmarks (may be inaccurate): bench / Jmh / run -i 1 -wi 1 -f1
    • Longer benchmarks: bench / Jmh / run -i 3 -wi 2 -f2
    • Specific class benchmarks: Use a regex to target a class, e.g., bench / Jmh / run -rf json .*PlayBench.*
    sbt
    # Inside sbt shell:
    bench / Jmh / run
  4. How CanPlay handles move sequences and errors

    master

    The CanPlay typeclass provides different strategies for handling move sequences depending on whether you want to stop at the first error or process the whole list.

    Error Handling Strategies

    1. All-or-Nothing (play): Returns Left(ErrorStr) if any move in the sequence is invalid. Use this when the entire sequence must be legal.
    2. Stop at Error (playWhileValid): Returns the state reached up to the point of the error, the moves that were successfully played, and the error that occurred. This is useful for analyzing where a move sequence went wrong.
    3. Folding (foldLeft / foldRight): Allows you to accumulate a value (like a score, a list of captured pieces, or a move tree) while playing moves. If an error occurs, the fold returns the accumulated value and the error.

    The Step Abstraction

    When using folding or transformation methods, you interact with a Step, which is a tuple: (next: A, move: MoveOrDrop, ply: Ply). This provides the state after the move, the move itself, and the current ply number.

  5. Parse an Opening name into Family and Variation

    master

    An Opening name can be decomposed into an OpeningFamily and an optional OpeningVariation using a colon (:) delimiter.

    • If the name contains a colon (e.g., "Sicilian Defense: Najdorf Variation"), the part before the colon is treated as the OpeningFamily and the part after is the OpeningVariation (trimmed and ignoring content after commas).
    • If no colon is present, the entire name is treated as the OpeningFamily and the variation is None.
    // Conceptual behavior of name splitting:
    // "Sicilian Defense: Najdorf" -> Family: "Sicilian Defense", Variation: Some("Najdorf")
    // "Ruy Lopez" -> Family: "Ruy Lopez", Variation: None
  6. Identify and use chess variants

    master

    The Variant class defines the rules and properties for different chess variants. You can identify a variant using its Id, LilaKey, or UciKey. The library provides a list of all supported variants via Variant.list.all.

    Common variants include:

    • Standard (the default)
    • Chess960
    • Crazyhouse
    • Antichess
    • Atomic
    • Horde
    • KingOfTheHill
    • ThreeCheck
    • RacingKings
    • FromPosition

    You can retrieve a variant by its ID or key using Variant(id) or Variant(key). If you want to ensure you always have a valid variant, use orDefault to fall back to Standard if the lookup fails.

    // Accessing variants
    val standard = Variant.default
    val chess960 = Variant.list.all.find(_.chess960).get
    
    // Lookup by ID or Key
    val variantById = Variant(someId)
    val variantByKey = Variant(LilaKey("crazyhouse"))
    
    // Fallback to default
    val safeVariant = Variant(unknownId).orDefault
  7. FEN Variant Extensions: Crazyhouse and Three-Check

    master

    The FenWriter implementation includes specific logic for non-standard chess variants:

    Crazyhouse

    When using the Crazyhouse variant, write appends a 'crazy pocket' section to the FEN. This section is prefixed with a / and contains the Forsyth notation for the pieces available in the players' pockets.

    Three-Check

    For the Three-Check variant, the write method appends a check count to the end of the FEN string in the format +<black_checks>+<white_checks> (e.g., +1+0).

  8. Represent PGN metadata with Tags and Tags collection

    master

    In scalachess, PGN (Portable Game Notation) metadata is handled using Tag objects and a Tags collection.

    • A Tag consists of a TagType (the name) and a String value. Tags are automatically escaped (e.g., quotes and backslashes) when converted to a string.
    • Tags is a wrapper around a List[Tag] that provides high-level methods to extract specific chess metadata like player names, ratings, FEN, and results.

    Common extraction patterns include:

    • tags.names: Returns a ByColor object containing Option[PlayerName] for White and Black.
    • tags.ratings: Returns a ByColor object containing Option[IntRating] for White and Black.
    • tags.fen: Returns the Option[FullFen] if the [FEN] tag is present.
    • tags.outcome: Returns the Option[Outcome] parsed from the [Result] tag.
    • tags.variant: Returns the Option[chess.variant.Variant] (e.g., Chess960, ThreeCheck).
  9. Generate FEN strings with FenWriter

    master

    The FenWriter trait provides methods to convert chess Position and Game objects into Forsyth-Edwards Notation (FEN) strings. It supports standard FEN, full FEN (including move numbers and clocks), and extensions for variants like Crazyhouse and Three-Check.

    Available methods:

    • write(position: Position): FullFen: Generates a full FEN string. If no move number is provided, it defaults to FullMoveNumber(1).
    • write(position: Position, fullMoveNumber: FullMoveNumber): FullFen: Generates a full FEN string with a specific move number.
    • write(parsed: Position.AndFullMoveNumber): FullFen: Generates a full FEN string from a parsed position object.
    • write(game: Game): FullFen: Generates a full FEN string based on the current position and move number of a Game.
    • writeOpening(position: Position): StandardFen: Generates a standard FEN string (board, active color, castling rights, and en passant target) without move clocks or move numbers.
    • writeBoardAndColor(position: Position, turnColor: Color): BoardAndColorFen: Generates a FEN string containing only the board state and the active color.
    // Example usage of FenWriter
    val fen = fenWriter.write(position)
    val openingFen = fenWriter.writeOpening(position)
  10. Validate a sequence of moves

    master

    To check if a sequence of moves is legal without necessarily transforming the state into a new object, use the validate method.

    • validate(moves: F[M]): Returns Right(()) if all moves are valid, or Left(ErrorStr) if any move is illegal.
    • validate(sans: F[SanStr]): Validates a sequence of SAN strings.

    This is useful for quick legality checks in move lists or input validation.

    // Validate a list of moves
    val isValid: Either[ErrorStr, Unit] = gameState.validate(List("e4", "e5", "Nf3"))