#!/usr/bin/python
# -*- python -*-

# Ympyris - a falling blocks type game with circular well
# Copyright (C) 2002-2005  Juha-Matti Tapio <jmtapio@users.sourceforge.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.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

# Soli Deo Gloria - To God alone be glory

from __future__ import generators

import whrandom, math, sys, ConfigParser, os

if sys.version_info < (2, 1):
    print 'Ympyris requires Python 2.1 or newer.'
    print 'Detected '+sys.version
    sys.exit(-1)

import pygame, pygame.display, pygame.draw, pygame.event, \
       pygame.time, pygame.font, pygame.mouse
from pygame.locals import *
import getpass

cvs_id = """$Id: ympyris,v 1.14 2005/01/02 19:57:45 jmtapio Exp $"""


class Block:
    """Tetris block with four pieces in various shapes"""

    # each block type lists the coordinates of the four pieces for
    # each of the four angles the block can be in
    types = [
        [
        ((1,0), (1,1), (1,2), (1,3)), # xxxx
        ((0,0), (1,0), (2,0), (3,0)),
        ((1,0), (1,1), (1,2), (1,3)),
        ((0,0), (1,0), (2,0), (3,0))
        ],

        [
        ((0,0), (1,0), (2,0), (0,1)), # xxx
        ((0,0), (1,0), (1,1), (1,2)), # x
        ((2,0), (0,1), (1,1), (2,1)),
        ((0,0), (1,2), (0,1), (0,2))
        ],

        [
        ((0,0), (1,0), (2,0), (2,1)), # x
        ((1,0), (1,1), (1,2), (0,2)), # xxx
        ((0,0), (0,1), (1,1), (2,1)),
        ((0,0), (1,0), (0,1), (0,2))
        ],

        [
        ((1,0), (2,0), (0,1), (1,1)), #  xx
        ((0,0), (0,1), (1,1), (1,2)), # xx
        ((1,0), (2,0), (0,1), (1,1)),
        ((0,0), (0,1), (1,1), (1,2))
        ],
        [
        ((0,0), (1,0), (1,1), (2,1)), # xx
        ((1,0), (1,1), (0,1), (0,2)), #  xx
        ((1,1), (2,1), (0,0), (1,0)),
        ((1,0), (1,1), (0,1), (0,2))
        ],

        [
        ((1,0), (2,0), (1,1), (2,1)), # xx
        ((1,0), (2,0), (1,1), (2,1)), # xx
        ((1,0), (2,0), (1,1), (2,1)),
        ((1,0), (2,0), (1,1), (2,1))
        ],

        [
        ((0,0), (1,0), (2,0), (1,1)), # xxx
        ((1,0), (1,1), (1,2), (0,1)), #  x
        ((0,1), (1,1), (2,1), (1,0)),
        ((0,0), (0,1), (0,2), (1,1))
        ]
        ]

    strong_colors = [
        (20, 40, 230),
        (10, 230, 240),
        (230, 230, 230),
        (250, 50, 210),
        (245, 230, 10),
        (240, 8, 8),
        (20, 230, 20)
        ]

    soft_colors = [
        (70, 80, 200),
        (30, 210, 220),
        (210, 210, 230),
        (210, 70, 180),
        (215, 200, 40),
        (230, 58, 38),
        (50, 190, 50)
        ]

    def __init__(self):
        self.blocktype = whrandom.choice(self.types)
        i = whrandom.choice(range(len(self.types)))
        self.blocktype = self.types[i]
        self.strong_color = self.strong_colors[i]
        self.soft_color = self.soft_colors[i]
        self.angle = 0
        self.blocks = self.blocktype[self.angle]

    def rotate_cw(self):
        """Rotate counter-clockwise"""
        self.angle += 1
        self.angle %= 4
        self.blocks = self.blocktype[self.angle]

    def rotate_ccw(self):
        """Rotate clockwise"""
        self.angle -= 1
        self.angle %= 4
        self.blocks = self.blocktype[self.angle]


