fix: resolve all typechecking errors across codebase
This commit is contained in:
parent
73beedee5f
commit
3f3123c5af
7 changed files with 45 additions and 61 deletions
58
AGENTS.md
58
AGENTS.md
|
|
@ -1,46 +1,26 @@
|
|||
# Tschausepp - Jass Card Tracker
|
||||
|
||||
## Project Overview
|
||||
Mobile-first React web app for tracking Schaffhauser (Jass) card game rounds using camera and feature matching to detect and assign cards to players.
|
||||
Mobile-first React app for tracking Schaffhauser (Jass) card game rounds via camera.
|
||||
|
||||
## Tech Stack
|
||||
- React 18 (lightweight runtime)
|
||||
- TensorFlow.js (lite mode, CDN-hosted, ~5MB)
|
||||
- HTML5 Camera API (Mobile-friendly)
|
||||
- localStorage (JSON format, max 100 games)
|
||||
- React 18, TypeScript, Vite
|
||||
- TensorFlow.js (lite mode, CDN-hosted)
|
||||
- HTML5 Camera API (environment-facing preference)
|
||||
- localStorage (JSON, max 100 games)
|
||||
|
||||
## Key Implementation Details
|
||||
## Developer Commands
|
||||
- `npm run typecheck`: Run TypeScript check
|
||||
- `npm run build`: Build production version
|
||||
- `npm run preview`: Preview production build
|
||||
- **Dev Server**: Always running on `localhost:5173`. Do not start manually.
|
||||
|
||||
### Architecture
|
||||
- Component-based structure with 5 main screens:
|
||||
1. Setup Screen - Player management and game configuration
|
||||
2. Camera Screen - Live preview with card detection overlay
|
||||
3. Detection Component - Card boundary detection using image processing
|
||||
4. Assignment Component - Radial sector layout with auto-assignment
|
||||
5. Results Screen - Score calculation and game results
|
||||
6. History Screen - Game storage and export functionality
|
||||
## Architecture & Logic
|
||||
- **Screens**: Setup → Camera (with Detection/Assignment) → Results → History.
|
||||
- **Detection**: Custom image processing + color analysis for suits (Schellen, Schilten, Eicheln, Rosen).
|
||||
- **Assignment**: Uses radial sectors to assign detected cards to the nearest player.
|
||||
- **Storage**: Max 100 games in localStorage; follows `games → rounds → cards` structure.
|
||||
|
||||
### Development Commands
|
||||
- `npm run build` - Build production version
|
||||
- `npm run preview` - Preview production build
|
||||
- `npm run typecheck` - Run TypeScript type checking
|
||||
|
||||
### Development Server
|
||||
- A development server is always guaranteed to be running on localhost:5173
|
||||
- **Do not start the development server manually under any circumstances**
|
||||
|
||||
### Camera & Detection
|
||||
- Uses HTML5 Camera API with environment-facing camera preference
|
||||
- Card detection implemented with custom image processing techniques
|
||||
- Suit identification (Schellen, Schilten, Eicheln, Rosen) via color analysis
|
||||
- Auto-assignment based on radial sector + nearest player calculation
|
||||
|
||||
### Data Storage
|
||||
- Games stored in localStorage with maximum of 100 games
|
||||
- Data structure follows: games → rounds → cards with player assignments
|
||||
- JSON format for export functionality
|
||||
|
||||
### Mobile-First Design
|
||||
- Responsive UI optimized for mobile devices
|
||||
- Touch-friendly controls and interfaces
|
||||
- Camera access optimized for mobile environment
|
||||
## Reference Docs
|
||||
- `ML_SETUP_GUIDE.md`: ML pipeline architecture, training, and TF.js deployment.
|
||||
- `DETECTION_IMPROVEMENT_PLAN.md`: Roadmap for improving detection stability and accuracy.
|
||||
- `swiss_jass_suits.md`: Domain reference for Jass suit colors and iconography.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useRef, useEffect } from 'react';
|
||||
import { GameState } from '../../types';
|
||||
import { GameState, Card, Player } from '../../types';
|
||||
import { useGameStateContext } from '../../context/GameStateContext';
|
||||
import Detection from '../Detection/Detection';
|
||||
import Assignment from '../Assignment/Assignment';
|
||||
|
|
@ -35,17 +35,17 @@ const CameraScreen: React.FC = () => {
|
|||
|
||||
startCamera();
|
||||
|
||||
return () => {
|
||||
if (gameState.cameraStream) {
|
||||
gameState.cameraStream.getTracks().forEach(track => track.stop());
|
||||
}
|
||||
};
|
||||
return () => {
|
||||
if (gameState.cameraStream) {
|
||||
gameState.cameraStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [showDebug, setShowDebug] = React.useState(false);
|
||||
const [liveCards, setLiveCards] = React.useState<any[]>([]);
|
||||
const [liveCards, setLiveCards] = React.useState<Card[]>([]);
|
||||
|
||||
const handleCardsDetected = (cards: any[]) => {
|
||||
const handleCardsDetected = (cards: Card[]) => {
|
||||
scanTable(cards);
|
||||
setShowDebug(true);
|
||||
};
|
||||
|
|
@ -55,7 +55,7 @@ const CameraScreen: React.FC = () => {
|
|||
(window as any).detectCards();
|
||||
} else {
|
||||
// Fallback if the global detection function is not yet available
|
||||
const detectedCards = [
|
||||
const detectedCards: Card[] = [
|
||||
{
|
||||
id: '1',
|
||||
suit: 'Schellen',
|
||||
|
|
@ -121,7 +121,7 @@ const CameraScreen: React.FC = () => {
|
|||
pointerEvents: 'none',
|
||||
zIndex: 10
|
||||
}}>
|
||||
{(showDebug ? liveCards : gameState.detectedCards).map((card) => (
|
||||
{(showDebug ? liveCards : gameState.detectedCards).map((card: Card) => (
|
||||
<div
|
||||
key={card.id}
|
||||
style={{
|
||||
|
|
@ -146,7 +146,7 @@ const CameraScreen: React.FC = () => {
|
|||
{/* Placeholder for radial sectors visualization */}
|
||||
{gameState.players.length > 0 && (
|
||||
<div className="radial-sectors">
|
||||
{gameState.players.map((player, index) => {
|
||||
{gameState.players.map((player: Player, index: number) => {
|
||||
const angle = (index * 2 * Math.PI) / gameState.players.length;
|
||||
const radius = 150;
|
||||
const x = 320 + radius * Math.cos(angle);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Game } from '../../types';
|
||||
import { Game, Player } from '../../types';
|
||||
import { useGameStateContext } from '../../context/GameStateContext';
|
||||
|
||||
const HistoryScreen: React.FC = () => {
|
||||
|
|
@ -12,7 +12,7 @@ const HistoryScreen: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div className="history-list">
|
||||
{gameState.gameHistory.map((game, index) => (
|
||||
{gameState.gameHistory.map((game: Game, index: number) => (
|
||||
<div
|
||||
key={game.id}
|
||||
className="history-item"
|
||||
|
|
@ -28,7 +28,7 @@ const HistoryScreen: React.FC = () => {
|
|||
<div className="history-scores">
|
||||
{Object.entries(game.finalScores).map(([playerId, score]) => (
|
||||
<span key={playerId} className="score-tag">
|
||||
{game.players.find(p => p.id === playerId)?.name}: {score}
|
||||
{game.players.find((p: Player) => p.id === playerId)?.name}: {score}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { GameState } from '../../types';
|
||||
import { GameState, Player, Card } from '../../types';
|
||||
import { useGameStateContext } from '../../context/GameStateContext';
|
||||
|
||||
const ResultsScreen: React.FC = () => {
|
||||
|
|
@ -13,15 +13,15 @@ const ResultsScreen: React.FC = () => {
|
|||
// Scoring logic based on card values
|
||||
const scores: Record<string, number> = {};
|
||||
|
||||
gameState.players.forEach(player => {
|
||||
gameState.players.forEach((player: Player) => {
|
||||
scores[player.id] = 0;
|
||||
});
|
||||
|
||||
// Assign cards to players with proper scoring
|
||||
gameState.detectedCards.forEach(card => {
|
||||
gameState.detectedCards.forEach((card: Card) => {
|
||||
// Use the assignedTo property if available, otherwise distribute randomly
|
||||
const player = card.assignedTo
|
||||
? gameState.players.find(p => p.id === card.assignedTo)
|
||||
? gameState.players.find((p: Player) => p.id === card.assignedTo)
|
||||
: gameState.players[Math.floor(Math.random() * gameState.players.length)];
|
||||
|
||||
if (player) {
|
||||
|
|
@ -63,7 +63,7 @@ const ResultsScreen: React.FC = () => {
|
|||
<h2>🔍 Round Results</h2>
|
||||
|
||||
<div className="results-container">
|
||||
{gameState.players.map(player => (
|
||||
{gameState.players.map((player: Player) => (
|
||||
<div key={player.id} className="player-result">
|
||||
<h3>{player.name}</h3>
|
||||
<div className="score">
|
||||
|
|
@ -78,7 +78,7 @@ const ResultsScreen: React.FC = () => {
|
|||
<div className="cards-summary">
|
||||
<h3>Detected Cards</h3>
|
||||
<div className="cards-grid">
|
||||
{gameState.detectedCards.map(card => (
|
||||
{gameState.detectedCards.map((card: Card) => (
|
||||
<div key={card.id} className="card-preview">
|
||||
<div className="card-suit">
|
||||
{card.suit === 'Schellen' && '🔔'}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const SetupScreen: React.FC = () => {
|
|||
<h2>Setup a New Game</h2>
|
||||
|
||||
<div className="player-slots">
|
||||
{gameState.players.map((player, index) => (
|
||||
{gameState.players.map((player: Player, index: number) => (
|
||||
<div key={player.id} className="player-slot">
|
||||
<h3>👤 {index + 1}. {player.name}</h3>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
// Simple integration test for TensorFlow.js
|
||||
import * as tf from '@tensorflow/tfjs';
|
||||
|
||||
// This file would be used in testing or for a simple model load test
|
||||
|
||||
// This file would be used in testing or for a simple model load test
|
||||
// Not needed in production but demonstrates how we would test the TensorFlow integration
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export interface Card {
|
|||
width: number;
|
||||
height: number;
|
||||
confidence: number;
|
||||
assignedTo?: string;
|
||||
}
|
||||
|
||||
export interface Round {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue