#!/usr/bin/python

# Fydo's Snake Game (snakegame.py)
systemVersion = "0.9"
# Copyright (C) 2006-2007 Chris 'fydo' Hopp
# Web: fydo.net	E-Mail: fydo@fydo.net

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# A copy of the GPL is available at http://www.gnu.org/copyleft/gpl.html

# Requirements / Dependencies:
#	python
#	pygame

#
# This is a clone of the ever-popular, super duper famous game in which
# you are a snake that likes to eat things. However, you like to eat things
# so much that you end up eating whatever is right in front of you.
# As such, you'll be in trouble if you've got a wall in front of you, or
# worse, your own snakey body. Since you're so poisonous, you'll die instantly
# if you bite yourself.
# However, if you manage to position yourself in front of something that is edible,
# you will eat it. And you will get points for it. And everything will be right
# in the world. In this game, everyone wins. Until you lose.
#
# Enjoy! :D
#

# Import needed stuff
import pygame, random, base64, zlib, optparse
from pygame.locals import *

USAGE = """%prog [-f]"""

VERSION = "%prog v" + systemVersion

def parse_options():
	"""parse_options() -> opts, args

	Parse and command-line options given returning both
	the parsed options and arguments.
	"""

	parser = optparse.OptionParser(usage=USAGE, version=VERSION)

	parser.add_option("-f", "--fullscreen",
			action="store_true", default=False, dest="fullscreen",
			help="enable fullscreen mode")

	opts, args = parser.parse_args()
	return opts, args

def get_spritesheet():
	"""get_image_data_ready() --> appleimage

	This function uncompresses the game image data and provides it as a pygame.Surface
	"""

	# spritesheet.png image data
	imagedata = ''.join((
	'eNqVle2CoyAMRflytSIqRUerqO//lnMD6qpju7P5YSmEHJJAUterCFGfxQnTdUa4+E/fyK6nb2Xf',
	'SzKOIxhutFYpO247u6jZRc23jHeESNkQNod4YdWSJEmuAsQddhr3nuGE/ijxgKNanhAlpukJSJ7b',
	'CyJC7hmb3jAM56V9IhzQLn8gz0lNTzDyXJEjQg99T1rxR7xhuGImHd6mfD5BhpmnLafRXDi48STE',
	'M0mWBQgFsYRgvCp63RcVZz1Bbhlcws7QSkhxZhQ012IOKvU4kROUCRJyQ1lEgDVVXbGB4dvwHh7f',
	'MRxvuO6KF8w183lpbjD5AhgqzoKxI8AgiNB9Kx/+wTTDt5EZHKHDZSzrg4UeIwqhcF/6y3tA2DUf',
	'DAjvseyEsCENJ4aFGzjZq8l0hq+kgFBuWStbTpCetzLN6GqKEkb8I70iCJI+PI5QCmEs8nBEEGMg',
	'hpQp8pHSgMdtnMawNoRVhqdTlvgYU97fW6zgjZVlp5KDRIbSkbGbOzJ2MDH0r+QHg3KudZYewtIy',
	'HYLFTwFk2qyPoytNOPHFB1Ouc2ZnTMvmhr1JLyU9k81+EWTbI+URICDuUlGMo9mIEWpzAK/8L+Pm',
	'nhr4dLjQbDDOEcAFa9eqFcC0ZsrS2TUNYEyfGHiXuj88zIEev3O1k8GauMYqzkpHOmMe05DTHY6I',
	'8b42CQrhWmCygKh9VfsiRKr8eanIv8LXlV+rLkzn07SspeRz/TvUOu9ReAU1mrubS6FypANIMGxV',
	'vlaS93X8Wu8xRYx3DQQUMNzWokaq8fEBfuhH1771Hz1q64YWMv6zrx7672977TfETF20'
	))

	# Decompress it
	theimage = pygame.image.fromstring(zlib.decompress(base64.decodestring(imagedata)),(100, 20), 'P')
	
	# Add some colors
	palette = (  (91, 213, 91), (141, 210, 141), (7, 90, 6), (250, 253, 250), (156, 2, 1), (241, 2, 2),
	  (248, 101, 101), (196, 237, 196), (60, 209, 60), (146, 153, 146), (203, 1, 1), (20, 200, 20),
	  (104, 171, 104), (14, 146, 14), (19, 176, 19), (100, 108, 100), (1, 1, 0), (104, 1, 0),
	  (245, 62, 62), (6, 39, 5), (224, 232, 224), (220, 246, 220), (46, 143, 46), (19, 188, 19),
	  (38, 202, 38), (199, 205, 199), (26, 199, 26), (46, 2, 0), (42, 57, 42), (232, 38, 37),
	  (63, 186, 63), (236, 23, 23),
	)

	theimage.set_palette(palette)
	theimage.set_colorkey(None, RLEACCEL)

	return theimage

