git.fiddlerwoaroof.com
Raw Blame History
from __future__ import division
import time
import copy
import libtcodpy as libtcod
import random
from utilities import Rect

try: import numpypy
except ImportError: pass
import numpy as np

import objects

class Tile(object):
	def __init__(self, x,y, blocked, block_sight=None):
		self.blocked = blocked
		self.explored = False
		if block_sight is None:
			block_sight = blocked
		self.block_sight = block_sight



class AutomataEngine(object):
	def __init__(self, width=None, height=None, data=None, randomize=True):
		if data is not None:
			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) ]
		#self.data = np.array(self.data)
		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])
		#result = self.data[x1:x2,y1:y2]
		result = [ row[y1:y2] for row in self.data[x1:x2] ]
		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):
		tmp_data = np.array(self.data)

		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

			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:
				w -= (lx+w) - self.width
			if ty + h >= self.height:
				h -= (ty+h) - self.height

			print '(',lx,ty, ')', w, h
			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):
		tmp_data = np.array(self.data)

		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)

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

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

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

import collections
class Map(collections.MutableSequence):
	def __init__(self, width, height, con, level):
		print 'hello again'
		self.gen = MazeGen(width, height)
		self.map = self.data = self.gen.munge()
		#self.data = Automata1(data=self.data.data).iter(2)
		#self.map = Smoother(data=self.data.data).munge()
		self.data = self.map.to_map()

		self.width = width
		self.height = height
		self.con = con
		self.level = level
		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

	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):
		self.data[k] = v
	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

		print '\n'.join(''.join(map(str, row)) for row in self.map.data)

		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

		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
			)

		return self


	def create_room(self, room):
		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)

	def create_h_tunnel(self, x1, x2, y):
		for x in range(min(x1,x2), max(x1, x2)+1):
			self[x][y] = Tile(x,y, False)

	def create_v_tunnel(self, x, y1, y2):
		for y in range(min(y1,y2), max(y1, y2)+1):
			self[x][y] = Tile(x,y, False)

	def is_blocked(self, x,y):
		if self[x][y].blocked:
			return True

		for obj in self.level.iter_objects():
			if obj.blocks and obj.x == x and obj.y == y:
				return True

		return False

	def choose_empty_point(self, room):
		empty_points = [p for p in room.iter_cells() if not self.is_blocked(*p)]

		result = None,None
		if empty_points:
			result = random.choice(empty_points)

		return result

	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)
			print 'chosen monster: %s' % choice,
			if choice:
				x,y = self.choose_empty_point(room)
				if x is not None and y is not None:
					result = choice(self, self.level, self.con, x,y)
					print result.name, result.fighter.hp, result.fighter.power, result.fighter.defense, x,y,
			print


	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:
				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()


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