################################################
###              'Serial'                    ###
### A game made for the PyWeek#1 contest     ###
### By Robin Palotai (Team 'Ron')            ###
###    p_robin@freemail.hu                   ###
### Source under GPL License, see 'copyright'###
################################################

# hardcore game-in-one-source follows.. oh my..
# at least it's indented :DD

import pygame
import pygame.display
import math
import sys
import os
from pygame.locals import *
from string import strip, split
import random

#################################################
### I'm lame but couldn't find something like ###
### the SDL GetTickCount function..           ###
#################################################
class MyTime:
	def __init__(self):
		self.clock = pygame.time.Clock()
		self.time = 0
	def msec(self):
		self.time += self.clock.tick()
		return self.time
	def delay(self, ticks):
		self.time += self.clock.tick(ticks)

################################################
### Keeps data about how input level changes ###
### though one input period                  ###
### Checks successful input                  ###
################################################
class InputWatcher:
	def __init__(self, messages, points, stage):
		self.has_bps = False
		self.levels = []
		self.bps_begin = 0
		self.period_time = 0
		self.bits_begin = 0
		self.bits_end = 0
		self.bps = 0
		self.screen_bps = 0
		self.stage = stage
		self.messages = messages
		self.compared = False
		self.measure_started = False
		self.highlevel = False
		self.points = points
	def refresh(self, msec):
		# Clear input mode if input ended
		stat = self._status(msec)
		if stat is 2:
			self.has_bps = False
			self.levels = []
		elif stat is 1 and not self.compared:
			# Compare results
			self.compared = True
			if self.messages.comparefirst(self.get8levels(msec)) and self.screen_bps >= self.stage.min_bps:
				# Success!!
				global good
				good.play()
				self.points.add(self.screen_bps * self.messages.firstmul())
				self.messages.delmessage()
			else:
				# Fail..
				global bad
				bad.play()
				self.points.sub(150)
				pass
		elif stat is 0:
			self.compared = False
			
	def event(self, msec, press):
		# Handle Low-High change
		if press:
			# Measure bps time
			if not self.has_bps:
				self.measure_started = True
				self.bps_begin = msec
			# Register change
			elif msec < self.bits_end:
				self.highlevel = True
				self.levels.append((msec, 1))
		# Handle High-Low change
		else:
			# Bps time measurment ends
			if not self.has_bps and self.measure_started:
				self.measure_started = False
				self.period_time = msec - self.bps_begin
				self.bps = 1000.0 / self.period_time
				self.screen_bps = int(self.bps * 1000)
				self.bits_begin = msec
				self.bits_end = msec + 8 * self.period_time
				self.input_end = self.bits_end + 1000
				self.levels.append((msec, 0))
				self.has_bps = True
			# Register change
			elif msec < self.bits_end:
				self.highlevel = False
				self.levels.append((msec, 0))
	# Returns the levels at the given times, if available
	def getlevels(self, msec, times):
		if self.has_bps:
			def cut(t):
				return t < msec
			times = filter(cut, times)
			collect = []
			last = (-1, 0)
			for (t, level) in self.levels:
				while (len(times)>0) and (last[0] < times[0] <= t):
					collect.append(last[1])
					times.pop(0)
				last = (t, level)
			for i in range(0, len(times)):
				collect.append(last[1])
			return collect
		else:
			return []
	# Returns the 8 bit-levels, if available
	def get8levels(self, msec):
		return self.getlevels(msec, [self.bits_begin + self.period_time/2 + k*self.period_time for k in range(0, 8)])
	# Status
	def _status(self, msec):
		if self.has_bps:
			if msec < self.bits_end:
				return 0 # Input mode
			elif msec < self.input_end:
				return 1 # Safety time
			else:
				return 2 # Input done
				
		else:
			return 2 # Input ready
	# Compare 

