feat(14-1): show sum of foul points per player per round in log table

- Table now groups turns into rounds (Aufnahmen) and displays sum of all foul points (including penalties) for each player in each round
- Improves clarity and accuracy of move log for 14/1

Refs #26
This commit is contained in:
Frank Schwenk
2025-06-24 10:53:37 +02:00
parent b8bc3f8a5c
commit c6557dc050
2 changed files with 116 additions and 19 deletions

View File

@@ -294,4 +294,44 @@
.log-entry:last-child { .log-entry:last-child {
border-bottom: none; border-bottom: none;
}
.game-log-table-container {
margin: 2.5rem auto 0 auto;
max-width: 100vw;
width: 100%;
overflow-x: auto;
background: #181818;
border-radius: 12px;
box-shadow: 0 2px 16px rgba(0,0,0,0.12);
padding: 1.5rem 1rem;
}
.game-log-table {
width: 100%;
border-collapse: collapse;
font-size: 1.05rem;
background: #222;
color: #fff;
}
.game-log-table th, .game-log-table td {
border: 1px solid #444;
padding: 0.5rem 0.7rem;
text-align: center;
}
.game-log-table th {
background: #333;
font-weight: 700;
font-size: 1.1rem;
}
.game-log-table tr:nth-child(even) td {
background: #232323;
}
.game-log-table tr:nth-child(odd) td {
background: #181818;
}
.game-log-table .log-player-col {
background: #222;
color: #ff9800;
font-size: 1.15rem;
border-bottom: 2px solid #ff9800;
} }

View File

@@ -21,25 +21,82 @@ const StartingPlayerModal = ({ players, onSelect, onCancel }) => (
</div> </div>
); );
const GameLog = ({ log }) => { const GameLogTable = ({ log, players }) => {
if (!log || log.length === 0) { if (!log || log.length === 0) return null;
return null; // Only turn and foul entries
const turnEntries = log.filter(e => e.type === 'turn');
const foulEntries = log.filter(e => e.type === 'foul');
// Group into rounds (Aufnahmen): each round = one turn per player, in order
const rounds = [];
for (let i = 0; i < turnEntries.length; i += players.length) {
rounds.push(turnEntries.slice(i, i + players.length));
}
// Helper: for each player/round, sum fouls between previous and current turn
function getFoulSum(playerName, turnIdx, turnEntry) {
// Find previous turn index for this player
let prevTurnIdx = -1;
for (let i = turnIdx - 1; i >= 0; --i) {
if (turnEntries[i].player === playerName) {
prevTurnIdx = i;
break;
}
} }
// Find fouls for this player between prevTurnIdx and turnIdx
return ( let fouls = foulEntries.filter(f => {
<div className={styles['game-log']}> // Find index of this foul in log
<h4 className={styles['log-title']}>Game Log</h4> const foulLogIdx = log.indexOf(f);
<ul className={styles['log-list']}> // Find log index of previous turn and current turn
{log.slice().reverse().map((entry, index) => ( const prevTurnLogIdx = prevTurnIdx >= 0 ? log.indexOf(turnEntries[prevTurnIdx]) : -1;
<li key={index} className={styles['log-entry']}> const currTurnLogIdx = log.indexOf(turnEntry);
{entry.type === 'rerack' && `Re-Rack (+${entry.ballsAdded} balls).`} return f.player === playerName && foulLogIdx > prevTurnLogIdx && foulLogIdx < currTurnLogIdx;
{entry.foul && `Foul by ${entry.player}: ${entry.foul} (${entry.totalDeduction} pts).`} });
{entry.ballsPotted !== undefined && `${entry.player}: ${entry.ballsPotted} balls potted. Score: ${entry.newScore}`} // Sum totalDeduction for all fouls
</li> return fouls.reduce((sum, f) => sum + (f.totalDeduction || 0), 0);
))} }
</ul> return (
</div> <div className={styles['game-log-table-container']}>
); <h4 className={styles['log-title']}>Aufnahmen</h4>
<table className={styles['game-log-table']}>
<thead>
<tr>
<th>Aufnahme</th>
{players.map((p, idx) => (
<th key={p.name} colSpan={3} className={styles['log-player-col']}>{p.name}</th>
))}
</tr>
<tr>
<th></th>
{players.map((p, idx) => [
<th key={p.name + '-balls'}>Bälle</th>,
<th key={p.name + '-foul'}>Foul</th>,
<th key={p.name + '-score'}>Score</th>
])}
</tr>
</thead>
<tbody>
{rounds.map((round, roundIdx) => (
<tr key={roundIdx}>
<td>{roundIdx + 1}</td>
{players.map((p, idx) => {
const entry = round.find(e => e.player === p.name);
if (entry) {
const turnIdx = turnEntries.indexOf(entry);
const foulSum = getFoulSum(p.name, turnIdx, entry);
return [
<td key={p.name + '-balls'}>{entry.ballsPotted}</td>,
<td key={p.name + '-foul'}>{foulSum > 0 ? foulSum : ''}</td>,
<td key={p.name + '-score'}>{entry.newScore}</td>
];
} else {
return [<td key={p.name + '-balls'}></td>,<td key={p.name + '-foul'}></td>,<td key={p.name + '-score'}></td>];
}
})}
</tr>
))}
</tbody>
</table>
</div>
);
}; };
const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => { const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
@@ -206,7 +263,7 @@ const GameDetail141 = ({ game, onUpdate, onUndo, onForfeit, onBack }) => {
<button onClick={() => handleFoul('break')} className={styles['foul-btn']}>Break Foul (-2)</button> <button onClick={() => handleFoul('break')} className={styles['foul-btn']}>Break Foul (-2)</button>
</div> </div>
<GameLog log={game.log} /> <GameLogTable log={game.log} players={game.players} />
<div className={styles['game-detail-controls']}> <div className={styles['game-detail-controls']}>
<button className="btn" onClick={onUndo} disabled={!game.undoStack || game.undoStack.length === 0}>Undo</button> <button className="btn" onClick={onUndo} disabled={!game.undoStack || game.undoStack.length === 0}>Undo</button>