+ Select page
This commit is contained in:
parent
cd49788cd9
commit
9a8505b0d9
3 changed files with 222 additions and 190 deletions
208
src/App.js
208
src/App.js
|
@ -1,194 +1,28 @@
|
||||||
import { useState } from "react";
|
import React from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const NameList = () => {
|
||||||
|
const names = ['tictactoe'];
|
||||||
|
|
||||||
function Square({ row, column, value, clickHandler }) {
|
|
||||||
let buttonClass = ""
|
|
||||||
switch (value){
|
|
||||||
case "X":
|
|
||||||
buttonClass = "square bg-amber-200 "
|
|
||||||
break;
|
|
||||||
case "O":
|
|
||||||
buttonClass = "square bg-violet-300"
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
buttonClass = "square bg-white"
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<button
|
<div className="min-h-screen bg-gray-100 flex justify-center items-center p-4">
|
||||||
className={buttonClass}
|
<div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
|
||||||
onClick={() => clickHandler(row, column)}
|
<h2 className="text-3xl font-bold text-center text-indigo-600 mb-6">想看看什么</h2>
|
||||||
|
<ul className="space-y-4">
|
||||||
|
{names.map((name) => (
|
||||||
|
<li key={name}>
|
||||||
|
<Link
|
||||||
|
to={`/${name}`}
|
||||||
|
className="block text-center text-xl font-semibold text-gray-700 hover:text-indigo-500 hover:bg-indigo-100 py-2 px-4 rounded-lg transition-all"
|
||||||
>
|
>
|
||||||
{value}
|
{name.toUpperCase()}
|
||||||
</button>
|
</Link>
|
||||||
);
|
</li>
|
||||||
}
|
))}
|
||||||
|
</ul>
|
||||||
function SquareRow({ rowId, rows, clickHandler }) {
|
|
||||||
const items =
|
|
||||||
rows.map((ele, index) =>
|
|
||||||
<Square
|
|
||||||
key={index}
|
|
||||||
row={rowId}
|
|
||||||
column={index}
|
|
||||||
value={ele}
|
|
||||||
clickHandler={clickHandler}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return <>{items}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Map({ mapSize }) {
|
|
||||||
// Config Here
|
|
||||||
const ticTacToeMaxMapSize = 6
|
|
||||||
|
|
||||||
if (mapSize == undefined) {
|
|
||||||
mapSize = 3;
|
|
||||||
}
|
|
||||||
const [map, setMap] = useState(
|
|
||||||
Array(mapSize).fill(null)
|
|
||||||
.map(() => Array(mapSize).fill(null))
|
|
||||||
);
|
|
||||||
const [currentMove, setCurrentMove] = useState(0);
|
|
||||||
const xIsNext = currentMove % 2 == 0;
|
|
||||||
const [winner, setWinner] = useState(null);
|
|
||||||
const [history, setHistory] = useState([Array(mapSize).fill(null)
|
|
||||||
.map(() => Array(mapSize).fill(null))])
|
|
||||||
|
|
||||||
function updateMap(nextMap) {
|
|
||||||
setHistory([...history, nextMap])
|
|
||||||
setMap(nextMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClick(row, column) {
|
|
||||||
if (map[row][column] != null || winner != null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const nextMap = map.map(row => [...row]);
|
|
||||||
if (xIsNext) {
|
|
||||||
nextMap[row][column] = 'X'
|
|
||||||
} else {
|
|
||||||
nextMap[row][column] = 'O'
|
|
||||||
}
|
|
||||||
updateMap(nextMap)
|
|
||||||
setCurrentMove(currentMove + 1)
|
|
||||||
if (calculateWinner(row, column,nextMap)) {
|
|
||||||
setWinner(nextMap[row][column])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateWinner(row, column,map) {
|
|
||||||
const who = map[row][column]; // 当前玩家标记
|
|
||||||
const winNeed = mapSize > ticTacToeMaxMapSize ? 5 : mapSize; // 棋盘大小(>5五子棋)
|
|
||||||
|
|
||||||
// 横向检查
|
|
||||||
let horizontal = 1;
|
|
||||||
for (let hLeft = column - 1; hLeft >= 0; hLeft--) {
|
|
||||||
if (map[row][hLeft] !== who) break;
|
|
||||||
horizontal++;
|
|
||||||
}
|
|
||||||
for (let hRight = column + 1; hRight < mapSize; hRight++) {
|
|
||||||
if (map[row][hRight] !== who) break;
|
|
||||||
horizontal++;
|
|
||||||
}
|
|
||||||
if (horizontal >= winNeed) return true;
|
|
||||||
|
|
||||||
// 纵向检查
|
|
||||||
let vertical = 1;
|
|
||||||
for (let vUp = row - 1; vUp >= 0; vUp--) {
|
|
||||||
if (map[vUp][column] !== who) break;
|
|
||||||
vertical++;
|
|
||||||
}
|
|
||||||
for (let vDown = row + 1; vDown < mapSize; vDown++) {
|
|
||||||
if (map[vDown][column] !== who) break;
|
|
||||||
vertical++;
|
|
||||||
}
|
|
||||||
if (vertical >= winNeed) return true;
|
|
||||||
|
|
||||||
// 主对角线 (左上到右下)
|
|
||||||
let mainDiagonal = 1;
|
|
||||||
for (let dUp = 1; row - dUp >= 0 && column - dUp >= 0; dUp++) {
|
|
||||||
if (map[row - dUp][column - dUp] !== who) break;
|
|
||||||
mainDiagonal++;
|
|
||||||
}
|
|
||||||
for (let dDown = 1; row + dDown < mapSize && column + dDown < mapSize; dDown++) {
|
|
||||||
if (map[row + dDown][column + dDown] !== who) break;
|
|
||||||
mainDiagonal++;
|
|
||||||
}
|
|
||||||
if (mainDiagonal >= winNeed) return true;
|
|
||||||
|
|
||||||
// 副对角线 (右上到左下)
|
|
||||||
let antiDiagonal = 1;
|
|
||||||
for (let dUp = 1; row - dUp >= 0 && column + dUp < mapSize; dUp++) {
|
|
||||||
if (map[row - dUp][column + dUp] !== who) break;
|
|
||||||
antiDiagonal++;
|
|
||||||
}
|
|
||||||
for (let dDown = 1; row + dDown < mapSize && column - dDown >= 0; dDown++) {
|
|
||||||
if (map[row + dDown][column - dDown] !== who) break;
|
|
||||||
antiDiagonal++;
|
|
||||||
}
|
|
||||||
if (antiDiagonal >= winNeed) return true;
|
|
||||||
|
|
||||||
// 如果没有满足条件,返回 false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapEle = map.map((ele, index) => (
|
|
||||||
<div key={`row-${index}`} className="board-row"> {/* Unique key for each row */}
|
|
||||||
<SquareRow rowId={index} rows={ele} clickHandler={handleClick} />
|
|
||||||
</div>
|
</div>
|
||||||
));
|
|
||||||
|
|
||||||
function handleLeftClick() {
|
|
||||||
if (currentMove > 0) {
|
|
||||||
setMap(history[currentMove - 1]);
|
|
||||||
setCurrentMove(currentMove - 1);
|
|
||||||
setHistory(history.slice(0, -1));
|
|
||||||
setWinner(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rounded-xl bg-sky-50 shadow-xl table items-center space-y-2 container mx-auto content-center">
|
|
||||||
<p className="font-sans text-2xl text-center ">{mapSize > ticTacToeMaxMapSize ? "五子棋" : "井字棋"}模式</p>
|
|
||||||
<p className="text-xl text-center ">现在是 {xIsNext ? "X" : "O"} 移动</p>
|
|
||||||
<div className="flex flex-row justify-center items-center">
|
|
||||||
<div className="board" >
|
|
||||||
{mapEle}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-row justify-center items-center">
|
|
||||||
<button className="bg-cyan-50 shadow-md rounded-md text-lg" onClick={handleLeftClick}>
|
|
||||||
←回到上一个
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p className="text-xl text-center">{winner != null ? winner + "赢了" : ""} </p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function Game(){
|
export default NameList;
|
||||||
const location = useLocation();
|
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
const useSize = params.get('mapSize');
|
|
||||||
let realSize = parseInt(useSize)
|
|
||||||
if (realSize == NaN || realSize < 3 || realSize > 20){
|
|
||||||
realSize = 3
|
|
||||||
}
|
|
||||||
return(
|
|
||||||
<>
|
|
||||||
<Map mapSize={realSize}/>
|
|
||||||
|
|
||||||
<div className="rounded-xl bg-green-50 shadow-md flex space-x-2 container mx-auto">
|
|
||||||
<form >
|
|
||||||
<label className="bg-green-50">
|
|
||||||
地图大小:
|
|
||||||
<input name="mapSize" defaultValue={realSize} type="number" />
|
|
||||||
</label>
|
|
||||||
<button type="submit" className="bg-cyan-200 rounded-md text-sm">确认</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
194
src/TicTacToe.js
Normal file
194
src/TicTacToe.js
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
function Square({ row, column, value, clickHandler }) {
|
||||||
|
let buttonClass = ""
|
||||||
|
switch (value){
|
||||||
|
case "X":
|
||||||
|
buttonClass = "square bg-amber-200 "
|
||||||
|
break;
|
||||||
|
case "O":
|
||||||
|
buttonClass = "square bg-violet-300"
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
buttonClass = "square bg-white"
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={buttonClass}
|
||||||
|
onClick={() => clickHandler(row, column)}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SquareRow({ rowId, rows, clickHandler }) {
|
||||||
|
const items =
|
||||||
|
rows.map((ele, index) =>
|
||||||
|
<Square
|
||||||
|
key={index}
|
||||||
|
row={rowId}
|
||||||
|
column={index}
|
||||||
|
value={ele}
|
||||||
|
clickHandler={clickHandler}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <>{items}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Map({ mapSize }) {
|
||||||
|
// Config Here
|
||||||
|
const ticTacToeMaxMapSize = 6
|
||||||
|
|
||||||
|
if (mapSize == undefined) {
|
||||||
|
mapSize = 3;
|
||||||
|
}
|
||||||
|
const [map, setMap] = useState(
|
||||||
|
Array(mapSize).fill(null)
|
||||||
|
.map(() => Array(mapSize).fill(null))
|
||||||
|
);
|
||||||
|
const [currentMove, setCurrentMove] = useState(0);
|
||||||
|
const xIsNext = currentMove % 2 == 0;
|
||||||
|
const [winner, setWinner] = useState(null);
|
||||||
|
const [history, setHistory] = useState([Array(mapSize).fill(null)
|
||||||
|
.map(() => Array(mapSize).fill(null))])
|
||||||
|
|
||||||
|
function updateMap(nextMap) {
|
||||||
|
setHistory([...history, nextMap])
|
||||||
|
setMap(nextMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(row, column) {
|
||||||
|
if (map[row][column] != null || winner != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const nextMap = map.map(row => [...row]);
|
||||||
|
if (xIsNext) {
|
||||||
|
nextMap[row][column] = 'X'
|
||||||
|
} else {
|
||||||
|
nextMap[row][column] = 'O'
|
||||||
|
}
|
||||||
|
updateMap(nextMap)
|
||||||
|
setCurrentMove(currentMove + 1)
|
||||||
|
if (calculateWinner(row, column,nextMap)) {
|
||||||
|
setWinner(nextMap[row][column])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateWinner(row, column,map) {
|
||||||
|
const who = map[row][column]; // 当前玩家标记
|
||||||
|
const winNeed = mapSize > ticTacToeMaxMapSize ? 5 : mapSize; // 棋盘大小(>5五子棋)
|
||||||
|
|
||||||
|
// 横向检查
|
||||||
|
let horizontal = 1;
|
||||||
|
for (let hLeft = column - 1; hLeft >= 0; hLeft--) {
|
||||||
|
if (map[row][hLeft] !== who) break;
|
||||||
|
horizontal++;
|
||||||
|
}
|
||||||
|
for (let hRight = column + 1; hRight < mapSize; hRight++) {
|
||||||
|
if (map[row][hRight] !== who) break;
|
||||||
|
horizontal++;
|
||||||
|
}
|
||||||
|
if (horizontal >= winNeed) return true;
|
||||||
|
|
||||||
|
// 纵向检查
|
||||||
|
let vertical = 1;
|
||||||
|
for (let vUp = row - 1; vUp >= 0; vUp--) {
|
||||||
|
if (map[vUp][column] !== who) break;
|
||||||
|
vertical++;
|
||||||
|
}
|
||||||
|
for (let vDown = row + 1; vDown < mapSize; vDown++) {
|
||||||
|
if (map[vDown][column] !== who) break;
|
||||||
|
vertical++;
|
||||||
|
}
|
||||||
|
if (vertical >= winNeed) return true;
|
||||||
|
|
||||||
|
// 主对角线 (左上到右下)
|
||||||
|
let mainDiagonal = 1;
|
||||||
|
for (let dUp = 1; row - dUp >= 0 && column - dUp >= 0; dUp++) {
|
||||||
|
if (map[row - dUp][column - dUp] !== who) break;
|
||||||
|
mainDiagonal++;
|
||||||
|
}
|
||||||
|
for (let dDown = 1; row + dDown < mapSize && column + dDown < mapSize; dDown++) {
|
||||||
|
if (map[row + dDown][column + dDown] !== who) break;
|
||||||
|
mainDiagonal++;
|
||||||
|
}
|
||||||
|
if (mainDiagonal >= winNeed) return true;
|
||||||
|
|
||||||
|
// 副对角线 (右上到左下)
|
||||||
|
let antiDiagonal = 1;
|
||||||
|
for (let dUp = 1; row - dUp >= 0 && column + dUp < mapSize; dUp++) {
|
||||||
|
if (map[row - dUp][column + dUp] !== who) break;
|
||||||
|
antiDiagonal++;
|
||||||
|
}
|
||||||
|
for (let dDown = 1; row + dDown < mapSize && column - dDown >= 0; dDown++) {
|
||||||
|
if (map[row + dDown][column - dDown] !== who) break;
|
||||||
|
antiDiagonal++;
|
||||||
|
}
|
||||||
|
if (antiDiagonal >= winNeed) return true;
|
||||||
|
|
||||||
|
// 如果没有满足条件,返回 false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapEle = map.map((ele, index) => (
|
||||||
|
<div key={`row-${index}`} className="board-row"> {/* Unique key for each row */}
|
||||||
|
<SquareRow rowId={index} rows={ele} clickHandler={handleClick} />
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
function handleLeftClick() {
|
||||||
|
if (currentMove > 0) {
|
||||||
|
setMap(history[currentMove - 1]);
|
||||||
|
setCurrentMove(currentMove - 1);
|
||||||
|
setHistory(history.slice(0, -1));
|
||||||
|
setWinner(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl bg-sky-50 shadow-xl table items-center space-y-2 container mx-auto content-center">
|
||||||
|
<p className="font-sans text-2xl text-center ">{mapSize > ticTacToeMaxMapSize ? "五子棋" : "井字棋"}模式</p>
|
||||||
|
<p className="text-xl text-center ">现在是 {xIsNext ? "X" : "O"} 移动</p>
|
||||||
|
<div className="flex flex-row justify-center items-center">
|
||||||
|
<div className="board" >
|
||||||
|
{mapEle}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row justify-center items-center">
|
||||||
|
<button className="bg-cyan-50 shadow-md rounded-md text-lg" onClick={handleLeftClick}>
|
||||||
|
←回到上一个
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xl text-center">{winner != null ? winner + "赢了" : ""} </p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Game(){
|
||||||
|
const location = useLocation();
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const useSize = params.get('mapSize');
|
||||||
|
let realSize = parseInt(useSize)
|
||||||
|
if (isNaN(realSize) || realSize < 3 || realSize > 20){
|
||||||
|
realSize = 3
|
||||||
|
}
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<Map mapSize={realSize}/>
|
||||||
|
|
||||||
|
<div className="rounded-xl bg-green-50 shadow-md flex space-x-2 container mx-auto">
|
||||||
|
<form >
|
||||||
|
<label className="bg-green-50">
|
||||||
|
地图大小:
|
||||||
|
<input name="mapSize" defaultValue={realSize} type="number" />
|
||||||
|
</label>
|
||||||
|
<button type="submit" className="bg-cyan-200 rounded-md text-sm">确认</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
10
src/index.js
10
src/index.js
|
@ -2,14 +2,18 @@ import React, { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
|
|
||||||
import App from "./App";
|
import TicTacToe from "./TicTacToe";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import NameList from "./App";
|
||||||
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
|
|
||||||
const root = createRoot(document.getElementById("root"));
|
const root = createRoot(document.getElementById("root"));
|
||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<Routes>
|
||||||
|
<Route path="/" element={<NameList />} />
|
||||||
|
<Route path="/tictactoe" element={<TicTacToe />} />
|
||||||
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
Loading…
Reference in a new issue