######################################
### Some nice graphics input-meter ###
### (the digital waveform view     ###
######################################
class GrInputWatcher(InputWatcher):
	global screen
	def refresh(self, msec):
		InputWatcher.refresh(self, msec)
		stat = self._status(msec)
		if stat is 2:
			self.bps_noticed = False
		if stat is 0:
			if not self.bps_noticed:
				self.noticer.draw_bps(self.screen_bps, self.screen_bps >= self.stage.min_bps)
				self.bps_noticed = True
			if (self.highlevel) and (msec > self.last_powerbit + 500 - self.bps*100):
				self.last_powerbit = msec
				self.powerline.putelectron(self.player)
	def __init__(self, messages, stage, noticer, points, pos=(5,120), player=True):
		InputWatcher.__init__(self, messages, points, stage)
		self.player = player
		self.width = 150
		self.height = 60
		self.xpos = pos[0]
		self.ypos = pos[1]
		self.dot_color = (150, 255, 255)
		# Powerline
		self.powerline = stage.powerline
		self.last_powerbit = 0	
		# Noticer
		self.noticer = noticer
		self.bps_noticed = False
		# Setup the bitwatch background
		global loadimg
		self.bg = loadimg('watch.png').convert_alpha()
		self.dot = loadimg('dot.png').convert_alpha()
		bitsize = self.width/8.0
		places = [bitsize/2 + i*bitsize for i in range(0,8)]
		for p in places:
			pygame.draw.line(self.bg, (180, 230, 180), (31+p,5), (31+p,self.height-5))
	def paint(self, msec):
		screen.blit(self.bg, (self.xpos, self.ypos))
		if self.has_bps:
			def scale(x):
				return self.bits_begin + self.period_time * 8.0 * x / self.width
			times = map(scale, range(0, self.width))
			levels = self.getlevels(msec, times)
			lastlevel = 0
			lastpoint = -1
			lenlev = len(levels)
			for i in range(0, lenlev):
				if lastlevel == levels[0]:
					screen.set_at((self.xpos+30+i, self.ypos+10+ (1-levels[0]) * (self.height - 20)), self.dot_color)
				else:
					lastlevel = levels[0]
					x = self.xpos+30
					pygame.draw.line(screen, self.dot_color, (x+i,10+self.ypos), (x+i,self.height-10+self.ypos))
				lastpoint = levels.pop(0)
			if (lastpoint >= 0) and (msec < self.bits_end):
				screen.blit(self.dot, (self.xpos+24+lenlev, self.ypos+5+(1-lastpoint)*(self.height - 20)))

###########################################
### This represents a graphical         ###
### box with the binary data the player ###
### has to type, point multiplier, sign ###
### The sign has no real meaning yet    ###
###########################################
class Message:
	global binfont, signfont, button, screen, dt
	def __init__(self, bindata, sign='N', bgcolor=(200, 50, 50), mul=1, pos=[0,0]):
		self.bindata = bindata
		self.binstring = ''
		for x in bindata:
			self.binstring += str(x)
		self.sign = sign
		self.mul = mul
		self.bgcolor = bgcolor
		self.pos = pos
		self.__update()
		self.binwidth = 14
		self.glows = False
		self.doslide = False
		self.col_white =  (200, 200, 200)
	def _copy(self):
		return Message(self.bindata, self.sign, self.bgcolor, self.mul, self.pos)
	def __update(self):
		self.bgrect = ((self.pos[0]+10, self.pos[1]+10), (button.get_width()-20, button.get_height()-20))
		self.liney = self.pos[1]+50
		self.linex = self.pos[0]+20
	def setpos(self, pos):
		self.pos = pos
		self.__update()
	def slideto(self, to):
		self.slide_to = to
		self.doslide = True
	def slide(self, dx):
		self.pos[0] -= dx
		self.__update()
	def glow(self, state):
		self.glows = state
	def paint(self, msec):
		# Slide to position if necessary
		if self.doslide:
			if self.pos[0]-dt <= self.slide_to:
				self.pos[0] = self.slide_to
				self.doslide = False
				# Audio effect!!
				global csing
				csing.play()
			else:
				self.pos[0] -= dt
			self.__update()
		bgcolor = self.bgcolor
		col_white = self.col_white
		if self.glows:
			bgcolor = [self.bgcolor[i]+50.0*math.sin(msec/500.0) for i in (0,1,2)]
			def validate(c):
				if c<0:
					return 0
				elif c>255:
					return 255
				return c
			bgcolor = map(validate, bgcolor)
			col_white = [self.col_white[0]+50.0*math.cos(msec/500.0)]*3
		screen.fill(bgcolor, self.bgrect)
		screen.blit(button, self.pos)
		
		screen.blit(binfont.render(self.binstring, True, col_white), (self.linex, self.pos[1]+10))
		screen.blit(signfont.render(self.sign, True, col_white), (self.pos[0]+125, self.pos[1]+3))
		# Draw the digital level
		i = 0
		old_ycoord = self.liney+15
		for x in self.bindata:
			ycoord = self.liney+(1-x)*15
			xcoord = self.linex + i*self.binwidth
			# Vertical line
			if old_ycoord != ycoord:
				pygame.draw.line(screen, col_white, (xcoord, old_ycoord), (xcoord, ycoord))
			# Horizontal line
			pygame.draw.line(screen, col_white, (xcoord, ycoord), (xcoord + self.binwidth, ycoord))
			old_ycoord = ycoord
			i+=1