class Ympyris:
    """Single game of Ympyris"""
    
    def __init__(self, display):
        self.display = display

        print 'Loading...',
        sys.stdout.flush()

        self.config = ConfigParser.ConfigParser()

        if sys.platform == 'win32':
            self.cfgfile = 'ympyris.cfg'
        else:
            self.cfgfile = os.path.expanduser('~/.ympyrisrc')

        self.config.read(self.cfgfile)

        if not self.config.has_section('ympyris'):
            self.config.add_section('ympyris')

        if not self.config.has_section('keys'):
            self.config.add_section('keys')
        self.init_keymap()

        self.font = pygame.font.Font(None, 20)
        self.bigfont = pygame.font.Font(None, 50)
        self.helpfont = pygame.font.Font(None, 15)

        self.helplabel = self.make_helplabel()
       
        self.screenrect = pygame.Rect((0,0,800,600))
        self.scorerect = pygame.Rect((500,5,300,25))
        self.wellrect = pygame.Rect((500-260,300-260,520,520))

        # Precalculate points for block polygons
        self.prepoints = []
        self.prebboxes = []
        for y in range(25):
            self.prepoints.append([])
            self.prebboxes.append([])
            for angle in range(16):
                self.prepoints[-1].append(self.calc_angles2polygon(angle, y))
                self.prebboxes[-1].append(self.calc_cell_bbox(angle, y))

        # Precalculate stuff for the coordinate circle
        self.coords_liners = [pygame.draw.line]*16
        self.coords_pre = []
        
        for angle in range(16):
            screen_y = 18.0 * (16) * math.sin(math.pi/8*(angle+.5))+300
            screen_x = 18.0 * (16) * math.cos(math.pi/8*(angle+.5))+500

            self.coords_pre.append((
                self.display,   # display
                (100,100,100),  # color
                (500, 300),     # center
                (screen_x, screen_y), # endpoint
                1               # line width
                ))

        # Alpha surface for topten
        #self.shader_s = pygame.Surface((240, 280))
        #self.shader_s.set_alpha(128)

        self.fps = pygame.time.Clock()

        # The style to draw static blocks
        if self.config.has_option('ympyris', 'drawmode'):
            self.drawmode = self.config.getint('ympyris', 'drawmode')
        else:
            self.drawmode = 1
            self.config.set('ympyris', 'drawmode', '1')

        self.drawers = (self.draw_blocks_std, self.draw_blocks_solid)
        self.draw_blocks = self.drawers[self.drawmode]


        if self.config.has_option('ympyris', 'autopause'):
            self.autopause = self.config.getboolean('ympyris', 'autopause')
        else:
            self.autopause = 0
            self.config.set('ympyris', 'autopause', '1')
            
        if sys.platform == 'win32':
            self.scorefile = 'ympyris.scores'
        else:
            self.scorefile = os.path.expanduser('~/.ympyris.scores')
            
        self.reset()

        print 'Done'
        sys.stdout.flush()
        return


    def make_helplabel(self):
        """Compose the bitmap for the key help"""

        return self.helpfont.render(
            ''.join(('KEYS  %s/%s: rotate the well  ',
                     '%s/%s: rotate the block  %s: descent  ',
                     'esc: quit  %s: toggle fullscreen  %s: pause  ',
                     '%s: drawing mode  %s: speedup'
                     )) % tuple([s.capitalize() for s in
                                 map(pygame.key.name,
                                     (self.keymap['well_rotate_ccw'],
                                      self.keymap['well_rotate_cw'],
                                      self.keymap['block_rotate_ccw'],
                                      self.keymap['block_rotate_cw'],
                                      self.keymap['descent'],
                                      self.keymap['toggle_fullscreen'],
                                      self.keymap['pause'],
                                      self.keymap['drawing_mode'],
                                      self.keymap['increase_speed']
                                      ))
                                 ]),
            1, (192, 192, 128))


    def init_keymap(self):
        """Initialize key bindings"""
        
        self.keymap = {
            'pause': K_p,
            'toggle_fullscreen': K_f,
            'well_rotate_cw': K_RIGHT,
            'well_rotate_ccw': K_LEFT,
            'block_rotate_cw': K_DOWN,
            'block_rotate_ccw': K_UP,
            'descent': K_SPACE,
            'drawing_mode': K_m,
            'increase_speed': K_s,
            }

        section = 'keys'
        for key in self.keymap.keys():
            if self.config.has_option(section, key):
                self.keymap[key] = self.config.getint(section, key)
            else:
                self.config.set(section, key, self.keymap[key])


    def save_config(self):
        """Save changed configuration data to the config file"""
        self.config.write(open(self.cfgfile,'w'))
        print 'Config saved'
        return


    def reset(self):
        """Reset the game state for a new game"""
        
        self.hiscore_asker_rect = self.screenrect
        self.dirtyrects = []
        self.angle = 0
        self.field = []
        self.colorfield = []
        for y in range(15):
            self.field.append(16*[0])
            self.colorfield.append(16*[(128,128,128)])

        self.runtime = pygame.time.get_ticks()
        self.speed = 600
        self.score = 0
        self.lines = 0
        self.gameover = 0
        self.debug = 0
        self.paused = -1
        self.entering_hiscore = 0
        self.hiscore_name = ''

        self.next_block = Block()
        self.block = Block()

        self.next_block_visible = 1

        self.angle, self.y = 3, 0
        self.merge_here()

        return
        

    def run(self):
        """Game mainloop"""

        self.draw()
        self.update_display()
        self.fps.tick()

        pygame.event.set_blocked((MOUSEMOTION, MOUSEBUTTONUP,
                                  MOUSEBUTTONDOWN, JOYAXISMOTION,
                                  JOYBALLMOTION, JOYHATMOTION,
                                  JOYBUTTONUP, JOYBUTTONDOWN,
                                  VIDEOEXPOSE))

        # Main loop
        while 1:
            if self.runtime < self.speed + pygame.time.get_ticks():
                self.run_logic()

                self.draw()
                pygame.display.update()
                self.fps.tick()

            event = pygame.event.poll()
            if event.type == NOEVENT:
                try: pygame.time.wait(20)
                except AttributeError: pygame.time.delay(20)
                continue

            elif self.entering_hiscore and event.type == KEYDOWN:
                self.dirtyrects.append(self.hiscore_asker_rect)
                if event.key == K_BACKSPACE:
                    self.hiscore_name = self.hiscore_name[:-1]
                elif event.key == K_RETURN:
                    self.record_hiscore()
                    break
                elif K_a <= event.key <= K_z:
                    if KMOD_SHIFT & pygame.key.get_mods():
                        self.hiscore_name += chr(event.key - 32)
                    else:
                        self.hiscore_name += chr(event.key)
                elif event.key in (K_MINUS, K_SPACE) or \
                     ord('0') <= event.key <= ord('9'):
                    self.hiscore_name += chr(event.key)

            elif event.type == QUIT or (
                event.type == KEYDOWN and event.key in (K_ESCAPE,)):
                break

            #elif (event.type == KEYDOWN and event.key == K_d):
            #    self.debug *= -1

            elif event.type == ACTIVEEVENT and self.autopause \
                     and not self.gameover:
                if self.paused < 0 and event.state > 1 and event.gain == 0:
                    self.paused = 1
                    self.dirtyrects.append(self.screenrect)
                elif self.paused > 0 and event.gain == 1:
                    try: pygame.time.wait(100)
                    except AttributeError: pygame.time.delay(100)
                    self.paused = -1
                    self.dirtyrects.append(self.screenrect)

            elif event.type == KEYDOWN:
                if event.key == self.keymap['increase_speed']:
                    self.speed -= self.speed / 10
                    continue

                elif event.key == self.keymap['drawing_mode']:
                    self.drawmode += 1
                    self.drawmode %= len(self.drawers)
                    self.draw_blocks = self.drawers[self.drawmode]
                    self.config.set('ympyris', 'drawmode', `self.drawmode`)
                    self.dirtyrects.append(self.screenrect)

                elif (event.key == self.keymap['pause']
                      and not self.gameover):
                    self.paused *= -1
                    self.dirtyrects.append(self.screenrect)

                elif event.key == self.keymap['toggle_fullscreen']:
                    pygame.display.toggle_fullscreen()
                    self.dirtyrects.append(self.screenrect)

                elif self.paused < 0 and not self.gameover:
                    if event.key == self.keymap['well_rotate_ccw']:
                        self.rotate_ccw()
                        if self.check_collision(): self.rotate_cw()
                        else: self.dirtyrects.append(self.wellrect)
                    elif event.key == self.keymap['well_rotate_cw']:
                        self.rotate_cw()
                        if self.check_collision(): self.rotate_ccw()
                        else: self.dirtyrects.append(self.wellrect)
                    elif event.key == self.keymap['block_rotate_ccw']:
                        rect = pygame.Rect(
                            self.block_bbox(self.block, elevation=self.y))
                        self.block.rotate_ccw()
                        if self.check_collision(): self.block.rotate_cw()
                        else:
                            rect.union_ip(
                                self.block_bbox(self.block, elevation=self.y))
                            self.dirtyrects.append(rect)
                            self.dirty_miniatures()
                        del rect
                    elif event.key == self.keymap['block_rotate_cw']:
                        rect = pygame.Rect(
                            self.block_bbox(self.block, elevation=self.y))
                        self.block.rotate_cw()
                        if self.check_collision(): self.block.rotate_ccw()
                        else:
                            rect.union_ip(
                                self.block_bbox(self.block, elevation=self.y))
                            self.dirtyrects.append(rect)
                            self.dirty_miniatures()
                        del rect
                    elif event.key == self.keymap['descent']:
                        self.dirtyrects.append(
                            self.block_bbox(self.block, elevation=self.y))
                        while self.try_descent():
                            self.y -= 1
                            self.score += 1
                        self.merge_here()
                        
            self.draw()
            self.update_display()
            self.fps.tick()

            if self.fps.get_fps() > 20:
                try: pygame.time.wait(20)
                except AttributeError: pygame.time.delay(20)


    def draw_coords(self):
        """Draw the polar coordinate lines"""
        
        # Draw lines from the center outwards
        #for angle in range(len(self.field[0])):
        #    pygame.draw.line(self.display, (100,100,100), (500, 300),
        #                     (screen_x, screen_y), 1)
        map(apply, self.coords_liners, self.coords_pre)

        # Draw red circle for the bottom
        pygame.draw.circle(self.display, (200, 50, 50), (500, 300),
                           25, 3)

        for y in xrange(len(self.field)):
            # Coordinate circles
            pygame.draw.circle(self.display, (100,100,100),
                              (500, 300), 15*(y+2)+10, 1)

        return


    def draw_miniatures(self):
        """Draw miniature helper blocks"""
        self.display.blit(self.font.render('Current',
                                           1, (255,255,255)),
                          (240, 30))
        for b in self.block.blocks:
            pygame.draw.circle(self.display, (255, 255, 100),
                               (280-b[1]*10, 80-b[0]*10), 5, 1)
        if self.next_block_visible:
            self.display.blit(self.font.render('Next',
                                               1, (255,255,255)),
                              (730, 30))
            for b in self.next_block.blocks:
                pygame.draw.circle(self.display, (255, 100, 255),
                                   (750-b[1]*10, 80-b[0]*10), 5, 1)


    def dirty_miniatures(self):
        """Mark current miniature dirty"""
        self.dirtyrects.append(pygame.Rect(280-40, 80-40, 50, 50))


    def draw_blocks_std(self):
        """Draw blocks with the regular drawing mode"""
        
        # Draw now-static blocks outwards from the center
        for y in range(len(self.field)):
            # Draw each block in the circular level
            for angle in range(len(self.field[y])):
                if self.field[y][angle]:
                    pointlist = self.prepoints[y][(angle-self.angle)%16]
                    pygame.draw.polygon(self.display, (100, 200, 200),
                                        pointlist, 1)
                    screen_x,screen_y = self.angles2xy(angle-self.angle, y)
                    pygame.draw.circle(self.display, (255,255,100),
                                       (int(screen_x), int(screen_y)), 7, 0)
        # Draw the blocks of the current piece
        for b in self.block.blocks:
            pointlist = self.angles2polygon(b[0], b[1]+self.y)
            pygame.draw.polygon(self.display, self.block.strong_color, #(50, 50, 200),
                            pointlist, 1)
            screen_x, screen_y = self.angles2xy(b[0], b[1]+self.y)
            pygame.draw.circle(self.display,
                               (255-50*b[1], 255, 50*b[1]),
                               (int(screen_x), int(screen_y)), 7, 0)
        return


    def draw_blocks_solid(self):
        """Draw blocks with the solid drawing mode"""
        
        # Draw now-static blocks outwards from the center
        for y in range(len(self.field)):
            # Draw each block in the circular level
            for angle in range(len(self.field[y])):
                if self.field[y][angle]:
                    pointlist = self.prepoints[y][(angle-self.angle)%16]
                    #pygame.draw.polygon(self.display, (100, 200, 200),
                    #                    pointlist, 0)
                    color = self.colorfield[y][angle]
                    pygame.draw.polygon(self.display, color, pointlist, 0)
                    pygame.draw.polygon(self.display, (0, 0, 0),
                                        pointlist, 1)
        # Draw the blocks at the current piece
        #c0 = self.block.color
        #c1 = (c0[0]/16, c0[1]/16, c0[2]/16)

        #r, g, b = self.block.soft_color
        #tmpcolor = (int(r/1.2), int(g/1.2), int(b/1.2))
        for b in self.block.blocks:
            pointlist = self.angles2polygon(b[0], b[1]+self.y)
            #tmplist = [(x-1, y) for (x, y) in pointlist]
            pygame.draw.polygon(self.display, self.block.soft_color,
                                #(150, 255, 200),
                                pointlist, 0)

            #pygame.draw.polygon(self.display,
            #                    tmpcolor, #(0, 0, 0),
            #                    tmplist, 2)
            #pygame.draw.lines(self.display, tmpcolor, 0, tmplist, 1)
        return


    def draw_hiscore_name_asker(self):
        """Draw hiscore name prompt"""
        
        self.hiscore_name = self.hiscore_name[:30]
        t1 = self.font.render('Your name for hiscores:',
                              1, (200, 255, 200))
        t2 = self.font.render(self.hiscore_name+'_',
                              1, (255, 255, 0))
        w = max(t1.get_width(), t2.get_width())

        self.hiscore_asker_rect = pygame.Rect((400-w/2-20, 300-40,
                                               w+20+20, 60+20))

        pygame.draw.rect(self.display, (20, 0, 20),
                         (400-w/2-10, 300-30, w+20, 60), 0)
        pygame.draw.rect(self.display, (255, 230, 230),
                         (400-w/2-10, 300-30, w+20, 60), 2)
        self.display.blit(t1, (400-w/2, 300-20))
        self.display.blit(t2, (400-w/2, 300+5))


    def draw_dirtyrects(self):
        for r in self.dirtyrects:
            pygame.draw.rect(self.display, (0, 200, 100), r, 1)


    def draw(self):
        """Draw everything"""
        
