diff --git a/src/components/App.jsx b/src/components/App.jsx
index a15375a..9450644 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -69,22 +69,42 @@ const App = () => {
const handleCreateGame = useCallback(({ player1, player2, player3, gameType, raceTo }) => {
const newGame = {
id: Date.now(),
- player1,
- player2,
- player3,
- score1: 0,
- score2: 0,
- score3: 0,
gameType,
- raceTo,
+ raceTo: parseInt(raceTo, 10),
status: 'active',
createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString()
+ updatedAt: new Date().toISOString(),
+ history: [],
};
+
+ if (gameType === '14/1 endlos') {
+ const players = [{ name: player1, score: 0, consecutiveFouls: 0 }, { name: player2, score: 0, consecutiveFouls: 0 }];
+ if (player3) {
+ players.push({ name: player3, score: 0, consecutiveFouls: 0 });
+ }
+ newGame.players = players;
+ newGame.currentPlayer = null; // Set to null, will be chosen in GameDetail141
+ newGame.ballsOnTable = 15;
+ } else {
+ newGame.player1 = player1;
+ newGame.player2 = player2;
+ newGame.score1 = 0;
+ newGame.score2 = 0;
+ if (player3) {
+ newGame.player3 = player3;
+ newGame.score3 = 0;
+ }
+ }
+
setGames(g => [newGame, ...g]);
return newGame.id;
}, []);
+ // Game update for 14.1
+ const handleUpdateGame = useCallback((updatedGame) => {
+ setGames(games => games.map(game => (game.id === currentGameId ? updatedGame : game)));
+ }, [currentGameId]);
+
// Score update
const handleUpdateScore = useCallback((player, change) => {
setGames(games => games.map(game => {
@@ -168,7 +188,7 @@ const App = () => {
setNewGameStep('gameType');
};
const handleGameTypeNext = (type) => {
- setNewGameData(data => ({ ...data, gameType: type }));
+ setNewGameData(data => ({ ...data, gameType: type, raceTo: type === '14/1 endlos' ? '150' : '50' }));
setNewGameStep('raceTo');
};
const handleRaceToNext = (raceTo) => {
@@ -250,8 +270,9 @@ const App = () => {
g.id === currentGameId)}
- onFinishGame={handleFinishGame}
onUpdateScore={handleUpdateScore}
+ onUpdate={handleUpdateGame}
+ onFinishGame={handleFinishGame}
onBack={showGameList}
/>
diff --git a/src/components/GameDetail.jsx b/src/components/GameDetail.jsx
index 84ac5f6..086b376 100644
--- a/src/components/GameDetail.jsx
+++ b/src/components/GameDetail.jsx
@@ -1,5 +1,6 @@
import { h } from 'preact';
import styles from './GameDetail.module.css';
+import GameDetail141 from './GameDetail141.jsx';
/**
* Game detail view for a single game.
@@ -7,11 +8,17 @@ import styles from './GameDetail.module.css';
* @param {object} props.game
* @param {Function} props.onFinishGame
* @param {Function} props.onUpdateScore
+ * @param {Function} props.onUpdate
* @param {Function} props.onBack
* @returns {import('preact').VNode|null}
*/
-const GameDetail = ({ game, onFinishGame, onUpdateScore, onBack }) => {
+const GameDetail = ({ game, onFinishGame, onUpdateScore, onUpdate, onBack }) => {
if (!game) return null;
+
+ if (game.gameType === '14/1 endlos') {
+ return ;
+ }
+
const isCompleted = game.status === 'completed';
const playerNames = [game.player1, game.player2, game.player3].filter(Boolean);
const scores = [game.score1, game.score2, game.score3].filter((_, i) => playerNames[i]);
diff --git a/src/components/GameDetail.module.css b/src/components/GameDetail.module.css
index bb8bdf5..2f736e5 100644
--- a/src/components/GameDetail.module.css
+++ b/src/components/GameDetail.module.css
@@ -122,4 +122,54 @@
margin: 40px 0 0 0;
width: 100%;
justify-content: center;
+}
+.franky .player-name {
+ font-weight: bold;
+ color: #ff8c00; /* Example color */
+}
+.active-player {
+ border: 2px solid #4caf50;
+ box-shadow: 0 0 10px #4caf50;
+}
+.turn-indicator {
+ margin: 20px 0;
+ font-size: 1.2rem;
+ text-align: center;
+}
+.potted-balls-container {
+ margin-top: 2rem;
+ padding: 1rem;
+ background: #2a2a2a;
+ border-radius: 8px;
+}
+.potted-balls-header {
+ text-align: center;
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ font-weight: 600;
+}
+.potted-balls-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
+ gap: 10px;
+}
+.potted-ball-btn {
+ padding: 1rem;
+ font-size: 1.2rem;
+ font-weight: bold;
+ border-radius: 8px;
+ border: 1px solid #444;
+ background-color: #333;
+ color: #fff;
+ cursor: pointer;
+ transition: background-color 0.2s, transform 0.2s;
+}
+.potted-ball-btn:hover:not(:disabled) {
+ background-color: #45a049;
+ transform: translateY(-2px);
+}
+.potted-ball-btn:disabled {
+ background-color: #222;
+ color: #555;
+ cursor: not-allowed;
}
\ No newline at end of file
diff --git a/src/components/GameDetail141.jsx b/src/components/GameDetail141.jsx
new file mode 100644
index 0000000..1f690c5
--- /dev/null
+++ b/src/components/GameDetail141.jsx
@@ -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 }) => (
+
+
e.stopPropagation()}>
+
+
Welcher Spieler fängt an?
+ {/* A cancel button isn't strictly needed if a choice is mandatory */}
+
+
+ {players.map((player, index) => (
+
+ ))}
+
+
+
+);
+
+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 ;
+ }
+
+ 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 (
+
+
+ 14/1 endlos | Race to {game.raceTo}
+
+
+
+ {game.players.map((p, idx) => (
+
+ {p.name}
+ {p.score}
+
+ ))}
+
+
+
+ Aktueller Spieler: {currentPlayer.name} ({game.ballsOnTable} Bälle auf dem Tisch)
+
+
+
+
Bälle am Ende der Aufnahme:
+
+ {Array.from({ length: 16 }, (_, i) => i).map(num => (
+
+ ))}
+
+
+
+ {/* Placeholder for future buttons */}
+
+
Fouls & Re-Racks (nächste Phase)
+
+
+
+
+
+
+ );
+};
+
+export default GameDetail141;
\ No newline at end of file