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.
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)