import os
import sys
import time
import random
import pygame


#如下是上一步新增的该项目所需变量
WIDTH = 600
HEIGHT = 600
NUMGRID = 8
GRIDSIZE = 64
XMARGIN = (WIDTH - GRIDSIZE * NUMGRID) // 2
YMARGIN = (HEIGHT - GRIDSIZE * NUMGRID) // 2
ROOTDIR = os.getcwd()
FPS = 30
#如上是上一步新增的该项目所需变量

#游戏界面初始化：
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('消消乐小游戏-大师码')

class gemSprite(pygame.sprite.Sprite):
    '''
    初始化函数：img_path=图片文件所在路径，size=整个界面大小，position=拼图块位置，downlen=消除拼图块后下降格数
    '''
    def __init__(self, img_path, size, position, downlen, **kwargs):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(img_path)
        self.image = pygame.transform.smoothscale(self.image, size)  # 改变加载的图片大小
        self.rect = self.image.get_rect()
        self.rect.left, self.rect.top = position
        self.downlen = downlen
        self.target_x = position[0]
        self.target_y = position[1] + downlen
        self.type = img_path.split('/')[-1].split('.')[0]
        self.fixed = False
        self.speed_x = 10
        self.speed_y = 10
        self.direction = 'down'

    def move(self):
        '''
        拼图块移动函数：主要通过鼠标控制拼图块交换方向，有上下左右四个方向
        '''
        if self.direction == 'down':
            self.rect.top = min(self.target_y, self.rect.top+self.speed_y)
            if self.target_y == self.rect.top:
                self.fixed = True
        elif self.direction == 'up':
            self.rect.top = max(self.target_y, self.rect.top-self.speed_y)
            if self.target_y == self.rect.top:
                self.fixed = True
        elif self.direction == 'left':
            self.rect.left = max(self.target_x, self.rect.left-self.speed_x)
            if self.target_x == self.rect.left:
                self.fixed = True
        elif self.direction == 'right':
            self.rect.left = min(self.target_x, self.rect.left+self.speed_x)
            if self.target_x == self.rect.left:
                self.fixed = True

    def getPosition(self):
        '''
        获取坐标函数:获取当前拼图块坐标，主要通过左（left）和上（top）两个方向定义坐标距离
        '''
        return self.rect.left, self.rect.top

    def setPosition(self, position):
        '''
        设置坐标函数：设置拼图块坐标
        '''
        self.rect.left, self.rect.top = position

class gemGame():
    def __init__(self, screen, sounds, font, gem_imgs, **kwargs):  # 初始化游戏类
        self.info = '大师码'
        self.screen = screen # 游戏窗口
        self.sounds = sounds # 音乐
        self.font = font # 字体
        self.gem_imgs = gem_imgs # 宝石图片
        self.reset()

    def isMatch(self):
        '''
        有无行列三个拼图块相同判断:是否有连续一样的三个块(无--返回0/水平--返回1/竖直--返回2)
        '''
        for x in range(NUMGRID):
            for y in range(NUMGRID):
                if x + 2 < NUMGRID:
                    if self.getGemByPos(x, y).type == self.getGemByPos(x+1, y).type == self.getGemByPos(x+2, y).type:  # 水平方向三个连续相同块
                        return [1, x, y]
                if y + 2 < NUMGRID:
                    if self.getGemByPos(x, y).type == self.getGemByPos(x, y+1).type == self.getGemByPos(x, y+2).type:   # 竖直方向三个连续相同块
                        return [2, x, y]
        return [0, x, y]


    def getGemByPos(self, x, y):
        '''
        根据坐标获取对应位置的拼图对象
        '''
        return self.all_gems[x][y]

    def reset(self):
        '''
        随机生成各个块(即初始化游戏地图各个元素)
        '''
        while True:
            self.all_gems = []
            self.gems_group = pygame.sprite.Group()  # 用到sprite模块的Group()函数方法
            for x in range(NUMGRID):
                self.all_gems.append([])
                for y in range(NUMGRID):
                    gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+x*GRIDSIZE, YMARGIN+y*GRIDSIZE-NUMGRID*GRIDSIZE], downlen=NUMGRID*GRIDSIZE)
                    # 每个gem就是每个宝石
                    self.all_gems[x].append(gem)
                    self.gems_group.add(gem)
            if self.isMatch()[0] == 0:
                break
        # 得分
        self.score = 0
        # 拼出一个的奖励
        self.reward = 10
        # 时间
        self.remaining_time = 300

    def start(self):
        '''
        开始游戏函数：游戏开始主循环
        '''
        clock = pygame.time.Clock()
        # 遍历整个游戏界面更新位置
        overall_moving = True
        # 指定某些对象个体更新位置
        individual_moving = False
        # 定义一些必要的变量
        gem_selected_xy = None
        gem_selected_xy2 = None
        swap_again = False
        add_score = 0
        add_score_showtimes = 10
        time_pre = int(time.time())
        # 游戏主循环
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
                    pygame.quit()
                    sys.exit()

                elif event.type == pygame.MOUSEBUTTONUP:
                    if (not overall_moving) and (not individual_moving) and (not add_score):
                        position = pygame.mouse.get_pos()  # 获取当前鼠标的位置
                        if gem_selected_xy is None:  # 没有选中
                            gem_selected_xy = self.checkSelected(position)
                        else:  # 选中了第一个宝石，则继续选择第二个宝石
                            gem_selected_xy2 = self.checkSelected(position)
                            if gem_selected_xy2:  # 选中第二个宝石
                                if self.swapGem(gem_selected_xy, gem_selected_xy2):  # 交换两个被选中的宝石
                                    individual_moving = True
                                    swap_again = False
                                else:
                                    gem_selected_xy = None

            if overall_moving:  # 遍历整个游戏界面更新位置
                overall_moving = not self.dropGems(0, 0)
                # 移动一次可能可以拼出多个3连块
                if not overall_moving:
                    res_match = self.isMatch()
                    add_score = self.removeMatched(res_match)
                    if add_score > 0:
                        overall_moving = True

            if individual_moving:  # 更新特定指定的宝石位置
                gem1 = self.getGemByPos(*gem_selected_xy)
                gem2 = self.getGemByPos(*gem_selected_xy2)
                gem1.move()
                gem2.move()
                if gem1.fixed and gem2.fixed:
                    res_match = self.isMatch()
                    if res_match[0] == 0 and not swap_again:
                        swap_again = True
                        self.swapGem(gem_selected_xy, gem_selected_xy2)
                        self.sounds['mismatch'].play()  # 播放匹配失败音效
                    else:
                        add_score = self.removeMatched(res_match)  # 增加得分
                        overall_moving = True
                        individual_moving = False
                        gem_selected_xy = None
                        gem_selected_xy2 = None
            self.screen.fill((135, 206, 235))
            if gem_selected_xy:
                self.drawBlock(self.getGemByPos(*gem_selected_xy).rect)
            self.gems_group.draw(self.screen)

            self.drawGrids()  # 绘出游戏网格
            if add_score:
                if add_score_showtimes == 10:
                    random.choice(self.sounds['match']).play()  # 随机选中匹配成功的音效
                self.drawAddScore(add_score)
                add_score_showtimes -= 1
                if add_score_showtimes < 1:
                    add_score_showtimes = 10
                    add_score = 0
            self.remaining_time -= (int(time.time()) - time_pre)  # 剩余时间
            time_pre = int(time.time())

            self.showRemainingTime()
            self.drawScore()
            if self.remaining_time <= 0:  # 时间结束，返回得分
                return self.score
            pygame.display.update()
            clock.tick(FPS)

    def drawBlock(self, block, color=(255, 0, 255), size=4):
        '''
        画矩形block框
        '''
        pygame.draw.rect(self.screen, color, block, size)

    def drawGrids(self):
        '''
        游戏界面的网格绘制
        '''
        for x in range(NUMGRID):
            for y in range(NUMGRID):
                rect = pygame.Rect((XMARGIN + x * GRIDSIZE, YMARGIN + y * GRIDSIZE, GRIDSIZE, GRIDSIZE))
                self.drawBlock(rect, color=(0, 0, 255), size=1)

    def showRemainingTime(self):
        '''
        显示游戏倒计时时间函数
        '''
        remaining_time_render = self.font.render('CountDown: %ss' % str(self.remaining_time), 1, (85, 65, 0))
        rect = remaining_time_render.get_rect()
        rect.left, rect.top = (WIDTH - 201, 6)
        self.screen.blit(remaining_time_render, rect)

    def drawScore(self):
        '''
        显示得分函数
        '''
        score_render = self.font.render('SCORE:' + str(self.score), 1, (85, 65, 0))
        rect = score_render.get_rect()
        rect.left, rect.top = (10, 6)
        self.screen.blit(score_render, rect)

    def removeMatched(self, res_match):
        '''
        消除匹配成功的拼图块：行或列三个一样的拼图块出现即消除
        '''
        if res_match[0] > 0:  # 大于0说明出现行或列有三个一样的块
            self.generateNewGems(res_match)  # 将res_match列表信息传给生成函数
            self.score += self.reward  # 匹配成功增加得分
            return self.reward
        return 0

    def dropGems(self, x, y):
        '''
        新的拼图块出现下落特效
        '''
        if not self.getGemByPos(x, y).fixed:
            self.getGemByPos(x, y).move()
        if x < NUMGRID-1:
            x += 1
            return self.dropGems(x, y)
        elif y < NUMGRID-1:
            x = 0
            y += 1
            return self.dropGems(x, y)
        else:
            return self.isFull()

    def isFull(self):
        '''
        是否每个位置都有拼图块了
        '''
        for x in range(NUMGRID):
            for y in range(NUMGRID):
                if not self.getGemByPos(x, y).fixed:
                    return False
        return True

    def checkSelected(self, position):
        '''
        检查有无拼图块被选中
        '''
        for x in range(NUMGRID):
            for y in range(NUMGRID):
                if self.getGemByPos(x, y).rect.collidepoint(*position):  # 宝石与当前鼠标位置是否接触，也即是否被选中。
                    return [x, y]
        return None

    def swapGem(self, gem1_pos, gem2_pos):
        '''
        交换拼图函数
        '''
        margin = gem1_pos[0] - gem2_pos[0] + gem1_pos[1] - gem2_pos[1]
        if abs(margin) != 1:  # 两个块位置不是相邻
            return False
        gem1 = self.getGemByPos(*gem1_pos)
        gem2 = self.getGemByPos(*gem2_pos)
        if gem1_pos[0] - gem2_pos[0] == 1:  # 宝石1在宝石2的右边，则交换时宝石1需要往左，宝石2需要往右
            gem1.direction = 'left'
            gem2.direction = 'right'
        elif gem1_pos[0] - gem2_pos[0] == -1:  # 宝石1在宝石2的左边，则交换时宝石1需要往右，宝石2需要往左
            gem2.direction = 'left'
            gem1.direction = 'right'
        elif gem1_pos[1] - gem2_pos[1] == 1:  # 宝石1在宝石2的下边，则交换时宝石1需要往上，宝石2需要往下
            gem1.direction = 'up'
            gem2.direction = 'down'
        elif gem1_pos[1] - gem2_pos[1] == -1:  # 宝石1在宝石2的上边，则交换时宝石1需要往下，宝石2需要往上
            gem2.direction = 'up'
            gem1.direction = 'down'
        gem1.target_x = gem2.rect.left
        gem1.target_y = gem2.rect.top
        gem1.fixed = False
        gem2.target_x = gem1.rect.left
        gem2.target_y = gem1.rect.top
        gem2.fixed = False
        self.all_gems[gem2_pos[0]][gem2_pos[1]] = gem1  # 将宝石1交换到原本宝石2的位置
        self.all_gems[gem1_pos[0]][gem1_pos[1]] = gem2  # 将宝石2交换到原本宝石1的位置
        return True

    def generateNewGems(self, res_match):
        '''
        新拼图块生成函数
        '''
        if res_match[0] == 1: # 需要用到isMatch()函数，返回1表示水平方向有三个连续一样的宝石
            start = res_match[2]
            while start > -2:
                for each in [res_match[1], res_match[1]+1, res_match[1]+2]:  # 水平方向匹配成功后，需要从第一个位置往后三列都需要生成新的宝石
                    gem = self.getGemByPos(*[each, start])
                    if start == res_match[2]:
                        self.gems_group.remove(gem)
                        self.all_gems[each][start] = None  # 当前位置设置为空
                    elif start >= 0:
                        gem.target_y += GRIDSIZE
                        gem.fixed = False
                        gem.direction = 'down'
                        self.all_gems[each][start+1] = gem  # 边框内匹配位置上面的三列宝石块全部下移
                    else:  # 在边框外生成新的宝石块然后下移一个位置
                        gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+each*GRIDSIZE, YMARGIN-GRIDSIZE], downlen=GRIDSIZE)
                        self.gems_group.add(gem)
                        self.all_gems[each][start+1] = gem
                start -= 1
        elif res_match[0] == 2: # 返回2表示竖直方向有三个连续一样的宝石
            start = res_match[2]
            while start > -4:
                if start == res_match[2]:
                    for each in range(0, 3):    # 竖直方向匹配后，只需要在一列中生成3个新的宝石
                        gem = self.getGemByPos(*[res_match[1], start+each])
                        self.gems_group.remove(gem)
                        self.all_gems[res_match[1]][start+each] = None  #  消除当前宝石
                elif start >= 0:
                    gem = self.getGemByPos(*[res_match[1], start])
                    gem.target_y += GRIDSIZE * 3
                    gem.fixed = False
                    gem.direction = 'down'
                    self.all_gems[res_match[1]][start+3] = gem   #  边框内宝石快右移三个位置
                else:
                    gem = gemSprite(img_path=random.choice(self.gem_imgs), size=(GRIDSIZE, GRIDSIZE), position=[XMARGIN+res_match[1]*GRIDSIZE, YMARGIN+start*GRIDSIZE], downlen=GRIDSIZE*3)
                    self.gems_group.add(gem)
                    self.all_gems[res_match[1]][start+3] = gem # 在边框外生成新的宝石，下移三个位置
                start -= 1

    def drawAddScore(self, add_score):
        '''
        分数计算函数
        '''
        score_render = self.font.render('+'+str(add_score), 1, (255, 100, 100))
        rect = score_render.get_rect()
        rect.left, rect.top = (250, 250)
        self.screen.blit(score_render, rect)


def main():

    # 加载背景音乐
    pygame.mixer.init()
    pygame.mixer.music.load(os.path.join(ROOTDIR, "resources/audios/bg.mp3"))
    pygame.mixer.music.set_volume(0.6)
    pygame.mixer.music.play(-1)

    # 加载音效，分别匹配成功和匹配失败
    sounds = {}
    sounds['mismatch'] = pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/badswap.wav'))
    sounds['match'] = []
    for i in range(6):  # 匹配成功一共有六个音效
        sounds['match'].append(pygame.mixer.Sound(os.path.join(ROOTDIR, 'resources/audios/match%s.wav' % i)))
    # 加载字体
    font = pygame.font.Font(os.path.join(ROOTDIR, 'resources/font.TTF'), 25)
    # 图片加载
    gem_imgs = []
    for i in range(1, 8):  # 一共七种宝石
        gem_imgs.append(os.path.join(ROOTDIR, 'resources/images/gem%s.png' % i))

    game = gemGame(screen, sounds, font, gem_imgs)
    while True:
        # score = 0
        score = game.start() # 后续打开该函数
        flag = False
        # 一轮游戏结束后玩家选择重玩或者退出
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
                    pygame.quit()
                    sys.exit()
                elif event.type == pygame.KEYUP and event.key == pygame.K_r: # r或者keyup则按住就重新开始游戏
                    flag = True
            if flag:
                break
            screen.fill((135, 206, 235))
            text0 = 'Final score: %s' % score
            text1 = 'Press r to restart the game.'
            text2 = 'Press esc to quit the game.'
            y = 150
            # enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列，同时列出数据和数据下标
            for idx, text in enumerate([text0, text1, text2]): # 该模块是针对三组文本置放在对应位置
                text_render = font.render(text, 1, (85, 65, 0))
                rect = text_render.get_rect()
                if idx == 0:
                    rect.left, rect.top = (212, y)
                elif idx == 1:
                    rect.left, rect.top = (122.5, y)
                else:
                    rect.left, rect.top = (126.5, y)
                y += 100
                screen.blit(text_render, rect)  # 绘出文本框
            pygame.display.update() # 这部分就是每次循环都要更新屏幕，这样能保持一直状态最新
        game.reset() # 后续打开该函数



if __name__ == '__main__':
    main()


if __name__ == '__main__':
    main()