用python自带的tkinter做游戏(三)—— 推箱子简易版 篇

今天分享得是:用python自带的tkinter做游戏系列的第三弹,推箱子简易版 篇
之前的二篇博文介绍的分别是贪食蛇和俄罗斯方块。
用python自带的tkinter做游戏(一)—— 贪吃蛇 篇
用python自带的tkinter做游戏(二)—— 俄罗斯方块 篇

用python自带的tkinter做游戏(三)—— 推箱子简易版 篇
  
  首先我要申明的是,本人也是新手一名,刚学Python半年。
  其实网上相关的游戏教程也有很多,我自己也阅读过不少,不过无奈自身水平有限,很多都是一知半解。不过借助各位大神们的思路,再加上自己的瞎折腾,也算是完成了几个入门的小游戏。
  拿出来分享也是为了给更多的新手们提供不同的思路,希望对你们有所帮助。
  毕竟本人也是新手一枚,太复杂高深的代码也不会。
  也希望各位高手给点鼓励和支持,谢谢~
  
  好了,回到正题。之前的两款游戏,贪吃蛇和俄罗斯方块,都属于方块类游戏,画面比较单一,规则也简单,制作起来比较容易上手。
  其实推箱子这个游戏严格上来说也属于方块类游戏,毕竟人物和箱子的移动距离还是以单元格为单位的,只是游戏规则更为复杂一些。
  
  之所以称之为简易版,因为今天要介绍的只是推箱子这个游戏系统的本身。
  所以游戏的画面还是以各种颜色的方格为主。
  下一期再介绍一下如何美化(用图片或者像素作画)。
  
  推箱子的游戏界面中一共会出现7种状态。

        self.color_dict = {0:  'white',     # 0表示空白
                           1:'#808080',     # 1表示墙
                           2: 'yellow',     # 2表示空地上的人
                           3:  'green',     # 3表示空地上的箱子
                           4:   'pink',     # 4表示终点
                           5:    'red',     # 5表示终点上的的箱子
                           6:'#ffa579',     # 6表示在终点上的人
                           }

这里用七种颜色来指代七种状态。
  
  这七种状态中,除了墙是固定不动的,其它的都会根据游戏的进程而产生变化,很考验逻辑思维。
  
  定义好了7种状态,就可以画地图了。游戏地图我在网上找了几个,有兴趣的同学也可以自行编辑关卡。

try:  # 同目录下搜寻m.txt地图扩充文件,若没有此文件就用内置地图
  with open("m.txt", "r") as f:
     game_map = f.read()
     game_map = eval(game_map)
except:
     game_map = [[
 [0, 0, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 4, 1, 0, 0, 0],
 [0, 0, 1, 0, 1, 1, 1, 1],
 [1, 1, 1, 3, 0, 3, 4, 1],
 [1, 4, 0, 3, 2, 1, 1, 1],
 [1, 1, 1, 1, 3, 1, 0, 0],
 [0, 0, 0, 1, 4, 1, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0]
 ],[

我这里的设计思路是关卡和游戏本身分离,关卡是保存在m.txt中,方便日后升级更新。当然没有也无妨,还可以使用内置的地图。
  
  代码里只展现了第一关,实际总共有六关,其中第六关是大地图测试关。
  解释下什么是大地图,贪吃蛇和俄罗斯方块的游戏地图都是固定的,都属于小地图。
  当游戏的地图大于显示屏幕的时候,这时候移动人物你会发现,其实人物只是在屏幕的中间摇摆,移动的只是地图,只有到了地图的尽头人物才正式开始移动。
  
  其实大多数的RPG游戏和卷轴类游戏都属于大地图。所以这样看来,tkinter可以制作任何类型的游戏。当然了,tkinter不支持音效,不过这不在本文的讨论范围之内。
  
  好了,言归正传,回到推箱子的游戏话题上。不管什么类型的游戏,只要是产生了位移,就需要获取坐标这个重要的数据。

    def boxman_xy(self):
        """ 获取人物坐标 """
        global boxman_x, boxman_y
        xy = []
        for i in range(0,len(game_map[self.game_stage-1])):
            try: # 查找数值为2的坐标,没有就返回0。为防止人物出现在0列,先加上1,最后再减去。
                x = game_map[self.game_stage-1][i].index(2) + 1
            except:
                x = 0
            xy.append(x)
        boxman_x = max(xy)
        boxman_y = xy.index(boxman_x)
        boxman_x = boxman_x - 1 # 之前加1,现在减回

        if boxman_x == -1: # 没有数值为2,说明人物在终点,即数值等于6
            xy = []
            for i in range(0,len(game_map[self.game_stage-1])):
                try:
                    x  = game_map[self.game_stage-1][i].index(6) + 1
                except:
                    x = 0
                xy.append(x)
            boxman_x = max(xy)
            boxman_y = xy.index(boxman_x)
            boxman_x = boxman_x - 1

因为人物有两种状态,不是站在空地上就是站在终点上。所以先搜索一遍空地上的人,没有的话再搜索终点上的,总有一款适合你~

人物的移动比贪吃蛇移动要复杂的多,一步步来吧,先看看不推箱子时的移动。

                # 0表示空白,  1表示墙,  2表示人,  3表示箱子,  4表示终点,  5表示已完成的箱子,  6表示在终点上的人
                def operation_forward_none(f1,f2,f3,f4,f5):
                    """ 前方是空地或是终点 """
                    if             game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] == f1: ### 前方是空地或是终点
                        if         game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] == 2:  #   人站在空地上
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = f2  ### 人离开后是空地
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f3  ### 前方是空地或是终点
                        else:                                                                         #   人站在终点上
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = f4  ### 身后是终点
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f5  ### 前方是空地或是终点

