#!/usr/bin/python
#coding: utf-8
#2013年8月23日

##定义一个二维数组作为游戏板。如果要实现新方块从无到有一行一行的出现，那么将游戏板多增加4行是个好主意，作为画板的一个虚拟部分，并不显示到屏幕，但能更方便的进行处理。 
##每种形状的尺寸都为4 * 4，为了容纳横竖两种长条。 
##定义blit()，将指定的形状复制到游戏板指定的行列上。 
##定义movep()，判断指定的形状在指定的行列是否能够移动。若能，5 - 8，若不能，9 
##定义erase()，移动方块之前，先删除之，然后blit()到新的位置。 
##定义is_full_line()，判断指定的行是否为满行。 
##定义del_line()，删除指定的行。 
##定义del_full_lines()，从指定的行开始向下4行进行is_full_line()判断，若是，则del_line()。从上到下删除比从下到上删除要来的方便。 
##下一个方块作为当前方块，并将其放在适当的位置上，重新开始新一轮循环。 
import pygame
from pygame.color import THECOLORS
import random
import sys
import settings

from shapes import SHAPES as shapes


UP, DOWN, LEFT, RIGHT = range(0, 4)


class Grid(object):
    def __init__(self, occupied, color):
        self.occupied = occupied
        self.color = color


class Board(object):
    def __init__(self, row, col):
        self.row = row
        self.col = col
        #self.data = [[Grid(False, BACKCOLOR)] * col] * (row + SHAPE_SIZE)  这样生成，所有的Grid对象都为同一个
        #在实际游戏板上方再增加虚拟的几行，用以实现新方块从上方一行一行地出现
        self.data = [[Grid(False, settings.BG_COLOR) for j in range(col)] for i in range(row + settings.SHAPE_SIZE)]

    def del_full_lines(self, shape, row):
        """返回删除的行号"""
        del_lines = []
        for i in range(row, row + shape.height):
            if self.is_full_line(i):
                self.del_line(i)
                del_lines.append(i)

        return del_lines
        
    def del_line(self, row):
        """删除指定行"""
        for i in range(row, 0, -1):
            blank_count = 0
            for j in range(0, settings.COLS):
                if not self.data[i - 1][j].occupied:
                    blank_count += 1
                self.data[i][j].occupied = self.data[i - 1][j].occupied
                self.data[i][j].color = self.data[i - 1][j].color
            if blank_count == settings.COLS:
                break
                  
    def is_full_line(self, row):
        for col in range(0, settings.COLS):
            if not self.data[row][col].occupied:
                return False
        return True
            
    def movep(self, shape, row, col, action):
        if action == DOWN:
            offset_row = 1
            offset_col = 0
        elif action == UP:
            offset_row = 0
            offset_col = self.adjust_pos(shape, col)
            shape = shape.next
        elif action == LEFT:
            offset_row = 0
            offset_col = -1
        elif action == RIGHT:
            offset_row = 0
            offset_col = 1
        else:
            print 'Fatal error has been occurs.'
            sys.exit(1)
##        if row + offset_row < 0 or row + offset_row + (shapes[num].height - 1) >= ROW\
##                or col + offset_col < 0 or col + offset_col + (shapes[num].width - 1) >= COL:
##                return False
        for i in range(settings.SHAPE_SIZE):
            for j in range(settings.SHAPE_SIZE):
                if shape.data[i][j]:
                    row_val = row + i + offset_row
                    col_val = col + j + offset_col
                    if (row_val < 0 or
                            row_val >= settings.ROWS + settings.SHAPE_SIZE  or
                            col_val < 0 or
                            col_val >= settings.COLS or
                            self.data[row_val][col_val].occupied):
                        return False                    
        return True
    
    def adjust_pos(self, shape, col):     #若方块移到最右边，便不能变换，体验不好，用于解决该问题
        trans = shape.next
        if col + shape.width < settings.COLS:
            return 0
        elif shape.width >= trans.width:
            return 0
        else:
            return shape.width - trans.width
    
    def erase(self, shape, row, col):     #从board上清除指定shape
        for i in range(settings.SHAPE_SIZE):
            for j in range(settings.SHAPE_SIZE):
                if shape.data[i][j]:
                    self.data[i + row][j + col].occupied = False
            

