### Author: Mattsi Jansky, Anthony Thomas, Michael Hunter
### Heavily modified from Sam Delaney and Sebastius' Game of Life app.
### Also uses James Spencer's Python dungeon generator from Roguebasin.com
### Description: A dungeon crawler for SHA 2017's badge
### Category: Games
### License: MIT
### Appname: GoL
### Built-in: no

import badge
import ugfx
import urandom
import deepsleep
import random
from random import randint
import utime
import sys


### BEGIN block of third party code
### The following code by James Spencer taken from Rogue Basin: http://www.roguebasin.com/index.php?title=A_Simple_Dungeon_Generator_for_Python_2_or_3
### (With slight adaptions)

# generator-1.py, a simple python dungeon generator by
# James Spencer <jamessp [at] gmail.com>.
 
# To the extent possible under law, the person who associated CC0 with
# pathfinder.py has waived all copyright and related or neighboring rights
# to pathfinder.py.
 
# You should have received a copy of the CC0 legalcode along with this
# work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

CHARACTER_TILES = {'stone': ' ',
				   'floor': '.',
				   'wall': '#'}


class Generator():
	def __init__(self, width=36, height=16, max_rooms=4, min_room_xy=3,
				 max_room_xy=6, rooms_overlap=False, random_connections=1,
				 random_spurs=3, tiles=CHARACTER_TILES):
		self.width = width
		self.height = height
		self.max_rooms = max_rooms
		self.min_room_xy = min_room_xy
		self.max_room_xy = max_room_xy
		self.rooms_overlap = rooms_overlap
		self.random_connections = random_connections
		self.random_spurs = random_spurs
		self.tiles = CHARACTER_TILES
		self.level = []
		self.room_list = []
		self.corridor_list = []
		self.tiles_level = []
 
	def gen_room(self):
		x, y, w, h = 0, 0, 0, 0
 
		w = random.randint(self.min_room_xy, self.max_room_xy)
		h = random.randint(self.min_room_xy, self.max_room_xy)
		x = random.randint(1, (self.width - w - 1))
		y = random.randint(1, (self.height - h - 1))
 
		return [x, y, w, h]
 
	def room_overlapping(self, room, room_list):
		x = room[0]
		y = room[1]
		w = room[2]
		h = room[3]
 
		for current_room in room_list:
 
			# The rectangles don't overlap if
			# one rectangle's minimum in some dimension
			# is greater than the other's maximum in
			# that dimension.
 
			if (x < (current_room[0] + current_room[2]) and
				current_room[0] < (x + w) and
				y < (current_room[1] + current_room[3]) and
				current_room[1] < (y + h)):
 
				return True
 
		return False
 
 
	def corridor_between_points(self, x1, y1, x2, y2, join_type='either'):
		if x1 == x2 and y1 == y2 or x1 == x2 or y1 == y2:
			return [(x1, y1), (x2, y2)]
		else:
			# 2 Corridors
			# NOTE: Never randomly choose a join that will go out of bounds
			# when the walls are added.
			join = None
			if join_type is 'either' and set([0, 1]).intersection(
				 set([x1, x2, y1, y2])):
 
				join = 'bottom'
			elif join_type is 'either' and set([self.width - 1,
				 self.width - 2]).intersection(set([x1, x2])) or set(
				 [self.height - 1, self.height - 2]).intersection(
				 set([y1, y2])):
 
				join = 'top'
			elif join_type is 'either':
				join = random.choice(['top', 'bottom'])
			else:
				join = join_type
 
			if join is 'top':
				return [(x1, y1), (x1, y2), (x2, y2)]
			elif join is 'bottom':
				return [(x1, y1), (x2, y1), (x2, y2)]
 
	def join_rooms(self, room_1, room_2, join_type='either'):
		# sort by the value of x
		sorted_room = [room_1, room_2]
		sorted_room.sort(key=lambda x_y: x_y[0])
 
		x1 = sorted_room[0][0]
		y1 = sorted_room[0][1]
		w1 = sorted_room[0][2]
		h1 = sorted_room[0][3]
		x1_2 = x1 + w1 - 1
		y1_2 = y1 + h1 - 1
 
		x2 = sorted_room[1][0]
		y2 = sorted_room[1][1]
		w2 = sorted_room[1][2]
		h2 = sorted_room[1][3]
		x2_2 = x2 + w2 - 1
		y2_2 = y2 + h2 - 1
 
		# overlapping on x
		if x1 < (x2 + w2) and x2 < (x1 + w1):
			jx1 = random.randint(x2, x1_2)
			jx2 = jx1
			tmp_y = [y1, y2, y1_2, y2_2]
			tmp_y.sort()
			jy1 = tmp_y[1] + 1
			jy2 = tmp_y[2] - 1
 
			corridors = self.corridor_between_points(jx1, jy1, jx2, jy2)
			self.corridor_list.append(corridors)
 
		# overlapping on y
		elif y1 < (y2 + h2) and y2 < (y1 + h1):
			if y2 > y1:
				jy1 = random.randint(y2, y1_2)
				jy2 = jy1
			else:
				jy1 = random.randint(y1, y2_2)
				jy2 = jy1
			tmp_x = [x1, x2, x1_2, x2_2]
			tmp_x.sort()
			jx1 = tmp_x[1] + 1
			jx2 = tmp_x[2] - 1
 
			corridors = self.corridor_between_points(jx1, jy1, jx2, jy2)
			self.corridor_list.append(corridors)
 
		# no overlap
		else:
			join = None
			if join_type is 'either':
				join = random.choice(['top', 'bottom'])
			else:
				join = join_type
 
			if join is 'top':
				if y2 > y1:
					jx1 = x1_2 + 1
					jy1 = random.randint(y1, y1_2)
					jx2 = random.randint(x2, x2_2)
					jy2 = y2 - 1
					corridors = self.corridor_between_points(
						jx1, jy1, jx2, jy2, 'bottom')
					self.corridor_list.append(corridors)
				else:
					jx1 = random.randint(x1, x1_2)
					jy1 = y1 - 1
					jx2 = x2 - 1
					jy2 = random.randint(y2, y2_2)
					corridors = self.corridor_between_points(
						jx1, jy1, jx2, jy2, 'top')
					self.corridor_list.append(corridors)
 
			elif join is 'bottom':
				if y2 > y1:
					jx1 = random.randint(x1, x1_2)
					jy1 = y1_2 + 1
					jx2 = x2 - 1
					jy2 = random.randint(y2, y2_2)
					corridors = self.corridor_between_points(
						jx1, jy1, jx2, jy2, 'top')
					self.corridor_list.append(corridors)
				else:
					jx1 = x1_2 + 1
					jy1 = random.randint(y1, y1_2)
					jx2 = random.randint(x2, x2_2)
					jy2 = y2_2 + 1
					corridors = self.corridor_between_points(
						jx1, jy1, jx2, jy2, 'bottom')
					self.corridor_list.append(corridors)
 
 
	def gen_level(self):
 
		# build an empty dungeon, blank the room and corridor lists
		for i in range(self.height):
			self.level.append(['stone'] * self.width)
		self.room_list = []
		self.corridor_list = []
 
		max_iters = self.max_rooms * 5
 
		for a in range(max_iters):
			tmp_room = self.gen_room()
 
			if self.rooms_overlap or not self.room_list:
				self.room_list.append(tmp_room)
			else:
				tmp_room = self.gen_room()
				tmp_room_list = self.room_list[:]
 
				if self.room_overlapping(tmp_room, tmp_room_list) is False:
					self.room_list.append(tmp_room)
 
			if len(self.room_list) >= self.max_rooms:
				break
 
		# connect the rooms
		for a in range(len(self.room_list) - 1):
			self.join_rooms(self.room_list[a], self.room_list[a + 1])
 
		# do the random joins
		for a in range(self.random_connections):
			room_1 = self.room_list[random.randint(0, len(self.room_list) - 1)]
			room_2 = self.room_list[random.randint(0, len(self.room_list) - 1)]
			self.join_rooms(room_1, room_2)
 
		# do the spurs
		for a in range(self.random_spurs):
			room_1 = [random.randint(2, self.width - 2), random.randint(
					 2, self.height - 2), 1, 1]
			room_2 = self.room_list[random.randint(0, len(self.room_list) - 1)]
			self.join_rooms(room_1, room_2)
 
		# fill the map
		# paint rooms
		for room_num, room in enumerate(self.room_list):
			for b in range(room[2]):
				for c in range(room[3]):
					self.level[room[1] + c][room[0] + b] = 'floor'
 
		# paint corridors
		for corridor in self.corridor_list:
			x1, y1 = corridor[0]
			x2, y2 = corridor[1]
			for width in range(abs(x1 - x2) + 1):
				for height in range(abs(y1 - y2) + 1):
					self.level[min(y1, y2) + height][
						min(x1, x2) + width] = 'floor'
 
			if len(corridor) == 3:
				x3, y3 = corridor[2]
 
				for width in range(abs(x2 - x3) + 1):
					for height in range(abs(y2 - y3) + 1):
						self.level[min(y2, y3) + height][
							min(x2, x3) + width] = 'floor'
 
		# paint the walls
		for row in range(1, self.height - 1):
			for col in range(1, self.width - 1):
				if self.level[row][col] == 'floor':
					if self.level[row - 1][col - 1] == 'stone':
						self.level[row - 1][col - 1] = 'wall'
 
					if self.level[row - 1][col] == 'stone':
						self.level[row - 1][col] = 'wall'
 
					if self.level[row - 1][col + 1] == 'stone':
						self.level[row - 1][col + 1] = 'wall'
 
					if self.level[row][col - 1] == 'stone':
						self.level[row][col - 1] = 'wall'
 
					if self.level[row][col + 1] == 'stone':
						self.level[row][col + 1] = 'wall'
 
					if self.level[row + 1][col - 1] == 'stone':
						self.level[row + 1][col - 1] = 'wall'
 
					if self.level[row + 1][col] == 'stone':
						self.level[row + 1][col] = 'wall'
 
					if self.level[row + 1][col + 1] == 'stone':
						self.level[row + 1][col + 1] = 'wall'
 
	def gen_tiles_level(self):
 
		for row_num, row in enumerate(self.level):
			tmp_tiles = []
 
			for col_num, col in enumerate(row):
				if col == 'stone':
					tmp_tiles.append(self.tiles['stone'])
				if col == 'floor':
					tmp_tiles.append(self.tiles['floor'])
				if col == 'wall':
					tmp_tiles.append(self.tiles['wall'])
 
			self.tiles_level.append(''.join(tmp_tiles))
	   
		return self.tiles_level
	   
