import { ErrorType, EventResult, IncomingEvent, IncomingEventType } from './gameEvent' import { GamePhase, GameState } from './gameState' import { DieResult, DieState, isSelectedState, setDeselected, setSelected, toggleSelected } from './dieState' import { isValidIndex } from '../util/precondition' export function handleEvent(state: GameState, event: IncomingEvent): EventResult { try { switch (event.type) { case IncomingEventType.SelectAction: return handleSelectActionEvent(state, event) case IncomingEventType.ToggleSelectDie: return handleToggleSelectIndexEvent(state, event) case IncomingEventType.SelectAllDice: return handleSelectAllEvent(state, true) case IncomingEventType.DeselectAllDice: return handleSelectAllEvent(state, false) case IncomingEventType.HoldSelectedDice: return handleHoldDiceEvent(state, true) case IncomingEventType.ReleaseSelectedDice: return handleHoldDiceEvent(state, false) case IncomingEventType.Abort: return handleAbortEvent(state) default: return { newState: state, error: { type: ErrorType.UnexpectedError, message: 'Event type not yet implemented', }, } } } catch (e) { return { newState: state, error: { type: ErrorType.UnexpectedError, message: `Failed handling event: ${e}`, }, } } } export interface SelectActionState { readonly gamePhase: GamePhase readonly theme: { readonly actions: readonly A[] } readonly action: A } export function handleSelectActionEvent>( state: T, { index }: { readonly index: number }, ): EventResult { if (state.gamePhase !== GamePhase.ROUND_START) { return { error: { type: ErrorType.NotValidRightNow, message: `You can only change actions when the turn has not started yet.`, }, newState: state, } } if (index < 0 || index >= state.theme.actions.length || index !== Math.floor(index)) { return { error: { type: ErrorType.InvalidIndex, message: `Select an integer action index between 0 (inclusive) and ${state.theme.actions.length} (exclusive)`, }, newState: state, } } return { newState: { ...state, action: state.theme.actions[index], }, events: [], } } export interface DiceStatesState { readonly gamePhase: GamePhase readonly lastRoll: readonly D[] } export function handleToggleSelectIndexEvent>( state: T, { index }: { readonly index: number }, ): EventResult { if (state.gamePhase !== GamePhase.TURN_ROLLED) { return { error: { type: ErrorType.NotValidRightNow, message: `You can only toggle the selected state of a die when dice have been rolled.`, }, newState: state, } } if (!isValidIndex(index, state.lastRoll.length)) { return { newState: state, error: { type: ErrorType.InvalidIndex, message: `Select an integer action index between 0 (inclusive) and ${state.lastRoll.length} (exclusive)`, }, } } return { newState: { ...state, lastRoll: [ ...state.lastRoll.slice(0, index), { ...state.lastRoll[index], state: toggleSelected(state.lastRoll[index].state), }, ...state.lastRoll.slice(index + 1), ], }, events: [], } } function handleSelectAllEvent>( state: T, select: boolean, ): EventResult { if (state.gamePhase !== GamePhase.TURN_ROLLED) { return { error: { type: ErrorType.NotValidRightNow, message: `You can only change the selected state of the dice when dice have been rolled.`, }, newState: state, } } return { newState: { ...state, lastRoll: state.lastRoll.map((x) => ({ ...x, state: select ? setSelected(x.state) : setDeselected(x.state), })), }, events: [], } } function handleHoldDiceEvent>( state: T, hold: boolean, ): EventResult { if (state.gamePhase !== GamePhase.TURN_ROLLED) { return { error: { type: ErrorType.NotValidRightNow, message: `You can only toggle the hold state of dice when dice have been rolled.`, }, newState: state, } } return { newState: { ...state, lastRoll: state.lastRoll.map((v) => { if (isSelectedState(v.state)) { return { ...v, state: hold ? DieState.HELD_SELECTED : DieState.SELECTED, } } else { return v } }), }, events: [], } } function handleAbortEvent(state: T): EventResult { if (state.gamePhase === GamePhase.ABORTED || state.gamePhase === GamePhase.VICTORY) { return { error: { type: ErrorType.NotValidRightNow, message: `The game is already over.`, }, newState: state, } } return { newState: { ...state, gamePhase: GamePhase.ABORTED, }, events: [], } }