#        self.display.lock()
        self.display.fill(0)

        if not self.entering_hiscore:
            self.draw_coords()

            if self.paused < 0:    # Unless paused
                self.draw_blocks()
            else:    # If paused
                self.display.blit(self.bigfont.render('PAUSED', 1,
                                                      (0, 255, 0)),
                                  (100, 250))

        # Print score data
        self.display.blit(self.font.render('Score: '+`self.score`,
                                           1, (255,255,255)),
                          (500, 10))
        self.display.blit(self.font.render('Circles: '+`self.lines`,
                                           1, (255,255,255)),
                          (650, 10))
        if self.debug:
            self.dirtyrects.append((300, 5, 100, 40))
            self.display.blit(self.font.render(`int(self.fps.get_fps())` \
                                               +' fps', 1, (255,255,255)),
                              (300, 10))
            self.display.blit(self.font.render(`self.speed`+' ms',
                                               1, (255, 255, 255)),
                              (300, 25))
                                               

        # Draw miniblocks
        self.draw_miniatures()

        # Game over screen overlay
        if self.gameover:
            self.display.blit(self.bigfont.render('GAME OVER!', \
                                                  1, (255,0,0)),
                              (10, 250))

        self.display.blit(self.helplabel, (10, 580))

        if self.entering_hiscore:
            self.draw_hiscore_name_asker()

        if self.debug:
            self.draw_dirtyrects()

