built-to-spell/src/store.tsx

138 lines
3.3 KiB
TypeScript
Raw Normal View History

2021-01-04 22:20:46 -05:00
import React, {
createContext,
Dispatch,
FunctionComponent,
Reducer,
useMemo,
useReducer,
} from 'react';
import twl06 = require('./twl06.json');
export interface State {
currentWord: string;
letters: string[];
score: number;
view: 'GAME' | 'SETUP';
words: string[];
}
const initialState: State = {
currentWord: '',
letters: [],
score: 0,
view: 'SETUP',
words: [],
};
export interface DeleteLetterAction {
type: 'DELETE_LETTER';
}
export interface EnterLetterAction {
type: 'ENTER_LETTER';
payload: string;
}
export interface EnterWordAction {
type: 'ENTER_WORD';
}
export interface SetLettersAction {
type: 'SET_LETTERS';
payload: string[];
}
export interface ShuffleLettersAction {
type: 'SHUFFLE_LETTERS';
}
export interface SwitchViewAction {
type: 'SWITCH_VIEW';
payload: State['view'];
}
type Action = DeleteLetterAction
| EnterLetterAction
| EnterWordAction
| SetLettersAction
| ShuffleLettersAction
| SwitchViewAction;
const shuffle = (letters: string[]): string[] => {
const newLetters = letters.slice(0, 1);
const choices = letters.slice(1);
for (let i = 1; i < letters.length; i += 1) {
const choice = Math.floor(Math.random() * choices.length);
newLetters.push(choices.splice(choice, 1)[0]);
}
return newLetters;
};
const canSubmitWord = (state: State): boolean => {
const { currentWord, words } = state;
return currentWord.length > 3 && !words.includes(currentWord)
&& currentWord.split('').includes(state.letters[0])
&& twl06.includes(currentWord.toLowerCase());
};
const scoreWord = (word: string): number => (word.length === 4 ? 1 : word.length);
const enterWordReducer: Reducer<State, EnterWordAction> = (state) => {
const valid = canSubmitWord(state);
return {
...state,
currentWord: '',
score: state.score + (valid ? scoreWord(state.currentWord) : 0),
words: valid ? [...state.words, state.currentWord] : state.words,
};
};
const reducer: Reducer<State, Action> = (state, action) => {
switch (action.type) {
case 'DELETE_LETTER':
return { ...state, currentWord: state.currentWord.slice(0, -1) };
case 'ENTER_LETTER':
return { ...state, currentWord: `${state.currentWord}${action.payload}` };
case 'ENTER_WORD':
return enterWordReducer(state, action);
case 'SET_LETTERS':
return action.payload.length === state.letters.length
&& action.payload.every((ch, i) => state.letters[i] === ch)
? state
: {
...state,
currentWord: '',
letters: action.payload,
score: 0,
words: [],
};
case 'SHUFFLE_LETTERS':
return { ...state, letters: shuffle(state.letters) };
case 'SWITCH_VIEW':
return { ...state, view: action.payload };
default:
throw new Error();
}
};
interface ContextValue {
state: State;
dispatch: Dispatch<Action>;
}
export const StoreContext = createContext({
state: initialState,
dispatch: () => undefined,
} as ContextValue);
export const StoreProvider: FunctionComponent = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const ctxVal = useMemo(() => ({ state, dispatch }), [state, dispatch]);
return (
<StoreContext.Provider value={ctxVal}>
{children}
</StoreContext.Provider>
);
};