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
113 lines
3.7 KiB
JavaScript
113 lines
3.7 KiB
JavaScript
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;
|