#~ Copyright (C) 2011 by Farbs

#~ Permission is hereby granted, free of charge, to any person obtaining a copy
#~ of this software and associated documentation files (the "Software"), to deal
#~ in the Software without restriction, including without limitation the rights
#~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#~ copies of the Software, and to permit persons to whom the Software is
#~ furnished to do so, subject to the following conditions:

#~ The above copyright notice and this permission notice shall be included in
#~ all copies or substantial portions of the Software.

#~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#~ THE SOFTWARE.


# Stupid fun
# Written by Farbs

appIni = {	"mCompanyName"		: "Mike Meyer",
		"mFullCompanyName"	: "Mike Meyer",
		"mProdName"		: "ROM CHECK FAIL WITHOUT A CAUSE",
		"mProductVersion"	: "1.0",
		"mTitle"			: "ROM CHECK FAIL WITHOUT A CAUSE",
		"mRegKey"			: "RCFWAC",
		"mWidth"			: 256,
		"mHeight"			: 224,
		"mAutoEnable3D"	: 1,
		"mVSyncUpdates"	: 1 }

import random, math

import Pycap as PC
PCR = None
board = None
font = None
goodFont = None
bonedFont = None
levelData = []
numLevels = None
frameCount = 0
doExit = 0
gameSpeed = None
isTuxcap = 0

playerClass = None
enemyClass = None
environmentClass = None
environment = None
score = 0
highscore = 0
lives = None
playerClasses = []
enemyClasses = []
environmentClasses = []
gameTunes = []
borkedSounds = []

KEY_UP	= None
KEY_DOWN	= None
KEY_LEFT	= None
KEY_RIGHT	= None
KEY_SPACE = None
KEY_ESC	= None

class Board:
	def __init__( self ):
		pass
		
	def randomizeNotify( self ):
		pass

	def update( self, delta ):
		pass
		
	def draw( self ):
		pass
		
	def keyDown( self, key ):
		pass
		
	def keyUp( self, key ):
		pass

class FontTestBoard( Board ):
	def __init__( self ):
		global gameSpeed
		gameSpeed = 1
		
	def draw( self ):
		global font
		PC.setColour( 255, 0, 255, 255 )
		PC.fillRect( 0, 0, 1024, 224 )
		for i in range( 0, 128 ):
			x = i * 8
			PC.setColour( 255, 255, 255, 255 )
			PC.setFont( goodFont )
			drawStringShadowed( chr( i + 33 ), x, 20 )
			PC.setFont( bonedFont )
			drawStringShadowed( chr( i + 33 ), x, 28 )

class TestGridBoard( Board ):
	def __init__( self ):
		self.alpha = 0
		global gameSpeed
		gameSpeed = 1
		
	def update( self, delta ):
		# update timer
		self.alpha += delta * 0.001
		
		# test for end
		if self.alpha >= 0.1:
			global board
			board = ChipCheckBoard()
			
	def draw( self ):
		PC.setColour( 0, 0, 0, 255 )
		PC.fillRect( 0, 0, 256, 224 )
		PC.drawImage( self.image, 0, 0 )

class ChipCheckBoard( Board ):
	def __init__( self ):
		self.alpha = 0
		self.age = 0
		self.ramCheck = 0
		self.ok = 0
		self.romCheck = 0
		self.fail = 0
		self.error = 0
		self.promptTime = 50
		self.prompt = 0
		self.timescale = 4
		
	def update( self, delta ):
		# update timers
		self.age += delta * self.timescale
		self.alpha += delta * 0.0025 * self.timescale
		if self.error:
			self.promptTime -= delta
			while self.promptTime < 0:
				self.prompt = not self.prompt
				self.promptTime += 3 + random.random() * 50
		
		if not self.ramCheck:
			self.ramCheck = 1
			PC.playSound( self.checkSound )
		if ( not self.ok ) and self.alpha > 0.2:
			self.ok = 1
			PC.playSound( self.okSound )
		if ( not self.romCheck ) and self.alpha > 0.4:
			self.romCheck = 1
			PC.playSound( self.checkSound )
		if ( not self.fail ) and self.alpha > 0.6:
			self.fail = 1
			PC.playSound( self.failSound )		
		if ( not self.error) and self.alpha >= 1:
			self.error = 1
			PC.playSound( self.errorSound )
			
	def keyDown( self, key ):
		# test for at end of sequence
		if self.alpha >= 1:
			global board
			board = FarbsSplashBoard()
			
	def draw( self ):
		PC.setColour( 0, 0, 0, 255 )
		PC.fillRect( 0, 0, 256, 224 )
		PC.setColour( 255, 255, 255, 255 )
		
		# ram check
		if self.ramCheck:
			PC.drawString( "RAM CHECK ", 128 - PCR.stringWidth( "RAM CHECK ", font ), 64 )
		if self.ok:
			PC.drawString( "OK!", 128, 64 )
		if self.romCheck:
			PC.drawString( "ROM CHECK ", 128 - PCR.stringWidth( "RAM CHECK ", font ), 80 )
		if self.fail:
			PC.drawString( "FAIL!", 128, 80 )
		if self.error:
			PC.setColour( 255, 0, 0, 255 )
			string = "SYSTEM ERROR!!!SYSTEM ERROR!!!SYSTEM ERROR!!!SYSTEM ERROR!!!SYSTEM ERROR!!!SYSTEM ERROR!!!SYSTEM ERROR!!!"
			x = 128 - PCR.stringWidth( string, font ) / 2 - 8 * ( int( self.age * 0.1 ) % 15 )
			PC.drawString( string, x, 128 )
			PC.drawString( string, x, 176 )
			if self.prompt:
				string = "PRESS FIRE"
				PC.drawString( string, 128 - PCR.stringWidth( string, font ) / 2, 144 )
				string = "TO CONTINUE"
				PC.drawString( string, 128 - PCR.stringWidth( string, font ) / 2, 160 )
			else:
				string = "SYSTEM ERROR"
				PC.drawString( string, 128 - PCR.stringWidth( string, font ) / 2, 144 )
				string = "SYSTEM ERROR"
				PC.drawString( string, 128 - PCR.stringWidth( string, font ) / 2, 160 )

class FarbsSplashBoard( Board ):
	def __init__( self ):
		self.alpha = 0
		playBorked( 4 )
		
	def update( self, delta ):
		global board
		# update timer
		self.alpha += delta * 0.0025
		
		# break stuff :D :D :D
		if self.alpha > 0.3:
			if random.random() < 0.1:
				PCR.mashPalette( self.image )
		if self.alpha > 0.3:
			if random.random() < 0.1:
				PCR.mashImage( self.image )
		if self.alpha > 0.5:
			global font
			font = bonedFont
		
		# test for end
		if self.alpha >= 1:
			board = AttractBoard()
			
	def draw( self ):
		PC.drawImage( self.image, 0, 0 )
		
		PC.setColour( 255, 255, 255, 255 )
		string = "WWW.FARBS.ORG"
		drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) * 0.5, 100 )
		if isTuxcap:
			string = "LINUX VERSION VIA TuxCap"
			drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) * 0.5, 116 )
		
		s = "ROM CHECK FAIL (C) FARBS 2008"
		PC.drawString( s, 128 - PCR.stringWidth( s, font ) / 2, 223 )

class AttractBoard( Board ):
	def __init__( self ):
		global font
		font = goodFont
		global gameSpeed
		gameSpeed = 1
		self.alpha = 0
		self.age = 0
		self.playTune()
		self.tuneTimer = 100
		self.staticPlaying = 0
		self.pickRandomSpot()
		
	def update( self, delta ):
		# update timers
		self.age += delta
		self.alpha += delta * 0.01
		self.tuneTimer -= delta
		while self.tuneTimer <= 0:
			self.playTune()
			self.tuneTimer += 100
			self.staticPlaying = 0
			self.pickRandomSpot()
		if self.tuneTimer <= 25 and not self.staticPlaying:
			self.staticPlaying = 1
			playBorked( 0.25 )
	
	def pickRandomSpot(self):
		self.chosenimage = int(random.random() * ( PacmanPlayer.pillImage + 1 ))
		
	def keyDown( self, key ):
		# test for at end of sequence
		if self.alpha >= 1:
			startGame()
			
	def playTune( self ):
		global gameTunes
		tune = gameTunes[ int( random.random() * 10 ) ]
		gameTunes.remove( tune )
		gameTunes.append( tune )
		PC.playTune( tune )
			
	def draw( self ):
		# draw some background trash
		if self.staticPlaying:
			PC.setColour( int( random.random() * 256 ), int( random.random() * 256 ), int( random.random() * 256 ), 255 )
		else:
			PC.setColour( 0, 0, 0, 255 )
		PC.fillRect( 0, 0, 256, 224 )
		PC.setColourize( 1 )
		
		PC.setColour( 255, 255, 255, 255 )
		# such an awful hack... assumes images don't get unloaded.
		PC.drawImage( self.chosenimage, 
					  128 - PCR.imageWidth( self.chosenimage ) / 2, 
					  160 - PCR.imageWidth( self.chosenimage ) / 2 + 8 )
					  
		PC.setColour( 0, 0, 0, 255 )
		PC.drawImage( self.image, 128 - PCR.imageWidth( self.image ) / 2 + 2, 66 + 2 )
		PC.setColour( 255, 255, 255, 255 )
		PC.setColourize( 0 )
		PC.drawImage( self.image, 128 - PCR.imageWidth( self.image ) / 2, 66 )
		msg = "controls: arrow keys and space"
		drawStringShadowed( msg, 128 - PCR.stringWidth( msg, font ) / 2, 130 )
		if self.age % 60 < 40 and self.alpha > 1:
			msg = "press space to begin"
			drawStringShadowed( msg, 128 - PCR.stringWidth( msg, font ) / 2, 142 )

class GameWinBoard( Board ):
	def __init__( self ):
		# game config
		global font
		font = goodFont
		PC.stopTune()
		global gameSpeed
		gameSpeed += 1
		
		# self
		self.frameTime = 100
		self.image = self.images[ int( random.random() * len( self.images ) ) ]
		playBorked( self.frameTime * 0.01 )
		
		self.quitCountDown = 40
		
	def update( self, delta ):
		# update timers
		self.frameTime -= delta
		while self.frameTime <= 0:
			self.frameTime += pow( random.random(), 4 ) * 50
			self.image = self.images[ int( random.random() * len( self.images ) ) ]
			self.quitCountDown -= 1
			if self.quitCountDown < 0:
				raise SystemExit(0)
			# only play sound if time is reasonable
			if self.frameTime > 10:
				playBorked( self.frameTime * 0.01 )
						
	def draw( self ):
		# draw win screen
		PC.drawImage( self.image, 0, 0 )
		
		# draw scores etc (copied from environment)
		drawStringShadowed( "  1-UP    HIGH SCORE    LIVES  ", 0, 16, 255, 0, 0 )
		PC.setColour( 255, 255, 255, 255 )
		string = str( score )
		drawStringShadowed( string, ( 4 - len( string ) / 2 ) * 8, 24 )
		string = str( highscore )
		drawStringShadowed( string, ( 15 - len( string ) / 2 ) * 8, 24 )
		string = str( lives )
		drawStringShadowed( string, ( 26 - len( string ) / 2 ) * 8, 24 )