### END OF GENERATOR

### constants
MSG_INTERACT = "interact"
MSG_DAMAGE = "damage"
MSG_ATTACK = "attack"
MSG_DEFEND = "defend"
MSG_REPORT_HP = "report hp"
STUFF_SOURCE = "source"
STUFF_TARGET = "target"
STUFF_VALUE = "value"
STUFF_ATTACK = "attack"
STUFF_DAMAGE = "damage"
STUFF_DEFEND = "defend"
#render, name, strength, defense, HP
mobTypes = [ ['h','harpy',3,5,4], ['s','skeleton',1,1,20], ['d','dickbutt',2,2,10], ['n','ninja',1,7,1] ]
adverbs = ["lightly", "deeply", "feebly", "firmly", "sloppily", "brutally"]
verbs = ["bemoans", "harasses", "molests", "manhandles", "slaps", "caresses", "aggrieves"]

class Position:
   
	def __init__(self, x, y):
		self.x = x
		self.y = y
	   
	def add(self,x,y):
		return Position(self.x + x, self.y + y)

class Message:
   
	def __init__(self, name):
		self.name = name
		self.stuff = {}

class Entity:
   
	def __init__(self, render, name, position):
		self.render = render
		self.name = name
		self.position = position
		self.components = []
   
	def recieve(self, msg):
		for component in self.components:
			component.recieve(msg)