#        self.display.unlock()


    def update_display(self):
        """Update the display and reset the dirty rectangle list"""
        #if self.debug: pygame.display.update()
        #else: pygame.display.update(self.dirtyrects)
        pygame.display.update(self.dirtyrects)
        self.dirtyrects = []


    def angles2xy(self, angle, y):
        """Convert polar coordinates to cartesian screen coordinates"""

        screen_y = (15 * (y+2) +2) \
                   * math.sin(math.pi/8*(angle+7))+300
        screen_x = (15 * (y+2) +2) \
                   * math.cos(math.pi/8*(angle+7))+500
        return int(round(screen_x)), int(round(screen_y))


    def block_bbox(self, block, elevation=0, angle=0):
        """Get a bounding box for a block"""
        b1 = block.blocks[0]
        bs = block.blocks[1:]
        rect1 = pygame.Rect(self.cell_bbox(b1[0] - angle, b1[1] + elevation))
        for b in bs:
            rect1.union_ip(self.cell_bbox(b[0] - angle, b[1] + elevation))
        return rect1


    def calc_cell_bbox(self, angle, y):
        """Calculate the bounding box for a cell in the polar grid"""
        poly = self.angles2polygon(angle, y)
        xlist = [p[0] for p in poly]
        ylist = [p[1] for p in poly]
        minx = min(xlist) - 3
        miny = min(ylist) - 3
        maxx = max(xlist) + 3
        maxy = max(ylist) + 3
        return (minx, miny, maxx-minx, maxy-miny)


    def cell_bbox(self, angle, y):
        try:
            return self.prebboxes[y][angle%16]
        except:
            print 'bbox', angle, y
            return self.calc_cell_bbox(angle, y)


    def calc_angles2polygon(self, angle, y):
        """Calculate the polygon for a single cell by polar coordinates"""
        #angles = (angle-0.5,  angle-0.5, angle-0.25, angle,
        #          angle+0.25, angle+0.5, angle+0.5)
        #dists =  (y-0.5,      y+0.5,     y+0.5,      y+0.5,
        #          y+0.5,      y+0.5,     y-0.5)
        angles = []
        dists = []

        for i in range(-2, 3):
            angles.append(float(i)*.25+angle)
            dists.append(y+0.5)
        for i in range(2, -3, -1):
            angles.append(float(i)*.25+angle)
            dists.append(y-0.5)

        return map(self.angles2xy, angles, dists)


    def angles2polygon(self, angle, y):
        try:
            return self.prepoints[y][angle%16]
        except:
            print angle, y
            return self.calc_angles2polygon(angle, y)
    

    def run_logic(self):
        """Game logic

        Run game logic at a near-constant speed.
        """

        while self.runtime < self.speed + pygame.time.get_ticks():
            self.runtime += self.speed
            if self.paused < 0 and not self.gameover:
                if self.try_descent():
                    rect1 = self.block_bbox(self.block, elevation=self.y)
                    self.y -= 1
                    rect1.union_ip(self.block_bbox(self.block,
                                                   elevation=self.y))
                    self.dirtyrects.append(rect1)
                else:
                    self.score += 1
                    self.merge_here()
            else:
                try: pygame.time.wait(10)
                except AttributeError: pygame.time.delay(10)

    def rotate_cw(self):
        """Rotate the well clockwise"""
        self.angle += 1
        self.angle %= 16

    def rotate_ccw(self):
        """Rotate the well counterclockwise"""
        self.angle -= 1
        self.angle %= 16

    def check_collision(self):
        """Check if the blocks have collided/landed"""
        for b in self.block.blocks:
            angle, y = b
            angle += self.angle
            angle %= 16
            y += self.y

            if y>=len(self.field): continue
            if self.field[y][angle]: return 1

        return 0


    def try_descent(self):
        """Try to descent the block one level lower"""
        for b in self.block.blocks:
            angle, y = b
            angle += self.angle
            angle %= 16
            y += self.y

            if y>=len(self.field): continue
            if y==0 or self.field[y-1][angle]: return 0
        
        return 1


    def merge_here(self):
        """Make the falling block stop fall"""
        self.dirtyrects.append(self.block_bbox(self.block, elevation=self.y))
        self.dirtyrects.append(self.scorerect)
        for b in self.block.blocks:
            angle, y = b
            angle += self.angle
            angle %= 16
            y += self.y
            if y >= len(self.field):
                self.gameover = 1
                self.save_score()
            else:
                self.field[y][angle] = 1
                self.colorfield[y][angle] = self.block.strong_color

        if self.gameover:
            self.dirtyrects.append(self.screenrect)
            return

        self.block = self.next_block
        self.next_block = Block()
        self.y = 20

        self.dirtyrects.append(self.block_bbox(self.block, elevation=self.y))

        line = 0
        while line < len(self.field):
            if not self.check_line(line): line += 1

        self.dirtyrects.append(self.screenrect)
        

    def save_score(self):
        """Begin the phase of saving the hiscores"""
        try:
            scores = [(int(s[0]), int(s[1]), s[2])
                      for s in
                      [s1.strip().split(',', 2)
                       for s1 in open(self.scorefile, 'r')]
                      if s]
        except:
            scores = []

        if (len(scores) < 10 or
            (self.score, self.lines) > (scores[-1][0], scores[-1][1])):
            # you made it!
            self.entering_hiscore = 1
            self.scorelist = scores


    def record_hiscore(self):
        """Actually save the hiscores"""
        scores = self.scorelist
        scores.append((self.score, self.lines,
                       self.hiscore_name.strip()[:30]))
        scores.sort()
        scores.reverse()

        f = open(self.scorefile, 'w')
        f.write('\n'.join([('%d,%d,%s' % s) for s in scores[:10]]) + '\n')
        f.close()


    def check_line(self, line):
        """Check if a specific line is full"""
        for x in self.field[line]:
            if not x: return 0

        self.field.append(len(self.field[line])*[0])
        self.colorfield.append(len(self.field[line])*[(128,128,128)])
        del self.field[line]
        del self.colorfield[line]

        # Decrease the delay between decending one level
        self.speed -= self.speed / 50 - 3
        self.lines += 1
        return 1