class GameBoard( Board ):
	STATE_INGAME		= 1
	STATE_POSTGAME	= 2
	def __init__( self ):
		# set own state stuff
		self.state = self.STATE_INGAME
		self.stateTime = 0
		self.age = 0
		self.awardedLife = 0
		self.awardedPoints = 0
		self.gameLost = 0
		self.randomizeCount = 0
		self.randomizeSoundCount = 0
		self.playingMusic = 0
		self.tune = 0
		self.brokeFont = 0
		self.allowenemies = False
		self.timescale = 0.75
		
		self.rebuildLevel()
		
		# play some music :)
		self.pickNewTune()
		self.startMusic()

	def rebuildLevel(self, reset_player=True, reset_enemies=True):
		global numLevels
		
		# create empty level
		if reset_player:
			self.player = None
			self.playerStartX = 0
			self.playerStartY = 0
		if reset_enemies:
			self.enemies = []
		self.level = []
			
		self.levelIndex = random.randrange(numLevels)
		for x in range( 16 ):
			self.level.append( [] )
			for y in range( 14 ):
				self.level[x].append( 0 )

		# create level from string data
		for y in range( 14 ):
			# new row
			for x in range( 16 ):
				t = levelData[y + self.levelIndex * 14][x]
				
				# tile data
				if t == "1":
					self.level[x][y] = 1
					
				# player
				elif t == "@" and reset_player:
					self.playerStartX = x * 16 + 8
					self.playerStartY = y * 16 + 8
					self.player = playerClass( DummyPlayer( self.playerStartX, self.playerStartY ) )
					
				# enemy
				elif t == "d" and reset_enemies:
					if self.allowenemies:
						self.enemies.append( enemyClass( DummyEnemy( x * 16 + 8, y * 16 + 8 ) ) )
		
	def startMusic( self ):
		self.playingMusic = 1
		PC.playTune( self.tune )
		
	def stopMusic( self ):
		self.playingMusic = 0
		PC.stopTune()
		
	def pickNewTune( self ):
		global gameTunes
		self.setTune( gameTunes[ int( random.random() * 10 ) ] )
		gameTunes.remove( self.tune )
		gameTunes.append( self.tune )
					
	def setTune( self, tune ):
		self.tune = tune
		if self.playingMusic:
			PC.playTune( tune )
	
	def randomizeNotify( self ):
		# fix the font
		global font
		font = goodFont
		
		# create new player using updated class
		self.player = playerClass( self.player )
		
		# create new enemies using updated class
		oldEnemies = self.enemies[:]
		self.enemies = []
		if self.allowenemies:
			for o in oldEnemies:
				self.enemies.append( enemyClass( o ) )
			
		# play some music :)
		self.pickNewTune()
		
		self.rebuildLevel(False, False)
			
	def findEnemies( self, x, y, w, h ):
		# Find enemies inside these bounds
		outlist = []
		if self.allowenemies:
			for e in self.enemies:
				if ( abs( e.x - x ) <= w + 8 ) and ( abs( e.y - y ) <= h + 8 ) and ( e.state == Enemy.STATE_LIVE ):
					outlist.append( e )
		return outlist

	def update( self, delta ):
		global lives
		global board
		global environment
		global font
		
		# update timers
		self.stateTime += delta * self.timescale
		self.age += delta * self.timescale
				
		# state based updates
		if self.state == self.STATE_INGAME:
			# randomize!
			desiredRandomizeCount = int( self.stateTime / 500 )
			if desiredRandomizeCount > self.randomizeCount:
				self.randomizeCount = desiredRandomizeCount
				randomizeClasses()
				environment.cleanup()
				environment = environmentClass()
				self.randomizeNotify()
				# speed up the breakdown
				self.timescale = self.timescale * 1.1
			# serious munging
			elif self.stateTime % 500 > 400:
				# break font
				font = bonedFont
				
				# break environment, faster over time
				if random.random() < ( self.stateTime % 500 - 400 ) * 0.01:
					environment.munge()
					
			# play mangled sound
			desiredRandomizeSoundCount = int( ( self.stateTime + 100 ) / 500 )
			if desiredRandomizeSoundCount > self.randomizeSoundCount:
				self.randomizeSoundCount = desiredRandomizeSoundCount
				PC.stopTune()
				playBorked()
			
			# update player
			if self.player:
				self.player.update( self, delta )
			

			
			# udpate enemies
			deadList = []
			bornList = []
			if self.allowenemies:
				for e in self.enemies:
					e.update( self, delta )
					bornList += e.getChildren()
					if e.isDead():
						deadList.append( e )
				for d in deadList:
					self.enemies.remove( d )
				self.enemies += bornList
		
			# test for level ends
			# handle player death
			elif self.player.isDead() and self.allowenemies:
				if lives <= 0:
					# game over
					self.gameLost = 1
					self.state = self.STATE_POSTGAME
					PC.playSound( self.gameOverSound )
					self.stateTime = 0
				else:
					# lose life
					lives -= 1
					self.player.resetTo( self.playerStartX, self.playerStartY )
					self.startMusic()
			elif len( self.enemies ) <= 0:
				if self.allowenemies:
					# win
					if ( self.levelIndex + 1 >= numLevels ):
						# total win!
						board = GameWinBoard()
					else:
						PC.playSound( self.levelWinSound )
						self.state = self.STATE_POSTGAME
						self.stateTime = 0
						self.stopMusic()
				elif self.timescale >= 30.0:
					#win in without a cause mode
					board = GameWinBoard()
		elif self.state == self.STATE_POSTGAME:
			# do we need to award stuff?
			if not self.gameLost and not self.awardedLife and not self.awardedPoints and self.stateTime >= 100:
				# award stuff
				if lives < 3:
					lives += 1
					self.awardedLife = 1
					PC.playSound( self.bonusLifeSound )
				else:
					addScore( 100 )
					self.awardedPoints = 1
					PC.playSound( self.bonusPointsSound )
			# done now?
			if self.stateTime >= 200:
				if self.gameLost:
					# reset the game
					board = AttractBoard()
				else:
					# next level
					newIndex = self.levelIndex + 1
					if newIndex >= numLevels:
						newIndex = 0
					board = GameBoard()
	
	def draw( self ):
		# draw environment background
		if environment:
			environment.drawBack( self )
		
		# draw enemies
		for e in self.enemies:
			# let player try to override the draw first
			if self.player:
				if self.player.enemyDrawOverride( e ):
					continue
			
			# failed. enemy draws
			e.draw()
		
		# draw player
		if self.player:
			self.player.draw()

		# draw environment foreground
		if environment:
			environment.drawFront( self )
			
		# draw level messages via environment
		if environment:
			environment.drawLevelMessages( self )

	def keyDown( self, key ):
		# pass player control keys to player
		if self.player and key in [ KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_SPACE ]:
			self.player.keyDown( key )
			
	def keyUp( self, key ):
		# pass player control keys to player
		if self.player and key in [ KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_SPACE ]:
			self.player.keyUp( key )
	
	def safeRead( self, x, y, default ):
		if x < 0 or x >= 16 or y < 0 or y >= 14:
			return default
		return self.level[x][y]
		
	def isSolid( self, x, y ):
		if y >= 14:
			return 0
		else:
			return self.safeRead( int( math.floor( x ) ), int( math.floor( y ) ), 1 ) != 0
	
	def step( self, x, y, w, h, dx, dy ):
		# convert to level space
		x = ( x - w * 0.5 ) / 16.0
		y = ( y - h * 0.5 ) / 16.0
		w /= 16.0
		h /= 16.0
		dx /= 16.0
		dy /= 16.0
		
		# assume not hit
		hit = { "left" : 0, "right" : 0, "up" : 0, "down" : 0 }
		
		# space kept between object and walls
		space = 0.001
		
		# floor teleport
		if y + h > 14:
			y -= 14

		# check down
		if dy > 0 and (y+h)%1 + dy > 1:
			next = int( math.floor( y + h + 1 ) )
			if self.isSolid( x, next ) or self.isSolid( x + w, next ):
				# hit bottom
				hit["down"] = 1
				dy = 0
				y = next - h - space
				
		# check up
		if dy < 0 and y%1 + dy < 0:
			next = int( math.floor( y - 1 ) )
			if self.isSolid( x, next ) or self.isSolid( x + w, next ):
				# hit top
				hit["up"] = 1
				dy = 0
				y = next + 1 + space
		
		# move y
		y += dy
				
		# check right
		if dx > 0 and (x+w)%1 + dx > 1:
			next = int( math.floor( x + w + 1 ) )
			if self.isSolid( next, y ) or self.isSolid( next, y + h ):
				# hit right
				hit["right"] = 1
				dx = 0
				x = next - w - space
				
		# check left
		if dx < 0 and x%1 + dx < 0:
			next = int( math.floor( x - 1 ) )
			if self.isSolid( next, y ) or self.isSolid( next, y + h ):
				# hit left
				hit["left"] = 1
				dx = 0
				x = next + 1 + space
		
		# move x
		x += dx
		
		# convert results to screen space
		x = x * 16 + w * 16 * 0.5
		y = y * 16 + h * 16 * 0.5
		dx *= 16
		dy *= 16
		
		# return results
		return x, y, dx, dy, hit

class Player:
	STATE_LIVE = 0
	STATE_DYING = 1
	STATE_INVINCIBLE = 2
	STATE_DEAD = 3
	def __init__( self, other ):
		self.x = other.x
		self.y = other.y
		self.dx = other.dx
		self.dy = other.dy
		self.left = other.left
		self.right = other.right
		self.up = other.up
		self.down = other.down
		self.firing = other.firing
		self.w = 12
		self.h = 12
		self.state = other.state
		self.stateTime = other.stateTime
		self.hitAreaX = 6
		self.hitAreaY = 6		
		self.reload = 0
		self.reloadTime = 1
		self.maxBullets = 3
		self.autoFire = 0
		self.bullets = []
		self.localAge = 0
		self.deathTime = 100
		
	def update( self, game, delta ):
		# timer
		self.localAge += delta
		
		# state based update
		self.stateTime += delta
		if self.state == self.STATE_LIVE or self.state == self.STATE_INVINCIBLE:
			# general life update
			self.liveUpdate( game, delta )
			
			# test for firing
			self.reload = max( 0, self.reload - delta )
			if self.firing and self.reload == 0 and len( self.bullets ) < self.maxBullets:
				self.reload = self.reloadTime
				self.fire( game )
				if not self.autoFire:
					self.firing = 0
			
			# stop being invincible after a while
			if self.state == self.STATE_INVINCIBLE and self.stateTime >= 300:
				self.state = self.STATE_LIVE
				self.stateTime = 0
		elif self.state == self.STATE_DYING:
			# death effect update
			self.dyingUpdate( game, delta )
			
			# stop dying and die :)
			if self.stateTime >= self.deathTime:
				self.state = self.STATE_DEAD
				self.stateTime = 0
				
		# update our bullets
		deadList = []
		for b in self.bullets:
			b.update( game, delta )
			if b.isDead:
				deadList.append( b )
		for d in deadList:
			self.bullets.remove( d )
			
	def liveUpdate( self, game, delta ):
		pass
		
	def dyingUpdate( self, game, delta ):
		pass
			
	def die( self, game ):
		self.state = self.STATE_DYING
		self.stateTime = 0
		game.stopMusic()
		
	def fire( self, game ):
		# implement in subclasses
		pass
			
	def isDead( self ):
		return self.state == self.STATE_DEAD
		
	def resetTo( self, x, y ):
		self.x = x
		self.y = y
		self.dx = 0
		self.dy = 0
		self.state = self.STATE_INVINCIBLE
		self.stateTime = 0
		
	def draw( self ):
		# draw bullets
		for b in self.bullets:
			b.draw()
		
		# draw self
		if self.state == self.STATE_DYING:
			self.drawDying()
		elif self.state == self.STATE_INVINCIBLE:
			self.drawInvincible()
		elif self.state == self.STATE_LIVE:
			self.drawLive()
			
	def drawDying( self ):
		PC.setColour( 127, 127, 127, 255 )
		PC.fillRect( self.x - 8, self.y - 8, 16, 16 )
		
	def drawInvincible( self ):
		if frameCount % 2:
			PC.setColour( 255, 255, 255, 255 )
			PC.fillRect( self.x - 8, self.y - 8, 16, 16 )
		
	def drawLive( self ):
		PC.setColour( 255, 255, 255, 255 )
		PC.fillRect( self.x - 8, self.y - 8, 16, 16 )
		
	def enemyDrawOverride( self, enemy ):
		return 0
		
	def keyDown( self, key ):
		if key == KEY_UP:
			self.up = 1
		elif key == KEY_DOWN:
			self.down = 1
		elif key == KEY_LEFT:
			self.left = 1
		elif key == KEY_RIGHT:
			self.right = 1
		elif key == KEY_SPACE:
			self.firing = 1
		
	def keyUp( self, key ):
		if key == KEY_UP:
			self.up = 0
		elif key == KEY_DOWN:
			self.down = 0
		elif key == KEY_LEFT:
			self.left = 0
		elif key == KEY_RIGHT:
			self.right = 0
		elif key == KEY_SPACE:
			self.firing = 0
		
class DummyPlayer:
	def __init__( self, x, y ):
		# hack: minor adjust to x and y to avoid weird tile collision failure case
		self.x = x + 0.01
		self.y = y + 0.01
		self.dx = 0
		self.dy = 0
		self.left = 0
		self.right = 0
		self.up = 0
		self.down = 0
		self.firing = 0
		self.state = Player.STATE_LIVE
		self.stateTime = 0

class ZeldaPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )

		# facing
		if self.left:
			self.facing = "left"
		elif self.right:
			self.facing = "right"
		elif self.up:
			self.facing = "up"
		elif self.down:
			self.facing = "down"
		else:
			self.facing = "right"
		
		# firing
		self.reloadTime = 25
		
		# slow death
		self.deathTime = 350

	def die( self, game ):
		# super
		Player.die( self, game )
		
		# sound
		PC.playSound( self.dyingSound )

	def liveUpdate( self, game, delta ):
		# move if not firing
		if self.reload <= 0:
			# don't carry over motion
			self.dx = 0
			self.dy = 0			

			# are we going somewhere?
			if self.left and not self.right:
				self.dx = -0.75
				self.facing = "left"
			elif self.right and not self.left:
				self.dx = 0.75
				self.facing = "right"
			elif self.up and not self.down:
				self.dy = -0.75
				self.facing = "up"
			elif self.down and not self.up:
				self.dy = 0.75
				self.facing = "down"
				
			# go!
			if delta > 0:
				self.dx *= delta
				self.dy *= delta
				self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
				self.dx /= delta
				self.dy /= delta
			
		# otherwise hit stuff with sword
		else:
			victims = []
			if self.facing == "up":
				victims = game.findEnemies( self.x, self.y - 14, 1, 6 )
			elif self.facing == "down":
				victims = game.findEnemies( self.x, self.y + 14, 1, 6 )
			elif self.facing == "left":
				victims = game.findEnemies( self.x - 14, self.y, 6, 1 )
			elif self.facing == "right":
				victims = game.findEnemies( self.x + 14, self.y, 6, 1 )
			for v in victims:
				v.die()
		
		# test for death
		if self.state == self.STATE_LIVE and len( game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY ) ) > 0:
			self.die( game )
			
	def drawInvincible( self ):
		if frameCount % 2:
			self.drawLive()
		
	def drawLive( self ):
		# pick frame
		image = self.dyingImage
		if self.reload <= 0:
			# walking or standing
			if self.dx == 0  and self.dy == 0 or int( self.localAge / 10.0 ) % 2:
				image = self.images[self.facing][0]
			else:
				image = self.images[self.facing][1]
		else:
			# attacking
			image = self.images[self.facing][2]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
		
	def drawDying( self ):
		PC.setColour( 255, 0, 0, 255 )
		PC.drawmodeAdd()
		PC.fillRect( 0, 0, 256, 224 )
		PC.drawmodeNormal()
		PC.setColour( 0, 0, 0, int( 255 * self.stateTime / self.deathTime ) )
		PC.fillRect( 0, 0, 256, 224 )
		keys = [ "down", "right", "up", "left" ]
		image = self.images[ keys[ int( self.stateTime * 0.15 ) % 4 ] ][0]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
		
	def fire( self, game ):
		# play a swoosh sound
		PC.playSound( self.fireSound )

class PacmanPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )

		# facing
		if abs( self.dx ) >= abs( self.dy ):
			if self.dx >= 0:
				self.facing = "right"
			else:
				self.facing = "left"
		else:
			if self.dy >= 0:
				self.facing = "down"
			else:
				self.facing = "up"
		self.desiredFacing = self.facing
		
		# slow death
		self.deathTime = 250
		
		# power pill
		self.powerpill = 0
		self.pillSpawnTime = 50
		self.pillSpawned = 0
		self.pillx = 0
		self.pilly = 0
		
		# movement
		self.wakkaTime = 0

	def keyDown( self, key ):
		# super
		Player.keyDown( self, key )
		
		# store desired facing
		if key == KEY_UP:
			self.desiredFacing = "up"
		elif key == KEY_DOWN:
			self.desiredFacing = "down"
		elif key == KEY_LEFT:
			self.desiredFacing = "left"
		elif key == KEY_RIGHT:
			self.desiredFacing = "right"

	def liveUpdate( self, game, delta ):
		# update wakka display
		self.wakkaTime += delta
		
		# udpate pill spawn
		self.pillSpawnTime -= delta
		if not self.pillSpawned and self.pillSpawnTime < 0:
			# spawn the pill!
			self.pillSpawned = 1
			self.pillx = game.playerStartX
			self.pilly = game.playerStartY
		
		# don't carry over motion - movement is based on facing
		self.dx = 0
		self.dy = 0
		
		# try to follow key commands
		step = 1.0 * delta
		if self.desiredFacing == "left":
			# try to go left
			blah, blah, blah, blah, hit = game.step( self.x, self.y, self.w, self.h, -4, 0 )
			if not hit[ "left" ]:
				self.facing = "left"
		if self.desiredFacing == "right":
			# try to go right
			blah, blah, blah, blah, hit = game.step( self.x, self.y, self.w, self.h, 4, 0 )
			if not hit[ "right" ]:
				self.facing = "right"
		if self.desiredFacing == "up":
			# try to go up
			blah, blah, blah, blah, hit = game.step( self.x, self.y, self.w, self.h, 0, -4 )
			if not hit[ "up" ]:
				self.facing = "up"
		if self.desiredFacing == "down":
			# try to go down
			blah, blah, blah, blah, hit = game.step( self.x, self.y, self.w, self.h, 0, 4 )
			if not hit[ "down" ]:
				self.facing = "down"
		
		# set speed based on facing
		if self.facing == "left":
			self.dx = -step
			self.dy = 0
		elif self.facing == "right":
			self.dx = step
			self.dy = 0
		elif self.facing == "up":
			self.dx = 0
			self.dy = -step
		elif self.facing == "down":
			self.dx = 0
			self.dy = step
	
		# go!
		if delta > 0:
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
			
		# test for powerup
		if abs( self.x - self.pillx ) < 8 and abs( self.y - self.pilly ) < 8 and self.pillSpawned and not self.powerpill:
			self.powerpill = 1
			game.setTune( self.powerpillTune )
		
		# test for kill / death
		hitEnemies = game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY )
		if len( hitEnemies ) > 0 and self.state in [ self.STATE_LIVE, self.STATE_INVINCIBLE ]:
			if self.powerpill:
				for v in hitEnemies:
					v.die()
			elif self.state == self.STATE_LIVE:
				self.die( game )
			
	def drawInvincible( self ):
		self.drawPill()
		if frameCount % 2:
			self.drawLive()
			
	def die( self, game ):
		if not self.powerpill:
			Player.die( self, game )
			
			# dying sound
			PC.playSound( self.dyingSound )
			
	def enemyDrawOverride( self, enemy ):
		# show ghosts instead of enemies if using the powerpill
		if self.powerpill and enemy.state == enemy.STATE_LIVE:
			image = self.ghostImages[ int( enemy.stateTime / 25 ) % 2 ]
			PC.drawImage( image, enemy.x - PCR.imageWidth( image ) / 2, enemy.y - PCR.imageHeight( image ) / 2 )
			return 1
		
		# otherwise draw as usual
		return 0
	
	def drawPill( self ):
		if self.pillSpawned and not self.powerpill:
			PC.setColour( 255, 255, 255, 255 )
			PC.drawImage( self.pillImage, self.pillx - PCR.imageWidth( self.pillImage ) / 2, self.pilly - PCR.imageHeight( self.pillImage ) / 2 )
		
	def drawLive( self ):
		self.drawPill()

		# set pac colour
		PC.setColour( 239, 239, 0, 255 )
		PC.setColourize( 1 )
		
		# pick frame
		index = 0
		if self.dx != 0 or self.dy != 0:
			index = int( ( self.wakkaTime / 4 ) % 4 )
		image = self.images[self.facing][index]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )

		# fix draw state
		PC.setColourize( 0 )

	def drawDying( self ):	
		if self.stateTime < 50:
			alpha = 0
		elif self.stateTime < 150:
			alpha = ( self.stateTime - 50 ) / 100.0
		elif self.stateTime < 200:
			alpha = 1
		else:
			# break early - display nothing towards the end
			return
			
		# set pac colour
		PC.setColour( 239, 239, 0, 255 )
		PC.setColourize( 1 )

		# draw dying
		image = self.deathImages[ int( ( len( self.deathImages ) - 1 ) * alpha ) ]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
		PC.setColourize( 0 )

class InvaderPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )
		
		# self
		self.hitAreaY = 2
		self.maxBullets = 1
		self.deathTime = 200
	
	def liveUpdate( self, game, delta ):
		# move
		self.dx = ( self.right - self.left ) * 1.0
		self.dy = 0
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
			
		# test for death
		if self.state == self.STATE_LIVE and len( game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY ) ) > 0:
			self.die( game )
			
	def die( self, game ):
		# super
		Player.die( self, game )
		
		# sound
		PC.playSound( self.dyingSound )
			
	def fire( self, game ):
		PC.playSound( self.fireSound )
		self.bullets.append( Bullet( self.x, self.y - 8, 1, 2, 0, -2, 0, 1 ) )
		
	def drawLive( self ):
		PC.drawImage( self.image, self.x - PCR.imageWidth( self.image ) * 0.5, self.y - PCR.imageHeight( self.image ) * 0.5 )
		
	def drawInvincible( self ):
		if frameCount % 2:
			self.drawLive()
			
	def drawDying( self ):
		# pick a frame
		if ( self.stateTime * 0.1 ) % 1 < 0.5:
			image = self.dyingImage1
		else:
			image = self.dyingImage2
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )


class AsteroidsPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )
		
		# self
		self.maxBullets = 4
		self.a = vToA( self.dx, self.dy, math.pi )
		self.engineSoundPlayRestart = 0
		self.particles = []
	
	def liveUpdate( self, game, delta ):
		# update engine sounds
		self.engineSoundPlayRestart = max( 0, self.engineSoundPlayRestart - delta )
		if self.engineSoundPlayRestart <= 0 and self.up:
			PC.playSound( self.engineSound )
			self.engineSoundPlayRestart = 15
		
		# ignore bogus tick
		if delta <= 0:
			return
		
		# stupid dx dy unfixed frame hack
		self.dx *= delta
		self.dy *= delta
		
		# turn
		self.a += ( self.left - self.right ) * delta * 0.05
		
		# accelerate
		if self.up:
			self.dx += math.sin( self.a ) * delta * 0.05
			self.dy += math.cos( self.a ) * delta * 0.05
			
		# move
		self.x, self.y, blah, blah, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
		
		# stupid hack again
		self.dx /= delta
		self.dy /= delta
		
		# bounce :)
		if hit["left"]:
			self.dx = abs( self.dx ) * 0.25
		if hit["right"]:
			self.dx = -abs( self.dx ) * 0.25
		if hit["up"]:
			self.dy = abs( self.dy ) * 0.25
		if hit["down"]:
			self.dy = -abs( self.dy ) * 0.25
		
		# test for death
		if self.state == self.STATE_LIVE and len( game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY ) ) > 0:
			self.die( game )
			
	def fire( self, game ):
		PC.playSound( self.shootSound )
		self.bullets.append( Bullet( self.x, self.y, 1, 1, math.sin( self.a ) * 2 + self.dx, math.cos( self.a ) * 2 + self.dy, 0, 1 ) )
		
	def die( self, game ):
		# super
		Player.die( self, game )
		
		# sound
		PC.playSound( self.dyingSound )
		
		# particles
		for i in range( 5 ):
			self.particles.append( [ self.a + math.pi * 2 * ( i / 5.0 ), 3, ( random.random() * 2 - 1 ) * 0.01, random.random() * 0.1 + 0.1 ] )
		
	def drawLive( self ):
		PC.drawmodeAdd()
		PC.drawImageRotF( self.image, self.x - PCR.imageWidth( self.image ) * 0.5, self.y - PCR.imageHeight( self.image ) * 0.5, self.a + math.pi )
		PC.drawmodeNormal()

	def drawInvincible( self ):
		if frameCount % 2:
			self.drawLive()
			
	def drawDying( self ):
		moveDelta = self.stateTime * 0.1
		spinDelta = self.stateTime * 2
		PC.drawmodeAdd()
		for p in self.particles:
			PC.drawImageRotF( self.particleImage, self.x + math.sin( p[0] ) * ( moveDelta + p[1] ), self.y + math.cos( p[0] )  * ( moveDelta + p[1] ), p[0] + p[2] * spinDelta + math.pi * 0.5 )
		PC.drawmodeNormal()

class SpyHunterPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )
		
		# self
		self.maxBullets = 100
		self.autoFire = 1
		self.reloadTime = 10
		self.fireAudioIndex = 0
		
		# die a little slower :)
		self.deathTime = 200
	
	def liveUpdate( self, game, delta ):
		# move
		self.dx += ( self.right - self.left ) * delta * 0.02
		self.dy *= pow( 0.5, delta * 0.1 )
		self.dy += ( self.down - self.up ) * delta * 0.05
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
			
		# test for death
		if self.state == self.STATE_LIVE and len( game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY ) ) > 0:
			self.die( game )
		
	def dyingUpdate( self, game, delta ):
		self.dx *= pow( 0.1, delta * 0.01 )
		self.dy *= pow( 0.1, delta * 0.01 )
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
		
	def die( self, game ):
		# super
		Player.die( self, game )
		
		# sound
		PC.playSound( self.dyingSound )

	def fire( self, game ):
		self.bullets.append( Bullet( self.x - 2, self.y - 8, 1, 2, self.dx, -5, 0, 1 ) )
		self.bullets.append( Bullet( self.x + 2, self.y - 8, 1, 2, self.dx, -5, 0, 1 ) )
		
		# play sound
		self.fireAudioIndex = ( self.fireAudioIndex + 1 ) % len( self.fireSounds )
		PC.playSound( self.fireSounds[ self.fireAudioIndex ] )
		
	def drawLive( self ):
		image = self.image
		if self.dx > 0.5:
			image = self.rightImage
		elif self.dx < -0.5:
			image = self.leftImage
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
		
	def drawInvincible( self ):
		if frameCount % 2:
			self.drawLive()
			
	def drawDying( self ):
		PC.drawImage( self.dyingImage, self.x - PCR.imageWidth( self.dyingImage ) * 0.5, self.y - PCR.imageHeight( self.dyingImage ) * 0.5 )
		if self.stateTime < 100:
			image = self.explosionImage
			for i in range( int( random.random() * 3 ) ):
				x = self.x - int( random.random() * 7 - 3 )
				y = self.y - int( random.random() * 7 - 3 )
				PC.drawImage( image, x - PCR.imageWidth( image ) * 0.5, y - PCR.imageHeight( image ) * 0.5 )

class MarioPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )
		
		# determine if facing left
		self.facingLeft = self.dx < 0
		self.jumping = 0
		self.runCycle = 0
		
		# die a little slower :)
		self.deathTime = 300
		
		# keep jump button state
		self.autoFire = 1
	
	def liveUpdate( self, game, delta ):
		# move
		
		# fall faster if not pushing up or firing
		if ( self.up or self.firing ) and self.dy < 0:
			self.dy += delta * 0.0325
		else:
			self.dy += delta * 0.075
		
		# accelerate / decelerate
		if self.left and not self.right:
			# left
			self.dx = max( -1.5, self.dx - 0.025 * delta )
		elif self.right and not self.left:
			# right
			self.dx = min( 1.5, self.dx + 0.025 * delta )
		else:
			# stop
			if self.dx < 0:
				self.dx = min( 0, self.dx + 0.01 * delta )
			else:
				self.dx = max( 0, self.dx - 0.01 * delta )
				
		# update runcycle
		if self.dx == 0:
			self.runCycle = 0
		else:
			self.runCycle = ( self.runCycle + ( abs( self.dx ) + 0.5 ) * delta * 0.025 ) % 1
		
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
		if self.dy == 0 and hit["down"] and ( self.up or self.firing ):
			# jump
			self.dy = -2.1001
			PC.playSound( self.jumpSound, 0.5 )
			
		# determine facing
		if self.dx < 0:
			self.facingLeft = 1
		elif self.dx > 0:
			self.facingLeft = 0

		# fall on stuff
		self.jumping = 0
		if not hit["down"]:
			self.jumping = 1
			victims = game.findEnemies( self.x, self.y + 6, 8, 3 )
			for v in victims:
				v.die()
				self.dy = -2.1001

		# test for death
		if self.state == self.STATE_LIVE and len( game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY ) ) > 0:
			self.die( game )
			
	def dyingUpdate( self, game, delta ):
		# sit
		if self.stateTime < 50:
			pass
		else:
			# fall
			self.dy += 0.05 * delta
			
			# integrate
			self.x += self.dx * delta
			self.y += self.dy * delta

	def die( self, game ):
		# super
		Player.die( self, game )
		
		# jump
		self.dx = 0
		self.dy = -2.0001
		
		# play dying sound
		PC.playSound( self.dyingSound )
	
	def drawLive( self ):
		# left or right?
		if self.facingLeft:
			# jumping?
			if self.jumping:
				# pick jump image
				image = self.leftJumpImage
			# skidding
			elif abs( self.dx ) > 0.25 and self.right:
				image = self.leftSkidImage
			# moonwalking
			elif self.right:
				image = self.rightRunImages[ int( self.runCycle * 4 ) ]
			# standing
			elif self.dx == 0:
				image = self.leftStandImage
			# running
			else:
				# running
				image = self.leftRunImages[ int( self.runCycle * 4 ) ]
		else:
			# jumping?
			if self.jumping:
				# pick jump image
				image = self.rightJumpImage
			# skidding
			elif abs( self.dx ) > 0.25 and self.left:
				image = self.rightSkidImage
			# moonwalking
			elif self.left:
				image = self.leftRunImages[ int( self.runCycle * 4 ) ]
			# standing
			elif self.dx == 0:
				image = self.rightStandImage
			# running
			else:
				# running
				image = self.rightRunImages[ int( self.runCycle * 4 ) ]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )

	
	def drawInvincible( self ):
		if frameCount % 2:
			self.drawLive()
			
	def drawDying( self ):
		PC.drawImage( self.dyingImage, self.x - PCR.imageWidth( self.dyingImage ) * 0.5, self.y - PCR.imageHeight( self.dyingImage ) * 0.5 )

class DefenderPlayer( Player ):
	def __init__( self, other ):
		# super
		Player.__init__( self, other )
		
		# self
		self.hitAreaY = 2
		self.maxBullets = 1
		self.goLeft = self.left

		self.deathTime = 200
		
		self.particles = []
		
	def keyDown( self, key ):
		Player.keyDown( self, key )
		Player.keyDown( self, key )
		if key == KEY_LEFT:
			self.goLeft = 1
		elif key == KEY_RIGHT:
			self.goLeft = 0
	
	def liveUpdate( self, game, delta ):
		# move
		self.dx += ( self.right - self.left ) * 0.05 * delta
		self.dy = ( self.down - self.up ) * 1.0
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
			
		# test for death
		if self.state == self.STATE_LIVE and len( game.findEnemies( self.x, self.y, self.hitAreaX, self.hitAreaY ) ) > 0:
			self.die( game )
			
	def fire( self, game ):
		if self.goLeft:
			self.bullets.append( Bullet( self.x - 10, self.y, 20, 1, -10, 0, 0, 1 ) )
		else:
			self.bullets.append( Bullet( self.x + 10, self.y, 20, 1, 10, 0, 0, 1 ) )
		PC.playSound( self.fireSound )

	def die( self, game ):
		# super
		Player.die( self, game )

		# play dying sound
		PC.playSound( self.dyingSound )
		
		# start death particles
		self.particles = []
		for i in range( 128 ):
			self.particles.append( [ random.random() * math.pi * 2, 1 - pow( random.random(), 2 ) ] )
	
	def drawLive( self ):
		if self.goLeft:
			image = self.imageLeft
		else:
			image = self.imageRight
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
		
	def drawInvincible( self ):
		if frameCount % 2:
			self.drawLive()
			
	def drawDying( self ):
		# set colour
		if self.stateTime < 50:
			PC.setColour( 255, 255, 255, 255 )
		elif self.stateTime < 100:
			PC.setColour( 255, 255, 255 - int( 255 * ( self.stateTime - 50 ) / 50.0 ), 255 )
		elif self.stateTime < 150:
			PC.setColour( 255, 255 - int( 255 * ( self.stateTime - 100 ) / 50.0 ), 0, 255 )
		else:
			PC.setColour( 255, 0, 0, 255 - int( 255 * ( self.stateTime - 150 ) / 50.0 ) )
		
		# draw particles
		dist = self.stateTime * 0.75
		for p in self.particles:
			x = self.x + math.sin( p[0] ) * dist * p[1]
			y = self.y + math.cos( p[0] ) * dist * p[1]
			PC.fillRect( x - 1, y - 1, 3, 3 )

class Enemy:
	STATE_LIVE = 0
	STATE_DYING = 1
	STATE_DEAD = 2
	def __init__( self, other ):
		self.x = other.x
		self.y = other.y
		self.dx = other.dx
		self.dy = other.dy
		self.state = other.state
		self.stateTime = other.stateTime
		self.bullets = []
		self.deathTime = 100
		
	def update( self, game, delta ):
		# state based update
		self.stateTime += delta
		if self.state == self.STATE_LIVE:
			# general life update
			self.liveUpdate( game, delta )
			
		elif self.state == self.STATE_DYING:
			# death effect update
			self.dyingUpdate( game, delta )
			
			# stop dying and die :)
			if self.stateTime >= self.deathTime:
				self.state = self.STATE_DEAD
				self.stateTime = 0
				
		# bullet update
		deadList = []
		for b in self.bullets:
			b.update( game, delta )
			if b.isDead:
				deadList.append( b )
		for d in deadList:
			self.bullets.remove( d )
				
	def liveUpdate( self, game, delta ):
		pass
		
	def dyingUpdate( self, game, delta ):
		pass
		
	def die( self ):
		self.state = self.STATE_DYING
		self.stateTime = 0
		addScore( 10 )
		
	def getChildren( self ):
		return []
		
	def draw( self ):
		# draw bullets
		for b in self.bullets:
			b.draw()
		
		# draw self
		if self.state == self.STATE_DYING:
			self.drawDying()
		else:
			self.drawLive()
		
	def drawLive( self ):
		PC.setColour( 255, 0, 0, 255 )
		PC.fillRect( self.x - 8, self.y - 8, 16, 16 )
		
	def drawDying( self ):
		pass
		#PC.setColour( 255, 127, 127, 255 )			
		#PC.fillRect( self.x - 8, self.y - 8, 16, 16 )		
		
	def isDead( self ):
		return self.state == self.STATE_DEAD
				
class DummyEnemy:
	def __init__( self, x, y ):
		# hack: minor adjust to x and y to avoid weird tile collision failure case
		self.x = x + 0.01
		self.y = y + 0.01
		self.dx = 0
		self.dy = 0
		self.state = Enemy.STATE_LIVE
		self.stateTime = 0

class ArkanoidEnemy( Enemy ):
	def __init__( self, other ):
		Enemy.__init__( self, other )
		self.deathTime = 10.0
	
	def die( self ):
		# super
		Enemy.die( self )
		
		# play death sound
		PC.playSound( self.dyingSound )
	
	def drawLive( self ):
		self.drawBrick( self.x, self.y - 4 )
		self.drawBrick( self.x, self.y + 4 )
		
	def drawBrick( self, x, y ):
		image = self.images[ int( y / 8 ) % len( self.images ) ]
		PC.drawImage( image, x - 8, y - 4 )
		
	def drawDying( self ):
		PC.setColour( 255, 255, 255, 255 )
		PC.fillRect( self.x - 8, self.y - 8, 16, 16 )
	
	
class InvaderEnemy( Enemy ):
	def __init__( self, other ):
		# super
		Enemy.__init__( self, other )
		
		# self
		self.dx = 1
		self.dy = 0
		self.type = int( self.y / 16 ) % 4
		
		self.reload = random.random() * 200
		
		self.age = 0
	
	def die( self ):
		# super
		Enemy.die( self )
		
		# play death sound
		PC.playSound( self.dyingSound )

	def liveUpdate( self, game, delta ):
		# timers
		self.age += delta
		
		# determine speed
		speed = 1.5 * delta
		if len( game.enemies ) > 0:
			liveCount = 0
			for e in game.enemies:
				if e.state == self.STATE_LIVE:
					liveCount += 1
			speed /= liveCount

		# move
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, blah, blah, hit = game.step( self.x, self.y, 15, 15, self.dx * speed, self.dy * speed )
			self.dx /= delta
			self.dy /= delta
		if hit["left"]:
			self.dx = abs( self.dx )
			self.x, self.y, blah, blah, blah = game.step( self.x, self.y, 15, 15, 0, 8 )
		if hit["right"]:
			self.dx = -abs( self.dx )
			self.x, self.y, blah, blah, blah = game.step( self.x, self.y, 15, 15, 0, 8 )

		# fire
		self.reload -= delta
		if self.reload <= 0:
			self.reload = 200
			self.bullets.append( Bullet( self.x, self.y + 5, 1, 4, 0, 0.3, 1, 0 ) )
			
			
	def drawLive( self ):
		image = self.images[ self.type ][ int( self.age * 0.2 ) % 2 ]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
	
	def drawDying( self ):
		image = self.dyingImage
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
	
