Python 学习笔记,制作控制台窗口小游戏-2048
博文地址: http://www.tonglei.win/post/python/python-%E7%BB%88%E7%AB%AF2048%E5%B0%8F%E6%B8%B8%E6%88%8F%E5%AE%8C%E6%95%B4%E5%AE%9E%E7%8E%B0/
代码来源: https://github.com/JLUNeverMore/easy_2048-in-200-lines
作为Python入门用实例
1. 声明文档为 utf-8 编码
#-*- coding: utf-8 -*-
2. 引入 curses,然后画棋盘第一行
curses 是控制台的图形界面
curses介绍: https://docs.python.org/3/howto/curses.html
#-*- coding: utf-8 -*-
import curses
def main(stdscr):
curses.use_default_colors()
stdscr.addstr('+------' * 4 + '+')
stdscr.addstr('\n')
stdscr.addstr('| ' * 4 + '|')
stdscr.addstr('\n')
stdscr.addstr('| ' * 4 + '|')
stdscr.addstr('\n')
stdscr.addstr('+------' * 4 + '+')
stdscr.addstr('\n')
stdscr.getch()
curses.wrapper(main)
3. 画出棋盘的另外三行
声明方法使代码看起来简洁一些
#-*- coding: utf-8 -*-
import curses, time
def main(stdscr):
def cast(string): # 打印一行文本到控制台
stdscr.addstr(string + '\n')
def draw_hor_separator(): # 打印横向分割线
cast('+------' * 4 + '+')
def draw_row(): # 打印显示数字的格子
cast('| ' * 4 + '|')
curses.use_default_colors() # 设置控制台窗口的颜色
draw_hor_separator() # +------+------+------+------+
draw_row() # | | | | |
draw_hor_separator() # +------+------+------+------+
draw_row() # | | | | |
draw_hor_separator() # +------+------+------+------+
draw_row() # | | | | |
draw_hor_separator() # +------+------+------+------+
draw_row() # | | | | |
draw_hor_separator() # +------+------+------+------+
stdscr.getch() # 等待用户输入后结束
curses.wrapper(main)
4. 在格子中显示数字,并保持格子形状不变形
这个需求主要改动 draw_row() 方法,我们将一行的数字值传给该方法,并在方法中输出出来
#-*- coding: utf-8 -*-
import curses, time
def main(stdscr):
def cast(string): # 打印一行文本到控制台
stdscr.addstr(string + '\n')
def draw_hor_separator(): # 打印横向分割线
cast('+------' * 4 + '+')
def draw_row(row): # 打印显示数字的格子
# 将每列数字
cast(''.join([('|' + '{:^6}'.format(cell if cell > 0 else '')) for cell in row]) + '|')
curses.use_default_colors() # 设置控制台窗口的颜色
draw_hor_separator() # +------+------+------+------+
draw_row([0, 0, 0, 0]) # | | | | |
draw_hor_separator() # +------+------+------+------+
draw_row([2, 4, 8, 16]) # | 2 | 4 | 8 | 16 |
draw_hor_separator() # +------+------+------+------+
draw_row([32, 64, 128, 256]) # | 32 | 64 | 128 | 256 |
draw_hor_separator() # +------+------+------+------+
draw_row([512, 1024, 2048, 4096]) # | 512 | 1024 | 2048 | 4096 |
draw_hor_separator() # +------+------+------+------+
stdscr.getch() # 等待用户输入后结束
curses.wrapper(main)
5. 接收控制台指令
2048 游戏有6个按键操作:
- w, s, a, d 控制数字平移方向
- r 放弃当前游戏,重置棋盘
- q 退出游戏
接收指令通过 stdscr.getch() 方法实现
# stdscr.getch() # 等待用户输入后结束
char = ''
while char != 'q': # q 按下后退出
char = chr(stdscr.getch())
# if char == 'w':
# if char == 's':
# if char == 'a':
# if char == 'd':
# if char == 'r':
6. 初始化棋盘
将棋盘数据用二维数组对象存储起来,并且增加初始化、向上下左右移动、判断是否可移动方法game 。第一步先实现向左的移动和判断
#-*- coding: utf-8 -*-
import curses
from random import randrange, choice
def main(stdscr):
cells = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
def cast(string): # 打印一行文本到控制台
stdscr.addstr(string + '\n')
def draw_hor_separator(): # 打印横向分割线
cast('+------' * 4 + '+')
def draw_row(row): # 打印显示数字的格子
# 将每列数字
cast(''.join([('|' + '{:^6}'.format(cell if cell > 0 else '')) for cell in row]) + '|')
def draw_game():
global cells
stdscr.clear()
for row in cells:
draw_hor_separator() # +------+------+------+------+
draw_row(row) # | | | | |
draw_hor_separator()
def init_game(): # 清空棋盘
global cells
cells = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
def spawn(): # 随机生成一个数字,放到数值为0的位置
global cells
new_element = 4 if randrange(100) > 89 else 2 # 0.1 的概率出现4
(i, j) = choice([(i, j) for i in range(4) for j in range(4)])
(i, j) = choice([(i, j) for i in range(4) for j in range(4) if cells[i][j] == 0])
cells[i][j] = new_element
def move_is_possible(direction): # direction 'w', 's', 'a', 'd'
def row_is_left_movable(row):
def change(i):
if row[i] == 0 and row[i + 1] != 0:
return True
if row[i] != 0 and row[i + 1] == row[i]:
return True
return False
return any(change(i) for i in range(len(row) - 1))
global cells
return any(row_is_left_movable(row) for row in cells)
def move(direction): # 移动格子,贴边 > 合并 > 贴边
def move_row_left(row): # 向左移动,其他方向的判断为先旋转至向左,再判断,再还原至初始方向
def tighten(row): # 贴边
new_row = [i for i in row if i != 0]
new_row += [0 for i in range(4 - len(new_row))]
return new_row
def merge(row): # 合并
pair = False # 是否可合并
new_row = [] # 合并后的结果
for i in range(len(row)):
if pair: # 已找到可合并的单元格,进行合并
new_row.append(2 * row[i])
pair = False
else: # 未找到合并的单元格,查找下一个可合并的单元格
if i + 1 < 4 and row[i] == row[i + 1]: # 找到了下一个可合并的单元格对
pair = True
new_row.append(0)
else:
new_row.append(row[i])
return new_row
return tighten(merge(tighten(row)))
if move_is_possible(direction):
global cells
cells = [move_row_left(row) for row in cells]
spawn()
draw_game()
return True
else:
return False
curses.use_default_colors() # 设置控制台窗口的颜色
# stdscr.getch() # 等待用户输入后结束
char = ''
while char != 'q': # q 按下后退出
char = chr(stdscr.getch())
# if char == 'w':
# if char == 's':
if char == 'a':
move('a')
# if char == 'd':
if char == 'r':
init_game()
spawn()
spawn()
draw_game()
curses.wrapper(main)
7. 增加其他几个方向
- 向右 = 反转->向左->反转
- 向上 = 对折->向左->对折
- 向下 = 对折->向右->对折
反转的实现
return [row[::-1] for row in field]
对折的实现
return [list(row) for row in zip(*field)]
修改 move 方法
def move(direction): # 移动格子,贴边 > 合并 > 贴边
...
if move_is_possible(direction):
global cells
if direction == 'a':
cells = [move_row_left(row) for row in cells]
elif direction == 'd':
cells = invert([move_row_left(row) for row in invert(cells)])
elif direction == 'w':
cells = transpose([move_row_left(row) for row in transpose(cells)])
elif direction == 's':
cells = transpose(invert([move_row_left(row) for row in invert(transpose(cells))]))
spawn()
draw_game()
return True
else:
return False
修改 move_is_possible 方法
def move_is_possible(direction): # direction 'w', 's', 'a', 'd'
...
if direction == 'a':
return any(row_is_left_movable(row) for row in cells)
elif direction == 'd':
return any(row_is_left_movable(row) for row in invert(cells))
elif direction == 'w':
return any(row_is_left_movable(row) for row in transpose(cells))
elif direction == 's':
return any(row_is_left_movable(row) for row in invert(transpose(cells)))
return False
演示效果
完整代码
#-*- coding: utf-8 -*-
import curses
from random import randrange, choice
def main(stdscr):
cells = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
def cast(string): # 打印一行文本到控制台
stdscr.addstr(string + '\n')
def draw_hor_separator(): # 打印横向分割线
cast('+------' * 4 + '+')
def draw_row(row): # 打印显示数字的格子
cast(''.join([('|' + '{:^6}'.format(cell if cell > 0 else '')) for cell in row]) + '|')
def draw_game():
global cells
stdscr.clear()
for row in cells:
draw_hor_separator() # +------+------+------+------+
draw_row(row) # | | | | |
draw_hor_separator()
def init_game(): # 清空棋盘
global cells
cells = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
def spawn(): # 随机生成一个数字,放到数值为0的位置
global cells
new_element = 4 if randrange(100) > 89 else 2 # 0.1 的概率出现4
(i, j) = choice([(i, j) for i in range(4) for j in range(4)])
(i, j) = choice([(i, j) for i in range(4) for j in range(4) if cells[i][j] == 0])
cells[i][j] = new_element
# 对角翻转
def transpose(field):
return [list(row) for row in zip(*field)]
# 水平翻转
def invert(field):
return [row[::-1] for row in field]
def move_is_possible(direction): # direction 'w', 's', 'a', 'd'
def row_is_left_movable(row):
def change(i):
if row[i] == 0 and row[i + 1] != 0:
return True
if row[i] != 0 and row[i + 1] == row[i]:
return True
return False
return any(change(i) for i in range(len(row) - 1))
global cells
if direction == 'a':
return any(row_is_left_movable(row) for row in cells)
elif direction == 'd':
return any(row_is_left_movable(row) for row in invert(cells))
elif direction == 'w':
return any(row_is_left_movable(row) for row in transpose(cells))
elif direction == 's':
return any(row_is_left_movable(row) for row in invert(transpose(cells)))
return False
def move(direction): # 移动格子,贴边 > 合并 > 贴边
def move_row_left(row): # 向左移动,其他方向的判断为先旋转至向左,再判断,再还原至初始方向
def tighten(row): # 贴边
new_row = [i for i in row if i != 0]
new_row += [0 for i in range(4 - len(new_row))]
return new_row
def merge(row): # 合并
pair = False # 是否可合并
new_row = [] # 合并后的结果
for i in range(len(row)):
if pair: # 已找到可合并的单元格,进行合并
new_row.append(2 * row[i])
pair = False
else: # 未找到合并的单元格,查找下一个可合并的单元格
if i + 1 < 4 and row[i] == row[i + 1]: # 找到了下一个可合并的单元格对
pair = True
new_row.append(0)
else:
new_row.append(row[i])
return new_row
return tighten(merge(tighten(row)))
if move_is_possible(direction):
global cells
if direction == 'a':
cells = [move_row_left(row) for row in cells]
elif direction == 'd':
cells = invert([move_row_left(row) for row in invert(cells)])
elif direction == 'w':
cells = transpose([move_row_left(row) for row in transpose(cells)])
elif direction == 's':
cells = transpose(invert([move_row_left(row) for row in invert(transpose(cells))]))
spawn()
draw_game()
return True
else:
return False
curses.use_default_colors() # 设置控制台窗口的颜色
# stdscr.getch() # 等待用户输入后结束
char = ''
while char != 'q': # q 按下后退出
char = chr(stdscr.getch())
if char == 'w':
move('w')
if char == 's':
move('s')
if char == 'a':
move('a')
if char == 'd':
move('d')
if char == 'r':
init_game()
spawn()
spawn()
draw_game()
curses.wrapper(main)
至此,游戏的主要内容都已经完成了。
8. 增加按键提示、分数、游戏胜利与失败判定
这一步主要是增加细节处理,最终完成 2048 小游戏。
效果演示
最终代码如下:
#-*- coding: utf-8 -*-
import curses
from random import randrange, choice
cells = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
score = 0
def main(stdscr):
def cast(string): # 打印一行文本到控制台
stdscr.addstr(string + '\n')
def draw_hor_separator(): # 打印横向分割线
cast('+------' * 4 + '+')
def draw_row(row): # 打印显示数字的格子
cast(''.join([('|' + '{:^6}'.format(cell if cell > 0 else '')) for cell in row]) + '|')
def new_game():
init_game()
spawn()
spawn()
draw_game()
def draw_game():
global cells
stdscr.clear()
for row in cells:
draw_hor_separator() # +------+------+------+------+
draw_row(row) # | | | | |
draw_hor_separator()
cast('Score: ' + str(score))
if is_win():
cast("You win!")
elif is_gameover():
cast("Game over!")
else:
cast('(W)Up (S)Down (A)Left (D)Right')
cast('(R)Restart (Q)Exit')
def init_game(): # 清空棋盘
global cells
global score
cells = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
score = 0
def spawn(): # 随机生成一个数字,放到数值为0的位置
global cells
new_element = 4 if randrange(100) > 89 else 2 # 0.1 的概率出现4
(i, j) = choice([(i, j) for i in range(4) for j in range(4)])
(i, j) = choice([(i, j) for i in range(4) for j in range(4) if cells[i][j] == 0])
cells[i][j] = new_element
# 对角翻转
def transpose(field):
return [list(row) for row in zip(*field)]
# 水平翻转
def invert(field):
return [row[::-1] for row in field]
def move_is_possible(direction): # direction 'w', 's', 'a', 'd'
def row_is_left_movable(row):
def change(i):
if row[i] == 0 and row[i + 1] != 0:
return True
if row[i] != 0 and row[i + 1] == row[i]:
return True
return False
return any(change(i) for i in range(len(row) - 1))
global cells
if direction == 'a':
return any(row_is_left_movable(row) for row in cells)
elif direction == 'd':
return any(row_is_left_movable(row) for row in invert(cells))
elif direction == 'w':
return any(row_is_left_movable(row) for row in transpose(cells))
elif direction == 's':
return any(row_is_left_movable(row) for row in invert(transpose(cells)))
return False
def move(direction): # 移动格子,贴边 > 合并 > 贴边
def move_row_left(row): # 向左移动,其他方向的判断为先旋转至向左,再判断,再还原至初始方向
def tighten(row): # 贴边
new_row = [i for i in row if i != 0]
new_row += [0 for i in range(4 - len(new_row))]
return new_row
def merge(row): # 合并
pair = False # 是否可合并
new_row = [] # 合并后的结果
for i in range(len(row)):
if pair: # 已找到可合并的单元格,进行合并
global score
new_row.append(2 * row[i])
score += 2 * row[i]
pair = False
else: # 未找到合并的单元格,查找下一个可合并的单元格
if i + 1 < 4 and row[i] == row[i + 1]: # 找到了下一个可合并的单元格对
pair = True
new_row.append(0)
else:
new_row.append(row[i])
return new_row
return tighten(merge(tighten(row)))
if move_is_possible(direction):
global cells
if direction == 'a':
cells = [move_row_left(row) for row in cells]
elif direction == 'd':
cells = invert([move_row_left(row) for row in invert(cells)])
elif direction == 'w':
cells = transpose([move_row_left(row) for row in transpose(cells)])
elif direction == 's':
cells = transpose(invert([move_row_left(row) for row in invert(transpose(cells))]))
spawn()
draw_game()
return True
else:
return False
def is_win():
global cells;
return any(any(i >= 2048 for i in row) for row in cells)
def is_gameover():
return not any(move_is_possible(move) for move in ['w','s','a','d'])
curses.use_default_colors() # 设置控制台窗口的颜色
new_game()
char = ''
while char != 'q':
char = chr(stdscr.getch())
if char in ['w','s','a','d']:
move(char)
if char == 'r':
new_game()
curses.wrapper(main)
本页所有代码可从以下位置下载:https://gitee.com/tonglei0429/python-learn/tree/master/game-2048