#!/usr/bin/env python2 --
import math, os, random, time

import pygame

pygame.init()
#music = pygame.mixer.music.load("dat/music.ogg")

WIDTH = 320
HEIGHT = 200
SCALE = 3

FPS = 60.0
SPF = 1.0/FPS
CAMBORDER = 64

real_camx = WIDTH//2
real_camy = HEIGHT//2

# Set up display
pygame.display.set_caption("Portal DAGger")
screen_real = pygame.display.set_mode((WIDTH*SCALE, HEIGHT*SCALE), 0, 0)
screen = pygame.Surface((WIDTH, HEIGHT), 0, screen_real.get_bitsize()).convert(screen_real)

def subtile(img, w, h, cx, cy):
	"""
	Gets a tile-indexed w by h subsurface from the tilemap "img".
	"""

	return img.subsurface((cx*w, cy*h, w, h))

# Load some images
img_tiles = pygame.image.load(os.path.join("dat", "tiles.tga"))
#img_tiles.set_colorkey(0, pygame.RLEACCEL)
img_tiles = img_tiles.convert(screen)

img_tiles_floor = subtile(img_tiles, 16, 16, 0, 1)
img_tiles_portal = subtile(img_tiles, 16, 16, 0, 2)
img_tiles_wall = subtile(img_tiles, 16, 16, 0, 3)
img_tiles_doorno = subtile(img_tiles, 16, 16, 0, 4)
img_tiles_dooryes = subtile(img_tiles, 16, 16, 0, 5)
img_tiles_portal_nextlv = subtile(img_tiles, 16, 16, 0, 6)
img_tiles_portal_firstlv = subtile(img_tiles, 16, 16, 0, 7)

def rgb(r, g, b):
	"""
	Turns an RGB tuple into a colour value.
	"""

	return screen.map_rgb((r, g, b))

def flip_screen():
	"""
	Push the back buffer to the front buffer.
	That is, actually draw what you were drawing to the screen.
	"""

	pygame.transform.scale(screen,
		(screen_real.get_width(), screen_real.get_height()),
		screen_real)
	pygame.display.flip()

class BaseCell:
	color = rgb(255, 0, 255)
	imgs = None
	solid = True

	def draw(self, surf, x, y, world):
		"""
		Draws the cell to the surface "surf".
		"""
		if self.imgs == None:
			# No image - draw a rect
			pass
			#pygame.draw.rect(surf, self.color, (x, y, 16, 16))

		elif type(self.imgs) in (list, tuple,):
			# Indexable sequence - pick an image
			# TODO: do more than just the first image
			surf.blit(self.imgs[0], (x, y))

		else:
			# Assuming this is a single image
			surf.blit(self.imgs, (x, y))

	def on_enter(self, ent):
		"""
		CALLBACK: Handles entry of an entity.
		"""
		pass

	def on_exit(self, ent):
		"""
		CALLBACK: Handles exit of an entity.
		"""
		pass

	def is_solid(self, world=None):
		"""
		Determines if this cell is solid or not.
		"""
		return self.solid

class BaseEnt:
	color = rgb(255, 0, 255)
	rect = (4, 4, 8, 8)
	world = 0

	def __init__(self, lvl, cx, cy):
		"""
		Places an entity on level "lvl" at cell cx, cy.
		"""
		self.lvl = lvl
		self.cx, self.cy = cx, cy
		self.ox, self.oy =  0,  0

	def draw(self, surf, x, y, world):
		"""
		Draws the entity at pixel position x, y.
		"""
		rx, ry, rw, rh = self.rect
		rx += x + self.cx*16 + self.ox
		ry += y + self.cy*16 + self.oy
		pygame.draw.rect(surf, self.color, (rx, ry, rw, rh))

	def tick(self):
		"""
		Logic update for entity.
		"""

		# To be overrided by subclasses.
		pass

