Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to navigate between letters #12

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Header from "./Header";
import '../styles/App.css';
import { GlobalSettingsContext, useGlobalSettings } from "../hooks/useGlobalSettings";
import { StatisticsContext, useStatistics } from "../hooks/useStatistics";
import { UnderlinePositionProvider } from "../hooks/useUnderlinePosition";

function App() {
const globalSettings = useGlobalSettings();
Expand All @@ -11,12 +12,14 @@ function App() {
return (
<StatisticsContext.Provider value={statistics}>
<GlobalSettingsContext.Provider value={globalSettings}>
<UnderlinePositionProvider>

<div className="app-container">
<Header />
<Game />
</div>

</UnderlinePositionProvider>
</GlobalSettingsContext.Provider>
</StatisticsContext.Provider>
);
Expand Down
57 changes: 41 additions & 16 deletions src/components/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { useContext, useEffect, useMemo, useState } from 'react';
import { useLocalStorage } from '../hooks/useLocalStorage';
import { GuessDistributionKeys, StatisticsContext } from '../hooks/useStatistics';
import { useUnderlinePosition } from '../hooks/useUnderlinePosition';
import { DailyWord, GuessLetter, GuessLetterState, GuessValidationResult, KeyboardButtonStates, KeyboardLetterStates, SavedDailyGame } from '../models';
import { WORD_SIZE, SAVED_GAME_KEY, GUESS_LIST_SIZE, GAME_END_DELAY } from '../shared/GameConstants';
import { KEY_RIGHT, KEY_LEFT, KEY_BACKSPACE, KEY_ENTER, KEY_LETTERS } from '../shared/KeyboardConstants';
import { getDailyWord, getLast, getToday, wordList } from '../utils';
import EndGameScreen from './EndGameScreen';
import GuessList from './GuessList';
import Keyboard from './Keyboard';

export const WORD_SIZE = 5;
export const GUESS_LIST_SIZE = 6;

export const KEY_BACKSPACE = 'Backspace';
export const KEY_ENTER = 'Enter';
export const KEY_LETTERS = 'abcdefghijklmnopqrstuvwxyz';

export const GAME_END_DELAY = 0.8 * 1000;

export const SAVED_GAME_KEY = 'savedGame';
export const SAVED_GAME_INIT: SavedDailyGame = {
date: getToday(),
guesses: [[]],
Expand All @@ -32,16 +25,30 @@ const BUTTON_STATES_INIT: KeyboardButtonStates = {

const updateKeyboardButtonStates = (guesses: GuessLetter[][]): KeyboardButtonStates => {
const lastGuess = getLast(guesses || [[]]);
const arrayIsEmpty = lastGuess.some(isGuessLetterEmpty);

return {
letters: lastGuess.length < WORD_SIZE,
letters: lastGuess.length < WORD_SIZE || arrayIsEmpty,
back: lastGuess.length > 0,
enter: lastGuess.length === WORD_SIZE,
}
}

const isGuessLetterEmpty = (guessLetter : GuessLetter) : boolean => {
if(!guessLetter) return true;
if(Object.keys(guessLetter).length === 0) return true;

return false;
}

function Game() {
const [statistics, setStatistics] = useContext(StatisticsContext);
const {
underlinePosition,
updateUnderlinePosition,
backspaceUnderlinePosition,
setSpecificUnderlinePosition
} = useUnderlinePosition();

const [{
date: savedDate, guesses, winState, letterStates,
Expand Down Expand Up @@ -146,22 +153,27 @@ function Game() {

const handleKeyboardLetter = (letter: string) => {
if (winState.isGameEnded) return;
const last = [...getLast(guesses)];
last[underlinePosition] = { letter, state: 'typing' };

updateUnderlinePosition(true);

const updatedGuesses = updateLastGuess([...getLast(guesses), { letter, state: 'typing' }]);
const updatedGuesses = updateLastGuess(last);

setSavedGame({ guesses: updatedGuesses });
setButtonStates(updateKeyboardButtonStates(updatedGuesses));
}

const handleKeyboardBack = () => {
if (winState.isGameEnded) return;
if(underlinePosition > WORD_SIZE) return;

const lastGuess = getLast(guesses);
const newGuess: GuessLetter[] = lastGuess
.slice(0, lastGuess.length - 1)
.map(oldGuess => ({ letter: oldGuess.letter, state: 'typing' }) as GuessLetter);
lastGuess[underlinePosition] = {} as GuessLetter;

const updatedGuesses = updateLastGuess(newGuess);
backspaceUnderlinePosition();

const updatedGuesses = updateLastGuess(lastGuess);

setSavedGame({ guesses: updatedGuesses });
setButtonStates(updateKeyboardButtonStates(updatedGuesses));
Expand Down Expand Up @@ -213,10 +225,23 @@ function Game() {
letterStates: newLetterStates,
});
setButtonStates(updateKeyboardButtonStates(updatedGuesses));
setSpecificUnderlinePosition(0);
}
}

const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === KEY_RIGHT)
{
updateUnderlinePosition(true);
return;
}

if (event.key === KEY_LEFT)
{
updateUnderlinePosition(false);
return;
}

if (event.key === KEY_BACKSPACE && buttonStates.back) {
handleKeyboardBack();
return;
Expand Down
31 changes: 28 additions & 3 deletions src/components/GuessLetterView.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import { useContext } from "react";
import { GlobalSettingsContext } from "../hooks/useGlobalSettings";
import { useUnderlinePosition } from "../hooks/useUnderlinePosition";
import { GuessLetterViewProps } from "../models";
import '../styles/GuessLetterView.css';

function GuessLetterView(props: GuessLetterViewProps) {
const { underlinePosition, setSpecificUnderlinePosition } = useUnderlinePosition();
const [{isColorblindModeActive}] = useContext(GlobalSettingsContext);

const className = "letter-wrapper rounded d-flex justify-content-center align-items-center "
+ props.state
const getAlign = () : string => {
return props.letter ? "justify-content-center" : "justify-content-end";
}

const isValidaState = () : boolean => {
const validStates = [undefined, "typing", "wordlistError"];
return validStates.includes(props.state);
}

const canAddUnderscore = () : boolean => {
if (underlinePosition !== props.index) return false;

return isValidaState();
}

const onClick = () : void => {
if(props.index === undefined) return;
if(!isValidaState()) return;

setSpecificUnderlinePosition(props.index);
}

const className = `letter-wrapper rounded d-flex ${getAlign()} align-items-center `
+ `${props.state && props.state}`
+ ( !!props.marginRight ? ' me-2' : '' )
+ ( isColorblindModeActive ? ' colorblind': '' );

return (
<div className={className}>
<div className={className} onClick={onClick}>
{props.letter}
{canAddUnderscore() && <div className="letter-underline-active"></div>}
</div>
);
}
Expand Down
7 changes: 4 additions & 3 deletions src/components/GuessList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GuessLetter, GuessListProps } from "../models";
import { GUESS_LIST_SIZE, WORD_SIZE } from "../shared/GameConstants";
import { completeArray } from "../utils";
import { GUESS_LIST_SIZE, WORD_SIZE } from "./Game";
import GuessLetterView from "./GuessLetterView";

const completeList = (guesses: GuessLetter[][]) => {
Expand All @@ -25,9 +25,10 @@ function GuessList(props: GuessListProps) {
>
{guess.map((letter, letterIndex) => (
<GuessLetterView
index={letterIndex}
key={guessIndex + '-' + letterIndex}
letter={letter.letter}
state={letter.state}
letter={letter?.letter}
state={letter?.state}
marginRight={letterIndex !== guess.length - 1}
/>
))}
Expand Down
62 changes: 62 additions & 0 deletions src/hooks/useUnderlinePosition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createContext, ReactNode, useContext, useState } from "react";
import { WORD_SIZE } from "../shared/GameConstants";

type UnderlinePositionProps = {
children: ReactNode;
}

export type UnderlinePositionData = {
underlinePosition : number,
updateUnderlinePosition : (increaseCount : boolean) => void,
setSpecificUnderlinePosition : (position : number) => void,
backspaceUnderlinePosition : () => void
};

export const UnderlinePosition = createContext({} as UnderlinePositionData);

export function UnderlinePositionProvider({ children }: UnderlinePositionProps) : JSX.Element {
const [underlinePosition, setUnderlinePosition] = useState(0);

const updateUnderlinePosition = (increaseCount : boolean) : void => {
if (increaseCount && underlinePosition === WORD_SIZE - 1)
{
setSpecificUnderlinePosition(0);
return;
};
if (!increaseCount && underlinePosition === 0) return;

const value = increaseCount ? 1 : -1;
setUnderlinePosition(x => x += value);
}

const setSpecificUnderlinePosition = (position : number) : void => {
if (position > WORD_SIZE) return;
if (position < 0) return;

setUnderlinePosition(position);
}

const backspaceUnderlinePosition = () : void => {
if(underlinePosition === 0)
return;

setUnderlinePosition(x => x -= 1);
}

return (
<UnderlinePosition.Provider
value={{
underlinePosition,
updateUnderlinePosition,
setSpecificUnderlinePosition,
backspaceUnderlinePosition
}}
>
{children}
</UnderlinePosition.Provider>
);
}

export const useUnderlinePosition = () : UnderlinePositionData => {
return useContext(UnderlinePosition);
};
1 change: 1 addition & 0 deletions src/models/guesslist.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export interface GuessListProps {
}

export interface GuessLetterViewProps extends GuessLetter {
index? : number,
marginRight?: boolean;
}
4 changes: 4 additions & 0 deletions src/shared/GameConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const WORD_SIZE = 5;
export const GUESS_LIST_SIZE = 6;
export const GAME_END_DELAY = 0.8 * 1000;
export const SAVED_GAME_KEY = 'savedGame';
5 changes: 5 additions & 0 deletions src/shared/KeyboardConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const KEY_LEFT = 'ArrowLeft';
export const KEY_RIGHT = 'ArrowRight';
export const KEY_BACKSPACE = 'Backspace';
export const KEY_ENTER = 'Enter';
export const KEY_LETTERS = 'abcdefghijklmnopqrstuvwxyz';
13 changes: 13 additions & 0 deletions src/styles/GuessLetterView.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.letter-wrapper {
flex-direction: column;
border: 2px solid #252525;
width: 48px;
height: 40px;
Expand Down Expand Up @@ -61,4 +62,16 @@

.wordlistError.colorblind {
border: 3px solid #D55E00;
}

.letter-underline-active {
width: 80%;
margin-left: auto;
margin-right: auto;

border-bottom: 3px solid;
border-radius: 1rem !important;

align-self: end;
margin-bottom: 2px;
}