找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 138|回复: 0

[教程] 扫雷迷你游戏代码讲解/教程【LLFs-OSRMGP内容扩展-1】

[复制链接]
发表于 2024-10-6 21:10:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
本帖最后由 烈林凤 于 2024-10-6 21:34 编辑

## 该项目来源于LLFs-OSRMGP(https://github.com/llfseik/LLFs-OSRMGP),遵循MIT协议。
## 严禁进行售卖!若需嵌入付费项目请与我联系!


这是本仓库的第一次内容扩展,也是本仓库的第一篇教程,感谢各位的支持!
仓库内的所有教程都会尽量保证详细准确,请尽量将代码、注释和教程放在一起使用!
renpy程序糕手们可能会觉得很多地方有些繁琐,那可以只看代码和注释的部分进行学习,谢谢配合!
若教程中有什么错误的地方,欢迎留言指出!
各种文档、仓库或其他网站的链接我都放在文字中了,点击蓝色文字即可跳转。


先展示完整代码——
[RenPy] 纯文本查看 复制代码
## 该项目来源于LLFs-OSRMGP([url]https://github.com/llfseik/LLFs-OSRMGP[/url]),遵循MIT协议。
## 严禁进行售卖!若需嵌入付费项目请与我联系!

label minesweeper_label:
    $ quick_menu = False
    ## 四个参数分别为:行数、列数、雷数
    call screen minesweeper_screen(10,10,10)

## 图标样式
style box_style:
    color "#ffffff"
    size 30
    xalign 0.5
    yalign 0.5

init python:
    import random

    ## 格子大小
    box_xysize = 50
    ## 行数、列数、所有格子的坐标、雷数、雷的坐标、每个坐标对应周围雷的数量
    box_rows = 10
    box_cols = 10
    all_box_list = []
    num_mines = 10
    mine_tag_list = []
    box_around_mines = {}
    ## 判断按键是否按下、格子是否被标记
    K_q = False
    marked_box_list = []
    ## 输赢、游戏状态
    win_or_lose = False
    game_over = None

    ## 初始化游戏参数
    def init_game(cols=10,rows=10,mines=10):

        ## 清空已经打开格子和标记
        box_around_mines.clear()
        marked_box_list.clear()
        
        if rows < 1 or cols < 1 or mines < 1 or mines > rows*cols:
            ## 检测参数是否合法,如果不合法则初始化为默认参数
            box_rows = 10
            box_cols = 10
            num_mines = 10
            renpy.notify("参数错误,已将游戏初始化为默认参数!")
        else:
            ## 初始化棋盘参数
            box_rows = rows
            box_cols = cols
            num_mines = mines
            ## 初始化棋盘参数
            renpy.run(SetVariable("box_rows",rows))
            renpy.run(SetVariable("box_cols",cols))
            renpy.run(SetVariable("num_mines",mines))
            renpy.notify("开始游戏!")

        ## 随机生成所有格子坐标
        all_box_list.clear()
        for i in range(1,box_rows+1):
            for j in range(1,box_cols+1):
                all_box_list.append((i,j))
        ## 随机生成雷的坐标
        mine_tag_list.clear()
        for i in range(num_mines):
            tag = random.choice(all_box_list)
            ## 如果随机生成的雷的坐标已经在雷的标记列表中,则重新生成
            while tag in mine_tag_list:
                tag = random.choice(all_box_list)
            mine_tag_list.append(tag)
        
        ## 开始游戏
        renpy.run(SetVariable("game_over",False))

    ## 打开格子
    def show_box(box_tag):
        ## 清除掉遗留下来的标记
        if box_tag in marked_box_list:
            marked_box_list.remove(box_tag)

        ## 范围内的雷的数量
        num_mines_around = 0
        ## 格子的具体坐标
        box_x = box_tag[0]
        box_y = box_tag[1]
        for i in mine_tag_list:
            if -1 <= box_x-i[0] <= 1:
                if -1 <= box_y-i[1] <= 1:
                    num_mines_around += 1
        box_around_mines[box_tag] = num_mines_around
        ## 如果周围没有雷,则再次调用函数,打开周围的格子
        if box_around_mines[box_tag] == 0:
            for i in range(box_x-1,box_x+2):
                for j in range(box_y-1,box_y+2):
                    if ((i,j) in all_box_list) and ((i,j) not in box_around_mines.keys()):
                        show_box((i,j))

    ## 标记格子
    def mark_box(box_tag=None,key=None):
        ## 如果按下的是q,则将格子标记为旗子
        if key == "q":
            if box_tag not in marked_box_list:
                marked_box_list.append(box_tag)
            else:
                marked_box_list.remove(box_tag)
    
    ## 结算游戏
    def check_win_or_lose(box_tag=None):

        ## 判断游戏是否结束
        if ((len(all_box_list)-len(mine_tag_list)) == len(box_around_mines.keys())) and (box_tag not in mine_tag_list):
            ## 打开所有格子,没有雷,游戏结束,胜利
            renpy.run(SetVariable("win_or_lose",True))
            renpy.run(SetVariable("game_over",True))
            renpy.notify("你赢了!")

        elif box_tag in mine_tag_list:
            ## 打开的格子有雷,游戏结束,输了
            renpy.run(SetVariable("win_or_lose",False))
            renpy.run(SetVariable("game_over",True))
            renpy.notify("踩雷了,你输了!")

        else:
            ## 游戏继续
            pass