class PlayerEnt(BaseEnt):
	color = rgb(0, 255, 255)

	# TODO: Move this function to a better spot eventually
	def cell_is_walkable(self, cx, cy, world=None):
		"""
		Determine if we can walk onto a given cell.
		"""

		# Get the relevant "world" we're checking this for
		if world == None:
			world = self.world

		# Get cell
		cell = self.lvl.get_cell(cx, cy)

		# Check if None
		if cell == None:
			return False # NEVER WALKABLE!

		# Check if solid
		return not cell.is_solid(world=world)

	def tick(self):
		# Move to relevant cell if need be
		if not(self.ox == 0 and self.oy == 0):
			if self.ox != 0:
				self.ox += (1 if self.ox < 0 else -1)
			if self.oy != 0:
				self.oy += (1 if self.oy < 0 else -1)

			# If we've just entered the cell, call on_enter
			if self.ox == 0 and self.oy == 0:
				self.lvl.get_cell(self.cx, self.cy).on_enter(self)

		# Set camera
		rx = self.cx*16 + self.ox + 8
		ry = self.cy*16 + self.oy + 8

		global real_camx
		global real_camy
		if rx - real_camx < -(WIDTH//2 - CAMBORDER):
			real_camx = (WIDTH//2 - CAMBORDER) + rx
		if rx - real_camx > (WIDTH//2 - CAMBORDER):
			real_camx = -(WIDTH//2 - CAMBORDER) + rx
		if ry - real_camy < -(HEIGHT//2 - CAMBORDER):
			real_camy = (HEIGHT//2 - CAMBORDER) + ry
		if ry - real_camy > (HEIGHT//2 - CAMBORDER):
			real_camy = -(HEIGHT//2 - CAMBORDER) + ry

		if self.ox == 0 and self.oy == 0:
			# Work out movement
			vx, vy = 0, 0
			if newkeys[pygame.K_LEFT]:
				vx -= 1
			if newkeys[pygame.K_RIGHT]:
				vx += 1
			if newkeys[pygame.K_UP]:
				vy -= 1
			if newkeys[pygame.K_DOWN]:
				vy += 1

			# Bail if we're not moving anywhere
			if vx == 0 and vy == 0:
				return

			# Work out if anything is in that cell
			if self.cell_is_walkable(self.cx + vx, self.cy + vy):
				self.lvl.get_cell(self.cx, self.cy).on_exit(self)
				self.cx += vx
				self.cy += vy
				self.ox -= 16*vx
				self.oy -= 16*vy
			elif self.cell_is_walkable(self.cx + vx, self.cy):
				self.lvl.get_cell(self.cx, self.cy).on_exit(self)
				self.cx += vx
				self.ox -= 16*vx
			elif self.cell_is_walkable(self.cx, self.cy + vy):
				self.lvl.get_cell(self.cx, self.cy).on_exit(self)
				self.cy += vy
				self.oy -= 16*vy

			#
			'''
			TODO:
			Add in a check to see if the player is in a SetLevelCell AND levelPos == totalLevels
			Then add in an endgame sequence etc. :)

			> will be making a FinishCell for this purpose --Ben
			'''
	

class FloorCell(BaseCell):
	color = rgb(170, 100, 85)
	imgs = img_tiles_floor
	solid = False

class WallCell(BaseCell):
	color = rgb(55, 55, 55)
	imgs = img_tiles_wall
	solid = True
	
class SetLevelCell(BaseCell):
	color = rgb(170, 100, 85)
	imgs = img_tiles_portal_nextlv
	solid = False

	def __init__(self, transfer):
		self.transfer = transfer
		assert transfer in ["next", "first",]

		if transfer == "next":
			self.imgs = img_tiles_portal_nextlv
		elif transfer == "first":
			self.imgs = img_tiles_portal_firstlv

	def on_enter(self, ent): 
		global levelPos

		oldLevelPos = levelPos
		if self.transfer == "next":
			levelPos = (levelPos + 1) % totalLevels
		elif self.transfer == "first":
			levelPos = 0
		else:
			raise Exception("EDOOFUS: Invalid transfer type for SetLevelCell")

		levelList[levelPos].player.world = levelList[oldLevelPos].player.world
		levelList[levelPos].respawn_player()

class WorldAcceptCell(BaseCell):
	def __init__(self, worlds):
		self.worlds = worlds

	def is_solid(self, world=None):
		return not (world in self.worlds)

	def draw(self, surf, x, y, world):
		surf.blit(
			(img_tiles_dooryes if world in self.worlds else img_tiles_doorno),
			(x, y))
		"""
		pygame.draw.rect(surf,
			(rgb(0, 255, 0) if world in self.worlds else rgb(255, 0, 0)),
			(x, y, 16, 16))
		"""

class WorldChangeCell(BaseCell):
	solid = False

	def __init__(self, change_map):
		self.change_map = change_map

	def on_enter(self, ent):
		if ent.world in self.change_map:
			ent.world = self.change_map[ent.world]

	def draw(self, surf, x, y, world):
		surf.blit(
			(img_tiles_portal if world in self.change_map else img_tiles_floor),
			(x, y))
		"""
		pygame.draw.rect(surf,
			(rgb(40, 0, 64) if world in self.change_map else rgb(170, 100, 85)),
			(x, y, 16, 16))
		"""

class Level:
	"""
	A Level contains all the logic and stuff for a, well, level.
	"""
	def __init__(self, data):
		# Allocate entity list
		self.ents = []
		self.player = None
		self.player_spawn = None

		# Allocate grid
		self.g = [[None for x in xrange(len(data[0]))]
			for y in xrange(len(data))]

		# Put stuff into grid
		for l, y in zip(data, xrange(len(data))):
			for c, x in zip(l, xrange(len(l))):
				self.g[y][x] = self.translate_level_char(c, x, y)

	def respawn_player(self):
		self.player.cx, self.player.cy = self.player_spawn
		self.player.ox, self.player.oy = (0, 0)
		global real_camx
		global real_camy
		real_camx, real_camy = map(lambda v: v*16+8, self.player_spawn)

	def get_cell(self, x, y):
		"""
		Gets a cell from the level for inspection or mutilation.
		"""
		if y < 0 or y >= len(self.g): return None
		if x < 0 or x >= len(self.g[y]): return None
		return self.g[y][x]

	def tick(self):
		"""
		Logic update for level.
		"""
		# Update entities
		for ent in self.ents:
			ent.tick()

	def draw(self, surf, camx, camy, world):
		"""
		Draws the level at (-camx, -camy) on surface "surf", using world "world".
		"""

		# Draw cells
		for y in xrange(len(self.g)):
			for x in xrange(len(self.g[0])):
				cell = self.g[y][x]
				if cell != None:
					cell.draw(surf, x*16-camx, y*16-camy, world)

		# Draw entities
		for ent in self.ents:
			ent.draw(surf, -camx, -camy, world)

	def translate_level_char(self, c, x, y):
		"""
		Maps a character from the source level to an actual class.
		"""

		if c == ".":
			return None

		elif c == ",":
			return FloorCell()

		elif c == "#":
			return WallCell()

		elif c == "0":
			return WorldAcceptCell(set((0,)))

		elif c == "1":
			return WorldChangeCell({0:2})

		elif c == "2":
			return WorldAcceptCell(set((2,)))

		elif c == "3":
			return WorldChangeCell({2:4})

		elif c == "4":
			return WorldAcceptCell(set((4,)))

		elif c == "5":
			return WorldChangeCell({4:0})
		
		elif c == "@":
			return SetLevelCell("next")
		
		elif c == "$":
			return SetLevelCell("first")

		elif c == "P":
			assert self.player == None
			self.player_spawn = (x, y)
			self.player = PlayerEnt(self, x, y)
			self.ents.append(self.player)
			return FloorCell()

		else:
			raise Exception("invalid level char: %s" % repr(c))

'''
-----Tom's Nooby guide to help him create maps---
0 is open at the start.
1 changes the state of 2.
2 is locked until 1 is entered.
3 changes the state of 4.
4 is locked until 3 is entered.
5 changes the state of 0.
@ is the end of the level.
$ is a portal back to the first level.
P is the Player's starting position.
'''		

# Create levels
LVL_STRINGS = [
"""
.........................
...####################..
.###,,,,,,,,,,,,,,,,,,#..
.#@#00###############,#..
.#2#,5#######....####0###
.#,444###,,,######,,,,,,#
.#,,,5444,P,2,,,,,3,,,,,#
.#,#2##3#,,,######4##0,,#
.#,#2##2#,,,#....#,#@0,,#
.#,0,01,#000#....#,#,0,,#
.###2####,,,#....#,###22#
...#,####,,,######,#.#,,#
...#,,,,2,1,4,,,,,,#.####
...######,,,########.....
........##,##............
.........#4#.............
........##,##............
........#,,,#............
........#,@,#............
........#,,,#............
........#####............
""".split("\n")[1:-1],

"""
...........................
........#####..............
........#,,,#..............
........#,@,#..............
........#,,,#..............
#########444##...#####.....
#,,013450,,,,#####,,,#.....
#,,######,,,,0,1,2,@,#.....
#,P235012,,,,#####,,,#.....
#,,######,,,##...#####.....
#,,451234,,,#..............
##4######,,,#..............
.#5#....#,,,#..............
.#0#....#2#4#..............
.#@#....#3#,#..............
.###....#,,,#..............
........##,##..............
.........#5#...............
.......###0###.............
.......#,,,,,#.............
.......#,,,$,#.............
.......#######.............
...........................
""".split("\n")[1:-1],

"""
..#####.#####..#########...
....#...#......#,,,,,,,###.
....#...#..###.##,,@,,,####
....#...#####...##,,,,,####
########........##,,,,,,###
#,,,,,#........####0#######
#,,,,,##########,,,,,,,,,##
#,,,,,,,0,,5,,,4,,,3,,,,,##
#,,@,,,,########222222222##
#,,,,,,##......#,,,,,,,,,##
##,,,,##.......#,,1,,,,###.
.##,###........#,,,,,,##...
###,####.......##00#####...
.#,,,,,########,P,,#.......
.#,,,,,4,,3,,,2,,4########.
.#,,,,,#########,450,,,,$#.
.#######.......####01#####.
.................#####.....
""".split("\n")[1:-1],

"""
...........................
...#####################...
...#,,,,,,4,,P,,2,,,,,,#...
...#,,,,,###,,,###,,,,,#...
...#,,,,#,,##,##,,#,,,,#...
...#,,,#,,,,#,#,,,,#,,,#...
...#,,###,,##,##,,###,,#...
...##,,#,,##0,2##,,#,,##...
....##,#,##,0#2,##,#,##....
.....#5###1,###,3###1#.....
....##,#,#4,2#0,4#,#,##....
...##,,#@,4423044,@#,,##...
...#,,###,,##4##,,###,,#...
...#,,,#,,,,#$#,,,,#,,,#...
...#,,,,#,,#####,,#,,,,#...
...#,,,,,###,,,###,,,,,#...
...#,,,,,0,,,,,,,0,,,,,#...
...#####################...
...........................
""".split("\n")[1:-1],

"""
.............#.............
............###............
...........#,0,#...........
..........#,,#,,#..........
.........#,,###,,#.........
........#,1,,2,,3,#........
.......#,,#######,,#.......
......#,,,,#,5,#,,,,#......
......#,,,,4,#,0,,,,#......
.....####4#######4####.....
.......#,,,,0,1,2,#........
.####...#,,,#####,#........
.#,,####,,,#....#,######...
.#P,,,,,,,#....#,,,,,,##...
.#,,####,,,#....##,,,######
.####..##,,###.###0#52,,,,#
......###,,,,###,,,###,,@,#
......#,,,,,,,,,,###.##,,,#
......############....#####
""".split("\n")[1:-1],

"""
...........................
...........................
.#######..##....#..#####...
.#........#.#...#..#....#..
.#######..#..#..#..#....#..
.########################..
.#,,,,,,,,,,,P,,,,,,,,,,#..
.########################..
.#........#...#.#..#....#..
.#######..#....##..#####...
...........................
...........................
...........................
...........................
...........................
...........................
...........................
...........................
...........................
""".split("\n")[1:-1],

]

# Test levels, NOT USED
'''
"""
##################
#5,32,,,2,@##,,,,#
#,###,5,#####,1,,#
#4###,,,0,,,0,,,,#
#,,,##44########2#
#,,,32P,#,1,#,,#,#
######,,0,,,2,,#,#
.#,1,2,,#####,,#,#
.#,,,#,54,,,2,,#,#
.#,######,3,####,#
.#,543210,,,0,,,1#
.#################
""".split("\n")[1:-1],

"""
#####
#2,P#
#30,#
#@50#
#####
""".split("\n")[1:-1],
'''

#One list to rule them all
levelList = map(Level, LVL_STRINGS)

#Calculates total levels (will be used to display an end-game message)
totalLevels = len(levelList)

#Used to determine what level the player is on
levelPos = 0

# Main loop
quitflag = False
oldkeys = pygame.key.get_pressed()

newkeys = pygame.key.get_pressed()
tick_next = time.time()
#pygame.mixer.music.play(-1)
while not quitflag:
	# Handle timing
	tick_current = time.time()

	if tick_current < tick_next:
		# Draw screen
		screen.fill(rgb(0,0,170))
		levelList[levelPos].draw(screen,
			real_camx - WIDTH//2,
			real_camy - HEIGHT//2,
			levelList[levelPos].player.world)
		flip_screen()

		# Prevent CPU fires
		time.sleep(0.01)

	else:
		# Poll events
		pygame.event.pump()
		newkeys = pygame.key.get_pressed()
		if oldkeys[pygame.K_ESCAPE] and not newkeys[pygame.K_ESCAPE]:
			quitflag = True

		# Update logic
		levelList[levelPos].tick()

		# Transfer newkeys to oldkeys
		oldkeys = newkeys

		# Update tick counter
		tick_next += SPF


# Clean up
pygame.quit()


#!/usr/bin/env python2 --
import math, os, random, time

import pygame

pygame.init()
#music = pygame.mixer.music.load("dat/music.ogg")

WIDTH = 320
HEIGHT = 200
SCALE = 3

FPS = 60.0
SPF = 1.0/FPS
CAMBORDER = 64

real_camx = WIDTH//2
real_camy = HEIGHT//2

# Set up display
pygame.display.set_caption("Portal DAGger")
screen_real = pygame.display.set_mode((WIDTH*SCALE, HEIGHT*SCALE), 0, 0)
screen = pygame.Surface((WIDTH, HEIGHT), 0, screen_real.get_bitsize()).convert(screen_real)

def subtile(img, w, h, cx, cy):
	"""
	Gets a tile-indexed w by h subsurface from the tilemap "img".
	"""

	return img.subsurface((cx*w, cy*h, w, h))

# Load some images
img_tiles = pygame.image.load(os.path.join("dat", "tiles.tga"))
#img_tiles.set_colorkey(0, pygame.RLEACCEL)
img_tiles = img_tiles.convert(screen)

img_tiles_floor = subtile(img_tiles, 16, 16, 0, 1)
img_tiles_portal = subtile(img_tiles, 16, 16, 0, 2)
img_tiles_wall = subtile(img_tiles, 16, 16, 0, 3)
img_tiles_doorno = subtile(img_tiles, 16, 16, 0, 4)
img_tiles_dooryes = subtile(img_tiles, 16, 16, 0, 5)
img_tiles_portal_nextlv = subtile(img_tiles, 16, 16, 0, 6)
img_tiles_portal_firstlv = subtile(img_tiles, 16, 16, 0, 7)

def rgb(r, g, b):
	"""
	Turns an RGB tuple into a colour value.
	"""

	return screen.map_rgb((r, g, b))

def flip_screen():
	"""
	Push the back buffer to the front buffer.
	That is, actually draw what you were drawing to the screen.
	"""

	pygame.transform.scale(screen,
		(screen_real.get_width(), screen_real.get_height()),
		screen_real)
	pygame.display.flip()

class BaseCell:
	color = rgb(255, 0, 255)
	imgs = None
	solid = True

	def draw(self, surf, x, y, world):
		"""
		Draws the cell to the surface "surf".
		"""
		if self.imgs == None:
			# No image - draw a rect
			pass
			#pygame.draw.rect(surf, self.color, (x, y, 16, 16))

		elif type(self.imgs) in (list, tuple,):
			# Indexable sequence - pick an image
			# TODO: do more than just the first image
			surf.blit(self.imgs[0], (x, y))

		else:
			# Assuming this is a single image
			surf.blit(self.imgs, (x, y))

	def on_enter(self, ent):
		"""
		CALLBACK: Handles entry of an entity.
		"""
		pass

	def on_exit(self, ent):
		"""
		CALLBACK: Handles exit of an entity.
		"""
		pass

	def is_solid(self, world=None):
		"""
		Determines if this cell is solid or not.
		"""
		return self.solid

class BaseEnt:
	color = rgb(255, 0, 255)
	rect = (4, 4, 8, 8)
	world = 0

	def __init__(self, lvl, cx, cy):
		"""
		Places an entity on level "lvl" at cell cx, cy.
		"""
		self.lvl = lvl
		self.cx, self.cy = cx, cy
		self.ox, self.oy =  0,  0

	def draw(self, surf, x, y, world):
		"""
		Draws the entity at pixel position x, y.
		"""
		rx, ry, rw, rh = self.rect
		rx += x + self.cx*16 + self.ox
		ry += y + self.cy*16 + self.oy
		pygame.draw.rect(surf, self.color, (rx, ry, rw, rh))

	def tick(self):
		"""
		Logic update for entity.
		"""

		# To be overrided by subclasses.
		pass

class PlayerEnt(BaseEnt):
	color = rgb(0, 255, 255)

	# TODO: Move this function to a better spot eventually
	def cell_is_walkable(self, cx, cy, world=None):
		"""
		Determine if we can walk onto a given cell.
		"""

		# Get the relevant "world" we're checking this for
		if world == None:
			world = self.world

		# Get cell
		cell = self.lvl.get_cell(cx, cy)

		# Check if None
		if cell == None:
			return False # NEVER WALKABLE!

		# Check if solid
		return not cell.is_solid(world=world)

	def tick(self):
		# Move to relevant cell if need be
		if not(self.ox == 0 and self.oy == 0):
			if self.ox != 0:
				self.ox += (1 if self.ox < 0 else -1)
			if self.oy != 0:
				self.oy += (1 if self.oy < 0 else -1)

			# If we've just entered the cell, call on_enter
			if self.ox == 0 and self.oy == 0:
				self.lvl.get_cell(self.cx, self.cy).on_enter(self)

		# Set camera
		rx = self.cx*16 + self.ox + 8
		ry = self.cy*16 + self.oy + 8

		global real_camx
		global real_camy
		if rx - real_camx < -(WIDTH//2 - CAMBORDER):
			real_camx = (WIDTH//2 - CAMBORDER) + rx
		if rx - real_camx > (WIDTH//2 - CAMBORDER):
			real_camx = -(WIDTH//2 - CAMBORDER) + rx
		if ry - real_camy < -(HEIGHT//2 - CAMBORDER):
			real_camy = (HEIGHT//2 - CAMBORDER) + ry
		if ry - real_camy > (HEIGHT//2 - CAMBORDER):
			real_camy = -(HEIGHT//2 - CAMBORDER) + ry

		if self.ox == 0 and self.oy == 0:
			# Work out movement
			vx, vy = 0, 0
			if newkeys[pygame.K_LEFT]:
				vx -= 1
			if newkeys[pygame.K_RIGHT]:
				vx += 1
			if newkeys[pygame.K_UP]:
				vy -= 1
			if newkeys[pygame.K_DOWN]:
				vy += 1

			# Bail if we're not moving anywhere
			if vx == 0 and vy == 0:
				return

			# Work out if anything is in that cell
			if self.cell_is_walkable(self.cx + vx, self.cy + vy):
				self.lvl.get_cell(self.cx, self.cy).on_exit(self)
				self.cx += vx
				self.cy += vy
				self.ox -= 16*vx
				self.oy -= 16*vy
			elif self.cell_is_walkable(self.cx + vx, self.cy):
				self.lvl.get_cell(self.cx, self.cy).on_exit(self)
				self.cx += vx
				self.ox -= 16*vx
			elif self.cell_is_walkable(self.cx, self.cy + vy):
				self.lvl.get_cell(self.cx, self.cy).on_exit(self)
				self.cy += vy
				self.oy -= 16*vy

			#
			'''
			TODO:
			Add in a check to see if the player is in a SetLevelCell AND levelPos == totalLevels
			Then add in an endgame sequence etc. :)

			> will be making a FinishCell for this purpose --Ben
			'''
	

class FloorCell(BaseCell):
	color = rgb(170, 100, 85)
	imgs = img_tiles_floor
	solid = False

class WallCell(BaseCell):
	color = rgb(55, 55, 55)
	imgs = img_tiles_wall
	solid = True
	
class SetLevelCell(BaseCell):
	color = rgb(170, 100, 85)
	imgs = img_tiles_portal_nextlv
	solid = False

	def __init__(self, transfer):
		self.transfer = transfer
		assert transfer in ["next", "first",]

		if transfer == "next":
			self.imgs = img_tiles_portal_nextlv
		elif transfer == "first":
			self.imgs = img_tiles_portal_firstlv

	def on_enter(self, ent): 
		global levelPos

		oldLevelPos = levelPos
		if self.transfer == "next":
			levelPos = (levelPos + 1) % totalLevels
		elif self.transfer == "first":
			levelPos = 0
		else:
			raise Exception("EDOOFUS: Invalid transfer type for SetLevelCell")

		levelList[levelPos].player.world = levelList[oldLevelPos].player.world
		levelList[levelPos].respawn_player()

class WorldAcceptCell(BaseCell):
	def __init__(self, worlds):
		self.worlds = worlds

	def is_solid(self, world=None):
		return not (world in self.worlds)

	def draw(self, surf, x, y, world):
		surf.blit(
			(img_tiles_dooryes if world in self.worlds else img_tiles_doorno),
			(x, y))
		"""
		pygame.draw.rect(surf,
			(rgb(0, 255, 0) if world in self.worlds else rgb(255, 0, 0)),
			(x, y, 16, 16))
		"""

class WorldChangeCell(BaseCell):
	solid = False

	def __init__(self, change_map):
		self.change_map = change_map

	def on_enter(self, ent):
		if ent.world in self.change_map:
			ent.world = self.change_map[ent.world]

	def draw(self, surf, x, y, world):
		surf.blit(
			(img_tiles_portal if world in self.change_map else img_tiles_floor),
			(x, y))
		"""
		pygame.draw.rect(surf,
			(rgb(40, 0, 64) if world in self.change_map else rgb(170, 100, 85)),
			(x, y, 16, 16))
		"""

class Level:
	"""
	A Level contains all the logic and stuff for a, well, level.
	"""
	def __init__(self, data):
		# Allocate entity list
		self.ents = []
		self.player = None
		self.player_spawn = None

		# Allocate grid
		self.g = [[None for x in xrange(len(data[0]))]
			for y in xrange(len(data))]

		# Put stuff into grid
		for l, y in zip(data, xrange(len(data))):
			for c, x in zip(l, xrange(len(l))):
				self.g[y][x] = self.translate_level_char(c, x, y)

	def respawn_player(self):
		self.player.cx, self.player.cy = self.player_spawn
		self.player.ox, self.player.oy = (0, 0)
		global real_camx
		global real_camy
		real_camx, real_camy = map(lambda v: v*16+8, self.player_spawn)

	def get_cell(self, x, y):
		"""
		Gets a cell from the level for inspection or mutilation.
		"""
		if y < 0 or y >= len(self.g): return None
		if x < 0 or x >= len(self.g[y]): return None
		return self.g[y][x]

	def tick(self):
		"""
		Logic update for level.
		"""
		# Update entities
		for ent in self.ents:
			ent.tick()

	def draw(self, surf, camx, camy, world):
		"""
		Draws the level at (-camx, -camy) on surface "surf", using world "world".
		"""

		# Draw cells
		for y in xrange(len(self.g)):
			for x in xrange(len(self.g[0])):
				cell = self.g[y][x]
				if cell != None:
					cell.draw(surf, x*16-camx, y*16-camy, world)

		# Draw entities
		for ent in self.ents:
			ent.draw(surf, -camx, -camy, world)

	def translate_level_char(self, c, x, y):
		"""
		Maps a character from the source level to an actual class.
		"""

		if c == ".":
			return None

		elif c == ",":
			return FloorCell()

		elif c == "#":
			return WallCell()

		elif c == "0":
			return WorldAcceptCell(set((0,)))

		elif c == "1":
			return WorldChangeCell({0:2})

		elif c == "2":
			return WorldAcceptCell(set((2,)))

		elif c == "3":
			return WorldChangeCell({2:4})

		elif c == "4":
			return WorldAcceptCell(set((4,)))

		elif c == "5":
			return WorldChangeCell({4:0})
		
		elif c == "@":
			return SetLevelCell("next")
		
		elif c == "$":
			return SetLevelCell("first")

		elif c == "P":
			assert self.player == None
			self.player_spawn = (x, y)
			self.player = PlayerEnt(self, x, y)
			self.ents.append(self.player)
			return FloorCell()

		else:
			raise Exception("invalid level char: %s" % repr(c))

'''
-----Tom's Nooby guide to help him create maps---
0 is open at the start.
1 changes the state of 2.
2 is locked until 1 is entered.
3 changes the state of 4.
4 is locked until 3 is entered.
5 changes the state of 0.
@ is the end of the level.
$ is a portal back to the first level.
P is the Player's starting position.
'''		

# Create levels
LVL_STRINGS = [
"""
.........................
...####################..
.###,,,,,,,,,,,,,,,,,,#..
.#@#00###############,#..
.#2#,5#######....####0###
.#,444###,,,######,,,,,,#
.#,,,5444,P,2,,,,,3,,,,,#
.#,#2##3#,,,######4##0,,#
.#,#2##2#,,,#....#,#@0,,#
.#,0,01,#000#....#,#,0,,#
.###2####,,,#....#,###22#
...#,####,,,######,#.#,,#
...#,,,,2,1,4,,,,,,#.####
...######,,,########.....
........##,##............
.........#4#.............
........##,##............
........#,,,#............
........#,@,#............
........#,,,#............
........#####............
""".split("\n")[1:-1],

"""
...........................
........#####..............
........#,,,#..............
........#,@,#..............
........#,,,#..............
#########444##...#####.....
#,,013450,,,,#####,,,#.....
#,,######,,,,0,1,2,@,#.....
#,P235012,,,,#####,,,#.....
#,,######,,,##...#####.....
#,,451234,,,#..............
##4######,,,#..............
.#5#....#,,,#..............
.#0#....#2#4#..............
.#@#....#3#,#..............
.###....#,,,#..............
........##,##..............
.........#5#...............
.......###0###.............
.......#,,,,,#.............
.......#,,,$,#.............
.......#######.............
...........................
""".split("\n")[1:-1],

"""
..#####.#####..#########...
....#...#......#,,,,,,,###.
....#...#..###.##,,@,,,####
....#...#####...##,,,,,####
########........##,,,,,,###
#,,,,,#........####0#######
#,,,,,##########,,,,,,,,,##
#,,,,,,,0,,5,,,4,,,3,,,,,##
#,,@,,,,########222222222##
#,,,,,,##......#,,,,,,,,,##
##,,,,##.......#,,1,,,,###.
.##,###........#,,,,,,##...
###,####.......##00#####...
.#,,,,,########,P,,#.......
.#,,,,,4,,3,,,2,,4########.
.#,,,,,#########,450,,,,$#.
.#######.......####01#####.
.................#####.....
""".split("\n")[1:-1],

"""
...........................
...#####################...
...#,,,,,,4,,P,,2,,,,,,#...
...#,,,,,###,,,###,,,,,#...
...#,,,,#,,##,##,,#,,,,#...
...#,,,#,,,,#,#,,,,#,,,#...
...#,,###,,##,##,,###,,#...
...##,,#,,##0,2##,,#,,##...
....##,#,##,0#2,##,#,##....
.....#5###1,###,3###1#.....
....##,#,#4,2#0,4#,#,##....
...##,,#@,4423044,@#,,##...
...#,,###,,##4##,,###,,#...
...#,,,#,,,,#$#,,,,#,,,#...
...#,,,,#,,#####,,#,,,,#...
...#,,,,,###,,,###,,,,,#...
...#,,,,,0,,,,,,,0,,,,,#...
...#####################...
...........................
""".split("\n")[1:-1],

"""
.............#.............
............###............
...........#,0,#...........
..........#,,#,,#..........
.........#,,###,,#.........
........#,1,,2,,3,#........
.......#,,#######,,#.......
......#,,,,#,5,#,,,,#......
......#,,,,4,#,0,,,,#......
.....####4#######4####.....
.......#,,,,0,1,2,#........
.####...#,,,#####,#........
.#,,####,,,#....#,######...
.#P,,,,,,,#....#,,,,,,##...
.#,,####,,,#....##,,,######
.####..##,,###.###0#52,,,,#
......###,,,,###,,,###,,@,#
......#,,,,,,,,,,###.##,,,#
......############....#####
""".split("\n")[1:-1],

"""
...........................
...........................
.#######..##....#..#####...
.#........#.#...#..#....#..
.#######..#..#..#..#....#..
.########################..
.#,,,,,,,,,,,P,,,,,,,,,,#..
.########################..
.#........#...#.#..#....#..
.#######..#....##..#####...
...........................
...........................
...........................
...........................
...........................
...........................
...........................
...........................
...........................
""".split("\n")[1:-1],

]

# Test levels, NOT USED
'''
"""
##################
#5,32,,,2,@##,,,,#
#,###,5,#####,1,,#
#4###,,,0,,,0,,,,#
#,,,##44########2#
#,,,32P,#,1,#,,#,#
######,,0,,,2,,#,#
.#,1,2,,#####,,#,#
.#,,,#,54,,,2,,#,#
.#,######,3,####,#
.#,543210,,,0,,,1#
.#################
""".split("\n")[1:-1],

"""
#####
#2,P#
#30,#
#@50#
#####
""".split("\n")[1:-1],
'''

#One list to rule them all
levelList = map(Level, LVL_STRINGS)

#Calculates total levels (will be used to display an end-game message)
totalLevels = len(levelList)

#Used to determine what level the player is on
levelPos = 0

# Main loop
quitflag = False
oldkeys = pygame.key.get_pressed()

newkeys = pygame.key.get_pressed()
tick_next = time.time()
#pygame.mixer.music.play(-1)
while not quitflag:
	# Handle timing
	tick_current = time.time()

	if tick_current < tick_next:
		# Draw screen
		screen.fill(rgb(0,0,170))
		levelList[levelPos].draw(screen,
			real_camx - WIDTH//2,
			real_camy - HEIGHT//2,
			levelList[levelPos].player.world)
		flip_screen()

		# Prevent CPU fires
		time.sleep(0.01)

	else:
		# Poll events
		pygame.event.pump()
		newkeys = pygame.key.get_pressed()
		if oldkeys[pygame.K_ESCAPE] and not newkeys[pygame.K_ESCAPE]:
			quitflag = True

		# Update logic
		levelList[levelPos].tick()

		# Transfer newkeys to oldkeys
		oldkeys = newkeys

		# Update tick counter
		tick_next += SPF


# Clean up
pygame.quit()


#!/usr/bin/env python2 -- #music = pygame.mixer.music.load("dat/music.ogg") # Set up display Gets a tile-indexed w by h subsurface from the tilemap "img". # Load some images #img_tiles.set_colorkey(0, pygame.RLEACCEL) Turns an RGB tuple into a colour value. Push the back buffer to the front buffer. 	That is, actually draw what you were drawing to the screen. Draws the cell to the surface "surf". # No image - draw a rect #pygame.draw.rect(surf, self.color, (x, y, 16, 16)) # Indexable sequence - pick an image # TODO: do more than just the first image # Assuming this is a single image CALLBACK: Handles entry of an entity. CALLBACK: Handles exit of an entity. Determines if this cell is solid or not. Places an entity on level "lvl" at cell cx, cy. Draws the entity at pixel position x, y. Logic update for entity. # To be overrided by subclasses. # TODO: Move this function to a better spot eventually Determine if we can walk onto a given cell. # Get the relevant "world" we're checking this for # Get cell # Check if None # NEVER WALKABLE! # Check if solid # Move to relevant cell if need be # If we've just entered the cell, call on_enter # Set camera # Work out movement # Bail if we're not moving anywhere # Work out if anything is in that cell # TODO: 			Add in a check to see if the player is in a SetLevelCell AND levelPos == totalLevels 			Then add in an endgame sequence etc. :)  			> will be making a FinishCell for this purpose --Ben pygame.draw.rect(surf, 			(rgb(0, 255, 0) if world in self.worlds else rgb(255, 0, 0)), 			(x, y, 16, 16)) pygame.draw.rect(surf, 			(rgb(40, 0, 64) if world in self.change_map else rgb(170, 100, 85)), 			(x, y, 16, 16)) A Level contains all the logic and stuff for a, well, level. # Allocate entity list # Allocate grid # Put stuff into grid Gets a cell from the level for inspection or mutilation. Logic update for level. # Update entities Draws the level at (-camx, -camy) on surface "surf", using world "world". # Draw cells # Draw entities Maps a character from the source level to an actual class. -----Tom's Nooby guide to help him create maps--- 0 is open at the start. 1 changes the state of 2. 2 is locked until 1 is entered. 3 changes the state of 4. 4 is locked until 3 is entered. 5 changes the state of 0. @ is the end of the level. $ is a portal back to the first level. P is the Player's starting position. # Create levels ......................... ...####################.. .###,,,,,,,,,,,,,,,,,,#.. .#@#00###############,#.. .#2#,5#######....####0### .#,444###,,,######,,,,,,# .#,,,5444,P,2,,,,,3,,,,,# .#,#2##3#,,,######4##0,,# .#,#2##2#,,,#....#,#@0,,# .#,0,01,#000#....#,#,0,,# .###2####,,,#....#,###22# ...#,####,,,######,#.#,,# ...#,,,,2,1,4,,,,,,#.#### ...######,,,########..... ........##,##............ .........#4#............. ........##,##............ ........#,,,#............ ........#,@,#............ ........#,,,#............ ........#####............ ........................... ........#####.............. ........#,,,#.............. ........#,@,#.............. ........#,,,#.............. #########444##...#####..... #,,013450,,,,#####,,,#..... #,,######,,,,0,1,2,@,#..... #,P235012,,,,#####,,,#..... #,,######,,,##...#####..... #,,451234,,,#.............. ##4######,,,#.............. .#5#....#,,,#.............. .#0#....#2#4#.............. .#@#....#3#,#.............. .###....#,,,#.............. ........##,##.............. .........#5#............... .......###0###............. .......#,,,,,#............. .......#,,,$,#............. .......#######............. ........................... ..#####.#####..#########... ....#...#......#,,,,,,,###. ....#...#..###.##,,@,,,#### ....#...#####...##,,,,,#### ########........##,,,,,,### #,,,,,#........####0####### #,,,,,##########,,,,,,,,,## #,,,,,,,0,,5,,,4,,,3,,,,,## #,,@,,,,########222222222## #,,,,,,##......#,,,,,,,,,## ##,,,,##.......#,,1,,,,###. .##,###........#,,,,,,##... ###,####.......##00#####... .#,,,,,########,P,,#....... .#,,,,,4,,3,,,2,,4########. .#,,,,,#########,450,,,,$#. .#######.......####01#####. .................#####..... ........................... ...#####################... ...#,,,,,,4,,P,,2,,,,,,#... ...#,,,,,###,,,###,,,,,#... ...#,,,,#,,##,##,,#,,,,#... ...#,,,#,,,,#,#,,,,#,,,#... ...#,,###,,##,##,,###,,#... ...##,,#,,##0,2##,,#,,##... ....##,#,##,0#2,##,#,##.... .....#5###1,###,3###1#..... ....##,#,#4,2#0,4#,#,##.... ...##,,#@,4423044,@#,,##... ...#,,###,,##4##,,###,,#... ...#,,,#,,,,#$#,,,,#,,,#... ...#,,,,#,,#####,,#,,,,#... ...#,,,,,###,,,###,,,,,#... ...#,,,,,0,,,,,,,0,,,,,#... ...#####################... ........................... .............#............. ............###............ ...........#,0,#........... ..........#,,#,,#.......... .........#,,###,,#......... ........#,1,,2,,3,#........ .......#,,#######,,#....... ......#,,,,#,5,#,,,,#...... ......#,,,,4,#,0,,,,#...... .....####4#######4####..... .......#,,,,0,1,2,#........ .####...#,,,#####,#........ .#,,####,,,#....#,######... .#P,,,,,,,#....#,,,,,,##... .#,,####,,,#....##,,,###### .####..##,,###.###0#52,,,,# ......###,,,,###,,,###,,@,# ......#,,,,,,,,,,###.##,,,# ......############....##### ........................... ........................... .#######..##....#..#####... .#........#.#...#..#....#.. .#######..#..#..#..#....#.. .########################.. .#,,,,,,,,,,,P,,,,,,,,,,#.. .########################.. .#........#...#.#..#....#.. .#######..#....##..#####... ........................... ........................... ........................... ........................... ........................... ........................... ........................... ........................... ........................... # Test levels, NOT USED """ ################## #5,32,,,2,@##,,,,# #,###,5,#####,1,,# #4###,,,0,,,0,,,,# #,,,##44########2# #,,,32P,#,1,#,,#,# ######,,0,,,2,,#,# .#,1,2,,#####,,#,# .#,,,#,54,,,2,,#,# .#,######,3,####,# .#,543210,,,0,,,1# .################# """.split("\n")[1:-1],  """ ##### #2,P# #30,# #@50# ##### """.split("\n")[1:-1], #One list to rule them all #Calculates total levels (will be used to display an end-game message) #Used to determine what level the player is on # Main loop #pygame.mixer.music.play(-1) # Handle timing # Draw screen # Prevent CPU fires # Poll events # Update logic # Transfer newkeys to oldkeys # Update tick counter # Clean up