class GoombaEnemy( Enemy ):
	def __init__( self, other ):
		# super
		Enemy.__init__( self, other )
		
		# self
		if self.dx < 0:
			self.dx = -0.5
		elif self.dx > 0:
			self.dx = 0.5
		else:
			if random.random() < 0.5:
				self.dx = 0.5
			else:
				self.dx = -0.5
				
		self.period = random.random()
		self.deathTime = 50.0
		
	def liveUpdate( self, game, delta ):
		# move
		self.dy += delta * 0.15
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, blah, self.dy, hit = game.step( self.x, self.y, 15, 15, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
		if hit["left"]:
			self.dx = abs( self.dx )
		if hit["right"]:
			self.dx = -abs( self.dx )
			
		# update animation
		self.period = ( self.period + delta * 0.02 ) % 1

	def dyingUpdate( self, game, delta ):
		# move
		self.dy += delta * 0.15
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, blah, self.dy, hit = game.step( self.x, self.y, 15, 15, 0, self.dy )
			self.dx /= delta
			self.dy /= delta
	
	def die( self ):
		# super
		Enemy.die( self )
		
		# play death sound
		PC.playSound( self.dyingSound, 0.5 )
		
	def drawLive( self ):
		PC.drawImage( self.images[ int( self.period * 2 ) ], self.x - 8, self.y - 8 )
	
	def drawDying( self ):
		PC.drawImage( self.dyingImage, self.x - 8, self.y - 8 )
	
class GauntletEnemy( Enemy ):
	def __init__( self, other ):
		# super
		Enemy.__init__( self, other )
		
		# self
		self.facing = "down"
		self.deathTime =  15
	
	def liveUpdate( self, game, delta ):
		# move
		self.pickFacing( game.player )
		speed = 0.5
		self.dx *= speed
		self.dy *= speed		
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			# clamp movement to target
			if self.dx > 0:
				self.dx = min( self.dx, game.player.x - self.x )
			elif self.dx < 0:
				self.dx = max( self.dx, game.player.x - self.x )
			if self.dy > 0:
				self.dy = min( self.dy, game.player.y - self.y )
			elif self.dy < 0:
				self.dy = max( self.dy, game.player.y - self.y )
				
			
			self.x, self.y, blah, self.dy, hit = game.step( self.x, self.y, 15, 15, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
			
	def pickFacing( self, target ):
		a = ( vToA( target.x - self.x, target.y - self.y, 0 ) % ( math.pi * 2.0 ) ) * 8 / math.pi / 2.0
		if a < 1:
			self.facing = "down"
			self.dx = 0
			self.dy = 1
		elif a < 2:
			self.facing = "downright"
			self.dx = 0.707
			self.dy = 0.707
		elif a < 3:
			self.facing = "right"
			self.dx = 1
			self.dy = 0
		elif a < 4:
			self.facing = "upright"
			self.dx = 0.707
			self.dy = -0.707
		elif a < 5:
			self.facing = "up"
			self.dx = 0
			self.dy = -1
		elif a < 6:
			self.facing = "upleft"
			self.dx = -0.707
			self.dy = -0.707
		elif a < 7:
			self.facing = "left"
			self.dx = -1
			self.dy = 0
		else:
			self.facing = "downleft"
			self.dx = -0.707
			self.dy =0.707
		
	def drawLive( self ):
		image = self.images[ self.facing ]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
	
	def drawDying( self ):
		image = self.dyingImages[ int( self.stateTime / float( self.deathTime ) * len( self.dyingImages ) ) ]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
	
class QixEnemy( Enemy ):
	def __init__( self, other ):
		# super
		Enemy.__init__( self, other )
		
		# inherited
		self.deathTime =  25
		
		# self
		self.lines = []
		self.lineAge = self.stateTime
		a = random.random() * math.pi * 2
		self.lineX = math.sin( a ) * 16
		self.lineY = math.cos( a ) * 16
		self.newTarget()
		self.lines.append( [ self.x - self.lineX, self.y - self.lineY, self.x + self.lineX, self.y + self.lineY, self.colour[:] ] )

	def newTarget( self ):
		a = random.random() * math.pi * 2
		self.lineXStart = self.lineX
		self.lineYStart = self.lineY
		self.lineXTarget = math.sin( a ) * 16
		self.lineYTarget = math.cos( a ) * 16
		a = random.random() * math.pi * 2
		r = 16 + random.random() * 32
		self.startX = self.x
		self.startY = self.y
		self.targetX = math.sin( a ) * r + self.x
		self.targetY = math.cos( a ) * r + self.y
		self.targetStartTime = self.stateTime
		self.targetEndTime = self.stateTime + 75
		self.colour = [ int( random.random() * 256 ), int( random.random() * 256 ), int( random.random() * 256 ) ]
		if random.random() < 1/3.0:
			self.colour = [ 255, 73, 73 ]
		elif random.random() < 0.5:
			self.colour = [ 73, 255, 73 ]
		else:
			self.colour = [ 73, 73, 255 ]
		self.alpha = 0
		
	def die( self ):
		# super
		Enemy.die( self )
		
		# play death sound
		PC.playSound( self.dyingSound, 0.7 )
		
	def liveUpdate( self, game, delta ):
		# timer
		self.alpha = ( self.stateTime - self.targetStartTime ) / ( self.targetEndTime - self.targetStartTime )
		if self.alpha > 1:
			self.newTarget()
			
		# lines
		while self.lineAge <= self.stateTime:
			# drop a line
			a = ( self.lineAge - self.targetStartTime ) / ( self.targetEndTime - self.targetStartTime )
			x = self.startX + ( self.targetX - self.startX ) * self.alpha
			y = self.startY + ( self.targetY - self.startY ) * self.alpha
			self.lineX = self.lineXStart + ( self.lineXTarget - self.lineXStart ) * a
			self.lineY = self.lineYStart + ( self.lineYTarget - self.lineYStart ) * a
			self.lines.append( [ x - self.lineX, y - self.lineY, x + self.lineX, y + self.lineY, self.colour[:] ] )
			
			# max size
			while len( self.lines ) > 8:
				self.lines = self.lines[1:]
			
			# update line age
			self.lineAge += 10
		
		# move
		self.dx = self.startX + ( self.targetX - self.startX ) * self.alpha - self.x
		self.dy = self.startY + ( self.targetY - self.startY ) * self.alpha - self.y
		
		self.x, self.y, blah, blah, hit = game.step( self.x, self.y, 15, 15, self.dx, self.dy )
		self.dx /= delta
		self.dy /= delta

		if hit["left"] or hit["right"] or hit["up"] or hit["down"]:
			self.newTarget()
						
	def drawLive( self ):
		for l in self.lines:
			PC.setColour( l[4][0], l[4][1], l[4][2], 255 )
			PC.drawLine( l[0], l[1], l[2], l[3] )
			
	def drawDying( self ):
		for l in self.lines:
			PC.setColour( 255, 255, 255, 255 )
			for i in range( 4 ):
				l[i] += int( random.random() * 7 - 3 )
			PC.drawLine( l[0], l[1], l[2], l[3] )
			#PC.drawLine( l[0] + int( random.random() * 7 - 3 ), l[1] + int( random.random() * 7 - 3 ), l[2] + int( random.random() * 7 - 3 ), l[3] + int( random.random() * 7 - 3 ) )			

class AsteroidEnemy( Enemy ):
	def __init__( self, other ):
		# super
		Enemy.__init__( self, other )
		self.deathTime =  50
		
		# self
		self.a = random.random() * math.pi * 2
		self.da = ( int( random.random() * 2 ) * 2 - 1 ) * 0.05
		self.size = int( random.random() * 2 )
		self.image = self.images[self.size][int( random.random() * 2 )]
		self.spawning = 0
		self.particles = []

	def reset( self, size ):
		self.size = size
		self.image = self.images[self.size][int( random.random() * 2 )]
		self.state = self.STATE_LIVE

	def liveUpdate( self, game, delta ):
		# move
		self.a += self.da * delta / ( self.size + 1 )
		
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, blah, blah, hit = game.step( self.x, self.y, 15, 15, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta

		if hit["left"]:
			self.dx = abs( self.dx )
		if hit["right"]:
			self.dx = -abs( self.dx )
		if hit["up"]:
			self.dy = abs( self.dy )
		if hit["down"]:
			self.dy = -abs( self.dy )
			
	def getChildren( self ):
		spawn = []
		if self.spawning and self.size > 0:
			a = random.random() * math.pi
			for i in range( 2 ):
				spawn.append( AsteroidEnemy( self ) )
				spawn[-1].reset( self.size - 1 )
				spawn[-1].dx += math.sin( a ) * 1
				spawn[-1].dy += math.sin( a ) * 1
				a += math.pi
		self.spawning = 0
		return spawn
		
	def die( self ):
		self.spawning = 1
		Enemy.die( self )
		if self.size <= 0:
			PC.playSound( self.dyingSoundSmall )
		else:
			PC.playSound( self.dyingSoundMedium )
			
		# start death particles
		self.particles = []
		for i in range( 16 ):
			self.particles.append( [ random.random() * math.pi * 2, 0.25 + 0.75 * ( 1 - pow( random.random(), 2 ) ) ] )
			
	def drawLive( self ):
		PC.drawmodeAdd()
		PC.drawImageRotF( self.image, self.x - PCR.imageWidth( self.image ) * 0.5, self.y - PCR.imageHeight( self.image ) * 0.5, self.a + math.pi )
		PC.drawmodeNormal()
		
	def drawDying( self ):
		# draw particles
		PC.setColour( 255, 255, 255, 255 )
		dist = self.stateTime * 0.3
		for p in self.particles:
			x = self.x + math.sin( p[0] ) * dist * p[1]
			y = self.y + math.cos( p[0] ) * dist * p[1]
			PC.fillRect( x, y, 1, 1 )

class PangEnemy( Enemy ):
	def __init__( self, other ):
		# super
		Enemy.__init__( self, other )
		
		# self
		self.size = int( random.random() * 2 )
		self.spawning = 0
		
		if self.dx > 0:
			self.dx  = 0.5
		else:
			self.dx = -0.5
			
		self.deathTime = 15

	def reset( self, size ):
		self.size = size
		self.state = self.STATE_LIVE

	def liveUpdate( self, game, delta ):
		# move
		
		self.dy += 0.01 * delta
		
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, blah, blah, hit = game.step( self.x, self.y, 15, 15, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta

		if hit["left"]:
			self.dx = abs( self.dx )
		if hit["right"]:
			self.dx = -abs( self.dx )
		if hit["up"]:
			self.dy = abs( self.dy )
		if hit["down"]:
			self.dy = ( self.size + 1 ) * ( -0.75 )
			
	def getChildren( self ):
		spawn = []
		if self.spawning and self.size > 0:
			spawn.append( PangEnemy( self ) )
			spawn[-1].reset( self.size - 1 )
			spawn[-1].dx = 0.5
			spawn[-1].dy = -abs( self.dy )
			spawn.append( PangEnemy( self ) )
			spawn[-1].reset( self.size - 1 )
			spawn[-1].dx = -0.5
			spawn[-1].dy = -abs( self.dy )
		self.spawning = 0
		return spawn
		
	def die( self ):
		self.spawning = 1
		Enemy.die( self )
		PC.playSound( self.dyingSound )
			
	def drawLive( self ):
		image = self.images[ self.size ]
		PC.drawImage( image, self.x - PCR.imageWidth( image ) * 0.5, self.y - PCR.imageHeight( image ) * 0.5 )
		
	def drawDying( self ):
		image = self.dyingImages[ min( 2, int( self.stateTime / 5.0 ) ) ]
		scale = self.size * 8 + 8
		PC.drawImageScaled( image, self.x - scale, self.y - scale, scale * 2, scale * 2 )

class Bullet:
	def __init__( self, x, y, w, h, dx, dy, hitsPlayer, hitsEnemies ):
		self.x = x
		self.y = y
		self.w = w
		self.h = h
		self.dx = dx
		self.dy = dy
		self.hitsPlayer = hitsPlayer
		self.hitsEnemies = hitsEnemies
		self.isDead = 0
		
	def update( self, game, delta ):
		# move
		if delta > 0:
			self.dx *= delta
			self.dy *= delta
			self.x, self.y, self.dx, self.dy, hit = game.step( self.x, self.y, self.w, self.h, self.dx, self.dy )
			self.dx /= delta
			self.dy /= delta
		if 1 in hit.values():
			# hit geometry. die now.
			self.isDead = 1
			return
		
		# hit player
		if self.hitsPlayer and game.player:
			if ( abs( self.x - game.player.x ) <= self.w + game.player.hitAreaX ) and ( abs( self.y - game.player.y ) <= self.h + game.player.hitAreaY ) and ( game.player.state == Player.STATE_LIVE ):
				game.player.die( game )
				self.isDead = 1
				
		# hit enemy
		if self.hitsEnemies:
			for e in game.enemies:
				if ( abs( self.x - e.x ) <= self.w + 8 ) and ( abs( self.y - e.y ) <= self.h + 8 ) and ( e.state == Enemy.STATE_LIVE ):
					e.die()
					self.isDead = 1
					
	def draw( self ):
		PC.setColour( 255, 255, 255, 255 )
		PC.fillRect( self.x - self.w, self.y - self.h, self.w * 2, self.h * 2 )

class Environment:
	def __init__( self ):
		self.imageList = []
		self.initialize()
		
	def initialize( self ):
		pass
		
	def cleanup( self ):
		for i in self.imageList:
			PCR.unloadImage( i )
		
	def update( self, delta ):
		pass
		
	def munge( self ):
		# early out if no images
		if len( self.imageList ) == 0:
			return
		
		# pick an image
		image = self.imageList[ int( random.random() * len( self.imageList ) ) ]
		# pick mash method
		if random.random() < 0.5:
			# palette
			PCR.mashPalette( image )
		else:
			# image
			PCR.mashImage( image )

	def drawBack( self, game ):	
		# draw background
		self.drawBackImage( game )

		# draw tiles
		for x in range( 16 ):
			for y in range( 14 ):
				t = game.level[x][y]
				if t != 0:
					self.drawTile( game, t, x, y )
					
	def drawBackImage( self, game ):
		PC.setColour( 0, 0, 0, 255 )
		PC.fillRect( 0, 0, 256, 224 )
		
	def drawTile( self, game, tile, x, y ):
		PC.setColour( 0, 0, 255, 255 )
		PC.fillRect( x * 16, y * 16, 16, 16 )
		
	def drawFront( self, game ):
		drawStringShadowed( "  1-UP    HIGH SCORE    LIVES  ", 0, 16, 255, 0, 0 )
		PC.setColour( 255, 255, 255, 255 )
		string = str( score )
		drawStringShadowed( string, ( 4 - len( string ) / 2 ) * 8, 24 )
		string = str( highscore )
		drawStringShadowed( string, ( 15 - len( string ) / 2 ) * 8, 24 )
		string = str( lives )
		drawStringShadowed( string, ( 26 - len( string ) / 2 ) * 8, 24 )
		
	def drawLevelMessages( self, game ):
		# state based
		if game.state == game.STATE_INGAME:
			if game.stateTime < 100 and game.stateTime % 20 < 10:
				string = "GO!"
				PC.setColour( 255, 255, 255, 255 )
				drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) / 2, 96 )
		if game.state == game.STATE_POSTGAME:
			PC.setColour( 255, 255, 255, 255 )
			if game.gameLost:
				string = "GAME OVER"
				drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) / 2, 96 )
			else:
				# level completed
				string = "LEVEL COMPLETE!"
				drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) / 2, 96 )
				if game.awardedLife:
					string = "BONUS LIFE AWARDED!"
					drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) / 2, 112 )
				elif game.awardedPoints:
					string = "+100 POINTS!"
					drawStringShadowed( string, 128 - PCR.stringWidth( string, font ) / 2, 112 )
		