## 游戏界面
screen minesweeper_screen(cols=10,rows=10,mines=10):

    ## 初始化游戏参数
    on "show" action [Function(init_game,rows=rows,cols=cols,mines=mines),SetVariable("game_over",False)]

    ## 判断按键是否按下
    key "keydown_K_q" action SetVariable("K_q",True)
    key "keyup_K_q" action SetVariable("K_q",False)

    ## 棋盘
    fixed:
        xycenter (0.5,0.5)
        vbox:
            xycenter (0.5,0.5)
            add "#b3b3b3" xysize (int(box_cols*box_xysize*1.2+box_xysize*0.6),int(box_rows*box_xysize*1.2+box_xysize*0.6)) xycenter (0.5,0.5) yoffset int(box_xysize*0.6)
            ## 不同游戏状态时切换不同的表情
            if game_over == None or game_over == False:
                add Text("😀",style="box_style",size=int(box_xysize)) xycenter (0.5,1.2)
            elif game_over == True and win_or_lose == True:
                add Text("😎",style="box_style",size=int(box_xysize)) xycenter (0.5,1.2)
            elif game_over == True and win_or_lose == False:
                add Text("💀",style="box_style",size=int(box_xysize)) xycenter (0.5,1.2)
        add "#000000" xysize (int(box_cols*box_xysize*1.2+box_xysize*0.2),int(box_rows*box_xysize*1.2+box_xysize*0.2)) xycenter (0.5,0.5)

        if game_over == None or game_over == False:
            grid box_cols box_rows:
                xycenter (0.5,0.5)
                xsize int(box_rows*box_xysize*1.2)
                ysize int(box_cols*box_xysize*1.2)
                spacing int(box_xysize*0.2)

                for i in all_box_list:
                    ## 判断格子的状态
                    if i in box_around_mines.keys():
                        ## 如果被打开,则变为图片,显示数字
                        if box_around_mines[i] == 0:
                            add Fixed("#363636",xysize=(int(box_xysize),int(box_xysize)))
                        else:
                            add Fixed("#363636",Text(str(box_around_mines[i]),style="box_style",size=int(box_xysize*0.6)),xysize=(int(box_xysize),int(box_xysize)))
                    else:
                        ## 如果没有被打开,则变为按钮
                        imagebutton:
                            ## 判断格子是否被标记
                            if i in marked_box_list:
                                idle Fixed("#7593d4",Text(("🚩"),style="box_style",size=int(box_xysize*0.6)),xysize=(int(box_xysize),int(box_xysize)))
                            else:
                                idle Fixed("#7593d4",xysize=(int(box_xysize),int(box_xysize)))

                            ## 判断按下的按键,并将已经打开的格子排除在外
                            if (K_q == True) and (i not in box_around_mines.keys()):
                                action Function(mark_box,i,"q") 
                            else:
                                action [Function(show_box,i),Function(check_win_or_lose,i)]
        
        ## 游戏结束后重开
        else:
            textbutton "重新开始":
                style "box_style"
                xalign 0.5
                yalign 0.5
                text_color "#7593d4"
                text_hover_color "#ffffff"
                text_size int(box_xysize*0.6)
                action [Function(init_game,rows=rows,cols=cols,mines=mines),SetVariable("game_over",False)]

这一段代码一共可以分为2大部分:
  • init python:最核心的算法部分
  • screen minesweeper_screen:玩家游玩所看到和需要交互的界面

剩下的style和label都不是必须使用的,因此各位可以根据需求进行替换。
我会更多地讲解init python部分,因为这是游戏玩法的核心,学会了最核心的算法,无论做什么样的格子类游戏都能够手到擒来!
而且screen的部分,通过代码注释,已经基本明了,因此不会有过多的解释,只需要根据自己的需要去增减配置即可。
接下来我们按顺序逐个分析代码——

init python

首先调用python中的random库,因为之后需要随机打乱地雷存在的位置,虽然也可以使用原生的renpy方法renpy.random,但这里还是使用了更常见的做法。
接着再定义各类变量,用法已经标注在注释中了,具体使用时也会再次说明。

def init_game:

游戏初始运行必要的函数,会检测导入的参数是否合法,并初始化所有参数(这一步是必须的,轻则游戏重开时数据未清除,重则直接报错)。
该函数有三个入参,分别是列数,行数,雷数。
之所以使用renpy.runSetVariable的,是因为我们直接在init python定义了变量,但在init python里定义相当于使用了define(会被认定成常量),更新后没办法使其被grid立刻识别并更新组件。
(之后在其他函数内使用renpy.run也是同样的原因,可以亲自试试())

