Files
wownerogue/test/monsterCollision.test.js
jwinterm fad2a695bd Wownerogue
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.
2026-07-27 08:23:01 -04:00

87 lines
3.5 KiB
JavaScript

/**
* Monster collision tests (Phase 3.3).
*
* With movesPerPlayerMove > 1 (e.g. the casino preset's 1.5x speed) the monster takes
* multiple steps per turn. Collision must be checked after EACH sub-step, or the monster
* can step onto the player's tile and off again between checks, phasing through the player
* and missing the catch (a player-favorable bug that lowers the house edge).
*/
const Game = require('../src/game/game');
const DungeonGenerator = require('../src/game/dungeon');
function openGame() {
// Default-size dungeon so the digger generates validly; we then override the map and
// positions with a known fully-open grid so the scenario is deterministic.
const game = new Game('sock-test', { id: 1 });
const FL = "'1";
game.dungeon.map = Array.from({ length: 7 }, () => Array(7).fill(FL));
game.gameConfig.primaryFloor = FL;
game.gameConfig.secondaryFloor = "'2";
game.gameState = 'active';
game.seededRNG = () => 0; // 0 < chaseAggressiveness => always chase
return game;
}
describe('Monster collision with multi-step movement', () => {
test('two-room fallback spawns the monster deterministically on a distant passable tile', () => {
const FL = "'1";
const map = Array.from({ length: 7 }, (_, y) => Array.from({ length: 7 }, (_, x) =>
(x === 0 || y === 0 || x === 6 || y === 6) ? '#' : FL));
const dungeon = {
map,
rooms: [],
entrance: [1, 1],
exit: [5, 5],
treasure: [4, 4],
torches: []
};
const generatedFingerprint = DungeonGenerator.layoutFingerprint(dungeon);
const generate = jest.spyOn(DungeonGenerator, 'generate').mockReturnValue(dungeon);
const previousLevels = process.env.DUNGEON_LEVELS;
process.env.DUNGEON_LEVELS = '1';
try {
const game = new Game('fallback-spawn', { id: 1 }, { width: 7, height: 7, cryptoType: 'XMR' });
expect(game.monster.getState()).toMatchObject({ x: 5, y: 4 });
expect(game.dungeon.map[game.monster.y][game.monster.x]).toBe(FL);
expect(game.monster.isAt(game.player.x, game.player.y)).toBe(false);
expect(game.monster.isAt(game.dungeon.exit[0], game.dungeon.exit[1])).toBe(false);
expect(game.monster.isAt(game.dungeon.treasure[0], game.dungeon.treasure[1])).toBe(false);
expect(DungeonGenerator.layoutFingerprint(game.dungeon)).toBe(generatedFingerprint);
} finally {
generate.mockRestore();
if (previousLevels === undefined) delete process.env.DUNGEON_LEVELS;
else process.env.DUNGEON_LEVELS = previousLevels;
}
});
test('2-step monster adjacent to the player catches it (no phasing through)', () => {
const game = openGame();
game.difficultyConfig.monster.movesPerPlayerMove = 2;
game.difficultyConfig.monster.chaseAggressiveness = 1.0;
game.monsterMoveAccumulator = 0;
game.player.moveTo(3, 3);
game.monster.moveTo(3, 4); // directly adjacent
game.monster.lastKnownPlayerX = null;
game.monster.lastKnownPlayerY = null;
const res = game.moveMonster();
expect(game.gameState).toBe('lost');
expect(res.event).toBe('monster_caught');
});
test('single-step monster moving onto the player still catches (sanity)', () => {
const game = openGame();
game.difficultyConfig.monster.movesPerPlayerMove = 1;
game.difficultyConfig.monster.chaseAggressiveness = 1.0;
game.monsterMoveAccumulator = 0;
game.player.moveTo(3, 3);
game.monster.moveTo(3, 4);
game.moveMonster();
expect(game.gameState).toBe('lost');
});
});