UNPKG

boardgame.io

Version:
512 lines (465 loc) 14 kB
import type { Object } from 'ts-toolbelt'; import type Koa from 'koa'; import type { Store as ReduxStore } from 'redux'; import type * as ActionCreators from './core/action-creators'; import type { ActionErrorType, UpdateErrorType } from './core/errors'; import type { Flow } from './core/flow'; import type { CreateGameReducer } from './core/reducer'; import type { INVALID_MOVE } from './core/constants'; import type { GameMethod } from './core/game-methods'; import type { Auth } from './server/auth'; import type * as StorageAPI from './server/db/base'; import type { EventsAPI } from './plugins/plugin-events'; import type { LogAPI } from './plugins/plugin-log'; import type { RandomAPI } from './plugins/random/random'; import type { Operation } from 'rfc6902'; export type { StorageAPI }; export type AnyFn = (...args: any[]) => any; // "Public" state to be communicated to clients. export interface State<G extends any = any> { G: G; ctx: Ctx; deltalog?: Array<LogEntry>; plugins: { [pluginName: string]: PluginState; }; _undo: Array<Undo<G>>; _redo: Array<Undo<G>>; _stateID: number; } export type ErrorType = UpdateErrorType | ActionErrorType; export interface ActionError { type: ErrorType; // TODO(#723): Figure out if we want to strongly type payloads. payload?: any; } export interface TransientMetadata { error?: ActionError; } // TODO(#732): Actually define a schema for the action dispatch results. export type ActionResult = any; // "Private" state that may include garbage that should be stripped before // being handed back to a client. export interface TransientState<G extends any = any> extends State<G> { transients?: TransientMetadata; } export type PartialGameState = Pick<State, 'G' | 'ctx' | 'plugins'>; export type StageName = string; export type PlayerID = string; export type StageArg = | StageName | { stage?: StageName; /** @deprecated Use `minMoves` and `maxMoves` instead. */ moveLimit?: number; minMoves?: number; maxMoves?: number; }; export type ActivePlayersArg = | PlayerID[] | { currentPlayer?: StageArg; others?: StageArg; all?: StageArg; value?: Record<PlayerID, StageArg>; minMoves?: number; maxMoves?: number; /** @deprecated Use `minMoves` and `maxMoves` instead. */ moveLimit?: number; revert?: boolean; next?: ActivePlayersArg; }; export interface ActivePlayers { [playerID: string]: StageName; } export interface Ctx { numPlayers: number; playOrder: Array<PlayerID>; playOrderPos: number; activePlayers: null | ActivePlayers; currentPlayer: PlayerID; numMoves?: number; gameover?: any; turn: number; phase: string; _activePlayersMinMoves?: Record<PlayerID, number>; _activePlayersMaxMoves?: Record<PlayerID, number>; _activePlayersNumMoves?: Record<PlayerID, number>; _prevActivePlayers?: Array<{ activePlayers: null | ActivePlayers; _activePlayersMinMoves?: Record<PlayerID, number>; _activePlayersMaxMoves?: Record<PlayerID, number>; _activePlayersNumMoves?: Record<PlayerID, number>; }>; _nextActivePlayers?: ActivePlayersArg; _random?: { seed: string | number; }; } export interface DefaultPluginAPIs { events: EventsAPI; log: LogAPI; random: RandomAPI; } export interface PluginState { data: any; api?: any; } export interface LogEntry { action: | ActionShape.MakeMove | ActionShape.GameEvent | ActionShape.Undo | ActionShape.Redo; _stateID: number; turn: number; phase: string; redact?: boolean; automatic?: boolean; metadata?: any; patch?: Operation[]; } export 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> }) => Data; isInvalid?: ( context: Omit<PluginContext<API, Data, G>, 'api'> ) => false | string; action?: (data: Data, payload: ActionShape.Plugin['payload']) => Data; api?: (context: { G: G; ctx: Ctx; game: Game<G>; data: Data; playerID?: PlayerID; }) => API; flush?: (context: PluginContext<API, Data, G>) => Data; dangerouslyFlushRawState?: (flushCtx: { state: State<G>; game: Game<G>; api: API; data: Data; }) => State<G>; fnWrap?: ( moveOrHook: (context: FnContext<G>, ...args: any[]) => any, methodType: GameMethod ) => (context: FnContext<G>, ...args: any[]) => any; playerView?: (context: { G: G; ctx: Ctx; game: Game<G>; data: Data; playerID?: PlayerID | null; }) => any; } export type FnContext< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > = PluginAPIs & DefaultPluginAPIs & { G: G; ctx: Ctx; }; export type MoveFn< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > = ( context: FnContext<G, PluginAPIs> & { playerID: PlayerID }, ...args: any[] ) => void | G | typeof INVALID_MOVE; export interface LongFormMove< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { move: MoveFn<G, PluginAPIs>; redact?: boolean | ((context: { G: G; ctx: Ctx }) => boolean); noLimit?: boolean; client?: boolean; undoable?: boolean | ((context: { G: G; ctx: Ctx }) => boolean); ignoreStaleStateID?: boolean; } export type Move< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > = MoveFn<G, PluginAPIs> | LongFormMove<G, PluginAPIs>; export interface MoveMap< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { [moveName: string]: Move<G, PluginAPIs>; } export interface PhaseConfig< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { start?: boolean; next?: ((context: FnContext<G, PluginAPIs>) => string | void) | string; onBegin?: (context: FnContext<G, PluginAPIs>) => void | G; onEnd?: (context: FnContext<G, PluginAPIs>) => void | G; endIf?: ( context: FnContext<G, PluginAPIs> ) => boolean | void | { next: string }; moves?: MoveMap<G, PluginAPIs>; turn?: TurnConfig<G, PluginAPIs>; wrapped?: { endIf?: (state: State<G>) => boolean | void | { next: string }; onBegin?: (state: State<G>) => void | G; onEnd?: (state: State<G>) => void | G; next?: (state: State<G>) => string | void; }; } export interface StageConfig< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { moves?: MoveMap<G, PluginAPIs>; next?: string; } export interface StageMap< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { [stageName: string]: StageConfig<G, PluginAPIs>; } export interface TurnOrderConfig< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { first: (context: FnContext<G, PluginAPIs>) => number; next: (context: FnContext<G, PluginAPIs>) => number | undefined; playOrder?: (context: FnContext<G, PluginAPIs>) => PlayerID[]; } export interface TurnConfig< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { activePlayers?: ActivePlayersArg; minMoves?: number; maxMoves?: number; /** @deprecated Use `minMoves` and `maxMoves` instead. */ moveLimit?: number; onBegin?: (context: FnContext<G, PluginAPIs>) => void | G; onEnd?: (context: FnContext<G, PluginAPIs>) => void | G; endIf?: ( context: FnContext<G, PluginAPIs> ) => boolean | void | { next: PlayerID }; onMove?: ( context: FnContext<G, PluginAPIs> & { playerID: PlayerID } ) => void | G; stages?: StageMap<G, PluginAPIs>; order?: TurnOrderConfig<G, PluginAPIs>; wrapped?: { endIf?: (state: State<G>) => boolean | void | { next: PlayerID }; onBegin?: (state: State<G>) => void | G; onEnd?: (state: State<G>) => void | G; onMove?: (state: State<G> & { playerID: PlayerID }) => void | G; }; } export interface PhaseMap< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown> > { [phaseName: string]: PhaseConfig<G, PluginAPIs>; } export type AiEnumerate = Array< | { event: string; args?: any[] } | { move: string; args?: any[] } | ActionShape.MakeMove | ActionShape.GameEvent >; export interface Game< G extends any = any, PluginAPIs extends Record<string, unknown> = Record<string, unknown>, SetupData extends any = any > { name?: string; minPlayers?: number; maxPlayers?: number; deltaState?: boolean; disableUndo?: boolean; seed?: string | number; setup?: ( context: PluginAPIs & DefaultPluginAPIs & { ctx: Ctx }, setupData?: SetupData ) => G; validateSetupData?: ( setupData: SetupData | undefined, numPlayers: number ) => string | undefined; moves?: MoveMap<G, PluginAPIs>; phases?: PhaseMap<G, PluginAPIs>; turn?: TurnConfig<G, PluginAPIs>; events?: { endGame?: boolean; endPhase?: boolean; endTurn?: boolean; setPhase?: boolean; endStage?: boolean; setStage?: boolean; pass?: boolean; setActivePlayers?: boolean; }; endIf?: (context: FnContext<G, PluginAPIs>) => any; onEnd?: (context: FnContext<G, PluginAPIs>) => void | G; playerView?: (context: { G: G; ctx: Ctx; playerID: PlayerID | null }) => any; plugins?: Array<Plugin<any, any, G>>; ai?: { enumerate: (G: G, ctx: Ctx, playerID: PlayerID) => AiEnumerate; }; processMove?: ( state: State<G>, action: ActionPayload.MakeMove ) => State<G> | typeof INVALID_MOVE; flow?: ReturnType<typeof Flow>; } export type Undo<G extends any = any> = { G: G; ctx: Ctx; plugins: { [pluginName: string]: PluginState; }; moveType?: string; playerID?: PlayerID; }; 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; isConnected?: boolean; }; export interface MatchData { gameName: string; players: { [id: number]: PlayerMetadata }; setupData?: any; gameover?: any; nextMatchID?: string; unlisted?: boolean; createdAt: number; updatedAt: number; } export type AppCtx = Koa.DefaultContext & { db: StorageAPI.Async | StorageAPI.Sync; auth: Auth; }; export type App = Koa<Koa.DefaultState, AppCtx>; } export namespace LobbyAPI { export type GameList = string[]; type PublicPlayerMetadata = Omit<Server.PlayerMetadata, 'credentials'>; export type Match = Omit<Server.MatchData, 'players'> & { matchID: string; players: PublicPlayerMetadata[]; }; export interface MatchList { matches: Match[]; } export interface CreatedMatch { matchID: string; } export interface JoinedMatch { playerID: string; playerCredentials: string; } export interface NextMatch { nextMatchID: string; } } 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 CredentialedActionShape.Any> = 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 Patch = ReturnType<typeof ActionCreators.patch>; export type Reset = ReturnType<typeof ActionCreators.reset>; export type Undo = StripCredentials<CredentialedActionShape.Undo>; export type Redo = StripCredentials<CredentialedActionShape.Redo>; // Private type used only for internal error processing. // Included here to preserve type-checking of reducer inputs. export type StripTransients = ReturnType< typeof ActionCreators.stripTransients >; export type Any = | MakeMove | GameEvent | AutomaticGameEvent | Sync | Update | Patch | Reset | Undo | Redo | Plugin | StripTransients; } export namespace ActionPayload { type GetPayload<T extends ActionShape.Any> = Object.At<T, 'payload'>; export type MakeMove = GetPayload<ActionShape.MakeMove>; export type GameEvent = GetPayload<ActionShape.GameEvent>; } export type FilteredMetadata = { id: number; name?: string; isConnected?: boolean; }[]; export interface SyncInfo { state: State; filteredMetadata: FilteredMetadata; initialState: State; log: LogEntry[]; } export interface ChatMessage { id: string; sender: PlayerID; payload: any; }