git.fiddlerwoaroof.com
maps.py
539d5ff4
 from __future__ import division
67ab39f4
 import time
539d5ff4
 import copy
76408e86
 import libtcodpy as libtcod
 import random
 from utilities import Rect
 
6bdb44be
 try: import numpypy
 except ImportError: pass
 import numpy as np
 
76408e86
 import objects
 
 class Tile(object):
539d5ff4
 	def __init__(self, x,y, blocked, block_sight=None):
76408e86
 		self.blocked = blocked
 		self.explored = False
 		if block_sight is None:
 			block_sight = blocked
 		self.block_sight = block_sight
 
 
160f4887
 
539d5ff4
 class AutomataEngine(object):
 	def __init__(self, width=None, height=None, data=None, randomize=True):
6bdb44be
 		if data is not None:
539d5ff4
 			self.data = data
 		elif randomize:
 			self.data = [ [ random.choice([0]+[1]*3) for y in range(height)] for x in range(width) ]
 		else:
 			self.data = [ [1 for y in range(height)] for x in range(width) ]
67ab39f4
 		#self.data = np.array(self.data)
539d5ff4
 		self.width = width
 		self.height = height
 
 	def get_rect(self, p1, p2):
 		x1,y1 = p1
 		x2,y2 = p2
 		x1,x2 = min([x1,x2]), max([x1,x2])
 		y1,y2 = min([y1,y2]), max([y1,y2])
67ab39f4
 		#result = self.data[x1:x2,y1:y2]
 		result = [ row[y1:y2] for row in self.data[x1:x2] ]
539d5ff4
 		return result
 
 	def sum_area(self, p1, p2=None, summator=sum):
 		rect = p1
 		if isinstance(p2, int):
 			x,y = rect
 			rect = self.get_rect( (x-p2,y-p2), (x+p2,y+p2) )
 		elif p2 is not None:
 			rect = self.get_rect(p1,p2)
 
 		return summator( summator(row) for row in rect )
 
 	def iter_with_coords(self):
 		for x,row in enumerate(self.data):
 			for y,cell in enumerate(row):
 				yield x,y,cell
 
 	def munge(self):
6bdb44be
 		tmp_data = np.array(self.data)
539d5ff4
 
 		for x,row in enumerate(self.data):
 			for y,cell in enumerate(row):
 				tmp_data[x][y] = self.rule(x,y, cell)
 		return self.__class__(data=tmp_data)
 
 	def iter(self, num):
 		result = self
 		for x in range(num):
 			result = self.munge()
 		return result
 
 	def to_map(self):
 		data = [[0]*len(self.data[0]) for _ in self.data]
 
 		for x,y,cell in self.iter_with_coords():
 			data[x][y] = Tile(x,y, cell in {1,2}, cell == 1)
 		return data
 
 class MazeGen(AutomataEngine):
 	def __init__(self, width=None, height=None, data=None, randomize=True, num_rooms=12, max_width=15, max_height=15, **kw):
 		kw['randomize'] = False
 		AutomataEngine.__init__(self, width, height, data, **kw)
 
 
 		points = [ (random.randrange(self.width), random.randrange(self.height))
 			for _ in range(num_rooms)
 		]
 
 		connections = []
 		for p in points:
 			p2 = random.choice(points)
 			while p == p2:
 				p2 = random.choice(points)
 			connections.append( (p,p2) )
 
 		for p,p2 in connections:
 			self.connect_points(p,p2, 9)
 
 		self.rooms = []
 		self.expand_rooms(points, max_width, max_height)
 
 	def connect_points(self, p1, p2, steps=4):
 		x1,y1 = p1
 		x2,y2 = p2
 		steps = random.randrange(2, steps+1)
 
 		cx, cy = x1,y1
 		h_steps = random.randrange(1,steps) # always at least one vstep
 		v_steps = steps - h_steps
 
 		while (cx,cy) != (x2,y2):
 			if h_steps > 0 and random.random() < .5:
 				dx = int(x2-cx)//(h_steps)
 				stop = cx+dx
 
 				a = min([stop,cx])
 				b = max([stop,cx])
 				for x in range(a,b):
 					self.data[x][cy] = 0
 
 				h_steps -= 1
 
 				cx = stop
 
 			elif v_steps > 0:
 
 				dy = int(y2-cy)//(v_steps)
 				stop = cy+dy
 
 				a = min([stop,cy])
 				b = max([stop,cy])
 				for y in range(a,b):
 					self.data[cx][y] = 0
 
 				v_steps -= 1
 				cy = stop
 			elif cx != x2:
 				stop = x2
 				a = min([stop,cx])
 				b = max([stop,cx])
 				for x in range(a,b+1):
 					self.data[x][cy] = 0
 			elif cy != y2:
 				stop = y2
 				a = min([stop,cy])
 				b = max([stop,cy])
 				for y in range(a,b+1):
 					self.data[cx][y] = 0
 			else:
 				print 'ouch'
 				break
 
 	def expand_rooms(self, points, max_width, max_height):
 		for point in points:
 			left_offset = random.randrange(int(max_width/2))
 			up_offset = random.randrange(int(max_height/2))
 
 			cx, cy = point
 			lx, ty = cx-left_offset, cy-up_offset
