fix: resolve all typechecking errors across codebase

This commit is contained in:
10x Developer 2026-05-11 20:15:03 +02:00
parent 73beedee5f
commit 3f3123c5af
7 changed files with 45 additions and 61 deletions

View file

@ -1,46 +1,26 @@
# Tschausepp - Jass Card Tracker # Tschausepp - Jass Card Tracker
## Project Overview Mobile-first React app for tracking Schaffhauser (Jass) card game rounds via camera.
Mobile-first React web app for tracking Schaffhauser (Jass) card game rounds using camera and feature matching to detect and assign cards to players.
## Tech Stack ## Tech Stack
- React 18 (lightweight runtime) - React 18, TypeScript, Vite
- TensorFlow.js (lite mode, CDN-hosted, ~5MB) - TensorFlow.js (lite mode, CDN-hosted)
- HTML5 Camera API (Mobile-friendly) - HTML5 Camera API (environment-facing preference)
- localStorage (JSON format, max 100 games) - 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 ## Architecture & Logic
- Component-based structure with 5 main screens: - **Screens**: Setup → Camera (with Detection/Assignment) → Results → History.
1. Setup Screen - Player management and game configuration - **Detection**: Custom image processing + color analysis for suits (Schellen, Schilten, Eicheln, Rosen).
2. Camera Screen - Live preview with card detection overlay - **Assignment**: Uses radial sectors to assign detected cards to the nearest player.
3. Detection Component - Card boundary detection using image processing - **Storage**: Max 100 games in localStorage; follows `games → rounds → cards` structure.
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
### Development Commands ## Reference Docs
- `npm run build` - Build production version - `ML_SETUP_GUIDE.md`: ML pipeline architecture, training, and TF.js deployment.
- `npm run preview` - Preview production build - `DETECTION_IMPROVEMENT_PLAN.md`: Roadmap for improving detection stability and accuracy.
- `npm run typecheck` - Run TypeScript type checking - `swiss_jass_suits.md`: Domain reference for Jass suit colors and iconography.
### 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

View file