ympyris_version = (0, 9, 5, )
ympyris_version_str = '.'.join(map(str, ympyris_version[:3]))
if ympyris_version[3:]:
    ympyris_version_str += ('-' + '-'.join(ympyris_version[3:]))

ympyris_copyright = """Ympyris version %s
Copyright (C) 2002-2005 Juha-Matti Tapio <jmtapio@users.sourceforge.net>

Ympyris comes with ABSOLUTELY NO WARRANTY.  You may redistribute
copies of Ympyris under the terms of the GNU General Public License.

For more information about these matters, see the file named COPYING.
""" % ympyris_version_str

about_text = """Ympyris is a falling blocks game (also known as a Tetris clone).

To make your head buzz, Ympyris uses a polar coordinate system instead
of the regular grid used by almost all Tetris clones. Also the falling
blocks do not rotate around the well but the well rotates under the
blocks.

This is unpolished software. Please report any problems or just give
feedback. (I would also appreciate help in almost any form.)

See http://ympyris.sourceforge.net/ for updates.
"""

class Menu:
    def __init__(self, display):
        if sys.platform=='win32':
            self.datapath = ('.',)
        else:
            self.datapath = map(os.path.expanduser,
                                ('/usr/share/games/ympyris',
                                 '/usr/lib/games/ympyris',
                                 '/usr/local/lib/games/ympyris',
                                 '/opt/ympyris',
                                 '~/lib/games/ympyris',
                                 '.'
                                 )
                                )

        self.display = display
        self.menufont = pygame.font.Font(None, 20)
        self.bigmenufont = pygame.font.Font(None, 30)
        l = self.load_image
        self.menuitems = (('New game', self.new_game, l('newgame.png')),
                          ('Scores', self.scores, l('scores.png')),
                          ('Settings', self.settings, l('settings.png')),
                          ('About', self.credits, l('credits.png')),
                          ('Quit', self.quit, l('quit.png')),
                          )
        self.selectionimage = l('selection.png')
        self.menulabels = [
            (self.menufont.render(s, 1, (255, 255, 220)),
             self.bigmenufont.render(s, 1, (200, 255, 200)))
            for s, fn, png in self.menuitems
            ]
        self.menuhelp = self.menufont.render(
            "<--  Where would you like to rotate to today?  -->",
            1, (130, 170, 255))
        self.game = Ympyris(display)
        self.currenti = 0.0
        self.rotate_speed = 0.0

    def lookup_datafile(self, filename):
        for path in self.datapath:
            name = os.path.join(path, filename)
            if os.path.exists(name):
                return name
        return None

    def load_image(self, filename):
        return pygame.image.load(self.lookup_datafile(filename))

    def go(self):
        for fn in self.select():
            fn()

    def select(self):
        pygame.event.set_blocked((MOUSEMOTION, MOUSEBUTTONUP,
                                  MOUSEBUTTONDOWN, JOYAXISMOTION,
                                  JOYBALLMOTION, JOYHATMOTION,
                                  JOYBUTTONUP, JOYBUTTONDOWN,
                                  VIDEOEXPOSE))
        self.itemi = 0
        
        # Main loop
        while 1:
            self.itemi %= len(self.menuitems)
            self.draw()
            while self.rotate_speed:
                self.draw()
            event = pygame.event.wait()
            if event.type == QUIT or (
                event.type == KEYDOWN and event.key in (K_ESCAPE, K_q)):
                yield self.menuitems[-1][1]
            elif event.type == KEYDOWN and event.key == K_f:
                pygame.display.toggle_fullscreen()
            elif event.type == KEYDOWN and event.key == K_LEFT:
                self.itemi -= 1
                self.rotate_speed = -0.2
            elif event.type == KEYDOWN and event.key == K_RIGHT:
                self.itemi += 1
                self.rotate_speed = 0.2
            elif event.type == KEYDOWN and (
                event.key in (K_SPACE, K_RETURN, K_KP_ENTER)):
                yield self.menuitems[self.itemi][1]

    def draw(self):