class MarioEnvironment( Environment ):
	def initialize( self ):
		# load images and register for munging
		MarioEnvironment.backImage = PCR.loadImage( "res\\environments\\marioBackground" )
		MarioEnvironment.tileImage = PCR.loadImage( "res\\environments\\marioTile" )
		MarioEnvironment.tileLowerImage = PCR.loadImage( "res\\environments\\marioTileLower" )
		MarioEnvironment.tileUpperImage = PCR.loadImage( "res\\environments\\marioTileUpper" )
		self.imageList = [ self.backImage, self.tileImage, self.tileLowerImage, self.tileUpperImage ]

	def drawBackImage( self, game ):
		# background
		PC.drawImage( self.backImage, 0, 0 )
	
	def drawTile( self, game, tile, x, y ):
		# lower
		if y >= 13:
			PC.drawImage( self.tileLowerImage, x * 16, y * 16 )
		# upper
		elif not game.safeRead( x, y-1, 1 ):
			PC.drawImage( self.tileUpperImage, x * 16, y * 16 )
		# normal
		else:
			PC.drawImage( self.tileImage, x * 16, y * 16 )

class PangEnvironment( Environment ):
	def initialize( self ):
		# load images and register for munging
		PangEnvironment.backImage = PCR.loadImage( "res\\environments\\pangBackground" )
		PangEnvironment.tileOuterImage = PCR.loadImage( "res\\environments\\pangTileOuter" )
		PangEnvironment.tileVertImage = PCR.loadImage( "res\\environments\\pangTileVert" )
		PangEnvironment.tileHorizImage = PCR.loadImage( "res\\environments\\pangTileHoriz" )
		self.imageList = [ self.backImage, self.tileOuterImage, self.tileVertImage, self.tileHorizImage ]
	
	def drawBackImage( self, game ):
		# background
		PC.drawImage( self.backImage, 0, 0 )

	def drawTile( self, game, tile, x, y ):
		# lower
		if y <= 0 or y >= 13 or x <= 0 or x >= 15:
			image = self.tileOuterImage
		elif game.safeRead( x, y - 1, 0 ) or game.safeRead( x, y + 1, 0 ):
			image = self.tileVertImage
		else:
			image = self.tileHorizImage
		PC.drawImage( image, x * 16, y * 16 )
		
class BombermanEnvironment( Environment ):
	def initialize( self ):
		# load images and register for munging
		BombermanEnvironment.softImage = PCR.loadImage( "res\\environments\\bombermanTileSoft" )
		BombermanEnvironment.hardImage = PCR.loadImage( "res\\environments\\bombermanTileHard" )
		self.imageList = [ self.softImage, self.hardImage ]
		self.backColour = [ 0, 149, 0 ]
		
	def munge( self ):
		# super
		Environment.munge( self )
		
		# self
		self.backColour = [ int( random.random() * 255 ), int( random.random() * 255 ), int( random.random() * 255 ), 255 ]
	
	def drawBackImage( self, game ):
		# background
		PC.setColour( self.backColour[0], self.backColour[1], self.backColour[2], 255 )
		PC.fillRect( 0, 0, 256, 224 )

	def drawTile( self, game, tile, x, y ):
		# hard
		hardX = [ 2, 4, 6, 9, 11, 13 ]
		hardY = [ 2, 4, 6, 7, 9, 11 ]
		if y <= 0 or y >= 13 or x <= 0 or x >= 15 or ( x in hardX and y in hardY ):
			image = self.hardImage
		else:
			image = self.softImage
		PC.drawImage( image, x * 16, y * 16 )

class PacManEnvironment( Environment ):
	def initialize( self ):
		PacManEnvironment.backImage = PCR.loadImage( "res\\environments\\pacBackground" )
		PacManEnvironment.emptyTileImage = PCR.loadImage( "res\\environments\\pacTileE" )
		PacManEnvironment.TLOTileImage = PCR.loadImage( "res\\environments\\pacTileTLO" )
		PacManEnvironment.TROTileImage = PCR.loadImage( "res\\environments\\pacTileTRO" )
		PacManEnvironment.BROTileImage = PCR.loadImage( "res\\environments\\pacTileBRO" )
		PacManEnvironment.BLOTileImage = PCR.loadImage( "res\\environments\\pacTileBLO" )
		PacManEnvironment.THTileImage = PCR.loadImage( "res\\environments\\pacTileTH" )
		PacManEnvironment.BHTileImage = PCR.loadImage( "res\\environments\\pacTileBH" )
		PacManEnvironment.LVTileImage = PCR.loadImage( "res\\environments\\pacTileLV" )
		PacManEnvironment.RVTileImage = PCR.loadImage( "res\\environments\\pacTileRV" )
		PacManEnvironment.TLITileImage = PCR.loadImage( "res\\environments\\pacTileTLI" )
		PacManEnvironment.TRITileImage = PCR.loadImage( "res\\environments\\pacTileTRI" )
		PacManEnvironment.BRITileImage = PCR.loadImage( "res\\environments\\pacTileBRI" )
		PacManEnvironment.BLITileImage = PCR.loadImage( "res\\environments\\pacTileBLI" )
		self.imageList = [ self.backImage, self.emptyTileImage, self.TLOTileImage, self.TROTileImage, self.BROTileImage, self.BLOTileImage, self.THTileImage, self.BHTileImage, self.LVTileImage, self.RVTileImage, self.TLITileImage, self.TRITileImage, self.BRITileImage, self.BLITileImage ]

	def drawBack( self, game ):
		# clear background
		PC.setColour( 0, 0, 0, 255 )
		PC.fillRect( 0, 0, 256, 224 )

		# draw tiles here (since we're using subtiles)
		for x in range( 16 ):
			for y in range( 14 ):
				# ignore empty
				if game.safeRead( x, y, 0 ) == 0:
					continue
				
				# top left
				if game.safeRead( x, y-1, 1 ):
					if game.safeRead( x-1, y, 1 ):
						if game.safeRead( x-1, y-1, 1 ):
							PC.drawImage( self.emptyTileImage, x * 16, y * 16 )
						else:
							PC.drawImage( self.TLITileImage, x * 16, y * 16 )
					else:
						PC.drawImage( self.LVTileImage, x * 16, y * 16 )
				else:
					if game.safeRead( x-1, y, 1 ):
						PC.drawImage(  self.THTileImage, x * 16, y * 16 )
					else:
						PC.drawImage( self.TLOTileImage, x * 16, y * 16 )
				# top right
				if game.safeRead( x, y-1, 1 ):
					if game.safeRead( x+1, y, 1 ):
						if game.safeRead( x+1, y-1, 1 ):
							PC.drawImage( self.emptyTileImage, x * 16 + 8, y * 16 )
						else:
							PC.drawImage( self.TRITileImage, x * 16 + 8, y * 16 )
					else:
						PC.drawImage( self.RVTileImage, x * 16 + 8, y * 16 )
				else:
					if game.safeRead( x+1, y, 1 ):
						PC.drawImage( self.THTileImage, x * 16 + 8, y * 16 )
					else:
						PC.drawImage( self.TROTileImage, x * 16 + 8, y * 16 )
				# bottom right
				if game.safeRead( x, y+1, 1 ):
					if game.safeRead( x+1, y, 1 ):
						if game.safeRead( x+1, y+1, 1 ):
							PC.drawImage( self.emptyTileImage, x * 16 + 8, y * 16 + 8 )
						else:
							PC.drawImage( self.BRITileImage, x * 16 + 8, y * 16 + 8 )
					else:
						PC.drawImage( self.RVTileImage, x * 16 + 8, y * 16 + 8 )
				else:
					if game.safeRead( x+1, y, 1 ):
						PC.drawImage( self.BHTileImage, x * 16 + 8, y * 16 + 8 )
					else:
						PC.drawImage( self.BROTileImage, x * 16 + 8, y * 16 + 8 )
				# bottom left
				if game.safeRead( x, y+1, 1 ):
					if game.safeRead( x-1, y, 1 ):
						if game.safeRead( x-1, y+1, 1 ):
							PC.drawImage( self.emptyTileImage, x * 16, y * 16 + 8 )
						else:
							PC.drawImage( self.BLITileImage, x * 16, y * 16 + 8 )
					else:
						PC.drawImage( self.LVTileImage, x * 16, y * 16 + 8 )
				else:
					if game.safeRead( x-1, y, 1 ):
						PC.drawImage( self.BHTileImage, x * 16, y * 16 + 8 )
					else:
						PC.drawImage( self.BLOTileImage, x * 16, y * 16 + 8 )
						
		# background image
		PC.drawImage( self.backImage, 0, 0 )
		
