UNPKG

boardgame.io

Version:
377 lines (339 loc) 10.1 kB
import { Object } from 'ts-toolbelt'; import Koa from 'koa'; import { Store as ReduxStore } from 'redux'; import * as ActionCreators from './core/action-creators'; import { Flow } from './core/flow'; import { CreateGameReducer } from './core/reducer'; import { INVALID_MOVE } from './core/constants'; import * as StorageAPI from './server/db/base'; import { EventsAPI } from './plugins/plugin-events'; import { RandomAPI } from './plugins/plugin-random'; export { StorageAPI }; export type AnyFn = (...args: any[]) => any; export interface State<G extends any = any, CtxWithPlugins extends Ctx = Ctx> { G: G; ctx: Ctx | CtxWithPlugins; deltalog?: Array<LogEntry>; plugins: { [pluginName: string]: PluginState; }; _undo: Array<Undo<G>>; _redo: Array<Undo<G>>; _stateID: number; } export type PartialGameState = Pick<State, 'G' | 'ctx' | 'plugins'>; export type StageName = string; export type PlayerID = string; export type StageArg = StageName | { stage?: StageName; moveLimit?: number }; export interface ActivePlayersArg { currentPlayer?: StageArg; others?: StageArg; all?: StageArg; value?: Record<PlayerID, StageArg>; moveLimit?: number; revert?: boolean; next?: ActivePlayersArg; } export interface ActivePlayers { [playerID: string]: StageName; } export interface Ctx { numPlayers: number; playOrder: Array<PlayerID>; playOrderPos: number; playerID?: PlayerID; activePlayers: null | ActivePlayers; currentPlayer: PlayerID; numMoves?: number; gameover?: any; turn: number; phase: string; _activePlayersMoveLimit?: Record<PlayerID, number>; _activePlayersNumMoves?: Record<PlayerID, number>; _prevActivePlayers?: Array<{ activePlayers: null | ActivePlayers; _activePlayersMoveLimit?: Record<PlayerID, number>; _activePlayersNumMoves?: Record<PlayerID, number>; }>; _nextActivePlayers?: ActivePlayersArg; _random?: { seed: string | number; }; // TODO public api should have these as non-optional // internally there are two contexts, one is a serialized POJO and another // "enhanced" context that has plugin api methods attached events?: EventsAPI; random?: RandomAPI; } export interface PluginState { data: any; api?: any; } export interface LogEntry { action: ActionShape.MakeMove | ActionShape.GameEvent; _stateID: number; turn: number; phase: string; redact?: boolean; automatic?: boolean; } interface PluginContext< API extends any = any, Data extends any = any, G extends any = any > { G: G; ctx: Ctx; game: Game; api: API; data: Data; } export interface Plugin< API extends any = any, Data extends any = any, G extends any = any > { name: string; noClient?: (context: PluginContext<API, Data, G>) => boolean; setup?: (setupCtx: { G: G; ctx: Ctx; game: Game<G, Ctx> }) => Data; action?: (data: Data, payload: ActionShape.Plugin['payload']) => Data; api?: (context: { G: G; ctx: Ctx; game: Game<G, Ctx>; data: Data; playerID?: PlayerID; }) => API; flush?: (context: PluginContext<API, Data, G>) => Data; dangerouslyFlushRawState?: (flushCtx: { state: State<G, Ctx>; game: Game<G, Ctx>; api: API; data: Data; }) => State<G, Ctx>; fnWrap?: (fn: AnyFn) => (G: G, ctx: Ctx, ...args: any[]) => any; } type MoveFn<G extends any = any, CtxWithPlugins extends Ctx = Ctx> = ( G: G, ctx: CtxWithPlugins, ...args: any[] ) => any; export interface LongFormMove< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { move: MoveFn<G, CtxWithPlugins>; redact?: boolean; noLimit?: boolean; client?: boolean; undoable?: boolean | ((G: G, ctx: CtxWithPlugins) => boolean); } export type Move<G extends any = any, CtxWithPlugins extends Ctx = Ctx> = | MoveFn<G, CtxWithPlugins> | LongFormMove<G, CtxWithPlugins>; export interface MoveMap< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { [moveName: string]: Move<G, CtxWithPlugins>; } export interface PhaseConfig< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { start?: boolean; next?: string; onBegin?: (G: G, ctx: CtxWithPlugins) => any; onEnd?: (G: G, ctx: CtxWithPlugins) => any; endIf?: (G: G, ctx: CtxWithPlugins) => boolean | void | { next: string }; moves?: MoveMap<G, CtxWithPlugins>; turn?: TurnConfig<G, CtxWithPlugins>; wrapped?: { endIf?: ( state: State<G, CtxWithPlugins> ) => boolean | void | { next: string }; onBegin?: (state: State<G, CtxWithPlugins>) => any; onEnd?: (state: State<G, CtxWithPlugins>) => any; }; } export interface StageConfig< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { moves?: MoveMap<G, CtxWithPlugins>; next?: string; } export interface StageMap< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { [stageName: string]: StageConfig<G, CtxWithPlugins>; } export interface TurnOrderConfig< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { first: (G: G, ctx: CtxWithPlugins) => number; next: (G: G, ctx: CtxWithPlugins) => number | undefined; playOrder?: (G: G, ctx: CtxWithPlugins) => PlayerID[]; } export interface TurnConfig< G extends any = any, CtxWithPlugins extends Ctx = Ctx > { activePlayers?: object; moveLimit?: number; onBegin?: (G: G, ctx: CtxWithPlugins) => any; onEnd?: (G: G, ctx: CtxWithPlugins) => any; endIf?: (G: G, ctx: CtxWithPlugins) => boolean | void | { next: PlayerID }; onMove?: (G: G, ctx: CtxWithPlugins) => any; stages?: StageMap<G, CtxWithPlugins>; moves?: MoveMap<G, CtxWithPlugins>; order?: TurnOrderConfig<G, CtxWithPlugins>; wrapped?: { endIf?: ( state: State<G, CtxWithPlugins> ) => boolean | void | { next: PlayerID }; onBegin?: (state: State<G, CtxWithPlugins>) => any; onEnd?: (state: State<G, CtxWithPlugins>) => any; onMove?: (state: State<G, CtxWithPlugins>) => any; }; } interface PhaseMap<G extends any = any, CtxWithPlugins extends Ctx = Ctx> { [phaseName: string]: PhaseConfig<G, CtxWithPlugins>; } export interface Game<G extends any = any, CtxWithPlugins extends Ctx = Ctx> { name?: string; disableUndo?: boolean; seed?: string | number; setup?: (ctx: CtxWithPlugins, setupData?: any) => any; moves?: MoveMap<G, CtxWithPlugins>; phases?: PhaseMap<G, CtxWithPlugins>; turn?: TurnConfig<G, CtxWithPlugins>; events?: { endGame?: boolean; endPhase?: boolean; endTurn?: boolean; setPhase?: boolean; endStage?: boolean; setStage?: boolean; pass?: boolean; setActivePlayers?: boolean; }; endIf?: (G: G, ctx: CtxWithPlugins) => any; onEnd?: (G: G, ctx: CtxWithPlugins) => any; playerView?: (G: G, ctx: CtxWithPlugins, playerID: PlayerID) => any; plugins?: Array<Plugin<any, any, G>>; ai?: { enumerate: ( G: G, ctx: Ctx, playerID: PlayerID ) => Array< | { event: string; args?: any[] } | { move: string; args?: any[] } | ActionShape.MakeMove | ActionShape.GameEvent >; }; processMove?: ( state: State<G, Ctx | CtxWithPlugins>, action: ActionPayload.MakeMove ) => State<G, CtxWithPlugins> | typeof INVALID_MOVE; flow?: ReturnType<typeof Flow>; } type Undo<G extends any = any> = { G: G; ctx: Ctx; moveType?: string; }; export namespace Server { export type GenerateCredentials = ( ctx: Koa.DefaultContext ) => Promise<string> | string; export type AuthenticateCredentials = ( credentials: string, playerMetadata: PlayerMetadata ) => Promise<boolean> | boolean; export type PlayerMetadata = { id: number; name?: string; credentials?: string; data?: any; }; export interface GameMetadata { gameName: string; players: { [id: number]: PlayerMetadata }; setupData?: any; gameover?: any; nextRoomID?: string; unlisted?: boolean; } export interface LobbyConfig { uuid?: () => string; generateCredentials?: GenerateCredentials; apiPort?: number; apiCallback?: () => void; } } export type Reducer = ReturnType<typeof CreateGameReducer>; export type Store = ReduxStore<State, ActionShape.Any>; export namespace CredentialedActionShape { export type MakeMove = ReturnType<typeof ActionCreators.makeMove>; export type GameEvent = ReturnType<typeof ActionCreators.gameEvent>; export type Plugin = ReturnType<typeof ActionCreators.plugin>; export type AutomaticGameEvent = ReturnType< typeof ActionCreators.automaticGameEvent >; export type Undo = ReturnType<typeof ActionCreators.undo>; export type Redo = ReturnType<typeof ActionCreators.redo>; export type Any = | MakeMove | GameEvent | AutomaticGameEvent | Undo | Redo | Plugin; } export namespace ActionShape { type StripCredentials<T extends object> = Object.P.Omit< T, ['payload', 'credentials'] >; export type MakeMove = StripCredentials<CredentialedActionShape.MakeMove>; export type GameEvent = StripCredentials<CredentialedActionShape.GameEvent>; export type Plugin = StripCredentials<CredentialedActionShape.Plugin>; export type AutomaticGameEvent = StripCredentials< CredentialedActionShape.AutomaticGameEvent >; export type Sync = ReturnType<typeof ActionCreators.sync>; export type Update = ReturnType<typeof ActionCreators.update>; export type Reset = ReturnType<typeof ActionCreators.reset>; export type Undo = StripCredentials<CredentialedActionShape.Undo>; export type Redo = StripCredentials<CredentialedActionShape.Redo>; export type Any = | MakeMove | GameEvent | AutomaticGameEvent | Sync | Update | Reset | Undo | Redo | Plugin; } export namespace ActionPayload { type GetPayload<T extends object> = Object.At<T, 'payload'>; export type MakeMove = GetPayload<ActionShape.MakeMove>; export type GameEvent = GetPayload<ActionShape.GameEvent>; } export type FilteredMetadata = { id: number; name?: string; }[]; export interface SyncInfo { state: State; filteredMetadata: FilteredMetadata; initialState: State; log: LogEntry[]; }