Path: blob/main/projects/cookie-clicker/DungeonGen.js
1834 views
/*1Orteil's crappy dungeon generation library, 20132Unfinished and buggy, use at your own risk (please credit)3http://orteil.dashnet.org45Rough process (might or might not be what actually happens) :61 make a room in the middle72 pick one of its walls (not corners)83 select a free tile on the other side of that wall94 iteratively expand the selection in one (corridors) or two (rooms) directions, stopping when we meet a wall or when we're above the size threshold105 compute that selection into a room116 add decorations to the room (pillars, water) but only on the center tiles, as to leave free passages (sprinkle destructible decorations anywhere)127 take a random floor tile in the room and repeat step 4, but don't stop at the walls of this room (this creates branching) - repeat about 5 times for interesting shapes138 add those branches to the room149 carve the room into the map, and set the initially selected wall as a door - set the new room's parent to the previous room, and add it to its parent's children1510 repeat step 2 with any free wall on the map until the amount of tiles dug is above the desired fill ratio1617Note : I should probably switch the rendering to canvas to allow stuff like occlusion shadows and lights18*/1920if (1==1 || undefined==Math.seedrandom)21{22//seeded random function, courtesy of http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html23(function(a,b,c,d,e,f){function k(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=j&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=j&f+1],c=c*d+h[j&(h[f]=h[g=j&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function l(a,b){var e,c=[],d=(typeof a)[0];if(b&&"o"==d)for(e in a)try{c.push(l(a[e],b-1))}catch(f){}return c.length?c:"s"==d?a:a+"\0"}function m(a,b){for(var d,c=a+"",e=0;c.length>e;)b[j&e]=j&(d^=19*b[j&e])+c.charCodeAt(e++);return o(b)}function n(c){try{return a.crypto.getRandomValues(c=new Uint8Array(d)),o(c)}catch(e){return[+new Date,a,a.navigator.plugins,a.screen,o(b)]}}function o(a){return String.fromCharCode.apply(0,a)}var g=c.pow(d,e),h=c.pow(2,f),i=2*h,j=d-1;c.seedrandom=function(a,f){var j=[],p=m(l(f?[a,o(b)]:0 in arguments?a:n(),3),j),q=new k(j);return m(o(q.S),b),c.random=function(){for(var a=q.g(e),b=g,c=0;h>a;)a=(a+c)*d,b*=d,c=q.g(1);for(;a>=i;)a/=2,b/=2,c>>>=1;return(a+c)/b},p},m(c.random(),b)})(this,[],Math,256,6,52);24}2526if (1==1 || undefined==choose) {function choose(arr) {if (arr.length==0) return 0; else return arr[Math.floor(Math.random()*arr.length)];}}272829var DungeonGen=function()30{31var TILE_EMPTY=0;//solid32var TILE_LIMIT=-100;//can't build anything here; edges of map33var TILE_FLOOR_EDGE=100;34var TILE_FLOOR_CENTER=110;35var TILE_DOOR=200;36var TILE_PILLAR=300;//not just pillars, could be any type of repetitive decoration37var TILE_WATER=400;38var TILE_WALL=500;39var TILE_WALL_CORNER=510;40var TILE_ENTRANCE=250;41var TILE_EXIT=260;4243var colors=[];44colors[TILE_EMPTY]='000';45colors[TILE_LIMIT]='900';46colors[TILE_FLOOR_EDGE]='ffc';47colors[TILE_FLOOR_CENTER]='ff9';48colors[TILE_DOOR]='f9f';49colors[TILE_PILLAR]='990';50colors[TILE_WATER]='99f';51colors[TILE_WALL]='960';52colors[TILE_WALL_CORNER]='630';53colors[TILE_ENTRANCE]='f9f';54colors[TILE_EXIT]='f9f';5556var rand=function(a,b){return Math.floor(Math.random()*(b-a+1)+a);}//return random value between a and b5758var Patterns=[];59this.Pattern=function(name,func)60{61this.name=name;62this.func=func;63Patterns.push(this);64}65new this.Pattern('Pillars',function(x,y,room)66{67if ((x+room.x)%2==0 && (y+room.y)%2==0 && Math.random()<0.8) return TILE_PILLAR;68return 0;69});70new this.Pattern('Large pillars',function(x,y,room)71{72if ((x+room.x)%3<2 && (y+room.y)%3<2 && Math.random()<0.8) return TILE_PILLAR;73return 0;74});75new this.Pattern('Sparse pillars',function(x,y,room)76{77if ((x+room.x)%3==0 && (y+room.y)%3==0 && Math.random()<0.8) return TILE_PILLAR;78return 0;79});80new this.Pattern('Lines',function(x,y,room)81{82if (room.x%2==0) if ((x+room.x)%2==0 && Math.random()<0.98) return TILE_PILLAR;83if (room.x%2==1) if ((y+room.y)%2==0 && Math.random()<0.98) return TILE_PILLAR;84return 0;85});868788var getRandomPattern=function()89{return choose(Patterns);}9091var defaultGenerator=function(me)92{93me.roomSize=10;94me.corridorSize=5;95me.fillRatio=1/3;96me.corridorRatio=0.2;97me.pillarRatio=0.2;98me.waterRatio=0;99me.branching=4;100me.sizeVariance=0.2;101102me.fillRatio=0.1+Math.random()*0.4;103me.roomSize=Math.ceil(rand(5,15)*me.fillRatio*2);104me.corridorSize=Math.ceil(rand(1,7)*me.fillRatio*2);105me.corridorRatio=Math.random()*0.8+0.1;106me.pillarRatio=Math.random()*0.5+0.5;107me.waterRatio=Math.pow(Math.random(),2);108me.branching=Math.floor(Math.random()*6);109me.sizeVariance=Math.random();110}111112113this.Map=function(w,h,seed,params)114{115//create a new map116//leave the seed out for a random seed117//params is an object that contains custom parameters as defined in defaultGenerator118//example : MyMap=new DungeonGen.Map(30,30,MySeed,{waterRatio:0.8}); (80 percent of the rooms will contain water)119if (undefined!=seed) this.seed=seed; else {Math.seedrandom();this.seed=Math.random();}120Math.seedrandom(this.seed);121this.seedState=Math.random;122this.w=w||20;123this.h=h||20;124125this.roomsAreHidden=0;126127this.rooms=[];128this.freeWalls=[];//all walls that would be a good spot for a door129this.freeTiles=[];//all passable floor tiles130this.doors=[];131this.tiles=this.w*this.h;132this.tilesDug=0;133this.digs=0;//amount of digging steps134this.stuck=0;//how many times we ran into a problem; stop digging if we get too many of these135136this.data=[];//fill the map with 0137for (var x=0;x<this.w;x++)138{139this.data[x]=[];140for (var y=0;y<this.h;y++)141{142this.data[x][y]=[TILE_EMPTY,-1,0];//data is stored as [tile system type,room id,tile displayed type] (-1 is no room)143if (x==0 || y==0 || x==this.w-1 || y==this.h-1) this.data[x][y]=[TILE_LIMIT,-1,0];144}145}146147defaultGenerator(this);148if (params)149{150for (var i in params)151{152this[i]=params[i];153}154}155Math.seedrandom();156157}158159this.Map.prototype.getType=function(x,y){return this.data[x][y][0];}160this.Map.prototype.getRoom=function(x,y){if (this.data[x][y][1]!=-1) return this.rooms[this.data[x][y][1]]; else return -1;}161this.Map.prototype.getTile=function(x,y){return this.rooms[this.data[x][y][2]];}162163this.Map.prototype.isWall=function(x,y)164{165var n=0;166for (var i in this.freeWalls){if (this.freeWalls[i][0]==x && this.freeWalls[i][1]==y) return n; else n++;}167return -1;168}169this.Map.prototype.isFloor=function(x,y)170{171var n=0;172for (var i in this.freeTiles){if (this.freeTiles[i][0]==x && this.freeTiles[i][1]==y) return n; else n++;}173return -1;174}175this.Map.prototype.removeFreeTile=function(x,y)176{177this.freeTiles.splice(this.isFloor(x,y),1);178}179180this.Map.prototype.fill=function(what)181{182//fill with something (either a set value, or a function that takes the map, a position X and a position Y as arguments)183//NOTE : this also resets the rooms!184//example : MyMap.fill(function(m,x,y){return Math.floor((Math.random());});185//...will fill the map with 0s and 1s186var func=0;187if (typeof(what)=='function') func=1;188for (var x=0;x<this.w;x++){for (var y=0;y<this.h;y++){189if (func) this.data[x][y]=[what(this,x,y),-1,0]; else this.data[x][y]=[what,-1,0];190}}191this.rooms=[];192}193194this.Map.prototype.fillZone=function(X,Y,W,H,what)195{196//just plain fill a rectangle197for (var x=X;x<X+W;x++){for (var y=Y;y<Y+H;y++){198this.data[x][y][0]=what;199}}200}201202this.Map.prototype.getRoomTile=function(room,x,y)203{204var n=0;205for (var i in room.tiles) {if (room.tiles[i].x==x && room.tiles[i].y==y) return n; else n++;}206return -1;207}208209this.Map.prototype.getFloorTileInRoom=function(room)210{211var tiles=[];212for (var i in room.tiles) {if (room.tiles[i].type==TILE_FLOOR_EDGE || room.tiles[i].type==TILE_FLOOR_CENTER) tiles.push(room.tiles[i]);}213return choose(tiles);214}215216this.Map.prototype.canPlaceRoom=function(rx,ry,rw,rh)217{218if (rx<2 || ry<2 || rx+rw>=this.w-1 || ry+rh>=this.h-1) return false;219for (var x=rx;x<rx+rw;x++)220{221for (var y=ry;y<ry+rh;y++)222{223var tile=this.getType(x,y);224var room=this.getRoom(x,y);225if (tile==TILE_LIMIT) return false;226if (room!=-1) return false;227}228}229return true;230}231232this.Map.prototype.setRoomTile=function(room,x,y,tile)233{234//var mapTile=this.getType(x,y);235var oldTile=this.getRoomTile(room,x,y);236var oldTileType=oldTile!=-1?room.tiles[oldTile].type:-1;237if (oldTile!=-1 && (238//(tile!=TILE_FLOOR_EDGE && tile!=TILE_FLOOR_CENTER) ||// && (oldTileType!=TILE_FLOOR_EDGE && oldTileType!=TILE_FLOOR_CENTER)) ||239//(tile!=TILE_FLOOR_EDGE && tile!=TILE_FLOOR_CENTER && (oldTileType!=TILE_FLOOR_EDGE && oldTileType!=TILE_FLOOR_CENTER)) ||240(tile==TILE_WALL || tile==TILE_WALL_CORNER) ||//don't place a wall over an existing room241(tile==TILE_FLOOR_EDGE && oldTileType==TILE_FLOOR_CENTER)//don't place an edge floor over a center floor242)) {return false;}243else244{245if (oldTile!=-1) room.tiles.splice(oldTile,1);246room.tiles.push({x:x,y:y,type:tile,score:0});247if ((tile==TILE_FLOOR_EDGE || tile==TILE_FLOOR_CENTER) && (oldTileType!=TILE_FLOOR_EDGE && oldTileType!=TILE_FLOOR_CENTER)) room.freeTiles++;248else if (tile!=TILE_FLOOR_EDGE && tile!=TILE_FLOOR_CENTER && (oldTileType==TILE_FLOOR_EDGE || oldTileType==TILE_FLOOR_CENTER)) room.freeTiles--;249return true;250}251}252253this.Map.prototype.expandRoom=function(room,rx,ry,rw,rh)254{255var x=0;var y=0;256//floor257for (var x=rx;x<rx+rw;x++){for (var y=ry;y<ry+rh;y++){258this.setRoomTile(room,x,y,TILE_FLOOR_EDGE);259}}260for (var x=rx+1;x<rx+rw-1;x++){for (var y=ry+1;y<ry+rh-1;y++){261this.setRoomTile(room,x,y,TILE_FLOOR_CENTER);262}}263//walls264y=ry-1;265for (var x=rx;x<rx+rw;x++){266this.setRoomTile(room,x,y,TILE_WALL);267}268y=ry+rh;269for (var x=rx;x<rx+rw;x++){270this.setRoomTile(room,x,y,TILE_WALL);271}272x=rx-1;273for (var y=ry;y<ry+rh;y++){274this.setRoomTile(room,x,y,TILE_WALL);275}276x=rx+rw;277for (var y=ry;y<ry+rh;y++){278this.setRoomTile(room,x,y,TILE_WALL);279}280//corners281x=rx-1;y=ry-1;282this.setRoomTile(room,x,y,TILE_WALL_CORNER);283x=rx+rw;y=ry-1;284this.setRoomTile(room,x,y,TILE_WALL_CORNER);285x=rx-1;y=ry+rh;286this.setRoomTile(room,x,y,TILE_WALL_CORNER);287x=rx+rw;y=ry+rh;288this.setRoomTile(room,x,y,TILE_WALL_CORNER);289290//decoration291var water=Math.random()<this.waterRatio?1:0;292var pattern=Math.random()<this.pillarRatio?getRandomPattern():0;293for (var x=rx;x<rx+rw;x++){for (var y=ry;y<ry+rh;y++){294if (room.tiles[this.getRoomTile(room,x,y)].type==TILE_FLOOR_CENTER)295{296var tile=0;297if (water!=0) tile=TILE_WATER;298if (pattern!=0)299{300tile=pattern.func(x,y,room)||tile;301}302if (tile!=0) this.setRoomTile(room,x,y,tile);303}304}}305}306307this.Map.prototype.newRoom=function(x,y,w,h,parent)308{309//create a new abstract room, ready to be carved310var room={};311room.id=this.rooms.length;312room.w=w;//||rand(2,this.roomSize);313room.h=h;//||rand(2,this.roomSize);314room.x=x||rand(1,this.w-room.w-1);315room.y=y||rand(1,this.h-room.h-1);316room.tiles=[];317room.freeTiles=0;318room.parent=parent?parent:-1;319room.children=[];320room.gen=0;321room.door=0;322room.corridor=Math.random()<this.corridorRatio?1:0;323room.hidden=this.roomsAreHidden;//if 1, don't draw324//if (room.parent!=-1) room.corridor=!room.parent.corridor;//alternate rooms and corridors325326return room;327}328this.Map.prototype.planRoom=function(room)329{330var branches=this.branching+1;331var forcedExpansions=[];332var w=room.w;333var h=room.h;334while (w>0 && h>0)335{336if (w>0) {forcedExpansions.push(1,3);w--;}337if (h>0) {forcedExpansions.push(2,4);h--;}338}339340for (var i=0;i<branches;i++)341{342var steps=0;343var expansions=[];344if (!room.corridor)345{346expansions=[1,2,3,4];347steps=this.roomSize;348}349else350{351expansions=choose([[1,3],[2,4]]);352steps=this.corridorSize;353}354steps=Math.max(room.w+room.h,Math.ceil(steps*(1-Math.random()*this.sizeVariance)));355if (room.tiles.length==0) {var rx=room.x;var ry=room.y;var rw=1;var rh=1;}356else {var randomTile=this.getFloorTileInRoom(room);var rx=randomTile.x;var ry=randomTile.y;var rw=1;var rh=1;}357for (var ii=0;ii<steps;ii++)358{359if (expansions.length==0) break;360var xd=0;var yd=0;var wd=0;var hd=0;361var side=choose(expansions);362if (forcedExpansions.length>0) side=forcedExpansions[0];363if (side==1) {xd=-1;wd=1;}364else if (side==2) {yd=-1;hd=1;}365else if (side==3) {wd=1;}366else if (side==4) {hd=1;}367if (this.canPlaceRoom(rx+xd,ry+yd,rw+wd,rh+hd)) {rx+=xd;ry+=yd;rw+=wd;rh+=hd;} else expansions.splice(expansions.indexOf(side),1);368if (forcedExpansions.length>0) forcedExpansions.splice(0,1);369}370if (rw>1 || rh>1)371{372this.expandRoom(room,rx,ry,rw,rh);373}374}375}376377378this.Map.prototype.carve=function(room)379{380//carve a room into the map381for (var i in room.tiles)382{383var thisTile=room.tiles[i];384var x=thisTile.x;var y=thisTile.y;385var myType=this.data[x][y][0];386var type=thisTile.type;387388if ((type==TILE_WALL || type==TILE_WALL_CORNER) && this.isWall(x,y)!=-1) {this.freeWalls.splice(this.isWall(x,y),1);}389390if (this.data[x][y][1]!=-1 && (type==TILE_WALL || type==TILE_WALL_CORNER)) {}391else392{393if (this.data[x][y][1]==-1) this.tilesDug++;394this.data[x][y]=[thisTile.type,room.id,0];395if (x>1 && y>1 && x<this.w-2 && y<this.h-2 && type==TILE_WALL) this.freeWalls.push([x,y]);396if (type==TILE_FLOOR_EDGE || type==TILE_FLOOR_CENTER) this.freeTiles.push([x,y]);397}398var pos=[x,y];399}400this.rooms[room.id]=room;401}402403this.Map.prototype.newRandomRoom=function(params)404{405var success=1;406params=params||{};//params is an object such as {corridor:1}407var door=choose(this.freeWalls);//select a free wall to use as a door408if (!door) {success=0;}409else410{411//this.data[door[0]][door[1]][0]=TILE_LIMIT;//not door412var parentRoom=this.getRoom(door[0],door[1]);413var sides=[];//select a free side of that door414if (this.getType(door[0]-1,door[1])==TILE_EMPTY) sides.push([-1,0]);415if (this.getType(door[0]+1,door[1])==TILE_EMPTY) sides.push([1,0]);416if (this.getType(door[0],door[1]-1)==TILE_EMPTY) sides.push([0,-1]);417if (this.getType(door[0],door[1]+1)==TILE_EMPTY) sides.push([0,1]);418var side=choose(sides);419if (!side) {success=0;this.freeWalls.splice(this.isWall(door[0],door[1]),1);}420else421{422var room=this.newRoom(door[0]+side[0],door[1]+side[1],0,0,parentRoom);//try a new room from this spot423for (var i in params)424{425room[i]=params[i];426}427this.planRoom(room);428if (room.tiles.length>0 && room.freeTiles>0)//we got a decent room429{430this.carve(room);431this.data[door[0]][door[1]][0]=TILE_DOOR;//place door432room.door=[door[0],door[1]];433this.data[door[0]][door[1]][1]=room.id;//set ID434this.freeWalls.splice(this.isWall(door[0],door[1]),1);//the door isn't a wall anymore435this.doors.push([door[0],door[1],room]);436//remove free tiles on either side of the door437if (this.isFloor(door[0]+side[0],door[1]+side[1])!=-1) this.removeFreeTile(door[0]+side[0],door[1]+side[1]);438if (this.isFloor(door[0]-side[0],door[1]-side[1])!=-1) this.removeFreeTile(door[0]-side[0],door[1]-side[1]);439room.parent=parentRoom;440parentRoom.children.push(room);441room.gen=parentRoom.gen+1;442}443else//not a good spot; remove this tile from the list of walls444{445this.freeWalls.splice(this.isWall(door[0],door[1]),1);446success=0;447}448}449}450if (success) return room;451else return 0;452}453454this.Map.prototype.getRandomSpotInRoom=function(room)455{456var listOfTiles=[];457for (var i in room.tiles)458{459if ((room.tiles[i].type==TILE_FLOOR_EDGE || room.tiles[i].type==TILE_FLOOR_CENTER) && this.isFloor(room.tiles[i].x,room.tiles[i].y)!=-1)460{461listOfTiles.push(room.tiles[i]);462}463}464if (listOfTiles.length==0) return -1;465return choose(listOfTiles);466}467this.Map.prototype.getBestSpotInRoom=function(room)468{469var highest=-1;470var listOfHighest=[];471for (var i in room.tiles)472{473if ((room.tiles[i].type==TILE_FLOOR_EDGE || room.tiles[i].type==TILE_FLOOR_CENTER) && this.isFloor(room.tiles[i].x,room.tiles[i].y)!=-1)474{475if (room.tiles[i].score>highest)476{477listOfHighest=[];478highest=room.tiles[i].score;479listOfHighest.push(room.tiles[i]);480}481else if (room.tiles[i].score==highest)482{483listOfHighest.push(room.tiles[i]);484}485}486}487if (listOfHighest.length==0) return -1;488return choose(listOfHighest);489}490this.Map.prototype.getEarliestRoom=function()491{492return this.rooms[0];493}494this.Map.prototype.getDeepestRoom=function()495{496var deepest=0;497var deepestRoom=this.rooms[0];498for (var i in this.rooms)499{500if ((this.rooms[i].gen+Math.sqrt(this.rooms[i].freeTiles)*0.05)>=deepest && this.rooms[i].corridor==0 && this.rooms[i].freeTiles>4) {deepest=(this.rooms[i].gen+Math.sqrt(this.rooms[i].freeTiles)*0.05);deepestRoom=this.rooms[i];}501}502return deepestRoom;503}504505this.Map.prototype.dig=function()506{507//one step in which we try to carve new stuff508//returns 0 when we couldn't dig this step, 1 when we could, and 2 when the digging is complete509Math.random=this.seedState;510511var badDig=0;512513if (this.digs==0)//first dig : build a starting room in the middle of the map514{515var w=rand(3,7);516var h=rand(3,7);517var room=this.newRoom(Math.floor(this.w/2-w/2),Math.floor(this.h/2-h/2),w,h);518room.corridor=0;519this.planRoom(room);520this.carve(room);521}522else523{524if (this.newRandomRoom()==0) badDig++;525}526if (badDig>0) this.stuck++;527528this.digs++;529530var finished=0;531if (this.tilesDug>=this.tiles*this.fillRatio) finished=1;532if (this.stuck>100) finished=1;533534if (finished==1)//last touch : try to add a whole room at the end535{536for (var i=0;i<10;i++)537{538var newRoom=this.newRandomRoom({corridor:0,w:rand(3,7),h:rand(3,7)});539if (newRoom!=0 && newRoom.freeTiles>15) break;540}541}542543Math.seedrandom();544if (finished==1) return 1; else if (badDig>0) return -1; else return 0;545}546547this.Map.prototype.finish=function()548{549//touch up the map : add pillars in corners etc550/*551//set paths552for (var i in this.rooms)553{554var me=this.rooms[i];555if (me.door!=0)556{557var doors=[];558doors.push(me.door);559for (var ii in me.children)560{561if (me.children[ii].door!=0) doors.push(me.children[ii].door);562}563for (var ii in doors)564{565this.data[doors[ii][0]][doors[ii][1]][0]=TILE_LIMIT;566//ideally we should run agents that step from each door to the next567}568}569}570*/571for (var i in this.rooms)572{573var pillars=Math.random()<this.pillarRatio;574for (var ii in this.rooms[i].tiles)575{576var x=this.rooms[i].tiles[ii].x;577var y=this.rooms[i].tiles[ii].y;578var me=this.data[x][y][0];579var x1=this.data[x-1][y][0];580var x2=this.data[x+1][y][0];581var y1=this.data[x][y-1][0];582var y2=this.data[x][y+1][0];583var xy1=this.data[x-1][y-1][0];584var xy2=this.data[x+1][y-1][0];585var xy3=this.data[x-1][y+1][0];586var xy4=this.data[x+1][y+1][0];587588var walls=0;589if ((x1==TILE_WALL||x1==TILE_WALL_CORNER)) walls++;590if ((y1==TILE_WALL||y1==TILE_WALL_CORNER)) walls++;591if ((x2==TILE_WALL||x2==TILE_WALL_CORNER)) walls++;592if ((y2==TILE_WALL||y2==TILE_WALL_CORNER)) walls++;593if ((xy1==TILE_WALL||xy1==TILE_WALL_CORNER)) walls++;594if ((xy2==TILE_WALL||xy2==TILE_WALL_CORNER)) walls++;595if ((xy3==TILE_WALL||xy3==TILE_WALL_CORNER)) walls++;596if ((xy4==TILE_WALL||xy4==TILE_WALL_CORNER)) walls++;597598var floors=0;599if ((x1==TILE_FLOOR_CENTER||x1==TILE_FLOOR_EDGE)) floors++;600if ((y1==TILE_FLOOR_CENTER||y1==TILE_FLOOR_EDGE)) floors++;601if ((x2==TILE_FLOOR_CENTER||x2==TILE_FLOOR_EDGE)) floors++;602if ((y2==TILE_FLOOR_CENTER||y2==TILE_FLOOR_EDGE)) floors++;603if ((xy1==TILE_FLOOR_CENTER||xy1==TILE_FLOOR_EDGE)) floors++;604if ((xy2==TILE_FLOOR_CENTER||xy2==TILE_FLOOR_EDGE)) floors++;605if ((xy3==TILE_FLOOR_CENTER||xy3==TILE_FLOOR_EDGE)) floors++;606if ((xy4==TILE_FLOOR_CENTER||xy4==TILE_FLOOR_EDGE)) floors++;607608var complete=0;609if (walls+floors==8) complete=1;610611var angle=0;612if (complete)613{614var top=0;615var left=0;616var right=0;617var bottom=0;618if ((xy1==TILE_WALL||xy1==TILE_WALL_CORNER) && (y1==TILE_WALL||y1==TILE_WALL_CORNER) && (xy2==TILE_WALL||xy2==TILE_WALL_CORNER)) top=1;619else if ((xy1==TILE_FLOOR_CENTER||xy1==TILE_FLOOR_EDGE) && (y1==TILE_FLOOR_CENTER||y1==TILE_FLOOR_EDGE) && (xy2==TILE_FLOOR_CENTER||xy2==TILE_FLOOR_EDGE)) top=-1;620if ((xy2==TILE_WALL||xy2==TILE_WALL_CORNER) && (x2==TILE_WALL||x2==TILE_WALL_CORNER) && (xy4==TILE_WALL||xy4==TILE_WALL_CORNER)) right=1;621else if ((xy2==TILE_FLOOR_CENTER||xy2==TILE_FLOOR_EDGE) && (x2==TILE_FLOOR_CENTER||x2==TILE_FLOOR_EDGE) && (xy4==TILE_FLOOR_CENTER||xy4==TILE_FLOOR_EDGE)) right=-1;622if ((xy1==TILE_WALL||xy1==TILE_WALL_CORNER) && (x1==TILE_WALL||x1==TILE_WALL_CORNER) && (xy3==TILE_WALL||xy3==TILE_WALL_CORNER)) left=1;623else if ((xy1==TILE_FLOOR_CENTER||xy1==TILE_FLOOR_EDGE) && (x1==TILE_FLOOR_CENTER||x1==TILE_FLOOR_EDGE) && (xy3==TILE_FLOOR_CENTER||xy3==TILE_FLOOR_EDGE)) left=-1;624if ((xy3==TILE_WALL||xy3==TILE_WALL_CORNER) && (y2==TILE_WALL||y2==TILE_WALL_CORNER) && (xy4==TILE_WALL||xy4==TILE_WALL_CORNER)) bottom=1;625else if ((xy3==TILE_FLOOR_CENTER||xy3==TILE_FLOOR_EDGE) && (y2==TILE_FLOOR_CENTER||y2==TILE_FLOOR_EDGE) && (xy4==TILE_FLOOR_CENTER||xy4==TILE_FLOOR_EDGE)) bottom=-1;626if ((top==1 && bottom==-1) || (top==-1 && bottom==1) || (left==1 && right==-1) || (left==-1 && right==1)) angle=1;627}628629if (pillars && Math.random()<0.8 && this.rooms[i].freeTiles>4)630{631if ((angle==1 || (complete && walls==7)) && me==TILE_FLOOR_EDGE && x1!=TILE_DOOR && x2!=TILE_DOOR && y1!=TILE_DOOR && y2!=TILE_DOOR)632{633this.data[x][y][0]=TILE_PILLAR;634me=TILE_PILLAR;635this.removeFreeTile(x,y);636this.rooms[i].freeTiles--;637}638}639640//calculate score (for placing items and exits)641if (top==1 || bottom==1 || left==1 || right==1)642{643this.rooms[i].tiles[ii].score+=2;644}645if (walls>5 || floors>5)646{647this.rooms[i].tiles[ii].score+=1;648}649if (walls==7 || floors==8)650{651this.rooms[i].tiles[ii].score+=5;652}653if ((me!=TILE_FLOOR_CENTER && me!=TILE_FLOOR_EDGE) || x1==TILE_DOOR || x2==TILE_DOOR || y1==TILE_DOOR || y2==TILE_DOOR) this.rooms[i].tiles[ii].score=-1;654655}656}657658659660//carve entrance and exit661var entrance=this.getBestSpotInRoom(this.getEarliestRoom());662this.data[entrance.x][entrance.y][0]=TILE_ENTRANCE;663this.entrance=[entrance.x,entrance.y];664entrance.score=0;665this.removeFreeTile(entrance.x,entrance.y);666var exit=this.getBestSpotInRoom(this.getDeepestRoom());667this.data[exit.x][exit.y][0]=TILE_EXIT;668this.exit=[exit.x,exit.y];669this.removeFreeTile(exit.x,exit.y);670exit.score=0;671672/*673for (var i in this.doors)//remove door tiles (to add later; replace the tiles by entities that delete themselves when opened)674{675this.data[this.doors[i][0]][this.doors[i][1]][0]=TILE_FLOOR_EDGE;676}677*/678}679680this.Map.prototype.isObstacle=function(x,y)681{682var free=[TILE_FLOOR_EDGE,TILE_FLOOR_CENTER,TILE_DOOR,TILE_ENTRANCE,TILE_EXIT];683for (var i in free)684{685if (this.data[x][y][0]==free[i]) return 0;686}687return 1;688}689690var joinTile=function(map,x,y,joinWith)691{692//for the tile at x,y, return 2 if it joins with its horizontal neighbors, 3 if it joins with its vertical neighbors, 1 if it joins with either both or neither.693//joinWith contains the tile types that count as joinable, in addition to this tile. (don't add the tested tile to joinWith!)694var p=1;695var me=map.data[x][y][0];696var x1=map.data[x-1][y][0];697var x2=map.data[x+1][y][0];698var y1=map.data[x][y-1][0];699var y2=map.data[x][y+1][0];700joinWith.push(me);701var joinsX=0;702for (var i in joinWith)703{704if (x1==joinWith[i]) joinsX++;705if (x2==joinWith[i]) joinsX++;706}707var joinsY=0;708for (var i in joinWith)709{710if (y1==joinWith[i]) joinsY++;711if (y2==joinWith[i]) joinsY++;712}713if (joinsX==2 && joinsY==2) p=1;714else if (joinsX==2) p=2;715else if (joinsY==2) p=3;716return p;717}718this.Map.prototype.getPic=function(x,y)719{720//return a position [x,y] in the tiles (as 0, 1, 2...) for the tile on the map at position x,y721if (Tiles[this.data[x][y][2]])722{723if (Tiles[this.data[x][y][2]].joinType=='join')724{725var thisPic=Tiles[this.data[x][y][2]].pic;726thisPic=[thisPic[0],thisPic[1]];//why is this even necessary?727var joinWith=[];728if (this.data[x][y][0]==TILE_WALL) joinWith.push(TILE_WALL_CORNER);729else if (this.data[x][y][0]==TILE_DOOR) joinWith.push(TILE_WALL,TILE_WALL_CORNER);730thisPic[0]+=joinTile(this,x,y,joinWith)-1;731return thisPic;732}733else if (Tiles[this.data[x][y][2]].joinType=='random3')734{735var thisPic=Tiles[this.data[x][y][2]].pic;736thisPic=[thisPic[0],thisPic[1]];737thisPic[0]+=Math.floor(Math.random()*3);738return thisPic;739}740return Tiles[this.data[x][y][2]].pic;741}742return [0,0];743}744745var Tiles=[];746var TilesByName=[];747this.Tile=function(name,pic,joinType)748{749this.name=name;750this.pic=pic;751this.joinType=joinType||'none';752this.id=Tiles.length;753Tiles[this.id]=this;754TilesByName[this.name]=this;755}756new this.Tile('void',[0,0]);757this.loadTiles=function(tiles)758{759for (var i in tiles)760{761var name=tiles[i][0];762var pic=tiles[i][1];763var joinType=tiles[i][2];764new this.Tile(name,pic,joinType);765}766}767768var computeTile=function(tile,tiles,value,name)769{770if (tile==value && tiles[name]) return TilesByName[tiles[name]];771return 0;772}773this.Map.prototype.assignTiles=function(room,tiles)774{775//set the displayed tiles for this room776for (var i in room.tiles)777{778var type=Tiles[0];779var me=room.tiles[i];780var tile=this.data[me.x][me.y][0];781type=computeTile(tile,tiles,TILE_WALL_CORNER,'wall corner')||type;782type=computeTile(tile,tiles,TILE_WALL,'wall')||type;783type=computeTile(tile,tiles,TILE_FLOOR_EDGE,'floor edges')||type;784type=computeTile(tile,tiles,TILE_FLOOR_CENTER,'floor')||type;785type=computeTile(tile,tiles,TILE_PILLAR,'pillar')||type;786type=computeTile(tile,tiles,TILE_DOOR,'door')||type;787type=computeTile(tile,tiles,TILE_WATER,'water')||type;788type=computeTile(tile,tiles,TILE_ENTRANCE,'entrance')||type;789type=computeTile(tile,tiles,TILE_EXIT,'exit')||type;790791this.data[me.x][me.y][2]=type.id;792}793}794795796this.Map.prototype.draw=function(size)797{798//return a string containing a rough visual representation of the map799var str='';800var size=size||10;801for (var y=0;y<this.h;y++){for (var x=0;x<this.w;x++){802var text='';803if (this.isFloor(x,y)!=-1) text='o';804if (this.isWall(x,y)!=-1) text+='x';805var room=this.getRoom(x,y);806var opacity=Math.max(0.1,1-(this.getRoom(x,y).gen/10));807var title=room.freeTiles;//this.data[x][y][0].toString();808text='';809str+='<div style="opacity:'+opacity+';width:'+size+'px;height:'+size+'px;position:absolute;left:'+(x*size)+'px;top:'+(y*size)+'px;display:block;padding:0px;margin:0px;background:#'+colors[this.data[x][y][0]]+';color:#999;" title="'+title+'">'+text+'</div>';810}811str+='<br>';812}813str='<div style="position:relative;width:'+(this.w*size)+'px;height:'+(this.h*size)+'px;background:#000;font-family:Courier;font-size:'+size+'px;float:left;margin:10px;">'+str+'</div>';814return str;815}816817this.Map.prototype.drawDetailed=function()818{819//return a string containing a rough visual representation of the map (with graphics)820var str='';821var size=16;822for (var y=0;y<this.h;y++){for (var x=0;x<this.w;x++){823var room=this.getRoom(x,y);824//var opacity=Math.max(0.1,room.tiles[this.getRoomTile(room,x,y)].score);825var opacity=1;826var title='void';827if (room!=-1)828{829opacity=Math.max(0.1,1-room.gen/5);830if (this.data[x][y][0]==TILE_ENTRANCE || this.data[x][y][0]==TILE_EXIT) opacity=1;831title=(room.corridor?'corridor':'room')+' '+room.id+' | depth : '+room.gen+' | children : '+room.children.length;832}833var pic=this.getPic(x,y);834str+='<div style="opacity:'+opacity+';width:'+size+'px;height:'+size+'px;position:absolute;left:'+(x*size)+'px;top:'+(y*size)+'px;display:block;padding:0px;margin:0px;background:#'+colors[this.data[x][y][0]]+' url(img/dungeonTiles.png) '+(-pic[0]*16)+'px '+(-pic[1]*16)+'px;color:#999;" title="'+title+'"></div>';835}836str+='<br>';837}838str='<div style="box-shadow:0px 0px 12px 6px #00061b;position:relative;width:'+(this.w*size)+'px;height:'+(this.h*size)+'px;background:#00061b;font-family:Courier;font-size:'+size+'px;float:left;margin:10px;">'+str+'</div>';839return str;840}841842this.Map.prototype.getStr=function()843{844//return a string containing the map with tile graphics, ready to be pasted in a wrapper845var str='';846var size=16;847for (var y=0;y<this.h;y++){for (var x=0;x<this.w;x++){848var room=this.getRoom(x,y);849//var opacity=Math.max(0.1,room.tiles[this.getRoomTile(room,x,y)].score);850var opacity=1;851var title='void';852var pic=this.getPic(x,y);853if (room!=-1)854{855/*856opacity=Math.max(0.1,1-room.gen/5);857if (room.hidden) opacity=0;858if (this.data[x][y][0]==TILE_ENTRANCE || this.data[x][y][0]==TILE_EXIT) opacity=1;859*/860if (room.hidden) pic=[0,0];861title=(room.corridor?'corridor':'room')+' '+room.id+' | depth : '+room.gen+' | children : '+room.children.length;862}863str+='<div style="opacity:'+opacity+';width:'+size+'px;height:'+size+'px;position:absolute;left:'+(x*size)+'px;top:'+(y*size)+'px;display:block;padding:0px;margin:0px;background:#'+colors[this.data[x][y][0]]+' url(img/dungeonTiles.png) '+(-pic[0]*16)+'px '+(-pic[1]*16)+'px;color:#999;" title="'+title+'"></div>';864}865str+='<br>';866}867return str;868}869870}871872