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,124 @@
|
||||
import { PongGame, PongState, TICK_MS, COUNTDOWN_SECONDS, FIELD_HEIGHT, PADDLE_MARGIN, PADDLE_WIDTH, PADDLE_HEIGHT, WIN_SCORE, SERVE_PAUSE_MS, botLogin, BotLevel } from '../pongGame';
|
||||
import { GameError } from '../minigames';
|
||||
import { fakeClock } from './helpers/fakeClock';
|
||||
|
||||
/** Deterministická pseudonáhoda, ať se boti nechovají pokaždé stejně jako s konstantou. */
|
||||
function lcg(seed = 1) {
|
||||
let x = seed;
|
||||
return () => (x = (x * 1103515245 + 12345) % 2147483648) / 2147483648;
|
||||
}
|
||||
|
||||
function setup(random?: () => number) {
|
||||
const clock = fakeClock(random);
|
||||
const states: PongState[] = [];
|
||||
const game = new PongGame(s => states.push(s), s => states.push(s), clock);
|
||||
const last = () => states[states.length - 1];
|
||||
const player = (login: string) => last().players.find(p => p.login === login)!;
|
||||
const startGame = () => {
|
||||
game.join('alice');
|
||||
game.join('bob');
|
||||
game.start('alice');
|
||||
clock.advance(COUNTDOWN_SECONDS * 1000);
|
||||
};
|
||||
const ticks = (n: number) => clock.advance(n * TICK_MS);
|
||||
return { game, clock, last, player, startGame, ticks };
|
||||
}
|
||||
|
||||
describe('pong', () => {
|
||||
test('je jen pro dva hráče a hru spouští zakladatel', () => {
|
||||
const ctx = setup();
|
||||
ctx.game.join('alice');
|
||||
expect(() => ctx.game.start('alice')).toThrow(GameError);
|
||||
ctx.game.join('bob');
|
||||
expect(() => ctx.game.join('carol')).toThrow(GameError);
|
||||
expect(() => ctx.game.start('bob')).toThrow(GameError);
|
||||
ctx.game.start('alice');
|
||||
expect(ctx.last().phase).toBe('countdown');
|
||||
});
|
||||
|
||||
test('pálka se hýbe podle vstupu a nevyjede z hřiště', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame();
|
||||
ctx.game.input('alice', 'up');
|
||||
ctx.ticks(10);
|
||||
expect(ctx.player('alice').y).toBeLessThan(FIELD_HEIGHT / 2);
|
||||
ctx.ticks(100);
|
||||
expect(ctx.player('alice').y).toBe(45);
|
||||
ctx.game.input('alice', 'stop');
|
||||
const y = ctx.player('alice').y;
|
||||
ctx.ticks(5);
|
||||
expect(ctx.player('alice').y).toBe(y);
|
||||
});
|
||||
|
||||
test('míček se od pálky odrazí', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame();
|
||||
const face = PADDLE_MARGIN + PADDLE_WIDTH;
|
||||
ctx.game.setBallForTest(face + 20, FIELD_HEIGHT / 2, -400, 0);
|
||||
ctx.ticks(3);
|
||||
expect(ctx.last().ball.x).toBeGreaterThan(face);
|
||||
expect(ctx.player('bob').score).toBe(0);
|
||||
});
|
||||
|
||||
test('minutý míček je bod pro soupeře a hra končí na vítězné skóre', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame();
|
||||
for (let i = 0; i < WIN_SCORE; i++) {
|
||||
// Míček u horního okraje, kam pálka uprostřed nedosáhne
|
||||
ctx.game.setBallForTest(60, 10, -600, 0);
|
||||
ctx.ticks(10);
|
||||
if (i < WIN_SCORE - 1) {
|
||||
expect(ctx.player('bob').score).toBe(i + 1);
|
||||
ctx.clock.advance(SERVE_PAUSE_MS);
|
||||
}
|
||||
}
|
||||
expect(ctx.last()).toMatchObject({ phase: 'finished', winner: 'bob' });
|
||||
});
|
||||
|
||||
test('myš: pálka rychle dojede k cílové výšce a šipka myš přebije', () => {
|
||||
const ctx = setup();
|
||||
ctx.startGame();
|
||||
ctx.game.aim('alice', 0);
|
||||
ctx.ticks(5);
|
||||
expect(ctx.player('alice').y).toBe(PADDLE_HEIGHT / 2);
|
||||
ctx.game.input('alice', 'down');
|
||||
ctx.ticks(5);
|
||||
expect(ctx.player('alice').y).toBeGreaterThan(PADDLE_HEIGHT / 2);
|
||||
});
|
||||
|
||||
test('bota přidá jen zakladatel a lze ho odebrat', () => {
|
||||
const ctx = setup();
|
||||
ctx.game.join('alice');
|
||||
expect(() => ctx.game.addBot('bob', 'easy')).toThrow(GameError);
|
||||
expect(() => ctx.game.addBot('alice', 'superhard')).toThrow(GameError);
|
||||
ctx.game.addBot('alice', 'hard');
|
||||
expect(ctx.last().players.map(p => p.login)).toEqual(['alice', botLogin('hard')]);
|
||||
ctx.game.removeBot('alice');
|
||||
expect(ctx.last().players).toHaveLength(1);
|
||||
});
|
||||
|
||||
/** Odehraje zápas člověka, který myší dokonale sleduje míček, proti botovi. */
|
||||
function playAgainstBot(level: BotLevel, maxSeconds: number) {
|
||||
const ctx = setup(lcg(7));
|
||||
ctx.game.join('alice');
|
||||
ctx.game.addBot('alice', level);
|
||||
ctx.game.start('alice');
|
||||
ctx.clock.advance(COUNTDOWN_SECONDS * 1000);
|
||||
for (let t = 0; t < maxSeconds * 1000 && ctx.last().phase === 'playing'; t += TICK_MS) {
|
||||
ctx.game.aim('alice', ctx.last().ball.y);
|
||||
ctx.ticks(1);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
test('lehký bot proti dokonalému hráči prohraje', () => {
|
||||
const ctx = playAgainstBot('easy', 300);
|
||||
expect(ctx.last()).toMatchObject({ phase: 'finished', winner: 'alice' });
|
||||
});
|
||||
|
||||
test('nemožný bot neztratí ani bod', () => {
|
||||
const ctx = playAgainstBot('impossible', 120);
|
||||
expect(ctx.player('alice').score).toBe(0);
|
||||
expect(ctx.player(botLogin('impossible')).score).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user