UNPKG

@gamepark/rules-api

Version:

API to implement the rules of a board game

167 lines 7.53 kB
import { isEqual } from 'es-toolkit'; import { hasEliminations } from './Eliminations'; /** * The Rules class is the basic minimal API to implement when adapting a board game on Game Park. * It has the ability to tell whether it is a player's turn to play, what moves are legal, what happens when a move is played, and when the game is over. * * @typeparam Game - Data structure of the game state * @typeparam Move - Data structure of a game move * @typeparam PlayerId - type of the player's identifiers. Usually a number or a numeric enum. */ export class Rules { /** * The state of the game */ game; /** * Construct a new instance of the Rules to work on a game state * @param game the state of the game to work on */ constructor(game) { this.game = game; } /** * A shortcut for this.game for backward compatibility */ get state() { return this.game; } /** * The delegation allows to split the rules in smaller and simpler parts. * Override this method to delegate the behavior of the other method to another Rules class. * * @return the Rule you want to delegate the current game behavior to */ delegate() { return; } /** * The delegation allows to split the rules in smaller and simpler parts. * Default behavior call {@link delegate} and returns either an empty array, or an array with the delegate if any. * Override this method to delegate the behavior of the other method to multiple Rules class. * * @return an array of Rules to delegate the current game behavior to */ delegates() { const delegate = this.delegate(); return delegate ? [delegate] : []; } /** * Implement this to tell at any point in the game which player is active or not. * When it's a player's turn, his thinking time decreases. * A player whose turn it's not can have authorised moves, so this method is not sufficient to prevent a player from playing. * Supports delegation: it returns true if any {@link delegates} return true. * * @param playerId - Identifier of a player * @returns true if it is this player's turn to play */ isTurnToPlay(playerId) { const rules = this.delegates(); if (rules.some(rules => rules.isTurnToPlay(playerId))) { return true; } return playerId === this.getActivePlayer(); } /** * When only one player is active at a time, you can implement this method instead of "isTurnToPlay" to tell which player is active. * Supports delegation: if any {@link delegates} returns an active player, it will return it. * * @return @typeParam PlayerId - The identifier of the active player */ getActivePlayer() { for (const delegate of this.delegates()) { const activePlayer = delegate.getActivePlayer(); if (activePlayer !== undefined) return activePlayer; } return; } /** * This method allows the Game Park server to authorise only valid moves in accordance with the game rules. * The default behavior calls {@link getLegalMoves}, and returns true when at least one move is equal. * Supports delegation with {@link delegates}. A move is legal if it is legal for at least one delegate. * * @param playerId - Identifier of a player * @param move - a Move to control * @returns true if the move can be played by this player */ isLegalMove(playerId, move) { const rules = this.delegates(); if (rules.some(rules => rules.isLegalMove(playerId, move))) { return true; } if (this.getLegalMoves(playerId).some(legalMove => isEqual(move, legalMove))) { return true; } if (hasEliminations(this) && this.giveUpMove) { return isEqual(move, this.giveUpMove(playerId)); } return false; } /** * This method lists all the moves that are legal for a given player. * This is optional but convenient: it is used by {@link isLegalMove}, {@link Bot} and many UI features from @gamepark/react-game to automatically * highlight what can be done on the screen. * Beware of the size of the list however: if the game offers to many legal moves, you might have to fall back to implementing {@link isLegalMove} and a * custom {@link Bot}. * * Supports delegation with {@link delegates}: by default it will return all the legal moves of each delegate. * * @param playerId - Identifier of a player * @returns the list of the moves */ getLegalMoves(playerId) { return this.delegates().flatMap(rules => rules.getLegalMoves(playerId)); } /** * When some game state require automatic actions to be taken by the players, you can return a list of moves to play played automatically. * It can be a sequences in the rules where everything is scripted for instance (no choices left for players). * * Supports delegation with {@link delegates}: by default it will return all the automatic moves of each delegate. * * @returns the list of the moves that must be played automatically */ getAutomaticMoves() { return this.delegates().flatMap(rules => rules.getAutomaticMoves()); } /** * Executes a move on current game state. * This method is the only one that should mutate the state of the game. * When a move is played by a player, or automatically played as a consequence of another move, it will be played by this method, independently on Game Park * server and on every player or spectator clients. It can also be replayed in replay or undo features. * * A move should not change a lot the game state: instead, this method should return new moves to play as consequences. * Dividing moves into small parts with consequences is key to producing step-by-step animations in the UI. * * see {@link https://en.wikipedia.org/wiki/Command_pattern} * * Supports delegation with {@link delegates}: by default it will play the move on each delegate, and return all the consequences. * * @param move - the Move to execute on the game state * @param context - context of execution: see {@link PlayMoveContext} * @returns A list of moves that should be immediately played as consequences of this move */ play(move, context) { return this.delegates().flatMap(rules => rules.play(move, context)); } /** * This method must return true when the game is over. * * Supports delegation with {@link delegates}: by default it will return true if there is at least one delegate, and every delegate returns true. * Default behavior returns true if playersIds is provided, and it is not {@link isTurnToPlay} for any player - however this behavior is deprecated. * * @param playerIds - All the player ids in the game. Do not use (deprecated). * @returns true if game is over */ isOver(playerIds) { const delegates = this.delegates(); if (delegates.length > 0 && delegates.every(delegate => delegate.isOver(playerIds))) { return true; } if (playerIds) { return !playerIds.some(playerId => this.isTurnToPlay(playerId)); } return this.getActivePlayer() === undefined; } } //# sourceMappingURL=Rules.js.map