diablo2-web/tests/maze-bounds.test.ts

198 lines
5.3 KiB
TypeScript

import { describe, expect, test } from 'vitest'
import type { Ds1, Ds1Cell } from '../src/formats/ds1'
import { generateMaze, type MazePiece, type MazePieceKind, type MazeRequest } from '../src/game/maze'
function makeMockDs1(width = 10, height = 10): Ds1 {
const cells: Ds1Cell[][] = []
for (let y = 0; y < height; y += 1) {
const row: Ds1Cell[] = []
for (let x = 0; x < width; x += 1) {
row.push({
walls: [{ prop1: 0, sequence: 0, style: 0, type: 0, unknown1: 0, unknown2: 0, hidden: false }],
floors: [{ prop1: 0, sequence: 0, style: 0, unknown1: 0, unknown2: 0, hidden: false }],
shadows: [],
substitutions: [],
})
}
cells.push(row)
}
return {
version: 18,
width,
height,
act: 1,
substitutionType: 0,
wallLayers: 1,
floorLayers: 1,
cells,
objects: [],
npcPathOffset: null,
}
}
function makeMockPiece(name: string, kind: MazePieceKind, sides: string): MazePiece {
return {
name,
kind,
sides,
levels: [makeMockDs1(10, 10)],
}
}
function makeCavePieceSet(): MazePiece[] {
const ds1 = makeMockDs1(10, 10)
const tokens = [
'NSEW',
'NSE', 'NSW', 'NEW', 'SEW',
'NS', 'EW', 'NE', 'NW', 'SE', 'SW',
'N', 'S', 'E', 'W',
]
const pieces: MazePiece[] = tokens.map(token => ({
name: `Act 1 - Cave ${token}`,
kind: 'room',
sides: token,
levels: [ds1],
}))
pieces.push({
name: 'Act 1 - Cave Prev NS',
kind: 'prev',
sides: 'NS',
levels: [ds1],
})
pieces.push({
name: 'Act 1 - Cave Prev EW',
kind: 'prev',
sides: 'EW',
levels: [ds1],
})
pieces.push({
name: 'Act 1 - Cave Down NS',
kind: 'down',
sides: 'NS',
levels: [ds1],
})
pieces.push({
name: 'Act 1 - Cave Down EW',
kind: 'down',
sides: 'EW',
levels: [ds1],
})
return pieces
}
describe('Maze Global Bounding Box Constraints (Issue #48)', () => {
test('generateMaze respects maxSectionsX and maxSectionsY limits', () => {
const pieces = makeCavePieceSet()
const request: MazeRequest = {
levelId: 2,
levelName: 'Act 1 - Cave 1',
levelTypeName: 'Act 1 - Cave',
sectionSize: 10,
sectionHeight: 10,
minRooms: 9,
merge: 0,
seed: 12345,
pieces,
maxSectionsX: 3,
maxSectionsY: 3,
}
const result = generateMaze(request)
expect(result.level.width).toBeLessThanOrEqual(30)
expect(result.level.height).toBeLessThanOrEqual(30)
expect(result.stats.maxCellsX).toBe(30)
expect(result.stats.maxCellsY).toBe(30)
expect(Number(result.stats.roomsPlaced)).toBeGreaterThanOrEqual(9)
})
test('generateMaze respects maxCellsX and maxCellsY directly', () => {
const pieces = makeCavePieceSet()
const request: MazeRequest = {
levelId: 2,
levelName: 'Act 1 - Cave 1',
levelTypeName: 'Act 1 - Cave',
sectionSize: 10,
sectionHeight: 10,
minRooms: 8,
merge: 500,
seed: 99999,
pieces,
maxCellsX: 30,
maxCellsY: 30,
}
const result = generateMaze(request)
expect(result.level.width).toBeLessThanOrEqual(30)
expect(result.level.height).toBeLessThanOrEqual(30)
expect(result.stats.maxCellsX).toBe(30)
expect(result.stats.maxCellsY).toBe(30)
})
test('forces maze to fold vertically when maxSectionsX is narrow (e.g. 2 sections wide)', () => {
const pieces = makeCavePieceSet()
const request: MazeRequest = {
levelId: 2,
levelName: 'Act 1 - Cave 1',
levelTypeName: 'Act 1 - Cave',
sectionSize: 10,
sectionHeight: 10,
minRooms: 8,
merge: 0,
seed: 42,
pieces,
maxSectionsX: 2,
maxSectionsY: 6,
}
const result = generateMaze(request)
expect(result.level.width).toBeLessThanOrEqual(20)
expect(result.level.height).toBeLessThanOrEqual(60)
expect(Number(result.stats.roomsPlaced)).toBeGreaterThanOrEqual(8)
expect(Number(result.stats.boundsRejected)).toBeGreaterThanOrEqual(1)
})
test('records boundsRejected when room growth attempts cross the configured boundary', () => {
const pieces = makeCavePieceSet()
const request: MazeRequest = {
levelId: 2,
levelName: 'Act 1 - Cave 1',
levelTypeName: 'Act 1 - Cave',
sectionSize: 10,
sectionHeight: 10,
minRooms: 9,
merge: 800,
seed: 54321,
pieces,
maxSectionsX: 3,
maxSectionsY: 3,
}
const result = generateMaze(request)
expect(result.stats.boundsRejected).toBeDefined()
expect(typeof result.stats.boundsRejected).toBe('number')
expect(result.level.width).toBeLessThanOrEqual(30)
expect(result.level.height).toBeLessThanOrEqual(30)
})
test('unconstrained maze allows wide sprawl when no bounds are specified', () => {
const pieces = makeCavePieceSet()
const request: MazeRequest = {
levelId: 2,
levelName: 'Act 1 - Cave 1',
levelTypeName: 'Act 1 - Cave',
sectionSize: 10,
sectionHeight: 10,
minRooms: 9,
merge: 0,
seed: 12345,
pieces,
}
const result = generateMaze(request)
expect(result.stats.maxCellsX).toBeUndefined()
expect(result.stats.maxCellsY).toBeUndefined()
expect(result.stats.boundsRejected).toBe(0)
expect(Number(result.stats.roomsPlaced)).toBeGreaterThanOrEqual(9)
})
})