在前方没有箱子的情况下,人物行走的代码就出现了。

                    operation_forward_none(0,0,2,4,2)
                    operation_forward_none(4,0,6,4,6)

当时写代码的时候大脑也是短路了。人站在空地上,离开后当然应该是空地了,终点也是。不然还能少写两个参数,现在也懒得改了,多两个参数就多两个吧。

反正就是空地(0),空地上的人(2),终点(4)和终点上的人(6)之间的关系。真正难的还在后面,前方是箱子的情况下,该如何操作呢?

                def operation_forward_box(f1,f2,f3,f4,f5,f6,f7):
                    """ 前方是空地上的箱子或是已完成的箱子 """
                    if             game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] == f1: ### 前方是空地上的箱子或是已完成的箱子
                        if         game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] == 2:  #   人站在空地
                            if     game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] == f2: ### 箱子的前方是空地或终点
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = 0   #   人离开后是空地
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f3  ### 前方是空地或是终点
                                   game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2]  = f4  ### 前方是空地上的箱子或是已完成的箱子
                        else:                                                                         ### 人站在终点上
                            if     game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] == f5: ### 箱子的前方是空地或是终点
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = 4   #   身后是终点
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f6  ### 前方是空地或是终点
                                   game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2]  = f7  ### 箱子的前方是空地或是终点

有点晕是不是?没事,请慢慢理解~

                    operation_forward_box(3,0,2,3,0,2,3)
                    operation_forward_box(3,4,2,5,4,2,5)
                    operation_forward_box(5,0,6,3,0,6,3)
                    operation_forward_box(5,4,6,5,4,6,5)

箱子也分两种状态,空地上的箱子和终点上的箱子。加上人的两种状态,一共有四种结果。

其实本游戏最烧脑的地方是撤消功能。最初我用的是简单粗暴的方式,直接是每走一步就复制一份地图,撤消时直接换地图就是。但这样做太耗内存,而且太没技术含量。为了追求完美,最终还是花了点心思在这个撤消功能上。

                    temp = [] # 记录每步的操作,供撤消使用。
                    temp.append(boxman_y) # 保存XY轴坐标值,即人物所在单元格的坐标
                    temp.append(boxman_x) # 后面6个分别是中,上,下,左,右和前方单元格的值
                    temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x + 0])
                    temp.append(game_map[self.game_stage-1][boxman_y - 1][boxman_x + 0])
                    temp.append(game_map[self.game_stage-1][boxman_y + 1][boxman_x + 0])
                    temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x - 1])
                    temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x + 1])
                    temp.append(game_map[self.game_stage-1][boxman_y + y][boxman_x + x])
                    
                    record_list.append(temp)
                    
                    if len(record_list) > 1:
                        if record_list[-1] == record_list[-2]:
                          del record_list[-1]   # 删除连续相同的数据

