智障儿童欢乐多,蹦蹦哒哒过六一:用Python开发连连看小游戏

Posted 盛夏温暖流年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智障儿童欢乐多,蹦蹦哒哒过六一:用Python开发连连看小游戏相关的知识,希望对你有一定的参考价值。

我一直相信,使生活如此美丽的,是我们藏起来的真诚和童心。

六一节快到了,520可以不过,但是儿童节绝对不能错过,毕竟谁还不是个几百个月的宝宝了,而且凭借自身智商,过这个节完全不成问题,哈哈哈哈哈。

作为程序媛,当然要用代码来过节啦,那就一起用 Python 开发个连连看小游戏来欢度六一吧!

从零造轮子那是不可能的,毕竟 Python 完全零基础,所以还是采取参考别人的例子的方式,从理解分析外加改造的过程中,来学习新的知识~

先来看一下最后的效果:

实现步骤如下:

一. 安装依赖包

安装 pygame 依赖包

python -m pip install pygame

二. 代码流程分析

导入需要的依赖

import sys, time
import math, random
import threading
import pygame
from pygame.locals import *

 声明全局的设置类,并实例化设置对象

# 全局的设置类
class Settings:
    # __init__方法负责对象的初始化
    def __init__(self):
        # 定义游戏窗口大小(800*640)
        self.screen_size = (self.screen_width, self.screen_height) = (800, 640)
        # 游戏中的块数排列(8行*10列)
        self.game_size = (self.game_row, self.game_col) = (8, 10)
        # 连连看的总块数
        self.map_total = self.game_row * self.game_col
        # 定义元素个数(共16种图片)
        self.element_num = 16
        self.bg_color = (220, 220, 220)
        self.title = '智障儿童连连看'
        self.win_image = './images/win.png'
        self.lose_image = './images/lose.png'
        self.grid_size = 80
        self.scale_size = (76, 76)
        self.points = []


# 实例化设置对象
settings = Settings()

声明全局变量

# 图像列表映射
map_list = []
#global map_list
image_list = []
# 点击右上角直接结束游戏标志位
is_exit = False
# 时间截止标志位
time_out = False;

 声明图片按钮类,即每一个消除的小方块

# 图片按钮类
class ImageBtn:

    __checkable = True
    __checked = False

    def __init__(self, screen, image_path, x, y, number, element):
        self.x = x
        self.y = y
        # 元素标号(1-16)
        self.element = element
        self.number = number
        self.screen = screen
        # 图片原始大小为 200x200,所以要进行缩放
        self.image = pygame.transform.scale(pygame.image.load(image_path), settings.scale_size)

    def __del__(self):
        pass

    def display(self):
        # 描边
        if self.__checked:
            pygame.draw.rect(self.image, (0,205,205,255), (0,0,self.image.get_width()-1,self.image.get_height()-1), 2)
        self.screen.blit(self.image, (self.x, self.y))

    def hide(self):
        self.__checked = False
        self.__checkable = False
        # 图片不可见
        self.image.fill((255, 255,240))

    def is_checkable(self):
        return self.__checkable

    def click(self):
        self.__checked = not self.__checked
        return self.__checked

    def reset(self):
        self.__checked = False

    def get_geometry(self):
        return (int(self.x), int(self.y), settings.scale_size[0], settings.scale_size[1])

 定义最核心的 水平扫描方法 horizontal_scan 和 垂直扫描方法 vertical_scan

