feat(game-14-1): Implement phase 1 foundation

Implements the foundational UI and logic for the 14.1 Endless game mode, as detailed in issue #18.

- Adds a new  component to handle the specific game view.
- Introduces a modal within the game view to select the starting player.
- Replaces text input with a button grid for selecting remaining balls.
- Updates  to correctly initialize the 14.1 game state.

Closes #18
This commit is contained in:
Frank Schwenk
2025-06-20 15:35:38 +02:00
parent 875e9c8795
commit a71c65852d
4 changed files with 202 additions and 11 deletions

View File

@@ -0,0 +1,113 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import styles from './GameDetail.module.css';
import modalStyles from './PlayerSelectModal.module.css';
const StartingPlayerModal = ({ players, onSelect, onCancel }) => (
<div className={modalStyles.modalOverlay}>
<div className={modalStyles.modalContent} onClick={e => e.stopPropagation()}>
<div className={modalStyles.modalHeader}>
<h3>Welcher Spieler fängt an?</h3>
{/* A cancel button isn't strictly needed if a choice is mandatory */}
</div>
<div className={modalStyles.playerList}>
{players.map((player, index) => (
<button key={player.name} className={modalStyles.playerItem} onClick={() => onSelect(index)}>
{player.name}
</button>
))}
</div>
</div>
</div>
);
const GameDetail141 = ({ game, onUpdate, onBack }) => {
const handleSelectStartingPlayer = (playerIndex) => {
onUpdate({
...game,
currentPlayer: playerIndex,
});
};
// If no player is selected yet, show the modal
if (game.currentPlayer === null || game.currentPlayer === undefined) {
return <StartingPlayerModal players={game.players} onSelect={handleSelectStartingPlayer} />;
}
const currentPlayer = game.players[game.currentPlayer];
const handleTurnEnd = (remainingBalls) => {
if (remainingBalls > game.ballsOnTable) {
console.error("Cannot leave more balls than are on the table.");
return;
}
const ballsPotted = game.ballsOnTable - remainingBalls;
const newScore = currentPlayer.score + ballsPotted;
const updatedPlayers = game.players.map((p, index) =>
index === game.currentPlayer ? { ...p, score: newScore } : p
);
const nextPlayer = (game.currentPlayer + 1) % game.players.length;
onUpdate({
...game,
players: updatedPlayers,
ballsOnTable: remainingBalls,
currentPlayer: nextPlayer,
history: [...game.history, { player: currentPlayer.name, ballsPotted, newScore, ballsOnTable: remainingBalls }],
});
};
return (
<div className={styles['game-detail']}>
<div className={styles['game-title']}>
14/1 endlos | Race to {game.raceTo}
</div>
<div className={styles['scores-container']}>
{game.players.map((p, idx) => (
<div
className={`${styles['player-score']} ${idx === game.currentPlayer ? styles['active-player'] : ''}`}
key={p.name}
>
<span className={styles['player-name']}>{p.name}</span>
<span className={styles['score']}>{p.score}</span>
</div>
))}
</div>
<div className={styles['turn-indicator']}>
Aktueller Spieler: <strong>{currentPlayer.name}</strong> ({game.ballsOnTable} Bälle auf dem Tisch)
</div>
<div className={styles['potted-balls-container']}>
<p className={styles['potted-balls-header']}>Bälle am Ende der Aufnahme:</p>
<div className={styles['potted-balls-grid']}>
{Array.from({ length: 16 }, (_, i) => i).map(num => (
<button
key={num}
onClick={() => handleTurnEnd(num)}
disabled={num > game.ballsOnTable}
className={styles['potted-ball-btn']}
>
{num}
</button>
))}
</div>
</div>
{/* Placeholder for future buttons */}
<div className={styles['foul-controls']}>
<p>Fouls & Re-Racks (nächste Phase)</p>
</div>
<div className={styles['game-detail-controls']}>
<button className="btn" onClick={onBack}>Zurück zur Liste</button>
</div>
</div>
);
};
export default GameDetail141;