python练手项目:2048实现与总结

本练手项目参考自实验楼:200 行 Python 代码实现 2048

涉及的知识点

  1. curses模块
    curses是一个python模块,它需要额外下载。
    这个模块可以实现文本展示。本程序的2048界面就是依赖curses“画”出来的。
    参考:Python Curses
  2. random模块
    random是python自带的模块。
    这个模块可以生成随机整数(randint)、随机小数(uniform)、在递增数列中随机选数(randrange)、在序列中选取随机元素(choice)、随机取序列的子集(sample)等。本例中使用是randrange、choice两个方法。
    参考:Python random模块(获取随机数)常用方法和使用例子
  3. collections模块
    collections是Python内建的一个集合模块,提供了许多有用的集合类。你可以理解成一个杂货铺。
    这个模块包括设置字典默认值(defaultdict)、字符计数(Counter)、双链表(deque)等方法。本例中使用了defaultdict方法
    参考:Python中collections的用法
  4. 状态机类程序的设计与实现
  5. lambda匿名函数
    lambda和普通的函数相比,就是省去了函数名称而已。好处是:
    (1) 省去定义函数的过程、例代码精简。
    (2) 对于不需要重用的函数,一次性定义,反而不用考虑如何命名,即时性很好。
    参考:关于Python中的lambda
  6. for遍历的灵活使用。
  7. 列表生成式: 列表生成式可以十分简便地生成一个序列,如一条语句生成平方序列:[x*x for x in range(1 , 11)]
  8. python切片的使用: b = a[::-1]实现了序列a的逆序排列。
  9. 其它函数
    ord(‘a’): 将字符串转化为acsii码对应数字
    zip(list1, list2): 将列表list1、list2中的元素分别对应取出,构造成元组。即映射。本例中,将zip巧用,“分别取出”即实现了由行取列,矩阵的转置也随即完成。
    range(): range(5) <==>range(0,5,1).即以0开始,以小于5结束,以步长为1取值。
    any(): 全或判断,即,对于any(iterable) ,如果iterable 中有一个为 True,则返回 True,只有在全部为 False时返回 False。

基本实现

通过分析,实现一个2048游戏,一共包含UI展示、游戏运行引擎、游戏数据的生成、游戏数据的演变等4个部分。

UI展示

本实验中,UI界面是通过curses模块实现的。

curses的初始化方法:wrapper

经常在导入curses模块以后,使用initscr()方法进行初始化。下面是初始化举例。

import curses
stdscr = curses.initscr()
# stdscr指代的就是显示器本器了。

完成初始化之后,还应当使用init_pair()等方法进行屏幕背景颜色、字体阴影颜色的设定。不过,若是不准备对curses界面进行精确控制、采用默认颜色即可的话,可以使用:wrapper方法。看如下对比:

import curses
def main1(stdscr)
    pass
curses.wrapper(main1)

# 上面的语句等价于:
import curses
def main2(stdscr)
    pass
stdscr = curses.initscr()
main2(stdscr)

结论:wrapper(main1)函数不仅会执行一次main1函数、完成curses的初始化,还会将初始化得到的stdscr强行传递给main1作为main1的参数。

curses的屏幕展示语句:addscr

我们的程序,主要是靠addscr这一方法来“绘制”,它的基本语法是:

stdscr.addscr(y, x, str or ch, attr)
# y,x 代表绘制的坐标。可省略
# str代表绘制的内容
# attr代表绘制时指定的属性。可省略。

理解基本的语法后,可以试着展示一条语句:、

import curses
def main1(stdscr):
    stdscr.addstr('hello world')
    ch=stdscr.getch() # 本句是为了使画面暂停。
curses.wrapper(main1)

输出如下:
在这里插入图片描述

curses正式开画

