Dungeon builder written in Python
Dungeon builder written in Python by Steve Wallace
- NOTES***
First I'd like to say that my main experience with writing any sort of script has been with Flash actionscript, and having exceeded the boundaries of Flash's capabilities I decided to learn a language with more potential. I started learning Python just a few days ago, so my appologies if my code isn't elegant. I have tried to make it as straightforward and easy to follow as possible.
Next, my os is Windows, you will notice I have used the Numeric module, which is an array specific module for windows. Python has no (decent) native array support under Windows, so anyone using a different os will probably want to impliment their preffered array methods. Apart from that the following can be run on any os that supports Python.
dMap class
This is a fairly flexible class that will build a dungeon level. It is configurable to produce a wide variety of dungeon types, from small networks of rooms to huge mazes, and with a little modification it can easily be extended to incorporate your own features too.
As it stands it produces 2 different room types, the basic room and the corridor, and 4 types of joins, open wall, closed door, open door and secret door.
dMap has 3 attributes:
1. mapArr[y][x] - this is the main array that stores the map data, being values from 0 to 5 (0-walkable floor, 1-blank/undiscovered, 2-wall, 3-open door, 4-closed door, 5-secret door)
2. roomList[n] - this is a list of every room (inc. corridors) and roomList[n] will be a list in the form of [y size of room, x size of room, x coord, y coord] ie a value of [4,5,2,6] will be a room 5 across and 4 down with its top left floor tile at coord 2,6. This shouldn't be edited as any changes made after its created will NOT reflect on the map, its purely a reference to the floor area of the map.
3.cList[n] - this is similar to roomList, but it purely contains the corridors only and is used for connecting corridors. Its value will be in the format [ref. no to roomList, start x coord, start y coord, direction it travels]. The reference number will give the index to its entry in roomList, x and y start coords will be the coordinate where the corridor actually starts (NOTE this may not be the same as its x and y pos) and direction travelled is a number from 0 to 3 (0-north, 1-east, 2-south,3-west). cList is purely a set of hints supplied to the joinCorridor() function, although it could be used to generate a list of only corridors for use in monster/item/etc placement. Alternatively its ref no could be used to exclude it from roomList to generate a list of rooms only. As with roomList, do not edit after its creation.
dMap has 1 main method that is used to create a map:
makeMap(xsize,ysize,fail,b1,mrooms)
xsize- x size of the map area
ysize- y size of the map area
fail- a value from 1 upwards. The higer the value of fail, the greater the chance of larger dungeons being created. A low value (>10) tends to produce only a few rooms, a high value (<50) raises the chance that the whole map area will be used to create rooms (up to the value of mrooms - see below)
b1- corridor bias. This is a value from 0 to 100 and represents the %chance a feature will be a corridor instead of a room. A value of 0 will produce rooms only, a value of 100 will produce corridors only
mrooms- maximum number of rooms to create. This, combined with fail, can be used to create a specific number of rooms
Implimentation
somename=dMap() somename.makeMap(50,50,10,50,20)
The above will make a map of maximum size 50 x 50, a low (10) chance of making new rooms with a 50% chance new rooms will be corridors, and a maximum of 20 rooms.
somevariable=somename.mapArr[y][x] will give you access to the actual map, where x and y are the coordinates, and the value of somevariable will be from 0 to 5 as detailed above in mapArr.
Well I hope this all makes sence, and once again sorry if the code is kinda ugly, like I said I only started learning Python a few days ago (and this is my second attempt at writing a map algorithm). However, feel free to use and modify the code to suit your needs.
- Class to produce random map layouts
from Numeric import * from random import * from math import * class dMap:
def __init__(self): self.roomList=[] self.cList=[] def makeMap(self,xsize,ysize,fail,b1,mrooms): """Generate random layout of rooms, corridors and other features""" #makeMap can be modified to accept arguments for values of failed, and percentile of features. #Create first room self.mapArr=ones((ysize,xsize)) w,l,t=self.makeRoom() y=int(random()*(39-l))+1 x=int(random()*(79-w))+1 p=self.placeRoom(l,w,x,y,6,0) failed=0 while failed<fail: #The lower the value that failed< , the smaller the dungeon chooseRoom=int(random()*len(self.roomList)) ex,ey,ex2,ey2,et=self.makeExit(chooseRoom) feature=int(random()*100) if feature<b1: #Begin feature choosing (more features to be added here) w,l,t=self.makeCorridor() else: w,l,t=self.makeRoom() roomDone=self.placeRoom(l,w,ex2,ey2,t,et) if roomDone==0: #If placement failed increase possibility map is full failed=failed+1 elif roomDone==2: #Possiblilty of linking rooms if self.mapArr[ey2][ex2]==0: if int(random()*100)<7: self.makePortal(ex,ey) failed=failed+1 else: failed=failed+1 else: #Otherwise, link up the 2 rooms self.makePortal(ex,ey) failed=0 if t<5: tc=[len(self.roomList)-1,ex2,ey2,t] self.cList.append(tc) self.joinCorridor(len(self.roomList)-1,ex2,ey2,t,50) if len(self.roomList)==mrooms: failed=fail self.finalJoins() def makeRoom(self): """Randomly produce room size""" rtype=5 rwide=int(random()*8)+3 rlong=int(random()*8)+3 return rwide,rlong,rtype def makeCorridor(self): """Randomly produce corridor length and heading""" clength=int(random()*18)+3 heading=int(random()*4) if heading==0: #North wd=1 lg=-clength if heading==1: #East wd=clength lg=1 if heading==2: #South wd=1 lg=clength if heading==3: #West wd=-clength lg=1 return wd,lg,heading def placeRoom(self,ll,ww,xposs,yposs,rty,ext): """Place feature if enough space and return canPlace as true or false""" #Arrange for heading xpos=xposs ypos=yposs if ll<0: ypos=ypos+(ll+1) ll=int(sqrt(ll*ll)) if ww<0: xpos=xpos+(ww+1) ww=int(sqrt(ww*ww)) #Make offset if type is room if rty==5: if ext==0 or ext==2: offset=int(random()*ww) xpos=xpos-offset else: offset=int(random()*ll) ypos=ypos-offset #Then check if there is space canPlace=1 if ww+xpos+1>79 or ll+ypos+1>40: canPlace=0 return canPlace elif xpos<1 or ypos<1: canPlace=0 return canPlace else: for j in range(ll): for k in range(ww): if self.mapArr[(ypos)+j][(xpos)+k]!=1: canPlace=2 #If there is space, add to list of rooms if canPlace==1: temp=[ll,ww,xpos,ypos] self.roomList.append(temp) for j in range(ll+2): #Then build walls for k in range(ww+2): self.mapArr[(ypos-1)+j][(xpos-1)+k]=2 for j in range(ll): #Then build floor for k in range(ww): self.mapArr[ypos+j][xpos+k]=0 placed=1 return canPlace #Return whether placed is true/false def makeExit(self,rn): """Pick random wall and random point along that wall""" room=self.roomList[rn] exitMade=0 while exitMade==0: rw=int(random()*4) if rw==0: #North wall rx=int(random()*room[1])+room[2] ry=room[3]-1 rx2=rx ry2=ry-1 if rw==1: #East wall ry=int(random()*room[0])+room[3] rx=room[2]+room[1] rx2=rx+1 ry2=ry if rw==2: #South wall rx=int(random()*room[1])+room[2] ry=room[3]+room[0] rx2=rx ry2=ry+1 if rw==3: #West wall ry=int(random()*room[0])+room[3] rx=room[2]-1 rx2=rx-1 ry2=ry if self.mapArr[ry][rx]==2: #If space is a wall set exit flag exitMade=1 return rx,ry,rx2,ry2,rw def makePortal(self,px,py): """Create doors in walls""" ptype=int(random()*100) if ptype>90: #Secret door self.mapArr[py][px]=5 return if ptype>75: #Closed door self.mapArr[py][px]=4 return if ptype>40: #Open door self.mapArr[py][px]=3 return else: #Hole in the wall self.mapArr[py][px]=0 def joinCorridor(self,cno,xp,yp,ed,psb): """Check corridor endpoint and make an exit if it links to another room""" cArea=self.roomList[cno] if xp!=cArea[2] or yp!=cArea[3]: #Find the corridor endpoint endx=xp-(cArea[1]-1) endy=yp-(cArea[0]-1) else: endx=xp+(cArea[1]-1) endy=yp+(cArea[0]-1) checkExit=[] if ed==0: #North corridor if endx>1: coords=[endx-2,endy,endx-1,endy] checkExit.append(coords) if endy>1: coords=[endx,endy-2,endx,endy-1] checkExit.append(coords) if endx<78: coords=[endx+2,endy,endx+1,endy] checkExit.append(coords) if ed==1: #East corridor if endy>1: coords=[endx,endy-2,endx,endy-1] checkExit.append(coords) if endx<78: coords=[endx+2,endy,endx+1,endy] checkExit.append(coords) if endy<38: coords=[endx,endy+2,endx,endy+1] checkExit.append(coords) if ed==2: #South corridor if endx<78: coords=[endx+2,endy,endx+1,endy] checkExit.append(coords) if endy<38: coords=[endx,endy+2,endx,endy+1] checkExit.append(coords) if endx>1: coords=[endx-2,endy,endx-1,endy] checkExit.append(coords) if ed==3: #West corridor if endx>1: coords=[endx-2,endy,endx-1,endy] checkExit.append(coords) if endy>1: coords=[endx,endy-2,endx,endy-1] checkExit.append(coords) if endy<38: coords=[endx,endy+2,endx,endy+1] checkExit.append(coords) for i in range(len(checkExit)): #Loop through possible exits xxx=checkExit[i][0] yyy=checkExit[i][1] xxx1=checkExit[i][2] yyy1=checkExit[i][3] if self.mapArr[yyy][xxx]==0: #If joins to a room if int(random()*100)<psb: #Possibility of linking rooms self.makePortal(xxx1,yyy1) def finalJoins(self): """Final stage, loops through all the corridors to see if any can be joined to other rooms""" for i in range(len(self.cList)): self.joinCorridor(self.cList[i][0],self.cList[i][1],self.cList[i][2],self.cList[i][3],10)