#        self.display.lock()
        self.display.fill(0)

        itemcount = len(self.menuitems)
        delta = math.pi * 2 / itemcount
        shift = -math.pi / 2

        if self.rotate_speed:
            d = self.currenti - float(self.itemi)
            if abs(d) < abs(self.rotate_speed):
                self.currenti = self.itemi
                self.rotate_speed = 0.0
            
            self.currenti += self.rotate_speed
            self.currenti %= itemcount
                
        c_i = self.currenti

        self.display.blit(self.selectionimage, (400-32, 220-32))
        for i in range(itemcount):
            self.display.blit(self.menuitems[i][2],
                              (400+80*math.cos(delta*(i-c_i)+shift)-32,
                               300+80*math.sin(delta*(i-c_i)+shift)-32)
                              )
            self.display.blit(self.menulabels[i][i==self.itemi],
                              (400+140*math.cos(delta*(i-c_i)+shift)
                               - self.menulabels[i][i==self.itemi].get_width()/2,
                               300+140*math.sin(delta*(i-c_i)+shift))
                              )

        self.display.blit(self.menuhelp,
                          (400-self.menuhelp.get_width()/2, 550))
            
#        self.display.unlock()
        pygame.display.update()
    
    def new_game(self):
        self.game.reset()
        self.game.run()
        if self.game.entering_hiscore:
            self.scores()

    def get_topten(self):
        if sys.platform == 'win32':
            scorefile = 'ympyris.scores'
        else:
            scorefile = os.path.expanduser('~/.ympyris.scores')
        try:
            return [(int(s[0]), int(s[1]), s[2]) for s in 
                    [s1.strip().split(',', 2) for s1 in open(scorefile,'r')]
                    if s
                    ]
        except IOError:
            return [(0, 0, 'Nobody')]

    def scores(self):
        topten = self.get_topten()

