From 67af4b06ab18888f659992b7b600e3d56d064c6b Mon Sep 17 00:00:00 2001 From: APuddle210 Date: Thu, 13 Mar 2025 09:02:30 -0400 Subject: [PATCH] TerrainMapGenerator General Cleanup (#234) > There were 4 separate implementations of getting neighboring coordinates for a given x,y pair, 3 completely unique implementations. These were found in neighbors(), processOcean(), processDistToLand(), and getArea(), with processOcean() and processDistToLand() having essentially identical implementations. Created new function getNeighborCoords() with implementation closely resembling that found in neighbors(). Revised aforementioned functions to utilize the new getNeighborCoords() instead, with revised implementations being much more consistent across the impacted functions. > Revised getArea() to correctly assess the area, was previously likely to overestimate the area as it included all coordinates it check regardless of their terrainType. getArea() now also explicitely implements the Coord[] interface, allowing for standardized implementation syntax in all functions which call getArea(). Also removed the parameter targetType as all calls to getArea() targetted the same TerrainType as was present at the passed x, y position. getArea() will now assess targetType based on the passed x, y values. Reordered the parameters of getArea() so that it conformed with the ordering used elsewhere in the script (x, y, map, visited). Updated all calling locations accordingly. > Revised processOcean() to make use of getArea() instead of using its own reimplementation of area checking. This coupled with the use of the new getNeighborCoords() significantly reduced the size of processOcean and improved comprehensibility. Also removed unnecessary check of waterBody.size that would always return TRUE in that context. > Removed checks against and setting of Terrain.ocean values in the removeSmallIslands() and removeSmallLakes() functions as these functions are called prior to processOcean() making the checking redundant/unncessary, and the setting potentially problematic. Revised Terrain class to default ocean to false, to address compiler warning, and since processOcean() handles setting this property to true explicitly. Reduced total lines from 417 to 372. --- src/scripts/TerrainMapGenerator.ts | 169 ++++++++++------------------- 1 file changed, 57 insertions(+), 112 deletions(-) diff --git a/src/scripts/TerrainMapGenerator.ts b/src/scripts/TerrainMapGenerator.ts index a827f804e..d9aa7d94f 100644 --- a/src/scripts/TerrainMapGenerator.ts +++ b/src/scripts/TerrainMapGenerator.ts @@ -34,7 +34,7 @@ enum TerrainType { class Terrain { public shoreline: boolean = false; public magnitude: number = 0; - public ocean: boolean; + public ocean: boolean = false; constructor(public type: TerrainType) {} } @@ -169,42 +169,20 @@ function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) { if (terrain.type === TerrainType.Water) { terrain.magnitude = distance; - for (const [dx, dy] of [ - [-1, 0], - [1, 0], - [0, -1], - [0, 1], - ]) { - const newX = coord.x + dx; - const newY = coord.y + dy; - - if ( - newX >= 0 && - newX < map.length && - newY >= 0 && - newY < map[0].length - ) { - queue.push([{ x: newX, y: newY }, distance + 1]); - } - } + const nCoords: Coord[] = getNeighborCoords(coord.x, coord.y, map); + nCoords.forEach((nCoord) => { + queue.push([{ x: nCoord.x, y: nCoord.y }, distance + 1]); + }); } } } function neighbors(x: number, y: number, map: Terrain[][]): Terrain[] { + const nCoords: Coord[] = getNeighborCoords(x, y, map); const ns: Terrain[] = []; - if (x > 0) { - ns.push(map[x - 1][y]); - } - if (x < map.length - 1) { - ns.push(map[x + 1][y]); - } - if (y > 0) { - ns.push(map[x][y - 1]); - } - if (y < map[0].length - 1) { - ns.push(map[x][y + 1]); - } + nCoords.forEach((nCoord) => { + ns.push(map[nCoord.x][nCoord.y]); + }); return ns; } @@ -220,49 +198,11 @@ function processOcean(map: Terrain[][]) { const key = `${x},${y}`; if (visited.has(key)) continue; - // Find all connected water tiles - const waterBody: Coord[] = []; - const queue: Coord[] = [{ x, y }]; - - while (queue.length > 0) { - const coord = queue.shift()!; - const currentKey = `${coord.x},${coord.y}`; - - if (visited.has(currentKey)) continue; - visited.add(currentKey); - - if (map[coord.x][coord.y].type === TerrainType.Water) { - waterBody.push(coord); - - // Check all four directions - for (const [dx, dy] of [ - [-1, 0], - [1, 0], - [0, -1], - [0, 1], - ]) { - const newX = coord.x + dx; - const newY = coord.y + dy; - - if ( - newX >= 0 && - newX < map.length && - newY >= 0 && - newY < map[0].length - ) { - queue.push({ x: newX, y: newY }); - } - } - } - } - - // Store this water body if it has any tiles - if (waterBody.length > 0) { - waterBodies.push({ - coords: waterBody, - size: waterBody.length, - }); - } + const waterBody: Coord[] = getArea(x, y, map, visited); + waterBodies.push({ + coords: waterBody, + size: waterBody.length, + }); } } } @@ -327,37 +267,31 @@ function packTerrain(mapName: string, map: Terrain[][]): Uint8Array { } function getArea( - map: Terrain[][], x: number, y: number, + map: Terrain[][], visited: Set, - targetType: TerrainType, -) { - const area = []; - const next = [[x, y]]; - while (next.length) { - const [x, y] = next.pop(); - const key = `${x},${y}`; - if ( - x < 0 || - x >= map.length || - y < 0 || - y >= map[0].length || - visited.has(key) - ) - continue; +): Coord[] { + const targetType: TerrainType = map[x][y].type; + const area: Coord[] = []; + const queue: Coord[] = [{ x, y }]; - if (map[x][y].type === targetType) { - next.push([x + 1, y]); - next.push([x - 1, y]); - next.push([x, y + 1]); - next.push([x, y - 1]); - } + while (queue.length > 0) { + const coord = queue.shift()!; + const key = `${coord.x},${coord.y}`; - area.push([x, y]); + if (visited.has(key)) continue; visited.add(key); - } + if (map[coord.x][coord.y].type === targetType) { + area.push({ x: coord.x, y: coord.y }); + + const nCoords: Coord[] = getNeighborCoords(coord.x, coord.y, map); + nCoords.forEach((nCoord) => { + queue.push({ x: nCoord.x, y: nCoord.y }); + }); + } + } return area; } @@ -370,13 +304,10 @@ function removeSmallIslands(map: Terrain[][]) { const key = `${x},${y}`; if (visited.has(key)) continue; - const island = getArea(map, x, y, visited, TerrainType.Land); + const island = getArea(x, y, map, visited); if (island.length < min_island_size) { - island.forEach((pos) => { - const x = pos[0]; - const y = pos[1]; - map[x][y].type = TerrainType.Water; - map[x][y].ocean = true; + island.forEach((coord) => { + map[coord.x][coord.y].type = TerrainType.Water; }); } } @@ -394,18 +325,15 @@ function removeSmallLakes(mapName: string, map: Terrain[][]) { for (let x = 0; x < map.length; x++) { for (let y = 0; y < map[0].length; y++) { - if (map[x][y].type === TerrainType.Water && !map[x][y].ocean) { + if (map[x][y].type === TerrainType.Water) { const key = `${x},${y}`; if (visited.has(key)) continue; - const lake = getArea(map, x, y, visited, TerrainType.Water); + const lake = getArea(x, y, map, visited); if (lake.length < min_lake_size) { - lake.forEach((pos) => { - const x = pos[0]; - const y = pos[1]; - map[x][y].type = TerrainType.Land; - map[x][y].magnitude = 0; - map[x][y].ocean = false; + lake.forEach((coord) => { + map[coord.x][coord.y].type = TerrainType.Land; + map[coord.x][coord.y].magnitude = 0; }); } } @@ -424,4 +352,21 @@ function logBinaryAsBits( console.log(`${mapName}: Binary data (bits):`, bits); } +function getNeighborCoords(x: number, y: number, map: Terrain[][]): Coord[] { + const coords: Coord[] = []; + if (x > 0) { + coords.push({ x: x - 1, y: y }); + } + if (x < map.length - 1) { + coords.push({ x: x + 1, y }); + } + if (y > 0) { + coords.push({ x: x, y: y - 1 }); + } + if (y < map[0].length - 1) { + coords.push({ x: x, y: y + 1 }); + } + return coords; +} + await loadTerrainMaps();