137 lines
3.3 KiB
TypeScript
137 lines
3.3 KiB
TypeScript
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>
|
|
);
|
|
};
|