fix(offline): hotseat finished-game medals by final score (fix all-last-place on a tie)
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 1m6s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
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 1m6s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m42s
Reported: a hotseat game ended by 6 scoreless passes deducts each rack (correct rule, same as online: -8/-14/-8 here), which tied two seats for the lead. The engine's winner() returns -1 on a tie, so seatMedal read it as a full draw and gave EVERY seat the last-place medal. - result.ts: seatMedal now ranks by the FINAL score (competition ranking — a tie for the lead shares the trophy), not the single-winner flag. A resigned / host-excluded seat places last with no medal and does not push the others down. - model.ts: Seat.resigned (offline-only); engine.resignedOf getter; the local source surfaces it on the game view. - The scoreless rack deduction is unchanged (standard rules, owner-confirmed).
This commit is contained in:
+23
-18
@@ -2,13 +2,14 @@ import { describe, expect, it } from 'vitest';
|
||||
import { resultBadge, seatMedal } from './result';
|
||||
import type { GameView, Seat } from './model';
|
||||
|
||||
const seat = (s: number, accountId: string, score: number, isWinner = false): Seat => ({
|
||||
const seat = (s: number, accountId: string, score: number, isWinner = false, resigned = false): Seat => ({
|
||||
seat: s,
|
||||
accountId,
|
||||
displayName: accountId,
|
||||
score,
|
||||
hintsUsed: 0,
|
||||
isWinner,
|
||||
resigned,
|
||||
});
|
||||
|
||||
function game(seats: Seat[], status = 'finished', toMove = 0): GameView {
|
||||
@@ -81,23 +82,27 @@ describe('seatMedal', () => {
|
||||
expect(seatMedal(game([seat(0, 'a', 5), seat(1, 'b', 3)], 'active'), 0)).toBe('');
|
||||
});
|
||||
|
||||
it('finished two-player: winner gets the trophy, the other second place', () => {
|
||||
const g = game([seat(0, 'a', 300, true), seat(1, 'b', 200)]);
|
||||
expect(seatMedal(g, 0)).toBe('🏆');
|
||||
expect(seatMedal(g, 1)).toBe('🥈');
|
||||
});
|
||||
|
||||
it('finished draw (no winner): every seat gets the medal', () => {
|
||||
const g = game([seat(0, 'a', 200), seat(1, 'b', 200)]);
|
||||
expect(seatMedal(g, 0)).toBe('🏅');
|
||||
expect(seatMedal(g, 1)).toBe('🏅');
|
||||
});
|
||||
|
||||
it('finished four-player: places each seat by score', () => {
|
||||
it('finished: places each seat by final score', () => {
|
||||
const g = game([seat(0, 'a', 100), seat(1, 'b', 400, true), seat(2, 'c', 300), seat(3, 'd', 200)]);
|
||||
expect(seatMedal(g, 1)).toBe('🏆'); // winner
|
||||
expect(seatMedal(g, 2)).toBe('🥈'); // 2nd by score
|
||||
expect(seatMedal(g, 3)).toBe('🥉'); // 3rd
|
||||
expect(seatMedal(g, 0)).toBe('🏅'); // 4th
|
||||
expect(seatMedal(g, 1)).toBe('🏆'); // 400 → 1st
|
||||
expect(seatMedal(g, 2)).toBe('🥈'); // 300 → 2nd
|
||||
expect(seatMedal(g, 3)).toBe('🥉'); // 200 → 3rd
|
||||
expect(seatMedal(g, 0)).toBe('🏅'); // 100 → 4th
|
||||
});
|
||||
|
||||
it('a tie for the lead shares first place; the rest place below (competition ranking)', () => {
|
||||
// The owner's case: a scoreless end deducted racks to -8 / -14 / -8, tying seats 0 and 2 for the
|
||||
// lead — they must SHARE the trophy, not all show the last-place medal (the reported bug).
|
||||
const g = game([seat(0, 'a', -8), seat(1, 'b', -14), seat(2, 'c', -8)]);
|
||||
expect(seatMedal(g, 0)).toBe('🏆');
|
||||
expect(seatMedal(g, 2)).toBe('🏆');
|
||||
expect(seatMedal(g, 1)).toBe('🥉'); // two seats rank above it → 3rd
|
||||
});
|
||||
|
||||
it('an excluded / resigned seat gets no medal and does not push the others down', () => {
|
||||
const g = game([seat(0, 'a', 50), seat(1, 'b', 900, false, true), seat(2, 'c', 30)]);
|
||||
expect(seatMedal(g, 1)).toBe(''); // resigned: no medal, though its frozen score is the highest
|
||||
expect(seatMedal(g, 0)).toBe('🏆'); // ranked among the rest: highest → 1st
|
||||
expect(seatMedal(g, 2)).toBe('🥈');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user