class Component:
   
	def recieve(self, msg) : raise NotImplementedError

class CharacterComponent(Component):

	def __init__(self, str, defn, hp):
		self.str = str
		self.defn = defn
		self.hp = hp
   
	def recieve(self, msg):
		if(msg.name == MSG_ATTACK):
			msg.stuff[STUFF_ATTACK] = randint(1,20) + self.str
			msg.stuff[STUFF_DAMAGE] = randint(1,6) + self.str
		if(msg.name == MSG_DEFEND):
			msg.stuff[STUFF_DEFEND] = 10 + self.defn
		if(msg.name == MSG_INTERACT):
			fight(msg.stuff[STUFF_SOURCE], msg.stuff[STUFF_TARGET])
		if(msg.name == MSG_DAMAGE):
			self.hp -= msg.stuff[STUFF_VALUE]
		if(msg.name == MSG_REPORT_HP):
			msg.stuff[STUFF_VALUE] = self.hp

class Tile:
   
	def __init__(self, render):
		self.render = render
		self.entity = None
   
	def __str__(self):
		if(self.entity is not None):
			return self.entity.render
		else:
			return self.render

#### globals
width = 36
height = 16
screenHeight = 7
cell_width = 8
cell_height = 12
grid = [[Tile('.') for x in range(height)] for y in range(width)]
playername = badge.nvs_get_str("owner", "name", "player")
player = Entity('@',playername[0:5], Position(1,1))
player.components.append(CharacterComponent(2,2,20))
grid[1][1].entity = player
camera = Position(0,0)
mobs = []
console = []
# rooms = []
walls = [' ','#']
hasPlayerActed = False