#################################
### A queue of messages,      ###
### handles sliding and stuff ###
#################################
class Messages:
	global button, dt
	def __init__(self, pos=(0,0)):
		self.messages = []
		self.maxsize = 4
		self.pos = pos
		self.spacing = button.get_width()+5
		self.slide = 0
	def clear(self):
		self.messages = []
		self.slide = 0
	def paint(self, msec):
		for m in self.messages:
			m.paint(msec)
	def notfull(self):
		return len(self.messages) < self.maxsize
	def empty(self):
		return len(self.messages) is 0
	def addmessage(self, msg):
		if self.notfull:
			msg.setpos([810, self.pos[1]])
			msg.slideto(self.pos[0] + len(self.messages)*self.spacing)
			if self.empty():
				msg.glow(True)
			self.messages.append(msg)
	def delmessage(self):
		if not self.empty():
			msg = self.messages.pop(0)
			if not self.empty():
				self.messages[0].glow(True)
				i=0
				for m in self.messages:
					m.slideto(self.pos[0] + i*self.spacing)
					i+=1
	def comparefirst(self, data):
		if self.empty():
			return False
		else:
			return data == self.messages[0].bindata
	def firstmul(self):
		if self.empty():
			return 0
		else:
			return self.messages[0].mul

################################
### This is the cloud effect ###
### gliding over the scene   ###
### It's nice I think        ###
################################
class Clouds:
	global cloud, screen
	def __init__(self, interval=10, on=True):
		self.interval = interval
		self.x = [-400, 0, 400]
		self.time = 0
		self.on = on
	def paint(self, msec):
		if not self.on:
			return
		# Refresh clouds
		if self.time < msec:
			self.time = msec + self.interval
			for i in [0,1,2]:
				self.x[i] += 1
				if self.x[i] == 0:
					self.x[i] = -400
				elif self.x[i] == 400:
					self.x[i] = 0
				elif self.x[i] == 800:
					self.x[i] = 400
		# Paint
		for y in [0, 300]:
			for i in [0,1,2]:
				screen.blit(cloud, (self.x[i], y))

###################################
### The wiring for transporting ###
### electrons                   ###
###################################
class Powerline:
	global dt, screen, electron1, electron2
	def __init__(self):	
		pass
	def init(self, lines): # Line is a 5-tuple: (0- 1| 2/ ,x1,y1,x2,y2)
		self.lines = lines
		self.fwdelectrons = []	# Electron is a 2-list: [position on actual line, actual line]
		self.backelectrons = []
		self.speed = 0.1
	def putelectron(self, forward=True):
		if forward:
			self.fwdelectrons.append([0, 0])
		else:
			self.backelectrons.append([0, -1])
	def paint(self):
		# Move electrons
		linelen = 0
		for (lst, dir) in [(self.fwdelectrons, 1), (self.backelectrons, 0)]:
			nextlist = []
			for e in lst:
				line = self.lines[e[1]]
				if line[0] is 0:
					# Y coords match
					linelen = math.fabs(line[1] - line[3])
				else:
					# X coords match, or dx = dy
					# Note: on non-straight lines, speed will seem faster
					linelen = math.fabs(line[2] - line[4])
					
				if e[0] + dt*self.speed > linelen:
					# Next line
					if dir:
						e[1] += 1
					else:
						e[1] -= 1
					if dir:
						if e[1] < len(self.lines):
							# This electron still has to go
							e[0] = 0
						else:
							lst.remove(e)
					else:
						if -e[1] > len(self.lines):
							lst.remove(e)
						else:
							e[0] = 0
				else:
					# Same line
					e[0] += dt*self.speed
		# Draw electrons
		for (lst, dir) in [(self.fwdelectrons, 1), (self.backelectrons, 0)]:
			for e in lst:
				line = self.lines[e[1]]
				
				dist = e[0]
				if dir:
					u1,v1,u2,v2 = 1,2,3,4
				else:
					u1,v1,u2,v2 = 3,4,1,2
				a,b = 0,0
				dist1 = dist2 = dist
				
				if line[u1] > line[u2]:
					dist1 = -dist1
				if line[v1] > line[v2]:
					dist2 = -dist2
				
				if line[0] is 0:
					a,b = line[u1] + dist1, line[v1]
				elif line[0] is 1:
					a,b = line[u1], line[v1] + dist2
				elif line[0] is 2:
					a,b = line[u1] + dist1, line[v1] + dist2
				electron = electron1
				if not dir:
					electron = electron2
				screen.blit(electron, (a-17,b-17))	

################################
### Score meter for a player ###
################################
class Points:
	global screen, pointfont, dt
	def __init__(self, pos):
		self.pos = pos
		self.points = 0
		self.points_add = 0
		self.points_sub = 0
		self.point_color = (210, 255, 210)
	def add(self, pts):
		self.points_add += pts
	def sub(self, pts):
		self.points_sub += pts
	def paint(self):
		# Add
		if self.points_add < 3*dt:
			self.points += self.points_add
			self.points_add = 0
		else:
			self.points += 3*dt
			self.points_add -= 3*dt
		# Sub
		if self.points_sub < 3*dt:
			self.points -= self.points_sub
			self.points_sub = 0
		else:
			self.points -= 3*dt
			self.points_sub -= 3*dt
		text = '%d' % self.points
		(fx, fy) = pointfont.size(text)
		screen.blit(pointfont.render(text, True, self.point_color), (self.pos[0]-fx, self.pos[1]))

############################################
### Makes electrons circle around        ###
### It is displayed if a player won      ###
############################################
class WonAnimator:
	global screen, msec, dt, electron1, electron2
	def __init__(self, mode = 0): # Mode: 0-both colors, 1-1P color, 2-2P color
		self.mode = mode
		self.r = 0.0
	def paint(self):
		delta = 2*math.pi / 24
		if self.r < 200:
			self.r += dt/10.0
		for i in range(0, 24):
			if self.mode == 0:
				if i % 2:
					electron = electron1
				else:
					electron = electron2
			elif self.mode == 1:
				electron = electron1
			elif self.mode == 2:
				electron = electron2
			dr = delta*i
			pos = (400+self.r*math.cos(msec/1000.0+dr), 300+self.r*math.sin(msec/1000.0+dr))
			screen.blit(electron, pos)
		
		
############################################
### Automated sliding in and out of text ###
### Make things easier                   ###
############################################
class Animator:
	global screen, msec
	def __init__(self, pos, config, surface, font, color, text, stay = False):
		self.pos = pos
		self.config = config
		self.surface = surface
		self.surface.fill((0,0,0))
		self.surface.blit(font.render(text, True, color), (0,0))
		self.hold1 = msec + self.config[0]
		self.full = self.hold1 + self.config[1]
		self.hold2 = self.full + self.config[2]
		self.off = self.hold2 + self.config[3]
		self.finished = False
		self.width = font.size(text)[0]
		self.stay = stay
	def paint(self):
		alpha = 255
		if msec <= self.hold1:
			alpha = 0
		elif msec <= self.full:
			alpha = 255.0*(self.config[1] - (self.full - msec))/self.config[1]
		# Not needed because of pre-init:
		# elif msec <= self.hold2:
		#	alpha = 255
		elif not self.stay:
			if msec <= self.off:
				alpha = 255.0*(self.off - msec)/self.config[3]
			else:
				self.finished = True
				return
		self.surface.set_alpha(alpha)
		screen.blit(self.surface, (self.pos[0] - self.width/2, self.pos[1]))

#####################################
### A collection of Animator data ###
### Other objects call the noticer###
### to render text                ###
#####################################
class Noticer:
	global screen, noticefont, msec
	def __init__(self, bps_pos, comment_pos, stage_pos, stage2_pos):
		self.animators = []
		self.stage_pos = stage_pos
		self.stage2_pos = stage2_pos
		self.bps_pos = bps_pos
		self.comment_pos = comment_pos
		# Ultimate working surfaces
		self.bps_surface = pygame.Surface((400, 90)).convert()
		self.bps_surface.set_colorkey((0,0,0))
		self.bps_cfg = (0.0, 1000.0, 600.0, 1000.0) # (hold, run-on, hold, run-off)
		# Bps comment
		self.comment_surface = pygame.Surface((500, 90)).convert()
		self.comment_surface.set_colorkey((0,0,0))
		self.comment_cfg = (0.0, 2000.0, 200.0, 900.0)
		# Stage begin, end
		self.stage_surface = pygame.Surface((700, 90)).convert()
		self.stage_surface.set_colorkey((0,0,0))
		self.stage_cfg = (0.0, 800.0, 1000.0, 800.0)
		# Game complete / over, Minimum bps
		self.stage2_surface = pygame.Surface((700, 90)).convert()
		self.stage2_surface.set_colorkey((0,0,0))
		self.stage2_cfg = (500.0, 300.0, 1000.0, 300.0)
	
	def bps_grade(self, bps, notslow):
		if not notslow:
			return ((200, 220, 200), 'Too Slow!')
		if bps < 1000:
			return ((30, 110, 30), 'Sleepwalking?')
		elif bps < 2000:
			return ((50, 190, 50), 'No Rush')
		elif bps < 3000:
			return ((70, 220, 70), 'Moderate')
		elif bps < 4000:
			return ((190, 30, 30), 'Speedy!')
		elif bps < 5000:
			return ((230, 50, 50), 'Extreme!!')
		else:
			return ((255, 255, 255), 'Hot Wires!!!')
	def draw_bps(self, bps, notslow):
		self.animators.append(Animator(self.bps_pos, self.bps_cfg, self.bps_surface, noticefont, (255,255,255), '%dbps'%bps))
		
		(color, comment) = self.bps_grade(bps, notslow)
		self.animators.append(Animator(self.comment_pos, self.comment_cfg, self.comment_surface, noticefont, color, comment))
	
	def draw_stageinfo(self, text, stay=False):
		self.animators.append(Animator(self.stage_pos, self.stage_cfg, self.stage_surface, noticefont, (155,255,155), text, stay))
	
	def draw_stageinfo2(self, text, stay=False):
		self.animators.append(Animator(self.stage2_pos, self.stage2_cfg, self.stage2_surface, noticefont, (255,255,55), text, stay))
		
	def paint(self):
		for a in self.animators:
			a.paint()
		for a in self.animators:
			if a.finished:
				self.animators.remove(a)

