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

wordle game(猜词游戏)小demo。

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

源码地址

play~

preview

#1 - init

#2 - using json-server

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

bash 复制代码
npm install json-server
json-server ./data/db.json [--port 3001]

#3 - Making a Wordle Hook

tsx 复制代码
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<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 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

tsx 复制代码
  // 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 < 5) {
        setCurrentGuess((prev) => prev + key.toLowerCase())
      }
    }
  }

#5 - Submitting Guesses

tsx 复制代码
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 < 5) {
        setCurrentGuess((prev) => prev + key.toLowerCase())
      }
    }
  }

#6 - Checking & Formatting Guesses

tsx 复制代码
  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

tsx 复制代码
  // 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

tsx 复制代码
import { Row } from "./Row"
import type { LetterObject } from "../hooks/useWordle" // Adjust the path if needed

type GridProps = {
  guesses: LetterObject[][]
  currentGuess: string
  turn: number
}

const Grid = ({guesses, currentGuess, turn} : GridProps) => {
  return (
    <div className="grid">
      {guesses.map((guess, index) => {
        return <Row key={index}></Row>
      })}
    </div>
  )
}

export default Grid
tsx 复制代码
export const Row = () => {
  return (
    <div className="row">
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </div>
  )
}

#9 - Showing Past Guesses

tsx 复制代码
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

tsx 复制代码
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

css 复制代码
.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

tsx 复制代码
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

tsx 复制代码
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

tsx 复制代码
...

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]);

  ...
}
ts 复制代码
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
相关推荐
摇滚侠22 分钟前
JavaScript 浮点数计算精度错误示例
开发语言·javascript·ecmascript
天蓝色的鱼鱼1 小时前
JavaScript垃圾回收:你不知道的内存管理秘密
javascript·面试
伍哥的传说1 小时前
React 英语打地鼠游戏——一个寓教于乐的英语学习游戏
学习·react.js·游戏
waillyer2 小时前
taro跳转路由取值
前端·javascript·taro
yume_sibai2 小时前
Vue 生命周期
前端·javascript·vue.js
gis收藏家2 小时前
游戏和地理空间应用中的空间索引
游戏
讨厌吃蛋黄酥3 小时前
🌟 React Router Dom 终极指南:二级路由与 Outlet 的魔法之旅
前端·javascript
FogLetter3 小时前
React中的forwardRef:打破父子组件间的"隔墙"
前端·react.js
m0_694845573 小时前
服务器系统时间不准确怎么办?
linux·运维·服务器·游戏·云计算
轻语呢喃4 小时前
useMemo & useCallback :React 函数组件中的性能优化利器
前端·javascript·react.js