> 技术文档 > wordle game(猜词游戏)小demo【react + ts】

wordle game(猜词游戏)小demo【react + ts】

wordle game(猜词游戏)小demo。

绿色代表字母对位置对,黄色代表字母对位置错,灰色是都错。

源码地址

play~

preview

wordle game(猜词游戏)小demo【react + ts】

#1 - init

#2 - using json-server

创建db.json文件,录入需要mock的json数据。

npm install json-serverjson-server ./data/db.json [--port 3001]

#3 - Making a Wordle Hook

import { useState } from \'react\'type LetterColor = \'green\' | \'yellow\' | \'grey\'type LetterObject = { key: string color: LetterColor}type UseWordleReturn = { turn: number currentGuess: string guesses: LetterObject[][] isCorrect: boolean handleKeyup: (event: KeyboardEvent) => void}const useWordle = (solution: string): UseWordleReturn => { const [turn, setTurn] = useState(0) const [currentGuess, setCurrentGuess] = useState(\'\') const [guesses, setGuesses] = useState([]) const [history, setHistory] = useState([]) const [isCorrect, setIsCorrect] = useState(false) // format a guess into an array of letter objects // e.g. [{key: \'a\', color: \'yellow\'}] const formatGuess = (): LetterObject[] => { // TODO: 实现格式化逻辑 return [] } // add a new guess to the guesses state // update the isCorrect state if the guess is correct // add one to the turn state const addNewGuess = () => { // TODO: 实现添加新猜测逻辑 } // handle keyup event & track current guess // if user presses enter, add the new guess const handleKeyup = ({key} : KeyboardEvent) => { // todo:处理按键响应 } return { turn, currentGuess, guesses, isCorrect, handleKeyup }}export default useWordle

#4 - Tracking the Current Guess

 // handle keyup event & track current guess // if user presses enter, add the new guess const handleKeyup = ({key} : KeyboardEvent) => { console.log(\'key pressed - \' + key) if (key === \'Backspace\') { setCurrentGuess((prev) => prev.slice(0, -1)) return } if (/^[A-Za-z]$/.test(key)) { // 如果按下的是字母键,则添加到currentGuess if (currentGuess.length  prev + key.toLowerCase()) } } }

#5 - Submitting Guesses

const handleKeyup = ({key} : KeyboardEvent) => { // console.log(\'key pressed - \' + key) if (key === \'Enter\') { if (turn >= 6) { console.log(\'You have used all your guesses.\') return } if (currentGuess.length !== 5) { console.log(\'Current guess must be 5 characters long.\') return } if (history.includes(currentGuess)) { console.log(\'You have already guessed that word.\') return } const formatted = formatGuess() console.log(\'formatted guess: \', formatted) } if (key === \'Backspace\') { setCurrentGuess((prev) => prev.slice(0, -1)) return } if (/^[A-Za-z]$/.test(key)) { // 如果按下的是字母键,则添加到currentGuess if (currentGuess.length  prev + key.toLowerCase()) } } }

#6 - Checking & Formatting Guesses

 const formatGuess = (): LetterObject[] => { // console.log(\'formatting guess for \' + currentGuess) let solutionArray : (string | null)[] = [...solution] const formattedGuess: LetterObject[] = currentGuess.split(\'\').map((letter) => { return { key: letter, color: \'grey\' } }) // find all green letters formattedGuess.forEach((letterObject, index) => { if (letterObject.key === solutionArray[index]) { letterObject.color = \'green\' solutionArray[index] = null // remove from solutionArray so we don\'t match it again } }) // find all yellow letters formattedGuess.forEach((letterObject) => { if (letterObject.color === \'green\') return // skip already matched letters const letterIndex = solutionArray.indexOf(letterObject.key) if (letterIndex > -1) { letterObject.color = \'yellow\' solutionArray[letterIndex] = null // remove from solutionArray so we don\'t match it again } }) return formattedGuess }

#7 - Adding New Guesses

 // add a new guess to the guesses state // update the isCorrect state if the guess is correct // add one to the turn state const addNewGuess = (formattedGuess: LetterObject[]) => { if (currentGuess === solution) { setIsCorrect(true) } // console.log(\'adding new guess: \', formattedGuess) setGuesses(prevGuesses => [ ...prevGuesses, formattedGuess ]) setHistory(prevHistory => [ ...prevHistory, currentGuess ]) setCurrentGuess(\'\') setTurn(prevTurn => prevTurn + 1) }

#8 - Creating a Game Grid

import { Row } from \"./Row\"import type { LetterObject } from \"../hooks/useWordle\" // Adjust the path if neededtype GridProps = { guesses: LetterObject[][] currentGuess: string turn: number}const Grid = ({guesses, currentGuess, turn} : GridProps) => { return ( 
{guesses.map((guess, index) => { return })}
)}export default Grid
export const Row = () => { return ( 
)}

