feat: skryté multiplayerové minihry přes websocket
CI / Generate TypeScript types (push) Canceled after 7s
CI / Server unit tests (push) Canceled after 0s
CI / Build server (push) Canceled after 0s
CI / Build client (push) Canceled after 0s
CI / Playwright E2E tests (push) Canceled after 0s
CI / Build and push Docker image (push) Canceled after 0s
CI / Notify (push) Canceled after 0s
CI / Generate TypeScript types (push) Canceled after 7s
CI / Server unit tests (push) Canceled after 0s
CI / Build server (push) Canceled after 0s
CI / Build client (push) Canceled after 0s
CI / Playwright E2E tests (push) Canceled after 0s
CI / Build and push Docker image (push) Canceled after 0s
CI / Notify (push) Canceled after 0s
Minihry se otevírají trojklikem na logo a napsáním kódu hry
("help" vypíše seznam do konzole prohlížeče):
- reakce: reakční hra, 3 kola, pořadí podle součtu časů
- snake: had pro více hráčů, simulaci drží server
- tetris: battle Tetris s odpadními řádky a lokálním tréninkem
- pong: Pong 1v1, ovládání myší i klávesami, bot se 4 obtížnostmi
- kresleni: kreslení a hádání jídel z dnešních menu
Ikony her se řídí zvoleným vzhledem. Stav her je v paměti serveru,
předpokládá se jedna replika.
This commit is contained in:
28 files changed
+5050
-2
No files matched your search
@@ -0,0 +1,102 @@
|
||||
import { SnakeGame, SnakeState, TICK_MS, COUNTDOWN_SECONDS, WIDTH, HEIGHT } from '../snakeGame';
|
||||
import { GameError } from '../minigames';
|
||||
import { fakeClock } from './helpers/fakeClock';
|
||||
|
||||
/** Náhoda vracející zadané hodnoty a pak nuly (jídlo pak padá do rohu [0,0]). */
|
||||
const sequence = (...values: number[]) => () => values.length ? values.shift()! : 0;
|
||||
|
||||
/** Hodnoty náhody, které umístí jídlo na danou buňku. */
|
||||
const foodAt = (x: number, y: number) => [x / WIDTH, y / HEIGHT];
|
||||
|
||||
function setup(random?: () => number) {
|
||||
const clock = fakeClock(random);
|
||||
const states: SnakeState[] = [];
|
||||
const game = new SnakeGame(s => states.push(s), s => states.push(s), 1, clock);
|
||||
const last = () => states[states.length - 1];
|
||||
const player = (login: string) => last().players.find(p => p.login === login)!;
|
||||
const startGame = (...logins: string[]) => {
|
||||
logins.forEach(l => game.join(l));
|
||||
game.start(logins[0]);
|
||||
clock.advance(COUNTDOWN_SECONDS * 1000);
|
||||
};
|
||||
const ticks = (n: number) => clock.advance(n * TICK_MS);
|
||||
return { game, clock, last, player, startGame, ticks };
|
||||
}
|
||||
|
||||
describe('had', () => {
|
||||
test('po odpočtu začne hra a had se posouvá o políčko za tick', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice');
|
||||
expect(ctx.last().phase).toBe('playing');
|
||||
expect(ctx.player('alice').body).toEqual([[5, 10], [4, 10], [3, 10]]);
|
||||
ctx.ticks(2);
|
||||
expect(ctx.player('alice').body).toEqual([[7, 10], [6, 10], [5, 10]]);
|
||||
});
|
||||
|
||||
test('zatáčí podle směru a obrat o 180° ignoruje', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice');
|
||||
ctx.game.turn('alice', 'left'); // obrat — ignoruje se
|
||||
ctx.ticks(1);
|
||||
expect(ctx.player('alice').body[0]).toEqual([6, 10]);
|
||||
ctx.game.turn('alice', 'up');
|
||||
ctx.ticks(1);
|
||||
expect(ctx.player('alice').body[0]).toEqual([6, 9]);
|
||||
});
|
||||
|
||||
test('nesmyslný směr se ignoruje', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice');
|
||||
ctx.game.turn('alice', 'diagonal');
|
||||
ctx.ticks(1);
|
||||
expect(ctx.player('alice').body[0]).toEqual([6, 10]);
|
||||
});
|
||||
|
||||
test('sněděné jídlo prodlouží hada a přidá bod', () => {
|
||||
const ctx = setup(sequence(...foodAt(7, 10)));
|
||||
ctx.startGame('alice');
|
||||
expect(ctx.last().food).toEqual([[7, 10]]);
|
||||
ctx.ticks(2);
|
||||
expect(ctx.player('alice').score).toBe(1);
|
||||
expect(ctx.player('alice').body).toHaveLength(4);
|
||||
expect(ctx.last().food).not.toContainEqual([7, 10]);
|
||||
});
|
||||
|
||||
test('náraz do zdi ukončí sólo hru', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice');
|
||||
ctx.ticks(WIDTH);
|
||||
expect(ctx.last().phase).toBe('finished');
|
||||
expect(ctx.player('alice')).toMatchObject({ alive: false, place: 1 });
|
||||
});
|
||||
|
||||
test('ve hře více hráčů vyhrává poslední přeživší', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice', 'bob');
|
||||
// alice startuje v řádku 6, nahoru do zdi dorazí v 7. ticku
|
||||
ctx.game.turn('alice', 'up');
|
||||
ctx.ticks(7);
|
||||
expect(ctx.last().phase).toBe('finished');
|
||||
expect(ctx.player('bob')).toMatchObject({ alive: true, place: 1 });
|
||||
expect(ctx.player('alice')).toMatchObject({ alive: false, place: 2 });
|
||||
});
|
||||
|
||||
test('čelní srážka zabije oba hady', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice', 'bob');
|
||||
// alice (5,6)→ doprava, bob (26,13)← doleva; po 7 tickách alice zatočí dolů
|
||||
// a po dalších 7 se obě hlavy potkají na (12,13)
|
||||
ctx.ticks(7);
|
||||
ctx.game.turn('alice', 'down');
|
||||
ctx.ticks(7);
|
||||
const state = ctx.last();
|
||||
expect(state.phase).toBe('finished');
|
||||
expect(state.players.every(p => !p.alive)).toBe(true);
|
||||
});
|
||||
|
||||
test('do běžící hry se nelze připojit', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame('alice');
|
||||
expect(() => ctx.game.join('bob')).toThrow(GameError);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user