def interact(source,target):
	msg = Message(MSG_INTERACT)
	msg.stuff[STUFF_SOURCE] = source
	msg.stuff[STUFF_TARGET] = target
	source.recieve(msg)

class Manipulator:
   
	def move(self,entity,position):
		origin = grid[entity.position.x][entity.position.y]
		target = grid[position.x][position.y]
		if(target.entity is None and target.render not in walls):
			origin.entity = None
			target.entity = entity
			entity.position = position
		elif(target.entity is not None):
			interact(entity, target.entity)

manipulator = Manipulator()

def log(message):
	if(len(message) < 36):
		console.append(message)
	if(len(console) > 3):
		del console[0]

def makemob(position):
	mobtype = mobTypes[randint(0,len(mobTypes)-1)]
	mob = Entity(mobtype[0], mobtype[1], position)
	mob.components.append(CharacterComponent(mobtype[2],mobtype[3],mobtype[4]))
	mobs.append(mob)
	return mob

def attackDescription():
	return "%s %s" % (adverbs[randint(0,len(adverbs)-1)], verbs[randint(0,len(verbs)-1)])

def fight(attacker, defender):
	attackMsg = Message(MSG_ATTACK)
	defendMsg = Message(MSG_DEFEND)
	attacker.recieve(attackMsg)
	defender.recieve(defendMsg)
	if(attackMsg.stuff[MSG_ATTACK] > defendMsg.stuff[MSG_DEFEND]):
		log("%s %s %s, %s DMG" % (attacker.name, attackDescription(), defender.name, attackMsg.stuff[STUFF_DAMAGE]))
		damageMsg = Message(MSG_DAMAGE)
		damageMsg.stuff[STUFF_VALUE] = attackMsg.stuff[STUFF_DAMAGE]
		defender.recieve(damageMsg)
	else:
		log("%s blocked %s's ATK!" % (defender.name, attacker.name))

def randomise(rooms):
	for i in range(randint(5,15)):
		i = 0
		room = rooms[randint(0,len(rooms)-1)]
		pos = Position(randint(room[0],room[0]+room[2]),randint(room[1],room[1]+room[3]))
		if(grid[pos.x][pos.y].entity is None):
			grid[pos.x][pos.y].entity = makemob(pos)

def moveMob(entity):
	direction = 1
	if(randint(0,1) == 0):
		direction = -direction
	if(randint(0,1) == 0):
		manipulator.move(entity,entity.position.add(direction,0))
	else:
		manipulator.move(entity,entity.position.add(0,direction))

