4 Star 0 Fork 0

雷正伟/基于视觉控制的2048

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
Game_2048.py 21.29 KB
一键复制 编辑 原始数据 按行查看 历史
雷正伟 提交于 2023-06-15 08:00 . update Game_2048.py.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
2048游戏
本模块已完整实现2048游戏的算法及分数的计算算法
本游戏的界面采用python 标准库 tkinter 来实现
此界面的布局采用tkinter中的grid布局
此模块是单独模块,通过此模块直接可以运行
"""
import random # 导入随机模块random,主要用于随机生成新的数字及数字摆方位置
import math # 导入数学模块,用来计算分数
import pygame
from tkinter import *
from tkinter import messagebox
from PIL import ImageGrab
# MapData 绑定一个 4 x 4 列表,此列表为2048游戏地图,初始值如下:
MapData = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
# 背景颜色
game_bg_color = "#bbada0"
# 游戏中每个数据对应色块的颜色
MapColor = {
0: ("#cdc1b4", "#776e65"),
2: ("#eee4da", "#776e65"),
4: ("#ede0c8", "#f9f6f2"),
8: ("#f2b179", "#f9f6f2"),
16: ("#f59563", "#f9f6f2"),
32: ("#f67c5f", "#f9f6f2"),
64: ("#f65e3b", "#f9f6f2"),
128: ("#edcf72", "#f9f6f2"),
256: ("#edcc61", "#f9f6f2"),
512: ("#e4c02a", "#f9f6f2"),
1024: ("#e2ba13", "#f9f6f2"),
2048: ("#ecc400", "#f9f6f2"),
4096: ("#ae84a8", "#f9f6f2"),
8192: ("#b06ca8", "#f9f6f2"),
# ----其它颜色都与8192相同---------
2**14: ("#b06ca8", "#f9f6f2"),
2**15: ("#b06ca8", "#f9f6f2"),
2**16: ("#b06ca8", "#f9f6f2"),
2**17: ("#b06ca8", "#f9f6f2"),
2**18: ("#b06ca8", "#f9f6f2"),
2**19: ("#b06ca8", "#f9f6f2"),
2**20: ("#b06ca8", "#f9f6f2"),
}
class Game2048:
"""
2048游戏类
"""
def __init__(self):
"""
初始化数据
"""
self.gesture = ''
# 判断窗口状态字段
self.active = True
self.root = None
self._map_data = None
self.keymap = None
self.frame = None
self.label = None
self.label_score = None
self.number = None
self.restart_button = None
self.map_labels = None
self.show_scores= None
self.play_music=None
self.is_show_message = False
self.paused = False
#添加一个paused字段,用于判断游戏是否处于暂停
#/////////////////////////////////////
#初始化pygame.mixer(背景音乐)
Smusic=r"D:/Desktop/2048/gesture-recognition-based-2048-master/sMUSIC.mp3"
pygame.mixer.init()
#加载音乐文件
pygame.mixer.music.load(Smusic)
pygame.mixer.music.play(-1)
#/////////////////////////////////////
def screenshot(self):
#截取屏幕并保存截图
#获取游戏窗口坐标和大小
x,y = self.root.winfo_x(), self.root.winfo_y()
w,h = self.root.winfo_width(),self.root.winfo_height()
#截取屏幕
image = ImageGrab.grab(bbox=(x,y,x+w,y+h))
#保存截图
image.save("screenshot.png")
def pause(self):
"""
暂停游戏
"""
self.paused = True # 将paused字段设置为True,表示游戏处于暂停状态
if messagebox.askyesno("暂停游戏", "是否继续游戏?"):
self.paused = False # 如果用户选择继续游戏,则将paused字段设置为False,表示游戏继续进行
else:
self.root.quit() # 如果用户选择退出游戏,则退出游戏
def on_key_down(self, event):
"""
键盘按下处理函数
"""
keysym = event.keysym
if keysym == "p":
self.pause() # 如果用户按下p键,则暂停游戏
elif keysym in self.keymap and not self.paused: # 如果游戏没有处于暂停状态,则处理键盘事件
if self.keymap[keysym](): # 如果有数字移动
self.fill2() # 填充一个新的2
else:
self.show_move_instructions() #显示移动指示
self.update_ui()
if self.is_game_over():
mb = messagebox.askyesno(
title="gameover", message="游戏结束!\n是否退出游戏!")
if mb:
self.root.quit()
else:
self.reset()
self.update_ui()
def show_move_instructions(self):
"""
显示移动指示
"""
message = "使用方向键或手势移动数字块。向上移动请按'上箭头'或向上滑动手势,向下移动请按'下箭头'或向下滑动手势,向左移动请按'左箭头'或向左滑动手势,向右移动请按'右箭头'或向右滑动手势。"
self.show_message(message)
@staticmethod
def _new_tkinter_obj():
"""
生成tkinter窗口对象
:return tkinter窗口对象
"""
# 创建tkinter窗口
root = Tk()
# 设置标题文字
root.title('2048(手势识别的2048)')
# 固定宽和高
root.resizable(width=False, height=False)
return root
def _new_frame(self):
"""
生成frame窗口对象
:return Frame窗口对象
"""
# 创建一个frame窗口,此创建将容纳全部的widget 部件
frame = Frame(self.root, bg=game_bg_color)
frame.grid(sticky=N + E + W + S)
# 设置焦点能接收按键事件
frame.focus_set()
frame.bind("<Key>", self.on_key_down)
return frame
def on_key_down(self, event):
"""
键盘按下处理函数
"""
keysym = event.keysym
if keysym in self.keymap:
if self.keymap[keysym](): # 如果有数字移动
self.fill2() # 填充一个新的2
self.update_ui()
if self.is_game_over():
mb = messagebox.askyesno(
title="gameover", message="游戏结束!\n是否退出游戏!")
if mb:
self.root.quit()
else:
self.reset()
self.update_ui()
def gesture_handle(self, direction):
"""
处理手势
:param direction: str 方向 'up' 'down' 'right' 'left'
"""
move_flag = False
if direction == 'up':
move_flag = self.up()
elif direction == 'down':
move_flag = self.down()
elif direction == 'left':
move_flag = self.left()
elif direction == 'right':
move_flag = self.right()
# elif direction == 'fist':
#
if move_flag:
# 填充一个新的2
self.fill2()
self.update_ui()
if self.is_game_over():
mb = messagebox.askyesno(
title="GameOver", message="游戏结束!\n是否退出游戏!")
if mb:
self.root.quit()
else:
self.reset()
self.update_ui()
def get_map_labels(self):
"""
获取map_labels
"""
# 初始化图形界面
# 创建4x4的数字块
map_labels = [] # 游戏各方块的lable Widget
for r in range(4):
row = []
for c in range(len(self._map_data[0])):
value = self._map_data[r][c]
text = str(value) if value else ''
self.label = Label(self.frame, text=text, width=4, height=2,
font=("黑体", 30, "bold"))
self.label.grid(row=r, column=c, padx=5, pady=5, sticky=N + E + W + S)
row.append(self.label)
map_labels.append(row)
return map_labels
def set_label_button(self):
"""
设置显示分数的Label和重新开始按钮
"""
# 设置显示分数的Lable
self.label = Label(self.frame, text='分数', font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da")
self.label.grid(row=4, column=0, padx=5, pady=5)
self.label_score = Label(self.frame, text='0', font=("黑体", 30, "bold"),
bg="#bbada0", fg="#ffffff")
self.label_score.grid(row=4, columnspan=2, column=1, padx=5, pady=5)
self.restart_button = Button(self.frame, text='重新开始', font=("黑体", 16, "bold"),
bg="#8f7a66", fg="#f9f6f2", command=self.reset_game)
self.restart_button.grid(row=4, column=3, padx=5, pady=5)
self.show_scores = Button(self.frame,text='查看分数',font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da", command=self.show_ranking)
self.show_scores.grid(row=4, column=4, padx=6, pady=6)
self.set_scores = Button(self.frame,text='保存分数',font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da",command=self.save_score)
self.set_scores.grid(row=3, column=4, padx=5, pady=5)
#停止音乐按钮
self.stop_music = Button(self.frame,text='暂停音乐',font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da",command=self.stop_music_)
self.stop_music.grid(row=2, column=4, padx=5, pady=5)
#播放音乐按钮
self.play_music = Button(self.frame,text='播放音乐',font=("黑体", 30, "bold"),
bg="#bbada0", fg="#eee4da",command=self._play_music)
self.play_music.grid(row=1, column=4, padx=5, pady=5)
#截图功能
self.screenshot_button = Button(self.frame, text='截图', font=("黑体", 16, "bold"),
bg="#8f7a66", fg="#f9f6f2", command=self.screenshot)
self.screenshot_button.grid(row=4, column=2, padx=5, pady=5)
def reset_game(self):
"""
重新开始游戏
"""
self.reset()
self.update_ui()
#停止音乐
pygame.mixer.music.stop()
def stop_music(self):
"""
暂停音乐
"""
pygame.mixer.music.stop()
def reset(self):
"""
重新设置游戏数据,将地图恢复为初始状态,并加入两个数据 2 作用初始状态
"""
# _map_data.clear()
self._map_data[:] = []
self._map_data.append([0, 0, 0, 0])
self._map_data.append([0, 0, 0, 0])
self._map_data.append([0, 0, 0, 0])
self._map_data.append([0, 0, 0, 0])
# 在空白地图上填充两个2
self.fill2()
self.fill2()
def get_space_count(self):
"""
获取没有数字的方格的数量,如果数量为0则说有无法填充新数据,游戏即将结束
"""
count = 0
for r in self._map_data:
count += r.count(0)
return count
# 添加保存分数的函数
def save_score(self):
score=self.get_score()
with open('score.txt', 'a') as f:
f.write(str(score) + '\n')
# 添加读取历史排名的函数
def read_scores(self):
scores = []
with open('score.txt', 'r') as f:
for line in f:
line = line.strip()
if line.isdigit():
scores.append(int(line))
return scores
# 添加显示历史排名的函数
def show_ranking(self):
scores = self.read_scores()
scores.sort(reverse=True)
message = '排名\t分数\n'
for i, score in enumerate(scores):
message += f'{i+1}\t{score}\n'
self.show_message(message)
def show_message(self, message):
messagebox.showinfo('提示', message)
#/////////////////////////////////////////
def get_score(self):
"""
获取游戏的分数,得分规则是每次有两个数加在一起则生成相应的分数。
如 2 和 2 合并后得4分, 8 和 8 分并后得 16分.
根据一个大于2的数字就可以知道他共合并了多少次,可以直接算出分数:
如:
4 一定由两个2合并,得4分
8 一定由两个4合并,则计:8 + 4 + 4 得32分
... 以此类推
"""
score = 0
for r in self._map_data:
for c in r:
score += 0 if c < 4 else c * int((math.log(c, 2) - 1.0))
if score >= 20 and self.is_show_message!=True:
messagebox.showinfo('恭喜', '你已经达到了20分!')
self.is_show_message = True
return score # 导入数学模块
def fill2(self):
"""
填充2到空位置,如果填度成功返回True,如果已满,则返回False
"""
blank_count = self.get_space_count() # 得到地图上空白位置的个数
if 0 == blank_count:
#播放游戏结束音乐
pygame.mixer.music.load("D:/Desktop/2048/gesture-recognition-based-2048-master/end.mp3")
pygame.mixer.music.play()
return False
# 生成随机位置, 如,当只有四个空时,则生成0~3的数,代表自左至右,自上而下的空位置
pos = random.randrange(0, blank_count)
offset = 0
for row in self._map_data: # row为行row
for col in range(4): # col 为列,column
if 0 == row[col]:
if offset == pos:
# 把2填充到第row行,第col列的位置,返回True
row[col] = 2
return True
offset += 1
def is_game_over(self):
"""
判断游戏是否结束,如果结束返回True,否是返回False
"""
for r in self._map_data:
# 如果水平方向还有0,则游戏没有结束
if r.count(0):
return False
# 水平方向如果有两个相邻的元素相同,应当是可以合并的,则游戏没有结束
for i in range(3):
if r[i] == r[i + 1]:
return False
for c in range(4):
# 竖直方向如果有两个相邻的元素相同,应当可以合并的,则游戏没有结束
for r in range(3):
if self._map_data[r][c] == self._map_data[r + 1][c]:
return False
# 以上都没有,则游戏结束
return True
# 以下是2048游戏的基本算法,此种算法不是最优算法,但我认为这是目前最容易理解的算法
@staticmethod
def _left_move_number(line):
"""
左移一行数字,如果有数据移动则返回True,否则返回False:
如: line = [0, 2, 0, 8] 即表达如下一行:
+---+---+---+---+
| 0 | 2 | 0 | 8 | <----向左移动
+---+---+---+---+
此行数据需要左移三次:
第一次左移结果:
+---+---+---+---+
| 2 | 0 | 8 | 0 |
+---+---+---+---+
第二次左移结果:
+---+---+---+---+
| 2 | 8 | 0 | 0 |
+---+---+---+---+
第三次左移结果:
+---+---+---+---+
| 2 | 8 | 0 | 0 | # 因为最左则为2,所以8不动
+---+---+---+---+
最终结果: line = [4, 8, 0, 0]
"""
move_flag = False # 是否移动的标识,先假设没有移动
for _ in range(3): # 重复执行下面算法三次
for i in range(3): # i为索引
if 0 == line[i]: # 此处有空位,右侧相邻数字向左侧移动,右侧填空白
move_flag = True
line[i] = line[i + 1]
line[i + 1] = 0
return move_flag
@staticmethod
def _left_merge_number(line):
"""
向左侧进行相同单元格合并,合并结果放在左侧,右侧补零
如: line = [2, 2, 4, 4] 即表达如下一行:
+---+---+---+---+
| 2 | 2 | 4 | 4 |
+---+---+---+---+
全并后的结果为:
+---+---+---+---+
| 4 | 0 | 8 | 0 |
+---+---+---+---+
最终结果: line = [4, 8, 8, 0]
"""
for i in range(3):
if line[i] == line[i + 1]:
line[i] *= 2 # 左侧翻倍
line[i + 1] = 0 # 右侧归零
def _left_move_aline(self, line):
"""
左移一行数据,如果有数据移动则返回True,否则返回False:
如: line = [2, 0, 2, 8] 即表达如下一行:
+---+---+---+---+
| 2 | | 2 | 8 | <----向左移动
+---+---+---+---+
左移算法分为三步:
1. 将所有数字向左移动来填补左侧空格,即:
+---+---+---+---+
| 2 | 2 | 8 | |
+---+---+---+---+
2. 判断是否发生碰幢,如果两个相临且相等的数值则说明有碰撞需要合并,
合并结果靠左,右则填充空格
+---+---+---+---+
| 4 | | 8 | |
+---+---+---+---+
3. 再重复第一步,将所有数字向左移动来填补左侧空格,即:
+---+---+---+---+
| 4 | 8 | | |
+---+---+---+---+
最终结果: line = [4, 8, 0, 0]
"""
move_flag = False
if self._left_move_number(line):
move_flag = True
if self._left_merge_number(line):
move_flag = True
if self._left_move_number(line):
move_flag = True
return move_flag
def left(self):
"""
游戏左键按下时或向左滑动屏幕时的算法
"""
move_flag = False # move_flag 是否成功移动数字标志位,如果有移动则为真值,原地图不变则为假值
# 将第一行都向左移动.如果有移动就返回True
for line in self._map_data:
if self._left_move_aline(line):
move_flag = True
return move_flag
def right(self):
"""
游戏右键按下时或向右滑动屏幕时的算法
选将屏幕进行左右对调,对调后,原来的向右滑动即为现在的向左滑动
滑动完毕后,再次左右对调回来
"""
# 左右对调
for r in self._map_data:
r.reverse()
move_flag = self.left() # 向左滑动
# 再次左右对调
for r in self._map_data:
r.reverse()
return move_flag
def up(self):
"""
游戏上键按下时或向上滑动屏幕时的算法
先把每一列都自上而下放入一个列表中line中,然后执行向滑动,
滑动完成后再将新位置摆回到原来的一列中
"""
move_flag = False
for col in range(4): # 先取出每一列
# 把一列中的每一行数入放入到line中
line = [0, 0, 0, 0] # 先初始化一行,准备放入数据
for row in range(4):
line[row] = self._map_data[row][col]
# 将当前列进行上移,即line 左移
if self._left_move_aline(line):
move_flag = True
# 把左移后的 line中的数据填充回原来的一列
for row in range(4):
self._map_data[row][col] = line[row]
return move_flag
def down(self):
"""
游戏下键按下时或向下滑动屏幕时的算法
选将屏幕进行上下对调,对调后,原来的向下滑动即为现在的向上滑动
滑动完毕后,再次上下对调回来
"""
self._map_data.reverse()
move_flag = self.up() # 上滑
self._map_data.reverse()
return move_flag
def update_ui(self):
"""
刷新界面函数
根据计算出的f地图数据,更新各个Label的设置
"""
for r in range(4):
for c in range(len(self._map_data[0])):
self.number = self._map_data[r][c] # 设置数字
self.label = self.map_labels[r][c] # 选中Lable控件
self.label['text'] = str(self.number) if self.number else ''
self.label['bg'] = MapColor[self.number][0]
self.label['foreground'] = MapColor[self.number][1]
self.label_score['text'] = str(self.get_score()) # 重设置分数
def quit(self):
"""
点击窗口关闭按钮
"""
if messagebox.askyesno('2048', '你确定要退出游戏吗?'):
self.root.destroy()
self.root.quit()
def run(self):
"""
需要在此函数初始化tkinter对象,因为tkinter对象和mainloop方法必须处于同一线程
"""
# 初始化tkinter对象
self.root = self._new_tkinter_obj()
# 设置退出释放tcl解释器
self.root.protocol("WM_DELETE_WINDOW", self.quit)
self._map_data = MapData
# keymap不删除,可以同时保留键盘操作
self.keymap = {
'a': self.left,
'd': self.right,
'w': self.up,
's': self.down,
'Left': self.left,
'Right': self.right,
'Up': self.up,
'Down': self.down,
'q': self.root.quit,
}
# 初始化frame对象
self.frame = self._new_frame()
# 获取map_labels
self.map_labels = self.get_map_labels()
# 设置显示分数的Label和重新开始按钮
self.set_label_button()
self.fill2()
self.fill2()
# 更新界面
self.update_ui()
# 进入tkinter主事件循环
self.root.mainloop()
# 关闭窗口,修改状态
self.active = False
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/lei-zhengwei/2048-based-on-visual-control.git
git@gitee.com:lei-zhengwei/2048-based-on-visual-control.git
lei-zhengwei
2048-based-on-visual-control
基于视觉控制的2048
master

搜索帮助