python-应用-实现2048


本篇文章是利用python 实现2048小游戏
目的:为了提高自己的编程思路

环境:win8+python3.6+curses模块 Pycharm
适用人群:python入门+想用python做点事情的朋友们

大家可能都玩过2048,我记得那时候还蛮火的。不过那个时候我还没有接触编程,根本想不到自己今天还会写代码。。

正文

游戏规则:玩家通过 w s a d 控制数字移动方向,达成 2048 这个数字即获胜。

每次可以选择一个方向移动,数字便会朝这个方向移动,如果遇到相同数字就相加,遇到不同数字或者边界就停止移动。同时会在空白的地方生成 2 或者 4 的随机数字。通过不断相撞、相加,最后达成 2048 这个数字。

编程思路:

1.首先想象一下游戏的几个状态:

状态1:初始化状态(也就是刚开始游戏)
状态2:游戏中(Game ing……)
状态3:你赢了(Win)
状态4:你输了(Game Over)

2.用户行为

用户通过行为(action)改变来完成一个游戏状态到另一个游戏状态的该表,比如用户上移,下移,左移,右移,重置和退出。

3.由一个状态转化到另一个状态

分析一下从一个状态到下一个状态的所有可能性:
1.当上一个状态是初始化(Init)状态时,下一个状态肯定是游戏中(Game ing…)的状态。

2.当上一个状态为游戏中(Game ing…)的状态时,下一个状态要根据用户的输入来判断,如用户输入Restart,则下一个状态返回为初始化状态(Init),如用户输入Exit的时候,下一个状态转换为退出游戏(Exit),

用户还可以上下左右移动,这时候下一个状态可能是你输了(Game Over)或者你赢了(Win),再或者继续游戏。

3.当上一个状态为你输了(Game Over),用户输入Restart,下一个状态可能为初始化(Init)状态,用户输入Exit的时候,下一个状态转换为退出游戏(Exit)。

4.当上一个状态为你赢了(Win),用户输入Restart,下一个状态可能为初始化(Init)状态,用户输入Exit的时候,下一个状态转换为退出游戏(Exit)。

其中3和4神似,可以合并起来得到not_game的功能。

上述文字用图表示如下:

! [ 图片来自实验楼] ( http://m.qpic.cn/psc?/V10c1VbY1Y4Fvm/3pY6KhS62k*1Vm7UATlxq*dT9N4M9SFdcoa2exyDwTjMQRzdwgQxzI3eywPjhcCPWMtBAbo8u2e.zNSVqh5WGr9RvKTwx9YMevj37h84A.s!/b&bo=ZgKCAQAAAAADF9U!&rf=viewer_4&t=5 )

另外除非用户按代表退出的按键(Exit)或者游戏被玩死了,否则游戏一直被挂起。

根据以上分析,下面可以初略的搭建程序大致的框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def main(stdscr):
def init():
# 重置游戏棋盘
game_field.reset()
return 'Game'

def game():
# 画出 GameOver 或者 Win 的界面
game_field.draw(stdscr)
# 读取用户输入得到action,判断是重启游戏还是结束游戏
action = get_user_action(stdscr)

if action =='Restart':
return 'Init'
if action=='Exit':
return 'Exit'
if game_field.move(action):
if game_field.is_win():
return 'Win'
if game_field.is_gameover():
return 'Gameover'
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]

# 游戏盘的四种状态
state_actions={
'Init':init,
'Win':lambda:not_game('Win'),
'Gameover':lambda:not_game('Gameover'),
'Game':game
}

game_field = GameField(win=2048)
state = 'Init'
while state!='Exit':
state = state_actions[state]()

4.丰富框架
大体的骨架搭好了,下面让它完善起来。

分为两类:
第一类,把它叫做通用工具类。

这两个操作主要是用户在操作游戏之后对棋盘状态的变化以及修改,拥有这两个函数能够节省不少的代码量。

1
2
3
4
5
def transpose(field):
return [list(row) for row in zip(*field)]

def invert(field):
return [row[::-1] for row in field]

如下为用户输入函数:

