Skip to content

Commit

Permalink
refactor App.tsx
Browse files Browse the repository at this point in the history
  • Loading branch information
iFwu committed Oct 7, 2024
1 parent 87462bd commit 56d5cdb
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 172 deletions.
203 changes: 49 additions & 154 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,28 @@
// App.tsx
import cv from '@techstark/opencv-js';
import { useState, useEffect, useCallback } from 'preact/hooks';

import { useState, useEffect } from 'preact/hooks';
import './app.css';

import { PieceColor, PieceName, PieceType } from './chessboard/types';
import { PieceColor, PieceType } from './chessboard/types';
import { detectAndExtractChessboard } from './chessboard/chessboardDetection';
import {
detectPieceInCell,
detectPieceColor,
processPiece,
} from './chessboard/pieceDetection';
import { detectPieceInCell, detectPieceColor, processPiece } from './chessboard/pieceDetection';
import { createOverlayImage } from './chessboard/overlayCreation';
import {
preprocessAllTemplates,
templateMatchingForPiece,
} from './chessboard/templateMatching';
import { templateMatchingForPiece } from './chessboard/templateMatching';
import { ImageUploader } from './components/ImageUploader';
import { BoardResult } from './components/BoardResult';
import { FENDisplay } from './components/FENDisplay';
import { SolutionDisplay } from './components/SolutionDisplay';
import { generateFenFromPieces } from './chessboard/fenGeneration';
import { ChessEngine } from './chessEngine';
import { updateFEN } from './chessboard/moveHelper';
import { useOpenCV } from './hooks/useOpenCV';
import { useChessEngine } from './hooks/useChessEngine';
import { useDepth } from './hooks/useDepth';
import { DepthControl } from './components/DepthControl';

export function App() {
const [overlayImageSrc, setOverlayImageSrc] = useState('');
const [fenCode, setFenCode] = useState('');
const [fenHistory, setFenHistory] = useState<string[]>([]);
const [engine, setEngine] = useState<ChessEngine | null>(null);
const [bestMove, setBestMove] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [moveHistory, setMoveHistory] = useState<string[]>([]);

const [templates, setTemplates] = useState<Record<PieceName, cv.Mat> | null>(
null
);
const [chessboardRect, setChessboardRect] = useState<{
x: number;
y: number;
Expand All @@ -47,55 +33,48 @@ export function App() {
width: number;
height: number;
}>();
const initialDepth = Number(localStorage.getItem('depth')) || 14;
const [depth, setDepth] = useState(initialDepth);

// Initialize OpenCV and Chess Engine
const { templates } = useOpenCV();
const { bestMove, loading, error, fetchBestMove, setBestMove } = useChessEngine();
const { depth, setDepth } = useDepth();

useEffect(() => {
const initialize = async () => {
await new Promise<void>((resolve) => {
cv.onRuntimeInitialized = resolve;
});
console.log('OpenCV.js is ready');
try {
const loadedTemplates = await preprocessAllTemplates();
if (Object.keys(loadedTemplates).length === 0) {
console.error('No templates were successfully loaded');
return;
}
setTemplates(loadedTemplates);
} catch (error) {
console.error('Error loading templates:', error);
}
if (fenCode) {
fetchBestMove(fenCode, depth);
}
}, [fenCode, depth, fetchBestMove]);

// Initialize the chess engine
const newEngine = new ChessEngine();
await newEngine.initEngine();
setEngine(newEngine);
};
const handleCopyFEN = () => {
navigator.clipboard.writeText(fenCode);
};

const handleNextMove = () => {
if (!bestMove || bestMove === 'red_wins' || bestMove === 'black_wins') {
return;
}
const newFen = updateFEN(fenCode, bestMove);
setFenCode(newFen);
setFenHistory((prev) => [...prev, newFen]);
setMoveHistory((prev) => [...prev, bestMove]);
setBestMove('');
};

initialize();
}, []);
const handlePreviousMove = () => {
if (fenHistory.length > 1) {
const newFenHistory = fenHistory.slice(0, -1);
const newMoveHistory = moveHistory.slice(0, -1);
const previousFen = newFenHistory[newFenHistory.length - 1];

// Process image when both templates and engine are ready
// useEffect(() => {
// if (templates && engine) {
// loadAndProcessDebugImage(templates);
// }
// }, [templates, engine]);
setFenHistory(newFenHistory);
setMoveHistory(newMoveHistory);
setFenCode(previousFen);
setBestMove('');
}
};

