#/usr/bin/env python

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

# Copyright (C) 2003 by Frithjof Engel <frithjof_engel@users.sourceforge.net>

# TODO:
# - Highscore file
#   * Score calculated by number of chars per time
#    |--> Selectable speed ?
# - Nice animations
# - Sound effects/music

import pygame, string, sys
from pygame.locals import *
from random import Random

if not pygame.font: print 'Warning, fonts disabled'

# The display resolution
DISPLAYRES_X = 800
DISPLAYRES_Y = 600
# The fontsize used for the chars to type
NORMALFONTSIZE = 32

COLOR_BLACK = (0, 0, 0)
COLOR_RED = (255, 0, 0)
COLOR_LOWERCASE = COLOR_RED
COLOR_UPPERCASE = (255, 128, 0)
COLOR_NUMBERS = (255, 255, 0)

# Determines at which stages (in levels) a bigger charsets gets loaded
CHARSET_CHANGES = (5, 10)

class ChantGame:
    def  __init__(self):
        self.__font = pygame.font.Font(None, NORMALFONTSIZE)
        self.__screen = pygame.display.set_mode((DISPLAYRES_X, DISPLAYRES_Y))
        self.__level = 0
        self.__dirtyRects = []
        self.__rightChars = 0
        self.__resetKeys()
        self.__charWidth = self.__maxCharWidth(self.__font)

    def rightChars(self):
        return self.__rightChars

    def __shiftPressed(self):
        return (pygame.key.get_mods() & KMOD_LSHIFT) or (pygame.key.get_mods()
                                                         & KMOD_RSHIFT)
    def __charIsUppercase(self, c):
        return c in string.ascii_uppercase
    def __charIsLowercase(self, c):
        return c in string.ascii_lowercase

    def __charIsValidKey(self, c):
        if len(c) != 1:
            return 0
        return c in string.ascii_letters or c in string.digits

    def __maxCharWidth(self, font):
        w = 0
        for i in string.ascii_letters + string.digits:
            tmp = font.size(i)[0]
            if tmp > w:
                w = tmp
        return w

    def __randomChar(self):
        ret = 'I'
        # Unfortunately the chars below look the same in the selected font,
        # so we can't use them.
        charset = []
        if self.__level < CHARSET_CHANGES[0]:
            charset = string.ascii_lowercase
        elif self.__level < CHARSET_CHANGES[1]:
            charset = string.ascii_uppercase + string.ascii_lowercase
        else:
            charset = string.ascii_uppercase + string.ascii_lowercase
            charset += string.digits

        while ret == 'I' or ret == 'l':
            r = Random()
            ret = charset[r.randint(0, len(charset)-1)]

        return ret

    def __randomRow(self):
        row = 0
        while row == 0:
            r = Random()
            row = r.randint(1, DISPLAYRES_X-self.__charWidth)
            for ri in self.__row:
                if row == ri:
                    row = 0
                    break
                elif row < ri and row + self.__charWidth >= ri:
                    row = 0
                    break
                elif ri < row and ri + self.__charWidth >= row:
                    row = 0
                    break

        return row

    def __charColor(self, c):
        if self.__charIsLowercase(c):
            return COLOR_LOWERCASE
        elif self.__charIsUppercase(c):
            return COLOR_UPPERCASE

        return COLOR_NUMBERS

    def __drawChar(self, index, c, pos, color):
        self.__screen.blit(pygame.Surface(self.__oldsize[index]),
                         (self.__row[index], self.__pos_counter[index]))
        text = self.__font.render(c, 1, color)
        self.__oldsize[index] = text.get_rect().size
        screenpos = (self.__row[index], pos)
        self.__screen.blit(text, screenpos)
        self.__dirtyRects.append(pygame.Rect(screenpos, self.__oldsize[index]))

    def loop(self):
        for i in range(0, len(self.__pos_counter)):
            self.__pos_counter[i] += 1
            if self.__pos_counter[i] >= DISPLAYRES_Y:
                self.__gameOver()

        if self.__rightChars != 0 and self.__rightChars % 10 == 0:
            if self.__level * 10 < self.__rightChars:
                self.__level += 1
                if self.__level in CHARSET_CHANGES:
                    self.__resetKeys()
                    self.__nextStage()
                else:
                    self.__addKey()

        for i in range(0, len(self.__key)):
            self.__drawChar(i, self.__key[i],
                            self.__pos_counter[i],
                            self.__charColor(self.__key[i]))

        pygame.display.update(self.__dirtyRects)
        self.__dirtyRects = []
        pygame.time.wait(5)

    def __newChar(self, index):
        # First remove the old key from screen
        pos = (self.__row[index], self.__pos_counter[index])
        self.__screen.blit(pygame.Surface(self.__oldsize[index]), pos)
        pygame.display.update(pos, self.__oldsize[index])

        rc = self.__key[index]
        while string.lower(rc) == string.lower(self.__key[index]):
            rc = self.__randomChar()

        self.__key[index] = rc
        self.__pos_counter[index] = 0
        self.__row[index] = self.__randomRow()
        self.__rightChars += 1

    def __addKey(self):
        self.__key.append(self.__randomChar())
        self.__pos_counter.append(0)
        self.__oldsize.append((0,0))
        self.__row.append(self.__randomRow())
        self.__newChar(len(self.__key)-1)

    def __resetKeys(self):
        self.__key = [self.__randomChar()]
        self.__pos_counter = [0]
        self.__oldsize = [(0,0)]
        self.__row = [DISPLAYRES_X/2]

    def keyhit(self, key):
        keyname = pygame.key.name(key)
        if not self.__charIsValidKey(keyname):
            return
        for i in range(len(self.__key)-1, -1, -1):
            if self.__key[i] == keyname:
                # keyname will always be a lower char,
                # if shift is pressed user was wrong
                if not self.__shiftPressed():
                    self.__newChar(i)
                    break
            elif string.lower(self.__key[i]) == string.lower(keyname):
                # keyname will always be a lower char,
                # if shift is pressed user was right
                if self.__shiftPressed():
                    self.__newChar(i)
                    break

    def __nextStage(self):
        self.__screen.fill(COLOR_BLACK)
        font = pygame.font.Font(None, 100)
        text = font.render('Next Stage...', 1, (0, 0, 255))
        x = DISPLAYRES_X/2-text.get_rect().width/2
        y = DISPLAYRES_Y/2-text.get_rect().height/2
        self.__screen.blit(text, (x, y))
        pygame.display.flip()
        pygame.time.wait(1500)
        self.__screen.fill(COLOR_BLACK)
        pygame.display.flip()

    def __gameOver(self):
        self.__screen.fill(COLOR_BLACK)
        font = pygame.font.Font(None, 100)
        text = font.render('Game Over!', 1, (0, 255, 0))
        x = DISPLAYRES_X/2-text.get_rect().width/2
        y = DISPLAYRES_Y/2-text.get_rect().height/2
        self.__screen.blit(text, (x, y))
        pygame.display.flip()
        pygame.time.wait(2000)
        self.__leaveGame()

    def __leaveGame(self):
        self.__screen.fill(COLOR_BLACK)
        font = pygame.font.Font(None, 64)
        txt = 'You did '+str(self.__rightChars)+' Charakters!'
        text = font.render(txt, 1, (0, 0, 255))
        x = DISPLAYRES_X/2-text.get_rect().width/2
        y = DISPLAYRES_Y/2-text.get_rect().height/2
        self.__screen.blit(text, (x, y))
        pygame.display.flip()
        pygame.time.wait(500)
        pygame.event.clear()
        get = pygame.event.get
        while 1:
            if get(KEYDOWN) or get(MOUSEBUTTONDOWN):
                sys.exit(0)

def main():
    pygame.display.init()
    pygame.font.init()
    pygame.display.set_caption(
        'Chant - Type the charakters before they reach the bottom!')
    game = ChantGame()

    def leaveGame():
        print 'You did '+str(game.rightChars())+' Charakters!'

    while 1:
        for event in pygame.event.get():
            if event.type is QUIT:
                leaveGame()
                return
            elif event.type is KEYDOWN:
                if pygame.key.name(event.key) == 'escape':
                    leaveGame()
                    return
                game.keyhit(event.key)
        game.loop()

if __name__ == '__main__': main()