# Make sure things are ULTRA RANDOM!
random.seed()

# Time for some class definitions. Here is the PlayerSnake, that contains all info about the snake
class PlayerSnake(pygame.sprite.Sprite):
	def __init__(self, color):
		pygame.sprite.Sprite.__init__(self)
		self.image = pygame.Surface((20, 20))
		self.image = self.image.convert()
		self.rect = self.image.get_rect()
		self.rect = pygame.draw.rect(self.image, color, self.rect)

		# Default values
		self.length = 1
		self.train = [(40,40)] # default starting location
		self.direction = 1 # 1 is right, 2 is left, 3 is down, 4 is up
		self.headedDirection = 1 # direction is where you're moving for sure, but this is where you want to go
		self.lastDirectionthisturn = 0
		self.locx = 40
		self.locy = 40
		self.life = True # It's aliiiiiive!

	def setDirection(self, newdirection):
		"""
		Alters the direction the snake is currently going
		"""	
	
		# Some error checking, as you can't go backwards (do a 180-degree turn)
		if self.direction == 1 and newdirection == 2:
			return
		elif self.direction == 2 and newdirection == 1:
			return
		elif self.direction == 3 and newdirection == 4:
			return
		elif self.direction == 4 and newdirection == 3:
			return
		else:
			self.headedDirection = newdirection
	
			if self.lastDirectionthisturn != self.headedDirection:
				self.lastDirectionthisturn = self.direction
				self.move()

	def move(self):
		"""
		Moves the snake (and does other bits of logic too)
		"""
		if self.headedDirection == 1: # right
			self.direction = 1
			self.locx += 20
			if self.locx > 620:
				self.locx = 620
				self.life = False
				return

		elif self.headedDirection == 2: # left
			self.direction = 2
			self.locx -= 20
			if self.locx < 0:
				self.locx = 0
				self.life = False
				return

		elif self.headedDirection == 3: # down
			self.direction = 3
			self.locy += 20
			if self.locy > 420:
				self.locy = 420
				self.life = False
				return

		elif self.headedDirection == 4: # up
			self.direction = 4
			self.locy -= 20
			if self.locy < 0:
				self.locy = 0
				self.life = False
				return

		# This part is a little tricky. self.train is an array, and so we want to insert
		# our current location into the *front* of the array.
		self.train.insert(0, (self.rect.left, self.rect.top))
		# And then we'll chop off the end of the array if needed (to convey a sense of movement)
		self.train = self.train[0:self.length-1]	

		if self.length < 3: # 3 is the minimum length of snake
			self.length += 1

		# Update the rect for drawing later
		self.rect.left = self.locx
		self.rect.top = self.locy

		for segment in self.train[:]:
			if (segment[0] == self.rect.left) and (segment[1] == self.rect.top):
				#touched self (segment), omg died!
				self.life = False
				return
		
	def update(self):
		pass

def DrawHead(screen, player, spritesheet):
	""" DrawHead

	Gets the Player's current direction and determines which sprite to
	use from the spritesheet
	"""
	if player.direction == 4: #up
		screen.blit(spritesheet, player.rect, (20,0,20,20))
	elif player.direction == 3: #down
		screen.blit(spritesheet, player.rect, (40,0,20,20))
	elif player.direction == 2: #left
		screen.blit(spritesheet, player.rect, (60,0,20,20))	
	elif player.direction == 1: #right
		screen.blit(spritesheet, player.rect, (80,0,20,20))

