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
相关推荐
TimelessHaze15 分钟前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
海海思思44 分钟前
React 虚拟列表中的 Hook 使用陷阱
react.js
bug爱好者1 小时前
Vue3 基于Element Plus 的el-input,封装一个数字输入框组件
前端·javascript
乔公子搬砖1 小时前
小程序开发提效:npm支持、Vant Weapp组件库与API Promise化(八)
前端·javascript·微信小程序·js·promise·vagrant·事件绑定
没烦恼3013 小时前
登录表单提交:按钮点击事件 vs form submit事件,该如何选择?
javascript
掘金安东尼5 小时前
Rspack 推出 Rslint:一个用 Go 编写的 TypeScript-First Linter
前端·javascript·github
黑客影儿5 小时前
在Godot中为您的游戏添加并控制游戏角色的完整技术指南
开发语言·游戏·游戏引擎·godot·gdscript·游戏开发·3d游戏
蓝胖子的小叮当5 小时前
JavaScript基础(十四)字符串方法总结
前端·javascript
weixin_541299947 小时前
VSCode: 从插件安装到配置,如何实现 Ctrl+S 保存时,完全按照 .eslintrc.js 中的 ESLint 规则自动格式化代码
javascript·ide·vscode
yw00yw7 小时前
常见的设计模式
开发语言·javascript·设计模式