1 #-*- coding:utf-8 -*- 2 3 import curses 4 from random import randrange, choice # generate and place new tile 5 from collections import defaultdict 6 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 = 2048 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('HGHSCORE: ' + str(self.highscore)) 112 for row in self.field: 113 draw_hor_separator() 114 draw_row(row) 115 draw_hor_separator() 116 if self.is_win(): 117 cast(win_string) 118 else: 119 if self.is_gameover(): 120 cast(gameover_string) 121 else: 122 cast(help_string1) 123 cast(help_string2) 124 125 def spawn(self): 126 new_element = 4 if randrange(100) > 89 else 2 127 (i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) 128 self.field[i][j] = new_element 129 130 def move_is_possible(self, direction): 131 def row_is_left_movable(row): 132 def change(i): # true if there'll be change in i-th tile 133 if row[i] == 0 and row[i + 1] != 0: # Move 134 return True 135 if row[i] != 0 and row[i + 1] == row[i]: # Merge 136 return True 137 return False 138 return any(change(i) for i in range(len(row) - 1)) 139 140 check = {} 141 check['Left'] = lambda field: 142 any(row_is_left_movable(row) for row in field) 143 144 check['Right'] = lambda field: 145 check['Left'](invert(field)) 146 147 check['Up'] = lambda field: 148 check['Left'](transpose(field)) 149 150 check['Down'] = lambda field: 151 check['Right'](transpose(field)) 152 153 if direction in check: 154 return check[direction](self.field) 155 else: 156 return False 157 158 def main(stdscr): 159 def init(): 160 #重置游戏棋盘 161 game_field.reset() 162 return 'Game' 163 164 def not_game(state): 165 #画出 GameOver 或者 Win 的界面 166 game_field.draw(stdscr) 167 #读取用户输入得到action,判断是重启游戏还是结束游戏 168 action = get_user_action(stdscr) 169 responses = defaultdict(lambda: state) #默认是当前状态,没有行为就会一直在当前界面循环 170 responses['Restart'], responses['Exit'] = 'Init', 'Exit' #对应不同的行为转换到不同的状态 171 return responses[action] 172 173 def game(): 174 #画出当前棋盘状态 175 game_field.draw(stdscr) 176 #读取用户输入得到action 177 action = get_user_action(stdscr) 178 179 if action == 'Restart': 180 return 'Init' 181 if action == 'Exit': 182 return 'Exit' 183 if game_field.move(action): # move successful 184 if game_field.is_win(): 185 return 'Win' 186 if game_field.is_gameover(): 187 return 'Gameover' 188 return 'Game' 189 190 191 state_actions = { 192 'Init': init, 193 'Win': lambda: not_game('Win'), 194 'Gameover': lambda: not_game('Gameover'), 195 'Game': game 196 } 197 198 curses.use_default_colors() 199 game_field = GameField(win=32) 200 201 202 state = 'Init' 203 204 #状态机开始循环 205 while state != 'Exit': 206 state = state_actions[state]() 207 208 curses.wrapper(main)