feat(14-1): manual turn change and input accumulation
- Turns are now only changed by a big, prominent button - Players can make multiple inputs (balls left, fouls, re-rack) before changing turns - Players can change turn with no input (0 balls potted, 0 foul) - All actions are accumulated in local state and finalized on turn change - No automatic turn changes remain Refs #26
This commit is contained in:
@@ -334,4 +334,41 @@
|
|||||||
color: #ff9800;
|
color: #ff9800;
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
border-bottom: 2px solid #ff9800;
|
border-bottom: 2px solid #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turn-change-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 2.5rem 0 1.5rem 0;
|
||||||
|
}
|
||||||
|
.turn-change-btn {
|
||||||
|
background: #ff9800;
|
||||||
|
color: #222;
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 2rem 4rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 18px;
|
||||||
|
box-shadow: 0 4px 32px rgba(255,152,0,0.18), 0 2px 8px rgba(0,0,0,0.12);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s, box-shadow 0.2s;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.turn-change-btn:hover, .turn-change-btn:focus {
|
||||||
|
background: #ffa726;
|
||||||
|
color: #111;
|
||||||
|
box-shadow: 0 6px 40px rgba(255,152,0,0.28), 0 2px 8px rgba(0,0,0,0.18);
|
||||||
|
}
|
||||||
|
.pending-foul-info {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #ffc107;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.selected {
|
||||||
|
outline: 3px solid #ff9800 !important;
|
||||||
|
background: #fff3e0 !important;
|
||||||
|
color: #222 !important;
|
||||||
}
|
}
|
||||||
@@ -100,6 +100,10 @@ const GameLogTable = ({ log, players }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
||||||
|
const [pendingBallsLeft, setPendingBallsLeft] = useState(null); // null means not set
|
||||||
|
const [pendingFouls, setPendingFouls] = useState(0);
|
||||||
|
const [pendingReRack, setPendingReRack] = useState(0);
|
||||||
|
|
||||||
const handleSelectStartingPlayer = (playerIndex) => {
|
const handleSelectStartingPlayer = (playerIndex) => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
...game,
|
...game,
|
||||||
@@ -114,100 +118,68 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
|||||||
|
|
||||||
const currentPlayer = game.players[game.currentPlayer];
|
const currentPlayer = game.players[game.currentPlayer];
|
||||||
|
|
||||||
const handleTurnEnd = (remainingBalls, foulPoints = 0) => {
|
// Handlers now only update local state
|
||||||
if (remainingBalls > game.ballsOnTable) {
|
const handleBallsLeft = (num) => {
|
||||||
console.error("Cannot leave more balls than are on the table.");
|
setPendingBallsLeft(num);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ballsPotted = game.ballsOnTable - remainingBalls;
|
|
||||||
const newScore = currentPlayer.score + ballsPotted - foulPoints;
|
|
||||||
|
|
||||||
const updatedPlayers = game.players.map((p, index) =>
|
|
||||||
index === game.currentPlayer ? { ...p, score: newScore, consecutiveFouls: 0 } : p
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextPlayer = (game.currentPlayer + 1) % game.players.length;
|
|
||||||
|
|
||||||
onUpdate({
|
|
||||||
...game,
|
|
||||||
players: updatedPlayers,
|
|
||||||
ballsOnTable: remainingBalls,
|
|
||||||
currentPlayer: nextPlayer,
|
|
||||||
log: [...(game.log || []), { type: 'turn', player: currentPlayer.name, ballsPotted, foulPoints, newScore, ballsOnTable: remainingBalls }],
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFoul = (foulType) => {
|
const handleFoul = (foulType) => {
|
||||||
let foulPoints = 0;
|
let foulPoints = 0;
|
||||||
let penalty = 0;
|
let penalty = 0;
|
||||||
const newConsecutiveFouls = (currentPlayer.consecutiveFouls || 0) + 1;
|
let newConsecutiveFouls = (currentPlayer.consecutiveFouls || 0) + 1;
|
||||||
|
if (foulType === 'standard') foulPoints = 1;
|
||||||
if (foulType === 'standard') {
|
else if (foulType === 'break') foulPoints = 2;
|
||||||
foulPoints = 1;
|
if (newConsecutiveFouls === 3) penalty = 15;
|
||||||
} else if (foulType === 'break') {
|
setPendingFouls(pendingFouls + foulPoints + penalty);
|
||||||
foulPoints = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newConsecutiveFouls === 3) {
|
|
||||||
penalty = 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalDeduction = foulPoints + penalty;
|
|
||||||
const newScore = currentPlayer.score - totalDeduction;
|
|
||||||
|
|
||||||
const updatedPlayers = game.players.map((p, index) => {
|
|
||||||
if (index === game.currentPlayer) {
|
|
||||||
return {
|
|
||||||
...p,
|
|
||||||
score: newScore,
|
|
||||||
consecutiveFouls: newConsecutiveFouls === 3 ? 0 : newConsecutiveFouls,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextPlayer = (game.currentPlayer + 1) % game.players.length;
|
|
||||||
|
|
||||||
onUpdate({
|
|
||||||
...game,
|
|
||||||
players: updatedPlayers,
|
|
||||||
currentPlayer: nextPlayer,
|
|
||||||
log: [
|
|
||||||
...(game.log || []),
|
|
||||||
{
|
|
||||||
type: 'foul',
|
|
||||||
player: currentPlayer.name,
|
|
||||||
foul: foulType,
|
|
||||||
foulPoints,
|
|
||||||
penalty,
|
|
||||||
totalDeduction,
|
|
||||||
newScore,
|
|
||||||
consecutiveFouls: newConsecutiveFouls
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReRack = (ballsToAdd) => {
|
const handleReRack = (ballsToAdd) => {
|
||||||
const scoreIncrement = game.ballsOnTable + ballsToAdd - 15;
|
setPendingReRack(pendingReRack + ballsToAdd);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Turn change button handler
|
||||||
|
const handleTurnChange = () => {
|
||||||
|
// Calculate balls potted
|
||||||
|
const ballsOnTableBefore = game.ballsOnTable;
|
||||||
|
const ballsLeft = pendingBallsLeft !== null ? pendingBallsLeft : ballsOnTableBefore;
|
||||||
|
const ballsPotted = ballsOnTableBefore - ballsLeft;
|
||||||
|
// Calculate score
|
||||||
|
let newScore = currentPlayer.score + ballsPotted - pendingFouls;
|
||||||
|
// Handle re-rack scoring
|
||||||
|
if (pendingReRack > 0) {
|
||||||
|
const scoreIncrement = ballsLeft + pendingReRack - 15;
|
||||||
|
newScore += scoreIncrement;
|
||||||
|
}
|
||||||
|
// Update player state
|
||||||
const updatedPlayers = game.players.map((p, idx) =>
|
const updatedPlayers = game.players.map((p, idx) =>
|
||||||
idx === game.currentPlayer ? { ...p, score: p.score + scoreIncrement } : p
|
idx === game.currentPlayer ? { ...p, score: newScore, consecutiveFouls: pendingFouls > 0 ? 0 : (p.consecutiveFouls || 0) } : p
|
||||||
);
|
);
|
||||||
|
// Update balls on table
|
||||||
|
let newBallsOnTable = ballsLeft;
|
||||||
|
if (pendingReRack > 0) newBallsOnTable = 15;
|
||||||
|
// Log turn
|
||||||
|
const newLog = [...(game.log || []), {
|
||||||
|
type: 'turn',
|
||||||
|
player: currentPlayer.name,
|
||||||
|
ballsPotted,
|
||||||
|
foulPoints: pendingFouls,
|
||||||
|
newScore,
|
||||||
|
ballsOnTable: newBallsOnTable,
|
||||||
|
reRack: pendingReRack > 0 ? pendingReRack : undefined
|
||||||
|
}];
|
||||||
|
// Advance player
|
||||||
|
const nextPlayer = (game.currentPlayer + 1) % game.players.length;
|
||||||
onUpdate({
|
onUpdate({
|
||||||
...game,
|
...game,
|
||||||
players: updatedPlayers,
|
players: updatedPlayers,
|
||||||
ballsOnTable: 15,
|
ballsOnTable: newBallsOnTable,
|
||||||
log: [...(game.log || []), {
|
currentPlayer: nextPlayer,
|
||||||
type: 'rerack',
|
log: newLog,
|
||||||
player: currentPlayer.name,
|
|
||||||
ballsAdded: ballsToAdd,
|
|
||||||
ballsOnTableBefore: game.ballsOnTable,
|
|
||||||
ballsOnTable: 15,
|
|
||||||
scoreIncrement,
|
|
||||||
newScore: updatedPlayers[game.currentPlayer].score
|
|
||||||
}],
|
|
||||||
});
|
});
|
||||||
|
// Reset local state
|
||||||
|
setPendingBallsLeft(null);
|
||||||
|
setPendingFouls(0);
|
||||||
|
setPendingReRack(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -243,9 +215,9 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
|||||||
{Array.from({ length: 15 }, (_, i) => i + 1).map(num => (
|
{Array.from({ length: 15 }, (_, i) => i + 1).map(num => (
|
||||||
<button
|
<button
|
||||||
key={num}
|
key={num}
|
||||||
onClick={() => handleTurnEnd(num)}
|
onClick={() => handleBallsLeft(num)}
|
||||||
disabled={num > game.ballsOnTable}
|
disabled={num > game.ballsOnTable}
|
||||||
className={styles['potted-ball-btn']}
|
className={styles['potted-ball-btn'] + (pendingBallsLeft === num ? ' ' + styles['selected'] : '')}
|
||||||
>
|
>
|
||||||
{num}
|
{num}
|
||||||
</button>
|
</button>
|
||||||
@@ -254,13 +226,20 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles['rerack-controls']}>
|
<div className={styles['rerack-controls']}>
|
||||||
<button onClick={() => handleReRack(14)} className={styles['rerack-btn']}>+14 Re-Rack</button>
|
<button onClick={() => handleReRack(14)} className={styles['rerack-btn'] + (pendingReRack === 14 ? ' ' + styles['selected'] : '')}>+14 Re-Rack</button>
|
||||||
<button onClick={() => handleReRack(15)} className={styles['rerack-btn']}>+15 Re-Rack</button>
|
<button onClick={() => handleReRack(15)} className={styles['rerack-btn'] + (pendingReRack === 15 ? ' ' + styles['selected'] : '')}>+15 Re-Rack</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles['foul-controls']}>
|
<div className={styles['foul-controls']}>
|
||||||
<button onClick={() => handleFoul('standard')} className={styles['foul-btn']}>Standard Foul (-1)</button>
|
<button onClick={() => handleFoul('standard')} className={styles['foul-btn']}>Standard Foul (-1)</button>
|
||||||
<button onClick={() => handleFoul('break')} className={styles['foul-btn']}>Break Foul (-2)</button>
|
<button onClick={() => handleFoul('break')} className={styles['foul-btn']}>Break Foul (-2)</button>
|
||||||
|
{pendingFouls > 0 && <span className={styles['pending-foul-info']}>Foulpunkte: {pendingFouls}</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles['turn-change-controls']}>
|
||||||
|
<button className={styles['turn-change-btn']} onClick={handleTurnChange}>
|
||||||
|
Aufnahme beenden / Turn wechseln
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GameLogTable log={game.log} players={game.players} />
|
<GameLogTable log={game.log} players={game.players} />
|
||||||
|
|||||||
Reference in New Issue
Block a user