export enum DieFace { FAIL = 'F', STOP = 'S', SCORE_0 = '0', SCORE_1 = '1', SCORE_2 = '2', SCORE_3 = '3', SCORE_4 = '4', SCORE_5 = '5', WILD = '*', HEART = '<3', QUESTION = '?', } export enum DieState { ROLLED = 'rolled', SELECTED = 'selected', HELD = 'held', HELD_SELECTED = 'held_selected', SCORED = 'scored', } export function isHeldState(d: DieState): d is DieState.HELD | DieState.HELD_SELECTED { return d === DieState.HELD || d === DieState.HELD_SELECTED } export function isSelectedState(d: DieState): d is DieState.SELECTED | DieState.HELD_SELECTED { return d === DieState.SELECTED || d === DieState.HELD_SELECTED } export function setSelected(d: DieState): DieState.SELECTED | DieState.HELD_SELECTED | DieState.SCORED { switch (d) { case DieState.ROLLED: return DieState.SELECTED case DieState.HELD: return DieState.HELD_SELECTED case DieState.SELECTED: case DieState.HELD_SELECTED: case DieState.SCORED: return d } } export function setDeselected(d: DieState): DieState.ROLLED | DieState.HELD | DieState.SCORED { switch (d) { case DieState.SELECTED: return DieState.ROLLED case DieState.HELD_SELECTED: return DieState.HELD case DieState.ROLLED: case DieState.HELD: case DieState.SCORED: return d } } export function toggleSelected(d: DieState): DieState { switch (d) { case DieState.ROLLED: return DieState.SELECTED case DieState.SELECTED: return DieState.ROLLED case DieState.HELD: return DieState.HELD_SELECTED case DieState.HELD_SELECTED: return DieState.HELD case DieState.SCORED: return DieState.SCORED } } export interface DieResult { readonly type: DieType readonly face: DieFace readonly state: DieState } export interface DieType { // The name of this die type readonly name: string // The faces of this die type; faces may be repeated to increase their odds readonly faces: readonly DieFace[] } export interface AIWeights { // How much this AI likes to choose actions that increase the damage bonus // damageBonusWeight is static and does not change readonly damageBonusWeight: number // How much this AI likes to choose actions that increase the requirement of stop dice in a turn // stopWeight is multiplied by the number of stop dice both players can gain readonly stopWeight: number // How much this AI likes to choose actions that decrease the limit of fail dice in a turn // failWeight is multiplied by the number of fail dice both players can lose readonly failWeight: number // How much this AI likes to choose actions that are finishers for the opponent. // finisherWeight is multiplied by the percentage of max damage the opponent has taken readonly finisherWeight: number // How much this AI dislikes choosing actions that are finishers for itself. // selfFinisherWeight is multiplied by the percentage of max damage the AI has taken // If this would reduce the weight of an action to 0 or less, that action has a weight of 0 and will only be // selected randomly if all actions have a weight of 0. readonly selfFinisherWeight: number // Approximate lead that the AI would prefer to have before ending its turn when it has the option to keep pressing readonly desiredLead: number // Approximate damage behind the previous turn total that the AI is willing to take to play it safe, // assuming doing so wouldn't be fatal (not enough damage to kill or not a finisher) // When there's one fail die left, the AI will prioritize stopping as long as the damage it will be taking is within // a randomly selected 50-150% of this value readonly allowedDamage: number // Integer from 0-100: // 100 means AI keeps rolling until it has one fail die left before trying to stop // 0 means AI always stops as soon as it has at least the desired lead // Anything in between is the percentage chance of continuing, which is rolled once for each fail die remaining // besides the last. If the chance fails once, then the AI uses that turn as if it were trying to stop. // This roll is only performed when the AI already has the lead, is ahead by a value within an acceptable distance // of the desired lead, and the AI has at least two additional fail dice remaining before failing. readonly recklessnessPercent: number // Integer from 0-100: // 0 means AI never holds stops until it's ready to end its turn // 100 means AI always holds stops it sees // Anything in between is the percentage chance of locking a new stop die that appears, rolled once per stop die // needed to end the turn per die that comes up stop. If the chance succeeds once, then the AI holds that stop die. // This roll is only performed when the AI is not already stopping its turn. readonly holdStopsPercent: number } export interface PlayerStartingState extends Pick { // Percentage of damage that this player starts off dealing readonly damageBonusBase: number // Percentage of damage this player's damage increases by with each damage bonus earned readonly damageBonusIncrement: number // Maximum number of damage bonuses that this player can have, or 0 if there's no upper limit readonly maxDamageBonuses: number // Minimum number of dice required to stop readonly minStopCount: number // Minimum number of dice required to fail readonly minFailCount: number // Maximum number of dice required to stop readonly maxStopCount: number // Maximum number of dice required to fail readonly maxFailCount: number // Base amount of damage that must be dealt to recover, or 0 if recovery is forbidden // Defaults to 1000 readonly recoverBase?: number // Amount by which the amount needed to recover increases each time recovery is earned // Defaults to 1000 readonly recoverIncrement?: number // Percentage of current damage taken that is removed when recovery is earned // Defaults to 50 readonly recoverPercent?: number // Instructions for if this player is an AI player. If not specified, this player cannot be played by the AI. readonly aiData?: AIWeights } export interface Difficulty { // Name of the difficulty in the select menu readonly name: string // Short description of the difficulty in the select menu readonly shortDescription?: string // Description of the difficulty when selected readonly description?: string // Starting values for the top readonly topStats: PlayerStartingState // Starting values for the bottom readonly bottomStats: PlayerStartingState } export interface PlayerText { // Possible random names for this player readonly names?: readonly string[] // Text given at the start of the game from this side readonly startText?: TriggeredText // Text given at the end of the game from this side readonly endText?: TriggeredText // The name of the damage value for this player readonly damage: string // The name of the player's damage limit, defaulting to "Max " + damage readonly maxDamage?: string // The name of this player's stop count requirement readonly stopCount: string // The name of this player's fail count limit readonly failCount: string // The name of this player's incoming damage bonus readonly damageBonus: string } export interface GameTheme { readonly name: string readonly shortDescription: string readonly description: string readonly actions: readonly GameAction[] readonly difficulties: readonly Difficulty[] readonly topText: PlayerText readonly bottomText: PlayerText } export interface TriggeredText { // The dialogues that can be triggered for this action. // Dialogue can be disabled for characters run by human players. readonly dialogue?: readonly string[] // The descriptions that can be triggered for this action. // Descriptions are always displayed, regardless of player. readonly description?: readonly string[] } export interface ActionText { // Name of the action in the select list for this side readonly name: string // Description of the action in the select list for this side readonly shortDescription: string // Description of the action when selected for this side readonly description: string // Text given when this side selects this action. readonly selectAction?: TriggeredText // Text given when the other side selects this action. readonly opponentSelectsAction?: TriggeredText // Text given when this side finishes a turn in this action without failing. readonly passTurn?: TriggeredText // Text given when this side receives a turn from the opponent in this action. readonly opponentPassesTurn?: TriggeredText // Text given when this side rerolls the dice for this action. readonly reroll?: TriggeredText // Text given when the opposing side rerolls the dice for this action. readonly opponentRerolls?: TriggeredText // Text given the first time each turn this side is one fail die away from failing readonly aboutToFailTurn?: TriggeredText // Text given the first time each turn the opposing side is one fail die away from failing readonly opponentAboutToFailTurn?: TriggeredText // Text given when this side fails the turn by accumulating fail dice readonly failTurn?: TriggeredText // Text given when the opposing side fails the turn by accumulating fail dice readonly opponentFailsTurn?: TriggeredText // Text given when this side fails the turn by stopping without enough points readonly abandonTurn?: TriggeredText // Text given when the opposing side fails the turn by stopping without enough points readonly opponentAbandonsTurn?: TriggeredText // Text given when this side is defeated by being pushed over their max damage with this action readonly defeated?: TriggeredText // Text given when this side defeats the opponent by pushing them over their max damage with this action readonly opponentDefeated?: TriggeredText } export interface GameAction { // The text from the top's perspective, and for the top's actions readonly topText: ActionText // The text from the bottom's perspective, and for the bottom's actions readonly bottomText: ActionText // The dice that are rolled for this action readonly dice: readonly DieType[] // True if the loser of this action increases the amount of damage they take readonly givesDamageBonus: boolean // True if the loser of this action has to get an additional stop die to end their turn readonly givesStopCount: boolean // True if the loser of this action has one fewer buffer for fail dice readonly givesFailCount: boolean // Whether this action can end the game in a loss for the top readonly canFinishTop: boolean // Whether this action can end the game in a loss for the bottom readonly canFinishBottom: boolean } export interface PlayerState { // This player's name readonly name: string // Amount of damage taken so far this battle readonly damage: number // Max damage that can be taken, 0 for endless mode readonly damageMax: number // The total damage taken across this battle readonly damageTotal: number // Number of stop dice required to end a turn with stops readonly stopCount: number // Number of fail dice required to fail a turn by accumulating too many fail dice readonly failCount: number // Incoming damage for this side is multiplied by 100% + 10% * damageBonuses readonly damageBonuses: number // Amount that Total Damage must reach to reduce this player's damage by 50% of its current value // 0 means no recovery is allowed for this player readonly nextRecoverAt: number // Number of times damage was recovered so far this game (adds 1000 + 100 * timesRecovered to nextRecoverAt) readonly timesRecovered: number } export interface GameState { // The version of the serialization format this state was saved with. readonly version: number // The state of the top player readonly top: PlayerState // The state of the bottom player readonly bottom: PlayerState // The theme that defines this game for the players. readonly theme: GameTheme // The difficulty selected for this game. readonly difficulty: Difficulty // Null if the top is a computer. readonly topHumanId: string | null // Null if the bottom is a computer. readonly bottomHumanId: string | null // If true, the top is choosing the action this round. readonly isTopRound: boolean // The action chosen for this round, or null if the current player is choosing an action readonly action: GameAction | null // If true, the top is rolling this turn. readonly isTopTurn: boolean // The total for the previous turn, or 0 if the current player is taking the first turn // If the current player fails when the turn total is 0, they take the penalty and pass the initiative without taking damage. // This can still end the game if the current action is capable of ending the game for this player. readonly lastTurnTotal: number // The dice available for the current turn, or null if the current player has not rolled yet this turn. readonly lastRoll: readonly DieResult[] | null // The total value of the selected dice, or 0 if the selected dice cannot be scored. readonly selectedDiceValue: number // Whether the selected dice are sufficient to end the turn. readonly selectedDiceEndTurn: boolean // The number of dice that have come up failures so far in the current turn readonly countedFails: number // The total of the dice that have been scored so far this turn readonly currentTurnTotal: number }