# 水平扫描
def horizontal_scan(points):
    """
    水平标定
    以 p1 和 p2 所在的两个行作为基准线,然后用垂直线在有效范围内扫描,
    一旦发现可行路径,直接返回。
    hLine1    和 hLine2     分别是扫描的上边线和下边线
    leftLimit 和 rightLimit 分别是扫描的左边线和右边线
    """
    # 设置的列数为10
    column = settings.game_col
    p1_x = int(points[0].number % column)
    p1_y = int(points[0].number / column)
    p2_x = int(points[1].number % column)
    p2_y = int(points[1].number / column)

    # 如果 p1 和 p2 在同一行,则不符合要求
    if p1_y == p2_y:
        return False

    # 记录两条水平基准线
    # hLine1 为上水平线,hLine2 为下水平线
    if p1_y < p2_y:
        hLine1 = p1_y
        hLine2 = p2_y
    else:
        hLine1 = p2_y
        hLine2 = p1_y

    # 初始化左、右边界线为 0
    leftLimit = 0;
    rightLimit = column-1
    # 寻找左边界线
    i = p1_x
    # 第一次扫描
    # 当 i 大于 0 时,才进入循环
    while i > 0:
        # 判断左边点是否为空
        if map_list[p1_y * column+ i - 1] != 0:
            break
        # 当左边点为空时会继续扫描下一个左边点
        i -= 1
    leftLimit = i;
    # 第二次扫描
    i = p2_x
    while i > 0:
        if map_list[p2_y * column + i - 1] != 0:
            break
        i -= 1
    # leftLimit 记录左边界线,该界线所在的点为空或p1、p2本身
    if i > leftLimit:
        leftLimit = i
    # 如果 leftLimit 为 0,说明p1、p2已经在外界接通了,直接返回
    if leftLimit == 0:
        return True
    # 寻找右边界线
    i = p1_x
    while i < column-1:
        if map_list[p1_y * column + i + 1] != 0:
            break;
        i += 1
    rightLimit = i
    i = p2_x
    while i < column-1:
        if map_list[p2_y * column + i + 1] != 0:
            break
        i += 1

    if i < rightLimit:
        rightLimit = i

    if rightLimit == column-1:
        return True    # Bug

    # 判断 leftLimit 和 rightLimit
    if leftLimit > rightLimit:
        # 如果左边界线超出右边界线,则无法连接
        return False
    else:
        # 从左往右扫描
        for i in range(leftLimit, rightLimit+1):
            #print("从左往右扫描:%d -> %d"%(i, rightLimit))
            j = hLine1 + 1
            for j in range(hLine1+1, hLine2):
                # 只要当前列有阻碍,马上跳出
                if map_list[j * column + i] != 0:
                    # 回退一行
                    #j -= 1
                    break
                j += 1
            if j == hLine2:
                # 水平扫描成功
                return True
        return False;

# 垂直扫描
def vertical_scan(points):
    """
    垂直标定
    以 p1 和 p2 所在的两个列作为基准线,然后用水平线在有效范围内扫描,
    一旦发现可行路径,直接返回。
    """
    row = settings.game_row
    column = settings.game_col
    p1_x = int(points[0].number % column)
    p1_y = int(points[0].number / column)
    p2_x = int(points[1].number % column)
    p2_y = int(points[1].number / column)

    # 如果 p1 和 p2 在同一列,则不符合要求
    if p1_x == p2_x:
        return False
    # 记录两条垂直基准线
    if p1_x < p2_x:
        vLine1 = p1_x # 左垂直线
        vLine2 = p2_x # 右垂直线
    else:
        vLine1 = p2_x
        vLine2 = p1_x

    # 初始化上、下边界线
    topLimit = 0
    bottomLimit = row-1

    # 寻找上边界线
    i = p1_y
    # 第一次扫描
    # 当 i 大于 0 时,才进入循环
    while i > 0:
        if map_list[p1_x + (i-1) * column] != 0:
            break # 判断上边点是否为空
        i -= 1 # 当上边点为空时会继续扫面下一个上边点
    topLimit = i
    # 第二次扫描
    i = p2_y
    while i > 0:
        if map_list[p2_x + (i-1) * column] != 0:
            break
        i -= 1
    # topLimit 记录上边界线,该界线所在的点为空或p1、p2本身
    if i > topLimit:
        topLimit = i

    # 如果 topLimit 为 0,说明p1、p2已经在外界接通了,直接返回
    if topLimit == 0:
        return True

    # 寻找下边界线
    i = p1_y
    while i < row-1:
        if map_list[p1_x + (i+1) * column] != 0:
            break
        i += 1
    bottomLimit = i
    i = p2_y
    while i < row-1:
        if map_list[p2_x + (i+1) * column] != 0:
            break
        i += 1

    if i < bottomLimit:
        bottomLimit = i

    if bottomLimit == row-1:
        return True

    # 判断 topLimit 和 bottomLimit
    if topLimit > bottomLimit:
        # 如果上边界线超出下边界线,则无法连接
        return False
    else:
        # 从上往下扫描
        for i in range(topLimit, bottomLimit+1):
            j = vLine1 + 1
            for j in range(vLine1+1, vLine2):
                # 只要当前行有阻碍,马上跳出
                if map_list[i * column + j] != 0:
                    # 回退一列
                    #j -= 1
                    break
                j += 1
            if j == vLine2:
                # 垂直扫描成功
                return True
        return False