class MoonPatrolEnvironment( Environment ):
	def initialize( self ):
		MoonPatrolEnvironment.backImage = PCR.loadImage( "res\\environments\\moonpatrolback" )
		self.imageList.append( self.backImage )
		MoonPatrolEnvironment.tileImages = []
		for i in range( 8 ):
			MoonPatrolEnvironment.tileImages.append( PCR.loadImage( "res\\environments\\moonTile" + str( i + 1 ) ) )
			self.imageList.append( self.tileImages[-1] )

	def __init__( self ):
		# super
		Environment.__init__( self )
		
		# self
		self.tiles = []
		self.backTiles = []
		for x in range( 16 ):
			self.tiles.append( [] )
			self.backTiles.append( [] )
			for y in range( 14 ):
				choices = range( len( self.tileImages ) )
				choice = choices[ int( random.random() * len( choices ) ) ]
				choices.remove( choice )
				self.tiles[-1].append( self.tileImages[ choice ] )
				choice = choices[ int( random.random() * len( choices ) ) ]
				self.backTiles[-1].append( self.tileImages[ choice ] )
	
	def drawBackImage( self, game ):
		# background
		PC.drawImage( self.backImage, 0, 0 )
		
	def drawTile( self, game, tile, x, y ):
		PC.setColourize( 1 )
		if y >= 11:
			PC.setColour( 255, 165, 82, 255 )
		else:
			if game.safeRead( x + 1, y, 1 ) == 0 or game.safeRead( x, y - 1, 1 ) == 0:
				# edge
				PC.setColour( 82, 85, 0, 255 )
				PC.drawImage( self.backTiles[x][y], x * 16 - 5 + 1, y * 16 - 5 - 1 )
			PC.setColour( 164, 121, 0, 255 )
		PC.drawImage( self.tiles[x][y], x * 16 - 5, y * 16 - 5 )
		PC.setColourize( 0 )

class ArkanoidEnvironment( Environment ):
	def initialize( self ):
		ArkanoidEnvironment.backImage = PCR.loadImage( "res\\environments\\arkanoidBackground" )
		ArkanoidEnvironment.foreImage = PCR.loadImage( "res\\environments\\arkanoidForeground" )
		ArkanoidEnvironment.tileImage = PCR.loadImage( "res\\environments\\arkanoidTile" )	
		self.imageList = [ self.backImage, self.foreImage, self.tileImage ]
	
	def drawBackImage( self, game ):
		# background
		PC.drawImage( self.backImage, 0, 0 )
		
	def drawFront( self, game ):
		# frame
		PC.drawImage( self.foreImage, 0, 0 )
		
		# super
		Environment.drawFront( self, game )
		
	def drawTile( self, game, tile, x, y ):
		PC.drawImage( self.tileImage, x * 16, y * 16 )
		
class SpyHunterEnvironment( Environment ):
	def initialize( self ):
		SpyHunterEnvironment.backImage = PCR.loadImage( "res\\environments\\spyhunterBackground" )
		SpyHunterEnvironment.tileImage = PCR.loadImage( "res\\environments\\spyhunterTile" )
		self.imageList = [ self.backImage, self.tileImage ]
	
	def drawBackImage( self, game ):
		# scroll amount
		scroll = int( game.age * 2.0 ) % 224
		# background
		PC.drawImage( self.backImage, 0, scroll - 224 )
		PC.drawImage( self.backImage, 0, scroll )

	def drawTile( self, game, tile, x, y ):
		PC.drawImage( self.tileImage, x * 16, y * 16 )
		
def randomizeClasses():
	global playerClass, playerClasses
	global enemyClass, enemyClasses
	global environmentClass, environmentClasses
	
	# pick a player class
	playerClass = playerClasses[ int( random.random() * 3 ) ]
	playerClasses.remove( playerClass )
	playerClasses.append( playerClass )

	# pick an enemy class
	enemyClass = enemyClasses[ int( random.random() * 3 ) ]
	enemyClasses.remove( enemyClass )
	enemyClasses.append( enemyClass )

	# pick an environment class
	environmentClass = environmentClasses[ int( random.random() * 3 ) ]
	environmentClasses.remove( environmentClass )
	environmentClasses.append( environmentClass )
	
	# hardcode test
	if 0:
		playerClass = PacmanPlayer
		#enemyClass = QixEnemy
		#environmentClass = BombermanEnvironment

def playBorked( time = 1 ):
	sound = borkedSounds[ int( random.random() * len( borkedSounds ) ) ]
	PC.playSound( sound, 0.5, 0, 12 * math.log( float( gameSpeed ) / time, 2 ) )

def startGame():
	# new game data init
	global lives
	clearScore()
	lives = 3
	
	# create initial board
	global gameSpeed
	gameSpeed = 1
	global board
	board = GameBoard()
	
def addScore( ds ):
	global score
	global highscore
	score += ds
	highscore = max( highscore, score )
	
def clearScore():
	global score
	score = 0
	
def readHighscore():
	# prepare the highscore object
	global highscore
	highscore = 1000

	# attempt to read string from registry
	hsString = PC.readReg( "highscore" )
	# check to see if anything was found
	if hsString:
		# found. cast to highscore
		if int( hsString ) > 0:
			highscore = int( hsString )

def writeHighscore():
	# create string
	hsString = str( highscore )
	# write string to registry
	PC.writeReg( "highscore", hsString )

