本篇文章是利用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 (): game_field.draw(stdscr) 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 ): game_field.draw(stdscr) 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' ] 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: 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={} 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) 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 :] 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)
随机生成一个 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 ): 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 from collections import defaultdictfrom random import randrange, choiceimport curses actions = ['Up' , 'Left' , 'Down' , 'Right' , 'Restart' , 'Exit' ] 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: 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={} 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) 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 :] 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) 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 ): 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 (): game_field.draw(stdscr) 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 ): game_field.draw(stdscr) 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