General rambling about game development and design by a professional games developer. Pickings at Python, complaints about C, lashings of Lisp, and whatever else is the language du jour.
Sunday, December 18
Lets try plugging this into this...
Lisp software renderer
Sunday, November 27
Bitmap Fonts, Java, Wingdings and Unicode
After my brush with the script-fu I thought I'd go back to basics and do the bitmap font creator as a Java app. I have whipped up this app for knocking up old school bitmap fonts. The gradient fills haven't really surrvived Pay attention to the picture, as it shows at what code points to find the Wingdings glyphs in the Unicode set. It's not 0..255, as has puzzled many Java coders before!
Thursday, November 17
Bitmap fonts and the script-fu.
Despite my rant against script-fu, it must be acknowlgeded it's highly useful for getting game art knocked out in double quick time for example, bitmap fonts. Here's a script that can knock one up in a jiffy - it's just a sligtly modified version of a standard script. I'm somewhat dismayed by the size of the .png file.
Wednesday, November 16
Script-fu
Today I delved into the realm of Gimp, SIOD, Script-fu; and let me tell you it's the least productive environment I've hit since the ZX81. I mean (set! variable value) is your assignment operator, so like a good lisp monkey I try (set! (aref string 1) "A"). Does that work? No. You use something called (aset string 1 65) why doesn't it have a bang, eh?
So, I want to create an array. Now I created a list with make-list, so, of course, I want to use make-array. No? Gotcha again! It's cons-array. Arrgh!
Add that to the fact that pressing the escape key closes the script-fu console window; which is something I do quite a lot, as it's hardwired into me that Escape means "clear the input" and that the documentation is just a scrappy list of functions that makes it hard to find what you need, that's a recipie for a very frustrating morning. The tragedy is that this is going to be some peoples first exposure to a lisp-like language..it could be so much better; the scintilla component could have been embedded in, there could be a much better tutorial and reference; but I guess someone would have to be paid for that..
Monday, November 14
Sony are in so much trouble..
This story just runs and runs. Not only do Sony release a rootkit that installs itself onto Windows PC's without telling people, and get discovered, it turns out that the rootkit is a GPL violation! This one is going to run and run...are Sony trying to replace Microsoft as the Evil Empire? They seem to be taking a pretty good shot at it!
Monday, November 7
Package du Jour: Dia
I've been playing with Dia and been moderately impressed with what I've found. I've been using it as part of my course; being able to export to .pngs is good. It's happy with a wide range of diagrams: mostly I am using it for entity relationship diagrams and UML. There's even a uml to code generator for it which I intend to try using for my Java assignment. While I can't say it's as good as Visio (when did that become part of Microsoft Office), it's a cheap way for students to explore UML and "good enough" for most small projects, and it has an interesting quirky diagram set: isometric game tiles - houses, railways, fields - which can be used to layout a small toy town. It might even be a game prototyping tool one day; with the right tileset..
Change of Direction
It might seem strange to some people that I have recently left Reflections and decided to enroll on a Masters degree course; it's a strange thing to do at 38 years of age. Even stranger, the Masters course is not strictly games-oriented. Well it's not as big a jump into the unknown as it looks: many of the new mobile platforms use Java, databases are increasingly important - both as repositories for the huge number of assets used in game development, and for the backend of MMPORG's as well as their more humble play-by-web cousins.
The web itself is probably the most interesting platform around today: it's a mass medium based on a mishmash of standards and technologies that can be bent to do almost everything; a giant laboratory free from the restrictions of proprietary console platform development.
I'm beginning to suspect a much better (as in scalable, among other things) platform for games development can be assembled out of some of the emergent technologies available now; a distributed, social system capable of handling vast amounts of assets, and large numbers of distributed collaborators. It's my intention to try and assemble a platform capable of this, and offer it as a model for further research.
It's also my intention to learn as much as possible in a short time whithout my brain exploding; as this is the most challenging course my local university has to offer, that might be hard. Still, onwards...
Thursday, October 13
More newbie thoughts on Lisp
However the lack of syntax has to be an achilles heel: the huge number of idioms that could be applied to simple programs means no two coders are going to write anything neccesarily similar. I'm assming the full set of coders of all levels of competence here. I expect a smaller set of seasoned coders would converge on similar solutions given similar problems. Which makes it a devastating weapon in the hands of a sesoned coder, but probably in a team of mixed abilities it would lead to much higher communication oveheads.
Thursday, October 6
Project Madness
I simply have to back off, I'm trying to do too many bloody things at once.
Here's my long list
- Get deeply enought into web/database tech to get a small play-by-web game running.
- Update the Omega rougelike game for the SDL & modern multiplatform code.
- Update Blah, a roguelike skeleton that fell off the net many moons ago, with SDL graphics and Lua scripting.
- Finish the LISP software renderer.
- Create a tool for parsing C into sexp's with an eye to auto-generating UFFI or CFFI bindings.
- Some unspecified Java app, just to keep my hand in : possibly a Blogger to Live-Journal migration tool.
- Some more games that have been bubbling under the radar for what seems like years.
- Lunch with Laura - a graphic novel. Nothing to do with coding, but it competes for time.
Clearly, something has to give, somewhere..
Thursday, September 8
XEmacs Wins
Mostly because you can configure it seven hundred ways to Sunday, and after lots of fiddling I arrived at an init.el I kind of liked. It has room for improvement. It probably shouldn't rely on cscope for tagging, becasue that's platform specific. It probably should fall back on plain old tags. Onwards to do some actual coding then: programmers are the only people who can get so preoccupied with sharpening an axe they forget about the damn tree.
Wednesday, September 7
Grand Text Editor
I find myself prevaricating between editors. I know I have to pick one and stick to it, but I can't make up my mind which. I've narrowed it down to a few promising candidates, all cross-platform.
XEmacs is pretty much *the* editor if I want to code in Lisp, as it has Slime, one of the most interactive and potentially productive programming environments I've ever seen. The Emacs Code Browser gives me IDE-like organisation and navigation. It integrates nicely with gdb and the shell. The con side is that the tagging is poor, and requiring more than three keystrokes to do anything useful makes it difficult and "slow;" feeling to work with. It's so configurable it's difficult to zero in on a single working style with it. The learning curve seems endless.
Vi - Improved. Another editing heavyweight, and the keystrokes are both brief and seared into my brain as I've been exposed to it on and off throughout my career. Frankly, if it wasn't for the amount of Lisp I do, it'd be my editor of choice. It doesn't have anything like ECB - but it does have sessions - a collection of settings that can be quickly saved and restarted. Which is fine for keeping track of different projects. It also as a huge collection of scripts and customisations, like Emacs..The keyword completion is wicked, but it's not context sensitive like Intellisence.
JEdit is another contender. It handles sessions and tags pretty much as well as Vi and XEmacs do. It's set of plugins are not as comprehensive as vim or XEmacs, but there's enough for the job. It's main benefit is a more modern gui and a slicker feel. Comfort is an important factor. The plugins are very Java-centric, and I suspect this editor would be a powerhouse for a Java coder, but I'm not so sure about how it would pan out for a C++/Lisp coder.
The only commercial editor of the bunch, and the slickest. It intelligently does completion of structure and class members, parameter lists, and does incremental tagging in the background: as a result I'm more productive when coding in C/C++ with this editor. It gave me a real competitive advantage over my Visual Studio bound colleagues back in the days of Visual Studio 6. Although they have since caguht up. It can handle Python and there is a script for Lisp support on the website, which I haven't tried.Lack of Lisp is a major minus, otherwise this would ne a no-brainer. Although, perhaps I could use this in conjunction with the LispWorks IDE.
So which one do I pick? They all have something I want, and they all miss something else. Can anyone spot anything I've missed? Some plugin or mode that would give one an advantage over the other? Shorter XEmacs keystrokes? Intellisense for vim or JEdit? A good Lisp mode for Visual Slick Edit?
Monday, September 5
Goodbye Slackware
One of the first things I've done with my newfound freedom since leaving Reflections is to review all my Linux installs. For a long time I've been soldiering on with the redoubtable Slackware. However, I resolved to give Debian a try, and frankly I'm amazed by what I've seen. Compared to Slackware install was fast, friendly and trouble-free: network, video, and sound were all auto-detected, and the graphical login came up first time.
Gnome is gorgeous, too, all point and click and "just works". Even this post came straight from the desktop blogging tool, rather than via Firefox.
Every time I revisit a mainstream distro, I'm amazed by the progress. Frankly, if something like this came pre-installed, damn few people would touch a Windows PC. Mind you, Steve Jobs has probably recently come to the same conclusion, so there maybe hope, yet...meanwhile, it's back to my Lisp rendering follies, I guess.
Saturday, June 18
Lisp, the language that does your coding for you..
Well, not quite - but almost. After reading Rogue Malcontents nifty post where he uses macros to wrap simple-arrays with structure - like syntax (very handy for interacting with APIs like OpenGL), I went back and looked at my code with a fresh eye. I found I was writing lots of forms like this :-
(red (get-red p0) (funcall red-interpolator))
(blue (get-blue p0) (funcall blue-interpolator))
(green (get-green p0) (funcall green-interpolator))
(alpha (get-alpha p0) (funcall alpha-interpolator))
..where the same form was wrapped around each colour channel of a pixel. So I thought "Hmm, time for a macro". My first attempt was something like this:
(defmacro with-pixel-colour-list (body)
(list
`list
(replace-symbol 'get-pixel 'get-red body)
(replace-symbol 'get-pixel 'get-blue body)
(replace-symbol 'get-pixel 'get-green body)
(replace-symbol 'get-pixel 'get-alpha body)))
Where replace-symbol was a utility function of my own devising which car'd its way through a form, replacing a matching symbol as it went - given here for completeness. So the form fed to with-pixel-colour list gets replicated four times - the first time with the get-pixel call replaced with a get-red call, the second time get-pixel replaced by a get-green call, and so forth. So I only have to write one form once, rathte than four times, and the compiler does the rest of the work for me, macro-expanding the code before compiling it.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun replace-symbol (sym1 sym2 list)
(cond
((null list)
nil)
((null (car list))
(replace-symbol sym1 sym2 (cdr list)))
((symbolp (car list))
(if (string= (symbol-name sym1) (symbol-name (car list)))
(cons sym2 (replace-symbol sym1 sym2 (cdr list)))
(cons (car list) (replace-symbol sym1 sym2 (cdr list)))))
((listp (car list))
(cons (replace-symbol sym1 sym2 (car list))
(replace-symbol sym1 sym2 (cdr list))))
(t (cons
(car list)
(replace-symbol sym1 sym2 (cdr list)))))))
So, (with-pixel-colours *my-pixel*) expands nicely to (LIST (GET-RED *MY-PIXEL*) (GET-BLUE *MY-PIXEL*) (GET-GREEN *MY-PIXEL*) (GET-ALPHA *MY-PIXEL*)) and I can write things like:
(defun sum-colours (pixel)
(apply #'+ (with-pixel-colours (get-pixel pixel))))
(setf *my-pixel* (make-pixel 1 1 1 0))
(sum-colours *my-pixel*)
3
Fantastic - but there has to be a wider application than this - so my next task was to generalise it a bit. More in the next post. :)
I do get the feeling I'm re-inventing a well known technique, as I'm a newbie, and this kind of thing is just too useful. Any experienced Lisp coders care to comment?
Tuesday, May 31
Ion Drive
I've never found a window manager I've been 100% happy with - KDE and Gnome are far to slow to live on my old P600 Laptop, Afterstep, while asethetically pleasing, had too many menus, too many different feels and options and and I never could settle on a consistent set. Ratpoison was my most recent discovery, and while I liked it for it's simplicity, it was a bit too simple - it handled modal dialogs like mozillas download manager dialog badly - putting them in a whole window. At last I've discovered Ion - a tad more sophisticated than Ratpoison, but not much, so it still responds instantly on a clapped out laptop. It just handles dialogs much better, and works on the same principe - you should be able to treat your window manager in exactly the same way as you treat your text editor window - by having multiple windows and frames you can split, delete, create, and traverse between via simple keystrokes. If you dislike mice, it's worth giving it a try..
The only problem is -- there will never be a Windows version. It'd be interesting to see what a Mac user would make of it, though..
Sunday, May 15
And now colour it in..
Added coloured tris to my renderer yesterday - it's coming along nicely. Started out as a "learn lisp" exercise, but now it's taking on a life of it's own.
Monday, May 2
Dynamic game difficulty adjustment.
Should a game adjust it's difficulty level based on the players observed ability in the game? Possibly, but it's an artificial device. Response to changes in the players ability should probably err on the cautious side when making the game easy and on the converse side when making the game harder. It's important to have player profiles, too. Visual feedback of the current setting and a manual override would be nice, too. Some people don't want things to be too easy and for the game to "surrender", but other people would use such a mechanism to cheat, or just plain want to go through the game quickly.
Feedback and control are the watchwords, as ever.
Sunday, April 24
Brand new Baby Bouncing Triangle
Lisp gets it right
After playing with lots of different methods of linearly interpolating the edges of triangles (well, 3 - DDA, MAI, & Bressenham) in Lisp, I settled for the straight add the gradient to the minor axis. Madness, I hear you say - not really, because Lisp doesn't represent the result of integer division as a float, but as a pair of integers that represent a ratio, so even doing it this way means I can reserve the FPU for perspective - correct texture mapping the way Intel intended. Obviously, the representation of this ratio is implementation dependent, but I'd really hope that in an implemenation with 32 bit fixnums, the ratio is expressed as a 64 bit fixed point integer..of course it's also probably possible to get LISP to use 16 bit integers for coordinates and 32 bit integers for the ratios, but I haven't got that far into the type sysetem, yet.
Friday, April 15
Lisp Software Renderer, Redux
Of course, it's impossible to leave a challenge I got so close to completing, and so I dust off the Lisp software renderer and return to the fray. I've had to grapple a bit with ASDF and learnt the hard way why upper case file names and lower case package names don't mix. Also, trying to write a tail-recursive Bressenham was interesting..only a few more bugs to wrinkle out and it can hit common-lisp.net.
Then I can go onto the freenode #lisp channel and get a critique, I hope. 123 notes at an (optimize (speed 3)) sounds like there's still a lot of work ahead..
Monday, February 21
CL-Ncurses
I'd really like to submit a patch but there doesn't seem to be any point. I wish there was some kind of policy on common-lisp.net for dealing with such orphaned projects. The same as with sourceforge, I guess.
Monday, February 14
LISP Software Renderer RIP
I gave up on my quest to write a software renderer in Lisp - I got as far as creating nice antialiased clipped lines, but was driven to the conclusion that the only sane language for such a beast is assembler...even C doesn't get quite close enough to the metal without generous helpings of inline assembler - which kind of misses the point..
I did discover that a LISP compiler with a decent static type system, vectorisation, SSE/SIMD support, and inline assembler could do just about as good a job as C. Trouble is, there's no LISP system like that in existence. There's at least two good open source Lisps that could be persuaded to do it, though..
Monday, January 31
Debug versus Release builds
The way I think of it, the difference between release and debug is the difference between trying to fly a plane with no instruments at all, and a possibly dodgy engine that won't neccessarily give you any warning that it's about to burn, versus a nice modern jet with thoroughly checked over engines and wiring, a full array of instrumentation and autopilots for everything you could possibly need.
Now - it's your backside in the plane - which do you want to fly?
Wednesday, January 26
Visual Studio and Standard Environments.
Just what is it that people see in it? Working with it as an editor is like swimming through treacle. Without the Visual Assist plugin it's auto completion is limited and without lengthy compile times, it can't manage to tag a project well. You can only really edit three languages with it, and use only the hand full of source code control systems that implement a Microsoft API. It's scripting facilities are ripe for viral attack, and poor tools for automation.
The wizards to help programmers who don't know how to do basic things ignore their ignorance, though. I'm just a little pissed off about this "standard environment" that' s been imposed on everybody at work. The a component of the last "standard environment" was the slowest and most broken source control system I've ever worked with.
When carrying 500kilo weights is the standard environment for the Olympic High Jump, then all this will make sense. I think. Standard environments might be great for managers as they reduce programmers to the lowest common interchangable denominaters, but for programmers looking for an edge and a chance to develop great software, they are a positive handicap.
Somewhere there is a cutoff point where staying within the standard environment becomes ultimately more punishing than the leap forward out of it. I just hope I can recognise that point.
Tuesday, January 25
Progress with Lisp
So far my initial enthusiasm for LISP is wearing off a bit. I can see it's practical potential, but the main upshot of my learning it, so far is that I've switched to Emacs from Vi.
Perhaps my problem is that I'm just put off by the constant fiddling needed with free Linux implementations and the unavailabilty of a good free as in beer Win32 implementation. I miss the ease of integration with a lot of useful libraries I'm used to working with in both Python & C++, as well as all the new idioms I have to absorb.
I can see the language can do things that Python only vaguely aspires to, though. I'm just not sure that in the cases when I don't need the extra grunt of Lisp that I'll be able to prototype as rapidly as I could in Python.
Tuesday, January 18
More Fun With Dungeon Generation
############ #### ## ### ##################
############ #### # ## ##################
#### ##############
### ## # #### # ##############
### ######## # ####### # ##############
######## ## ###### ##############
############# # #### ### ##############
########### # # #### ## ##############
# ########### # ### ################ ##############
# # ################ ##############
# ######### ##### ######################################
######### ## ######################################
# ######### ## # ########################################
# ########### ## ## ###################################
# # ########### ### ###################################
########## ######## ###################################
#### ###################### ###################################
##### ###################### ###################################
##### ###################### ###################################
##### ###################### ###################################
###################### ###################################
# ########################## ###################################
# ##################### ###################################
# ##################### #####################################
# ######################## #####################################
# ##### ############# #####################################
# ##### #### ###################################
# ##### #### # ######## ###################################
# ###################################
#### ##########################################
######## # ######################
# #### #### ######### ################# ######################
#### ############################# ######################
# ###### ################### ######################
# ################ ## ################### ######################
################ ## ################### ######################
################# ## ################### ######################
############## ## ################### ######################
### ############## ################### ####################
### ################ ##################### ####################
### ################ ##################### ####################
### ################ ##################### ####################
### ################ #### ############### ####################
### ################ #### ############### ####################
### ################ ## ############### ####################
### ################# ## ######### ####################
### ################# ##### ######### ##########################
### ################# ##### ##########################
### ###################### ### ### ##########################
### ###################### ### ### ##########################
# ###################### ### ##########################
######################## ### ####################
##################### ### ############ ####### ###########
## ##################### ### ############ ###########
# # ##################### ### ############ ## ## ##########
## # ##################### ############ ## ## ### ##########
## ##################### ############### ## ## ### ##########
################ ########### ## ## ### ##########
######### ### ####### ########### ## # ##########
######### ### ####### ########### ## ## ##########
######### # ####### ########### ## ## ########
######### ########## # ########## ## ## ########
######### ## # ########## ###### ## ########
######### ## ############# #################################
import Numeric
import whrandom
import math
class Burrower:
def __init__(self, x, y, lifetime = 0, dx = 0, dy = 0):
self.data = [ x, y, lifetime, dx, dy ]
def burrower_is_dead(self):
return self.data[2] == -1
def kill_burrower(self):
self.data[2] = -1
def tick_burrower(self,b):
self.data[2] = self.data[2] + 1
def pick_random_motion(self, allow_diagonals):
motion = whrandom.randrange(0,3)
if motion == 0:
dx = - 1
dy = self.data[4]
if not allow_diagonals:
dy = 0
elif motion == 1:
dx = 1
if not allow_diagonals:
dy = 0
elif motion == 2:
dy = -1
if not allow_diagonals:
dx = 0
elif motion == 3:
dy = 1
if not allow_diagonals:
dx = 0
# don't double back on yourself
if dx == -self.data[3] and dy == self.data[4]:
dy = dx
dx = self.data[4]
if dy == -self.data[4] and dx == self.data[3]:
dx = dy
dy = self.data[3]
self.data[3] = dx
self.data[4] = dy
def clip_burrower(self, minx, miny, maxx, maxy):
if self.data[0] < minx:
self.data[0] = minx
if self.data[0] > maxx:
self.data[0] = maxx
if self.data[1] < miny:
self.data[1] = miny
if self.data[1] > maxy:
self.data[1] = miny
def un_move_burrower(self, minx, miny, maxx, maxy, allow_diagonals):
self.data[0] = self.data[0] - self.data[3]
self.data[1] = self.data[1] - self.data[4]
self.clip_burrower(minx, miny, maxx, maxy)
self.pick_random_motion(allow_diagonals)
# move the burrower one cell, carving as you go along
# to do rather than randomly pick a direction, randomly *change* a direction (more control of twistiness, etc)
def move_burrower(self, minx, miny, maxx, maxy, changeprob, allow_diagonals):
if whrandom.random() < changeprob:
self.pick_random_motion(allow_diagonals)
# am i dead ? if so I should be concenrating on a nice firm rigor mortis
if self.burrower_is_dead():
return
self.data[0] = self.data[0] + self.data[3]
self.data[1] = self.data[1] + self.data[4]
if self.data[0] < minx:
self.data[0] = 0
self.data[3] = 1
if self.data[1] < miny:
self.data[1] = 0
self.data[4] = 1
if self.data[0] > maxx:
self.data[0] = maxx
self.data[3] = -1
if self.data[1] > maxy:
self.data[1] = maxy
self.data[4] = -1
return
class Dungeon:
TileIdNumber = { "granite" : 0 , "room" : 1, "corridor" : 2 }
def __init__(self, width, depth):
self.tiles = Numeric.zeros((width, depth))
self.width = width
self.depth = depth
self.room_centres = [ ]
def room_too_close(self, x, y, min_room_distance):
for r in self.room_centres:
dx = r[0] - x
dy = r[1] - y
dist = math.sqrt(x * x + y * y)
if (dist < min_room_distance):
return True
return False
def burrower_room_create(self, b, broom_prob, max_room_width, max_room_depth, min_room_distance, burrower_index):
# we have a room
if whrandom.random() > broom_prob:
return 0
room_width = whrandom.randrange(max_room_width / 2) + 1
room_depth = whrandom.randrange(max_room_depth / 2) + 1
if self.room_too_close(b.data[0], b.data[1], min_room_distance):
return 0
self.room_centres.append((b.data[0], b.data[1]))
for x in range (b.data[0] - room_width, b.data[0] + room_width):
for y in range (b.data[1] - room_depth, b.data[1] + room_depth):
if (x >= 0) and (x < self.width) and (y >= 0) and (y < self.depth):
self.tiles[x][y] = burrower_index
return 1
def burrower_birth(self,bbirth_prob):
return whrandom.random() < bbirth_prob
def baby_burrower(self, b):
return Burrower(b.data[0] + whrandom.randrange(2), b.data[1] + whrandom.randrange(2), 0, 0, 0)
def print_dungeon(self):
for y in range(0,self.depth):
s = ""
for x in range(0,self.width):
if self.tiles[x][y] == Dungeon.TileIdNumber["granite"]:
s = s + "#"
else:
s = s + " "
print s
#
# burrowers start on one side of the level, on each turn they can either split (birth prob),
# create a room (die), have a max lifetime, and the algo terminates when room_count is reached
#
def generate(self, burrower_total, room_total, birth_prob, turn_prob, room_prob, lifetime, max_room_width, max_room_depth, min_room_distance, allow_diagonals):
# set up burrowers
burrowers = []
for i in range(0,burrower_total):
side = whrandom.randrange(0,4)
if side == 0:
burrower = Burrower( 0, whrandom.randrange(0, self.depth) , 0, 1, 0 )
elif side == 1:
burrower = Burrower( self.width-1, whrandom.randrange(0, self.depth) , -1, 0, 0 )
elif side == 2:
burrower = Burrower( whrandom.randrange(0,self.width), 0, 0, 0, 1 )
else:
burrower = Burrower( whrandom.randrange(0,self.width), self.depth-1 , 0, 0, -1 )
burrowers.append(burrower)
room_count = 0
burrower_alive = True
i = 0
while burrower_alive:
print "Generation " , i
print "Rooms # ", room_count
print "Burrowers ", len(burrowers)
self.print_dungeon()
burrower_alive = False
birth_list = []
burrower_index = 1
for b in burrowers:
if not b.burrower_is_dead():
burrower_alive = True
b.move_burrower(0, 0, self.width-1, self.depth-1, turn_prob, allow_diagonals)
# if the burrower reaches another corridor, kill it
burrower_tile = self.tiles[b.data[0]][b.data[1]]
if (burrower_tile != Dungeon.TileIdNumber["granite"]) and (burrower_tile != burrower_index):
b.un_move_burrower(0,0, self.width-1, self.depth-1, allow_diagonals)
b.data[2] = b.data[2] + 1
if b.data[2] > lifetime:
b.kill_burrower()
self.tiles[b.data[0]][b.data[1]] = burrower_index
if self.burrower_birth(birth_prob):
birth_list.append(self.baby_burrower(b))
room_count = room_count + self.burrower_room_create(b, room_prob, max_room_width, max_room_depth, min_room_distance, burrower_index)
burrower_index = burrower_index + 1
if birth_list:
burrowers.extend(birth_list)
if room_count >= room_total:
birth_prob = 0.0
room_prob = 0.0
i = i + 1
return
if __name__=="__main__":
dungeon = Dungeon(64,64)
dungeon.generate(16, # burrower total
12, # room total
0.009, # birth probability
0.25, # turn probability
0.03, # room probability
2, # lifetime (max non self - hits allowwd)
9, # max_room_depth
9, # max room width
6, # min room distance
False)
Thursday, January 13
Fun With Dungeon Generation
One of the more preinnal topics of roguelike game design, or general dungeon cralws is "How do I generate that funky dungeon procedurally?". There's an unbelievable number of algorithms, but this one is my favourite...
import Numeric
import whrandom
class Burrower:
def __init__(self, x, y, lifetime = 0, dx = 0, dy = 0):
self.data = [ x, y, lifetime, dx, dy ]
def burrower_is_dead(self):
return self.data[2] == -1
def kill_burrower(self):
self.data[2] = -1
def tick_burrower(self,b):
self.data[2] = self.data[2] + 1
def pick_random_motion(self, allow_diagonals):
motion = whrandom.randrange(0,3)
if motion == 0:
self.data[3] = - 1
if not allow_diagonals:
self.data[4] = 0
elif motion == 1:
self.data[3] = 1
if not allow_diagonals:
self.data[4] = 0
elif motion == 2:
self.data[4] = -1
if not allow_diagonals:
self.data[3] = 0
elif motion == 3:
if not allow_diagonals:
self.data[3] = 0
self.data[4] = 1
# move the burrower one cell, carving as you go along
# to do rather than randomly pick a direction, randomly *change* a direction (more control of twistiness, etc)
def move_burrower(self, minx, miny, maxx, maxy, changeprob, allow_diagonals):
if whrandom.random() < changeprob:
self.pick_random_motion(allow_diagonals)
# am i dead ? if so I should be concenrating on a nice firm rigor mortis
if self.burrower_is_dead():
return
self.data[0] = self.data[0] + self.data[3]
self.data[1] = self.data[1] + self.data[4]
if self.data[0] < minx:
self.data[0] = 0
self.data[3] = 1
if self.data[1] < miny:
self.data[1] = 0
self.data[4] = 1
if self.data[0] > maxx:
self.data[0] = maxx
self.data[3] = -1
if self.data[1] > maxy:
self.data[1] = maxy
self.data[4] = -1
# up lifetime count
self.data[2] = self.data[2] + 1
return
class Dungeon:
TileIdNumber = { "granite" : 0 , "empty" : 1, "corridor" : 2 }
def __init__(self, width, depth):
self.tiles = Numeric.zeros((width, depth))
self.width = width
self.depth = depth
# die, maybe, if lifetime expired, die with a whimper,
# else die with a bang and create a room
# returns count of rooms created so we can track them
# to do : probability of generating "special" rooms?
#
def burrower_death(self, b, bdeath_prob, lifetime, max_room_width, max_room_depth):
result = 0
# if out of time, just die quietly
if b.data[2] > lifetime:
b.kill_burrower()
# if random chance of death turns up, burst into a room
if whrandom.random() < bdeath_prob:
# we have a room
room_width = whrandom.randrange(max_room_width / 2) + 1
room_depth = whrandom.randrange(max_room_depth / 2) + 1
for x in range (b.data[0] - room_width, b.data[0] + room_width):
for y in range (b.data[1] - room_depth, b.data[1] + room_depth):
if (x >= 1) and (x < self.width-1) and (y >= 1) and (y < self.depth-1):
self.tiles[x][y] = Dungeon.TileIdNumber["empty"]
# b.kill_burrower()
result = 1
return result
def burrower_birth(self,bbirth_prob):
return whrandom.random() < bbirth_prob
def baby_burrower(self, b):
return Burrower(b.data[0] + whrandom.randrange(2), b.data[1] + whrandom.randrange(2), 0, 0, 0)
def print_dungeon(self):
for y in range(0,self.depth):
s = ""
for x in range(0,self.width):
if self.tiles[x][y] == Dungeon.TileIdNumber["granite"]:
s = s + "#"
else:
s = s + " "
print s
#
# burrowers start on one side of the level, on each turn they can either split (birth prob),
# create a room (die), have a max lifetime, and the algo terminates when room_count is reached
#
def generate(self, burrower_total, room_total, birth_prob, death_prob, turn_prob, lifetime, max_room_width, max_room_depth, allow_diagonals):
# set up burrowers
burrowers = []
for i in range(0,burrower_total):
side = whrandom.randrange(0,4)
if side == 0:
burrower = Burrower( 0, whrandom.randrange(0, self.depth) , 0, 1, 0 )
elif side == 1:
burrower = Burrower( self.width-1, whrandom.randrange(0, self.depth) , -1, 0, 0 )
elif side == 2:
burrower = Burrower( whrandom.randrange(0,self.width), 0, 0, 0, 1 )
else:
burrower = Burrower( whrandom.randrange(0,self.width), self.depth-1 , 0, 0, -1 )
burrowers.append(burrower)
room_count = 0
burrower_alive = True
i = 0
while burrower_alive:
print "Generation " , i
print "Rooms # ", room_count
self.print_dungeon()
burrower_alive = False
birth_list = []
for b in burrowers:
if not b.burrower_is_dead():
burrower_alive = True
b.move_burrower(0, 0, self.width-1, self.depth-1, turn_prob, allow_diagonals)
if (self.tiles[b.data[0]][b.data[1]] == Dungeon.TileIdNumber["corridor"]):
b.kill_burrower()
self.tiles[b.data[0]][b.data[1]] = Dungeon.TileIdNumber["corridor"]
if self.burrower_birth(birth_prob):
birth_list.append(self.baby_burrower(b))
room_count = room_count + self.burrower_death(b, death_prob, lifetime, max_room_width, max_room_depth)
if birth_list:
burrowers.extend(birth_list)
if room_count == room_total:
birth_prob = 0
death_prob = 0
return
if __name__=="__main__":
dungeon = Dungeon(64,32)
dungeon.generate(16, 32, 0.01, 0.005, 0.05, 1024, 16, 16, False)