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:
@@ -335,3 +335,40 @@
|
||||
font-size: 1.15rem;
|
||||
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 [pendingBallsLeft, setPendingBallsLeft] = useState(null); // null means not set
|
||||
const [pendingFouls, setPendingFouls] = useState(0);
|
||||
const [pendingReRack, setPendingReRack] = useState(0);
|
||||
|
||||
const handleSelectStartingPlayer = (playerIndex) => {
|
||||
onUpdate({
|
||||
...game,
|
||||
@@ -114,100 +118,68 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
||||
|
||||
const currentPlayer = game.players[game.currentPlayer];
|
||||
|
||||
const handleTurnEnd = (remainingBalls, foulPoints = 0) => {
|
||||
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 - 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 }],
|
||||
});
|
||||
// Handlers now only update local state
|
||||
const handleBallsLeft = (num) => {
|
||||
setPendingBallsLeft(num);
|
||||
};
|
||||
|
||||
const handleFoul = (foulType) => {
|
||||
let foulPoints = 0;
|
||||
let penalty = 0;
|
||||
const newConsecutiveFouls = (currentPlayer.consecutiveFouls || 0) + 1;
|
||||
|
||||
if (foulType === 'standard') {
|
||||
foulPoints = 1;
|
||||
} else if (foulType === 'break') {
|
||||
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
|
||||
},
|
||||
],
|
||||
});
|
||||
let newConsecutiveFouls = (currentPlayer.consecutiveFouls || 0) + 1;
|
||||
if (foulType === 'standard') foulPoints = 1;
|
||||
else if (foulType === 'break') foulPoints = 2;
|
||||
if (newConsecutiveFouls === 3) penalty = 15;
|
||||
setPendingFouls(pendingFouls + foulPoints + penalty);
|
||||
};
|
||||
|
||||
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) =>
|
||||
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({
|
||||
...game,
|
||||
players: updatedPlayers,
|
||||
ballsOnTable: 15,
|
||||
log: [...(game.log || []), {
|
||||
type: 'rerack',
|
||||
player: currentPlayer.name,
|
||||
ballsAdded: ballsToAdd,
|
||||
ballsOnTableBefore: game.ballsOnTable,
|
||||
ballsOnTable: 15,
|
||||
scoreIncrement,
|
||||
newScore: updatedPlayers[game.currentPlayer].score
|
||||
}],
|
||||
ballsOnTable: newBallsOnTable,
|
||||
currentPlayer: nextPlayer,
|
||||
log: newLog,
|
||||
});
|
||||
// Reset local state
|
||||
setPendingBallsLeft(null);
|
||||
setPendingFouls(0);
|
||||
setPendingReRack(0);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -243,9 +215,9 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
||||
{Array.from({ length: 15 }, (_, i) => i + 1).map(num => (
|
||||
<button
|
||||
key={num}
|
||||
onClick={() => handleTurnEnd(num)}
|
||||
onClick={() => handleBallsLeft(num)}
|
||||
disabled={num > game.ballsOnTable}
|
||||
className={styles['potted-ball-btn']}
|
||||
className={styles['potted-ball-btn'] + (pendingBallsLeft === num ? ' ' + styles['selected'] : '')}
|
||||
>
|
||||
{num}
|
||||
</button>
|
||||
@@ -254,13 +226,20 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
|
||||
</div>
|
||||
|
||||
<div className={styles['rerack-controls']}>
|
||||
<button onClick={() => handleReRack(14)} className={styles['rerack-btn']}>+14 Re-Rack</button>
|
||||
<button onClick={() => handleReRack(15)} className={styles['rerack-btn']}>+15 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'] + (pendingReRack === 15 ? ' ' + styles['selected'] : '')}>+15 Re-Rack</button>
|
||||
</div>
|
||||
|
||||
<div className={styles['foul-controls']}>
|
||||
<button onClick={() => handleFoul('standard')} className={styles['foul-btn']}>Standard Foul (-1)</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>
|
||||
|
||||
<GameLogTable log={game.log} players={game.players} />
|
||||
|
||||
Reference in New Issue
Block a user