feat(14.1): Implement foul system

Implements a comprehensive foul system for the 14.1 game mode as per issue #20.

- **`src/components/GameDetail141.jsx`**

    - Adds `handleFoul` function to manage standard (-1pt) and break (-2pt) fouls.

    - Implements logic for the 3-consecutive-foul rule, applying a -15pt penalty.

    - Adds foul counters and a visual warning for players with 2 consecutive fouls.

    - Resets the consecutive foul counter on a legal (non-foul) turn.

- **`src/components/GameDetail.module.css`**

    - Adds styles for foul buttons (`.foul-btn`).

    - Adds styles for the foul counter indicator (`.foul-indicator`) and warning (`.foul-warning`).

This commit fulfills the requirements for issue #20. The `.gitea` file is left unstaged as it points to the next issue to be worked on.
This commit is contained in:
Frank Schwenk
2025-06-22 10:43:21 +02:00
parent 6a25c18153
commit aa5ef1c5b2
2 changed files with 97 additions and 3 deletions

View File

@@ -192,4 +192,43 @@
}
.rerack-btn:hover {
background-color: #4a6fbf;
}
.foul-controls {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 1.5rem;
}
.foul-btn {
padding: 0.8rem 1.5rem;
font-size: 1rem;
font-weight: bold;
border-radius: 8px;
border: 1px solid #c0392b;
background-color: #e74c3c;
color: #fff;
cursor: pointer;
transition: background-color 0.2s;
}
.foul-btn:hover {
background-color: #c0392b;
}
.foul-indicator {
font-size: 1rem;
font-weight: bold;
color: #fff;
background-color: #c0392b;
padding: 4px 8px;
border-radius: 4px;
margin-top: 8px;
display: inline-block;
}
.foul-warning {
background-color: #f39c12;
color: #000;
}

View File

@@ -46,7 +46,7 @@ const GameDetail141 = ({ game, onUpdate, onBack }) => {
const newScore = currentPlayer.score + ballsPotted - foulPoints;
const updatedPlayers = game.players.map((p, index) =>
index === game.currentPlayer ? { ...p, score: newScore } : p
index === game.currentPlayer ? { ...p, score: newScore, consecutiveFouls: 0 } : p
);
const nextPlayer = (game.currentPlayer + 1) % game.players.length;
@@ -60,6 +60,56 @@ const GameDetail141 = ({ game, onUpdate, onBack }) => {
});
};
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,
history: [
...game.history,
{
player: currentPlayer.name,
foul: foulType,
foulPoints,
penalty,
totalDeduction,
newScore,
consecutiveFouls: newConsecutiveFouls
},
],
});
};
const handleReRack = (ballsToAdd) => {
const newBallsOnTable = game.ballsOnTable + ballsToAdd;
onUpdate({
@@ -83,6 +133,11 @@ const GameDetail141 = ({ game, onUpdate, onBack }) => {
>
<span className={styles['player-name']}>{p.name}</span>
<span className={styles['score']}>{p.score}</span>
{p.consecutiveFouls > 0 && (
<span className={`${styles['foul-indicator']} ${p.consecutiveFouls === 2 ? styles['foul-warning'] : ''}`}>
Fouls: {p.consecutiveFouls}
</span>
)}
</div>
))}
</div>
@@ -112,9 +167,9 @@ const GameDetail141 = ({ game, onUpdate, onBack }) => {
<button onClick={() => handleReRack(15)} className={styles['rerack-btn']}>+15 Re-Rack</button>
</div>
{/* Placeholder for future buttons */}
<div className={styles['foul-controls']}>
<p>Fouls & Re-Racks (nächste Phase)</p>
<button onClick={() => handleFoul('standard')} className={styles['foul-btn']}>Standard Foul (-1)</button>
<button onClick={() => handleFoul('break')} className={styles['foul-btn']}>Break Foul (-2)</button>
</div>
<div className={styles['game-detail-controls']}>