首先呢,在走出第一步的时候,就开始记录各项数据,包括人物的坐标和上下左右等一共八个数据。为了精简,删除了连续相同的数据(因为前面是墙而滞留的一步)。

                def restore(): 
                    """ 撤销步骤的函数 """
                    m = game_map[self.game_stage-1]
                    
                    before_forward = 0                   # 之前所面对的(0是临时值)
                    
                    before_stand   = record_list[-2][2]  # 之前所站的单元格的值
                    now_stand      = record_list[-1][2]  # 当前所站的单元格的值
                    now_forward    = record_list[-1][7]  # 当前所面对的单元格的值

                    before_x       = record_list[-2][1]  # 之前所站的X轴坐标
                    before_y       = record_list[-2][0]  # 之前所站的Y轴坐标
                    now_x          = record_list[-1][1]  # 当前所站的X轴坐标
                    now_y          = record_list[-1][0]  # 当前所站的Y轴坐标
                    
                    b_up           = record_list[-2][3]  # 之前上方单元格的值
                    b_dw           = record_list[-2][4]  # 之前下方单元格的值
                    b_lf           = record_list[-2][5]  # 之前左方单元格的值
                    b_rg           = record_list[-2][6]  # 之前右方单元格的值

                    #  推断出之前所面对的单元格的值
                    if     before_x         > now_x:
                           next_x           = now_x - 1
                           before_forward   = b_lf
                    elif   before_x         < now_x:
                           next_x           = now_x + 1
                           before_forward   = b_rg
                    else:
                           next_x           = now_x
                             
                    if     before_y         > now_y:
                           next_y           = now_y - 1
                           before_forward   = b_up
                    elif   before_y         < now_y:
                           next_y           = now_y + 1
                           before_forward   = b_dw
                    else:
                           next_y           = now_y
                    
                    # 0表示空白,  1表示墙,  2表示人,  3表示箱子,  4表示终点,  5表示已完成的箱子,  6表示在终点上的人
                    m[before_y][before_x] = before_stand # 人退回之前的状态,2或者6
                    m[   now_y][   now_x] = before_forward
                    # 推断出当前面对的单元格的值
                    if                      before_forward == 3:
                        if                     now_forward == 3:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 0
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 0
                        elif                   now_forward == 5:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 4
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 0 
                                         
                    elif                    before_forward == 5:
                        if                     now_forward == 3:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 0
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 0
                        elif                   now_forward == 5:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 0
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 4
                restore()
                del record_list[-1]  # 每撤消一步就删除最后一组列表

撤消的逻辑要比之前的推箱子更复杂,撤消过程中每撤消一步就删除最后一组记录。
等record_list删除到只剩最后一组数据后,最后一步的撤消就直接恢复初始地图就可以了。

移动的问题搞定了,后面的都简单了,先看看通关条件。

        def game_pass(): # 通关条件为箱子数为0
            """ 获取箱子数量,等于0的话过关 """
            xy = []
            for i in range(0,len(game_map[self.game_stage-1])):
                x = game_map[self.game_stage-1][i].count(3)
                xy.append(x)
            box_number = sum(xy)
            if box_number == 0:
                pass_win()

空地上的箱子数为0就算通关了,就这么简单。
其它的功能也简单,就不逐一说明了。

至于大地图,其实也简单,就是加入两个调节值,调节坐标的偏差。
移动的时候,根据人物的坐标,选择是移动地图还是人物。
当然了,如果加载的是大地图,必须要以人物为坐标中心载入。具体的可以自行去第六关测试关体验一下。

因为关卡少,所以选关画面做的粗糙了些。
游戏中也可以直接用数字键来选择关卡(反正也只有六关)。

展示一下游戏图(第四关为小地图):
在这里插入图片描述
画面是简陋了些,下一期就来谈谈如何改进游戏画面。
敬请期待:
用python自带的tkinter做游戏(四)—— 推箱子重制版 篇

最终附上本篇的完整代码:

# -*- coding: utf-8 -*-
"""
Created on Tue Mar 16 13:31:29 2021

@author: Juni Zhu (wechat:znix1116)
"""

import tkinter as tk
import copy
from tkinter.messagebox import showinfo


try:  # 同目录下搜寻m.txt地图扩充文件,若没有此文件就用内置地图
  with open("m.txt", "r") as f:
     game_map = f.read()
     game_map = eval(game_map)