#############
### Maybe a background class.. but no real need for it yet
#############
#class Background:
#	def __init__(self, clouds=False, interval=60):
#		clouds = Clouds()
#	def paint(self):

###################################
### Handles loading stages,     ###
### monitoring fail or success, ###
### restarting stages etc.      ###
### Contains the Powerline      ###
###################################
class Stage:
	global dt, msec, modem, screen, game, notify
	def __init__(self, mode, messages, noticer): # Mode: (0,1,2:1pstage,1psurv,2psurv, file)
		self.stagepath = 'stages'
		self.noticer = noticer
		self.messages = messages
		self.mode = mode
		self.min_bps = 0
		self.random = random.Random()
		self.powerline = Powerline()
		if mode[0] == 0:
			self.fn = mode[1]
			self.load(self.fn)
			self.noticer.draw_stageinfo(self.title)
			self.noticer.draw_stageinfo2('Minimum %d bps' % self.min_bps)
		else:
			self.load(mode[1])
			self._genmessagelist()
			self.min_bps = 500
			self.noticer.draw_stageinfo('Receiving %d Messages' % len(self.messagelist))
			self.noticer.draw_stageinfo2('Minimum %d bps' % self.min_bps)
		notify.play()
		self.randtype = 0
		class DummyPaint:
			def __init__(self):
				pass
			def paint(self):
				pass
		self.wonanim = DummyPaint()
		self.gameover = (False, False, 0) # (Gameover, Won, Restart time)
		self.loadnext_at = (False, 0)
	def load(self, fn):
		self.fn = fn
		self.gameover = (False, True)
		self.loadnext_at = (False, 0)
		self.randtype = 0
		self.messagelist = []
		# Open stage file
		f = file(os.path.join(self.stagepath, fn), 'r')
		# Line 0: stage title
		self.title = strip(f.readline())
		# Line 1: next stage file
		self.nextstage = strip(f.readline())
		# Line 2: background
		bg = strip(f.readline())
		self.bg = loadimg(bg).convert()
		# Line 3: minimum bps
		self.min_bps = int(f.readline())
		# Line 4: modem1 position
		def toint(x):
			return int(x)
		self.modem1_pos =  map(toint, split(strip(f.readline()),','))
		# Line 5: modem2 position
		self.modem2_pos =  map(toint, split(strip(f.readline()),','))
		self.bg.blit(modem, self.modem1_pos)
		self.bg.blit(modem, self.modem2_pos)

		# Line 6: powerline data in format: hv,x1,y1,x2,y2:...
		def commacut(x):
			return map(toint, split(x,','))
		self.powerline.init(map(commacut, split(strip(f.readline()),':')))
		# From line 7: incoming message data
		# format: <msec delay after last msg>,<multiplier>,<sign>,<binstring>
		line = f.readline()
		while line != '':
			line = strip(line)
			(delay, mul, sign, binstring) = split(line, ',')
			delay, mul = int(delay), int(mul)
			# (self, bindata, sign='N', bgcolor=(200, 50, 50), mul=1, pos=[0,0])
			def bindata(bs):
				data = []
				for c in bs:
					if c == '1':
						data.append(1)
					else:
						data.append(0)
				return data
			self.messagelist.append([delay, Message(bindata(binstring), sign, self._getcolor(mul), mul)])
			line = f.readline()
		f.close()
		# Load complete
		# Notice noticer
	# Generate a messagelist for Survival play
	def _genmessagelist(self):
		if self.mode[0] < 2:
			self.messagelist = []
		else:
			self.messagelist = ([],[])
		for j in range(0, self.random.randint(7, 15)):
			rmul = self.random.randint(1,100)
			if rmul < 60:
				mul = 1
			elif rmul < 80:
				mul = 2
			elif rmul < 90:
				mul = 3
			elif rmul < 95:
				mul = 4
			else:
				mul = 5
			data = [self.random.randint(0,1) for k in range(0,8)]
			while data == [0,0,0,0,0,0,0,0]:
				data = [self.random.randint(0,1) for k in range(0,8)]
			delay = self.random.randint(2000, 15000)
			m = Message(data, self._getsign(mul), self._getcolor(mul), mul)
			if self.mode[0] < 2:
				self.messagelist.append([delay, m])
			else:
				self.messagelist[0].append([delay, m])
				self.messagelist[1].append([delay, m._copy()])
	# No better idea for sign yet:
	def _getsign(self, mul):
		if mul is 1:
			return 'S'
		elif mul is 2:
			return 'D'
		elif mul is 3:
			return 'T'
		elif mul is 4:
			return 'Q'
		else:
			return 'X'
	def _getcolor(self, mul):
		if mul is 1:
			return (100,180,100)
		elif mul is 2:
			return (100,120,180)
		elif mul is 3:
			return (190,100,100)
		elif mul is 4:
			return (190,120,190)
		else:
			return (10,10,10)
	def refresh(self):
		global game
		# Restart?
		if self.gameover[0] and not self.gameover[1]:
			if msec >= self.gameover[2]:
				if self.mode[0] == 0:
					# Reload
					self.load(self.fn)
					self.noticer.draw_stageinfo(self.title)
					self.noticer.draw_stageinfo2('Minimum %d bps' % self.min_bps)
					self.messages.clear()
					return
				else:
					# Quit to menu
					game = False
					return
			
		# Stage loading?
		if (self.loadnext_at[0]) and (msec > self.loadnext_at[1]):
			self.load(self.nextstage)
			self.noticer.draw_stageinfo(self.title)
			self.noticer.draw_stageinfo2('Minimum %d bps' % self.min_bps)
			return
		
		# No ambient generation at stage end or 2Player mode
		if self.mode[0] < 2 and not self.loadnext_at[0]:
			# Ambient electron generation
			if self.randtype is 1:
				# Overall sequence length
				self.randtime -= dt
				if self.randtime <= 0:
					self.randtype = 0
				# Wait for next generation time
				self.gen_acttime -= dt
				if self.gen_acttime <= 0:
					# Generate ambient electron
					self.gen_acttime = self.gen_time
					self.powerline.putelectron(False)
			else:
				# Not generating
				if self.random.randint(1,1000) < dt:
					self.randtime = self.random.randint(200, 1000)
					self.gen_time = self.random.randint(30,500)
					self.gen_acttime = self.gen_time
					self.randtype = 1
		
		# Check for incoming messages		
		if self.mode[0] < 2:
			if len(self.messagelist):
				self.messagelist[0][0] -= dt
				# If player has no message, instantly send him
				if (self.messagelist[0][0] <= 0) or (self.messages.empty()):
					# It arrived
					m = self.messagelist.pop(0)[1]
					if self.messages.notfull():
						self.messages.addmessage(m)	
					else:
						# Stage failed, sign and restart
						if not self.gameover[0]:
							self.messages.addmessage(m)
							if self.mode[0] == 0:
								self.gameover = (True, False, msec + 3000)
								self.noticer.draw_stageinfo('Buffer Overflow!')
								self.noticer.draw_stageinfo2('Stage Failed')
							else:
								self.gameover = (True, False, msec + 10000)
								self.noticer.draw_stageinfo('Reached Your Limit!', True)
								self.noticer.draw_stageinfo2('Well Done', True)
			else:
				# No more messages waiting, any in game yet?
				if self.messages.empty():
					if self.mode[0] == 0:
						# Stage completed!
						# Notice noticer
						# Load next stage
						if self.nextstage != 'none':
							if not self.loadnext_at[0]:
								self.loadnext_at = (True, msec + 5000)
								self.noticer.draw_stageinfo('Stage Complete!')
								notify.play()
						else:
							# All stages complete, congratulations!!
							if not self.gameover[0]:
								self.gameover = (True, True, msec + 5000)
								self.wonanim = WonAnimator()
								self.noticer.draw_stageinfo('All Done!', True)
								self.noticer.draw_stageinfo2('You Are Great!', True)
								notify.play()
					else:
						# Advance to next stage
						self.min_bps += 500
						self._genmessagelist()
						self.noticer.draw_stageinfo('Receiving %d Messages' % len(self.messagelist))
						self.noticer.draw_stageinfo2('Minimum %d bps' % self.min_bps)
						notify.play()
		else:
			lost = [False, False]
			actgameover = self.gameover[0]
			for i in range(0,2):
				if len(self.messagelist[i]):
					self.messagelist[i][0][0] -= dt
						
					if self.messagelist[i][0][0] <= 0 or self.messages[i].empty():
						# Arrived
						m = self.messagelist[i].pop(0)[1]
											
						if self.messages[i].notfull():
							self.messages[i].addmessage(m)
						else:
							lost[i] = True
							if not self.gameover[0]:
								self.gameover = (True, False, msec + 25000)
								self.messages[i].addmessage(m)
			if self.messages[0].empty() and self.messages[1].empty():
				# Advance to next stage
				self.min_bps += 500
				self._genmessagelist()
				self.noticer.draw_stageinfo('Receiving %d Messages' % len(self.messagelist))
				self.noticer.draw_stageinfo2('Minimum %d bps' % self.min_bps)
				notify.play()
			if not actgameover and (lost[0] or lost[1]):
				if lost[0] and lost[1]:
					self.gameover = (True, False, msec + 5000)
					self.noticer.draw_stageinfo('Buffers Overflew!')
					self.noticer.draw_stageinfo2('Players Lost')
				elif lost[0]:
					self.wonanim = WonAnimator(2)
					self.noticer.draw_stageinfo('1P Buffer Overflow!')
					self.noticer.draw_stageinfo2('2P Won!', True)
				else:
					self.wonanim = WonAnimator(1)
					self.noticer.draw_stageinfo('2P Buffer Overflow!')
					self.noticer.draw_stageinfo2('1P Won!', True)
	
	def paint(self):
		if self.gameover[0]:
			if self.mode[0] == 0:
				if self.gameover[1]:
					self.wonanim.paint()
			else:
				self.wonanim.paint()
		self.powerline.paint()

