wordle game(猜词游戏)小demo【react + ts】
wordle game(猜词游戏)小demo。
绿色代表字母对位置对,黄色代表字母对位置错,灰色是都错。
源码地址
play~
preview

#1 - init
#2 - using json-server
创建db.json文件,录入需要mock的json数据。
npm install json-server
json-server ./data/db.json [--port 3001]
#3 - Making a Wordle Hook
import { useState } from 'react'type LetterColor = 'green' | 'yellow' | 'grey'type LetterObject = {key: stringcolor: LetterColor
}type UseWordleReturn = {turn: numbercurrentGuess: stringguesses: LetterObject[][]isCorrect: booleanhandleKeyup: (event: KeyboardEvent) => void
}const useWordle = (solution: string): UseWordleReturn => {const [turn, setTurn] = useState<number>(0)const [currentGuess, setCurrentGuess] = useState<string>('')const [guesses, setGuesses] = useState<LetterObject[][]>([])const [history, setHistory] = useState<string[]>([])const [isCorrect, setIsCorrect] = useState<boolean>(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 stateconst addNewGuess = () => {// TODO: 实现添加新猜测逻辑}// handle keyup event & track current guess// if user presses enter, add the new guessconst 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 guessconst handleKeyup = ({key} : KeyboardEvent) => {console.log('key pressed - ' + key)if (key === 'Backspace') {setCurrentGuess((prev) => prev.slice(0, -1))return}if (/^[A-Za-z]$/.test(key)) {// 如果按下的是字母键,则添加到currentGuessif (currentGuess.length < 5) {setCurrentGuess((prev) => 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)) {// 如果按下的是字母键,则添加到currentGuessif (currentGuess.length < 5) {setCurrentGuess((prev) => 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 lettersformattedGuess.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 lettersformattedGuess.forEach((letterObject) => {if (letterObject.color === 'green') return // skip already matched lettersconst 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 stateconst 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: stringturn: number
}const Grid = ({guesses, currentGuess, turn} : GridProps) => {return (<div className="grid">{guesses.map((guess, index) => {return <Row key={index}></Row>})}</div>)
}export default Grid
export const Row = () => {return (<div className="row"><div></div><div></div><div></div><div></div><div></div><div></div></div>)
}
#9 - Showing Past Guesses
import type { LetterObject } from "../hooks/useWordle"type RowProps = {guess?: LetterObject[]
}export const Row = ({ guess }: RowProps) => {if (guess) {return (<div className="row">{guess.map((letter, index) => {return <div key={index} className={letter.color}>{letter.key}</div>})}</div>)}return (<div className="row"><div></div><div></div><div></div><div></div><div></div></div>)
}
#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 (<div className="row current">{letters.map((letter, index) => {return <div key={index} className="current">{letter}</div>})}{/* Fill the rest of the row with empty divs */}{Array.from({ length: 5 - letters.length }).map((_, index) => (<div key={index}></div>))}</div>)}...
}
#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<string[]>([]);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 (<div className="keypad">{letters &&letters.map((letter, index) => <div key={index}>{letter}</div>)}</div>);
};
#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 (<div className="keypad">{letters &&letters.map((letter, index) => {const color = usedKeys[letter];return (<div key={index} className={color}>{letter}</div>);})}</div>);
};
#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: booleansolution: stringturn: 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