+ Select page

This commit is contained in:
Kagura 2024-11-17 15:12:06 +08:00
parent cd49788cd9
commit 9a8505b0d9
3 changed files with 222 additions and 190 deletions

View file

@ -1,194 +1,28 @@
import { useState } from "react";
import { useLocation } from 'react-router-dom';
import React from 'react';
import { Link } 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);
}
}
const NameList = () => {
const names = ['tictactoe'];
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 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"
>
{name.toUpperCase()}
</Link>
</li>
))}
</ul>
</div>
<div className="flex flex-row justify-center items-center">
<button className="bg-cyan-50 shadow-md rounded-md text-lg" onClick={handleLeftClick}>
&#8592;回到上一个
</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
View 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}>
&#8592;回到上一个
</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>
</>
)
}

View file

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