Mercurial
view react_games/src/Wordle/main.tsx @ 136:75c144fd6964
[MrJuneJune] Fix the js file to hit correct endpoints.
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Fri, 09 Jan 2026 11:21:54 -0800 |
| parents | fb9bcd3145cb |
| children |
line wrap: on
line source
import { CSSProperties, useCallback, useEffect, useReducer } from 'react'; /** * Question: * Create a playable Wordle game in the browser using React within one hour. The game should allow the player to guess a hidden 5-letter word in at most 6 tries. After each guess, display feedback for each letter: correct letter and position (green), correct letter wrong position (yellow), or not in the word (gray). The game ends when the player guesses the word or runs out of tries. * * Requirements: * * Choose a fixed 5-letter target word in your code. * * Render a grid showing guesses made and remaining attempts. * * Accept input from the user (keyboard or on-screen). * * Color letters according to correctness after each guess. * * Display a win or lose message at the end of the game. */ const MAX_WORD_LEN = 5; const MAX_ATTEMP_LEN = 6; interface Position { letterPos: number; attempPos: number; } enum WordStatus { CORRECT_IN_POSITION, CORRECT_IN_WRONG_POSITION, DEFAULT, } interface Letter { value: string; status: WordStatus; } type Word = Letter[] type Board = Word[]; enum GameStatus { PLAYABLE="Try to guess the word!", WON="You won!", LOST="You Lost!", } interface GameState { board: Board; targetWord: string[]; position: Position; status: GameStatus; } const constructGameState = (): GameState => { const board: Board = Array.from( { length: MAX_ATTEMP_LEN }, () => { return Array.from({ length: MAX_WORD_LEN }, () => { return { value: "", status: WordStatus.DEFAULT, } }) } ); // dummy will get replace when it hits db const targetWord: string[] = "hello".split(''); const position: Position = { letterPos: 0, attempPos: 0, } return { board, status: GameStatus.PLAYABLE, targetWord, position, } } interface LetterComponentProp { letter: Letter; } interface StyleProp { board: CSSProperties; letter: (wordStatus: WordStatus) => CSSProperties; } const styles: StyleProp = { board: { display: "grid", gridTemplateColumns: `repeat(${MAX_WORD_LEN}, 1fr)`, width: 400, gap: 8, }, letter: (wordStatus: WordStatus): CSSProperties => { let backgroundColor: string = "#e6e6e6"; switch(wordStatus) { case WordStatus.CORRECT_IN_POSITION: backgroundColor = "green"; break; case WordStatus.CORRECT_IN_WRONG_POSITION: backgroundColor = "yellow"; break; case WordStatus.DEFAULT: break; } return { display: "flex", justifyContent: "center", alignItems: "center", backgroundColor, width: "100%", aspectRatio: "1 / 1", border: "1px solid black", } } } const LetterComponent = ({letter}: LetterComponentProp) => { return ( <div style={styles.letter(letter.status)}> {letter.value} </div> ) } enum GameActionEnum { INITIALZE, PLACE, }; type GameAction = | { type: GameActionEnum.PLACE, character: string } | { type: GameActionEnum.INITIALZE, targetWord: string }; const gameStateReducer = (state: GameState, action: GameAction): GameState => { if (state.status != GameStatus.PLAYABLE) return state; switch(action.type) { case GameActionEnum.INITIALZE: return { ...state, targetWord: action.targetWord.split(''), }; case GameActionEnum.PLACE: const { character } = action; let newBoard = state.board.map( (word, attempIdx) => attempIdx === state.position.attempPos ? word.map( (letter, letterIdx) => letterIdx === state.position.letterPos ? {...letter, value: character} : letter) : word); const newPosition = calculatePosition(state.position); let newStatus: GameStatus = state.status; if (newPosition.letterPos === 0) { const {correctLetterIdxes, correctWrongPositionLetterIndex} = gameLogic(newBoard[state.position.attempPos], state.targetWord); newBoard = newBoard.map( (word, attempIdx) => attempIdx === state.position.attempPos ? word.map( (letter, letterIdx) => correctLetterIdxes.some((value) => value === letterIdx) ? {...letter, status: WordStatus.CORRECT_IN_POSITION } : (correctWrongPositionLetterIndex.some((value) => value === letterIdx) ? {...letter, status: WordStatus.CORRECT_IN_WRONG_POSITION} : letter) ) : word); newStatus = correctLetterIdxes.length === MAX_WORD_LEN ? GameStatus.WON : ( state.position.attempPos + 1 === MAX_ATTEMP_LEN ? GameStatus.LOST : GameStatus.PLAYABLE ); } return { ...state, board: newBoard, position: newPosition, status: newStatus, }; } } function gameLogic(word: Word, targetWord: string[]) { const map: Record<string, number> = {}; const correctLetterIdxes: number[] = [] const correctWrongPositionLetterIndex: number[] = [] for (let i = 0; i < MAX_WORD_LEN; i++) { map[targetWord[i]] ? map[targetWord[i]]++ : map[targetWord[i]] = 1; } for (let i = 0; i < MAX_WORD_LEN; i++) { if (word[i].value === targetWord[i]) { correctLetterIdxes.push(i); map[targetWord[i]]--; } } for (let i = 0; i < MAX_WORD_LEN; i++) { if (correctLetterIdxes.some((value) => value===i)) continue; if ( targetWord.some((value) => value===word[i].value && map[value] > 0) ) { correctWrongPositionLetterIndex.push(i); map[word[i].value]--; } } return { correctLetterIdxes, correctWrongPositionLetterIndex, } } function calculatePosition(position: Position): Position { const letterPos = (position.letterPos + 1) % MAX_WORD_LEN; const attempPos = ( (position.letterPos + 1) === MAX_WORD_LEN ? position.attempPos + 1 : position.attempPos) % MAX_ATTEMP_LEN; return { letterPos, attempPos, } } const Wordle = () => { const [gameState, gameDispatch] = useReducer(gameStateReducer, null, constructGameState); useEffect(() => { (async() => { const res = await fetch('/api/v1/wordle'); if (!res.ok) return; const response = await res.json(); gameDispatch({ type: GameActionEnum.INITIALZE, targetWord: response.targetWord }); })(); }, []); const placeLetters = useCallback((event: KeyboardEvent) => { const letters = event.key.trim().toLowerCase(); const regex = new RegExp('[a-z]'); if ( !regex.exec(letters) || letters === "enter" ) { return; } gameDispatch({ type: GameActionEnum.PLACE, character: event.key.trim() }) }, [gameDispatch]); useEffect(() => { document.addEventListener("keypress", placeLetters) }, []) return ( <> <h1> Wordle </h1> <h2> {gameState.status} </h2> <div style={styles.board}> {gameState.board.map( (word, wordIdx) => word.map( (letter, letterIdx) => ( <LetterComponent key={`${wordIdx}-${letterIdx}`} letter={letter} /> )) )} </div> </> ); } export { Wordle, }