feat(game): add persistent bush-wall breakthroughs
This commit is contained in:
@@ -346,6 +346,37 @@ static void test_victory_and_draw_window(void) {
|
||||
printf("game outward result window: PASS (200 ms draw boundary, 201 ms winner)\n");
|
||||
}
|
||||
|
||||
static void test_persistent_bush_wall_breakthrough(void) {
|
||||
maze32_maze_t maze;
|
||||
open_test_grid(&maze);
|
||||
maze.passages[index_of(2U, 2U)] &= (uint8_t)~MAZE32_PASSAGE_EAST;
|
||||
maze.passages[index_of(3U, 2U)] &= (uint8_t)~MAZE32_PASSAGE_WEST;
|
||||
maze32_game_t game = active_game(&maze);
|
||||
game.players[0].position = (maze32_cell_t){2U, 2U};
|
||||
|
||||
for (uint8_t hit = 1U; hit < MAZE32_GAME_BREAKTHROUGH_HITS; ++hit) {
|
||||
maze32_game_command_result_t result =
|
||||
move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, hit * 500U);
|
||||
assert(result.result == MAZE32_GAME_RESULT_BLOCKED);
|
||||
assert(result.breakthrough_hits == hit);
|
||||
assert(!result.breakthrough_opened);
|
||||
assert(game.players[0].position.x == 2U);
|
||||
}
|
||||
maze32_game_command_result_t result =
|
||||
move_player(&game, MAZE32_GAME_ROLE_PLAYER_ONE, MAZE32_GAME_DIRECTION_EAST, 2500U);
|
||||
assert(result.result == MAZE32_GAME_RESULT_ACCEPTED && result.breakthrough_opened);
|
||||
assert(result.breakthrough_hits == MAZE32_GAME_BREAKTHROUGH_HITS);
|
||||
assert(game.players[0].position.x == 3U);
|
||||
assert(game.opened_passages[index_of(2U, 2U)] & MAZE32_PASSAGE_EAST);
|
||||
assert(game.opened_passages[index_of(3U, 2U)] & MAZE32_PASSAGE_WEST);
|
||||
|
||||
game.players[1].position = (maze32_cell_t){3U, 2U};
|
||||
result = move_player(&game, MAZE32_GAME_ROLE_PLAYER_TWO, MAZE32_GAME_DIRECTION_WEST, 2600U);
|
||||
assert(result.result == MAZE32_GAME_RESULT_ACCEPTED);
|
||||
assert(game.players[1].position.x == 2U);
|
||||
printf("game persistent bush-wall breakthrough: PASS (five impacts, shared passage)\n");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_inventory_drop_and_sabotage();
|
||||
test_atomic_meeting_return();
|
||||
@@ -353,6 +384,7 @@ int main(void) {
|
||||
test_vision_artifact_duration();
|
||||
test_compass_and_role_projections();
|
||||
test_victory_and_draw_window();
|
||||
test_persistent_bush_wall_breakthrough();
|
||||
puts("game mechanics tests: PASS");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -165,7 +165,9 @@ test("playful feedback uses bounded maze-only effects and a top message rail", (
|
||||
assert.match(css, /\[data-reduced-motion="true"\] \.maze\.rate-limited \{ transition: none;/);
|
||||
assert.match(css, /\.message-rail \{ position: fixed; top:/);
|
||||
assert.match(script, /moveDirections: new Map\(\)/);
|
||||
assert.match(script, /if \(message\.payload\.code === "blocked"\) \{ showImpact\(direction\); feedback\(false\); return; \}/);
|
||||
assert.match(script, /if \(message\.payload\.code === "blocked"\) \{ showImpact\(direction, message\.payload\.breakthroughHits\); feedback\(false\); return; \}/);
|
||||
assert.match(css, /\.cell\.bush-impact/);
|
||||
assert.match(css, /\.cell\.trampled-n::before/);
|
||||
assert.match(script, /if \(message\.payload\.code === "rate_limited"\) \{ showRateLimit\(\); feedback\(false\); return; \}/);
|
||||
assert.doesNotMatch(script, /Там стена\. Очень убедительная\.|Слишком быстро! Сервер просит немного меньше турбо\./);
|
||||
});
|
||||
@@ -243,17 +245,17 @@ test("command envelopes contain intentions rather than client-owned state", () =
|
||||
|
||||
test("chunk assembly replaces projection cells without explored-map memory", () => {
|
||||
const assembler = new player.SnapshotAssembler();
|
||||
const second = {v: 1, type: "view", seq: 20, payload: {role: "player1", width: 8, height: 8, chunk: 1, chunks: 2, cells: [[10, 15]], objects: []}};
|
||||
const first = {v: 1, type: "view", seq: 20, payload: {role: "player1", phase: "active", result: "none", width: 8, height: 8, self: 9, inventory: null, visionMoves: 0, hint: "none", chunk: 0, chunks: 2, cells: [[9, 15]], objects: [["key", 0, 0, 10]]}};
|
||||
const second = {v: 1, type: "view", seq: 20, payload: {role: "player1", width: 8, height: 8, chunk: 1, chunks: 2, cells: [[10, 15, 0]], objects: []}};
|
||||
const first = {v: 1, type: "view", seq: 20, payload: {role: "player1", phase: "active", result: "none", width: 8, height: 8, self: 9, inventory: null, visionMoves: 0, hint: "none", chunk: 0, chunks: 2, cells: [[9, 15, 2]], objects: [["key", 0, 0, 10]]}};
|
||||
assert.equal(assembler.accept(second), null);
|
||||
const complete = assembler.accept(first);
|
||||
assert.deepEqual(player.projectedCellIndices(complete), [9, 10]);
|
||||
|
||||
const replacement = assembler.accept({v: 1, type: "view", seq: 21, payload: {role: "player1", phase: "active", result: "none", width: 8, height: 8, self: 18, inventory: 0, visionMoves: 9, hint: "ne", chunk: 0, chunks: 1, cells: [[18, 15]], objects: []}});
|
||||
const replacement = assembler.accept({v: 1, type: "view", seq: 21, payload: {role: "player1", phase: "active", result: "none", width: 8, height: 8, self: 18, inventory: 0, visionMoves: 9, hint: "ne", chunk: 0, chunks: 1, cells: [[18, 15, 0]], objects: []}});
|
||||
assert.deepEqual(player.projectedCellIndices(replacement), [18]);
|
||||
assert.equal(replacement.cells.some(cell => cell[0] === 9 || cell[0] === 10), false);
|
||||
assert.equal(assembler.accept({v: 1, type: "view", seq: 22, payload: {chunk: 0, chunks: player.MAX_VIEW_CHUNKS + 1, cells: [], objects: []}}), null);
|
||||
assert.equal(player.snapshotIsValid({width: 32, height: 32, cells: [[1023, 15]], objects: []}), true);
|
||||
assert.equal(player.snapshotIsValid({width: 32, height: 32, cells: [[1023, 15, 0]], objects: []}), true);
|
||||
assert.equal(player.snapshotIsValid({width: 33, height: 32, cells: [], objects: []}), false);
|
||||
assert.equal(player.snapshotIsValid({width: 32, height: 33, cells: [], objects: []}), false);
|
||||
});
|
||||
@@ -344,7 +346,7 @@ test("observer protocol exposes no mutation control path", () => {
|
||||
const html = fs.readFileSync(path.join(project, "web/index.html"), "utf8");
|
||||
const observerSection = html.match(/<section class="screen observer-screen"[\s\S]*?<\/section>/)[0];
|
||||
assert.doesNotMatch(observerSection, /data-direction|drop-key|start-match/);
|
||||
const observer = {v: 1, type: "view", seq: 30, payload: {role: "observer", phase: "active", result: "none", width: 2, height: 2, players: [{cell: 0, key: null, exited: 0, connected: 1}, {cell: 3, key: 1, exited: 0, connected: 1}], chunk: 0, chunks: 1, cells: [[0, 6], [1, 12], [2, 3], [3, 9]], objects: []}};
|
||||
const observer = {v: 1, type: "view", seq: 30, payload: {role: "observer", phase: "active", result: "none", width: 2, height: 2, players: [{cell: 0, key: null, exited: 0, connected: 1}, {cell: 3, key: 1, exited: 0, connected: 1}], chunk: 0, chunks: 1, cells: [[0, 6, 0], [1, 12, 0], [2, 3, 0], [3, 9, 0]], objects: []}};
|
||||
const snapshot = new player.SnapshotAssembler().accept(observer);
|
||||
assert.equal(snapshot.role, "observer");
|
||||
assert.deepEqual(snapshot.players.map(item => item.cell), [0, 3]);
|
||||
|
||||
Reference in New Issue
Block a user