bdc4654e
 
539d5ff4
 			if lx < 0:
 				max_width += lx
 				lx = 0
 			if ty < 0:
 				max_height += ty
 				ty = 0
 			w, h = random.randrange(1,max_width+1), random.randrange(1, max_height+1)
 
 			if lx + w >= self.width:
bdc4654e
 				w -= (lx+w) - self.width
539d5ff4
 			if ty + h >= self.height:
bdc4654e
 				h -= (ty+h) - self.height
539d5ff4
 
bdc4654e
 			print '(',lx,ty, ')', w, h
539d5ff4
 			room = Rect(lx,ty, w,h)
 			success = True
 			for o_room in self.rooms:
 				success = not o_room ^ room
 			if success:
 				self.rooms.append(room)
 
 	def rule(self, x,y, cell):
 		for room in self.rooms:
 			if (x,y) in room:
 				return 0
 		return cell
 
 	def munge(self):
6bdb44be
 		tmp_data = np.array(self.data)
539d5ff4
 
 		for x,row in enumerate(self.data):
 			for y,cell in enumerate(row):
 				tmp_data[x][y] = self.rule(x,y, cell)
 		return AutomataEngine(data=tmp_data)
 
160f4887
 class AutomataLoader(AutomataEngine):
 	def load_rules(self):
 		self.rules = yaml.load(
 			file(
 				os.path.join('.', 'data', 'mapgenerator.yml')
 			)
 		)
 
 	def parse_rule(self, rule):
 		comp, val = rule.split('->')
 		val = val.strip()
 		if comp == 'is':
 			if val == 'odd':
 				return lambda a: (a%2)==1
 			elif val == 'even':
 				return lambda a: (a%2)==0
 			else:
 				return lambda a: a == int(val)
 		else:
 			val = int(val)
 			if comp.startswith('>'):
 				if comp.startswith('>='):
 					return lambda a: a >= val
 				return lambda a: a > val
 
 			elif comp.startswith('<'):
 				if comp.startswith('<='):
 					return lambda a: a <= val
 				return lambda a: a < val
 
 			elif comp.startswith('=='):
 				return lambda a: a == val
 
 	def rule(self, x,y, cell):
 		sum = self.sum_area((x,y), 1)
 		for rule in self.rules:
 			rule, _, result = rule.partition('::')
 			if parse_rule(rule)(cell):
 				if hasattr(result, 'upper') and result.lower()=='cell':
 					result = cell
 				return result
539d5ff4
 
 class Automata1(AutomataEngine):
 	def rule(self, x,y, cell):
 		sum = self.sum_area( (x,y), 1 ) - cell
 
 		if cell == 0 and sum > 3:
 			return 0
 		elif sum > 5:
 			return 1
 		elif sum in {2,3}:
 			return cell
 		else:
 			return 0
 		#elif sum <= 1:
 		#	return 1
 		#else:
 		#	return cell
 
 
 
 class Smoother(AutomataEngine):
 	def rule(self, x,y, cell):
 		sum = self.sum_area( (x,y), 1 )
 
 		if sum <= 1 and cell == 1:
 			return 0
 		if sum == 8 and cell == 0:
 			return 1
 		else:
 			return cell
 
1d2d26d2
 class NewSmoother(AutomataEngine):
 	def rule(self, x,y, cell):
 		avg = self.sum_area( (x,y), 2 ) / 16
 		if avg < .5:
 			return 0
 		else:
 			return 1
 
76408e86
 import collections
 class Map(collections.MutableSequence):
 	def __init__(self, width, height, con, level):
539d5ff4
 		print 'hello again'
 		self.gen = MazeGen(width, height)
160f4887
 		self.map = self.data = self.gen.munge()
67ab39f4
 		#self.data = Automata1(data=self.data.data).iter(2)
 		#self.map = Smoother(data=self.data.data).munge()
160f4887
 		self.data = self.map.to_map()
76408e86
 
 		self.width = width
 		self.height = height
 		self.con = con
 		self.level = level
539d5ff4
 		self.player = None
 		self._map_entrance = None
 
 	@property
 	def map_entrance(self):
 		if self._map_entrance:
 			return self._map_entrance
 		else:
 			return 0,0
 	@map_entrance.setter
 	def map_entrance(self, val):
 		assert not self.is_blocked(*val)
 		self._map_entrance = val
 
 	#def enter(self, player):
 	#	self.player = player
 	#	return self
