diff --git a/www/mods/_rougeLike/demo.html b/www/mods/_rougeLike/demo.html index ce4aca7a..cbec4f78 100644 --- a/www/mods/_rougeLike/demo.html +++ b/www/mods/_rougeLike/demo.html @@ -11,9 +11,9 @@ diff --git a/www/mods/_rougeLike/mapController.js b/www/mods/_rougeLike/mapController.js index 9ac60dae..31fe9345 100644 --- a/www/mods/_rougeLike/mapController.js +++ b/www/mods/_rougeLike/mapController.js @@ -5,11 +5,19 @@ MATTIE.util = MATTIE.util || {}; +//the chance on each wall tile that the rest of the wall will not generate +const chanceForWallToCollapse = .05; +/** the min and max amount of wall that can collapse */ +const [minCollapse,maxCollapse] = [2, 5]; const wallTile=1579; const floorTiles = [1587] +const collapseTiles = [1661,1662,1663] const floorTile=()=>floorTiles[Math.floor(Math.random()*floorTiles.length)]; +const collapseTile=()=>collapseTiles[Math.floor(Math.random()*collapseTiles.length)]; const logging = false; const defaultCanvasId = 'viewport'; +/** the max depth to go to for width/height based splitting */ +const maxDepthForWidthSplit = 10 /** @todo remove this when done testing */ function getRandomColor() { @@ -55,6 +63,7 @@ class Tile { } } + /** * @description a class representing a region of space on the map. */ @@ -67,6 +76,17 @@ class Region { * @param {number} botY y cord for top right corner */ constructor(topX, topY, botX, botY, parent = null) { + /** + * @description an dict of left,right,up and down arrays of all regions this region directly connects to + * @type {{Region[]}} + */ + this.connectsTo = { + left:[], + right:[], + up:[], + down:[], + }; + /** * the upper left corner of the region * @type {RougeLikePoint} @@ -79,6 +99,12 @@ class Region { * */ this.bottomRightCorner; + /** + * @description an array containing all other points that are not in the main rectangle + * @type {RougeLikePoint[]} + */ + this.otherPoints=[]; + /** @description a variable that marks if this is the highest layer of the region IE: a region with no parent */ this.isRoot = false; @@ -111,6 +137,36 @@ class Region { this.checkRoot(); } + /** + * + * @param {Region[]} arr + * @param {*} canvasId + */ + static DrawArrayOntoCanvas(arr, canvasId = defaultCanvasId){ + arr.forEach(region=>{ + region.drawAllChildrenOnCanvas() + }) + arr.forEach(region=>{ + region.drawAllDoors() + }) + } + + /** + * @description return an array of all leafs in this region's children + * @returns {Region[]} + * */ + getAllLeafs(){ + if (!this.checkLeaf()) { + let arr = [] + this.children.forEach((child) => { + arr = [...child.getAllLeafs(), ...arr] + }); + return arr; + } else { + return [this] + } + } + /** @description draw all children onto the canvas */ drawAllChildrenOnCanvas(canvasId = defaultCanvasId) { this.drawOnCanvas(canvasId); @@ -156,9 +212,6 @@ class Region { * @param {number} forceHorizontal above .5 to force a horizontal split, below to force a vertical */ split(minPercentageLeft = 20, percentagePadding = 10, forceHorizontal = undefined) { - // the total percentage of space that is not included in padding - const workablePercentage = 100 - (percentagePadding * 2); - const width = this.getWidth(); const height = this.getHeight(); @@ -215,6 +268,10 @@ class Region { const bottomBoxLowerLeftCorner = this.bottomRightCorner; const bottomRegion = new Region(bottomBoxUpperLeftCorner.x, bottomBoxUpperLeftCorner.y, bottomBoxLowerLeftCorner.x, bottomBoxLowerLeftCorner.y, this); + //add to eachother's connection array + bottomRegion.connectsTo.up.push(topRegion); + topRegion.connectsTo.down.push(bottomRegion); + const door = new RougeLikePoint(MATTIE.util.randBetween(minX + 2, maxX - 2), splitYLevel); topRegion.doors.push(door); bottomRegion.doors.push(door); @@ -250,6 +307,10 @@ class Region { const rightBoxLowerLeftCorner = this.bottomRightCorner; const rightRegion = new Region(rightBoxUpperLeftCorner.x, rightBoxUpperLeftCorner.y, rightBoxLowerLeftCorner.x, rightBoxLowerLeftCorner.y, this); + //add to eachother's connection array + rightRegion.connectsTo.left.push(leftRegion); + leftRegion.connectsTo.right.push(rightRegion); + const door = new RougeLikePoint(splitXLevel, MATTIE.util.randBetween(minY + 2, maxY - 2)); leftRegion.doors.push(door); rightRegion.doors.push(door); @@ -280,15 +341,25 @@ class Region { * @param {*} height the target height * @returns null */ - splitTillWidth(width, height) { + splitTillWidth(width, height, currentDepth=0) { let force; - if (MATTIE.util.checkNumberInRange(this.getWidth(), 0, width)) return; force = 0; - if (MATTIE.util.checkNumberInRange(this.getHeight(), 0, height)) return; force = 1; - this.split(force); + const inWidth = MATTIE.util.checkNumberInRange(this.getWidth(), 0, width); + const inHeight = MATTIE.util.checkNumberInRange(this.getHeight(), 0, height); + if(logging)console.log(`width:${this.getWidth()}`) + if(logging)console.log(`Height:${this.getHeight()}`) + //if close to width goal force split + if (!inWidth && inHeight && (MATTIE.util.checkNumberInRange(this.getWidth(), 0, width*1.2)||currentDepth>3)) force = 0; + //if close to height goal force split + if (inWidth && !inHeight && (MATTIE.util.checkNumberInRange(this.getHeight(), 0, height*1.2)||currentDepth>2)) force = 1; + if(inWidth && inHeight) return; + if(currentDepth >= maxDepthForWidthSplit) return; + + + this.split(10,20,force); if (!this.checkLeaf()) { this.children.forEach((child) => { // decrement by one and recurse - child.splitTillWidth(width, height); + child.splitTillWidth(width, height, currentDepth+1); }); } } @@ -318,6 +389,24 @@ class Region { getHeight() { return Math.abs(this.upperLeftCorner.y - this.bottomRightCorner.y); } + + /** + * @description check if a point is within the bounds of the region + * @param {*} x + * @param {*} y + */ + isWithinBounds(x,y){ + let inBounds = false; + //check if point in set of other points + if(this.otherPoints.some(point=>point.x==x&&point.y==y)) return true; + + //check if x and y are in bounds + if(this.bottomRightCorner.x < x && this.upperLeftCorner.y < y) return false; + if(this.bottomRightCorner.y > y && this.upperLeftCorner.x > x) return false; + + //thus in bounds + return true; + } } /** @@ -339,10 +428,10 @@ class RougeLikeMap { */ constructor(width = 100, height = 100, x = 0, y = 0) { /** @description the width of the map to be generated */ - this.width = 50; + this.width = 25; /** @description the width of the map to be generated */ - this.height = 50; + this.height = 25; /** @description x coordinate of the upper left corner */ this.upperLeftX = 0; @@ -377,12 +466,13 @@ class RougeLikeMap { */ this.rooms=[]; + let targetWidth = 13; this.width = width; this.height = height; this.upperLeftX = x; this.upperLeftY = y; this.rootRegion = new Region(x,y,width,height); - this.rootRegion.splitXDeep(3); + this.rootRegion.splitTillWidth(targetWidth,targetWidth); this.updateRegions(); this.updateTiles(); } @@ -395,7 +485,9 @@ class RougeLikeMap { }; this.regions.push(region); + if(region.checkLeaf()){this.rooms.push(region)}; region.children.forEach(child => { + if(child.checkLeaf()){this.rooms.push(child)}; this.regions.push(child); this.updateRegions(child); }) @@ -431,7 +523,8 @@ class RougeLikeMap { /** @description rebuild mapTiles */ updateTiles() { this.makeBlankTileSet(); - this.regions.forEach(region => { + let rooms = [...this.rooms,this.rootRegion] + rooms.forEach(region => { //draw top line for (let x = region.upperLeftCorner.x; x < region.bottomRightCorner.x; x++) { let y = region.upperLeftCorner.y; @@ -476,3 +569,128 @@ class RougeLikeMap { } } + +/** + * @description the main class for generating and controlling maps + */ +class DungeonMap { + /** + * @description + * @param {number} topX top left bound + * @param {number} topY top bound + * @param {number} bottomX bottom right bound + * @param {number} bottomY bottom bound + */ + constructor(topX, topY, bottomX, bottomY){ + /** + * @description all the rooms of the dungeon + * @type {Region[]} + */ + this.rooms = []; + + /** + * @description halls + * @type {Region[]} + */ + this.halls = []; + + /** @description the min size of a room (width or height)*/ + this.roomSizeMin=10; + + /** @description the max size of a room (width or height)*/ + this.roomSizeMax=20; + + /** @description the minimum rooms in the dungeon */ + this.minRoomsInDungeon=5; + + /** @description the maximum rooms in the dungeon */ + this.maxRoomsInDungeon=15; + + /** @description the min padding between rooms */ + this.minPaddingBetweenRooms = 3; + /** @description the max padding between rooms */ + this.maxPaddingBetweenRooms = 8; + + /** @description the region denoting the bounds of the map */ + this.mapBounds = new Region(topX,topY,bottomX,bottomY); + + //the min and max percentages of a region to use as a room + this.minPercentageUsed = .4; + this.maxPercentageUsed = 1; + } + + /** @description generate random rooms */ + _generateRandomRooms(){ + const numberOfRoomsToSpawn = MATTIE.util.randBetween(this.minRoomsInDungeon,this.maxRoomsInDungeon); + for (let index = 0; index < numberOfRoomsToSpawn; index++) { + let room; + + let i =0; + do { + let xMin = this.mapBounds.upperLeftCorner.x + let yMin = this.mapBounds.upperLeftCorner.y + let xMax = this.mapBounds.bottomRightCorner.x + let yMax = this.mapBounds.bottomRightCorner.y + + let point1 = new RougeLikePoint( + MATTIE.util.lerp(xMin,xMax,MATTIE.util.randBetween(0,1)), + MATTIE.util.lerp(yMin,yMax,MATTIE.util.randBetween(0,1)) + ) + + let point2 = new RougeLikePoint( + MATTIE.util.lerp(xMin,xMax,MATTIE.util.randBetween(0,1)), + MATTIE.util.lerp(yMin,yMax,MATTIE.util.randBetween(0,1)) + ) + room = new Region(point1.x,point1.y,point2.x,point2.y); + i++; + } while (!this.rooms.some(other=>other.isWithinBounds(room)) && i < 4) + this.rooms.push(room); + } + } + + /** @description generate random rooms */ + generateRandomRooms(){ + const numberOfRoomsToSpawn = MATTIE.util.randBetween(this.minRoomsInDungeon,this.maxRoomsInDungeon); + + //split our space if its not split up already + if(this.mapBounds.children.length==0) + this.mapBounds.splitTillWidth(11,11) + + //extract all leaves and then randomly choose some + let arrOfRoomBounds = this.mapBounds.getAllLeafs(); + + let arrayOfChosenBounds = []; + + //loop for each room to spawn + for (let index = 0; index < numberOfRoomsToSpawn; index++) { + let room; + + //choose a region that has not been used yet + let chosenRegion = arrOfRoomBounds[MATTIE.util.randBetween(0,arrOfRoomBounds.length)]; + while(arrayOfChosenBounds.indexOf(chosenRegion) != -1){ + chosenRegion = arrOfRoomBounds[MATTIE.util.randBetween(0,arrOfRoomBounds.length)]; + } + arrayOfChosenBounds.push(chosenRegion); + + let xMin = chosenRegion.upperLeftCorner.x + let yMin = chosenRegion.upperLeftCorner.y + let xMax = chosenRegion.bottomRightCorner.x + let yMax = chosenRegion.bottomRightCorner.y + + const alpha = MATTIE.util.randBetween(0,this.minPercentageUsed); + let topleftPoint = new RougeLikePoint( + MATTIE.util.lerp(xMin,xMax,alpha), + MATTIE.util.lerp(yMin,yMax,alpha) + ) + + const alpha2 = MATTIE.util.randBetween(0,this.minPercentageUsed-alpha); + let bottomRightPoint = new RougeLikePoint( + MATTIE.util.lerp(xMax,xMin,alpha2), + MATTIE.util.lerp(yMax,yMin,alpha2) + ) + + room = new Region(topleftPoint.x,topleftPoint.y, bottomRightPoint.x,bottomRightPoint.y); + this.rooms.push(room) + } + } +} \ No newline at end of file diff --git a/www/mods/commonLibs/_common/util.js b/www/mods/commonLibs/_common/util.js index 58554d1c..541f62e3 100644 --- a/www/mods/commonLibs/_common/util.js +++ b/www/mods/commonLibs/_common/util.js @@ -13,6 +13,17 @@ MATTIE.util.setSeed = function (seed) { MATTIE.util.seedRandom = new Math.seedrandom(seed); // create new seedrandom from seedrandom.js }; +/** + * linerly interpret between 2 numbers + * @param {number} a number 1 + * @param {number} b number 2 + * @param {number} alpha val between 0-1 of which number we are at + */ +MATTIE.util.lerp = function (a, b, alpha) { + return a + alpha * (b - a); +}; + + /** @description get the next random number in the set seed */ MATTIE.util.getSeedRandom = function () { return MATTIE.util.seedRandom(); @@ -222,12 +233,3 @@ MATTIE.util.getMapVariant = function (x = 0) { return vars[$gameVariables.value(randVars[x])]; }; -/** - * linerly interpret between 2 numbers - * @param {number} a number 1 - * @param {number} b number 2 - * @param {number} alpha val between 0-1 of which number we are at - */ -MATTIE.util.lerp = function (a, b, alpha) { - return a + alpha * (b - a); -};