#        self.display.lock()
        self.display.fill(0)

        self.display.blit(
            self.menufont.render('Player', 1, (255,255,255)),
            (250, 180))
        self.display.blit(
            self.menufont.render('Score', 1, (255,255,255)),
            (500, 180))
        self.display.blit(
            self.menufont.render('Circles', 1, (255,255,255)),
            (550, 180))
        for i in range(len(topten)):
            try:
                if topten[i]:
                    self.display.blit(
                        self.menufont.render(topten[i][2], 1, (255,255,200)),
                        (250, 200+i*20))
                    self.display.blit(
                        self.menufont.render(`topten[i][0]`, 1, (255,255,200)),
                        (500, 200+i*20))
                    self.display.blit(
                        self.menufont.render(`topten[i][1]`, 1, (255,255,200)),
                        (550, 200+i*20))
            except IndexError:
                pass

        self.display.blit(self.menufont.render('Press any key...', 1,
                                               (30, 240, 80)),
                          (20, 550))
#        self.display.unlock()
        pygame.display.update()
        while pygame.event.wait().type not in (KEYDOWN, QUIT): pass

    def settings(self):
#        self.display.lock()
        self.display.fill(0)

        self.display.blit(self.menufont.render('No one has coded this screen yet. Sorry. Press any key...', 1, (255, 100, 100)),
                          (350, 290))

#        self.display.unlock()
        pygame.display.update()
        while pygame.event.wait().type not in (KEYDOWN, QUIT): pass

    def credits(self):
#        self.display.lock()
        self.display.fill(0)

        for row, line in zip(xrange(sys.maxint),
                        about_text.split('\n')):
            self.display.blit(self.menufont.render(line, 1,
                                                   (255,255,200)),
                              (100, 15 * row + 80))

        for row, line in zip(xrange(sys.maxint),
                        ympyris_copyright.split('\n')):
            self.display.blit(self.menufont.render(line, 1,
                                                   (150,150,150)),
                              (100, 15 * row + 400))

        self.display.blit(self.menufont.render('Press any key...', 1,
                                               (100, 50, 255)),
                          (20, 550))
#        self.display.unlock()
        pygame.display.update()
        while pygame.event.wait().type not in (KEYDOWN, QUIT): pass

    def quit(self):
        self.game.save_config()
        raise SystemExit


if __name__=='__main__':
    print ympyris_copyright
    pygame.init()
    display = pygame.display.set_mode((800, 600))
    pygame.display.set_caption('Ympyris %s' % ympyris_version_str)
    pygame.mouse.set_visible(0)
    menu = Menu(display)
    menu.go()