# Haha.. a Piece of Fruit!
# These probably don't need to be two separate classes, I just thought it was funny.
# Anyways, the Fruit class tracks the location of the yummy red fruit that the
# snake loves so much.
class Piece(pygame.sprite.Sprite):
	def __init__(self, color, x, y, spritesheet):
		pygame.sprite.Sprite.__init__(self)
		self.image = pygame.Surface((20,20))
		self.image.blit(spritesheet, (0,0), (0,0,20,20))
		self.rect = self.image.get_rect()
		self.rect.left = x
		self.rect.top = y

	def update(self):
		pass

class Fruit(Piece):
	def __init__(self, spritesheet):
		Piece.__init__(self, (200, 10, 10), random.randint(0,30)*20, random.randint(0,20)*20, spritesheet)

	def update(self):
		pass

# This function was created so that the escape button will still work
# while displaying a title (intro/death/gameover)
def Pause(waittime):

	# Fancy time stuff
	currtime = pygame.time.get_ticks()
	maxtime = currtime + waittime
	
	while (currtime <= maxtime):
		event = pygame.event.poll()
		if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
			pygame.display.quit()
			return True # Note that we're returning true if the user wants to quit

		pygame.time.delay(100)
		currtime += 100 # Yeah, I realize this isn't very accurate, because the handling of
		# event above will use up some milliseconds, but this serves its purpose

	return False

def IsCollision(player, fruit, spritesheet):
	if (player.rect.left == fruit.rect.left) and (player.rect.top == fruit.rect.top):
		awesome = False
		while not awesome:
			fruit.__init__(spritesheet) #reset location of the yummy fruit
			countOffendingSquares = 0
			for segment in player.train[:]:
				if (segment[0] == fruit.rect.left) and (segment[1] == fruit.rect.top):
					countOffendingSquares += 1
					#print 'prevented a bad from happening'
				
			if countOffendingSquares == 0:
				awesome = True
		player.length += 2 
		return True
	else:
		return False