其中最重要的便是随机生成格子坐标的部分——
首先,通过两次for遍历,往all_box_list列表中中添加元组,两次遍历分别是每排的格子数和每列的格子数,
分别对应着“x”和“y”,最后加入的元组形式为(x,y)(例如[(1,1)]),遍历出的元组数量总量即为格子数量总量,之后只需将all_box_list遍历进grid即可(这便是后话了)。

然后遍历所需要的雷的数量,在里面设定一个临时变量tag,使用random.choice方法将all_box_list里随机选取的一个元素储存在里面,
使用while循环,反复判断tag是否已经在all_box_list中,如果是,则再次在all_box_list里随机选取的一个元素储存在里面,否则,将tag添加进mine_tag_list列表中。

最后,将game_over改为false,表示游戏开始。

def show_box:

包含了最重要的游戏算法,是游戏主要玩法的核心函数,每次点击格子后都会调用这个函数。
该函数有一个入参,为本格格子的坐标。
首先,点击打开格子后,会清除掉格子上存在的标记,将自身的坐标,也就是传入函数内的box_tag变量从marked_box_list中清除(如果存在的话)。

然后,定义一个num_mines_around临时变量,来存放方格周围存在雷的数量,
定义两个临时变量——box_x和box_y,分别储存box_tag的x和y,再通过遍历mine_tag_list,判断周围9格是否存在雷(如果本身是雷也会通过判断,但对游戏进行没有影响),
如果存在雷则使num_mines_around的值+1,最后将box_tag和num_mines_around组成键值对(例如{(1,1):2}),添加进box_around_mines字典中,代表该坐标周围存在的雷数。
注意!box_around_mines内部元素的存在与否代表着格子是否已经开启,如果某个格子的坐标不在字典内,则代表该格子没有被打开,反之则表示已开启,这一点非常重要!

接着,我们判断该坐标周围的雷数是否为0,如果是0则通过遍历,将周围8格作为入参(一定要使用if排除掉自身这格,否则会因此反复调用函数,栈溢出导致游戏报错),再次调用函数。

最后,重复上述过程,直到周围雷的数量大于0为止。

def mark_box:

这部分是为了保证玩家有更好的游戏体验而设计的,并非必须,但强烈建议保留。
该函数有两个入参,分别为本格格子的坐标和当前按键反馈。
如果已经按下了按键key,并且坐标不在marked_box_list列表中的话,则将坐标加入marked_box_list,反之则移除

def check_win_or_lose:

用于判断游戏胜负,同样是游戏主要玩法的核心函数,每次点击格子后都会调用这个函数。
该函数有一个入参,为本格格子的坐标。

首先,判断box_around_mines里键的数量是否等于总格子数减去雷数,以及判断本格坐标是否在雷的坐标列表中,
如果是,则将win_or_lose改为True,表示赢了,将game_over改为True,表示游戏结束。

然后,判断本格坐标是否在雷的坐标列表中,
如果是,则将win_or_lose改为False,表示输了,将game_over改为True,表示游戏结束。

最后,如果以上判断都没有通过,则继续游戏。

好了,以上将init python,也就是最核心的游戏算法部分讲完了,
接下来就是负责向玩家展示和交互的screen部分了——

screen:
界面一共有三个入参,分别是列数,行数,雷数,展示界面时使用这三个入参改变游戏的基础配置

on "show"

当界面被展示时,立刻使用Function行为SetVariable行为,调用init_game并将game_over改为True表示游戏开始。

key

按下Q键不同的状态都将会改变“K_q”的状态

grid
这是游戏中最主要的界面,强烈建议不要随意更改。
首先,当game_over不为True时,将会展示这个,grid将以box_cols和box_rows来规划网格。

然后,使用for遍历all_box_list将所有方格放入其中,将已经打开的方格变为图片,减少性能损耗(没什么用的小功能(笑)),同时,通过box_around_mines判断,
如果已经打开的格子周围没有雷则不显示数字,否则在格子上显示周围雷的数量。

接着,如果不是已经打开的方格,则作为图片按钮供玩家点击,
如果方格的坐标在标志列表中,则在按钮上显示旗帜,否则不显示旗帜。

最后,通过判断是否按下q键和是否已经被打开来判断该使用哪个按钮行为,
如果是,则只调用mark_box函数,传入当前坐标和“q”作为入参,
否则,调用show_box和check_win_or_lose函数,都以当前坐标作为入参。

至此,关于扫雷迷你游戏代码讲解/教程都已经全部讲完了,希望这篇教程能更好地让各位配置和学习扫雷游戏!
之后我会发布更多renpy相关的开源迷你小游戏教程,并添加进LLFs-OSRMGP中,欢迎各位关注仓库和加星!

各位下期再见!
minesweeper_1.jpg

评分

参与人数 1活力 +300 干货 +3 收起 理由
blackpineapple + 300 + 3 感谢分享!

查看全部评分

本帖被以下淘专辑推荐:

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|RenPy中文空间 ( 苏ICP备17067825号|苏公网安备 32092302000068号 )

GMT+8, 2024-10-16 19:24 , Processed in 0.132820 second(s), 35 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表