【Python小游戏】使用tkinter和矩阵制作2048小游戏

目录

1. 2048小游戏基本介绍

2. 难点一:数据合并

3. 难点二:方向变化

4. 完整代码


1. 2048小游戏基本介绍

2048小游戏主要是在一个4×4的矩阵内,游戏初始化会有初始的数字2或4。通过四个方向的滑动,来对数据进行移动或合并,每一次有效的移动或合并后,会在随机的一个空白的格子内随机生成2或者是4,随机的概率为9:1。玩家通过不断地合并数字格,使得数字进行合并,当四个方向都无法实现数字移动或合并的时候游戏结束,所有数据之和为游戏分数。

本项目将会使用tkinter和numpy制作一个2048小游戏。由于代码不算麻烦,因此下面只对其中的两个较难的部分进行介绍。

2. 难点一:数据合并

2048小游戏,其主要的难点是在于如何对一列的数据进行“推箱子”般的计算。如一个[2,0,2,4]的列表,通过运算,得到[4,4,0,0]的列表结果。在这里,主要是由三步进行计算,即排序→相加→再排序,下面以[2,0,2,4]为例进行说明:

  1. 排序。第一次排序,把不为0的数据排在前面,在为0的数据排在后面,注意,不为0的数据虽然有大小之分,但是并不需要把这些数字再按大小进行排序,只需要对数据进行0和非0的区分。那么,[2,0,2,4]在第一次排序后,结果为[2,2,4,0]。
  2. 相加。将临近的两个相同的值进行相加。如在[2,2,4,0]中,将两个临近的2进行相加,得到4,注意,相加后的结果为[4,0,4,0],此时两个4之间不再相邻,因此不会再进行相加。故[4,0,4,0]即为相加后的结果。
  3. 再排序。第二次排序,重复第一步的过程,得到最终的处理结果。[4,0,4,0]再进行排序后,得到的结果为[4,4,0,0]。[4,4,0,0]则为[2,0,2,4]在运算后的结果。
# 将列表重新排序,有数据的在前面,没数据的在后面
def list_order(alist):
    blist = list(filter(lambda x: x > 0, alist))
    clist = list(filter(lambda x: x == 0, alist))
    dlist = blist + clist
    return dlist

def list_merge(alist):
    # 排序->相加->再排序
    alist = list_order(alist)
    for i in range(0, len(alist) - 1):
        j = i + 1
        if alist[j] > 0:
            if alist[j] == alist[i]:
                alist[i] += alist[j]
                alist[j] = 0
    alist = list_order(alist)
    return alist

3. 难点二:方向变化

从上面来讲,我们对于数据合并的操作都是类似于一个“从右往左推”的一个动作,这个动作对于向左的操作来说没有问题,但是对于向右、向下、向上的操作需要如何进行,总不能每个方面都单独写一个操作吧。在这里,我们对于该部分的操作巧妙地运用了矩阵的变换和列表的变换:

  • 向上。对于向上操作,我们先对矩阵进行转置(T),然后向左推,接着再转回来,就实现了向上推的动作了。
  • 向右。对于向右操作,我们对列表进行翻转(reversed),然后向左推,接着再转回来,就实现了向右推的动作了。
  • 向下。对于向下操作,我们先对矩阵进行转置(T),然后再对列表进行翻转(reversed),转为向左推,再变换回来,就实现了向下推的动作了。
# 向左
raw_list = A.tolist()
new_list = [list_merge(i) for i in raw_list]

# 向右
raw_list = A.tolist()
new_list = [list(reversed(list_merge(list(reversed(i))))) for i in raw_list]

# 向上
A = np.transpose(A)
raw_list = A.tolist()
new_list = [list_merge(i) for i in raw_list]

# 向下
A = np.transpose(A)
raw_list = A.tolist()
new_list = [list(reversed(list_merge(list(reversed(i))))) for i in raw_list]

4. 完整代码

# @author: moyuweiqing
# @date: 2023.04.14

from tkinter import *
import numpy as np
import random

root = Tk()                 # 创建窗口
root.geometry('280x320')
root.title("2048")          # 窗口名字
w1 = Canvas(root, width=280,height=280,background='lightcyan')
w1.pack()

score_text = StringVar()
score_text.set('总分:')
score_label = Label(root, textvariable=score_text, font=('黑体', 12)).place_configure(x=0, y=290)
w1.pack()
A=np.full((4,4),0)          # 储存数字
B=np.full((4,4),0)          # 记录状态

score = 0                   # 记录总分

# 色块映射情况
num_color_dic = {
    '2': '#FFFF00',
    '4': '#FFCC33',
    '8': '#FF9900',
    '16': '#FF33FF',
    '32': '#66CCFF',
    '64': '#66CC00',
    '128': '#33CC33',
    '256': '#009999',
    '512': '#666699',
    '1024': '#6633FF',
    '2048': '#6600FF',
    '4096': '#660066',
    '8192': '#006644',
    '16384': '#669999',
    '32768': '#336633',
    '65536': '#0033FF'
}