# Ah, the ubiqutious main() !
def main(fullscreen):

	# This game is so complex that we will attempt to enable psyco for a performance boost.
	try:
		import psyco
		psyco.background()
		print 'psyco found and enabled!'
	except ImportError:
		print 'psyco not found! don\'t worry, this game doesn\'t really need it.'

	# Initialize! Pow!
	pygame.init()
	size = width, height = 640, 480
	if fullscreen:
		screen = pygame.display.set_mode(size, FULLSCREEN)
	else:
		screen = pygame.display.set_mode(size)
	pygame.mouse.set_visible(False)
	pygame.display.set_caption('fydo\'s snake game! v' + systemVersion)
	spritesheet = get_spritesheet()
	font = pygame.font.Font(None, 40)
	score = 0
	lives = 2
	presscount = 1
	
	# Mmm... colors.
	white = (255, 255, 255)
	lightgrey = (225, 225, 225)
	black = (0, 0, 0)
	green = (25, 200, 25)

	# Setting up some surfaces
	background = pygame.Surface(screen.get_size())
	background = background.convert()
	background.fill(black)
	statusBar = pygame.Surface((640, 40))
	statusBar = statusBar.convert()
	statusBar.fill(lightgrey)

	# show background & status bar
	screen.blit(background, (0, 0))
	screen.blit(statusBar, (0, 440))

	# Build a title/welcome screen by blitting two pieces of text to the screen
	text = font.render("fydo's snake game!", 1, white)
	textpos = text.get_rect(centerx = screen.get_width()/2, centery = (screen.get_height()/2 - 80))
	screen.blit(text, textpos)

	text = font.render("visit http://fydo.net", 1, white) # Everyone loves me website! Har!
	textpos = (text.get_rect(centerx = screen.get_width()/2)[0], textpos[1] + 60)
	screen.blit(text, textpos)

	# textpos = (appleimage.get_rect(centerx = screen.get_width()/2)[0], textpos[1] + 80)
	# screen.blit(appleimage, textpos)

	pygame.display.flip()

	# Initialize some important objects
	player = PlayerSnake(green)
	yumfruit = Fruit(spritesheet)
	allsprites = pygame.sprite.RenderPlain((player, yumfruit))

	if Pause(3000): return # Wait 3 seconds for the user to memorize my website address ;)
	pygame.event.clear()

	while 1:
		
		# Add some delay inbetween frames (other processes running on the user's machine LOVE this!)
		pygame.time.delay(120 - int((player.length) / 2.2)) # delay increases as your snake length increases

		player.lastDirectionthisturn = 0		

		if pygame.event.peek([QUIT, KEYDOWN]):
			# Inputs! Loop through the entire queue and do the appropriate action
			for event in pygame.event.get(): # Note that I'm leaving everything else on the queue
				if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
					pygame.display.quit()
					return
				if event.type == KEYDOWN:
					if event.key == K_RIGHT:
						presscount += 1
						player.setDirection(1)
					elif event.key == K_LEFT:
						presscount += 1
						player.setDirection(2)
					elif event.key == K_DOWN:
						presscount += 1
						player.setDirection(3)
					elif event.key == K_UP:
						presscount += 1
						player.setDirection(4)
					elif event.key == K_f:
						fullscreen = not fullscreen
						if fullscreen:
							pygame.display.set_mode(size, FULLSCREEN)
						else:
							pygame.display.set_mode(size)
						#pygame.display.flip()
					if IsCollision(player, yumfruit, spritesheet): score += 100
		else:
			player.move() # Player object computes movement, etc

		# Check to see if player has eaten a fruit
		if IsCollision(player, yumfruit, spritesheet): score += 100
			
		screen.blit(background, (0, 0))

		if player.life == False:
			if lives == 0: # No more lifes, game over!

				# Draw the 'game over' titles				
				text = font.render("you totally lost!", 1, white)
				textpos = text.get_rect(centerx = screen.get_width()/2, centery = screen.get_height()/2 - 60)
				screen.blit(text, textpos)
				
				text = font.render("final score: %d" % (score), 1, white)
				textpos = (text.get_rect(centerx = screen.get_width()/2)[0], textpos[1] + 80)
				screen.blit(text, textpos)
				
				# Show them
				pygame.display.flip()
				print '\nyou totally lost!'
				print '\nyour final score: %d' % (score)
				print 'your efficiency: %d / %d' % ((score / 100), presscount)

				# Pause for 3 seconds so the user can notice that he or she has lost
				Pause(3000)	
				return

			else: # Player is dead from running into something bad, but not a game over yet
				lives -= 1

				# Draw the fruit and stuff
				allsprites.update()
				allsprites.draw(screen)

				# Draw each bit of the snake
				for segment in player.train[:]:
					screen.blit(player.image, segment)

				# Draw head of the snake
				#if player.length > 2:
				#	DrawHead(screen, player, spritesheet)

				screen.blit(statusBar, (0, 440))
	
				# The player needs to know that they should be getting ready!
				text = font.render("get ready!", 1, white)
				textpos = text.get_rect(centerx = screen.get_width()/2, centery = screen.get_height()/2)
				screen.blit(text, textpos)

				# Show the frame
				pygame.display.flip()

				# Pause for 1.2 seconds
				if Pause(1200): return 

				# Reset everything!
				pygame.event.clear()
				player.__init__(green)
				
				awesome = False
				while not awesome:
					yumfruit.__init__(spritesheet) #reset location of the yummy fruit
					countOffendingSquares = 0
					for segment in player.train[:]:
						if (segment[0] == yumfruit.rect.left) and (segment[1] == yumfruit.rect.top):
							countOffendingSquares += 1
							#print 'prevented a bad from happening'
						
					if countOffendingSquares == 0:
						awesome = True

		else:
			# Draw the fruit and stuff
			allsprites.update()
			allsprites.draw(screen)

			# Draw each bit of the snake
			for segment in player.train:
				screen.blit(player.image, segment)
			# Draw head of the snake
			if player.length > 1:
				DrawHead(screen, player, spritesheet)			

			# Statusbar fun!
			screen.blit(statusBar, (0, 440))
			#ff = "0"
			#if score != 0: # Compute the efficiency of the player
			eff = "%d / %d" % ((score / 100), presscount)
			text = font.render("score: %d   lives left: %d   eff: %s" % (score, lives, eff), 1, black)
			textpos = (text.get_rect(centerx = screen.get_width()/2)[0], 440)
			screen.blit(text, textpos)

			# Show the complete, updated frame
			pygame.display.flip()

if __name__ == '__main__':
	# Welcome the player
	print 'fydo\'s snake game - version %s - http://www.fydo.net' % systemVersion

	opts, args = parse_options()

	# Play time
	main(opts.fullscreen)
	
	# Say goodbye like every good little program should
	print '\nthanks for playing!'