except:
     game_map = [[
 [0, 0, 1, 1, 1, 0, 0, 0],
 [0, 0, 1, 4, 1, 0, 0, 0],
 [0, 0, 1, 0, 1, 1, 1, 1],
 [1, 1, 1, 3, 0, 3, 4, 1],
 [1, 4, 0, 3, 2, 1, 1, 1],
 [1, 1, 1, 1, 3, 1, 0, 0],
 [0, 0, 0, 1, 4, 1, 0, 0],
 [0, 0, 0, 1, 1, 1, 0, 0]
 ],[
 [0, 0, 0, 1, 1, 1, 1, 1, 1, 0],
 [0, 1, 1, 1, 0, 0, 0, 0, 1, 0],
 [1, 1, 4, 0, 3, 1, 1, 0, 1, 1],
 [1, 4, 4, 3, 0, 3, 0, 0, 2, 1],
 [1, 4, 4, 0, 3, 0, 3, 0, 1, 1],
 [1, 1, 1, 1, 1, 1, 0, 0, 1, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 0]
 ],[
 [0, 0, 1, 1, 1, 1, 0, 0],
 [0, 0, 1, 4, 4, 1, 0, 0],
 [0, 1, 1, 0, 4, 1, 1, 0],
 [0, 1, 0, 0, 3, 4, 1, 0],
 [1, 1, 0, 3, 0, 0, 1, 1],
 [1, 0, 0, 1, 3, 3, 0, 1],
 [1, 0, 0, 2, 0, 0, 0, 1],
 [1, 1, 1, 1, 1, 1, 1, 1]
 ],[
 [1, 1, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 1, 0, 0, 0, 1],
 [1, 0, 3, 4, 4, 3, 0, 1],
 [1, 2, 3, 4, 5, 0, 1, 1],
 [1, 0, 3, 4, 4, 3, 0, 1],
 [1, 0, 0, 1, 0, 0, 0, 1],
 [1, 1, 1, 1, 1, 1, 1, 1]
 ],[
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
 [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0],
 [0, 0, 0, 0, 0, 1, 0, 0, 3, 3, 0, 1, 0],
 [1, 1, 1, 1, 1, 1, 0, 3, 1, 0, 0, 1, 0],
 [1, 4, 4, 4, 1, 1, 1, 0, 1, 0, 0, 1, 1],
 [1, 4, 0, 0, 1, 0, 0, 3, 0, 1, 0, 0, 1],
 [1, 4, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 1],
 [1, 4, 0, 0, 1, 0, 0, 3, 0, 1, 0, 0, 1],
 [1, 4, 4, 4, 1, 1, 1, 0, 1, 0, 0, 1, 1],
 [1, 1, 1, 1, 1, 1, 0, 3, 0, 0, 0, 1, 0],
 [0, 0, 0, 0, 0, 1, 0, 2, 1, 0, 0, 1, 0],
 [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0], 
 ],[
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
 [1, 0, 4, 4, 4, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 4, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 5, 0, 0, 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
 ]]


class Boxman():
    """ 推箱子游戏 """
    def __init__(self, game_stage):
        """ 游戏参数设置 """       
        self.game_stage = game_stage        # 游戏关卡
        self.canvas_bg  = '#d7d7d7'         # 游戏背景色
        self.cell_size  = 48                # 方格单元格大小
        self.cell_gap   = 1                 # 方格间距
        self.frame_x    = 25                # 左右边距
        self.frame_y    = 25                # 上下边距
        self.max_cells  = 10                # 游戏画面长宽最大单元格数
        self.win_w_plus = 220               # 窗口右边额外多出的宽度 
        self.big_map    = 0                 # 判断当前地图是否是超出窗口大小。1为是,0为不是
        
        # 根据地图自动调整窗口大小
        self.canvas_w   = len(game_map[self.game_stage-1][0]) * self.cell_size + self.frame_x*2 
        self.canvas_h   = len(game_map[self.game_stage-1]   ) * self.cell_size + self.frame_y*2 
        self.color_dict = {0:  'white',     # 0表示空白
                           1:'#808080',     # 1表示墙
                           2: 'yellow',     # 2表示空地上的人
                           3:  'green',     # 3表示空地上的箱子
                           4:   'pink',     # 4表示终点
                           5:    'red',     # 5表示终点上的的箱子
                           6:'#ffa579',     # 6表示在终点上的人
                           }
        
        # 若地图过大,窗口则根据max_cells值来设定大小
        if len(game_map[self.game_stage-1][0]) > self.max_cells:
            self.big_map  = 1
            self.canvas_w = self.cell_size*self.max_cells + self.frame_x*2
        if len(game_map[self.game_stage-1]) > self.max_cells:
            self.big_map  = 1
            self.canvas_h = self.cell_size*self.max_cells + self.frame_y*2 
                              
        self.win_w_size = self.canvas_w + self.win_w_plus
        self.win_h_size = self.canvas_h
        
        
    def create_canvas(self): 
        """ 创建canvas """
        global canvas
        canvas = tk.Canvas(window, 
                           bg=self.canvas_bg, 
                           height=self.canvas_h,
                           width=self.canvas_w,
                           highlightthickness = 0)
                           

    def window_center(self,window,w_size,h_size):
        """ 窗口居中 """
        screenWidth  =  window.winfo_screenwidth()  # 获取显示区域的宽度
        screenHeight = window.winfo_screenheight()  # 获取显示区域的高度
        left =  (screenWidth - w_size) // 2
        top  = (screenHeight - h_size) // 2
        window.geometry("%dx%d+%d+%d" % (w_size, h_size, left, top))

        
    def create_game_cells(self,a,b):   # a,b值为偏差值,若地图大于窗口的话,用于调节起始坐标
        """ 创建初始版的游戏单元格 """   # 通过game_map列表,对应字典color_dict里的颜色画出地图
        for y in range(0,len(game_map[self.game_stage-1])-b):
              for x in range(0,len(game_map[self.game_stage-1][0])-a):            
                canvas.create_rectangle(self.frame_x + self.cell_size * x + self.cell_gap,
                                        self.frame_y + self.cell_size * y + self.cell_gap,
                                        self.frame_x + self.cell_size * (x+1),
                                        self.frame_y + self.cell_size * (y+1),
                                        fill    = self.color_dict[game_map[self.game_stage-1][y+b][x+a]],
                                        outline = self.canvas_bg,
                                        width   = 0)
                                        
        canvas.place(x=0,y=0)
        
        
    def boxman_xy(self):
        """ 获取人物坐标 """
        global boxman_x, boxman_y
        xy = []
        for i in range(0,len(game_map[self.game_stage-1])):
            try: # 查找数值为2的坐标,没有就返回0。为防止人物出现在0列,先加上1,最后再减去。
                x = game_map[self.game_stage-1][i].index(2) + 1
            except:
                x = 0
            xy.append(x)
        boxman_x = max(xy)
        boxman_y = xy.index(boxman_x)
        boxman_x = boxman_x - 1 # 之前加1,现在减回

        if boxman_x == -1: # 没有数值为2,说明人物在终点,即数值等于6
            xy = []
            for i in range(0,len(game_map[self.game_stage-1])):
                try:
                    x  = game_map[self.game_stage-1][i].index(6) + 1
                except:
                    x = 0
                xy.append(x)
            boxman_x = max(xy)
            boxman_y = xy.index(boxman_x)
            boxman_x = boxman_x - 1


    def move_action(self,event): 
        """ 按键控制 """
        def Boxman_move(event,key,x,y):
                """ 操控人物 """
                # 0表示空白,  1表示墙,  2表示人,  3表示箱子,  4表示终点,  5表示已完成的箱子,  6表示在终点上的人
                def operation_forward_none(f1,f2,f3,f4,f5):
                    """ 前方是空地或是终点 """
                    if             game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] == f1: ### 前方是空地或是终点
                        if         game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] == 2:  #   人站在空地上
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = f2  ### 人离开后是空地
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f3  ### 前方是空地或是终点
                        else:                                                                         #   人站在终点上
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = f4  ### 身后是终点
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f5  ### 前方是空地或是终点

                def operation_forward_box(f1,f2,f3,f4,f5,f6,f7):
                    """ 前方是空地上的箱子或是已完成的箱子 """
                    if             game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1] == f1: ### 前方是空地上的箱子或是已完成的箱子
                        if         game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0] == 2:  #   人站在空地
                            if     game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] == f2: ### 箱子的前方是空地或终点
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = 0   #   人离开后是空地
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f3  ### 前方是空地或是终点
                                   game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2]  = f4  ### 前方是空地上的箱子或是已完成的箱子
                        else:                                                                         ### 人站在终点上
                            if     game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2] == f5: ### 箱子的前方是空地或是终点
                                   game_map[self.game_stage-1][boxman_y + y*0][boxman_x + x*0]  = 4   #   身后是终点
                                   game_map[self.game_stage-1][boxman_y + y*1][boxman_x + x*1]  = f6  ### 前方是空地或是终点
                                   game_map[self.game_stage-1][boxman_y + y*2][boxman_x + x*2]  = f7  ### 箱子的前方是空地或是终点


                direction = event.keysym
                if(direction == key):
                    operation_forward_none(0,0,2,4,2)
                    operation_forward_none(4,0,6,4,6)
                    operation_forward_box(3,0,2,3,0,2,3)
                    operation_forward_box(3,4,2,5,4,2,5)
                    operation_forward_box(5,0,6,3,0,6,3)
                    operation_forward_box(5,4,6,5,4,6,5)
                    
                    Boxman(self.game_stage).boxman_xy()   # 刷新坐标
                    
                    temp = [] # 记录每步的操作,供撤消使用。
                    temp.append(boxman_y) # 保存XY轴坐标值,即人物所在单元格的坐标
                    temp.append(boxman_x) # 后面6个分别是中,上,下,左,右和前方单元格的值
                    temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x + 0])
                    temp.append(game_map[self.game_stage-1][boxman_y - 1][boxman_x + 0])
                    temp.append(game_map[self.game_stage-1][boxman_y + 1][boxman_x + 0])
                    temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x - 1])
                    temp.append(game_map[self.game_stage-1][boxman_y + 0][boxman_x + 1])
                    temp.append(game_map[self.game_stage-1][boxman_y + y][boxman_x + x])
                    
                    record_list.append(temp)
                    
                    if len(record_list) > 1:
                        if record_list[-1] == record_list[-2]:
                          del record_list[-1]   # 删除连续相同的数据


        def game_select_stage(event,key):
            """ 返回游戏主页面 """
            global game_map
            direction = event.keysym
            if(direction == key): 
                game_map = copy.deepcopy(backup_map)
                window.destroy()  # 窗口关闭并启动起始窗口
                Boxman(self.game_stage).index_game()


        def reset_stage(event,key):
            """ 重置关卡 """
            global game_map
            direction = event.keysym
            if(direction == key): 
                del record_list[:]
                game_map = copy.deepcopy(backup_map)


        def to_stage(event,key,stage): # 直接按数字键选关
            """ 选择游戏关卡 """
            global game_map
            direction = event.keysym
            if(direction == key): 
                del record_list[:]
                game_map = copy.deepcopy(backup_map)
                window.destroy()
                Boxman(stage).window_open()


        def restore_stage(event,key):
            """ 撤销功能 """
            direction = event.keysym
            if(direction == key): 
                def restore(): 
                    """ 撤销步骤的函数 """
                    m = game_map[self.game_stage-1]
                    
                    before_forward = 0                   # 之前所面对的(0是临时值)
                    
                    before_stand   = record_list[-2][2]  # 之前所站的单元格的值
                    now_stand      = record_list[-1][2]  # 当前所站的单元格的值
                    now_forward    = record_list[-1][7]  # 当前所面对的单元格的值

                    before_x       = record_list[-2][1]  # 之前所站的X轴坐标
                    before_y       = record_list[-2][0]  # 之前所站的Y轴坐标
                    now_x          = record_list[-1][1]  # 当前所站的X轴坐标
                    now_y          = record_list[-1][0]  # 当前所站的Y轴坐标
                    
                    b_up           = record_list[-2][3]  # 之前上方单元格的值
                    b_dw           = record_list[-2][4]  # 之前下方单元格的值
                    b_lf           = record_list[-2][5]  # 之前左方单元格的值
                    b_rg           = record_list[-2][6]  # 之前右方单元格的值

                    #  推断出之前所面对的单元格的值
                    if     before_x         > now_x:
                           next_x           = now_x - 1
                           before_forward   = b_lf
                    elif   before_x         < now_x:
                           next_x           = now_x + 1
                           before_forward   = b_rg
                    else:
                           next_x           = now_x
                             
                    if     before_y         > now_y:
                           next_y           = now_y - 1
                           before_forward   = b_up
                    elif   before_y         < now_y:
                           next_y           = now_y + 1
                           before_forward   = b_dw
                    else:
                           next_y           = now_y
                    
                    # 0表示空白,  1表示墙,  2表示人,  3表示箱子,  4表示终点,  5表示已完成的箱子,  6表示在终点上的人
                    m[before_y][before_x] = before_stand # 人退回之前的状态,2或者6
                    m[   now_y][   now_x] = before_forward
                    # 推断出当前面对的单元格的值
                    if                      before_forward == 3:
                        if                     now_forward == 3:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 0
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 0
                        elif                   now_forward == 5:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 4
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 0 
                                         
                    elif                    before_forward == 5:
                        if                     now_forward == 3:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 0
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 0
                        elif                   now_forward == 5:
                            if                   now_stand == 2:
                                         m[next_y][next_x]  = 0
                            elif                 now_stand == 6:
                                         m[next_y][next_x]  = 4
                restore()
                
                del record_list[-1]  # 每撤消一步就删除最后一组列表

                
        def game_pass(): # 通关条件为箱子数为0
            """ 获取箱子数量,等于0的话过关 """
            xy = []
            for i in range(0,len(game_map[self.game_stage-1])):
                x = game_map[self.game_stage-1][i].count(3)
                xy.append(x)
            box_number = sum(xy)
            if box_number == 0:
                pass_win()
        
        
        def pass_win():
            """ 箱子为零时,显示过关窗口 """
            global game_map
            showinfo('恭喜过关','返回首页')
            window.destroy()
            game_map = copy.deepcopy(backup_map)
            Boxman(0).index_game()
            

        def move_map_ws(event,key,a,b):
            """ 若是大地图,则上下移动地图 """
            global py
            direction = event.keysym
            if(direction == key):             
              my = len(game_map[self.game_stage-1])
              if    self.big_map == 1:  
                if      boxman_y  >= int(self.max_cells//2) + a:
                  if    boxman_y  < my - int(self.max_cells//2):
                      if game_map[self.game_stage-1][boxman_y + b]  [boxman_x] not in [1] and \
                         game_map[self.game_stage-1][boxman_y + b*2][boxman_x] not in [1,3,5]:
                          py  = boxman_y - int(self.max_cells//2) + b
                  elif  boxman_y >= my - int(self.max_cells//2):
                              py  = my - self.max_cells 
                else:
                              py  = 0
              else:
                              py  = 0


        def move_map_ad(event,key,a,b): 
            """ 若是大地图,则左右移动地图 """
            global px
            direction = event.keysym
            if(direction == key): 
              mx = len(game_map[self.game_stage-1][0])
              if      self.big_map == 1:
                if      boxman_x >= int(self.max_cells/2) + a:
                  if    boxman_x  < mx - int(self.max_cells//2):
                      if game_map[self.game_stage-1][boxman_y][boxman_x + b  ] not in [1] and \
                         game_map[self.game_stage-1][boxman_y][boxman_x + b*2] not in [1,3,5] :
                          px  = boxman_x - int(self.max_cells//2) + b
                  elif  boxman_x >= mx - int(self.max_cells//2):
                              px  = mx - self.max_cells
                else:
                              px  = 0
              else:
                              px  = 0
                
                
        move_map_ws(event,    'w',  1, -1)
        move_map_ws(event,    's',  0,  1)
        move_map_ad(event,    'a',  1, -1)
        move_map_ad(event,    'd',  0,  1)

        move_map_ws(event,    'W',  1, -1)
        move_map_ws(event,    'S',  0,  1)
        move_map_ad(event,    'A',  1, -1)
        move_map_ad(event,    'D',  0,  1)

        move_map_ws(event,   'Up',  1, -1)
        move_map_ws(event, 'Down',  0,  1)
        move_map_ad(event, 'Left',  1, -1)
        move_map_ad(event,'Right',  0,  1)


        Boxman_move(event,    'w',  0, -1)
        Boxman_move(event,    's',  0,  1)
        Boxman_move(event,    'a', -1,  0)
        Boxman_move(event,    'd',  1,  0)

        Boxman_move(event,    'W',  0, -1)
        Boxman_move(event,    'S',  0,  1)
        Boxman_move(event,    'A', -1,  0)
        Boxman_move(event,    'D',  1,  0)

        Boxman_move(event,   'Up',  0, -1)
        Boxman_move(event, 'Down',  0,  1)
        Boxman_move(event, 'Left', -1,  0)
        Boxman_move(event,'Right',  1,  0)


        game_select_stage(event, 'p')     # 返回主页面
        game_select_stage(event, 'P')
        reset_stage(event, 'j')           # 重置该关卡
        reset_stage(event, 'J')
        
        to_stage(event, '1', 1)
        to_stage(event, '2', 2)
        to_stage(event, '3', 3)
        to_stage(event, '4', 4)
        to_stage(event, '5', 5)
        to_stage(event, '6', 6)
        
        
        if  len(record_list) <= 1:
            reset_stage(event, 'r')
            reset_stage(event, 'R')
        else:
            restore_stage(event, 'r')
            restore_stage(event, 'R')
        
        move_map_ws(event,   'r', 0, 0)
        move_map_ad(event,   'r', 0, 0)
        move_map_ws(event,   'R', 0, 0)
        move_map_ad(event,   'R', 0, 0)
        
        Boxman(self.game_stage).create_canvas() # 不刷新的话在大地图中有小BUG
        canvas.delete('all')
        Boxman(self.game_stage).create_game_cells(px,py)
        Boxman(self.game_stage).boxman_xy() # 重新刷新人物坐标,不然会有BUG
        
        game_pass()
            

    def window_open(self):
        """ 开启游戏窗口 """
        global window,txt_lable,px,py
        window = tk.Tk()
        window.focus_force()
        window.title('Boxman')
        Boxman(self.game_stage).window_center(window,self.win_w_size,self.win_h_size)
        
        if self.game_stage == 0:  # 若等于0,代表是最后一关
            n = len(game_map)
        else:
            n = self.game_stage
        
        txt_lable = tk.Label(window, text=
                              "当前为第" + str(n) + "关"
                             +"\n白色单元格为空地"
                             +"\n灰色单元格为墙" 
                             +"\n黄色单元格为打工人"
                             +"\n绿色单元格为箱子"
                             +"\n粉色单元格为终点"
                             +"\n红色单元格为已完成的箱子"
                             +"\n橘色单元格为站在终点上的人"
                             +"\n"
                             +"\n字母ADSW或方向键移动"
                             +"\n字母键P返回主页面"
                             +"\n字母键J重置本关"
                             +"\n字母键R为撤消"
                             +"\n数字键1~6直接选择关卡"
                             +"\n(第六关为测试关)"
                             +"\n"
                             +"\n"
                             +"\nBy Juni Zhu"
                             +"\n微信: znix1116"
                             ,
                             font=('Arial', 11),anchor="ne", justify="left")
        txt_lable.pack(side='right')
        
        Boxman(self.game_stage).boxman_xy()
        
        # 如果是大地图,更换起始坐标,以人物为中心建立地图
        mx = len(game_map[self.game_stage-1][0])
        my = len(game_map[self.game_stage-1])
        if      self.big_map == 1:
                if      boxman_x  > int(self.max_cells//2):
                  if    boxman_x  < mx - int(self.max_cells//2):
                              px  = boxman_x - int(self.max_cells//2)
                  elif  boxman_x >= mx - int(self.max_cells//2):
                              px  = mx - self.max_cells
                else:
                              px  = 0
        else:
                              px  = 0
        if      self.big_map == 1:                            
                if      boxman_y  > int(self.max_cells//2):
                  if    boxman_y  < my - int(self.max_cells//2):
                              py  = boxman_y - int(self.max_cells//2)
                  elif  boxman_y >= my - int(self.max_cells//2):
                              py  = my - self.max_cells
                else:
                              py  = 0
        else:
                              py  = 0
                              
                              
        Boxman(self.game_stage).create_canvas()
        Boxman(self.game_stage).create_game_cells(px,py)
        
        window.bind('<Key>', Boxman(self.game_stage).move_action)
        window.mainloop()
        
        
    def run_game(self):
        """ Run Game """
        global backup_map,record_list
        
        record_list = [] # 记录每步的操作,供撤消用
        backup_map = []  # 备份地图,供恢复用
        backup_map = copy.deepcopy(game_map)  # 备份地图,需要用深度复制
        Boxman(self.game_stage).window_open()
        
        
    def index_game(self):
        """ 起始界面 """  # 窗口大小会根据按键的数量自动调整
        for i in range(1,len(game_map)+1):  # 批量生成函数,to_game_1,2,3,4,5...
            exec("def to_game_" + str(i) + "():\n\
                 index_win.destroy()\n\
                 Boxman(" + str(i) + ").run_game()")
            
        global index_win
        index_win = tk.Tk()
        index_win.focus_force()
        index_win.title('Welcome')
        Boxman(self.game_stage).window_center(index_win,200,(len(game_map)+1)*30)
        
        for i in range(1,len(game_map)+1): # 批量添加按键
            exec("b" + str(i) + " = tk.Button(index_win, text='" + str(i) 
                 + "', font=('Arial', 12), width=10, height=1, command=to_game_" + str(i) + ")")
            exec("b" + str(i) + ".pack()")
        
        index_win.mainloop()
        
    
if __name__ == '__main__':

 Boxman(0).index_game()
  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值