# 画线和画格子
for i in range(0, 5):
    w1.create_line(i * 60 + 20, 20, i * 60 + 20, 260)
    w1.create_line(20, i * 60 + 20, 260, i * 60 + 20)
for j in range(0, 4):
    for i in range(0, 4):
        w1.create_rectangle(i * 60 + 22, j * 60 + 22, i * 60 + 78, j * 60 + 78, fill='#FFFFCC')

# 计算分数
def cal_score():
    global A, score, score_text
    score = np.sum(A)
    score_text.set('总分:' + str(score))

# 重新画线
def re_draw():
    global A, B, w1

    for i in range(0, 5):
        w1.create_line(i * 60 + 20, 20, i * 60 + 20, 260)
        w1.create_line(20, i * 60 + 20, 260, i * 60 + 20)
    for i in range(0, 4):
        for j in range(0, 4):
            if B[i][j] == 0:
                # 注意画布和矩阵之间的对应关系
                w1.create_rectangle(j * 60 + 22, i * 60 + 22, j * 60 + 78, i * 60 + 78, fill='#ADD8E6')
            else:
                w1.create_rectangle(j * 60 + 22, i * 60 + 22, j * 60 + 78, i * 60 + 78, fill=num_color_dic[str(A[i][j])])
                w1.create_text(j * 60 + 50, i * 60 + 50, text=A[i][j], font=('宋体', 20), fill='#000000')

# 生成随机的数字
def make_random_num():
    global A, B, w1

    # 获取所有的空格
    nullblock_list = []
    nullblocks = np.where(B == 0)
    for i in zip(list(nullblocks[0]), list(nullblocks[1])):
        nullblock_list.append(i)

    # 随机是2或者是4
    randint = random.randint(0, 10)
    num = 2 if randint != 9 else 4
    randint = random.randint(0, len(nullblock_list) - 1)

    # 获取随机的位置x和y
    x = nullblock_list[randint][0]
    y = nullblock_list[randint][1]
    A[x][y] = num
    B[x][y] = 1

    # 重新画图和计算分数
    re_draw()
    cal_score()

# 将列表重新排序,有数据的在前面,没数据的在后面
def list_order(alist):
    blist = list(filter(lambda x: x > 0, alist))
    clist = list(filter(lambda x: x == 0, alist))
    dlist = blist + clist
    return dlist

def list_merge(alist):
    # 排序->相加->再排序
    alist = list_order(alist)
    for i in range(0, len(alist) - 1):
        j = i + 1
        if alist[j] > 0:
            if alist[j] == alist[i]:
                alist[i] += alist[j]
                alist[j] = 0
    alist = list_order(alist)
    return alist

def right_press(event):
    global A, B

    # 先保存原矩阵
    raw_A = A

    raw_list = A.tolist()
    new_list = [list(reversed(list_merge(list(reversed(i))))) for i in raw_list]
    A = np.array(new_list)

    B = np.full((4, 4), 0)
    nullblocks = np.where(A > 0)
    for i in zip(list(nullblocks[0]), list(nullblocks[1])):
        B[i[0]][i[1]] = 1

    if (A == raw_A).all() == False:
        make_random_num()

def left_press(event):
    global A, B

    # 先保存原矩阵
    raw_A = A

    raw_list = A.tolist()
    new_list = [list_merge(i) for i in raw_list]
    A = np.array(new_list)

    B = np.full((4, 4), 0)
    nullblocks = np.where(A > 0)
    for i in zip(list(nullblocks[0]), list(nullblocks[1])):
        B[i[0]][i[1]] = 1

    if (A == raw_A).all() == False:
        make_random_num()

def up_press(event):
    global A, B

    # 先保存原矩阵
    raw_A = A

    A = np.transpose(A)
    raw_list = A.tolist()
    new_list = [list_merge(i) for i in raw_list]
    A = np.array(new_list)
    A = np.transpose(A)

    B = np.full((4, 4), 0)
    nullblocks = np.where(A > 0)
    for i in zip(list(nullblocks[0]), list(nullblocks[1])):
        B[i[0]][i[1]] = 1

    if (A == raw_A).all() == False:
        make_random_num()

def down_press(event):
    global A, B

    # 先保存原矩阵
    raw_A = A

    A = np.transpose(A)
    raw_list = A.tolist()
    new_list = [list(reversed(list_merge(list(reversed(i))))) for i in raw_list]
    A = np.array(new_list)
    A = np.transpose(A)

    B = np.full((4, 4), 0)
    nullblocks = np.where(A > 0)
    for i in zip(list(nullblocks[0]), list(nullblocks[1])):
        B[i[0]][i[1]] = 1

    if (A == raw_A).all() == False:
        make_random_num()

make_random_num()

root.bind("", right_press)
root.bind("", up_press)
root.bind("", left_press)
root.bind("", down_press)

mainloop()

你可能感兴趣的:(小游戏,算法,python,游戏程序,矩阵,numpy)