最近我写的源码解析文章有点多了,想换个口味。今天决定练习一下Python,尝试完成一款当时风靡一时的2048小游戏。
游戏规则:《2048》是一款数字兼并游戏,玩家经过上下左右滑动来操控一切方块的移动,当相同数字的方块移动时会兼并成一个方块,数值相加。游戏的终极目标是组成一个数值为2048的方块。
在Python编程语言中,为了表明2048游戏的棋盘,能够选用二维列表的数据结构。在这个二维列表中,每个方块都会被一个数字所代表,其中0表明空格。
咱们写一段简略的代码,不需过多的UI结构,直接在操控台运转即可。让咱们来运用一下colorama。colorama是一个Python模块,专门用于在操控台和命令行中输出五颜六色文字,能够在各种操作系统上运用。
游戏逻辑
在这里简要介绍游戏逻辑,以便更好地了解事务代码。
- 初始化游戏棋盘,随机生成一个数字2。
- 查看游戏是否完毕,即棋盘是否填满且不能再移动。
- 完成上下左右滑动操作,兼并相同数字的方块。
- 判别是否到达2048,游戏成功。
- 根据用户输入的方向操作,更新棋盘状况。
在这里我将具体解说完成的逻辑。这里只涉及数字向左移动,无论用户是向上、向右仍是向下移动,都会被转换为向左移动。接下来我将演示如何根据向左移动的事务逻辑来完成向上移动。
再仔细审视一下向右移动的逻辑,同样的思路也能够应用到向右移动,即直接运用[::-1]来完成。完成了向左移动的兼并逻辑后,再运用[::-1]来康复原始次序即可。
处理了上一个问题后,咱们会进一步深入探讨如何在向左移动时优化兼并相同数字的操作。这个过程也相对简略,简略来说,便是对列表进行递归处理:假如前两个元素持平,则将它们兼并,并持续处理剩下部分;假如前两个元素不持平,则保留第一个元素并持续处理剩下部分。直至列表长度小于2时中止递归,最终回来处理完的成果列表。
主程序流程
根据上述基本逻辑,咱们将简略完成主程序流程。考虑到需求持续监听用户的键盘操作,因而咱们的主程序必须以一个while循环来完成。但是如何处理用户想要强制退出的情况呢?不能让用户关机,因而咱们需求设定一个退出键来完成用户主动退出的功用。
- 初始化游戏棋盘。
- 进入游戏循环,直到游戏完毕或许成功。
- 在每轮循环中,接受用户输入的方向(W/A/S/D键)。
- 判别是否退出游戏(Q键)
- 根据用户输入的方向更新棋盘状况(全部转化为左)。
- 判别游戏是否完毕或许成功。
from random import choice
from os import system
from readchar import readchar, readkey
from colorama import init
from termcolor import colored
N = 4
FGS = ['white', 'green', 'yellow', 'blue', 'cyan', 'magenta', 'red']
TERM = (10, 80)
OFFSET = (TERM[0] // 2 - 2, TERM[1] // 2 - 10)
pos = lambda y, x: 'x1b[%d;%dH' % (y, x)
color = lambda i: colored('%4d' % i, FGS[len('{:b}'.format(i)) % len(FGS)] if i else 'grey')
formatted = lambda m: 'n'.join(pos(y, OFFSET[1]) + ' '.join(color(i) for i in l) for l, y in zip(m, range(OFFSET[0], OFFSET[0] + 4))) ## 正方形格式化打印出二维数组
combine = lambda l: ([l[0] * 2] + combine(l[2:]) if l[0] == l[1] else [l[0]] + combine(l[1:])) if len(l) >= 2 else l ## 假如列表的前两个元素持平,就将它们兼并并递归地持续处理剩下部分;假如前两个元素不持平,则保留第一个元素并持续处理剩下部分。直到列表长度小于2时中止递归,回来成果列表。
expand = lambda l: [l[i] if i < len(l) else 0 for i in range(N)] ## 函数的作用是扩展列表 l 的长度至 N,假如 l 的长度小于 N,则在末尾添加足够多的 0 使其到达长度 N。
merge_left = lambda l: expand(combine(list(filter(bool, list(l)))))
merge_right = lambda l: merge_left(l[::-1])[::-1] ## 先倒置,按照左兼并相同,再倒置回来
left = lambda m: list(map(merge_left, m))
right = lambda m: list(map(merge_right, m))
up = lambda m: list(map(list, zip(*left(zip(*m))))) ## 先回转列表,然后左移再回转
down = lambda m: list(map(list, zip(*right(zip(*m))))) ## 先回转列表,然后右移再回转
add_num_impl = lambda m, p: m[p[0]].__setitem__(p[1], 2) # 这里写死的2,其实能够挑选一个(2和4)随机值添加游戏体验
add_num = lambda m: add_num_impl(m, choice([(x, y) for x in range(N) for y in range(N) if not m[x][y]])) ## 随机挑选一个符合要求的二维坐标地址
win = lambda m: 2048 in sum(m, []) ## 只需存在2048即赢
gameover = lambda m: all(m == t(m) for t in trans.values()) ## 假如一切变换都相同则完毕游戏
draw = lambda m: system("CLS") or print(formatted(m)) ## 打印数组,这里注意下,假如CLS找不到命令可切换clear,意图是清空操控台
trans = {'a': left, 'd': right, 'w': up, 's': down}
m = [[0] * N for _ in range(N)] ## 初始化一个N x N的二维列表,而且每个元素值都是0
init() ## 命令行输出五颜六色文字
add_num(m)
draw(m)
while True:
while True:
move = readkey()
if move in list(trans.keys()) + ['q']:
break
if move == 'q': ## 键盘‘Q’是游戏退出
break
n = trans[move](m)
if n != m:
add_num(n)
m = n
draw(m)
if win(m):
print('n' + colored('(^_^) You Win!'.center(TERM[1]), 'yellow'))
break
elif gameover(m):
print('n' + colored('(>﹏<) Game Over!'.center(TERM[1]), 'red'))
break
总结
最终,咱们成功完成了经典游戏2048。现在,能够直接运转代码。本游戏利用二维列表数据结构来表明游戏棋盘,并在操控台中利用colorama模块完成了五颜六色文字输出。游戏的逻辑包含初始化棋盘、查看游戏是否完毕、执行滑动操作、查看成功条件等。经过简略的代码,咱们完成了主程序流程,监听用户操作并更新棋盘状况,使得游戏具有交互逻辑。其中,最具挑战性的部分在于方向转换、兼并和扩展数组。其他操作则相对基础。