def updateGame():
	for mob in mobs:
		msg = Message(MSG_REPORT_HP)
		mob.recieve(msg)
		if(msg.stuff[STUFF_VALUE] > 0):
			moveMob(mob)
		else:
			mobs.remove(mob)
			grid[mob.position.x][mob.position.y].entity = None
			log("%s has died" % (mob.name))

def left(pressed):
	global hasPlayerActed
	if(pressed):
		manipulator.move(player,player.position.add(-1,0))
		hasPlayerActed = True
   
def right(pressed):
	global hasPlayerActed
	if(pressed):
		manipulator.move(player,player.position.add(1,0))
		hasPlayerActed = True
   
def up(pressed):
	global hasPlayerActed
	if(pressed):
		manipulator.move(player,player.position.add(0,-1))
		hasPlayerActed = True
   
def down(pressed):
	global hasPlayerActed
	if(pressed):
		manipulator.move(player,player.position.add(0,1))
		hasPlayerActed = True

def select(pressed):
	log("in select")

def getPlayerHealth():
	msg = Message(MSG_REPORT_HP)
	player.recieve(msg)
	return msg.stuff[STUFF_VALUE]

def placePlayer(rooms):
	playerPlaced = False
	for room in rooms:
		for x in range(room[0],room[0]+room[2]):
			for y in range(room[1],room[1]+room[3]):
				if(grid[x][y].entity is None and grid[x][y].render not in walls):
					grid[player.position.x][player.position.y].entity = None
					player.position = Position(x,y)
					grid[x][y].entity = player
					playerPlaced = True
					break;
			if(playerPlaced):
				break;
		



def emj_test():
	global hasPlayerActed
	random.seed(utime.time())
	badge.eink_init()
	ugfx.init()
	ugfx.input_init()
	ugfx.input_attach(ugfx.JOY_RIGHT, right)
	ugfx.input_attach(ugfx.JOY_LEFT, left)
	ugfx.input_attach(ugfx.JOY_UP, up)
	ugfx.input_attach(ugfx.JOY_DOWN, down)
	#ugfx.input_attach(ugfx.BTN_A, reboot)
	#ugfx.input_attach(ugfx.BTN_B, reboot)
	ugfx.input_attach(ugfx.BTN_START, reboot)
	ugfx.input_attach(ugfx.BTN_SELECT, select)
	ugfx.clear(ugfx.WHITE)
	ugfx.flush()
	gen = Generator()
	gen.gen_level()
	print("Room List")
	print(gen.room_list)
	for y, row in enumerate(gen.gen_tiles_level()):
			for x, c in enumerate(row):
				grid[x][y].render = c
	print("Rooms now")
	print(gen.room_list)
	these_rooms = gen.room_list
	gen = None
	randomise(these_rooms)
	placePlayer(these_rooms)
   
	def display():
		camera = player.position.add(-18,-3)
		#main screen
		ugfx.clear(ugfx.WHITE)
		for x in range(0, width):
			for y in range(0, screenHeight):
				char = ' '
				if(x + camera.x > 0 and y + camera.y > 0 and x + camera.x < len(grid) and y + camera.y < len(grid[0])):
					char = str(grid[x + camera.x][y + camera.y])
				ugfx.text(x * cell_width, y * cell_height, char, ugfx.BLACK)
		#console
		ugfx.line(0,screenHeight * cell_height + 1, width * cell_width, screenHeight * cell_height + 1, ugfx.BLACK)
		for index, msg in enumerate(console):
			ugfx.text(0,screenHeight * cell_height + 1 + index * cell_height, msg, ugfx.BLACK)
	   
		badge.eink_busy_wait()
		ugfx.flush()

	while (getPlayerHealth() > 0):
		if(hasPlayerActed):
			updateGame()
			hasPlayerActed = False
		display()
   
	ugfx.clear(ugfx.WHITE)
	ugfx.flush()
	ugfx.clear(ugfx.WHITE)
	ugfx.flush()
	ugfx.text(50,100,"YOU DIED",ugfx.BLACK)
	ugfx.flush()
   
def reboot(wut):
	deepsleep.reboot()


emj_test()