首先导入需要的包:
1
2
3
|
import curses from random import randrange, choice from collections import defaultdict |
主逻辑
用户行为
所有的有效输入都可以转换为"上,下,左,右,游戏重置,退出"这六种行为,用 actions 表示
1
|
actions = [ 'Up' , 'Left' , 'Down' , 'Right' , 'Restart' , 'Exit' ] |
有效输入键是最常见的 W(上),A(左),S(下),D(右),R(重置),Q(退出),这里要考虑到大写键开启的情况,获得有效键值列表:
1
|
letter_codes = [ ord (ch) for ch in 'WASDRQwasdrq' ] |
将输入与行为进行关联:
1
|
actionsdict = dict ( zip (lettercodes, actions * 2 )) |
状态机
处理游戏主逻辑的时候我们会用到一种十分常用的技术:状态机,或者更准确的说是有限状态机(FSM)
你会发现 2048 游戏很容易就能分解成几种状态的转换。
state 存储当前状态, state_actions 这个词典变量作为状态转换的规则,它的 key 是状态,value 是返回下一个状态的函数:
1
2
3
4
|
Init: init() Game: game() Win: lambda : not_game( 'Win' ) Gameover: lambda : not_game( 'Gameover' ) |
Exit: 退出循环
状态机会不断循环,直到达到 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
|
def main(stdscr): def init(): #重置游戏棋盘 return 'Game' def not_game(state): #画出 GameOver 或者 Win 的界面 #读取用户输入得到action,判断是重启游戏还是结束游戏 responses = defaultdict( lambda : state) #默认是当前状态,没有行为就会一直在当前界面循环 responses[ 'Restart' ], responses[ 'Exit' ] = 'Init' , 'Exit' #对应不同的行为转换到不同的状态 return responses[action] def game(): #画出当前棋盘状态 #读取用户输入得到action if action = = 'Restart' : return 'Init' if action = = 'Exit' : return 'Exit' #if 成功移动了一步: if 游戏胜利了: return 'Win' if 游戏失败了: return 'Gameover' return 'Game' state_actions = { 'Init' : init, 'Win' : lambda : not_game( 'Win' ), 'Gameover' : lambda : not_game( 'Gameover' ), 'Game' : game } state = 'Init' #状态机开始循环 while state ! = 'Exit' : state = state_actions[state]() |
用户输入处理
阻塞+循环,直到获得用户有效输入才返回对应行为:
1
2
3
4
5
|
def get_user_action(keyboard): char = "N" while char not in actions_dict: char = keyboard.getch() return actions_dict[char] |
矩阵转置与矩阵逆转
加入这两个操作可以大大节省我们的代码量,减少重复劳动,看到后面就知道了。
矩阵转置:
1
2
|
def transpose(field): return [ list (row) for row in zip ( * field)] |
矩阵逆转(不是逆矩阵):
1
2
|
def invert(field): return [row[:: - 1 ] for row in field] |
创建棋盘
初始化棋盘的参数,可以指定棋盘的高和宽以及游戏胜利条件,默认是最经典的 4×4~2048。
1
2
3
4
5
6
7
8
|
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() #棋盘重置 |
棋盘操作
随机生成一个 2 或者 4
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
|
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 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() #### 一行向左合并 (注:这一操作是在 move 内定义的,拆出来是为了方便阅读) 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: 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))) |
棋盘走一步
通过对矩阵进行转置与逆转,可以直接从左移得到其余三个方向的移动操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def move( self , direction): def move_row_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 self .move_is_possible(direction): self .field = moves[direction]( self .field) self .spawn() return True 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
|
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): defrow_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 )) check = {} check[ 'Left' ] = lambda field: any (row_is_left_movable(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
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
|
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 :] 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 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) |
完成主逻辑
完成以上工作后,我们就可以补完主逻辑了!
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
|
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) #读取用户输入得到action action = get_user_action(stdscr) if action = = 'Restart' : return 'Init' if action = = 'Exit' : return 'Exit' if game_field.move(action): # move successful if 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() game_field = GameField(win = 32 ) state = 'Init' #状态机开始循环 while state ! = 'Exit' : state = state_actions[state]() |
运行
填上最后一行代码:
1
|
curses.wrapper(main) |
完整版代码地址:https://github.com/JLUNeverMore/easy_2048-in-200-lines
总结
以上所述是小编给大家介绍的200 行python 代码实现 2048 游戏,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!