175 lines
7.4 KiB
JavaScript
175 lines
7.4 KiB
JavaScript
import React, { useState } from 'react';
|
||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||
import { generateNQueensCase, solveNQueensCase, runNQueensBenchmark } from '../api';
|
||
|
||
const NQueens = () => {
|
||
const [n, setN] = useState(8);
|
||
const [currentSolution, setCurrentSolution] = useState(null);
|
||
const [solutionCount, setSolutionCount] = useState(null);
|
||
const [solveTime, setSolveTime] = useState(null);
|
||
const [benchmarkResults, setBenchmarkResults] = useState(null);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const handleSolve = async () => {
|
||
setLoading(true);
|
||
try {
|
||
// Generate is trivial (just N), so we skip explicit generate step for UI simplicity
|
||
// and just call solve directly
|
||
const results = await solveNQueensCase(n, ["backtracking"]);
|
||
if (results && results.length > 0) {
|
||
const res = results[0];
|
||
setCurrentSolution(res.first_solution);
|
||
setSolutionCount(res.solution_count);
|
||
setSolveTime(res.time_seconds);
|
||
}
|
||
} catch (error) {
|
||
console.error("Error solving N-Queens:", error);
|
||
alert("求解失败");
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const handleBenchmark = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const sizes = [4, 8, 10, 12, 13];
|
||
const results = await runNQueensBenchmark(sizes, ["backtracking"]);
|
||
|
||
const chartData = results.map(item => {
|
||
const point = { size: item.size };
|
||
item.algorithms.forEach(algo => {
|
||
if (algo.time_seconds !== undefined && algo.time_seconds !== null) {
|
||
point[algo.algorithm] = algo.time_seconds;
|
||
}
|
||
});
|
||
return point;
|
||
});
|
||
|
||
setBenchmarkResults(chartData);
|
||
} catch (error) {
|
||
console.error("Error running benchmark:", error);
|
||
alert("性能测试失败");
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
// Render the chess board
|
||
const renderBoard = () => {
|
||
if (!currentSolution) return null;
|
||
|
||
const boardSize = 400;
|
||
const cellSize = boardSize / n;
|
||
|
||
return (
|
||
<div
|
||
className="relative border-2 border-gray-800"
|
||
style={{ width: boardSize, height: boardSize }}
|
||
>
|
||
{Array.from({ length: n }).map((_, row) => (
|
||
Array.from({ length: n }).map((_, col) => {
|
||
const isBlack = (row + col) % 2 === 1;
|
||
const hasQueen = currentSolution[row] === col;
|
||
return (
|
||
<div
|
||
key={`${row}-${col}`}
|
||
className={`absolute flex items-center justify-center ${isBlack ? 'bg-gray-600' : 'bg-white'}`}
|
||
style={{
|
||
width: cellSize,
|
||
height: cellSize,
|
||
top: row * cellSize,
|
||
left: col * cellSize
|
||
}}
|
||
>
|
||
{hasQueen && <span className="text-red-500 font-bold" style={{ fontSize: cellSize * 0.7 }}>♛</span>}
|
||
</div>
|
||
);
|
||
})
|
||
))}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div className="p-6 space-y-8">
|
||
<h1 className="text-3xl font-bold text-gray-800">N皇后问题 (N-Queens)</h1>
|
||
<p className="text-gray-600">目标:在 N×N 的棋盘上放置 N 个皇后,使得它们互不攻击。</p>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* Controls */}
|
||
<div className="bg-white p-6 rounded-lg shadow space-y-6">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700">棋盘大小 (N)</label>
|
||
<input
|
||
type="number"
|
||
min="4"
|
||
max="14"
|
||
value={n}
|
||
onChange={(e) => setN(parseInt(e.target.value))}
|
||
className="mt-1 block w-full border rounded-md p-2"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">建议 N ≤ 14,否则计算时间过长</p>
|
||
</div>
|
||
|
||
<button
|
||
onClick={handleSolve}
|
||
disabled={loading}
|
||
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:bg-gray-400"
|
||
>
|
||
{loading ? '计算中...' : '求解 (Backtracking)'}
|
||
</button>
|
||
|
||
{solutionCount !== null && (
|
||
<div className="mt-4 p-4 bg-green-50 rounded-md">
|
||
<p className="font-medium text-green-800">找到解的数量: {solutionCount}</p>
|
||
<p className="text-sm text-green-600">耗时: {solveTime?.toFixed(6)} 秒</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Visualization */}
|
||
<div className="bg-white p-6 rounded-lg shadow flex justify-center items-center min-h-[450px]">
|
||
{currentSolution ? renderBoard() : (
|
||
<div className="text-gray-400 text-center">
|
||
<p>点击求解以查看结果</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Benchmark */}
|
||
<div className="bg-white p-6 rounded-lg shadow">
|
||
<div className="flex justify-between items-center mb-6">
|
||
<h2 className="text-xl font-semibold">性能测试 (N=4 to 13)</h2>
|
||
<button
|
||
onClick={handleBenchmark}
|
||
disabled={loading}
|
||
className="bg-purple-600 text-white py-2 px-4 rounded-md hover:bg-purple-700 disabled:bg-gray-400"
|
||
>
|
||
运行性能测试
|
||
</button>
|
||
</div>
|
||
|
||
{benchmarkResults && (
|
||
<div className="h-96 w-full">
|
||
<ResponsiveContainer width="100%" height="100%">
|
||
<LineChart
|
||
data={benchmarkResults}
|
||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||
>
|
||
<CartesianGrid strokeDasharray="3 3" />
|
||
<XAxis dataKey="size" label={{ value: 'N', position: 'insideBottomRight', offset: -10 }} />
|
||
<YAxis label={{ value: '耗时 (秒)', angle: -90, position: 'insideLeft' }} />
|
||
<Tooltip />
|
||
<Legend />
|
||
<Line type="monotone" dataKey="backtracking" stroke="#ff0000" name="回溯算法" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default NQueens;
|