##################
## Main program ##
##################

# Grab config options
# params: m - no music, s - no sound or music, w - windowed, c - no clouds, v - verbose
cfg_sound = 's' not in sys.argv
cfg_music = ('s' not in sys.argv) and ('m' not in sys.argv)
cfg_fullscreen = 'w' not in sys.argv
cfg_clouds = 'c' not in sys.argv
cfg_verbose = 'v' in sys.argv

###########################
### Enter graphics mode ###
###########################			
pygame.init()
# Fullscreen?
flags = 0
if cfg_fullscreen:
	flags = FULLSCREEN
screen = pygame.display.set_mode((800, 600), flags)
pygame.mouse.set_visible(0)
pygame.display.set_caption('Serial')

#############################################
### Load global graphics (ugly globals..) ###
#############################################
def loadimg(img):
	path = os.path.join('images', img)
	if cfg_verbose:
		print 'Loadimg', path
	return pygame.image.load(path)
	
mainbg    = loadimg('mainbg.png').convert()
modem     = loadimg('modem.png').convert()
button    = loadimg('gomb2.png').convert_alpha()
electron1 = loadimg('electron1.png').convert_alpha()
electron2 = loadimg('electron2.png').convert_alpha()
if cfg_clouds:
	cloud     = loadimg('cloud.png').convert_alpha()