1
2
3
4
5
6
7
8
9
10
11
actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit']
# 分别用 W(上),A(左),S(下),D(右),R(重置),Q(退出),进行输入来操作游戏,这里考虑到大写锁定键锁定的情况:
letter_codes = [ord(c) for c in 'WASDRQwasdrq']
actions_dict = dict(zip(letter_codes, actions*2))

def get_user_action(keyboard):
char = 'N'
#阻塞+循环,直到获得用户有效输入才返回对应行为:
while char not in actions_dict:
char = keyboard.getch()
return actions_dict[char]

第二大类:棋盘类
包含重置棋盘、棋盘走一步、判断输赢、绘制游戏界面、随机生成一个 2 或者 4、判断能否移动的功能。

重置棋盘

1
2
3
4
5
6
7
8
#重置棋盘
def reset(self):
if self.score>self.highscore:
self.highscore = self.score
self.score = 0
self.field = [[0 for i in range(self.width)] for j in range(self.height)]
self.spawn()
self.spawn()

棋盘走一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def move(self,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(len(row)-len(new_row))]
return new_row
def merge(row):
pair=False
new_row=[]
for i in range(len(row)):
if pair:
# 合并后,加入乘 2 后的元素在 0 元素后面
new_row.append(2*row[i])
self.score+=2*row[i]
pair=False
else:
if i+1<len(row) and row[i]==row[i+1]:
pair=True
# 不能合并,新列表中加入该元素
new_row.append(0)
else:
new_row.append(row[i])
assert len(new_row)==len(row)
return new_row
# 先挤到一块再合并再挤到一块
return tighten(merge(tighten(row)))

# 创建 moves 字典,把不同的棋盘操作作为不同的 key,对应不同的方法函数
moves={}
moves['Left'] = lambda filed:[move_row_left(row) for row in filed]
moves['Right'] = lambda filed:invert(moves['Left'](invert(filed)))
moves['Up'] = lambda field:transpose(moves['Left'](transpose(field)))
moves['Down'] = lambda field:transpose((moves['Right'](transpose(field))))

if direction in moves:
if self.move_is_possible(direction):
self.field = moves[direction](self.field)
#生成一个随机2或者4
self.spawn()
return True
else:
return False

判断输赢

1
2
3
4
5
6
# 判断输赢
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)

绘制游戏界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 + '+')[1:]
# defaultdict传入为函数时,可以实现value为某个常量,使用lambda可以使得代码更为简洁
separator = defaultdict(lambda: line)
#hasattr(object, name) 判断object对象中是否存在name属性
# python中的函数是一种对象,它有属于对象的属性。函数还可以自定义自己的属性。注意,属性是和对象相关的,和作用域无关。
if not hasattr(draw_hor_separator,'counter'):
draw_hor_separator.counter=0
cast(separator[draw_hor_separator.counter])
draw_hor_separator.counter+=1

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('HGHSCORE: ' + 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)

随机生成一个 2 或者 4

1
2
3
4
def spawn(self):
new_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 判断是否能移动
def move_is_possible(self, dictions):
def row_is_left_moveable(row):
def change(i):
# 当左边有空位(0),右边有数字时,可以向左移动
if row[i] == 0 and row[i + 1] != 0:
return True
# 当左边有一个数和右边的数相等时,可以向左合并
if row[i] != 0 and row[i + 1] == row[i]:
return True

return any(change(i) for i in range(len(row) - 1))

