#!/usr/bin/env python

#
# microTetris 0.1
#
# Copyright (C) 2006, Federico Pelloni <federico.pelloni@gmail.com>
#
# microTetris 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.
#
# microTetris 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 microTetris; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, 
# Boston, MA  02110-1301  USA
#
# ---
#
# microTetris web: http://microtetris.sourceforge.net/
#
# If you download the .py file, you can see the full license text at:
# http://www.opensource.org/licenses/gpl-license.php
#


import random
import gtk, gobject




class Game:
    
    # Row number
    rows = 20
    # Column number
    columns = 11
    # Size of a square
    square_size = 3
    # Spacing between squares
    spacing = 1
    
    # Speed (interval between two ticks in milliseconds: larger for slower speed)
    timeout = 800
    
    
    # Do not show in taskbar & pager
    skip_taskbar = False
    # Show icon in taskbar
    show_icon = True


    keys = {
        # Q
        113:    "quit",
        # ESC
        65307:  "quit",
        # N
        110:    "new_game",
        # P
        112:    "toggle_pause",
        # Left arrow
        65361:  "left",
        # Right arrow
        65363:  "right",
        # Up arrow
        65362:  "rotate",
        # Down arrow
        65364:  "drop"
    }
    
    
    # Red, Green, Blue triplets
    colors = ( 
            (   1,   1,   0),   # 1 Yellow
            (   0,   1,   0),   # 2 Green
            ( 0.2, 0.2,   1),   # 3 Blue
            (   1,   0,   1),   # 4 Purple
            (   0,   1,   1),   # 5 Cyan
            (   1,   0,   0),   # 6 Red
            (   1,   1,   1),   # 7 White
        )
    
    
    # Shape matrix: 0 transparent, > 0 colour block
    blocks = (
            ((2, 2, 2), (2, 0, 0)),     # Green L
            ((6, 6, 6), (0, 0, 6)),     # Reversed Red L
            ((3, 3, 3), (0, 3, 0)),     # Blue T
            ((4, 4, 4, 4), ),           # Purple I
            ((7, 7, 0), (0, 7, 7)),     # White "Z"
            ((0, 1, 1), (1, 1, 0)),     # Yellow "S"
            ((5, 5), (5, 5))            # Cyan square
        )
        
    
    
    icon = [ "48 48 5 1",
            "  c red",
            ". c #FFFFFF",
            "X c #37C837",
            "# c #FFD42A",
            "= c #00ABD4",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############.                                   ",
            "############............           .............",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "#######################.           .============",
            "############........................============",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            "############.XXXXXXXXXX.========================",
            ".............XXXXXXXXXX.............============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============",
            "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.============"]
    
    
    paused = True
    falling_block = None
    row_matrix = None
    
    
    def __init__(self):
    
        w = gtk.Window()
        self.window = w
        w.set_title("microTetris")
        if self.skip_taskbar:
            w.set_property('skip-pager-hint', True)
            w.set_property('skip-taskbar-hint', True)
            
        w.set_decorated(False)
        if self.show_icon:
            w.set_icon(gtk.gdk.pixbuf_new_from_xpm_data(self.icon))
        
        self.width = self.columns * (self.square_size + self.spacing) - self.spacing + 4
        self.height = self.rows * (self.square_size + self.spacing) - self.spacing + 4
        w.resize(self.width, self.height)
        
        sw = gtk.gdk.screen_width()
        sh = gtk.gdk.screen_height()
        w.move(sw - self.width, sh - self.height)
        
        w.connect("key-press-event", self.catch_keypress)
        w.connect("destroy", self.quit)
        w.connect("focus-out-event", lambda *a: self.toggle_pause(True))
        
        da = gtk.DrawingArea()
        self.widget = da
        w.add(da)
        
        da.connect("expose-event", self.draw)
        
        random.seed()
        
        


    def catch_keypress(self, widget, event):
        
        k = event.keyval
        if k in self.keys:
            f = self.keys[k]
            assert hasattr(self, f), "Missing callback for '%s'" % f
            getattr(self, f)()
                
    
    
    
    def run(self):
        
        self.new_game()
        self.window.show_all()
        gtk.main()
    
    
    
    def quit(self, *args):
        
        gtk.main_quit()
    
    
    
    def draw(self, widget, event):
        
        widget.window.clear()
        cr = widget.window.cairo_create()
        
        cr.rectangle(*event.area)
        cr.clip()
        
        cr.rectangle(*event.area)
        cr.set_source_rgb(0, 0, 0)
        cr.fill()
        cr.rectangle(*event.area)
        cr.set_source_rgb(0.5, 0.5, 0.5)
        cr.stroke()
        
        if self.falling_block is not None:
            
            basex = self.falling_block['x'] * (self.square_size + self.spacing) + 2
            basey = self.falling_block['y'] * (self.square_size + self.spacing) + 2
            i = 0
            for r in self.falling_block['block']:
                j = 0
                for c in r:
                    if c > 0:
                        cr.set_source_rgb(*self.colors[c - 1])
                        x = basex + j * (self.square_size + self.spacing)
                        y = basey + i * (self.square_size + self.spacing)
                        cr.rectangle(x, y, self.square_size, self.square_size)
                        cr.fill()
                    j += 1
                i += 1
         
        i = 0       
        for r in self.row_matrix:
            j = 0
            for c in r:
                if c > 0:
                    cr.set_source_rgb(*self.colors[c - 1])
                    x = j * (self.square_size + self.spacing) + 2
                    y = i * (self.square_size + self.spacing) + 2
                    cr.rectangle(x, y, self.square_size, self.square_size)
                    cr.fill()
                j += 1
            i += 1
            
  
    
    
        
    # Game routine
    
    def new_game(self):
        
        self.row_matrix = [[0 for i in range(1, self.columns + 1)] for j in range(1, self.rows + 1)]
        self.falling_block = None
        self.toggle_pause(False)
    
    
    
    def new_block(self):
        
        block = random.choice(self.blocks)
        rotation = random.randint(0, 3)
        for i in range(rotation):
            block = self.rotate_block(block)
        self.falling_block = dict(block = block, x = (self.columns - len(block[0])) / 2, y = 0)
        
        
        
    def tick(self):
        
        if self.falling_block is None:
            self.new_block()
        else:
            fb = self.falling_block
            
            touched = False
            
            if len(fb['block']) + fb['y'] == self.rows:
                touched = True
            else:
                i = 0
                for r in fb['block']:
                    j = 0
                    for c in r:
                        if c > 0:
                            y = fb['y'] + i
                            x = fb['x'] + j
                            if (self.row_matrix[y + 1][x] > 0):
                                touched = True
                                break
                        j += 1
                    i += 1
            
            
            if touched:
                self.fix_block(fb)
                self.new_block()
            else:
                fb['y'] += 1
            
        self.widget.queue_draw()
        
        return not self.paused
    
    
    def fix_block(self, fb):
        if fb['y'] <= 1:
            self.new_game()
            return
        i = 0
        for r in fb['block']:
            j = 0
            for c in r:
                if c > 0:
                    self.row_matrix[fb['y'] + i][fb['x'] + j] = c
                j += 1
            i += 1
        for r in self.row_matrix:
            if 0 not in r:
                self.row_matrix.remove(r)
                self.row_matrix.insert(0, [0 for i in range(self.columns + 1)])
    
    
    
    def toggle_pause(self, paused = None):
        
        if paused is None:
            paused = not self.paused
            
        if not paused:
            gobject.timeout_add(self.timeout, self.tick)
        
        self.paused = paused
        
    
    
    def left(self):
        if not self.paused:
            x = max(self.falling_block['x'] - 1, 0)
            self.falling_block['x'] = x
            self.widget.queue_draw()
       
    def right(self):
        if not self.paused:
            x = min(self.falling_block['x'] + 1, self.columns - len(self.falling_block['block'][0]))
            self.falling_block['x'] = x
            self.widget.queue_draw()
        
    def rotate(self):
        if not self.paused:
            b = self.falling_block['block']
            b = self.rotate_block(b)
            self.falling_block['block'] = b
            self.widget.queue_draw()
    
    def drop(self):
        if not self.paused:
            fb = self.falling_block
            
            touched = True
            y = self.rows - len(fb['block'])
            while touched:
                touched = False
                i = 0
                for r in fb['block']:
                    cy = y + i
                    j = 0
                    for c in r:
                        if c > 0 and self.row_matrix[cy][fb['x']+j] > 0:
                            touched = True
                            break
                        j += 1
                    i += 1
                y -= 1
            fb['y'] = y + 1
            self.fix_block(fb)
            self.new_block()
            self.tick()
        
    
    def rotate_block(self, matrix):

        rows = len(matrix[0])
        cols = len(matrix)
        new = []
        for i in range(rows):
            cur = []
            new.append(cur)
            for j in range(cols):
                cur.append(matrix[j][rows - i - 1])
        return new
        
    



if __name__ == "__main__":
    
    game = Game()
    game.run()
    
    