76408e86
 
 	def __iter__(self):
 		return iter(self.data)
 	def __len__(self):
 		return len(self.data)
 	def __contains__(self, it):
 		return it in self.data
 	def __getitem__(self, k):
 		return self.data[k]
 	def __setitem__(self, k,v):
1d2d26d2
 		self.data[k] = v
76408e86
 	def __delitem__(self, k):
 		del self.data[k]
 
 	def iter_cells_with_coords(self):
 		'''NOTE: row is a list, col an instance of Tile'''
 		for x,row in enumerate(self):
 			for y,cell in enumerate(row):
 				yield (x,y,cell)
 
 	def insert(self, *a):
 		self.data.insert(*a)
 
 	def populate_map(self, max_rooms, min_size, max_size, monster_types, max_num_monsters, item_types, max_num_items):
 		rooms = []
 		num_rooms = 0
 
67ab39f4
 		print '\n'.join(''.join(map(str, row)) for row in self.map.data)
 
539d5ff4
 		for x in range(self.width):
 			for y in range(self.height):
 				if x in {0,self.width-1} or y in {0,self.height-1}:
 					self[x][y].blocked = True
 					self[x][y].block_sight = True
76408e86
 
539d5ff4
 		start_room = self.gen.rooms[0]
 		x,y = start_room.random_point
 		while self.is_blocked(x,y):
 			x,y = start_room.random_point
 		self.map_entrance = x,y
 
 		self.place_items(self.gen.rooms[0], item_types, max_num_items)
 		for r in self.gen.rooms[1:]:
 			self.place_objects(r,
 				monster_types, max_num_monsters,
 				item_types, max_num_items
 			)
 
76408e86
 		return self
 
 
 	def create_room(self, room):
539d5ff4
 		for x in range(room.x1, room.x2+1):
 			for y in range(room.y1, room.y2+1):
 				if x in {room.x1,room.x2} or y in {room.y1,room.y2}:
 					self[x][y] = Tile(x,y, True)
76408e86
 
 	def create_h_tunnel(self, x1, x2, y):
 		for x in range(min(x1,x2), max(x1, x2)+1):
539d5ff4
 			self[x][y] = Tile(x,y, False)
76408e86
 
 	def create_v_tunnel(self, x, y1, y2):
 		for y in range(min(y1,y2), max(y1, y2)+1):
539d5ff4
 			self[x][y] = Tile(x,y, False)
76408e86
 
 	def is_blocked(self, x,y):
 		if self[x][y].blocked:
 			return True
 
539d5ff4
 		for obj in self.level.iter_objects():
76408e86
 			if obj.blocks and obj.x == x and obj.y == y:
 				return True
 
 		return False
 
 	def choose_empty_point(self, room):
539d5ff4
 		empty_points = [p for p in room.iter_cells() if not self.is_blocked(*p)]
2dda4cb6
 
 		result = None,None
539d5ff4
 		if empty_points:
2dda4cb6
 			result = random.choice(empty_points)
 
 		return result
76408e86
 
 	def place_objects(self, room, monster_types, max_num_monsters, item_types, max_num_items):
 		self.place_monsters(room, monster_types, max_num_monsters)
 		self.place_items(room, item_types, max_num_items)
 
 	def place_monsters(self, room, monster_types, max_num):
 		num_monsters = random.randrange(1, max_num)
 		for i in range(num_monsters):
 			choice = choose_obj(monster_types)
42741360
 			print 'chosen monster: %s' % choice,
76408e86
 			if choice:
 				x,y = self.choose_empty_point(room)
539d5ff4
 				if x is not None and y is not None:
42741360
 					result = choice(self, self.level, self.con, x,y)
 					print result.name, result.fighter.hp, result.fighter.power, result.fighter.defense, x,y,
 			print
76408e86
 
 
 	def place_items(self, room, item_types, max_num):
 		num_items = random.randrange(0, max_num)
 		for i in range(num_items):
 			item_type = choose_obj(item_types)
 			if item_type:
539d5ff4
 				x,y = self.choose_empty_point(room)
 				if x is not None and y is not None:
 					self.level.add_object(
 						objects.Object(self,
 							self.con, x,y, item_type.char, item_type.name, item_type.color,
 
 							item=item_type(),
 						)
 					).send_to_back()
76408e86
 
 
 def choose_obj(chance_dict):
 	tot = float(
 		sum(chance_dict.values())
 	)
 	accum = 0
 	result = None
 	rand = random.randrange(100)
 	for choice, chance in chance_dict.items():
 		if choice:
 			accum += (chance/tot) * 100
 			if rand < accum:
 				result = choice
 				break
 	return result