Open-source interactive fiction engine, powered by React, Redux and TypeScript. _ https://discoteque.pub/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

195 lines
6.6 KiB

import * as R from 'ramda';
import { createStore, Reducer, combineReducers } from 'redux';
import { EngineState, Chrono } from 'discoteque/lib/engine/types';
type INIT = 'INIT';
type ENGINE_INIT = 'INIT-ENGINE';
type ENGINE_RESET = 'RESET';
type ENGINE_LOAD = 'LOAD-ENGINE';
type ENGINE_SET_STATE = 'SET-STATE';
type ENGINE_ADVANCE_LINE = 'ADVANCE-LINE';
type ENGINE_ADVANCE_TIME = 'ADVANCE-TIME';
type ENGINE_CHANGE_NODE = 'CHANGE-NODE';
type ENGINE_CHANGE_LOCATION = 'CHANGE-LOCATION';
type ENGINE_SET_SKILL = 'SET-SKILL';
type ENGINE_SET_SKILLPOINTS = 'SET-SKILLPOINTS';
type ENGINE_SET_CHRONO = 'SET-CHRONO';
type ENGINE_LOCK_SKILLS = 'LOCK-SKILLS';
type ENGINE_UNLOCK_SKILLS = 'UNLOCK-SKILLS';
type EngineActionType =
INIT
| ENGINE_INIT
| ENGINE_RESET
| ENGINE_ADVANCE_LINE
| ENGINE_CHANGE_NODE
| ENGINE_CHANGE_LOCATION
| ENGINE_SET_SKILL
| ENGINE_SET_SKILLPOINTS
| ENGINE_LOAD
| ENGINE_ADVANCE_TIME
| ENGINE_SET_CHRONO
| ENGINE_LOCK_SKILLS
| ENGINE_UNLOCK_SKILLS
| ENGINE_SET_STATE;
interface IEngineAdvanceLine {
line: number;
}
interface IEngineChangeNode {
node: string;
}
interface IEngineChangeLocaiton {
location: string;
}
interface IEngineSetSkill {
skill: string,
value: number,
}
interface IEngineSetState {
state: any,
}
interface IEngineInitAction<GS> extends EngineState<GS> {};
type EngineActionData<GS = object> =
IEngineAdvanceLine
| IEngineChangeNode
| IEngineChangeLocaiton
| IEngineInitAction<GS>
| IEngineSetSkill
| IEngineSetState
| Chrono
| string
| number
| null;
export interface IEngineStateAction<GS = object, MT = undefined, ST extends string = string> {
type: EngineActionType;
data?: EngineActionData<GS>;
}
export const initEngineState = <GS, MT, ST extends string = string>(state: EngineState<GS, MT, ST>): IEngineStateAction<GS> =>
({ type: 'INIT-ENGINE', data: state });
export const resetState = <GS, MT, ST extends string = string>(startNode: string): IEngineStateAction<GS, MT, ST> =>
({ type: 'RESET', data: startNode })
export const setState = <GS, MT, ST extends string = string>(state: any): IEngineStateAction<GS, MT, ST> =>
({ type: 'SET-STATE', data: state });
export const loadEngine = <GS, MT, ST extends string = string>(state: EngineState<GS, MT, ST>): IEngineStateAction<GS> =>
({ type: 'LOAD-ENGINE', data: state })
export const advanceLine = <GS, MT, ST extends string = string>(line: number): IEngineStateAction<GS, MT, ST> =>
({ type: 'ADVANCE-LINE', data: { line } });
export const advanceTime = <GS, MT, ST extends string = string>(time: number): IEngineStateAction<GS, MT, ST> =>
({ type: 'ADVANCE-TIME', data: time });
export const setChrono = <GS, MT, ST extends string = string>(chrono: Chrono | undefined): IEngineStateAction<GS, MT, ST> =>
({ type: 'SET-CHRONO', data: chrono });
export const changeNode = <GS, MT, ST extends string = string>(node: string): IEngineStateAction<GS, MT, ST> =>
({ type: 'CHANGE-NODE', data: { node } });
export const changeLocation = <GS, MT, ST extends string = string>(location: string): IEngineStateAction<GS, MT, ST> =>
({ type: 'CHANGE-LOCATION', data: { location } });
export const setSkill = <GS, MT, ST extends string = string>(skill: string, value: number): IEngineStateAction<GS, MT, ST> =>
({ type: 'SET-SKILL', data: { skill, value } });
export const setSkillpoints = <GS, MT, ST extends string = string>(value: number): IEngineStateAction<GS, MT, ST> =>
({ type: 'SET-SKILLPOINTS', data: value });
export const lockSkills = <GS, MT, ST extends string = string>(): IEngineStateAction<GS, MT, ST> =>
({ type: 'LOCK-SKILLS' });
export const unlockSkills = <GS, MT, ST extends string = string>(): IEngineStateAction<GS, MT, ST> =>
({ type: 'UNLOCK-SKILLS' });
type InitStateData<GS> = {
engineState: EngineState,
gameState: GS,
};
export const initState = <GS = object>(data: InitStateData<GS>) =>
({ type: 'INIT', data });
export const setSkillMenu = (value: boolean) => ({
type: 'SET-STATE',
data: {
ui: { skillsOpen: value },
}
});
const getDefaultState = (): EngineState => ({
nodeMap: {},
line: 0,
node: '',
skills: {},
backlog: [],
skillPoints: 0,
protectSkills: false,
ui: {
isOver: false,
skillsOpen: false,
menuOpen: true,
}
});
const reducer = (state = getDefaultState(), action: IEngineStateAction): EngineState => {
if (action.type === 'INIT-ENGINE') {
return action.data as EngineState;
} else if (action.type === 'INIT') {
return (action.data as any).engineState as EngineState;
} else if (action.type === 'RESET') {
const newState = {
...getDefaultState(),
line: 0,
node: action.data as string,
nodeMap: state.nodeMap,
skills: Object.keys(state.skills).reduce((acc, key) => ({ ...acc, [key]: 5 }), {}),
skillPoints: state.skillPoints,
};
return newState;
} else if (action.type === 'LOAD-ENGINE') {
const { nodeMap, ...newState } = action.data as EngineState;
return {
...state,
...newState,
};
} else if (action.type === 'SET-STATE') {
return R.merge(state, action?.data as object ||{});
} else if (action.type === 'ADVANCE-LINE') {
return { ...state, ...action.data as object };
} else if (action.type === 'SET-CHRONO') {
return { ...state, chrono: action.data as Chrono | undefined }
} else if (action.type === 'ADVANCE-TIME') {
const newTime = (state?.chrono?.time || 0) + (action.data as number);
return { ...state, chrono: {
...state?.chrono,
time: newTime <= 1440 ? newTime : 1,
} }
} else if (action.type === 'CHANGE-NODE') {
return { ...state, ...action.data as object, line: 0 };
} else if (action.type === 'CHANGE-LOCATION') {
return { ...state, ...action.data as object };
} else if (action.type === 'SET-SKILL') {
const data = action.data as IEngineSetSkill;
return { ...state, skills: { ...state.skills, [data.skill]: data.value } }
} else if (action.type === 'SET-SKILLPOINTS') {
const data = action.data as number;
return { ...state, skillPoints: data }
} else if (action.type === 'LOCK-SKILLS') {
return { ...state, protectSkills: true };
} else if (action.type === 'UNLOCK-SKILLS') {
return { ...state, protectSkills: false };
}
return state;
}
export const stateSelector: <GS = object, MT = undefined, ST extends string = string>(state: object) => EngineState<GS, MT, ST> =
R.propOr(getDefaultState(), 'engine');
export const gameStateSelector: <GS = object>(state: object) => GS =
R.propOr(getDefaultState(), 'game');
function makeStore<T>(gameReducer: Reducer): T {
return createStore(combineReducers({
engine: reducer,
game: gameReducer
}));
}
export default makeStore;