#9 - Showing Past Guesses

import type { LetterObject } from \"../hooks/useWordle\"type RowProps = { guess?: LetterObject[]}export const Row = ({ guess }: RowProps) => { if (guess) { return ( 
{guess.map((letter, index) => { return
{letter.key}
})}
) } return (
)}

#10 - Showing the Current Guess

import type { LetterObject } from \"../hooks/useWordle\"type RowProps = { guess?: LetterObject[] currentGuess?: string}export const Row = ({ guess, currentGuess }: RowProps) => { ... if (currentGuess) { let letters = currentGuess.split(\'\') return ( 
{letters.map((letter, index) => { return
{letter}
})} {/* Fill the rest of the row with empty divs */} {Array.from({ length: 5 - letters.length }).map((_, index) => (
))}
) } ...}

#11 - Animating Tiles

.row > div.green { --background: #5ac85a; --border-color: #5ac85a; animation: flip 0.5s ease forwards;}.row > div.grey { --background: #a1a1a1; --border-color: #a1a1a1; animation: flip 0.6s ease forwards;}.row > div.yellow { --background: #e2cc68; --border-color: #e2cc68; animation: flip 0.5s ease forwards;}.row > div:nth-child(2) { animation-delay: 0.2s;}.row > div:nth-child(3) { animation-delay: 0.4s;}.row > div:nth-child(4) { animation-delay: 0.6s;}.row > div:nth-child(5) { animation-delay: 0.8s;}.row.current > div.filled { animation: bounce 0.2s ease-in-out forwards;}/* keyframe animations */@keyframes flip { 0% { transform: rotateX(0); background: #fff; border-color: #333; } 45% { transform: rotateX(90deg); background: white; border-color: #333; } 55% { transform: rotateX(90deg); background: var(--background); border-color: var(--border-color); } 100% { transform: rotateX(0deg); background: var(--background); border-color: var(--border-color); color: #eee; }}@keyframes bounce { 0% { transform: scale(1); border-color: #ddd; } 50% { transform: scale(1.2); } 100% { transform: scale(1); border-color: #333; }}

#12 - Making a Keypad

import { useEffect, useState } from \"react\";export const Keypad = () => { const [letters, setLetters] = useState([]); useEffect(() => { const fetchLetters = async () => { const response = await fetch(\"/mock/db.json\"); const data = await response.json(); setLetters(data.letters.map((letter: { key: string }) => letter.key)); }; fetchLetters(); }, []); return ( 
{letters && letters.map((letter, index) =>
{letter}
)}
);};

#13 - Coloring Used Keys

import { useEffect, useState } from \"react\";import type { LetterColor } from \"../hooks/useWordle\";type KeypadProps = { usedKeys: { [key: string]: LetterColor }; // to track used keys and their colors};export const Keypad = ({ usedKeys }: KeypadProps) => { ... return ( 
{letters && letters.map((letter, index) => { const color = usedKeys[letter]; return (
{letter}
); })}
);};

#14 - Ending A Game

...export default function Wordle({ solution }: { solution: string }) { const { currentGuess, handleKeyup, guesses, isCorrect, turn, usedKeys } = useWordle(solution); useEffect(() => { window.addEventListener(\"keyup\", handleKeyup); if (isCorrect) { console.log(\"Congratulations! You\'ve guessed the word!\"); window.removeEventListener(\"keyup\", handleKeyup); } if (turn >= 6 && !isCorrect) { console.log(\"Game over! The correct word was: \" + solution); window.removeEventListener(\"keyup\", handleKeyup); } return () => { window.removeEventListener(\"keyup\", handleKeyup); }; }, [handleKeyup]); ...}

#15 - Making a Modal

import React from \'react\'type ModalProps = { isCorrect: boolean solution: string turn: number}const Modal = ({ isCorrect, solution, turn }: ModalProps) => { return ( <div className=\'modal\'> {isCorrect ? ( <div> <h2>Congratulations!</h2> <p>You guessed the word \"{solution}\" in {turn + 1} turns!</p> </div> ) : ( <div> <h2>Game Over</h2> <p>The correct word was \"{solution}\". Better luck next time!</p> </div> )} <button onClick={() => window.location.reload()}>Play Again</button> </div> )}export default Modal