// const loadAndProcessDebugImage = (templates: Record<PieceName, cv.Mat>) => {
// const img = new Image();
// img.onload = () => {
// processImage(img, templates);
// };
// img.src = '/assets/test.png';
// };
const processImage = (img: HTMLImageElement) => {
if (!templates) return;

const processImage = (
img: HTMLImageElement,
templates: Record<PieceName, cv.Mat>
) => {
setOriginalImageSize({ width: img.width, height: img.height });
const { gridCells, chessboardRect } = detectAndExtractChessboard(img);

Expand Down Expand Up @@ -158,78 +137,11 @@ export function App() {
setFenHistory([initialFenCode]);
};

// Fetch best move whenever fenCode or depth changes and engine is ready
useEffect(() => {
if (fenCode && engine) {
fetchBestMove(fenCode);
}
}, [fenCode, engine, depth]);

const handleCopyFEN = () => {
navigator.clipboard.writeText(fenCode);
};

const handleDepthChange = (newDepth: number) => {
setDepth(newDepth);
localStorage.setItem('depth', newDepth.toString());
};

const fetchBestMove = useCallback(
async (fen: string) => {
if (!engine) return;
setLoading(true);
setError(null);
try {
const move = await engine.getBestMove(fen, depth);
setBestMove(move);
if (move === 'red_wins' || move === 'black_wins') {
setLoading(false);
return;
}
} catch (err: any) {
setError(`Error: ${err.message}`);
} finally {
setLoading(false);
}
},
[engine, depth]
);

const handleNextMove = async () => {
if (!bestMove || bestMove === 'red_wins' || bestMove === 'black_wins') {
setError('No valid move available');
return;
}
const newFen = updateFEN(fenCode, bestMove);
setFenCode(newFen);
setFenHistory((prev) => [...prev, newFen]);
setMoveHistory((prev) => [...prev, bestMove]);
setBestMove('');
};

const handlePreviousMove = () => {
if (fenHistory.length > 1) {
const newFenHistory = fenHistory.slice(0, -1);
const newMoveHistory = moveHistory.slice(0, -1);
const previousFen = newFenHistory[newFenHistory.length - 1];

setFenHistory(newFenHistory);
setMoveHistory(newMoveHistory);
setFenCode(previousFen);
setBestMove('');
}
};

const handleImageUpload = (img: HTMLImageElement) => {
if (templates) {
setFenHistory([]);
setMoveHistory([]);
setBestMove('');
setError(null);
processImage(img, templates);
} else {
console.error('Templates not loaded yet');
}
setFenHistory([]);
setMoveHistory([]);
setBestMove('');
processImage(img);
};

return (
Expand Down Expand Up @@ -259,31 +171,14 @@ export function App() {
originalImageSize={originalImageSize}
/>
<FENDisplay fenCode={fenCode} onCopy={handleCopyFEN} />
<div className="depth-control-section">
<h2>搜索深度控制</h2>
<div className="depth-slider-container">
<label htmlFor="depth-slider">当前深度: {depth}</label>
<input
id="depth-slider"
type="range"
min="10"
max="30"
value={depth}
onChange={(e) => {
if (e.target instanceof HTMLInputElement) {
handleDepthChange(Number(e.target.value));
}
}}
/>
</div>
</div>
<DepthControl depth={depth} onDepthChange={setDepth} />
</div>
</div>
</main>
<footer>
<p>
© 2024 象棋棋盘识别与分析系统 |
Powered by <a href="https://github.com/official-pikafish/Pikafish"> Pikafish</a>&nbsp;|&nbsp;
Powered by <a href="https://github.com/official-pikafish/Pikafish">Pikafish</a>&nbsp;|&nbsp;
<a href="https://github.com/iFwu/xiangqi-analysis">GitHub 源码仓库</a>
</p>
</footer>
Expand Down
27 changes: 9 additions & 18 deletions src/chessboard/templateMatching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@ import { PieceType, PieceColor, PieceName } from './types';
// 使用 import.meta.glob 预加载所有模板图片
const templateImages = import.meta.glob('/assets/chess_templates/*.png', { eager: true });

// 预处理模板图像
function preprocessTemplateImage(templateImage: cv.Mat, cellSize: [number, number]): cv.Mat {
const resizedTemplate = new cv.Mat();
cv.resize(templateImage, resizedTemplate, new cv.Size(cellSize[0], cellSize[1]));
return resizedTemplate;
}

// 预处理所有模板
export async function preprocessAllTemplates(cellSize: [number, number] = [60, 60]): Promise<Record<PieceName, cv.Mat>> {
export async function preprocessAllTemplates(): Promise<Record<PieceName, cv.Mat>> {
const templates: Partial<Record<PieceName, cv.Mat>> = {};

for (const [path, module] of Object.entries(templateImages)) {
Expand All @@ -26,10 +19,8 @@ export async function preprocessAllTemplates(cellSize: [number, number] = [60, 6
const src = cv.imread(img);
const gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);
const preprocessed = preprocessTemplateImage(gray, cellSize);
templates[pieceName] = preprocessed;
templates[pieceName] = gray;
src.delete();
gray.delete();
}
}

Expand All @@ -45,7 +36,7 @@ export function templateMatchingForPiece(
let maxMatchValue = -1;
let matchedPiece: PieceType = 'none';

const cellSize = [ cellImage.cols, cellImage.rows ];
const cellSize = [cellImage.cols, cellImage.rows];

const grayCellImage = new cv.Mat();
if (cellImage.channels() === 4) {
Expand All @@ -56,20 +47,20 @@ export function templateMatchingForPiece(
cellImage.copyTo(grayCellImage);
}

const colorSpecificTemplates = Object.entries(templates).filter(([ pieceName, _ ]) => {
const colorSpecificTemplates = Object.entries(templates).filter(([pieceName, _]) => {
if (pieceColor === 'red') return pieceName.startsWith('red_');
if (pieceColor === 'black') return pieceName.startsWith('black_');
return true;
});

for (const [ pieceName, template ] of colorSpecificTemplates) {
for (const [pieceName, template] of colorSpecificTemplates) {
const resizedTemplate = new cv.Mat();
cv.resize(template, resizedTemplate, new cv.Size(cellSize[ 0 ], cellSize[ 1 ]));
cv.resize(template, resizedTemplate, new cv.Size(cellSize[0], cellSize[1]));

const result = new cv.Mat();
cv.matchTemplate(grayCellImage, resizedTemplate, result, cv.TM_CCOEFF_NORMED);

// @ts-ignore
// @ts-ignore 保留这个注释
const minMax = cv.minMaxLoc(result);

if (minMax.maxVal > maxMatchValue) {
Expand All @@ -83,8 +74,8 @@ export function templateMatchingForPiece(
'cannon': 'c',
'pawn': 'p'
};
const pieceTypeName = pieceName.split('_')[ 1 ];
matchedPiece = pieceTypeMap[ pieceTypeName ] || 'none';
const pieceTypeName = pieceName.split('_')[1];
matchedPiece = pieceTypeMap[pieceTypeName] || 'none';
}

resizedTemplate.delete();
Expand Down
29 changes: 29 additions & 0 deletions src/components/DepthControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FunctionComponent } from 'preact';

interface DepthControlProps {
depth: number;
onDepthChange: (newDepth: number) => void;
}

export const DepthControl: FunctionComponent<DepthControlProps> = ({ depth, onDepthChange }) => {
return (
<div className="depth-control-section">
<h2>搜索深度控制</h2>
<div className="depth-slider-container">
<label htmlFor="depth-slider">当前深度: {depth}</label>
<input
id="depth-slider"
type="range"
min="10"
max="30"
value={depth}
onChange={(e) => {
if (e.target instanceof HTMLInputElement) {
onDepthChange(Number(e.target.value));
}
}}
/>
</div>
</div>
);
};
Loading

0 comments on commit 56d5cdb

Please sign in to comment.