p-chess/chess
A PHP chess library for Laravel and standalone use. Parse FEN/PGN, generate and validate legal moves, handle castling, en passant and promotion, and detect check, checkmate and stalemate through a clean object-oriented API. Ideal for building chess apps, bots and analysis tools.
Install with Composer and confirm the actual namespace/class names from the installed source before wiring it into application code:
composer require p-chess/chess
php artisan tinker
use PChess\Chess\Chess;
$chess = new Chess(); // starting position
$chess->move('e4');
$chess->move('e5');
echo $chess->fen(); // persist this
print_r($chess->moves()); // legal moves in current position
The source under vendor/p-chess/chess/src is the best first reference; this is a small engine package, not a Laravel-first SDK. Look first for the main Chess/Game class, FEN loading, PGN loading, move application, and legal-move generation.
First practical use case: validate a user-submitted chess move and persist the resulting FEN. Start with a queued or service-layer smoke test rather than embedding engine calls directly in controllers.
$chess = new Chess($fenFromDatabase);
try {
$move = $chess->move($request->string('move'));
} catch (\Throwable $e) {
// illegal move, ambiguous SAN, bad notation, etc.
}
if (!$move) {
// package may return null/false instead of throwing
}
$newFen = $chess->fen();
Use the engine as a domain service, not as a singleton. The Chess object carries mutable board state. Create a fresh instance per game/request/job, hydrate it from FEN or PGN, apply exactly one mutation, then persist the resulting FEN/PGN.
final class ChessMoveService
{
public function apply(string $fen, string $move): array
{
$chess = new Chess($fen);
$applied = $chess->move($move);
if (!$applied) {
throw ValidationException::withMessages([
'move' => 'Illegal or ambiguous move.',
]);
}
return [
'fen' => $chess->fen(),
'pgn' => $chess->pgn(),
'turn' => $chess->turn(), // verify method name in installed version
];
}
}
Store FEN as the current snapshot and PGN as the replayable history. A common schema is:
$table->string('fen', 120)->nullable();
$table->longText('pgn')->nullable();
$table->string('status')->default('active');
FEN is cheap to load and ideal for resuming a game. PGN is better for displaying move history and exporting games. Store both if you need fast resume plus full history.
Use a custom validation rule for move input. Keep the rule thin and delegate to the service so tests can target one place.
final class LegalChessMoveRule implements ValidationRule
{
public function __construct(private string $fen) {}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$chess = new Chess($this->fen);
if (!$chess->move($value)) {
$fail('The move is not legal in the current position.');
}
}
}
Build API resources from engine state. Expose only the fields clients need rather than dumping the entire object graph.
return [
'fen' => $chess->fen(),
'turn' => $chess->turn(),
'legal_moves' => $chess->moves(),
'in_check' => $chess->inCheck(),
'game_over' => $chess->gameOver(),
];
Queue heavy work. Move generation and any search/analysis loop are CPU-bound. Do not run iterative analysis in a normal HTTP request. Put it in a job, enforce depth/node/time limits, and return FEN/PGN plus analysis metadata.
final class AnalyzePosition implements ShouldQueue
{
public function __construct(public string $fen) {}
public function handle(): void
{
$chess = new Chess($this->fen);
// Bound any loop yourself; this package is not Stockfish.
foreach ($chess->moves() as $move) {
// evaluate hypothetical lines on cloned/rehydrated positions
}
}
}
Rehydrate for simulations instead of relying on deep cloning. If you explore candidate lines, create a new engine from the current FEN for each branch:
$child = new Chess($parentFen);
$child->move($candidate);
This avoids accidental shared-state bugs and matches the package's FEN-centric design.
Keep a thin application wrapper. Because the package is small and its API may vary between versions, isolate it behind ChessMoveService, ChessGamePresenter, or similar classes. That gives you one place to adjust when method names or exception behavior change.
Nf3, e4, O-O, e8=Q) and coordinate notation (g1f3, e2e4) are not interchangeable. Decide which format your API accepts and normalize before calling the engine.move() mutates the object. Do not reuse a Chess instance across requests, queued jobs, or hypothetical branches without rehydrating from FEN.clone, verify the board and piece objects are actually independent. Rehydrating from FEN is safer and easier to reason about.inCheck()/gameOver() or the equivalent methods.How can I help you explore Laravel packages today?