UNPKG

@gamepark/rules-api

Version:

API to implement the rules of a board game

209 lines (208 loc) 13.2 kB
import { Action } from '../Action'; import { RandomMove } from '../RandomMove'; import { PlayMoveContext, Rules } from '../Rules'; import { TimeLimit } from '../TimeLimit'; import { Undo } from '../Undo'; import { UnpredictableMoves } from '../UnpredictableMove'; import { Material, MaterialMutator } from './items'; import { LocationStrategy } from './location'; import { MaterialGame } from './MaterialGame'; import { GameMemory, PlayerMemory } from './memory'; import { CustomMove, ItemMove, ItemMoveRandomized, ItemMoveView, MaterialMove, MaterialMoveRandomized, MaterialMoveView, RollItem } from './moves'; import { MaterialRulesPart, MaterialRulesPartCreator } from './rules'; /** * The MaterialRules class is the main class to implement the rules of a board game with the "Material oriented" approach. * With this approach, the game state and the game moves is structured around the game material items and their movements. * The rules are also automatically split into small parts. * Finally, a "memory" util is available to store temporary information which is not part of the material or the rules state. * If you need to implement game with hidden information, see {@link HiddenMaterialRules} or {@link SecretMaterialRules}. * * @typeparam Player - identifier of a player. Either a number or a numeric enum (eg: PlayerColor) * @typeparam MaterialType - Numeric enum of the types of material manipulated in the game * @typeparam LocationType - Numeric enum of the types of location in the game where the material can be located */ export declare abstract class MaterialRules<Player extends number = number, MaterialType extends number = number, LocationType extends number = number, RuleId extends number = number> extends Rules<MaterialGame<Player, MaterialType, LocationType, RuleId>, MaterialMove<Player, MaterialType, LocationType, RuleId>, Player> implements RandomMove<MaterialMove<Player, MaterialType, LocationType, RuleId>, MaterialMoveRandomized<Player, MaterialType, LocationType, RuleId>, Player>, Undo<MaterialGame<Player, MaterialType, LocationType, RuleId>, MaterialMove<Player, MaterialType, LocationType, RuleId>, Player>, UnpredictableMoves<MaterialMove<Player, MaterialType, LocationType, RuleId>>, TimeLimit<MaterialGame<Player, MaterialType, LocationType, RuleId>, MaterialMove<Player, MaterialType, LocationType, RuleId>, Player> { /** * When you implement a game using the "material" approach, you are also strongly advised to split the rules of the game into many small parts. * You should usually create a numeric enum class "RuleId" that will list all those small parts. * This "rules" property must be implemented to map each "rule id" with the corresponding MaterialRulesPart class. * This way, the behavior of the rules can be delegated to the corresponding rule part at each step of the game. */ abstract readonly rules: Record<RuleId, MaterialRulesPartCreator<Player, MaterialType, LocationType, RuleId>>; /** * The "location strategies" are global rules that always apply in a game when we want to maintain a consistency in the position of the material. * For example, we usually want the cards in a player hand to always go from x=0 to x=n without a gap, so we use a {@link PositiveSequenceStrategy} to enforce * the rule once and for all. If we want to create a "river" of card we can use a {@link FillGapStrategy}. * Games with more complex use cases can implement their own {@link LocationStrategy}. */ readonly locationsStrategies: Partial<Record<MaterialType, Partial<Record<LocationType, LocationStrategy<Player, MaterialType, LocationType>>>>>; /** * Helper function to manipulate the material items of the game. See {@link Material}. * * @param type The type of Material we want to work on * @returns a Material instance to manipulate all the material of that type in current game state. */ material(type: MaterialType): Material<Player, MaterialType, LocationType>; /** * Shortcut for this.game.players * @returns array of the players identifiers */ get players(): Player[]; /** * @return the active player if exactly one player is active */ get activePlayer(): Player | undefined; /** * @returns all the active players */ get activePlayers(): Player[]; /** * Utility function to access the memory tool for the game or on player. * this.game.memory can be used to store any data that is not available through the state of the material, or current rule. * * @param player Optional, identifier of the player if we want to manipulate a specific player's memory * @returns {@link GameMemory} or {@link PlayerMemory} utility * @protected */ protected getMemory(player?: Player): GameMemory<Player> | PlayerMemory<Player>; /** * Save a new value inside the memory. * @param key The key to index the memorized value. * @param value Any JSON serializable value to store, or a function that takes previous stored value and returns the new value to store. * @param player optional, if we need to memorize a different value for each player. */ memorize<T = any>(key: keyof any, value: T | ((lastValue: T) => T), player?: Player): T; /** * Retrieve the value memorized under a given key. * Shortcut for this.game.memory[key] or this.game.memory[key][player] * * @param key Key under which the memory is store. Usually a value of a numeric enum named "Memory". * @param player optional, if we need to memorize a different value for each player. */ remind<T = any>(key: keyof any, player?: Player): T; /** * Delete a value from the memory * @param key Key of the value to delete * @param player optional, if we need to memorize a different value for each player. */ forget(key: keyof any, player?: Player): void; startPlayerTurn: <P extends number = number, R extends number = number>(id: R, player: P) => import("./moves").StartPlayerTurn<P, R>; startSimultaneousRule: <P extends number = number, R extends number = number>(id: R, players?: P[]) => import("./moves").StartSimultaneousRule<P, R>; startRule: <R extends number = number>(id: R) => import("./moves").StartRule<R>; customMove: <Type extends number = number>(type: Type, data?: any) => CustomMove; endGame: () => import("./moves").EndGame; /** * Instantiates the class that handled the rules of the game corresponding to the current rule id. * This function reads the current value in this.game.rule.id and instantiate the corresponding class in the {@link rules} property. * * @returns the class that handled the rules of the game, at current specific game state. */ get rulesStep(): MaterialRulesPart<Player, MaterialType, LocationType, RuleId> | undefined; /** * Returns a utility class to change the state of the items. * Used by the framework to apply the Material moves on the items (should not be manipulated directly in the games). * * @param type MaterialType of the item we want to modify * @returns a MaterialMutator to change the state of the items */ mutator(type: MaterialType): MaterialMutator<Player, MaterialType, LocationType>; /** * Items can sometime be stored with a quantity (for example, coins). * By default, if you create or move similar items to the exact same location, they will merge into one item with a quantity. * However, if you have 2 cards that can be at the same spot for a short time (swapping or replacing), you can override this function to prevent them to merge * * @param _type type of items * @returns true if items can merge into one item with a quantity (default behavior) */ itemsCanMerge(_type: MaterialType): boolean; /** * In the material approach, the rules behavior is delegated to the current {@link rulesStep}. See {@link Rules.delegate} */ delegate(): Rules<MaterialGame<Player, MaterialType, LocationType, RuleId>, MaterialMove<Player, MaterialType, LocationType, RuleId>, Player> | undefined; /** * Randomize Shuffle of Roll moves (see {@link RandomMove.randomize}) * @param move The Material Move to randomize * @returns the randomized move */ randomize(move: MaterialMove<Player, MaterialType, LocationType, RuleId>): MaterialMove<Player, MaterialType, LocationType, RuleId> & MaterialMoveRandomized<Player, MaterialType, LocationType, RuleId>; /** * When a RollItem move is create, it has to be randomized on the server side before it is saved and shared. * This function provides the random value. By default, it returns a value between 0 and 5 assuming a 6 sided dice is rolled. * If you need to flip a coin or roll non-cubic dice, you need to override this function. * * @param _move The RollItem move to randomize * @returns a random rolled value, by default a value between 0 and 5 (cubic dice result) */ roll(_move: RollItem<Player, MaterialType, LocationType>): number; /** * Execution of the Material Moves. See {@link Rules.play}. * * @param move Material move to play on the game state * @param context Context in which the move was played * @returns Consequences of the move */ play(move: MaterialMoveRandomized<Player, MaterialType, LocationType, RuleId> | MaterialMoveView<Player, MaterialType, LocationType, RuleId>, context?: PlayMoveContext): MaterialMove<Player, MaterialType, LocationType, RuleId>[]; protected onPlayItemMove(move: ItemMoveRandomized<Player, MaterialType, LocationType> | ItemMoveView<Player, MaterialType, LocationType>, context?: PlayMoveContext): MaterialMove<Player, MaterialType, LocationType, RuleId>[]; protected beforeItemMove(move: ItemMove<Player, MaterialType, LocationType>, context?: PlayMoveContext): MaterialMove<Player, MaterialType, LocationType, RuleId>[]; protected afterItemMove(move: ItemMove<Player, MaterialType, LocationType>, context?: PlayMoveContext): MaterialMove<Player, MaterialType, LocationType, RuleId>[]; protected onCustomMove(move: CustomMove, context?: PlayMoveContext): MaterialMove<Player, MaterialType, LocationType, RuleId>[]; private onPlayRulesMove; private changeRule; /** * By default, a Material Move can be undone if no player became active and no dice was rolled. * See {@link Undo.canUndo} and {@link HiddenMaterialRules.canUndo} * * @param action Action to consider * @param consecutiveActions Action played in between * @returns true if the action can be undone by the player that played it */ canUndo(action: Action<MaterialMove<Player, MaterialType, LocationType, RuleId>, Player>, consecutiveActions: Action<MaterialMove<Player, MaterialType, LocationType, RuleId>, Player>[]): boolean; private consecutiveActionBlocksUndo; private actionBlocksUndo; private actionActivatesPlayer; /** * @protected * If a moves blocks the undo, any action with this move cannot be undone. * By default, a move block the undo if it activates a player or exposes new information (roll result, hidden information revealed...) * * @param move The move to consider * @param player The player that triggered the move * @returns true if the move blocks the undo */ protected moveBlocksUndo(move: MaterialMove<Player, MaterialType, LocationType, RuleId>, player?: Player): boolean; private moveActivatesPlayer; /** * Restore help display & local item moves */ restoreTransientState(previousState: MaterialGame<Player, MaterialType, LocationType, RuleId>): void; /** * Random moves, or moves that reveals something to me, are unpredictable. * Unpredictable moves cannot be precomputed on client side, the server side's response is necessary. * See {@link Rules.isUnpredictableMove} * * @param move Material move to consider * @param _player The player playing the move * @returns true if the move outcome cannot be predicted on client side */ isUnpredictableMove(move: MaterialMove<Player, MaterialType, LocationType, RuleId>, _player: Player): boolean; /** * A Material Game is over when there is no rule left to execute. This state results of a {@link EndGame} move. * * @returns true if game is over */ isOver(): boolean; /** * Amount of time given to a player everytime it is their turn to play. * @param playerId Id of the player, if you want to give different time depending on the id for asymmetric games. * @return number of seconds to add to the player's clock */ giveTime(playerId: Player): number; } /** * Signature interface of the constructor of a class that implements MaterialRules */ export interface MaterialRulesCreator<P extends number = number, M extends number = number, L extends number = number, R extends number = number> { new (state: MaterialGame<P, M, L, R>, client?: { player?: P; }): MaterialRules<P, M, L, R>; }