def loadBase():
	import PycapRes
	global PCR
	PCR = PycapRes
	
	# load base fonts
	global goodFont
	goodFont = PCR.loadFont( "res\\fonts\\Arcade6.txt" )
	global bonedFont
	bonedFont = PCR.loadFont( "res\\fonts\\ArcadeBoned6.txt" )
	
	# load sprites
	GoombaEnemy.images = [0,0]
	GoombaEnemy.images[0] = PCR.loadImage( "res\\sprites\\goomba1" )
	GoombaEnemy.images[1] = PCR.loadImage( "res\\sprites\\goomba2" )
	GoombaEnemy.dyingImage = PCR.loadImage( "res\\sprites\\goombaFlat" )
	
	InvaderEnemy.images = []
	InvaderEnemy.images.append([])
	InvaderEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\invader1a" ) )
	InvaderEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\invader1b" ) )
	InvaderEnemy.images.append([])
	InvaderEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\invader2a" ) )
	InvaderEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\invader2b" ) )
	InvaderEnemy.images.append([])
	InvaderEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\invader3a" ) )
	InvaderEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\invader3b" ) )
	InvaderEnemy.images.append([])
	InvaderEnemy.images[-1].append( InvaderEnemy.images[1][0] )
	InvaderEnemy.images[-1].append( InvaderEnemy.images[1][1] )
	InvaderEnemy.dyingImage = PCR.loadImage( "res\\sprites\\invaderDeath" )
	
	ArkanoidEnemy.images = []
	ArkanoidEnemy.images.append( PCR.loadImage( "res\\sprites\\arkanoidBrick1" ) )
	ArkanoidEnemy.images.append( PCR.loadImage( "res\\sprites\\arkanoidBrick2" ) )
	ArkanoidEnemy.images.append( PCR.loadImage( "res\\sprites\\arkanoidBrick3" ) )
	ArkanoidEnemy.images.append( PCR.loadImage( "res\\sprites\\arkanoidBrick4" ) )
	ArkanoidEnemy.images.append( PCR.loadImage( "res\\sprites\\arkanoidBrick5" ) )
	
	AsteroidEnemy.images = []
	AsteroidEnemy.images.append([])
	AsteroidEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\asteroidSmall1" ) )
	AsteroidEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\asteroidSmall2" ) )
	AsteroidEnemy.images.append([])
	AsteroidEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\asteroidBig1" ) )
	AsteroidEnemy.images[-1].append( PCR.loadImage( "res\\sprites\\asteroidBig2" ) )
	
	PangEnemy.images = []
	PangEnemy.images.append( PCR.loadImage( "res\\sprites\\pangBallSmall" ) )
	PangEnemy.images.append( PCR.loadImage( "res\\sprites\\pangBallMedium" ) )
	PangEnemy.dyingImages = []
	for i in range( 3 ):
		PangEnemy.dyingImages.append( PCR.loadImage( "res\\sprites\\pangExplosion" + str( i + 1 ) ) )
	
	GauntletEnemy.images = {}
	GauntletEnemy.images["up"] = PCR.loadImage( "res\\sprites\\gauntletGhostU" )
	GauntletEnemy.images["upright"] = PCR.loadImage( "res\\sprites\\gauntletGhostUR" )
	GauntletEnemy.images["upleft"] = PCR.loadImage( "res\\sprites\\gauntletGhostUL" )
	GauntletEnemy.images["down"] = PCR.loadImage( "res\\sprites\\gauntletGhostD" )
	GauntletEnemy.images["downleft"] = PCR.loadImage( "res\\sprites\\gauntletGhostDL" )
	GauntletEnemy.images["downright"] = PCR.loadImage( "res\\sprites\\gauntletGhostDR" )
	GauntletEnemy.images["left"] = PCR.loadImage( "res\\sprites\\gauntletGhostL" )
	GauntletEnemy.images["right"] = PCR.loadImage( "res\\sprites\\gauntletGhostR" )
	GauntletEnemy.dyingImages = []
	GauntletEnemy.dyingImages.append( PCR.loadImage( "res\\sprites\\gauntletExplosion1" ) )
	GauntletEnemy.dyingImages.append( PCR.loadImage( "res\\sprites\\gauntletExplosion2" ) )
	
	MarioPlayer.dyingImage = PCR.loadImage( "res\\sprites\\marioDeath" )
	MarioPlayer.leftRunImages = []
	MarioPlayer.leftRunImages.append( PCR.loadImage( "res\\sprites\\marioRun2Left" ) )
	MarioPlayer.leftRunImages.append( PCR.loadImage( "res\\sprites\\marioRun3Left" ) )
	MarioPlayer.leftRunImages.append( PCR.loadImage( "res\\sprites\\marioRun1Left" ) )
	MarioPlayer.leftRunImages.append( PCR.loadImage( "res\\sprites\\marioRun3Left" ) )
	MarioPlayer.leftSkidImage = PCR.loadImage( "res\\sprites\\marioSkidLeft" )
	MarioPlayer.leftJumpImage = PCR.loadImage( "res\\sprites\\marioJumpLeft" )
	MarioPlayer.leftStandImage = PCR.loadImage( "res\\sprites\\marioStaticLeft" )
	MarioPlayer.rightRunImages = []
	MarioPlayer.rightRunImages.append( PCR.loadImage( "res\\sprites\\marioRun2Right" ) )
	MarioPlayer.rightRunImages.append( PCR.loadImage( "res\\sprites\\marioRun3Right" ) )
	MarioPlayer.rightRunImages.append( PCR.loadImage( "res\\sprites\\marioRun1Right" ) )
	MarioPlayer.rightRunImages.append( PCR.loadImage( "res\\sprites\\marioRun3Right" ) )
	MarioPlayer.rightSkidImage = PCR.loadImage( "res\\sprites\\marioSkidRight" )
	MarioPlayer.rightJumpImage = PCR.loadImage( "res\\sprites\\marioJumpRight" )
	MarioPlayer.rightStandImage = PCR.loadImage( "res\\sprites\\marioStaticRight" )

	InvaderPlayer.image = PCR.loadImage( "res\\sprites\\antiinvader" )
	InvaderPlayer.dyingImage1 = PCR.loadImage( "res\\sprites\\antiinvaderDying1" )
	InvaderPlayer.dyingImage2 = PCR.loadImage( "res\\sprites\\antiinvaderDying2" )
	
	AsteroidsPlayer.image = PCR.loadImage( "res\\sprites\\asteroidsPlayer" )
	AsteroidsPlayer.particleImage = PCR.loadImage( "res\\sprites\\asteroidsPlayerParticle" )
	
	ZeldaPlayer.dyingImage = PCR.loadImage( "res\\sprites\\linkDead" )
	ZeldaPlayer.images = {}
	ZeldaPlayer.images["left"] = []
	ZeldaPlayer.images["left"].append( PCR.loadImage( "res\\sprites\\linkLeft1" ) )
	ZeldaPlayer.images["left"].append( PCR.loadImage( "res\\sprites\\linkLeft2" ) )
	ZeldaPlayer.images["left"].append( PCR.loadImage( "res\\sprites\\linkLeftAttack" ) )
	ZeldaPlayer.images["right"] = []
	ZeldaPlayer.images["right"].append( PCR.loadImage( "res\\sprites\\linkRight1" ) )
	ZeldaPlayer.images["right"].append( PCR.loadImage( "res\\sprites\\linkRight2" ) )
	ZeldaPlayer.images["right"].append( PCR.loadImage( "res\\sprites\\linkRightAttack" ) )
	ZeldaPlayer.images["up"] = []
	ZeldaPlayer.images["up"].append( PCR.loadImage( "res\\sprites\\linkUp1" ) )
	ZeldaPlayer.images["up"].append( PCR.loadImage( "res\\sprites\\linkUp2" ) )
	ZeldaPlayer.images["up"].append( PCR.loadImage( "res\\sprites\\linkUpAttack" ) )
	ZeldaPlayer.images["down"] = []
	ZeldaPlayer.images["down"].append( PCR.loadImage( "res\\sprites\\linkDown1" ) )
	ZeldaPlayer.images["down"].append( PCR.loadImage( "res\\sprites\\linkDown2" ) )
	ZeldaPlayer.images["down"].append( PCR.loadImage( "res\\sprites\\linkDownAttack" ) )
	
	SpyHunterPlayer.image = PCR.loadImage( "res\\sprites\\spyhunter" )
	SpyHunterPlayer.rightImage = PCR.loadImage( "res\\sprites\\spyhunterRight" )
	SpyHunterPlayer.leftImage = PCR.loadImage( "res\\sprites\\spyhunterLeft" )
	SpyHunterPlayer.dyingImage = PCR.loadImage( "res\\sprites\\spyhunterDying" )
	SpyHunterPlayer.explosionImage = PCR.loadImage( "res\\sprites\\spyhunterExplosion" )
	
	DefenderPlayer.imageLeft = PCR.loadImage( "res\\sprites\\defenderLeft" )
	DefenderPlayer.imageRight = PCR.loadImage( "res\\sprites\\defenderRight" )
	
	ballImage = PCR.loadImage( "res\\sprites\\pacmanBall" )
	PacmanPlayer.images = {}
	PacmanPlayer.images["left"] = []
	PacmanPlayer.images["left"].append( PCR.loadImage( "res\\sprites\\pacmanLeft1" ) )
	PacmanPlayer.images["left"].append( PCR.loadImage( "res\\sprites\\pacmanLeft2" ) )
	PacmanPlayer.images["left"].append( PacmanPlayer.images["left"][0] )
	PacmanPlayer.images["left"].append( ballImage )
	PacmanPlayer.images["right"] = []
	PacmanPlayer.images["right"].append( PCR.loadImage( "res\\sprites\\pacmanRight1" ) )
	PacmanPlayer.images["right"].append( PCR.loadImage( "res\\sprites\\pacmanRight2" ) )
	PacmanPlayer.images["right"].append( PacmanPlayer.images["right"][0] )
	PacmanPlayer.images["right"].append( ballImage )
	PacmanPlayer.images["up"] = []
	PacmanPlayer.images["up"].append( PCR.loadImage( "res\\sprites\\pacmanUp1" ) )
	PacmanPlayer.images["up"].append( PCR.loadImage( "res\\sprites\\pacmanUp2" ) )
	PacmanPlayer.images["up"].append( PacmanPlayer.images["up"][0] )
	PacmanPlayer.images["up"].append( ballImage )
	PacmanPlayer.images["down"] = []
	PacmanPlayer.images["down"].append( PCR.loadImage( "res\\sprites\\pacmanDown1" ) )
	PacmanPlayer.images["down"].append( PCR.loadImage( "res\\sprites\\pacmanDown2" ) )
	PacmanPlayer.images["down"].append( PacmanPlayer.images["down"][0] )
	PacmanPlayer.images["down"].append( ballImage )
	PacmanPlayer.deathImages = []
	for i in range( 11 ):
		PacmanPlayer.deathImages.append( PCR.loadImage( "res\\sprites\\pacmanDeath" + str( i + 1 ) ) )
	PacmanPlayer.ghostImages = []
	for i in range( 2 ):
		PacmanPlayer.ghostImages.append( PCR.loadImage( "res\\sprites\\pacGhost" + str( i + 1 ) ) )
	PacmanPlayer.pillImage = PCR.loadImage( "res\\sprites\\pacmanPill" )
	
	# game win images
	GameWinBoard.images = []
	for i in range( 31 ):
		GameWinBoard.images.append( PCR.loadImage( "res\\endings\\end" + str( i + 1 ) ) )
	
	# load sounds
	global borkedSounds
	for i in range( 4 ):
		borkedSounds.append( PCR.loadSound( "res\\audio\\borked" + str( i + 1 ) ) )
	
	ChipCheckBoard.checkSound = PCR.loadSound( "res\\audio\\check" )
	ChipCheckBoard.okSound = PCR.loadSound( "res\\audio\\ok" )
	ChipCheckBoard.failSound = PCR.loadSound( "res\\audio\\fail" )
	ChipCheckBoard.errorSound = PCR.loadSound( "res\\audio\\error" )
	
	GameBoard.levelWinSound = ChipCheckBoard.checkSound
	GameBoard.bonusLifeSound = ChipCheckBoard.okSound
	GameBoard.bonusPointsSound = ChipCheckBoard.okSound
	GameBoard.gameOverSound = ChipCheckBoard.errorSound
	
	AsteroidsPlayer.shootSound = PCR.loadSound( "res\\audio\\asteroidsPlayerShoot" )
	AsteroidsPlayer.dyingSound = PCR.loadSound( "res\\audio\\asteroidsPlayerDeath" )
	AsteroidsPlayer.engineSound = PCR.loadSound( "res\\audio\\asteroidsPlayerEngine" )

	MarioPlayer.jumpSound = PCR.loadSound( "res\\audio\\marioJump" )
	MarioPlayer.dyingSound = PCR.loadSound( "res\\audio\\marioDying" )

	InvaderPlayer.dyingSound = PCR.loadSound( "res\\audio\\invaderPlayerDying" )
	InvaderPlayer.fireSound = PCR.loadSound( "res\\audio\\invaderFire" )
	
	ZeldaPlayer.dyingSound = PCR.loadSound( "res\\audio\\zeldaPlayerDying" )
	ZeldaPlayer.fireSound = PCR.loadSound( "res\\audio\\zeldaPlayerFire" )

	DefenderPlayer.dyingSound = PCR.loadSound( "res\\audio\\defenderDying" )
	DefenderPlayer.fireSound = PCR.loadSound( "res\\audio\\defenderFire" )

	SpyHunterPlayer.dyingSound = PCR.loadSound( "res\\audio\\spyHunterDying" )
	SpyHunterPlayer.fireSounds = []
	for i in range( 7 ):
		SpyHunterPlayer.fireSounds.append( PCR.loadSound( "res\\audio\\spyHunterFiring" + str( i + 1 ) ) )
		
	PacmanPlayer.dyingSound = PCR.loadSound( "res\\audio\\pacmanDying" )

	AsteroidEnemy.dyingSoundMedium = PCR.loadSound( "res\\audio\\asteroidDeathMedium" )
	AsteroidEnemy.dyingSoundSmall= PCR.loadSound( "res\\audio\\asteroidDeathSmall" )

	GoombaEnemy.dyingSound = PCR.loadSound( "res\\audio\\goombaDying" )

	ArkanoidEnemy.dyingSound = PCR.loadSound( "res\\audio\\arkanoidBlockDying" )

	InvaderEnemy.dyingSound = PCR.loadSound( "res\\audio\\invaderDying" )

	PangEnemy.dyingSound = PCR.loadSound( "res\\audio\\pangPop" )
	
	QixEnemy.dyingSound = PCR.loadSound( "res\\audio\\qixDying" )

	# load music
	global gameTunes
	gameTunes.append( PCR.loadTune( "res\\audio\\Asteroids.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\BubbleBobble1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\BubbleBobble2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\BubbleBobble3.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\DonkeyKong.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\DoubleDragon1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\DoubleDragon2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\DoubleDragon3.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Galaga.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Galaxian.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\MarbleMadness1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\MarbleMadness2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Mario1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Mario2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Mario3.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Mario4.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\MarioBoss1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\MarioBoss2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\MarioStar.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\MoonPatrol.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\PacMan.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Rodland.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\SpyHunter1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\SpyHunter2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\SpyHunter3.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\TetrisA1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\TetrisA2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\TetrisB1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Wonderboy.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Wonderboyboss1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Wonderboyboss2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Zelda1.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Zelda2.Mid" ) )
	gameTunes.append( PCR.loadTune( "res\\audio\\Zelda3.Mid" ) )
	
	PacmanPlayer.powerpillTune = PCR.loadTune( "res\\audio\\PacmanPowerpill.Mid" )
	
	# randomize game tunes
	tunes = gameTunes[:]
	gameTunes = []
	while len( tunes ) > 0:
		tune = tunes[ int( random.random() * len( tunes ) ) ]
		gameTunes.append( tune )
		tunes.remove( tune )
	
	# load level data
	global levelData
	global numLevels
	f = open( "res\\levels.lvl", "r" )
	buffer = f.readline()
	while buffer != "":
		levelData.append( buffer[:] )
		buffer = f.readline()
	numLevels = len( levelData ) / 14
	
	# load general crud
	FarbsSplashBoard.image = PCR.loadImage( "res\\crud\\farbsbs.png" )
	TestGridBoard.image = PCR.loadImage( "res\\crud\\testgrid.png" )
	AttractBoard.image = PCR.loadImage( "res\\crud\\newtitle" )

def init():
	# hide mouse
	PC.showMouse( 0 )
	
	# set initial font
	global font
	font = goodFont
	
	# set initial game speed
	global gameSpeed
	gameSpeed = 1
	
	# grab key scan codes
	global KEY_UP
	global KEY_DOWN
	global KEY_LEFT
	global KEY_RIGHT
	global KEY_SPACE
	global KEY_ESC
	KEY_UP	= PC.getKeyCode( "UP" )
	KEY_DOWN	= PC.getKeyCode( "DOWN" )
	KEY_LEFT	= PC.getKeyCode( "LEFT" )
	KEY_RIGHT	= PC.getKeyCode( "RIGHT" )
	KEY_SPACE = PC.getKeyCode( "SPACE" )
	KEY_ESC	= PC.getKeyCode( "ESCAPE" )
	
	# pick initial classes for game
	global playerClasses, enemyClasses, environmentClasses
	playerClasses = [ ZeldaPlayer, MarioPlayer, SpyHunterPlayer, DefenderPlayer, AsteroidsPlayer, PacmanPlayer, InvaderPlayer ]
	enemyClasses = [ PangEnemy, AsteroidEnemy, GoombaEnemy, InvaderEnemy, ArkanoidEnemy, GauntletEnemy, QixEnemy ]
	environmentClasses = [ MarioEnvironment, MoonPatrolEnvironment, PacManEnvironment, ArkanoidEnvironment, SpyHunterEnvironment, PangEnvironment, BombermanEnvironment ]
	randomizeClasses()
	
	# choose general environment
	global environment
	environment = environmentClass()
	
	# load highscore value
	readHighscore()
	
	# start it up :)
	global board
	board = AttractBoard()

def fini():
	# save highscore value
	writeHighscore()

def update( delta ):			
	# update board	
	if board:
		board.update( delta * ( gameSpeed * 0.5 + 0.5 ) )
	PC.markDirty()

def draw():
	global frameCount
	frameCount += 1
	
	# set default font
	PC.setFont( font )
	
	# draw the board
	if board:
		board.draw()
	
	# return to normal draw position
	PC.setTranslation( 0, 0 )

def keyDown( key ):
	if key == KEY_ESC:
		# quit
		global doExit
		doExit = 1
	# otherwise pass on to board
	elif board:
		board.keyDown( key )
		
def keyUp( key ):
	if board:
		board.keyUp( key )

def exitGame():
	return doExit;
	
def drawStringShadowed( string, x, y, r=255, g=255, b=255, a=255 ):
	PC.setColour( 0, 0, 0, 255 )
	PC.drawString( string, x + 1, y + 1 )
	PC.setColour( r, g, b, a )
	PC.drawString( string, x, y )

def vToA( x, y, a ):
	"""calculate angle from a 2D vector and default value"""
	if y == 0:
		# dangerous special case (avoids div by 0 in dx/dy)
		if x > 0:
			return math.pi * 0.5
		elif x < 0:
			return -math.pi * 0.5
		else:
			# don't change angle... previous is probably best
			return a
	else:
		# safe to use atan technique
		a = math.atan( x / y )
		if y < 0:
			# inverted
			a += math.pi
		return a
