From 56d5cdb78f873f5f653a006ab640d849c002fab4 Mon Sep 17 00:00:00 2001 From: iFwu Date: Mon, 7 Oct 2024 17:29:00 +0800 Subject: [PATCH] refactor App.tsx --- src/App.tsx | 203 +++++++---------------------- src/chessboard/templateMatching.ts | 27 ++-- src/components/DepthControl.tsx | 29 +++++ src/hooks/useChessEngine.ts | 42 ++++++ src/hooks/useDepth.ts | 12 ++ src/hooks/useOpenCV.ts | 31 +++++ 6 files changed, 172 insertions(+), 172 deletions(-) create mode 100644 src/components/DepthControl.tsx create mode 100644 src/hooks/useChessEngine.ts create mode 100644 src/hooks/useDepth.ts create mode 100644 src/hooks/useOpenCV.ts diff --git a/src/App.tsx b/src/App.tsx index bd8aa4a..8752a57 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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([]); - const [engine, setEngine] = useState(null); - const [bestMove, setBestMove] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); const [moveHistory, setMoveHistory] = useState([]); - - const [templates, setTemplates] = useState | null>( - null - ); const [chessboardRect, setChessboardRect] = useState<{ x: number; y: number; @@ -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((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) => { - // 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 - ) => { setOriginalImageSize({ width: img.width, height: img.height }); const { gridCells, chessboardRect } = detectAndExtractChessboard(img); @@ -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 ( @@ -259,31 +171,14 @@ export function App() { originalImageSize={originalImageSize} /> -
-

搜索深度控制

-
- - { - if (e.target instanceof HTMLInputElement) { - handleDepthChange(Number(e.target.value)); - } - }} - /> -
-
+ diff --git a/src/chessboard/templateMatching.ts b/src/chessboard/templateMatching.ts index ec6bcb2..17fc856 100644 --- a/src/chessboard/templateMatching.ts +++ b/src/chessboard/templateMatching.ts @@ -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> { +export async function preprocessAllTemplates(): Promise> { const templates: Partial> = {}; for (const [path, module] of Object.entries(templateImages)) { @@ -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(); } } @@ -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) { @@ -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) { @@ -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(); diff --git a/src/components/DepthControl.tsx b/src/components/DepthControl.tsx new file mode 100644 index 0000000..81f5037 --- /dev/null +++ b/src/components/DepthControl.tsx @@ -0,0 +1,29 @@ +import { FunctionComponent } from 'preact'; + +interface DepthControlProps { + depth: number; + onDepthChange: (newDepth: number) => void; +} + +export const DepthControl: FunctionComponent = ({ depth, onDepthChange }) => { + return ( +
+

搜索深度控制

+
+ + { + if (e.target instanceof HTMLInputElement) { + onDepthChange(Number(e.target.value)); + } + }} + /> +
+
+ ); +}; \ No newline at end of file diff --git a/src/hooks/useChessEngine.ts b/src/hooks/useChessEngine.ts new file mode 100644 index 0000000..d34c7ce --- /dev/null +++ b/src/hooks/useChessEngine.ts @@ -0,0 +1,42 @@ +import { useState, useEffect, useCallback } from 'preact/hooks'; +import { ChessEngine } from '../chessEngine'; + +export function useChessEngine() { + const [engine, setEngine] = useState(null); + const [bestMove, setBestMove] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const initializeEngine = async () => { + const newEngine = new ChessEngine(); + await newEngine.initEngine(); + setEngine(newEngine); + }; + + initializeEngine(); + }, []); + + const fetchBestMove = useCallback( + async (fen: string, depth: number) => { + 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] + ); + + return { engine, bestMove, loading, error, fetchBestMove, setBestMove }; +} \ No newline at end of file diff --git a/src/hooks/useDepth.ts b/src/hooks/useDepth.ts new file mode 100644 index 0000000..04895a6 --- /dev/null +++ b/src/hooks/useDepth.ts @@ -0,0 +1,12 @@ +import { useState, useEffect } from 'preact/hooks'; + +export function useDepth() { + const initialDepth = Number(localStorage.getItem('depth')) || 14; + const [depth, setDepth] = useState(initialDepth); + + useEffect(() => { + localStorage.setItem('depth', depth.toString()); + }, [depth]); + + return { depth, setDepth }; +} \ No newline at end of file diff --git a/src/hooks/useOpenCV.ts b/src/hooks/useOpenCV.ts new file mode 100644 index 0000000..dadff93 --- /dev/null +++ b/src/hooks/useOpenCV.ts @@ -0,0 +1,31 @@ +import { useState, useEffect } from 'preact/hooks'; +import cv from '@techstark/opencv-js'; +import { PieceName } from '../chessboard/types'; +import { preprocessAllTemplates } from '../chessboard/templateMatching'; + +export function useOpenCV() { + const [templates, setTemplates] = useState | null>(null); + + useEffect(() => { + const initialize = async () => { + await new Promise((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); + } + }; + + initialize(); + }, []); + + return { templates }; +} \ No newline at end of file