python小项目——2048小游戏(详解)
2048游戏
- 原版游戏地址
- 第一部分 导入所需要的库
- 第二部分 确认游戏键位设置,并和对应的操作关联
- 第三部分 获取用户输入的值,并直到有效键位
- 第四部分 对矩阵的应用,减少代码量
- 第五部分 创建棋盘
- 第六部分 棋盘的操作
- 第七部分 画棋盘
- 第八部分 主逻辑!
- 第九部分 合体代码!
原版游戏地址
项目通过 Python 实现了一个在终端上运行的 2048 小游戏
2048原版游戏地址
第一部分 导入所需要的库
# curses 用来在终端上显示图形界面
import curses
# random 模块用来生成随机数
from random import randrange, choice
# collections 提供了一个字典的子类 defaultdict。可以指定 key 值不存在时,value 的默认值。
from collections import defaultdict
第二部分 确认游戏键位设置,并和对应的操作关联
我们把键位设置的有效输入都可以转换为"上,下,左,右,游戏重置,退出"这六种行为。 W(上),A(左),S(下),D(右),R(重置),Q(退出),这里要考虑到大写键开启的情况,并将键位的ascii值和对应的操作关联。
#ord()函数以一个字符作为参数,返回参数的ascii的数值,便于和后面的键位关联
letter_codes = [ord(ch) for ch in 'WASDRQwasdrq']
actions = ['Up','Left','Down','Right','Restart','Exit']
#键位关联
actions_dict = dict(zip(letter_codes,actioins * 2))
第三部分 获取用户输入的值,并直到有效键位
def get_user_action(keyboard):char = 'N'while char not in actions_dict:char = keyboard.getch()#返回对应的键位return actions_dict[char]
第四部分 对矩阵的应用,减少代码量
这个游戏是将相同数字通过合并来达到某一个数值来取得胜利。我们的操作有向左合并,向右合并,向上合并,向下合并。
但是我们将所有方向的合并函数写出来,会显得代码冗余和繁重,于是我们通过矩阵的转置和逆转,和矩阵的向左合并来实现剩余三个方向的合并函数。
矩阵的转置:
矩阵的逆序:
这里只是将矩阵的每一行倒序,和逆矩阵的概念无关。
首先我们实现向左合并的代码(接下来会解释),然后我们来实现向右,上,下合并的操作。
向右合并:矩阵逆转,向左合并,矩阵逆转。
向上合并:矩阵转置,向左合并,矩阵逆转。
向下合并:矩阵转置,向右合并(参考第一步),矩阵转置。
#矩阵的转置
def transpose(field):return [list(row) for row in zip(*field)]#矩阵的逆转
def invert(field):return [row[::-1] for row in field]
第五部分 创建棋盘
初始化棋盘的参数,可以指定棋盘的高和宽以及游戏胜利条件,默认是最经典的 4x4 ~ 2048。
class GameField(object):def __init__(self, height=4, width=4, win=2048):self.height = height # 高self.width = width # 宽self.win_value = 2048 # 过关分数self.score = 0 # 当前分数self.highscore = 0 # 最高分self.reset() # 棋盘重置
第六部分 棋盘的操作
- 在棋盘中不为0的随机位置,生成一个随机的2或者4
def spawn(self):# 从 100 中取一个随机数,如果这个随机数大于 89,new_element 等于 4,否则等于 2new_element = 4 if randrange(100) > 89 else 2# 得到一个随机空白位置的元组坐标(i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0])self.field[i][j] = new_element
- 重置棋盘(矩阵清零)
在重置棋盘后要随机产生两个随机数,重置最高分,将当前分数变为0
#重置棋盘后,随机获取两个随机数def reset(self):if self.score > self.highscore:self.highscore = self.scoreself.score = 0self.field = [[0 for i in range(self.width)] for j range(self.height)]self.spawn()self.spawn()
- 判断能否移动
只需要实现判断能否向左合并,剩下三个方向的判断,通过第四部分的矩阵运用,来实现。
def move_is_possible(self,direction):#判断当前行能否左移def row_is_left_move(row):def change(i):if row[i] == 0 and row[i + 1] != 0:return Trueif row[i] != 0 and row[i + 1] == row[i]:return Truereturn Falsereturn any(change(i) for i in range(len(row) - 1))check = {}#判断能否向左合并的匿名函数,并将其放进字典中cheak['Left'] = lambda field: any(row_is_left_move(row) for row in field)#判断能否向右合并的匿名函数,并将其放进字典中check['Right'] = lambda field: check['Left'](invert(field))#判断能否向上合并的匿名函数,并将其放进字典中check['Up'] = lambda field: check['Left'](transpose(field))#判断能否向下合并的匿名函数,并将其放进字典中check['Down'] = lambda field: check['Right'](transpose(field))#判断if direction in check:return check[direction](self.field)else:return False
- 合并
def move(self,direction):#一行向左移动,参数为一行的数字def move_row_left(row):#把数字集中在左边def move_left(row):new_row = [i for i in row if i != 0]new_row += [0 for i in range(len(row) - len(new_row))]return new_row#把相同的数字合并def merge(row):flag = Falsenew_row = []for i in range(len(row)):if flag:new_row.append(row[i] * 2)self.score += row[i] * 2flag = Falseelse:if i + 1 < len(row) and row[i] == row[i + 1]:flag = Truenew_row.append(0)else:new_row.append(row[i])return new_row#实现一行向左合并return move_left(merge(move_left(row)))#利用矩阵的转置和逆转实现各个方向的合并,并在字典内设置匿名函数moves = {}moves['Left'] = lambda field: [move_row_left(row) for row in field]moves['Right'] = lambda field: invert(moves['Left'](invert(field)))moves['Up'] = lambda field: transpose(moves['Left'](transpose(field)))moves['Down'] = lambda field: transpose(moves['Right'](transpose(field)))#如果符合要求则合并,并产生一个随机数if direction in moves:if seif.move_is_possible(direction):self.field = moves[direction](self.field)self.spawn()return Trueelse:return False
- 合并完后,判断是否胜利或者失败
#执行完判断是否胜利 def is_win(self):return any(any(i >= self.win_value for i in row) for row in self.field)#执行完判断是否失败def is_gameover(self):return not any(self.move_is_possible(move) for move in actions)
第七部分 画棋盘
每走一步,就会重新画一次棋盘,这部分的关键在于 cast 函数。在 draw 函数传入的 screen 参数表示绘画的窗体对象, screen.addstr() 的作用是绘制字符,screen.clear() 的作用是清空屏幕,达到刷新的目的。
def draw(self, screen):help_string1 = '(W)Up (S)Down (A)Left (D)Right'help_string2 = ' (R)Restart (Q)Exit'gameover_string = ' GAME OVER'win_string = ' YOU WIN!'# 绘制函数def cast(string):# addstr() 方法将传入的内容展示到终端screen.addstr(string + '\n')# 绘制水平分割线的函数def draw_hor_separator():line = '+' + ('+------' * self.width + '+')[1:]cast(line)# 绘制竖直分割线的函数def draw_row(row):cast(''.join('|{: ^5} '.format(num) if num > 0 else '| ' for num in row) + '|')# 清空屏幕screen.clear()# 绘制分数和最高分cast('SCORE: ' + str(self.score))if 0 != self.highscore:cast('HIGHSCORE: ' + str(self.highscore))# 绘制行列边框分割线for row in self.field:draw_hor_separator()draw_row(row)draw_hor_separator()# 绘制提示文字if self.is_win():cast(win_string)else:if self.is_gameover():cast(gameover_string)else:cast(help_string1)cast(help_string2)
第八部分 主逻辑!
处理游戏主逻辑的时候会用到一种十分常用的技术:状态机
你会发现 2048 游戏很容易就能分解成几种状态的转换。
状态机会不断循环,直到达到 Exit 终结状态结束程序。
init函数用来初始化我们的游戏棋盘,使游戏变成初始状态。
not_game函数表示的是游戏结束时的状态(会停止更新棋盘,等待用户操作)。游戏结束时,只有胜利和失败两种结果。在展示这两种结果的同时,我们还需要提供“Restart”和“Exit”功能。
game函数表示的是游戏进行时的状态,在不重新开始或退出的情况下,只要游戏没有胜利或失败,就会一直处于游戏状态。
#主逻辑
def main(stdscr):def init():#重置游戏棋盘game_field.reset()return 'Game'def not_game(state):# 画出 GameOver 或者 Win 的界面game_field.draw(stdscr)# 读取用户输入得到action,判断是重启游戏还是结束游戏action = get_user_action(stdscr)responses = defaultdict(lambda: state) # 默认是当前状态,没有行为就会一直在当前界面循环responses['Restart'], responses['Exit'] = 'Init', 'Exit' # 对应不同的行为转换到不同的状态return responses[action]def game():# 画出当前棋盘状态game_field.draw(stdscr)# 读取用户输入得到actionaction = get_user_action(stdscr)if action == 'Restart':return 'Init'if action == 'Exit':return 'Exit'if game_field.move(action): # move successfulif game_field.is_win():return 'Win'if game_field.is_gameover():return 'Gameover'return 'Game'state_actions = {'Init': init,'Win': lambda: not_game('Win'),'Gameover': lambda: not_game('Gameover'),'Game': game}curses.use_default_colors()# 设置终结状态最大数值为 32game_field = GameField(win=32)state = 'Init'# 状态机开始循环while state != 'Exit':state = state_actions[state]()curses.wrapper(main)
第九部分 合体代码!
#2048小游戏
#curses用来在终端上显示图形的界面
import curses
#random 提供随机数和随机位置
from random import randrange, choice
#collections 的defaultdict提供一个子类,可以指定key值不存在时,value的默认值
from collections import defaultdict#第一部分,确认游戏键位设置,并和对应的操作关联
#ord()函数以一个字符作为参数,返回参数的ascii的数值,便于和后面的键位关联
letter_codes = [ord(ch) for ch in 'WASDRQwasdrq']
actions = ['Up','Left','Down','Right','Restart','Exit']
#键位关联
actions_dict = dict(zip(letter_codes,actioins * 2))#第二部分获取用户输入的值,并直到有效键位
def get_user_action(keyboard):char = 'N'while char not in actions_dict:char = keyboard.getch()#返回对应的键位return actions_dict[char]#第三部分,对矩阵的应用,减少代码量
#矩阵的转置
def transpose(field):return [list(row) for row in zip(*field)]#矩阵的逆转
def invert(field):return [row[::-1] for row in field]#第四部分
#创建棋盘
class GameField(object):def __init__(self, height=4, width=4, win=2048):self.height = heightself.width = widthself.win_value = winself.score = 0self.highscore = 0self.reset() #棋盘重置#重置棋盘后,随机获取两个随机数def reset(self):if self.score > self.highscore:self.highscore = self.scoreself.score = 0self.field = [[0 for i in range(self.width)] for j range(self.height)]self.spawn()self.spawn()#产生随机数def spawn(self):new_value = 4 if randrange(100) > 89 else 2(i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0])self.field[i][j] = new_value#第五部分#移动def move(self,direction):#一行向左移动,参数为一行的数字def move_row_left(row):#把数字集中在左边def move_left(row):new_row = [i for i in row if i != 0]new_row += [0 for i in range(len(row) - len(new_row))]return new_row#把相同的数字合并def merge(row):flag = Falsenew_row = []for i in range(len(row)):if flag:new_row.append(row[i] * 2)self.score += row[i] * 2flag = Falseelse:if i + 1 < len(row) and row[i] == row[i + 1]:flag = Truenew_row.append(0)else:new_row.append(row[i])return new_row#实现一行向左合并return move_left(merge(move_left(row)))#利用矩阵的转置和逆转实现各个方向的合并,并在字典内设置匿名函数moves = {}moves['Left'] = lambda field: [move_row_left(row) for row in field]moves['Right'] = lambda field: invert(moves['Left'](invert(field)))moves['Up'] = lambda field: transpose(moves['Left'](transpose(field)))moves['Down'] = lambda field: transpose(moves['Right'](transpose(field)))#如果符合要求则合并if direction in moves:if seif.move_is_possible(direction):self.field = moves[direction](self.field)self.spawn()return Trueelse:return False#执行完判断是否胜利 def is_win(self):return any(any(i >= self.win_value for i in row) for row in self.field)#执行完判断是否失败def is_gameover(self):return not any(self.move_is_possible(move) for move in actions)#判断是否能够移动def move_is_possible(self,direction):#判断当前行能否左移def row_is_left_move(row):def change(i):if row[i] == 0 and row[i + 1] != 0:return Trueif row[i] != 0 and row[i + 1] == row[i]:return Truereturn Falsereturn any(change(i) for i in range(len(row) - 1))check = {}cheak['Left'] = lambda field: any(row_is_left_move(row) for row in field)check['Right'] = lambda field: check['Left'](invert(field))check['Up'] = lambda field: check['Left'](transpose(field))check['Down'] = lambda field: check['Right'](transpose(field))if direction in check:return check[direction](self.field)else:return False#画棋盘def draw(self,screen):help_string1 = '(W)Up (S)Down (A)Left (D)Right'help_string2 = ' (R)Restart (Q)Exit'gameover_string = ' GAME OVER'win_string = ' YOU WIN!'#将传入的内容展示到终端def cast(string):screen.addstr(string + '\n')#水平分割线def draw_hor_separator():line = '+------' * self.width + '+'cast(line)def draw_row(row):cast(''.join('|{:^6}'.format(num) if num > 0 else '| ' for num in row) + '|')screen.clear()cast('SCORE: ' + str(self.score))if self.highscore != 0:cast('HIGHSCORE: ' + str(self.highscore))for row in self.field:draw_hor_separator()draw_row(row)draw_hor_separator()#判断if self.is_win():cast(win_str)else:if self.is_gameover():cast(gameover_str)else:cast(help_str1)cast(help_str2)#主逻辑
def main(stdscr):def init():#重置游戏棋盘game_field.reset()return 'Game'def not_game(state):# 画出 GameOver 或者 Win 的界面game_field.draw(stdscr)# 读取用户输入得到action,判断是重启游戏还是结束游戏action = get_user_action(stdscr)responses = defaultdict(lambda: state) # 默认是当前状态,没有行为就会一直在当前界面循环responses['Restart'], responses['Exit'] = 'Init', 'Exit' # 对应不同的行为转换到不同的状态return responses[action]def game():# 画出当前棋盘状态game_field.draw(stdscr)# 读取用户输入得到actionaction = get_user_action(stdscr)if action == 'Restart':return 'Init'if action == 'Exit':return 'Exit'if game_field.move(action): # move successfulif game_field.is_win():return 'Win'if game_field.is_gameover():return 'Gameover'return 'Game'state_actions = {'Init': init,'Win': lambda: not_game('Win'),'Gameover': lambda: not_game('Gameover'),'Game': game}curses.use_default_colors()# 设置终结状态最大数值为 32game_field = GameField(win=32)state = 'Init'# 状态机开始循环while state != 'Exit':state = state_actions[state]()curses.wrapper(main)
python小项目——2048小游戏(详解)相关推荐
- QT小项目---2048小游戏
目录 效果展示 游戏简介 游戏详情 操作指南 操作技巧 代码实现 QWidget.h Widget.cpp GameWidget.h GameWidget.cpp 总结 效果展示 游戏简介 游戏详情 ...
- 2022年全国大学生数学建模竞赛E题目-小批量物料生产安排详解+思路+Python代码时序预测模型(三)
目录 前言 一.六种物料挑选 二.周数处理 三.时序预测模型 模型预测结果 建模的部分后续将会写出,想要了解更多的欢迎加博主微信,免费获取更多细化思路+模型! 点关注,防走丢,如有纰漏之处,请留言指教 ...
- 【C语言】扫雷小游戏详解
[C语言]扫雷小游戏详解 前言: 还记得大明湖畔的夏雨荷,电脑课上的扫雷吗? ---------------------------是 他 吗--------------------------- 没 ...
- 新星降临,小悠游戏平台特性详解
2015-3-10 18:32| 发布者: admin| 查看: 23| 评论: 0 深圳小悠娱乐科技有限公司,成立时间是2013年5月份,立足于做一个专业的游戏聚合平台,主打精品手柄类游戏,目标平台 ...
- 百度提前批-百度智能小程序(面经详解)
文章目录 百度提前批-百度智能小程序(面经详解) 1.定位 2.z-index .层叠 3.作用域(scope) 4.单例模式 5.原型链 6.继承(借用构造函数,寄生组合继承,缺点是什么) 7.闭包 ...
- php小程序onload,微信小程序 loading 组件实例详解
这篇文章主要介绍了微信小程序 loading 组件实例详解的相关资料,需要的朋友可以参考下 loading通常使用在请求网络数据时的一种方式,通过hidden属性设置显示与否 主要属性: wxml 显 ...
- Python Tkinter——数字拼图游戏详解版
Python Tkinter 实践系列--数字拼图游戏详解版 import random #Python中的random是一个标准库用于生成随机数.随机整数.还有随机从数据集取数据. import t ...
- java课设小迷宫含代码_Java小项目之迷宫游戏的实现方法
项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...
- java 迷宫游戏_Java小项目之迷宫游戏的实现方法
项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...
最新文章
- Java Enumeration接口
- git遇到的一些问题
- 40.QT-QPropertyAnimationdong和QParallelAnimationGroup动画实现
- [基础题] * 9.(*)设计一个Student接口,以一维数组存储一个班级的学生姓名。
- 绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示? 切换目录用什么命令?
- .NET系统架构改造的经验和教训
- ubuntu上如何安装tomcat
- 六、MySQL DML数据操纵语言学习笔记(插入、修改、删除详解 + 强化复习)
- 《程序员的呐喊》读书笔记
- 手动在viewpager的最后一页滑到第一页。
- 【论文阅读】EMNLP 2018 基于自适应的多轮解码机制的神经机器翻译模型
- TSX指令集之RTM无锁并发能加快速度吗?与mutex加锁比较
- 如何使用多种方法在 Mac 上截屏?
- C++,获取当前工作路径
- 主动降噪python_尝试使用Pyadi主动降噪时遇到错误
- 安装TypeScript
- 云原生究竟怎么落地?
- 网络虚拟化之virtio-net和vhost
- “马的遍历”问题的贪婪法解决算法
- 【动态规划信奥赛一本通】1285:最大上升子序列和(详细代码)