@ -1,5 +1,5 @@
import React, { useRef, useEffect } from 'react'; import React, { useRef, useEffect } from 'react';
import { GameState } from '../../types'; import { GameState, Card, Player } from '../../types';
import { useGameStateContext } from '../../context/GameStateContext'; import { useGameStateContext } from '../../context/GameStateContext';
import Detection from '../Detection/Detection'; import Detection from '../Detection/Detection';
import Assignment from '../Assignment/Assignment'; import Assignment from '../Assignment/Assignment';
@ -37,15 +37,15 @@ const CameraScreen: React.FC = () => {
return () => { return () => {
if (gameState.cameraStream) { if (gameState.cameraStream) {
gameState.cameraStream.getTracks().forEach(track => track.stop()); gameState.cameraStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
} }
}; };
}, []); }, []);
const [showDebug, setShowDebug] = React.useState(false); 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); scanTable(cards);
setShowDebug(true); setShowDebug(true);
}; };
@ -55,7 +55,7 @@ const CameraScreen: React.FC = () => {
(window as any).detectCards(); (window as any).detectCards();
} else { } else {
// Fallback if the global detection function is not yet available // Fallback if the global detection function is not yet available
const detectedCards = [ const detectedCards: Card[] = [
{ {
id: '1', id: '1',
suit: 'Schellen', suit: 'Schellen',
@ -121,7 +121,7 @@ const CameraScreen: React.FC = () => {
pointerEvents: 'none', pointerEvents: 'none',
zIndex: 10 zIndex: 10
}}> }}>
{(showDebug ? liveCards : gameState.detectedCards).map((card) => ( {(showDebug ? liveCards : gameState.detectedCards).map((card: Card) => (
<div <div
key={card.id} key={card.id}
style={{ style={{
@ -146,7 +146,7 @@ const CameraScreen: React.FC = () => {
{/* Placeholder for radial sectors visualization */} {/* Placeholder for radial sectors visualization */}
{gameState.players.length > 0 && ( {gameState.players.length > 0 && (
<div className="radial-sectors"> <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 angle = (index * 2 * Math.PI) / gameState.players.length;
const radius = 150; const radius = 150;
const x = 320 + radius * Math.cos(angle); const x = 320 + radius * Math.cos(angle);

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Game } from '../../types'; import { Game, Player } from '../../types';
import { useGameStateContext } from '../../context/GameStateContext'; import { useGameStateContext } from '../../context/GameStateContext';
const HistoryScreen: React.FC = () => { const HistoryScreen: React.FC = () => {
@ -12,7 +12,7 @@ const HistoryScreen: React.FC = () => {
return ( return (
<div className="history-list"> <div className="history-list">
{gameState.gameHistory.map((game, index) => ( {gameState.gameHistory.map((game: Game, index: number) => (
<div <div
key={game.id} key={game.id}
className="history-item" className="history-item"
@ -28,7 +28,7 @@ const HistoryScreen: React.FC = () => {
<div className="history-scores"> <div className="history-scores">
{Object.entries(game.finalScores).map(([playerId, score]) => ( {Object.entries(game.finalScores).map(([playerId, score]) => (
<span key={playerId} className="score-tag"> <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> </span>
))} ))}
</div> </div>

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { GameState } from '../../types'; import { GameState, Player, Card } from '../../types';
import { useGameStateContext } from '../../context/GameStateContext'; import { useGameStateContext } from '../../context/GameStateContext';
const ResultsScreen: React.FC = () => { const ResultsScreen: React.FC = () => {
@ -13,15 +13,15 @@ const ResultsScreen: React.FC = () => {
// Scoring logic based on card values // Scoring logic based on card values
const scores: Record<string, number> = {}; const scores: Record<string, number> = {};
gameState.players.forEach(player => { gameState.players.forEach((player: Player) => {
scores[player.id] = 0; scores[player.id] = 0;
}); });
// Assign cards to players with proper scoring // 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 // Use the assignedTo property if available, otherwise distribute randomly
const player = card.assignedTo 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)]; : gameState.players[Math.floor(Math.random() * gameState.players.length)];
if (player) { if (player) {
@ -63,7 +63,7 @@ const ResultsScreen: React.FC = () => {
<h2>🔍 Round Results</h2> <h2>🔍 Round Results</h2>
<div className="results-container"> <div className="results-container">
{gameState.players.map(player => ( {gameState.players.map((player: Player) => (
<div key={player.id} className="player-result"> <div key={player.id} className="player-result">
<h3>{player.name}</h3> <h3>{player.name}</h3>
<div className="score"> <div className="score">
@ -78,7 +78,7 @@ const ResultsScreen: React.FC = () => {
<div className="cards-summary"> <div className="cards-summary">
<h3>Detected Cards</h3> <h3>Detected Cards</h3>
<div className="cards-grid"> <div className="cards-grid">
{gameState.detectedCards.map(card => ( {gameState.detectedCards.map((card: Card) => (
<div key={card.id} className="card-preview"> <div key={card.id} className="card-preview">
<div className="card-suit"> <div className="card-suit">
{card.suit === 'Schellen' && '🔔'} {card.suit === 'Schellen' && '🔔'}

View file

@ -54,7 +54,7 @@ const SetupScreen: React.FC = () => {
<h2>Setup a New Game</h2> <h2>Setup a New Game</h2>
<div className="player-slots"> <div className="player-slots">
{gameState.players.map((player, index) => ( {gameState.players.map((player: Player, index: number) => (
<div key={player.id} className="player-slot"> <div key={player.id} className="player-slot">
<h3>👤 {index + 1}. {player.name}</h3> <h3>👤 {index + 1}. {player.name}</h3>
</div> </div>

View file

@ -1,4 +1,7 @@
// Simple integration test for TensorFlow.js // 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 // 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 // Not needed in production but demonstrates how we would test the TensorFlow integration

View file

@ -12,6 +12,7 @@ export interface Card {
width: number; width: number;
height: number; height: number;
confidence: number; confidence: number;
assignedTo?: string;
} }
export interface Round { export interface Round {