界面可分为得分、棋盘、信息提示,其中棋盘又包括画横线、画竖线。

  1. 准备函数
    screen即stdscr。screen.addstr()太过冗长,故重写,以简化。

    def cast(string):
        # 对addstr操作作简化
        screen.addstr(string + '\n')
    
  2. 得分

    screen.clear() # 清屏
    cast('SCORE: ' + str(self.score))
    cast('HIGHSCORE: ' + str(self.highscore))
    
  3. 棋盘
    画分割线:
    本例所画侵害线并非必须。简单地,我们可以有:

    def draw_hor_separator():
        line = ('+-----' * self.width + '+')
        cast(line)
    

    实验中给出的代码较复杂,可以作参考:

    def draw_hor_separator():
        line = '+' + ('+------' * self.width + '+')[1:]
        	#[1:]是python切片的使用,表示从1开始,这里的作用是省略掉'++---+--..'中的第一个'+'。
        	#对于x='abced',有:x[0]=='a'、x[1]=='b'
        separator = defaultdict(lambda: line)
        if not hasattr(draw_hor_separator, 'counter'):
            draw_hor_separator.counter = 0
        cast(separator[draw_hor_separator.counter])
        draw_hor_separator.counter += 1
    

    画一行:
    对于已经给定信息的每一行,我们需要的内容包括竖分割线、给定的数字。欲实现这些内容,都回归到“是填充数字还是填充空白”这一问题上来。
    这里,利用str.format()及类似{:^10d}的语句(居中并占10个空)来处理数字与空白间的关系。

    def draw_row(row):
        s = ''
        for i in row:
            if i > 0:
                s += '|{: ^6}'.format(i)
            else:
                s += '|      '
        s += '|' # 收尾分割符
        cast(s)
    

    当然,为了更加pythonic,我们可以将if-else合并:

    def draw_row(row):
        s = ''
        for i in row:
            s += '|{: ^6}'.format(i) if i != 0 else '|      '
        s += '|' # 收尾分割符
        cast(s)
    

    还有更pythonic的可能:join()方法列表生成式的结合使用。

    def draw_row(row):
        cast(''.join('|{: ^6}'.format(num) if num > 0 else '|      ' for num in row) + '|' + '\n')
    

    画出整个二维棋盘
    拥有画出一行的能力以后,让这个画的动作遍历棋盘

    for row in self.field:
        draw_hor_separator()
        draw_row(row)
    draw_hor_separator() # 补最后一行分割线。
    
  4. 信息提示
    提示用户可执行的操作和当前状态。在不同情况下有不同显示内容,伪代码如下:

    如果胜利(附带判断):
      输出“你赢了“
    如果失败(附带判断):
      输出”你输了“
    如果正常进行游戏(既非赢也非输):
      输出游戏操作提示
    
    显示一些需要常态显示的提示
    

    代码实现如下(胜负判断稍后完成):

    help_string1 = '(W)Up (S)Down (A)Left (D)Right'
    help_string2 = '     (R)Restart  (Q)Exit'
    gameover_string = '            GAME OVER'
    win_string = '            YOU WIN!'
    
    if self.is_win():           # 如果胜利(附带判断):
        cast(win_string)        #    输出“你赢了”
    elif self.is_gameover():    # 如果失败(附带判断):
        cast(gameover_string)   #    输出“你输了”
    else:                       # 既非赢也非输:
        cast(help_string1)      #    输出游戏操作提示
    
    cast(help_string2)          # 需要常态显示的提示
    

游戏运行引擎

引擎分析

从打开这个2048游戏的角度来讲,游戏共区别为初始化(Init)、游戏中(Game)、游戏胜利(Win)、游戏失败(Gameover)4个状态。它们可以如下图相互转化:
4个状态的相互转化
实现这样的转化,我们既要这个状态的名称、又要这个状态能通过某种方式执行。字典的特性完美地帮助了我们。

    state_actions = {
   
            'Init': init,
            'Win': lambda: not_game('Win'),
            'Gameover': lambda: not_game('Gameover'),
            'Game': game,
            }

字典的“键”以字符串的形式存储了状态名称,字典的“健值”甚至可以存储函数名。
配合函数的返回功能,状态机就在“程序运行”-“状态名称”-“程序运行”间相互转换。

从“程序运行”到“状态名称”

这一步,由return来实现,我们需要确保的,就是return返回的字符串刚好是state_actions 里面的键值。

    def init():
        # 重置
        print("do init.")
        return 'Game'
        # 上面的状态图告诉我们,这里只会返回到Game状态

根据状态图,我们可以有以下程序。

    def game():
        # 展示出当前画面,接收用户输入,并根据用户输入,反馈出继续游戏(Game)、游戏胜利(Win)、游戏失败(Gameover)三个结果。
        
  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值