#############################################
### Load some fonts.. May find some nicer ###
#############################################
binfont = pygame.font.Font('FreeSans.ttf', 25)
pointfont = pygame.font.Font('FreeSans.ttf', 35)
signfont = pygame.font.Font('FreeSans.ttf', 50)
noticefont = pygame.font.Font('FreeSans.ttf', 45)
binfont.set_italic(True)
noticefont.set_italic(True)
signfont.set_italic(True)

###########################
### Some audio, finally ###
###########################
def loadsnd(snd):
	if cfg_sound:
		path = os.path.join('audio', snd)
		if cfg_verbose:
			print 'Loadsnd', path
		return pygame.mixer.Sound(path)
	else:
		class Dummy:
			def __init__(self):
				pass
			def play(self):
				pass
		return Dummy()

csing  = loadsnd('KDE_Beep_ClockChime.wav')
coin   = loadsnd('KDE_Dialog_Appear.wav')
coin2  = loadsnd('KDE_Dialog_Disappear.wav')
notify = loadsnd('KDE_Notify.wav')
good   = loadsnd('KDE_Window_DeIconify.wav')
bad    = loadsnd('KDE_Window_Iconify.wav')

if cfg_music and cfg_sound:
	# Load music list and play
	f = file(os.path.join('audio', 'music'), 'r')
	musiclist = f.readlines()
	f.close()
	def prefix(s):
		return os.path.join('audio',s)
	musiclist = map(prefix, map(strip, musiclist))
	musicrandom = random.Random()
	pygame.mixer.music.set_endevent(USEREVENT)
	pygame.mixer.music.load(musicrandom.choice(musiclist))
	pygame.mixer.music.play()

##### Global ########
time = MyTime()
msec = time.msec()
key_1p = K_w
key_2p = K_o
last_msec = time.msec()
game = False
#################
### Main loop ###
#################
mainexit = (False, 0)