check = {}
# 判断矩阵每一行有没有可以左移动的元素
check['Left'] = lambda field: any(row_is_left_moveable(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 dictions in check:
return check[dictions](self.field)
else:
return False

附录:

  • 附所有源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!user/bin/env/python3
# -*- coding:utf-8 -*-
from collections import defaultdict
from random import randrange, choice
import curses

actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit']
# 分别用 W(上),A(左),S(下),D(右),R(重置),Q(退出),进行输入来操作游戏,这里考虑到大写锁定键锁定的情况:
letter_codes = [ord(c) for c in 'WASDRQwasdrq']
actions_dict = dict(zip(letter_codes, actions*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 = height
self.width = width
self.win_value =win #过关分数
self.score = 0 #当前分数
self.highscore =0 #最高分数
self.reset() #棋盘重置

#重置棋盘
def reset(self):
if self.score>self.highscore:
self.highscore = self.score
self.score = 0
self.field = [[0 for i in range(self.width)] for j in range(self.height)]
self.spawn()
self.spawn()

def move(self,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(len(row)-len(new_row))]
return new_row
def merge(row):
pair=False
new_row=[]
for i in range(len(row)):
if pair:
# 合并后,加入乘 2 后的元素在 0 元素后面
new_row.append(2*row[i])
self.score+=2*row[i]
pair=False
else:
if i+1<len(row) and row[i]==row[i+1]:
pair=True
# 不能合并,新列表中加入该元素
new_row.append(0)
else:
new_row.append(row[i])
assert len(new_row)==len(row)
return new_row
# 先挤到一块再合并再挤到一块
return tighten(merge(tighten(row)))

# 创建 moves 字典,把不同的棋盘操作作为不同的 key,对应不同的方法函数
moves={}
moves['Left'] = lambda filed:[move_row_left(row) for row in filed]
moves['Right'] = lambda filed:invert(moves['Left'](invert(filed)))
moves['Up'] = lambda field:transpose(moves['Left'](transpose(field)))
moves['Down'] = lambda field:transpose((moves['Right'](transpose(field))))

if direction in moves:
if self.move_is_possible(direction):
self.field = moves[direction](self.field)
#生成一个随机2或者4
self.spawn()
return True
else:
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 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 + '+')[1:]
# defaultdict传入为函数时,可以实现value为某个常量,使用lambda可以使得代码更为简洁
separator = defaultdict(lambda: line)
#hasattr(object, name) 判断object对象中是否存在name属性
# python中的函数是一种对象,它有属于对象的属性。函数还可以自定义自己的属性。注意,属性是和对象相关的,和作用域无关。
if not hasattr(draw_hor_separator,'counter'):
draw_hor_separator.counter=0
cast(separator[draw_hor_separator.counter])
draw_hor_separator.counter+=1

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('HGHSCORE: ' + 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)

# 随机生成一个 2 或者 4
def spawn(self):
new_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

# 判断是否能移动
def move_is_possible(self, dictions):
def row_is_left_moveable(row):
def change(i):
# 当左边有空位(0),右边有数字时,可以向左移动
if row[i] == 0 and row[i + 1] != 0:
return True
# 当左边有一个数和右边的数相等时,可以向左合并
if row[i] != 0 and row[i + 1] == row[i]:
return True

return any(change(i) for i in range(len(row) - 1))

check = {}
# 判断矩阵每一行有没有可以左移动的元素
check['Left'] = lambda field: any(row_is_left_moveable(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 dictions in check:
return check[dictions](self.field)
else:
return False


def main(stdscr):
def init():
# 重置游戏棋盘
game_field.reset()
return 'Game'

def game():
# 画出 GameOver 或者 Win 的界面
game_field.draw(stdscr)
# 读取用户输入得到action,判断是重启游戏还是结束游戏
action = get_user_action(stdscr)

if action =='Restart':
return 'Init'
if action=='Exit':
return 'Exit'
if game_field.move(action):
if game_field.is_win():
return 'Win'
if game_field.is_gameover():
return 'Gameover'
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]

# 游戏盘的四种状态
state_actions={
'Init':init,
'Win':lambda:not_game('Win'),
'Gameover':lambda:not_game('Gameover'),
'Game':game
}

curses.use_default_colors()

game_field = GameField(win=2048)
state = 'Init'
while state!='Exit':
state = state_actions[state]()

curses.wrapper(main)

在下载目录运行如下命令:
pip install –upgrade curses-2.2+utf8-cp36-cp36m-win_amd64.whl



觉得不错的话,给点打赏吧 ୧(๑•̀⌄•́๑)૭



wechat pay



alipay

python-应用-实现2048
http://yuting0907.github.io/2019/12/28/python实现2048/
作者
yuting
发布于
2019年12月28日
许可协议