#!/usr/bin/python

# Fydo's Snake Game v0.8
# Copyright (C) 2006 Chris 'fydo' Hopp - October 2006
# 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,
#  (a red block that is symbolic of a fruit) 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
from pygame.locals import *

# 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!

	# Alters the direction the snake is currently going
	def setDirection(self, newdirection):
		
		# 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()

	# Moves the snake (and does other bits of logic too)
	def move(self):
		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

		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

		# 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

	def update(self):
		pass

# 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):
		pygame.sprite.Sprite.__init__(self)
		self.image = pygame.Surface((20, 20))
		self.rect = self.image.get_rect()
        	self.rect = pygame.draw.rect(self.image, color, self.rect)
		self.rect.left = x
		self.rect.top = y

	def update(self):
		pass

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

	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):
	if (player.rect.left == fruit.rect.left) and (player.rect.top == fruit.rect.top):
		awesome = False
		while not awesome:
			fruit.__init__() #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():
	# Initialize! Pow!
	pygame.init()
	size = width, height = 640, 480
	screen = pygame.display.set_mode(size)
	pygame.display.set_caption('fydo\'s snake game!')
	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)

	pygame.display.flip()

	# Initialize some important objects
	player = PlayerSnake(green)
	yumfruit = Fruit()
	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(150 - 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)
					if IsCollision(player, yumfruit): score += 100
		else:
			player.move() # Player object computes movement, etc

		# Check to see if player has eaten a fruit
		if IsCollision(player, yumfruit): 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! your final score: %d' % (score)

				# 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)

				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__() #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)

			# Statusbar fun!
			screen.blit(statusBar, (0, 440))
			eff = "0.000"
			if score != 0: # Compute the efficiency of the player
				eff = "%.3f" % (100 - 4*(presscount / (score / 100.0)))
			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 0.8 - http://www.fydo.net'

	# 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.'
	
	# Play time
	main()
	
	# Say goodbye like every good little program should
	print 'thanks for playing!'
