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:
@@ -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;
|
||||
}
|
||||
@@ -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']}>
|
||||
|
||||
Reference in New Issue
Block a user