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 + '\n')
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)
![](https://images2015.cnblogs.com/blog/787014/201603/787014-20160325200731745-1245384549.jpg)