1 #-*- coding:utf-8 -*- 2 import curses 3 from random import randrange, choice # generate and place new tile 4 from collections import defaultdict 5 6 # user`s action 7 letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] 8 actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit'] 9 actions_dict = dict(zip(letter_codes, actions * 2)) 10 11 def get_user_action(keyboard): 12 char = "N" 13 while char not in actions_dict: 14 char = keyboard.getch() 15 return actions_dict[char] 16 17 def transpose(field): 18 return [list(row) for row in zip(*field)] 19 20 def invert(field): 21 return [row[::-1] for row in field] 22 23 class GameField(object): 24 def __init__(self, height=4, width=4, win=2048): 25 self.height = height 26 self.width = width 27 self.win_value = win 28 self.score = 0 29 self.highscore = 0 30 self.reset() 31 32 def reset(self): 33 if self.score > self.highscore: 34 self.highscore = self.score 35 self.score = 0 36 self.field = [[0 for i in range(self.width)] for j in range(self.height)] 37 self.spawn() 38 self.spawn() 39 40 def move(self, direction): 41 def move_row_left(row): 42 def tighten(row): # squeese non-zero elements together 43 new_row = [i for i in row if i != 0] 44 new_row += [0 for i in range(len(row) - len(new_row))] 45 return new_row 46 47 def merge(row): 48 pair = False 49 new_row = [] 50 for i in range(len(row)): 51 if pair: 52 new_row.append(2 * row[i]) 53 self.score += 2 * row[i] 54 pair = False 55 else: 56 if i + 1 < len(row) and row[i] == row[i + 1]: 57 pair = True 58 new_row.append(0) 59 else: 60 new_row.append(row[i]) 61 assert len(new_row) == len(row) 62 return new_row 63 return tighten(merge(tighten(row))) 64 65 moves = {} 66 moves['Left'] = lambda field: 67 [move_row_left(row) for row in field] 68 moves['Right'] = lambda field: 69 invert(moves['Left'](invert(field))) 70 moves['Up'] = lambda field: 71 transpose(moves['Left'](transpose(field))) 72 moves['Down'] = lambda field: 73 transpose(moves['Right'](transpose(field))) 74 75 if direction in moves: 76 if self.move_is_possible(direction): 77 self.field = moves[direction](self.field) 78 self.spawn() 79 return True 80 else: 81 return False 82 83 def is_win(self): 84 return any(any(i >= self.win_value for i in row) for row in self.field) 85 86 def is_gameover(self): 87 return not any(self.move_is_possible(move) for move in actions) 88 89 def draw(self, screen): 90 help_string1 = '(W)Up (S)Down (A)Left (D)Right' 91 help_string2 = ' (R)Restart (Q)Exit' 92 gameover_string = ' GAME OVER' 93 win_string = ' YOU WIN!' 94 def cast(string): 95 screen.addstr(string + ' ') 96 97 def draw_hor_separator(): 98 line = '+' + ('+------' * self.width + '+')[1:] 99 separator = defaultdict(lambda: line) 100 if not hasattr(draw_hor_separator, "counter"): 101 draw_hor_separator.counter = 0 102 cast(separator[draw_hor_separator.counter]) 103 draw_hor_separator.counter += 1 104 105 def draw_row(row): 106 cast(''.join('|{: ^5} '.format(num) if num > 0 else '| ' for num in row) + '|') 107 108 screen.clear() 109 cast('SCORE: ' + str(self.score)) 110 if 0 != self.highscore: 111 cast('HIGHSCORE: ' + str(self.highscore)) 112 for row in self.field: 113 #print the --- 114 draw_hor_separator() 115 #print the number we get 116 draw_row(row) 117 draw_hor_separator() 118 if self.is_win(): 119 cast(win_string) 120 else: 121 if self.is_gameover(): 122 cast(gameover_string) 123 else: 124 cast(help_string1) 125 cast(help_string2) 126 127 #Get a radom number from1~100,if this number is larger than 89,then print 2 128 def spawn(self): 129 new_element = 4 if randrange(100) > 89 else 2 130 (i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) 131 self.field[i][j] = new_element 132 133 def move_is_possible(self, direction): 134 def row_is_left_movable(row): 135 def change(i): # true if there'll be change in i-th tile 136 if row[i] == 0 and row[i + 1] != 0: # Move 137 return True 138 if row[i] != 0 and row[i + 1] == row[i]: # Merge 139 return True 140 return False 141 return any(change(i) for i in range(len(row) - 1)) 142 143 check = {} 144 check['Left'] = lambda field: 145 any(row_is_left_movable(row) for row in field) 146 147 check['Right'] = lambda field: 148 check['Left'](invert(field)) 149 150 check['Up'] = lambda field: 151 check['Left'](transpose(field)) 152 153 check['Down'] = lambda field: 154 check['Right'](transpose(field)) 155 156 if direction in check: 157 return check[direction](self.field) 158 else: 159 return False 160 161 def main(stdscr): 162 def init(): 163 #重置游戏棋盘 164 game_field.reset() 165 return 'Game' 166 167 def not_game(state): 168 #画出 GameOver 或者 Win 的界面 169 game_field.draw(stdscr) 170 #读取用户输入得到action,判断是重启游戏还是结束游戏 171 action = get_user_action(stdscr) 172 responses = defaultdict(lambda: state) #默认是当前状态,没有行为就会一直在当前界面循环 173 responses['Restart'], responses['Exit'] = 'Init', 'Exit' #对应不同的行为转换到不同的状态 174 return responses[action] 175 176 def game(): 177 #画出当前棋盘状态 178 game_field.draw(stdscr) 179 #读取用户输入得到action 180 action = get_user_action(stdscr) 181 182 if action == 'Restart': 183 return 'Init' 184 if action == 'Exit': 185 return 'Exit' 186 if game_field.move(action): # move successful 187 if game_field.is_win(): 188 return 'Win' 189 if game_field.is_gameover(): 190 return 'Gameover' 191 return 'Game' 192 193 194 state_actions = { 195 'Init': init, 196 'Win': lambda: not_game('Win'), 197 'Gameover': lambda: not_game('Gameover'), 198 'Game': game 199 } 200 201 curses.use_default_colors() 202 203 # 设置终结状态最大数值为 2048 204 game_field = GameField(win=2048) 205 206 207 state = 'Init' 208 209 #状态机开始循环 210 while state != 'Exit': 211 state = state_actions[state]() 212 213 curses.wrapper(main)
游戏代码来源于网络,仅供参考。