Compare commits
	
		
			14 Commits
		
	
	
		
			main
			...
			fix-mapgen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3b1c0af916 | |||
| 70c2fcc491 | |||
| b4aa9329ad | |||
| ec2e21c712 | |||
| 0b7d447c5b | |||
| 025b1c9333 | |||
| e2aa4a3ee7 | |||
| b302538ade | |||
| 764d1e4892 | |||
| 4117608073 | |||
| ba151a76fd | |||
| 898a93d8e5 | |||
| 234a42b1e3 | |||
| 8bf7f0f151 | 
| @@ -111,6 +111,15 @@ export class Point { | ||||
|     let dy = other.y - this.y; | ||||
|     return Math.sqrt(dx * dx + dy * dy); | ||||
|   } | ||||
|  | ||||
|   neighbors(): Point[] { | ||||
|     return [ | ||||
|       new Point(this.x, this.y - 1), | ||||
|       new Point(this.x - 1, this.y), | ||||
|       new Point(this.x, this.y + 1), | ||||
|       new Point(this.x + 1, this.y), | ||||
|     ]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class Size { | ||||
| @@ -264,19 +273,29 @@ export class Grid<T> { | ||||
|     return new Grid(this.size, (xy) => cbCell(this.get(xy), xy)); | ||||
|   } | ||||
|  | ||||
|   #checkPosition(position: Point) { | ||||
|     if ( | ||||
|   #invalidPosition(position: Point): boolean { | ||||
|     return ( | ||||
|       position.x < 0 || | ||||
|       position.x >= this.size.w || | ||||
|       Math.floor(position.x) != position.x || | ||||
|       position.y < 0 || | ||||
|       position.y >= this.size.h || | ||||
|       Math.floor(position.y) != position.y | ||||
|     ) { | ||||
|     ); | ||||
|   } | ||||
|   #checkPosition(position: Point) { | ||||
|     if (this.#invalidPosition(position)) { | ||||
|       throw new Error(`invalid position for ${this.size}: ${position}`); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   maybeGet(position: Point): T | null { | ||||
|     if (this.#invalidPosition(position)) { | ||||
|       return null; | ||||
|     } | ||||
|     return this.#data[position.y][position.x]; | ||||
|   } | ||||
|  | ||||
|   get(position: Point): T { | ||||
|     this.#checkPosition(position); | ||||
|     return this.#data[position.y][position.x]; | ||||
|   | ||||
| @@ -6,6 +6,8 @@ import { addButton } from "./button.ts"; | ||||
| import { getPlayerProgress } from "./playerprogress.ts"; | ||||
| import { getStateManager } from "./statemanager.ts"; | ||||
| import { getCheckModal } from "./checkmodal.ts"; | ||||
| //import { LadderPickup } from "./pickups.ts"; | ||||
| // import { generateMap } from "./mapgen.ts"; | ||||
|  | ||||
| type Button = { | ||||
|   label: string; | ||||
| @@ -53,6 +55,32 @@ export class Hotbar { | ||||
|       enabled: true, | ||||
|       endorse: getPlayerProgress().getBlood() < 100, | ||||
|     }); | ||||
|     /* | ||||
|     buttons.push({ | ||||
|       label:"Cheat", | ||||
|       cbClick: () => { | ||||
|        new LadderPickup().onClick(); | ||||
|       }, | ||||
|       enabled: true, | ||||
|       endorse: false, | ||||
|     }) | ||||
|     buttons.push({ | ||||
|       label:"Dig for bad maps", | ||||
|       cbClick: () => { | ||||
|         let i = 0; | ||||
|         try { | ||||
|           for(; i < 10000; i++) { | ||||
|             generateMap(); | ||||
|           } | ||||
|         } catch(e) { | ||||
|           console.log(`Map gen failed after ${i} tries.`); | ||||
|         } | ||||
|         console.log("Ten thousand maps generated successfully."); | ||||
|       }, | ||||
|       enabled: true, | ||||
|       endorse: true, | ||||
|     }) | ||||
|     */ | ||||
|     return buttons; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -89,6 +89,72 @@ class Knife { | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   showDebug(merged: Record<number, number>) { | ||||
|     if (true) { | ||||
|       let out = ""; | ||||
|       let errors: string[] = []; | ||||
|       const size = this.#regions.size; | ||||
|       for (let y = 0; y < size.h; y++) { | ||||
|         for (let x = 0; x < size.w; x++) { | ||||
|           const loc = new Point(x, y); | ||||
|           out += (() => { | ||||
|             if (this.#map.get(loc).architecture == Architecture.Wall) { | ||||
|               return this.#sealedWalls.get(loc) ? "◘" : "█"; | ||||
|             } | ||||
|             let r = this.#regions.get(loc); | ||||
|             if (typeof r === "number") { | ||||
|               const resolved = merged[r]; | ||||
|               if (typeof resolved === "number") { | ||||
|                 r = resolved; | ||||
|               } else { | ||||
|                 errors.push(`${loc} is region ${r}, not found in merged`); | ||||
|               } | ||||
|               if (r < 0) { | ||||
|                 return "!"; | ||||
|               } | ||||
|               // 0...9 and lowercase | ||||
|               if (r < 36) { | ||||
|                 return r.toString(36); | ||||
|               } | ||||
|               // uppercase | ||||
|               r -= 26; | ||||
|               if (r < 36) { | ||||
|                 return r.toString(36).toUpperCase(); | ||||
|               } | ||||
|               // Greek lowercase | ||||
|               r -= 36; | ||||
|               if (r < 25) { | ||||
|                 return String.fromCodePoint(r + 0x3b1); | ||||
|               } | ||||
|               // Greek uppercase (there is a hole at 0x3a2) | ||||
|               r -= 25; | ||||
|               if (r < 17) { | ||||
|                 return String.fromCodePoint(r + 0x391); | ||||
|               } | ||||
|               r -= 17; | ||||
|               if (r < 7) { | ||||
|                 return String.fromCodePoint(r + 0x3a3); | ||||
|               } | ||||
|               // Hebrew | ||||
|               r -= 7; | ||||
|               if (r < 27) { | ||||
|                 return String.fromCodePoint(r + 0x5d0); | ||||
|               } | ||||
|               // give up | ||||
|               return "?"; | ||||
|             } | ||||
|             return "."; // room without region | ||||
|           })(); | ||||
|         } | ||||
|         out += "\n"; | ||||
|       } | ||||
|       console.log(out); | ||||
|       if (errors.length > 0) { | ||||
|         console.log(`uh-oh: \n\t${errors.join("\n\t")}`); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function generateMap(): LoadedNewMap { | ||||
| @@ -99,6 +165,11 @@ export function generateMap(): LoadedNewMap { | ||||
|       if (e instanceof TryAgainException) { | ||||
|         continue; | ||||
|       } | ||||
|       if (e instanceof BadMapError) { | ||||
|         console.log(`Bad map generated: ${e.message}:`); | ||||
|         showDebug(e.badMap); | ||||
|         // continue; | ||||
|       } | ||||
|       throw e; | ||||
|     } | ||||
|   } | ||||
| @@ -108,7 +179,7 @@ export function tryGenerateMap(vaultTemplates: VaultTemplate[]): LoadedNewMap { | ||||
|   let width = WIDTH; | ||||
|   let height = HEIGHT; | ||||
|   if (width % 2 == 0 || height % 2 == 0) { | ||||
|     throw "must be odd-sized"; | ||||
|     throw new Error("map bounds must be odd-sized"); | ||||
|   } | ||||
|  | ||||
|   let grid = new LoadedNewMap("generated", new Size(width, height)); | ||||
| @@ -484,7 +555,7 @@ function connectRegions(knife: Knife) { | ||||
|       ); | ||||
|     } | ||||
|     iter++; | ||||
|     showDebug(knife.map); | ||||
|     knife.showDebug(merged); | ||||
|     if (connectors.length == 0) { | ||||
|       throw new TryAgainException( | ||||
|         "couldn't figure out how to connect sections", | ||||
| @@ -498,12 +569,15 @@ function connectRegions(knife: Knife) { | ||||
|     let sources: number[] = dedup(basicRegions.map((i) => merged[i])); | ||||
|     let dest: number | undefined = sources.pop(); | ||||
|     if (dest == undefined) { | ||||
|       throw "each connector should touch more than one region"; | ||||
|       throw new BadMapError( | ||||
|         `each connector should touch more than one region but ${connector} does not`, | ||||
|         knife.map, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (Math.random() > EXTRA_CONNECTOR_CHANCE) { | ||||
|       // at random, don't regard them as merged | ||||
|       for (let i = 0; i < knife.region; i++) { | ||||
|       for (let i = 0; i <= knife.region; i++) { | ||||
|         if (sources.indexOf(merged[i]) != -1) { | ||||
|           merged[i] = dest; | ||||
|         } | ||||
| @@ -532,6 +606,12 @@ function connectRegions(knife: Knife) { | ||||
|     } | ||||
|     connectors = connectors2; | ||||
|   } | ||||
|   knife.showDebug(merged); | ||||
|  | ||||
|   // The map should now be fully connected. | ||||
|   if (!knife.map.isConnected()) { | ||||
|     throw new BadMapError("unconnected", knife.map); | ||||
|   } | ||||
| } | ||||
|  | ||||
| function growMaze(knife: Knife, start: Point) { | ||||
| @@ -624,7 +704,7 @@ function decorateRoom(_map: LoadedNewMap, _rect: Rect) {} | ||||
|  | ||||
| function randrange(lo: number, hi: number) { | ||||
|   if (lo >= hi) { | ||||
|     throw `randrange: hi must be >= lo, ${hi}, ${lo}`; | ||||
|     throw new Error(`randrange: hi must be >= lo, ${hi}, ${lo}`); | ||||
|   } | ||||
|  | ||||
|   return lo + Math.floor(Math.random() * (hi - lo)); | ||||
| @@ -658,3 +738,11 @@ function showDebug(grid: LoadedNewMap) { | ||||
| } | ||||
|  | ||||
| class TryAgainException extends Error {} | ||||
| class BadMapError extends Error { | ||||
|   badMap: LoadedNewMap; | ||||
|  | ||||
|   constructor(msg: string, badMap: LoadedNewMap) { | ||||
|     super(msg); | ||||
|     this.badMap = badMap; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -105,6 +105,57 @@ export class LoadedNewMap { | ||||
|   getZoneLabel(point: Point): string | null { | ||||
|     return this.#zoneLabels.get(point); | ||||
|   } | ||||
|  | ||||
|   isConnected(): boolean { | ||||
|     const size = this.#size; | ||||
|     let reached = new Grid<boolean>(size, () => false); | ||||
|  | ||||
|     // find starting location | ||||
|     const found: Point | null = (() => { | ||||
|       for (let x = 0; x < size.w; x++) { | ||||
|         for (let y = 0; y < size.w; y++) { | ||||
|           const p = new Point(x, y); | ||||
|           if (this.#architecture.get(p) == Architecture.Floor) { | ||||
|             return p; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return null; | ||||
|     })(); | ||||
|     if (found === null) { | ||||
|       // technically, all open floors on the map are indeed connected | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     let stack: Point[] = [found]; | ||||
|     reached.set(found, true); | ||||
|     while (stack.length > 0) { | ||||
|       const loc = stack.pop() as Point; | ||||
|       for (var p of loc.neighbors()) { | ||||
|         if ( | ||||
|           this.#architecture.maybeGet(p) === Architecture.Floor && | ||||
|           !reached.get(p) | ||||
|         ) { | ||||
|           reached.set(p, true); | ||||
|           stack.push(p); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     for (let x = 0; x < size.w; x++) { | ||||
|       for (let y = 0; y < size.w; y++) { | ||||
|         const p = new Point(x, y); | ||||
|         if ( | ||||
|           this.#architecture.get(p) == Architecture.Floor && | ||||
|           !reached.get(p) | ||||
|         ) { | ||||
|           return false; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
| } | ||||
|  | ||||
| export class CellView { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user