+ 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 { useLocation } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
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 (
|
||||
<button
|
||||
className={buttonClass}
|
||||
onClick={() => clickHandler(row, column)}
|
||||
<div className="min-h-screen bg-gray-100 flex justify-center items-center p-4">
|
||||
<div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md">
|
||||
<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}
|
||||
</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} />
|
||||
{name.toUpperCase()}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</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 (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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default NameList;
|
||||
|
|
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 "./styles.css";
|
||||
|
||||
import App from "./App";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import TicTacToe from "./TicTacToe";
|
||||
import NameList from "./App";
|
||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||
|
||||
const root = createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<Routes>
|
||||
<Route path="/" element={<NameList />} />
|
||||
<Route path="/tictactoe" element={<TicTacToe />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
);
|
Loading…
Reference in a new issue