fix(ui): unify tie-for-lead result across lobby + game (shared victory, not a draw)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m7s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m7s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m41s
Bring the online lobby badge and the in-game 'you won/lost' text to the same competition ranking the hotseat seat medals use: in a 3-4 player game a TIE for the lead is a SHARED victory for the top scorers and a placed finish for those below — not a full draw for everyone (winner() is -1 on any tie, so isWinner alone conflated them). An aborted game and a genuine all-level finish stay a draw; a win by resignation (winner at a not-higher score) is unchanged. - result.ts: resultBadge no-winner branch ranks by final score; placeBadge helper; resigned seats excluded from the ranking. - Game.svelte: resultText aligned (shared lead = won, below = lost).
This commit is contained in:
+24
-9
@@ -9,6 +9,13 @@ export interface ResultBadge {
|
||||
emoji: string;
|
||||
}
|
||||
|
||||
/** placeBadge maps a 1-based finishing rank to its label + medal (2nd is a defeat in a duel). */
|
||||
function placeBadge(rank: number, players: number): ResultBadge {
|
||||
if (rank === 2) return players === 2 ? { key: 'result.defeat', emoji: '🥈' } : { key: 'result.place2', emoji: '🥈' };
|
||||
if (rank === 3) return { key: 'result.place3', emoji: '🥉' };
|
||||
return { key: 'result.place4', emoji: '🏅' };
|
||||
}
|
||||
|
||||
export function resultBadge(game: GameView, myId: string): ResultBadge {
|
||||
const me = game.seats.find((s) => s.accountId === myId);
|
||||
|
||||
@@ -19,16 +26,24 @@ export function resultBadge(game: GameView, myId: string): ResultBadge {
|
||||
}
|
||||
|
||||
if (me?.isWinner) return { key: 'result.victory', emoji: '🏆' };
|
||||
if (!game.seats.some((s) => s.isWinner)) return { key: 'result.draw', emoji: '🏅' };
|
||||
|
||||
// Someone else won and it is not me, so I did not win — even when scores are level (a
|
||||
// win by resignation or timeout can leave the winner at or below my score). The winner
|
||||
// takes rank 1; place me among the remaining seats by score, starting at rank 2.
|
||||
const ahead = game.seats.filter((s) => !s.isWinner && s.accountId !== myId && s.score > (me?.score ?? 0)).length;
|
||||
const rank = 2 + ahead;
|
||||
if (rank === 2) return game.players === 2 ? { key: 'result.defeat', emoji: '🥈' } : { key: 'result.place2', emoji: '🥈' };
|
||||
if (rank === 3) return { key: 'result.place3', emoji: '🥉' };
|
||||
return { key: 'result.place4', emoji: '🏅' };
|
||||
// No single winner: a tie for the lead, a full draw, or an aborted game. An abort or an all-level
|
||||
// finish is a draw; a tie for the lead is a SHARED victory for the top scorers, and a placed finish
|
||||
// for those below. (winner() is -1 on ANY tie for the lead, so isWinner alone cannot tell a
|
||||
// three-way "two share first, one is last" apart from a genuine draw — the reported bug.)
|
||||
if (!game.seats.some((s) => s.isWinner)) {
|
||||
const myScore = me?.score ?? 0;
|
||||
const higher = game.seats.filter((s) => !s.resigned && s.score > myScore).length;
|
||||
const allLevel = game.seats.filter((s) => !s.resigned).every((s) => s.score === myScore);
|
||||
if (game.endReason === 'aborted' || (higher === 0 && allLevel)) return { key: 'result.draw', emoji: '🏅' };
|
||||
return higher === 0 ? { key: 'result.victory', emoji: '🏆' } : placeBadge(1 + higher, game.players);
|
||||
}
|
||||
|
||||
// A winner exists but it is not me — even when scores are level (a win by resignation or timeout
|
||||
// can leave the winner at or below my score). The winner takes rank 1; place me among the remaining
|
||||
// (non-resigned) seats by score, starting at rank 2.
|
||||
const ahead = game.seats.filter((s) => !s.isWinner && !s.resigned && s.accountId !== myId && s.score > (me?.score ?? 0)).length;
|
||||
return placeBadge(2 + ahead, game.players);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user