mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-17 14:21:25 +02:00
A browser-based roguelike synchronized with crypto block timing. A run is a race against the next block: reach the exit before the block lands, or the house wins. The same codebase runs the solo dungeon, the social Tavern, and operator-selected PvP and race rulesets, with separate free and paid leaderboards. Self-hostable under the MIT License. See README.md to run it, docs/ARCHITECTURE.md for the design, and docs/DEPLOY.md to operate an instance.
99 lines
3.6 KiB
JavaScript
99 lines
3.6 KiB
JavaScript
const TavernMatchBridge = require('../src/network/tavernMatchBridge');
|
|
|
|
describe('TavernMatchBridge', () => {
|
|
function makeManager() {
|
|
const rooms = new Map();
|
|
return {
|
|
rooms,
|
|
onTick: jest.fn(),
|
|
onFinish: jest.fn()
|
|
};
|
|
}
|
|
|
|
function makeBridge({ rooms = [], io = null } = {}) {
|
|
const manager = makeManager();
|
|
for (const r of rooms) manager.rooms.set(r.id, r);
|
|
const emitted = [];
|
|
const bridge = new TavernMatchBridge({
|
|
matchManager: manager,
|
|
io: io || { to: () => ({ emit: (e, p) => emitted.push({ e, p }) }) },
|
|
debugManager: null
|
|
});
|
|
bridge.enabled = true;
|
|
return { bridge, manager, emitted };
|
|
}
|
|
|
|
function makeRoom(id = 'm1') {
|
|
return {
|
|
id,
|
|
economy: 'free',
|
|
variant: 'race',
|
|
tickCount: 5,
|
|
status: 'active',
|
|
rows: 2,
|
|
cols: 3,
|
|
occupants: new Map([['a', { id: 'a', x: 1, y: 2, name: 'A', avatar: 'default', facing: 'down' }]]),
|
|
playerStates: new Map([['a', { alive: true, finished: false, escaped: false, hasTreasure: false, placement: null }]]),
|
|
monster: { x: 3, y: 4 },
|
|
treasure: null,
|
|
dungeon: { map: [["'1", "'1", '#'], ['#', "'1", '>']], entrance: [0, 0], exit: [2, 1] },
|
|
winnerId: null,
|
|
seedHash: 'deadbeef',
|
|
seed: 'never-publish-this'
|
|
};
|
|
}
|
|
|
|
test('forwards public match tick to tavern room', () => {
|
|
const room = makeRoom('m1');
|
|
const { bridge, manager, emitted } = makeBridge({ rooms: [room] });
|
|
bridge.initialize();
|
|
manager.onTick('m1', { tick: 5, events: [] });
|
|
expect(emitted.some(e => e.e === 'tavern_match_tick' && e.p.matchId === 'm1')).toBe(true);
|
|
});
|
|
|
|
test('forwards match end to tavern room', async () => {
|
|
const room = makeRoom('m2');
|
|
room.status = 'finished';
|
|
room.winnerId = 'a';
|
|
const { bridge, manager, emitted } = makeBridge({ rooms: [room] });
|
|
bridge.initialize();
|
|
await manager.onFinish(room);
|
|
expect(emitted.some(e => e.e === 'tavern_match_end' && e.p.matchId === 'm2')).toBe(true);
|
|
});
|
|
|
|
test('getActiveMatches returns public snapshots', () => {
|
|
const room = makeRoom('m3');
|
|
const { bridge } = makeBridge({ rooms: [room] });
|
|
const list = bridge.getActiveMatches();
|
|
expect(list.length).toBe(1);
|
|
expect(list[0].matchId).toBe('m3');
|
|
expect(list[0].players.length).toBe(1);
|
|
});
|
|
|
|
test('public snapshots include a renderer-safe map but never the unrevealed seed', () => {
|
|
const room = makeRoom('m-render');
|
|
room.toGameState = () => ({
|
|
visibleTiles: room.dungeon.map,
|
|
exploredTiles: room.dungeon.map,
|
|
lighting: {},
|
|
dungeonRows: 2,
|
|
dungeonCols: 3,
|
|
players: [{ id: 'a', x: 1, y: 1, alive: true }],
|
|
monster: { x: 0, y: 1 },
|
|
treasure: { x: 1, y: 0, carrierId: null },
|
|
entrance: [0, 0],
|
|
exit: [2, 1],
|
|
seed: room.seed,
|
|
privateEvents: [{ input: 'left' }]
|
|
});
|
|
const { bridge } = makeBridge({ rooms: [room] });
|
|
const snap = bridge.getActiveMatches()[0];
|
|
|
|
expect(snap.visibleTiles).toEqual(room.dungeon.map);
|
|
expect(snap.exploredTiles).toEqual(room.dungeon.map);
|
|
expect(snap).toMatchObject({ dungeonRows: 2, dungeonCols: 3, seedHash: 'deadbeef' });
|
|
expect(snap.seed).toBeUndefined();
|
|
expect(snap.privateEvents).toBeUndefined();
|
|
});
|
|
});
|