@gamepark/rules-api
Version:
API to implement the rules of a board game
209 lines (208 loc) • 13.2 kB
TypeScript
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>;
}