Python Curses Example of Dungeon-Building Algorithm

From RogueBasin
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

By Zack Hovatter

import curses
import random

# Please note that curses uses a y,x coordination system.
# In the code you see here, I'll be using x,y

# If any of the code seems inconsistent, it's because I took
# some of the things from my current project out and replaced
# for the purposes of this article

# Also, some of this was poorly converted. If you want to clean
# it up, go for it. Otherwise, I might get around to it eventually ;)

# I have not included placing up/down stairs or any of that business

# TODO: COMMENT THINGS BETTER!

# This is just the basic dungeon tile. It holds a shape.
class dungeon_tile:
	_shape = ''

	def __init__(self, shape):
		self._shape = shape

	def get_shape(self):
		return self._shape

	def set_shape(self, shape):
		self._shape = shape

# Our randomly generated dungeon
class dungeon:
	_map_size = (0, 0)
	_tiles = []

	# max_features - how many rooms/corridors will be generated at the most
	# room_chance - the % chance that the generated feature will be a room
	def __init__(self, map_size_x, map_size_y, max_features, room_chance):
		# seed the psuedo-random number generator
		random.seed()

		# set the map size
		self._map_size = (map_size_x, map_size_y)

		# fill the map with blank tiles
		for x in range (0, self._map_size[0]):
			self._tiles.append([])
			for y in range (0,  self._map_size[1]):
				self._tiles[x].append(dungeon_tile(''))

		current_features = 1
		self._make_room(self._map_size[0]/2, self._map_size[1]/2, 5, 5, random.randint(0, 3))

		for i in range (0, 1000):
			if current_features == max_features: break

			newx = 0
			xmod = 0
			newy = 0
			ymod = 0
			direction = -1

			for j in range (0, 1000):
				newx = random.randint(1, self._map_size[0]-2)
				newy = random.randint(1, self._map_size[1]-2)
				direction = -1

				shape = self._tiles[newx][newy].get_shape()

				if shape == '#' or shape == 'c':
					if self._tiles[newx][newy+1].get_shape() == '.' or self._tiles[newx][newy+1].get_shape() == 'c':
						xmod = 0
						ymod = -1
						direction = 0
					elif self._tiles[newx-1][newy].get_shape() == '.' or self._tiles[newx-1][newy].get_shape() == 'c':
						xmod = 1
						ymod = 0
						direction = 1
					elif self._tiles[newx][newy-1].get_shape() == '.' or self._tiles[newx][newy-1].get_shape() == 'c':
						xmod = 0
						ymod = 1
						direction = 2
					elif self._tiles[newx+1][newy].get_shape() == '.' or self._tiles[newx+1][newy].get_shape() == 'c':
						xmod = -1
						ymod = 0
						direction = 3

					if direction > -1: break

			if direction > -1:
				feature = random.randint(0, 100)

				if feature <= room_chance:
					if self._make_room(newx+xmod, newy+ymod, 10, 10, direction):
						current_features += 1
						self._tiles[newx][newy].set_shape('+')
						self._tiles[newx+xmod][newy+ymod].set_shape('.')
				elif feature > room_chance:
					if self._make_corridor(newx+xmod, newy+ymod, 6, direction):
						current_features += 1
						self._tiles[newx][newy].set_shape('+')

	# this shows the map. we're not doing a scrolling map for this article, so it's easier
	def draw(self, stdscr):
		for x in range (0, self._map_size[0]):
			for y in range (0, self._map_size[1]):
				stdscr.addstr(y, x, self._tiles[x][y].get_shape())
		stdscr.refresh()

	# this makes a corridor at x,y in a direction
	def _make_corridor(self, x, y, length, direction):
		len = random.randint(2, length)

		dir = 0
		if direction > 0 and direction < 4: dir = direction

		if dir == 0:
			if x < 0 or x > self._map_size[0]-1: return False

			for i in range (y, y-len, -1):
				if i < 0 or i > self._map_size[1]-1: return False
				if self._tiles[x][i].get_shape() != '':return False

			for i in range (y, y-len, -1):
				self._tiles[x][i].set_shape('c')
		elif dir == 1:
			if y < 0 or y > self._map_size[1]-1: return False

			for i in range (x, x+len):
				if i < 0 or i > self._map_size[1]-1: return False
				if self._tiles[i][y].get_shape() != '': return False

			for i in range (x, x+len):
				self._tiles[i][y].set_shape('c')
		elif dir == 2:
			if x < 0 or x > self._map_size[0]-1: return False

			for i in range (y, y+len):
				if i < 0 or i > self._map_size[1]-1: return False
				if self._tiles[x][i].get_shape() != '': return False

			for i in range (y, y+len):
				self._tiles[x][i].set_shape('c')
		elif dir == 3:
			if y < 0 or y > self._map_size[1]-1: return False

			for i in range (x, x-len, -1):
				if i < 0 or i > self._map_size[1]-1: return False
				if self._tiles[i][y].get_shape() != '': return False

			for i in range (x, x-len, -1):
				self._tiles[i][y].set_shape('c')

		return True

	# this makes a room at x,y with the width,height and in a direction
	def _make_room(self, x, y, width, height, direction):
		rand_width = random.randint(4, width)
		rand_height = random.randint(4, height)

		dir = 0
		if direction > 0 and direction < 4: dir = direction

		if dir == 0:
			for i in range (y, y-rand_height, -1):
				if i < 0 or i > self._map_size[1]-1: return False
				for j in range (x-rand_width/2, (x+(rand_width+1)/2)):
					if j < 0 or j > self._map_size[0]-1: return False
					if self._tiles[j][i].get_shape() != '': return False

			for i in range (y, y-rand_height, -1):
				for j in range (x-rand_width/2, (x+(rand_width+1)/2)):
					if j == x-rand_width/2: self._tiles[j][i].set_shape('#')
					elif j == x+(rand_width-1)/2: self._tiles[j][i].set_shape('#')
					elif i == y: self._tiles[j][i].set_shape('#')
					elif i == y-rand_height+1: self._tiles[j][i].set_shape('#')
					else: self._tiles[j][i].set_shape('.')
		elif dir == 1:
			for i in range (y-rand_height/2, y+(rand_height+1)/2):
				if i < 0 or i > self._map_size[1]-1: return False
				for j in range (x, x+rand_width):
					if j < 0 or j > self._map_size[0]-1: return False
					if self._tiles[j][i].get_shape() != '': return False

			for i in range (y-rand_height/2, y+(rand_height+1)/2):
				for j in range (x, x+rand_width):
					if j == x: self._tiles[j][i].set_shape('#')
					elif j == x+(rand_width-1): self._tiles[j][i].set_shape('#')
					elif i == y-rand_height/2: self._tiles[j][i].set_shape('#')
					elif i == y+(rand_height-1)/2: self._tiles[j][i].set_shape('#')
					else: self._tiles[j][i].set_shape('.')
		elif dir == 2:
			for i in range (y, y+rand_height):
				if i < 0 or i > self._map_size[1]-1: return False
				for j in range (x-rand_width/2, x+(rand_width+1)/2):
					if j < 0 or j > self._map_size[0]-1: return False
					if self._tiles[j][i].get_shape() != '': return False

			for i in range (y, y+rand_height):
				for j in range (x-rand_width/2, x+(rand_width+1)/2):
					if j == x-rand_width/2: self._tiles[j][i].set_shape('#')
					elif j == x+(rand_width-1)/2: self._tiles[j][i].set_shape('#')
					elif i == y: self._tiles[j][i].set_shape('#')
					elif i == y+(rand_height-1): self._tiles[j][i].set_shape('#')
					else: self._tiles[j][i].set_shape('.')
		elif dir == 3:
			for i in range (y-rand_height/2, y+(rand_height+1)/2):
				if i < 0 or i > self._map_size[1]-1: return False
				for j in range (x, x-rand_width, -1):
					if j < 0 or j > self._map_size[0]-1: return False
					if self._tiles[j][i].get_shape() != '': return False

			for i in range (y-rand_height/2, y+(rand_height+1)/2):
				for j in range (x, x-rand_width-1, -1):
					if j == x: self._tiles[j][i].set_shape('#')
					elif j == x-rand_width: self._tiles[j][i].set_shape('#')
					elif i == y-rand_height/2: self._tiles[j][i].set_shape('#')
					elif i == y+(rand_height-1)/2: self._tiles[j][i].set_shape('#')
					else: self._tiles[j][i].set_shape('.')

		return True

def main(stdscr):
	screen_y, screen_x = stdscr.getmaxyx()

	this_dungeon = dungeon(screen_x, screen_y, 5, 50)
	this_dungeon.draw(stdscr)

	stdscr.getch()

if __name__ == '__main__':
	curses.wrapper(main)