fix: možnost připojit se ke hře Tetris během odpočtu před startem

This commit is contained in:
mates committed 2026-10-20 09:43:21 +02:00
1 parent fe12098ed0
commit bc2cc75d7d
3 files changed
+62 -17

No files matched your search

+26 -1
View File
@@ -1,4 +1,4 @@
import { TetrisGame, TetrisState, TetrisSettings, GarbageEvent, COUNTDOWN_SECONDS, MIN_ATTACK_GAP_MS, WATCH_FLUSH_MS, BOARD_WIDTH, BOARD_HEIGHT, PLAYER_TIMEOUT_MS, DEFAULT_SETTINGS } from '../tetrisGame';
import { TetrisGame, TetrisState, TetrisSettings, GarbageEvent, COUNTDOWN_SECONDS, JOIN_GRACE_SECONDS, MIN_ATTACK_GAP_MS, WATCH_FLUSH_MS, BOARD_WIDTH, BOARD_HEIGHT, PLAYER_TIMEOUT_MS, DEFAULT_SETTINGS } from '../tetrisGame';
import { GameError } from '../minigames';
import { fakeClock } from './helpers/fakeClock';
@@ -195,4 +195,29 @@ describe('tetris', () => {
ctx.startGame('alice');
expect(() => ctx.game.join('bob')).toThrow(GameError);
});
test('během odpočtu se lze ještě připojit', () => {
const ctx = setup();
ctx.game.join('alice');
ctx.game.start('alice');
ctx.clock.advance(1000);
ctx.game.join('bob');
expect(ctx.last()).toMatchObject({ phase: 'countdown', countdown: COUNTDOWN_SECONDS - 1 });
expect(ctx.last().players.map(p => p.login)).toEqual(['alice', 'bob']);
ctx.clock.advance((COUNTDOWN_SECONDS - 1) * 1000);
expect(ctx.last().phase).toBe('playing');
});
test('připojení na poslední chvíli prodlouží odpočet', () => {
const ctx = setup();
ctx.game.join('alice');
ctx.game.start('alice');
ctx.clock.advance((COUNTDOWN_SECONDS - 1) * 1000);
ctx.game.join('bob');
expect(ctx.last()).toMatchObject({ phase: 'countdown', countdown: JOIN_GRACE_SECONDS });
ctx.clock.advance((JOIN_GRACE_SECONDS - 1) * 1000);
expect(ctx.last().phase).toBe('countdown');
ctx.clock.advance(1000);
expect(ctx.last().phase).toBe('playing');
});
});
+10 -3
View File
@@ -5,7 +5,7 @@ import { Clock, defaultClock, GameError, gameHandler } from "./minigames";
* Multiplayerový Tetris (battle) přes websocket.
*
* Každý hráč hraje vlastní desku v prohlížeči (kvůli odezvě ovládání), server koordinuje:
* lobby a odpočet, společný seed pro pořadí kostek, přeposílání desek soupeřům,
* lobby a odpočet (během něj se ještě dá připojit), společný seed pro pořadí kostek, přeposílání desek soupeřům,
* útoky (smazané řádky → odpadní řádky soupeři podle zvoleného režimu míření) a vyřazování.
* Zakladatel lobby volí režim míření a způsob hodnocení: buď vyhrává poslední hráč ve hře
* (sólo hra končí prohrou), nebo nejvyšší skóre po uplynutí časového limitu.
@@ -17,7 +17,10 @@ import { Clock, defaultClock, GameError, gameHandler } from "./minigames";
export const BOARD_WIDTH = 10;
export const BOARD_HEIGHT = 20;
export const MAX_PLAYERS = 8;
export const COUNTDOWN_SECONDS = 3;
/** Odpočet po spuštění hry — během něj se ještě mohou připojit opozdilci. */
export const COUNTDOWN_SECONDS = 5;
/** Kolik sekund odpočtu má nejméně zbývat po připojení dalšího hráče, ať se stihne připravit. */
export const JOIN_GRACE_SECONDS = 3;
/** Neodstartované lobby se po této době zruší (ms). */
export const LOBBY_TIMEOUT_MS = 3 * 60 * 1000;
/** Pojistka proti nekonečné hře (ms). */
@@ -147,7 +150,7 @@ export class TetrisGame {
this.phase = 'lobby';
this.host = login;
this.schedule(() => this.cancel(), LOBBY_TIMEOUT_MS);
} else if (this.phase !== 'lobby') {
} else if (this.phase !== 'lobby' && this.phase !== 'countdown') {
throw new GameError('Hra už běží, počkejte na další.');
}
if (!this.players.has(login)) {
@@ -155,6 +158,10 @@ export class TetrisGame {
throw new GameError('Lobby je plné.');
}
this.players.set(login, { login, board: EMPTY_BOARD, score: 0, lines: 0, alive: true, lastAttack: 0, lastSeen: 0, garbageReceived: 0 });
// Opozdilec připojený na poslední chvíli dostane čas se připravit (počet hráčů je omezený, odpočet se neprodlužuje donekonečna)
if (this.phase === 'countdown' && this.countdown < JOIN_GRACE_SECONDS) {
this.countdown = JOIN_GRACE_SECONDS;
}
}
this.emitAll();
}