定义其他操作方法,包括 判断是否能消除事件处理(结束事件和点击事件)构建游戏图片布局检查所有图片是否全部消除游戏倒计时实现 共5个方法。

# 判断能否消除
def can_clear(points):
    # 如果两个元素不相同,还连个屁
    if points[0].element != points[1].element:
        return False
    else:
        # 如果两个元素垂直可以相连或者水平可以相连
        if vertical_scan(points) or horizontal_scan(points):
            return True
        else:
            return False

# 事件处理(结束事件,按钮点击事件)
def check_event(btn_list):
    """
        Key event capture and key_control
    """
    for event in pygame.event.get():
        if event.type == QUIT:
            global is_exit
            is_exit = True
            #print("exit...")
            sys.exit()
        # 判断是否按下按钮
        if event.type == MOUSEBUTTONDOWN:
            pos = pygame.mouse.get_pos()
            #print("Mouse button down, position:", pos)

            for btn in btn_list:
                geo = btn.get_geometry()
                x = geo[0]; y = geo[1]; w = geo[2]; h = geo[3]
                # 判断是否在图片块范围内
                if pos[0] > x and pos[0] < x+w and pos[1] > y and pos[1] < y+h:
                    #print("在图片块范围内:", id(btn))
                    # 如果图片还没被消除
                    if btn.is_checkable():

                        if not btn.click():
                            # 点自己是无效的
                            settings.points.clear()
                            break

                        # 前面已经记录了一个点
                        if settings.points != []:
                            settings.points.append(btn)
                            # 检查是否相同、能否相连
                            if can_clear(settings.points):
                                # 消灭它们
                                for point in settings.points:
                                    map_list[point.number] = 0
                                    point.number = 0
                                    point.hide()
                            else:
                                # 不匹配,恢复图片状态
                                for point in settings.points:
                                    point.reset()
                            # 判断完毕,清除记录的点
                            settings.points.clear()

                        # 前面没有记录点
                        else:
                            # 记录第一个点
                            settings.points.append(btn)
                    # 如果图片已经被消除
                    break


# 构建游戏图片布局
def build_map():
    t_list = []
    m_list = []
    # 成对数据构建
    for i in range(0, settings.map_total, 2):
        # 随机生成成对的图片元素标号(1-16),存放于 tmp_list
        e = math.ceil(random.random()*settings.element_num)
        # double append
        t_list.append(e)
        t_list.append(e)
    # 对全部数据进行打乱
    for i in range(0, settings.map_total, 1):
        # 将 tmp_list 中的图片元素随机排列在 m_list
        index = int(random.random()*(settings.map_total-i))
        m_list.append(t_list[index])
        # 删除已保存到m_list中的元素
        t_list.pop(index)
    return m_list


# 检查是否全部消除
def is_over():
    for each in map_list:
        if each > 0:
            return False
    return True


# 倒计时实现(默认120秒,超时则判断为游戏失败)
def game_clock():
    clock_ = 120
    while clock_>0:
        global is_exit
        if is_exit:
            break
        clock_ = clock_ - 1
        time.sleep(1)
        print("倒计时还剩:", clock_)
    print("时间停止,游戏结束啦!")
    global time_out
    time_out = True;

定义主函数调用 