class Game(object):
    def __init__(self):
        self.board = Board(settings.ROWS, settings.COLS)
        self.score = 0
        self.pause = False
        self.pressed = False

        self.next_shape = random.choice(shapes)
        self.next_color = random.choice(settings.COLORS)

        self.current_shape = None
        self.current_color = None
        self.current_row = 0
        self.current_col = 0
        self.delay = settings.DELAY
        self.screen = pygame.display.set_mode((settings.WIDTH, settings.HEIGHT))
        self.clock = pygame.time.Clock()

        self.set_next()
        self.set_pos()
        self.set_speed()

    def set_next(self):
        """下一个方块成为当前方块"""
        self.current_shape = self.next_shape
        self.current_color = self.next_color
        self.next_shape = random.choice(shapes)
        self.next_color = random.choice(settings.COLORS)

    def set_pos(self):
        """将新方块初始到适当位置"""
        self.current_row = settings.SHAPE_SIZE - self.current_shape.height
        self.current_col = settings.COLS / 2 - self.current_shape.width / 2

    def display(self):
        self.display_board()
        self.display_right_frame()

    def blit(self):
        """将一个shape移到board上"""
        for i in range(settings.SHAPE_SIZE):
            for j in range(settings.SHAPE_SIZE):
                if self.current_shape.data[i][j]:
                    self.board.data[i + self.current_row][j + self.current_col].occupied = True
                    self.board.data[i + self.current_row][j + self.current_col].color = self.current_color

    def display_board(self):
        for row in range(self.board.row):
            for col in range(self.board.col):
                if self.board.data[row + settings.SHAPE_SIZE][col].occupied:
                    rect = pygame.Rect(col * settings.GRID_SIZE,
                                       row * settings.GRID_SIZE,
                                       settings.GRID_SIZE,
                                       settings.GRID_SIZE)
                    pygame.draw.rect(self.screen, self.board.data[row + settings.SHAPE_SIZE][col].color, rect, 0)
                    pygame.draw.rect(self.screen, settings.FG_COLOR, rect, 1)

    def display_right_frame(self):
        """显示右侧边栏，预览，分数"""
        pygame.draw.line(self.screen,
                         settings.FG_COLOR,
                         (settings.COLS * settings.GRID_SIZE, 0),
                         (settings.COLS * settings.GRID_SIZE, settings.HEIGHT))
        self.display_next()
        self.display_score()

    def display_score(self):
        font = pygame.font.Font(None, 35)
        text = font.render('Score', True, settings.FG_COLOR)
        self.screen.blit(text, [settings.RIGHT_BOX_X, settings.HEIGHT - settings.HEIGHT / 2])

        font = pygame.font.Font(None, 30)
        text = font.render(str(self.score), True, THECOLORS['lightblue'])
        self.screen.blit(text, [settings.RIGHT_BOX_X, settings.HEIGHT - settings.HEIGHT / 2 + 40])

    def display_next(self):
        font = pygame.font.Font(None, 35)
        text = font.render('Next', True, settings.FG_COLOR)
        self.screen.blit(text, [settings.RIGHT_BOX_X, 85])

        grid_size = 25
        for row in range(settings.SHAPE_SIZE):
            for col in range(settings.SHAPE_SIZE):
                if self.next_shape.data[row][col]:
                    rect = pygame.Rect(settings.RIGHT_BOX_X + col * grid_size,
                                       130 + row * grid_size,
                                       grid_size,
                                       grid_size)
                    pygame.draw.rect(self.screen, self.next_color, rect, 0)
                    pygame.draw.rect(self.screen, settings.FG_COLOR, rect, 1)

    def set_score(self, rows):
        """根据消除的行数增加分数，同时加快游戏速度"""
        scores = [0, 50, 100, 200, 400]
        self.score += scores[rows]

    def set_speed(self):
        """根据当前score，设置speed"""
        if self.delay > settings.DELAY_LIMIT:
            self.delay = settings.DELAY - self.score / 20

    def gameover(self):
        font = pygame.font.Font(None, 60)
        text = font.render('Game  Over', True, THECOLORS['white'])
        self.screen.blit(text,
                         [settings.COLS * settings.GRID_SIZE / 2 - text.get_width() / 2,
                          settings.HEIGHT / 2 - text.get_height() / 2])
        pygame.display.flip()

        keyup = False
        clock = pygame.time.Clock()
        loop = True
        while loop:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()

                # 按键之后重新开始
                elif event.type == pygame.KEYDOWN:
                    # not pressed意味着在没按下移键的情况下，方块到达顶端，游戏结束。
                    # 而keyup意味着一直按着下移键直到游戏结束，此时虽然按着键盘，但并
                    # 不会导致游戏重新开始，要松开键盘再按一次才可。要的就是这种效果。
                    if not self.pressed or keyup:
                        self.__init__()
                        loop = False

                elif event.type == pygame.KEYUP:
                    keyup = True

            clock.tick(settings.FPS)

    def process_to_next(self):
        """方块不能下移意味着要启用新的方块，进行一系列处理"""
        self.blit()
        if self.current_row < settings.SHAPE_SIZE:
            self.gameover()
        else:
            rows = self.board.del_full_lines(self.current_shape, self.current_row)
            self.blink(rows)
            self.set_next()
            self.set_pos()
            self.set_score(len(rows))
            self.set_speed()
            pygame.time.set_timer(pygame.USEREVENT, self.delay)

    def blink(self, del_list):
        """在删除行时增加闪动效果，接收欲删除的行号列表"""
        if len(del_list) == 0:
            return
        for row in del_list:
            row -= settings.SHAPE_SIZE
            for col in range(settings.COLS):
                rect = pygame.Rect(col * settings.GRID_SIZE,
                                   row * settings.GRID_SIZE,
                                   settings.GRID_SIZE,
                                   settings.GRID_SIZE)
                pygame.draw.rect(self.screen, settings.BG_COLOR, rect, 0)
        pygame.display.flip()
        pygame.time.delay(100)

    def play(self):
        pygame.init()
        pygame.display.set_caption(settings.CAPTION)
        pygame.key.set_repeat(200, 30)
        pygame.time.set_timer(pygame.USEREVENT, self.delay)

        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    exit()
                elif event.type == pygame.KEYDOWN:
                    self.pressed = True
                    if event.key == pygame.K_ESCAPE:
                        self.pause = not self.pause
                    if self.pause:
                        continue
                    if event.key in [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT,
                                     pygame.K_w, pygame.K_s, pygame.K_a, pygame.K_d]:
                        self.board.erase(self.current_shape, self.current_row, self.current_col)

                    if event.key in [pygame.K_UP, pygame.K_w]:
                        if self.board.movep(self.current_shape, self.current_row, self.current_col, UP):
                            self.current_col += self.board.adjust_pos(self.current_shape, self.current_col)
                            self.current_shape = self.current_shape.next
                            self.blit()
                            self.display_board()
                    elif event.key in [pygame.K_DOWN, pygame.K_s]:
                        if self.board.movep(self.current_shape, self.current_row, self.current_col, DOWN):
                            self.current_row += 1
                        else:
                            self.process_to_next()
                    elif event.key in [pygame.K_LEFT, pygame.K_a]:
                        if self.board.movep(self.current_shape, self.current_row, self.current_col, LEFT):
                            self.current_col -= 1
                    elif event.key in [pygame.K_RIGHT, pygame.K_d]:
                        if self.board.movep(self.current_shape, self.current_row, self.current_col, RIGHT):
                            self.current_col += 1

                    self.blit()
                elif event.type == pygame.KEYUP:
                    self.pressed = False
                elif self.pause:
                    continue

                elif event.type == pygame.USEREVENT:
                    self.board.erase(self.current_shape, self.current_row, self.current_col)
                    if self.board.movep(self.current_shape, self.current_row, self.current_col, DOWN):
                        self.current_row += 1
                        self.blit()
                    else:
                        self.process_to_next()

            self.screen.fill(settings.BG_COLOR)
            self.display()
            self.display_right_frame()
            pygame.display.flip()
            self.clock.tick(settings.FPS)