while True:
	#################
	### Main menu ###
	#################
	start_1p = 0
	start_2p = 0
	startgame = (False, 0) # Start?, time back to start
	
	timeanim = None
	timesurf = pygame.Surface((150, 70)).convert()
	timesurf.set_colorkey((0,0,0))
			
	
	menu = True
	while menu:
		# Exit?
		if mainexit[0] and msec > mainexit[1]:
			sys.exit()

		# What to print depending on menu state
		def text():
			if start_2p > 0:
				return ('2P Survival Mode', '')
			elif start_1p == 0:
				return ('W: 1P Start', 'O: 2P Start')
			elif start_1p == 1:
				return ('1P Stages Mode', 'W: 1P Survival')
			elif start_1p > 1:
				return ('1P Survival Mode', '')
		# Time
		msec = time.msec()
		dt = msec - last_msec
	
		# Input handling	
		for event in pygame.event.get():
			if (event.type == USEREVENT):
				pygame.mixer.music.load(musicrandom.choice(musiclist))
				pygame.mixer.music.play()
			if (event.type == QUIT) or (event.type is KEYDOWN and event.key == K_ESCAPE):
				if not mainexit[0]:
					mainexit = (True, msec + 1500)
					# Time to say good bye :d
					timeanim = Animator((400, 290), (0,150,150,1000), timesurf, signfont, (250, 255, 150), 'Bye!')
					if cfg_sound and cfg_music:
						pygame.mixer.music.fadeout(1500)
			elif (event.type == KEYDOWN) and (event.key == key_1p):
				if not start_2p and start_1p < 2:
					start_1p += 1
					if not startgame[0]:
						startgame = (True, msec + 3000)
					coin.play()
			elif (event.type == KEYDOWN) and (event.key == key_2p):
				if not start_2p:
					start_2p = 1
					if not startgame[0]:
						startgame = (True, msec + 3000)
					coin2.play()
		# Time to start game?
		if startgame[0]:
			if msec > startgame[1]:
				menu = False
			else:
				# Flash secs back
				if timeanim == None or timeanim.finished:
					timeanim = Animator((400, 290), (0,100,200,600), timesurf, signfont, (100, 255, 150), '%d'%((startgame[1]-msec)/1000))
	
		# Drawing on-screen
		screen.blit(mainbg, (0,0))
		
		text = text()
		size = map(binfont.size, text)
		# Some color boost
		def validate(c):
				if c<0:
					return 0
				elif c>255:
					return 255
				return c
		def colorize(col, phase):
			return [validate(col[i]+50.0*math.sin(phase+msec/250.0)) for i in (0,1,2)]
		screen.blit(binfont.render(text[0], True, colorize((245, 111, 0), 0)), (400-size[0][0]/2, 200))
		screen.blit(binfont.render(text[1], True, colorize((255, 151, 0), 1.62)), (400-size[1][0]/2, 250))
		if timeanim:
			timeanim.paint()
		
		pygame.display.flip()
		last_msec = msec
		time.delay(100)
	
	noticefont.set_italic(False)
	signfont.set_italic(False)
	#########################
	### Load game objects ###
	#########################
	
	# clouds?
	clouds = Clouds(25, cfg_clouds)
	
	#2p game?
	if start_2p > 0:
		mode = 2
	elif start_1p == 1:
		mode = 0
	elif start_1p > 1:
		mode = 1

	if mode == 2:
		mm = (Messages((10, 500)), Messages((10, 10)))
		notice = (Noticer((400, 330), (400, 380), (400, 240), (400,280)), Noticer((400, 150), (400, 190), (400, 240), (400,280)))
		stage = Stage((2,'survival'), mm, notice[0])
		points = (Points((750, 450)), Points((750, 90)))
		iw =(GrInputWatcher(mm[0], stage, notice[0], points[0], (0, 380), True), GrInputWatcher(mm[1], stage, notice[1], points[1], (0, 160), False))

	else:
		mm = Messages((10, 500))
		notice = Noticer((400, 330), (400, 380), (400, 240), (400,280))
		if mode == 0:
			stage = 'stage1'
		else:
			stage = 'survival'
		stage = Stage((mode,'stage1'), mm, notice)
		points = Points((300, 10))
		iw = GrInputWatcher(mm, stage, notice, points, (0, 280))
		
	
	#################
	### Real Game ###
	#################
	game = True
	while game:
		# Time
		msec = time.msec()
		dt = msec - last_msec
			
		# Some game logic
		if mode < 2:
			iw.refresh(msec)
		else:
			iw[0].refresh(msec)
			iw[1].refresh(msec)
		stage.refresh()
		
		# Input handling
		for event in pygame.event.get():
			if (event.type == USEREVENT):
				pygame.mixer.music.load(musicrandom.choice(musiclist))
				pygame.mixer.music.play()
			if (event.type == QUIT) or (event.type is KEYDOWN and event.key == K_ESCAPE):
				game = False
			elif (event.type == KEYDOWN) and (event.key == key_1p):
				if mode < 2:
					iw.event(msec, True)
				else:
					iw[0].event(msec, True)
			elif (event.type == KEYUP) and (event.key == key_1p):
				if mode < 2:
					iw.event(msec, False)
				else:
					iw[0].event(msec, False)
			elif mode == 2:
				if (event.type == KEYDOWN) and (event.key == key_2p):
					iw[1].event(msec, True)
				elif (event.type == KEYUP) and (event.key == key_2p):
					iw[1].event(msec, False)
		# Drawing on-screen
		screen.blit(stage.bg, (0,0))
		clouds.paint(msec)
		if mode < 2:
			iw.paint(msec)
			mm.paint(msec)
			points.paint()
			notice.paint()
		else:
			iw[0].paint(msec)
			iw[1].paint(msec)
			mm[0].paint(msec)
			mm[1].paint(msec)
			points[0].paint()
			points[1].paint()
			notice[0].paint()
			notice[1].paint()
				
		stage.paint()
		
		pygame.display.flip()
		last_msec = msec
		time.delay(100)
