UNPKG

shellquest

Version:

Terminal-based procedurally generated dungeon crawler

1,035 lines (928 loc) 35.2 kB
import type {PRNG} from 'seedrandom'; import type {LevelTile} from '../types.ts'; export class FenceGenerator { constructor( private tiles: LevelTile[][], private width: number, private height: number, ) {} generateHouseFence( houseX: number, houseY: number, houseWidth: number, houseHeight: number, rng: PRNG, ): void { const pattern = Math.floor(rng() * 4); switch (pattern) { case 0: this.generateYardFence(houseX, houseY, houseWidth, houseHeight, rng); break; case 1: this.generateGardenFence(houseX, houseY, houseWidth, houseHeight, rng); break; case 2: this.generatePropertyLineFence(houseX, houseY, houseWidth, houseHeight, rng); break; case 3: this.generateCornerFence(houseX, houseY, houseWidth, houseHeight, rng); break; } } private generateYardFence( houseX: number, houseY: number, houseWidth: number, houseHeight: number, rng: PRNG, ): void { const side = rng() < 0.5 ? 'front' : 'side'; if (side === 'front' && houseY + houseHeight + 8 < this.height) { const yardDepth = Math.floor(rng() * 3) + 4; const fenceY = houseY + houseHeight + yardDepth; for (let dy = 0; dy < yardDepth; dy++) { const y = houseY + houseHeight + dy; const x = houseX - 1; if (x >= 0 && !this.tiles[y][x].topTile) { if (dy === 0) { this.tiles[y][x].topTile = 'fence-corner-top-right'; } else if (dy === yardDepth - 1) { this.tiles[y][x].topTile = 'fence-corner-bottom-left'; } else { this.tiles[y][x].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y][x].solid = true; } } const gatePos = Math.floor(houseWidth / 2); for (let dx = 0; dx < houseWidth + 2; dx++) { const x = houseX - 1 + dx; if (x >= 0 && x < this.width && !this.tiles[fenceY][x].topTile) { if (dx === gatePos || dx === gatePos + 1) continue; if (dx === 0) { } else if (dx === houseWidth + 1) { this.tiles[fenceY][x].topTile = 'fence-corner-bottom-right'; } else if (dx === gatePos - 1) { this.tiles[fenceY][x].topTile = 'fence-horizontal-right'; } else if (dx === gatePos + 2) { this.tiles[fenceY][x].topTile = 'fence-horizontal-left'; } else { this.tiles[fenceY][x].topTile = dx % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } if (this.tiles[fenceY][x].topTile) { this.tiles[fenceY][x].solid = true; } } } for (let dy = 0; dy < yardDepth; dy++) { const y = houseY + houseHeight + dy; const x = houseX + houseWidth; if (x < this.width && !this.tiles[y][x].topTile) { if (dy === 0) { this.tiles[y][x].topTile = 'fence-corner-top-left'; } else if (dy === yardDepth - 1) { } else { this.tiles[y][x].topTile = 'fence-vertical-middle-withbar'; } if (this.tiles[y][x].topTile) { this.tiles[y][x].solid = true; } } } } else if (side === 'side') { const sideChoice = rng() < 0.5 ? 'left' : 'right'; const fenceLength = Math.floor(rng() * 4) + 6; if (sideChoice === 'left' && houseX > fenceLength) { const startX = houseX - fenceLength; const attachY = houseY + Math.floor(houseHeight / 2); for (let i = 0; i < fenceLength; i++) { if (!this.tiles[attachY][startX + i].topTile) { if (i === 0) { this.tiles[attachY][startX + i].topTile = 'fence-horizontal-left'; } else if (i === fenceLength - 1) { this.tiles[attachY][startX + i].topTile = 'fence-corner-top-right'; } else { this.tiles[attachY][startX + i].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[attachY][startX + i].solid = true; } } const returnLength = Math.floor(rng() * 3) + 3; for (let j = 1; j < returnLength; j++) { const y = attachY + j; if (y < this.height && !this.tiles[y][houseX - 1].topTile) { if (j === returnLength - 1) { this.tiles[y][houseX - 1].topTile = 'fence-vertical-bottom'; } else { this.tiles[y][houseX - 1].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y][houseX - 1].solid = true; } } } } } private generateGardenFence( houseX: number, houseY: number, houseWidth: number, houseHeight: number, rng: PRNG, ): void { const gardenWidth = Math.floor(rng() * 2) + 4; const gardenHeight = Math.floor(rng() * 2) + 3; const side = rng() < 0.5 ? 'left' : 'right'; let gardenX: number, gardenY: number; if (side === 'left') { gardenX = houseX - gardenWidth - 2; gardenY = houseY + houseHeight - gardenHeight; } else { gardenX = houseX + houseWidth + 2; gardenY = houseY + houseHeight - gardenHeight; } if ( gardenX < 0 || gardenX + gardenWidth >= this.width || gardenY < 0 || gardenY + gardenHeight >= this.height ) return; for (let dy = 0; dy < gardenHeight; dy++) { for (let dx = 0; dx < gardenWidth; dx++) { if (this.tiles[gardenY + dy][gardenX + dx].topTile) return; } } this.placeRectangularFence(gardenX, gardenY, gardenWidth, gardenHeight, true, rng); } private generatePropertyLineFence( houseX: number, houseY: number, houseWidth: number, houseHeight: number, rng: PRNG, ): void { const side = Math.floor(rng() * 4); switch (side) { case 0: if (houseX > 8) { const fenceX = houseX - 6; const startY = Math.max(0, houseY - 2); const endY = Math.min(this.height - 1, houseY + houseHeight + 4); for (let y = startY; y < endY; y++) { if (!this.tiles[y][fenceX].topTile) { if (y === startY) { this.tiles[y][fenceX].topTile = 'fence-vertical-top'; } else if (y === endY - 1) { this.tiles[y][fenceX].topTile = 'fence-vertical-bottom'; } else { this.tiles[y][fenceX].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y][fenceX].solid = true; } } } break; case 1: if (houseX + houseWidth + 8 < this.width) { const fenceX = houseX + houseWidth + 6; const startY = Math.max(0, houseY - 2); const endY = Math.min(this.height - 1, houseY + houseHeight + 4); for (let y = startY; y < endY; y++) { if (!this.tiles[y][fenceX].topTile) { if (y === startY) { this.tiles[y][fenceX].topTile = 'fence-vertical-top'; } else if (y === endY - 1) { this.tiles[y][fenceX].topTile = 'fence-vertical-bottom'; } else { this.tiles[y][fenceX].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y][fenceX].solid = true; } } } break; case 2: if (houseY > 8) { const fenceY = houseY - 4; const startX = Math.max(0, houseX - 3); const endX = Math.min(this.width - 1, houseX + houseWidth + 3); for (let x = startX; x < endX; x++) { if (!this.tiles[fenceY][x].topTile) { if (x === startX) { this.tiles[fenceY][x].topTile = 'fence-horizontal-left'; } else if (x === endX - 1) { this.tiles[fenceY][x].topTile = 'fence-horizontal-right'; } else { this.tiles[fenceY][x].topTile = x % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[fenceY][x].solid = true; } } } break; } } private generateCornerFence( houseX: number, houseY: number, houseWidth: number, houseHeight: number, rng: PRNG, ): void { const corner = Math.floor(rng() * 4); const length1 = Math.floor(rng() * 3) + 4; const length2 = Math.floor(rng() * 3) + 4; let startX: number, startY: number; let dir1X: number, dir1Y: number; let dir2X: number, dir2Y: number; switch (corner) { case 0: startX = houseX - 2; startY = houseY - 2; dir1X = -1; dir1Y = 0; dir2X = 0; dir2Y = 1; break; case 1: startX = houseX + houseWidth + 1; startY = houseY - 2; dir1X = 1; dir1Y = 0; dir2X = 0; dir2Y = 1; break; case 2: startX = houseX - 2; startY = houseY + houseHeight + 1; dir1X = -1; dir1Y = 0; dir2X = 0; dir2Y = -1; break; default: startX = houseX + houseWidth + 1; startY = houseY + houseHeight + 1; dir1X = 1; dir1Y = 0; dir2X = 0; dir2Y = -1; break; } let canPlace = true; for (let i = 0; i < length1; i++) { const x = startX + dir1X * i; const y = startY + dir1Y * i; if (x < 0 || x >= this.width || y < 0 || y >= this.height || this.tiles[y][x].topTile) { canPlace = false; break; } } if (!canPlace) return; for (let i = 0; i < length1; i++) { const x = startX + dir1X * i; const y = startY + dir1Y * i; if (dir1Y === 0) { if (i === 0 && dir1X < 0) { this.tiles[y][x].topTile = 'fence-horizontal-right'; } else if (i === 0 && dir1X > 0) { this.tiles[y][x].topTile = 'fence-horizontal-left'; } else if (i === length1 - 1) { if (dir1X < 0 && dir2Y > 0) this.tiles[y][x].topTile = 'fence-corner-bottom-left'; else if (dir1X < 0 && dir2Y < 0) this.tiles[y][x].topTile = 'fence-corner-top-left'; else if (dir1X > 0 && dir2Y > 0) this.tiles[y][x].topTile = 'fence-corner-bottom-right'; else if (dir1X > 0 && dir2Y < 0) this.tiles[y][x].topTile = 'fence-corner-top-right'; } else { this.tiles[y][x].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } } else { if (i === 0 && dir1Y < 0) { this.tiles[y][x].topTile = 'fence-vertical-bottom'; } else if (i === 0 && dir1Y > 0) { this.tiles[y][x].topTile = 'fence-vertical-top'; } else if (i === length1 - 1) { } else { this.tiles[y][x].topTile = 'fence-vertical-middle-withbar'; } } this.tiles[y][x].solid = true; } const cornerX = startX + dir1X * (length1 - 1); const cornerY = startY + dir1Y * (length1 - 1); for (let i = 1; i < length2; i++) { const x = cornerX + dir2X * i; const y = cornerY + dir2Y * i; if (x >= 0 && x < this.width && y >= 0 && y < this.height && !this.tiles[y][x].topTile) { if (dir2Y === 0) { if (i === length2 - 1 && dir2X < 0) { this.tiles[y][x].topTile = 'fence-horizontal-left'; } else if (i === length2 - 1 && dir2X > 0) { this.tiles[y][x].topTile = 'fence-horizontal-right'; } else { this.tiles[y][x].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } } else { if (i === length2 - 1 && dir2Y < 0) { this.tiles[y][x].topTile = 'fence-vertical-top'; } else if (i === length2 - 1 && dir2Y > 0) { this.tiles[y][x].topTile = 'fence-vertical-bottom'; } else { this.tiles[y][x].topTile = 'fence-vertical-middle-withbar'; } } this.tiles[y][x].solid = true; } } } placeRectangularFence( x: number, y: number, width: number, height: number, hasGate: boolean, rng: PRNG, ): void { for (let dx = 0; dx < width; dx++) { if (!this.tiles[y][x + dx].topTile) { if (dx === 0) { this.tiles[y][x + dx].topTile = 'fence-corner-top-left'; } else if (dx === width - 1) { this.tiles[y][x + dx].topTile = 'fence-corner-top-right'; } else { this.tiles[y][x + dx].topTile = dx % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[y][x + dx].solid = true; } } for (let dy = 1; dy < height - 1; dy++) { if (!this.tiles[y + dy][x].topTile) { this.tiles[y + dy][x].topTile = 'fence-vertical-left-withbar'; this.tiles[y + dy][x].solid = true; } if (!this.tiles[y + dy][x + width - 1].topTile) { this.tiles[y + dy][x + width - 1].topTile = 'fence-vertical-right-withbar'; this.tiles[y + dy][x + width - 1].solid = true; } } const gatePos = hasGate ? Math.floor(width / 2) : -1; for (let dx = 0; dx < width; dx++) { if (hasGate && (dx === gatePos || (width > 3 && dx === gatePos - 1))) continue; if (!this.tiles[y + height - 1][x + dx].topTile) { if (dx === 0) { this.tiles[y + height - 1][x + dx].topTile = 'fence-corner-bottom-left'; } else if (dx === width - 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-corner-bottom-right'; } else if (hasGate && dx === gatePos - 2) { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-right'; } else if (hasGate && dx === gatePos + 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-left'; } else { this.tiles[y + height - 1][x + dx].topTile = dx % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[y + height - 1][x + dx].solid = true; } } } generateTownFences(rng: PRNG): void { const fenceGroups = 25; const placedFences: Array<{x: number; y: number; w: number; h: number}> = []; for (let i = 0; i < 5; i++) { const isHorizontal = rng() < 0.5; if (isHorizontal) { const y = Math.floor(rng() * (this.height - 20)) + 10; const startX = Math.floor(rng() * 20) + 5; const length = Math.floor(rng() * 15) + 10; this.placeLongFenceLine(startX, y, length, true, rng); placedFences.push({x: startX, y: y, w: length, h: 1}); } else { const x = Math.floor(rng() * (this.width - 20)) + 10; const startY = Math.floor(rng() * 20) + 5; const length = Math.floor(rng() * 15) + 10; this.placeLongFenceLine(x, startY, length, false, rng); placedFences.push({x: x, y: startY, w: 1, h: length}); } } for (let i = 0; i < fenceGroups; i++) { let x: number, y: number; if (rng() < 0.3 && placedFences.length > 0) { const nearFence = placedFences[Math.floor(rng() * placedFences.length)]; x = nearFence.x + Math.floor(rng() * 20) - 10; y = nearFence.y + Math.floor(rng() * 20) - 10; } else { x = Math.floor(rng() * (this.width - 10)) + 5; y = Math.floor(rng() * (this.height - 10)) + 5; } x = Math.max(2, Math.min(this.width - 15, x)); y = Math.max(2, Math.min(this.height - 15, y)); const rand = rng(); if (rand < 0.15) { this.placeLShapedFence(x, y, rng); } else if (rand < 0.3) { this.placeSmallEnclosure(x, y, rng); } else if (rand < 0.45) { this.placeZigzagFence(x, y, rng); } else if (rand < 0.6) { this.placeUShapedFence(x, y, rng); } else if (rand < 0.75) { this.placeDiagonalFence(x, y, rng); } else if (rand < 0.85) { this.placeConnectedFences(x, y, rng); } else { this.placeStraightFence(x, y, rng); } } for (let i = 0; i < 10; i++) { const x = Math.floor(rng() * (this.width - 5)) + 2; const y = Math.floor(rng() * (this.height - 5)) + 2; this.placeMiniFence(x, y, rng); } } private placeLongFenceLine( startX: number, startY: number, length: number, isHorizontal: boolean, rng: PRNG, ): void { if (isHorizontal) { for (let i = 0; i < length; i++) { const x = startX + i; if (x >= this.width) break; if (this.tiles[startY][x].topTile) { if (rng() < 0.3) { const dir = rng() < 0.5 ? -1 : 1; for (let j = 1; j <= 2; j++) { const y = startY + j * dir; if (y >= 0 && y < this.height && !this.tiles[y][x].topTile) { this.tiles[y][x].topTile = 'fence-vertical-middle-withbar'; this.tiles[y][x].solid = true; } } } continue; } if (i === 0) { this.tiles[startY][x].topTile = 'fence-horizontal-left'; } else if (i === length - 1) { this.tiles[startY][x].topTile = 'fence-horizontal-right'; } else { if (rng() < 0.1 && i > 2 && i < length - 2) { continue; } this.tiles[startY][x].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[startY][x].solid = true; } } else { for (let i = 0; i < length; i++) { const y = startY + i; if (y >= this.height) break; if (this.tiles[y][startX].topTile) { if (rng() < 0.3) { const dir = rng() < 0.5 ? -1 : 1; for (let j = 1; j <= 2; j++) { const x = startX + j * dir; if (x >= 0 && x < this.width && !this.tiles[y][x].topTile) { this.tiles[y][x].topTile = 'fence-horizontal-withbar'; this.tiles[y][x].solid = true; } } } continue; } if (i === 0) { this.tiles[y][startX].topTile = 'fence-vertical-top'; } else if (i === length - 1) { this.tiles[y][startX].topTile = 'fence-vertical-bottom'; } else { if (rng() < 0.1 && i > 2 && i < length - 2) { continue; } this.tiles[y][startX].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y][startX].solid = true; } } } private placeUShapedFence(x: number, y: number, rng: PRNG): void { const width = Math.floor(rng() * 3) + 4; const height = Math.floor(rng() * 3) + 3; const openSide = Math.floor(rng() * 4); if (x + width >= this.width || y + height >= this.height) return; for (let dy = 0; dy < height; dy++) { for (let dx = 0; dx < width; dx++) { if (this.tiles[y + dy][x + dx].topTile) return; } } if (openSide !== 0) { for (let dx = 0; dx < width; dx++) { if (!this.tiles[y][x + dx].topTile) { if (dx === 0) { this.tiles[y][x + dx].topTile = 'fence-corner-top-left'; } else if (dx === width - 1) { this.tiles[y][x + dx].topTile = 'fence-corner-top-right'; } else { this.tiles[y][x + dx].topTile = dx % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[y][x + dx].solid = true; } } } if (openSide !== 2) { for (let dx = 0; dx < width; dx++) { if (!this.tiles[y + height - 1][x + dx].topTile) { if (dx === 0 && openSide !== 3) { this.tiles[y + height - 1][x + dx].topTile = 'fence-corner-bottom-left'; } else if (dx === width - 1 && openSide !== 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-corner-bottom-right'; } else if (openSide === 3 && dx === 0) { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-left'; } else if (openSide === 1 && dx === width - 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-right'; } else { this.tiles[y + height - 1][x + dx].topTile = dx % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[y + height - 1][x + dx].solid = true; } } } if (openSide !== 3) { for (let dy = 1; dy < height - 1; dy++) { if (!this.tiles[y + dy][x].topTile) { this.tiles[y + dy][x].topTile = 'fence-vertical-left-withbar'; this.tiles[y + dy][x].solid = true; } } } if (openSide !== 1) { for (let dy = 1; dy < height - 1; dy++) { if (!this.tiles[y + dy][x + width - 1].topTile) { this.tiles[y + dy][x + width - 1].topTile = 'fence-vertical-right-withbar'; this.tiles[y + dy][x + width - 1].solid = true; } } } } private placeDiagonalFence(x: number, y: number, rng: PRNG): void { const length = Math.floor(rng() * 4) + 3; const goingRight = rng() < 0.5; const goingDown = rng() < 0.5; let currentX = x; let currentY = y; for (let i = 0; i < length; i++) { if (currentX < this.width && currentY < this.height && currentX >= 0 && currentY >= 0) { if (!this.tiles[currentY][currentX].topTile) { this.tiles[currentY][currentX].topTile = 'fence-horizontal-withbar'; this.tiles[currentY][currentX].solid = true; } if (i < length - 1) { const nextY = currentY + (goingDown ? 1 : -1); if (nextY >= 0 && nextY < this.height && !this.tiles[nextY][currentX].topTile) { this.tiles[nextY][currentX].topTile = 'fence-vertical-middle-withbar'; this.tiles[nextY][currentX].solid = true; } } } currentX += goingRight ? 1 : -1; currentY += goingDown ? 1 : -1; } } private placeConnectedFences(x: number, y: number, rng: PRNG): void { const segments = Math.floor(rng() * 2) + 2; let currentX = x; let currentY = y; for (let seg = 0; seg < segments; seg++) { const length = Math.floor(rng() * 4) + 3; const direction = Math.floor(rng() * 4); for (let i = 0; i < length; i++) { let nextX = currentX; let nextY = currentY; switch (direction) { case 0: nextY--; break; case 1: nextX++; break; case 2: nextY++; break; case 3: nextX--; break; } if (nextX < 0 || nextX >= this.width || nextY < 0 || nextY >= this.height) break; if (this.tiles[nextY][nextX].topTile) break; if (direction === 0 || direction === 2) { if (i === 0 && seg === 0) { this.tiles[nextY][nextX].topTile = direction === 0 ? 'fence-vertical-bottom' : 'fence-vertical-top'; } else if (i === length - 1 && seg === segments - 1) { this.tiles[nextY][nextX].topTile = direction === 0 ? 'fence-vertical-top' : 'fence-vertical-bottom'; } else { this.tiles[nextY][nextX].topTile = 'fence-vertical-middle-withbar'; } } else { if (i === 0 && seg === 0) { this.tiles[nextY][nextX].topTile = direction === 3 ? 'fence-horizontal-right' : 'fence-horizontal-left'; } else if (i === length - 1 && seg === segments - 1) { this.tiles[nextY][nextX].topTile = direction === 3 ? 'fence-horizontal-left' : 'fence-horizontal-right'; } else { this.tiles[nextY][nextX].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } } this.tiles[nextY][nextX].solid = true; currentX = nextX; currentY = nextY; } } } private placeMiniFence(x: number, y: number, rng: PRNG): void { const length = Math.floor(rng() * 2) + 2; const isHorizontal = rng() < 0.5; if (isHorizontal) { for (let i = 0; i < length; i++) { if (x + i >= this.width || this.tiles[y][x + i].topTile) return; } for (let i = 0; i < length; i++) { if (i === 0) { this.tiles[y][x + i].topTile = 'fence-horizontal-left'; } else if (i === length - 1) { this.tiles[y][x + i].topTile = 'fence-horizontal-right'; } else { this.tiles[y][x + i].topTile = 'fence-horizontal-withbar'; } this.tiles[y][x + i].solid = true; } } else { for (let i = 0; i < length; i++) { if (y + i >= this.height || this.tiles[y + i][x].topTile) return; } for (let i = 0; i < length; i++) { if (i === 0) { this.tiles[y + i][x].topTile = 'fence-vertical-top'; } else if (i === length - 1) { this.tiles[y + i][x].topTile = 'fence-vertical-bottom'; } else { this.tiles[y + i][x].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y + i][x].solid = true; } } } private placeLShapedFence(x: number, y: number, rng: PRNG): void { const length1 = Math.floor(rng() * 4) + 4; const length2 = Math.floor(rng() * 4) + 4; let canPlace = true; for (let i = 0; i < length1; i++) { if (x + i >= this.width || this.tiles[y][x + i].topTile) { canPlace = false; break; } } if (canPlace) { for (let i = 0; i < length1; i++) { if (i === 0) { this.tiles[y][x + i].topTile = 'fence-horizontal-left'; } else if (i === length1 - 1) { this.tiles[y][x + i].topTile = 'fence-corner-top-right'; } else { this.tiles[y][x + i].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[y][x + i].solid = true; } for (let j = 1; j < length2; j++) { if (y + j < this.height && !this.tiles[y + j][x + length1 - 1].topTile) { if (j === length2 - 1) { this.tiles[y + j][x + length1 - 1].topTile = 'fence-vertical-bottom'; } else { this.tiles[y + j][x + length1 - 1].topTile = 'fence-vertical-right-withbar'; } this.tiles[y + j][x + length1 - 1].solid = true; } } } } private placeStraightFence(x: number, y: number, rng: PRNG): void { const isHorizontal = rng() < 0.5; const length = Math.floor(rng() * 6) + 5; if (isHorizontal) { let canPlace = true; for (let i = 0; i < length; i++) { if (x + i >= this.width || this.tiles[y][x + i].topTile) { canPlace = false; break; } } if (canPlace) { for (let i = 0; i < length; i++) { if (i === 0) { this.tiles[y][x + i].topTile = 'fence-horizontal-left'; } else if (i === length - 1) { this.tiles[y][x + i].topTile = 'fence-horizontal-right'; } else { this.tiles[y][x + i].topTile = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } this.tiles[y][x + i].solid = true; } } } else { let canPlace = true; for (let i = 0; i < length; i++) { if (y + i >= this.height || x < 0 || x >= this.width || this.tiles[y + i]?.[x].topTile) { canPlace = false; break; } } if (canPlace) { for (let i = 0; i < length; i++) { if (i === 0) { this.tiles[y + i][x].topTile = 'fence-vertical-top'; } else if (i === length - 1) { this.tiles[y + i][x].topTile = 'fence-vertical-bottom'; } else { this.tiles[y + i][x].topTile = 'fence-vertical-middle-withbar'; } this.tiles[y + i][x].solid = true; } } } } private placeSmallEnclosure(x: number, y: number, rng: PRNG): void { const width = Math.floor(rng() * 2) + 3; const height = Math.floor(rng() * 2) + 3; let canPlace = true; for (let dy = 0; dy < height; dy++) { for (let dx = 0; dx < width; dx++) { if (x + dx >= this.width || y + dy >= this.height || this.tiles[y + dy][x + dx].topTile) { canPlace = false; break; } } if (!canPlace) break; } if (!canPlace) return; for (let dx = 0; dx < width; dx++) { if (dx === 0) { this.tiles[y][x + dx].topTile = 'fence-corner-top-left'; } else if (dx === width - 1) { this.tiles[y][x + dx].topTile = 'fence-corner-top-right'; } else { this.tiles[y][x + dx].topTile = 'fence-horizontal-withbar'; } this.tiles[y][x + dx].solid = true; } for (let dy = 1; dy < height - 1; dy++) { this.tiles[y + dy][x].topTile = 'fence-vertical-left-withbar'; this.tiles[y + dy][x].solid = true; this.tiles[y + dy][x + width - 1].topTile = 'fence-vertical-right-withbar'; this.tiles[y + dy][x + width - 1].solid = true; } const gapPos = Math.floor(width / 2); for (let dx = 0; dx < width; dx++) { if (dx === gapPos) continue; if (dx === 0) { this.tiles[y + height - 1][x + dx].topTile = 'fence-corner-bottom-left'; } else if (dx === width - 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-corner-bottom-right'; } else if (dx === gapPos - 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-right'; } else if (dx === gapPos + 1) { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-left'; } else { this.tiles[y + height - 1][x + dx].topTile = 'fence-horizontal-withbar'; } if (this.tiles[y + height - 1][x + dx].topTile) { this.tiles[y + height - 1][x + dx].solid = true; } } } private placeZigzagFence(x: number, y: number, rng: PRNG): void { const segments = Math.floor(rng() * 3) + 3; let currentX = x; let currentY = y; let lastDirection = -1; for (let seg = 0; seg < segments; seg++) { const length = Math.floor(rng() * 4) + 3; let direction: number; if (lastDirection === -1) { direction = Math.floor(rng() * 4); } else { if (lastDirection === 0 || lastDirection === 2) { direction = rng() < 0.5 ? 1 : 3; } else { direction = rng() < 0.5 ? 0 : 2; } } let canPlace = true; for (let i = 0; i < length; i++) { let checkX = currentX; let checkY = currentY; switch (direction) { case 0: checkY -= i; break; case 1: checkX += i; break; case 2: checkY += i; break; case 3: checkX -= i; break; } if ( checkX < 0 || checkX >= this.width || checkY < 0 || checkY >= this.height || this.tiles[checkY][checkX].topTile ) { canPlace = false; break; } } if (!canPlace) break; for (let i = 0; i < length; i++) { let tileX = currentX; let tileY = currentY; switch (direction) { case 0: tileY -= i; break; case 1: tileX += i; break; case 2: tileY += i; break; case 3: tileX -= i; break; } let tileName: string; if (i === 0 && seg > 0) { if (lastDirection === 0 && direction === 1) tileName = 'fence-corner-bottom-right'; else if (lastDirection === 0 && direction === 3) tileName = 'fence-corner-bottom-left'; else if (lastDirection === 2 && direction === 1) tileName = 'fence-corner-top-right'; else if (lastDirection === 2 && direction === 3) tileName = 'fence-corner-top-left'; else if (lastDirection === 1 && direction === 0) tileName = 'fence-corner-top-left'; else if (lastDirection === 1 && direction === 2) tileName = 'fence-corner-bottom-left'; else if (lastDirection === 3 && direction === 0) tileName = 'fence-corner-top-right'; else if (lastDirection === 3 && direction === 2) tileName = 'fence-corner-bottom-right'; else tileName = 'fence-horizontal-withbar'; } else if (direction === 0 || direction === 2) { if (i === 0 && seg === 0) { tileName = direction === 0 ? 'fence-vertical-bottom' : 'fence-vertical-top'; } else if (i === length - 1 && seg === segments - 1) { tileName = direction === 0 ? 'fence-vertical-top' : 'fence-vertical-bottom'; } else { tileName = 'fence-vertical-middle-withbar'; } } else { if (i === 0 && seg === 0) { tileName = direction === 3 ? 'fence-horizontal-right' : 'fence-horizontal-left'; } else if (i === length - 1 && seg === segments - 1) { tileName = direction === 3 ? 'fence-horizontal-left' : 'fence-horizontal-right'; } else { tileName = i % 2 === 0 ? 'fence-horizontal-withbar' : 'fence-horizontal-middle-nobar'; } } this.tiles[tileY][tileX].topTile = tileName; this.tiles[tileY][tileX].solid = true; } switch (direction) { case 0: currentY -= length - 1; break; case 1: currentX += length - 1; break; case 2: currentY += length - 1; break; case 3: currentX -= length - 1; break; } lastDirection = direction; } } }