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()               # 棋盘重置

第六部分 棋盘的操作

  1. 在棋盘中不为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
  1. 重置棋盘(矩阵清零)
    在重置棋盘后要随机产生两个随机数,重置最高分,将当前分数变为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()
  1. 判断能否移动
    只需要实现判断能否向左合并,剩下三个方向的判断,通过第四部分的矩阵运用,来实现。
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
  1. 合并
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
  1. 合并完后,判断是否胜利或者失败
#执行完判断是否胜利        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小游戏(详解)相关推荐

  1. QT小项目---2048小游戏

    目录 效果展示 游戏简介 游戏详情 操作指南 操作技巧 代码实现 QWidget.h Widget.cpp GameWidget.h GameWidget.cpp 总结 效果展示 游戏简介 游戏详情 ...

  2. 2022年全国大学生数学建模竞赛E题目-小批量物料生产安排详解+思路+Python代码时序预测模型(三)

    目录 前言 一.六种物料挑选 二.周数处理 三.时序预测模型 模型预测结果 建模的部分后续将会写出,想要了解更多的欢迎加博主微信,免费获取更多细化思路+模型! 点关注,防走丢,如有纰漏之处,请留言指教 ...

  3. 【C语言】扫雷小游戏详解

    [C语言]扫雷小游戏详解 前言: 还记得大明湖畔的夏雨荷,电脑课上的扫雷吗? ---------------------------是 他 吗--------------------------- 没 ...

  4. 新星降临,小悠游戏平台特性详解

    2015-3-10 18:32| 发布者: admin| 查看: 23| 评论: 0 深圳小悠娱乐科技有限公司,成立时间是2013年5月份,立足于做一个专业的游戏聚合平台,主打精品手柄类游戏,目标平台 ...

  5. 百度提前批-百度智能小程序(面经详解)

    文章目录 百度提前批-百度智能小程序(面经详解) 1.定位 2.z-index .层叠 3.作用域(scope) 4.单例模式 5.原型链 6.继承(借用构造函数,寄生组合继承,缺点是什么) 7.闭包 ...

  6. php小程序onload,微信小程序 loading 组件实例详解

    这篇文章主要介绍了微信小程序 loading 组件实例详解的相关资料,需要的朋友可以参考下 loading通常使用在请求网络数据时的一种方式,通过hidden属性设置显示与否 主要属性: wxml 显 ...

  7. Python Tkinter——数字拼图游戏详解版

    Python Tkinter 实践系列--数字拼图游戏详解版 import random #Python中的random是一个标准库用于生成随机数.随机整数.还有随机从数据集取数据. import t ...

  8. java课设小迷宫含代码_Java小项目之迷宫游戏的实现方法

    项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...

  9. java 迷宫游戏_Java小项目之迷宫游戏的实现方法

    项目要求: 一个网格迷宫由n行n列的单元格组成,每个大院个要么是空地(用0表示),要么是障碍物(用1表示),你的任务是找一条从起点到终点的移动序列,其中只能上下左右移动到相邻单元格.任何时候都不能在有 ...

最新文章

  1. Java Enumeration接口
  2. git遇到的一些问题
  3. 40.QT-QPropertyAnimationdong和QParallelAnimationGroup动画实现
  4. [基础题] * 9.(*)设计一个Student接口,以一维数组存储一个班级的学生姓名。
  5. 绝对路径用什么符号表示?当前目录、上层目录用什么表示?主目录用什么表示? 切换目录用什么命令?
  6. .NET系统架构改造的经验和教训
  7. ubuntu上如何安装tomcat
  8. 六、MySQL DML数据操纵语言学习笔记(插入、修改、删除详解 + 强化复习)
  9. 《程序员的呐喊》读书笔记
  10. 手动在viewpager的最后一页滑到第一页。
  11. 【论文阅读】EMNLP 2018 基于自适应的多轮解码机制的神经机器翻译模型
  12. TSX指令集之RTM无锁并发能加快速度吗?与mutex加锁比较
  13. 如何使用多种方法在 Mac 上截屏?
  14. C++,获取当前工作路径
  15. 主动降噪python_尝试使用Pyadi主动降噪时遇到错误
  16. 安装TypeScript
  17. 云原生究竟怎么落地?
  18. 网络虚拟化之virtio-net和vhost
  19. “马的遍历”问题的贪婪法解决算法
  20. 【动态规划信奥赛一本通】1285:最大上升子序列和(详细代码)

热门文章

  1. 【仿真建模】第一课:AnyLogic入门基础教程 - 行人库入门讲解
  2. 模块内高内聚?模块间低耦合?MVC+EF演示给你看!
  3. SSD202 驱动WIFI-ssw01b的STA模式
  4. linux编译符号那些事儿
  5. 谷歌打开后开始页面被hao123篡改
  6. 设置vscode默认打开浏览器为谷歌
  7. 腾讯云企业网盘正式入驻数字工具箱
  8. 云服务器上手是多么的简单?你看了就知道了
  9. 计算机图形学01:直线生成算法(DDA算法)
  10. 1D mesauring