15 Puzzle Game使用ReactJS
在本文中,我们将使用ReactJS创建15 Puzzle Game。15 puzzle game基本上是一款基于图块的游戏,其中有16个图块,其中1个图块是空的,其余的图块以1到15的随机顺序填充数字。用户必须按照数字顺序排列所有图块,规则是他们只能移动直接邻接空图块的图块。
在这个项目中,我们基本上使用了React函数组件和使用了React hooks,比如useState, useEffect等。玩家可以将图块从其位置拖动到相邻的空位置。通过使用JSX实现拖动和获胜的逻辑。
让我们看看我们的最终项目将是什么样子:
使用的技术/先决条件:
- ReactJs
- Tailwind CSS
- JSX
- 在React中使用功能组件
- React Hooks
方法: 容器是有状态的React组件(基于class)。组件是无状态的React组件(基于函数)。在这个项目中,我使用了组件(基于函数),为了使它们有状态,我在React中使用了hooks,如useState、useEffect等。
项目结构
步骤
1. 使用以下命令设置React项目
npx create-react-app <name of project>
2. 使用以下步骤导航到项目文件夹
cd <name_of_project>
3. 创建一个名为“ components ”的文件夹来存储组件,以及一个名为“ utils ”的文件夹,我们将在其中创建随机生成数字数组的实用函数。在“components”文件夹中添加4个文件,分别是“Game.js”、“Puzzle.js”、“Tile.js”、“Timer.js”,并在“utils”文件夹中创建一个名为“shuffleFunction.js”的文件。
4. 要在项目中添加 tailwindcss,请在“index.html”中添加以下脚本标签。
<script src="https://cdn.tailwindcss.com"></script>
在不同的文件中编写以下代码(文件名在每个代码块的第一行中提到)。
示例:
- index.html : 自动创建的文件,在这里我们需要导入tailwindcss标签。
- index.js : 自动创建的文件,React用于最终渲染。
- App.js : 此文件导入Game组件并导出它。
- Game.js : 此文件包含游戏的整体逻辑和所有所需的组件。
- Puzzle.js : 此文件包含拼图游戏的组件。
- Tile.js : 此文件包含两个组件,分别是“EmptyTile”和“FilledTile”,用于在Puzzle.js中渲染拼图块。
- Timer.js : 此文件包含计时器的逻辑并渲染时间。
- shuffleFunction.js : 此文件包含将数组从1到16洗牌的逻辑,其中第16个位置为空字符串,并返回洗牌后的数组。
JavaScript
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Javascript
// App.js
import Game from "./components/Game";
export default function App() {
return <Game />
}
Javascript
// Game.js
import { useEffect, useState } from "react";
import shuffleArray from "../utils/shuffleFunction";
import Puzzle from "./Puzzle";
import Timer from "./Timer";
export default function Game() {
const [shuffledArray, setShuffledArray] = useState(shuffleArray());
const [moves, setMoves] = useState(0);
const [time, setTime] = useState(0);
const [timerActive, setTimerActive] = useState(false);
const [win, setWin] = useState(false);
useEffect(() => {
if (moves === 1) setTimerActive(true);
let won = true;
for (let i = 0; i < shuffledArray.length - 1; i++) {
const value = shuffledArray[i];
if (i == value - 1) continue;
else {
won = false;
break;
}
}
if (won) {
setWin(true);
setTimerActive(false);
}
return;
}, [moves]);
const newGame = () => {
setMoves(0);
setTimerActive(false);
setTime(0);
setShuffledArray(shuffleArray());
setWin(false);
};
const dragStart = (e) => e.dataTransfer.setData("tile", e.target.id);
const dragOver = (e) => e.preventDefault();
const dropped = (e) => {
e.preventDefault();
const tile = e.dataTransfer.getData("tile");
const oldPlace = Number(document.getElementById(tile).parentElement.id.slice(6)) - 1;
const newPlace = Number(e.target.id.slice(6)) - 1;
if (!(Math.abs(oldPlace - newPlace) == 4 || Math.abs(oldPlace - newPlace) == 1)) return;
const [i, j] = [Math.min(oldPlace, newPlace), Math.max(oldPlace, newPlace)];
setShuffledArray([
...shuffledArray.slice(0, i),
shuffledArray[j],
...shuffledArray.slice(i + 1, j),
shuffledArray[i],
...shuffledArray.slice(j + 1),
]);
setMoves(moves + 1);
};
return (
<div className="h-screen flex text-gray-300 bg-gray-950">
<div className="mx-auto mt-8">
{win && (
<div className="rounded-md border-l-4 border-green-500 bg-green-100 p-2 mb-2">
<div className="flex items-center justify-center space-x-4">
<p className="font-medium text-green-600">
HURRAY!! You have won the game 🙂
</p>
</div>
</div>
)}
<h1 className="text-3xl text-emerald-600 font-bold text-center">
GeeksforGeeks
</h1>
<h3 className="text-xl font-bold text-center bg-clip-text text-transparent bg-gradient-to-r from-indigo-500 from-10% via-sky-500 via-30% to-emerald-500 to-90%">
15 Puzzle Game
</h3>
<div className="flex justify-between px-6 mt-2">
<p>Moves: {moves}</p>
<Timer time={time} timerActive={timerActive} setTime={setTime} />
</div>
<Puzzle shuffledArray={shuffledArray} dragStart={dragStart} dragOver={dragOver} dropped={dropped}/>
<div className="px-6 mt-4">
<button
onClick={newGame}
className="text-black font-bold block bg-gray-900 p-2 rounded w-full h-full bg-gradient-to-r from-indigo-500 from-10% via-sky-500 via-30% to-emerald-500 to-90%"
>
New Game
</button>
</div>
</div>
</div>
);
}
Javascript
// Puzzle.js
import { FilledTile, EmptyTile } from "./Tile";
export default function Puzzle({ shuffledArray, dragOver, dragStart, dropped }){
return (
<div className="grid grid-cols-4 gap-8 mt-6 px-6 rounded">
{shuffledArray.map((value, index) => {
if (value === "")
return (
<EmptyTile dragOver={dragOver} dropped={dropped} index={index} />
);
return (
<FilledTile index={index} value={value} dragStart={dragStart} />
);
})}
</div>
)
}
Javascript
// Tile.js
export function FilledTile({index, value, dragStart}) {
return (
<div
id={`place-{index + 1}`}
className={
"shadow w-20 h-20 rounded " +
(index == value - 1
? "bg-gradient-to-r from-pink-500 to-yellow-500"
: "bg-gray-900")
}
>
<p
id={`tile-{value}`}
draggable="true"
onDragStart={dragStart}
className="fw-bold text-xl grid grid-cols-1 place-items-center w-20 h-20 rounded cursor-pointer hover:bg-gray-800"
>
{value}
</p>
</div>
);
}
export function EmptyTile({dragOver, dropped, index}) {
return (
<div
onDragOver={dragOver}
onDrop={dropped}
id={`place-${index + 1}`}
className="bg-gray-900 shadow w-20 h-20 rounded"
></div>
);
}
JavaScript
// Timer.js
import { useEffect } from "react";
export default function Timer({ time, setTime, timerActive }) {
useEffect(() => {
let interval = null;
if (timerActive)
interval = setInterval(() => {
setTime((time) => time + 1);
}, 1000);
else clearInterval(interval);
return () => {
clearInterval(interval);
};
}, [timerActive]);
return <p>Time: {time}s</p>;
}
Javascript
// shuffleFunction.js
export default function shuffleArray() {
let array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ""];
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
HTML
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Project | 15 Puzzle Game</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
运行应用程序的步骤:
1. 在终端中输入以下命令:
npm start
2. 在网络浏览器中打开以下URL:
http://localhost:3000/
输出: