pygame 欢乐五子棋(二)

作者: lovetianyats | 来源:发表于2017-08-16 21:54 被阅读648次

    上期,我们做好了游戏的棋盘,本期我们处理如何落子,及设计我们的AI
    废话少说,先看看如何落子

    落子

    落子的过程大概为

    1. 获取鼠标的位置
    2. 计算位置所对应的落子点
    3. 画出棋子

    看一下主函数中的流程

    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.MOUSEBUTTONDOWN:
        # 1.有鼠标点击事件发生了,我们获取鼠标的位置
    
        # 2.计算鼠标点击所在网格点的位置
    
        # 3.添加棋子
    
    1. 获取鼠标位置
      获取鼠标的位置很简单,pygame为我们做好了各种事件的检测及记录,我们只需要看有没有鼠标落下事件的发生,然后获取位置就好了
      好了,我们看如何获取鼠标的位置,只有一个语句就好了
    pos = event.pos
    
    1. 计算网格点的位置
      计算网格点的位置也很简单,只要用坐标值除以网格的宽度就好了,这里有一点需要注意,就是我们不可能每次都点击到网格点上,因此需要有一个四舍五入的过程,就是我们点击的位置距离哪个点近,我们就默认用户点击了哪一个点。
    grid = (int(round(event.pos[0] / (GRID_WIDTH + .0))),
            int(round(event.pos[1] / (GRID_WIDTH + .0))))
    
    1. 添加棋子
      要画出棋子,我们必须记录下我们走的每一步棋,并且在刷新屏幕的时候将这些棋子全部画出来。我们定义一个全局变量movements用于我们的每一步棋。然后,每次落子之后,我们就将落子信息存储在里面。
    movements = []
    def add_coin(screen, pos, color):
        movements.append(((pos[0] * GRID_WIDTH, pos[1] * GRID_WIDTH), color))
        pygame.draw.circle(screen, color,
            (pos[0] * GRID_WIDTH, pos[1] * GRID_WIDTH), 16)
    

    再定义一个画出每一步棋的函数

    def draw_movements(screen):
        for m ini movements:
            # m[0] 存的是位置,m[1]存的是颜色
            pygame.draw.circle(screen, m[1], m[0], 16)
    

    好了,我们在添加棋子的注释下面添加一句

    add_coin(screen, pos, BLACK)
    

    在刷新屏幕之前调用画出棋子的函数

    draw_movements(screen)
    

    好了,我们运行一下,在屏幕中画一个五字~~

    wu.png

    OK,落子顺利完成,
    下一步就是我们游戏的核心了,我们要写我们自己的AI~~

    五子棋 AI

    在我们开发我们的AI之前我们还有一个事情要做

    判断游戏结束

    如何判断游戏结束?只要有五个子连成线就好了!对的,每次落子的时候我们只要判断所落的子周围有没有统一颜色的棋子可以连成五子的,因此我们需要一个矩阵记录每个位置子的颜色~

    color_metrix = [[None] * 20 for i in range(20)]
    

    此时,我们就可以定义我们的判断游戏结束的函数了,函数的逻辑很简单,只要判断所落子的周围是否有五子连成线一共有四个方向

    def game_is_over(pos, color):
        hori = 1
        verti = 1
        slash = 1
        backslash = 1
        left = pos[0] - 1
        while left > 0 and color_metrix[left][pos[1]] == color:
            left -= 1
            hori += 1
    
        right = pos[0] + 1
        while right < 20 and color_metrix[right][pos[1]] == color:
            right += 1
            hori += 1
    
        up = pos[1] - 1
        while up > 0 and color_metrix[pos[0]][up] == color:
            up -= 1
            verti += 1
    
        down = pos[1] + 1
        while down < 20 and color_metrix[pos[0]][down] == color:
            down += 1
            verti += 1
    
        left = pos[0] - 1
        up = pos[1] - 1
        while left > 0 and up > 0 and color_metrix[left][up] == color:
            left -= 1
            up -= 1
            backslash += 1
    
        right = pos[0] + 1
        down = pos[1] + 1
        while right < 20 and down < 20 and color_metrix[right][down] == color:
            right += 1
            down += 1
            backslash += 1
    
        right = pos[0] + 1
        up = pos[1] - 1
        while right < 20 and up > 0 and color_metrix[right][up] == color:
            right += 1
            up -= 1
            slash += 1
    
        left = pos[0] - 1
        down = pos[1] + 1
        while left > 0 and down < 20 and color_metrix[left][down] == color:
            left -= 1
            down += 1
            slash += 1
    
        if max([hori, verti, backslash, slash]) == 5:
            return True
    
    

    好的在我们画出棋子之后加入游戏结束的判断

    if game_is_over(grid, BLACK):
        running = False
    

    对了记得在添加棋子的函数里将改变我们color_metrix的语句加进去。这样只要我们有五个同色子连成线游戏就结束了!
    先前我们是直接在主循环中处理事件的这样不好,我们提取出一个函数来用于处理用户走子,及AI的响应函数接口像这样:

    def move(surf, pos):
        '''
        Args:
            surf: 我们的屏幕
            pos: 用户落子的位置
        Returns a tuple or None:
            None: if move is invalid else return a
            tuple (bool, player):
                bool: True is game is not over else False
                player: winner (USER or AI)
        '''
    

    这个函数首先判断落子的位置是否已近有子,有的话返回None, 否则落子为合法的,我们调用add_coin, 最后我们调用respond函数
    这个函数是我们用来运行我们的AI,并决定下一步动作的,返回值和我们的move一样,这样,我们的move就变成了下面这样

    def move(surf, pos):
        '''
        Args:
            surf: 我们的屏幕
            pos: 用户落子的位置
        Returns a tuple or None:
            None: if move is invalid else return a
            tuple (bool, player):
                bool: True is game is not over else False
                player: winner (USER or AI)
        '''
        grid = (int(round(pos[0] / (GRID_WIDTH + .0))),
                int(round(pos[1] / (GRID_WIDTH + .0))))
    
        if grid[0] <= 0 or grid[0] > 19:
            return
        if grid[1] <= 0 or grid[1] > 19:
            return
    
        pos = (grid[0] * GRID_WIDTH, grid[1] * GRID_WIDTH)
    
        # num_pos = gridpos_2_num(grid)
        # if num_pos not in remain:
        #     return None
        if color_metrix[grid[0]][grid[1]] is not None:
            return None
    
        curr_move = (pos, BLACK)
        add_coin(surf, BLACK, grid, USER)
    
        if game_is_over(grid, BLACK):
            return (False, USER)
    
        return respond(surf, movements, curr_move)
    

    这里我们给add_coin 添加了一个参数,就是当前落子的角色,其中

    USER, AI = 1, 0
    

    我们用随机落子代替我们的AI

    def respond(surf, movements, curr_move):
        # 测试用,随机落子
    
        grid_pos = (random.randint(1, 19), random.randint(1, 19))
        # print(grid_pos)
        add_coin(surf, WHITE, grid_pos, 16)
        if game_is_over(grid_pos, WHITE):
            return (False, AI)
    
        return None
    

    看一下效果~

    屏幕快照_2017-08-16_下午8.50.16.png

    好了~ 我们的五子棋完成了!

    什么?? 不满意?

    好吧,AI确实比较麻烦,这里我给大家介绍一下思路,完整的代码大家可以到我的github上去下载,下载链接在文章的最后~

    AI思路

    五子棋的规则大家都懂,没下一个棋子都对周围的区域会产生影响,我们的AI可以记录每个位置的价值,每次下棋的时候可以选择得分最高的那个位置,具体操作我们可以这样,连成子的数目越多,价值越高,如果可以在多个方向上连成子,我们可以将价值相应的增加倍数,同时在对方有三个子连成线(独立的,即两边没有其他子)的时候必须堵住对方。大概就是这样的。
    具体实现我们就不讲解了,看一下最终效果


    final.png

    我试了一下,和AI下我基本上有输的,也有赢的。可能是我的水平比较差,大家可以写自己的AI或者在我的AI基础上再改进!
    完整的AI代码可以去我的 github 看,点击这里进入GitHub。
    如果这篇文章对您有帮助,赞赏一下吧~
    您的支持是我继续创作的动力~~~

    相关文章

      网友评论

        本文标题:pygame 欢乐五子棋(二)

        本文链接:https://www.haomeiwen.com/subject/grywrxtx.html