def main():
    # 初始化 Pygame
    pygame.init()
    # 创建一个游戏窗口
    screen = pygame.display.set_mode(settings.screen_size, 0, 0)
    # 设置窗口标题
    pygame.display.set_caption(settings.title)
    # 设置倒计时计时器
    t = threading.Thread(target=game_clock,args=());
    t.start();
    
    # 准备图片元素
    global map_list
    map_list = build_map()

    # 创建图片列表
    for i in range(0, settings.map_total):
        x = int(i%settings.game_col) * settings.grid_size + (settings.grid_size -settings.scale_size[0])/2
        y = int(i/settings.game_col) * settings.grid_size + (settings.grid_size -settings.scale_size[0])/2
        element = './images/element_'+str(map_list[i])+'.png'
        image_list.append(ImageBtn(screen, element, x, y, i, map_list[i]))

    play = True
    i = 0
    
    while True:
        # 背景颜色填充
        screen.fill(settings.bg_color)
        # 如果倒计时结束,提示游戏输了
        if time_out:
            youlose = pygame.image.load(settings.lose_image)
            screen.blit(youlose, ((settings.screen_width-youlose.get_width())/2,(settings.screen_height-youlose.get_height())/2))
        elif play:
            if is_over():
                play = False
            for im in image_list:
                im.display()
        else:
            youwin = pygame.image.load(settings.win_image)
            screen.blit(youwin, ((settings.screen_width-youwin.get_width())/2,(settings.screen_height-youwin.get_height())/2))
        pygame.display.update()
        
        # 检查按键事件
        check_event(image_list)
        time.sleep(0.04)

这里有一个需要注意的地方是,我们的倒计时是需要单独开一个线程执行的,不然会阻塞主线程:

# 设置倒计时计时器
t = threading.Thread(target=game_clock,args=());
t.start();

完整代码路径如下(不要积分哈,放心下载):

https://download.csdn.net/download/j1231230/19285196

三. 总结和提升

通过对这个案例的学习和改造,我们大概了解了 pygame 的常用写法,来补充下基础知识吧。

1. pygame模块

模块名功能
pygame.cdrom访问光驱
pygame.cursors加载光标
pygame.display访问显示设备
pygame.draw绘制形状、线和点
pygame.event管理事件
pygame.font使用字体
pygame.image加载和存储图片
pygame.joystick使用游戏手柄或者 类似的东西
pygame.key读取键盘按键
pygame.mixer声音
pygame.mouse鼠标
pygame.movie播放视频
pygame.music播放音频
pygame.overlay访问高级视频叠加
pygame.rect管理矩形区域
pygame.sndarray操作声音数据
pygame.sprite操作移动图像
pygame.surface管理图像和屏幕
pygame.surfarray管理点阵图像数据
pygame.time管理时间和帧信息
pygame.transform缩放和移动图像

2. Hello World快速入手模板

#导入pygame库
import pygame
#导入一些常用的函数和常量
from pygame.locals import *
#向sys模块的exit函数用来退出程序
from sys import exit

# 指定图像文件名称
background_image_filename = 'back.jpg'
mouse_image_filename = 'center.jpg'

#初始化pygame,为使用硬件做准备
pygame.init()
#创建窗口
screen = pygame.display.set_mode((480, 480), 0, 32)
#设置窗口标题
pygame.display.set_caption("Hello, World!")
#加载并转换图像
background = pygame.image.load(background_image_filename).convert()
mouse_cursor = pygame.image.load(mouse_image_filename).convert_alpha()

#游戏主循环
while True:
    for event in pygame.event.get():
        #接收到退出事件后退出程序
        if event.type == QUIT:
            exit()
    #将背景图画上去
    screen.blit(background, (0,0))
    #获得鼠标位置
    x, y = pygame.mouse.get_pos()
    #计算光标的左上角位置
    x-= mouse_cursor.get_width() / 2
    y-= mouse_cursor.get_height() / 2
    #把光标画上去
    screen.blit(mouse_cursor, (x, y))
    #刷新一下画面
    pygame.display.update()

随便找两张图片,重命名为代码中的名字即可,需要放在代码同层路径下。


代码参考的博客原始路径如下,十分感谢:

https://blog.csdn.net/lu_embedded/article/details/86484603

 最后送给大家一句话:

希望你时常记得这世界的可爱,无惧岁月漫长,不只在大喜大悲时才感受到真实的存在。

祝所有大儿童们六一快乐。

以上是关于智障儿童欢乐多,蹦蹦哒哒过六一:用Python开发连连看小游戏的主要内容,如果未能解决你的问题,请参考以下文章

陪我家小朋友过六一!

逗比儿童,欢乐多

sandglass(沙漏)——一个让人解脱的python时间处理库

如何用Python欢度六一?

六一欢乐赛 考试总结

Comet OJ - 2019 六一欢乐赛