/
screen.py
258 lines (198 loc) · 6.97 KB
/
screen.py
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
from glass import Glass
import math
import curses
import copy
class Screen():
"""
Represents drawable part of the terminal,
where other parts (glass, figures) are drawed.
"""
glass = None
def init(self):
"""
Initialize curses.
"""
# Initialize ncurses library.
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(1)
curses.curs_set(0)
self.width = 28
self.height = 24
# Status message. Eg. "Paused"
self.status = None
self.level = 1
self.score = 0
self.lines = 0
# Draw main window.
self.window = curses.newwin(self.height, self.width, 0, 0)
self.window.border()
self.window.refresh()
self.window.nodelay(1)
self.glass = Glass(self)
self.next_figure = None
def destroy(self):
"""
Free ncurses library resources and back screen to
normal mode.
"""
# Release ncurses.
curses.nocbreak()
self.stdscr.keypad(0)
curses.echo()
curses.endwin()
curses.curs_set(1)
def move_figure_to_start(self, figure):
"""
Move figure to start position.
Figure shoud appears at the middle top of the glass.
"""
figure.y = 0
figure.x = int(math.floor(self.glass.width / 2))
def draw(self, figure):
"""
Redraw the screen.
"""
y_base = 10
x_base = self.glass.width + 4
self.window.move(y_base, x_base)
self.window.addstr("Score:" + "{0:>6}".format(self.score))
self.window.move(y_base + 1, x_base)
self.window.addstr("Lines:" + "{0:>6}".format(self.lines))
self.window.move(y_base + 2, x_base)
self.window.addstr("Level:" + "{0:>6}".format(self.level))
self.window.move(y_base + 4, x_base)
if self.status:
self.window.addstr(self.status, curses.A_BLINK)
else:
self.window.addstr(" " * (self.width - 1 - x_base))
self.glass.clear()
self.glass.draw()
#
# Draw next picture preview.
#
# Clear place before.
for y in range(self.preview_figure.y - 2, self.preview_figure.y + 3):
for x in range(self.preview_figure.x - 2, self.preview_figure.x + 3):
self.window.move(y, x)
self.window.addstr(" ")
self.preview_figure.draw(self.window, self.width, self.height)
figure.draw(self.glass.window, self.glass.width, self.glass.height)
def set_next_figure(self, figure):
"""
Set next falling figure for preview.
"""
self.preview_figure = copy.copy(figure)
self.preview_figure.x = self.glass.width + 7
self.preview_figure.y = 5
def getch(self):
"""
Return pressed key.
"""
return self.window.getch()
def move_figure_down(self, figure):
"""
Move figure down for one position, -- falling.
Returns False if figure was joined to lees.
"""
if self._is_figure_down_moveable(figure):
figure.y += 1
return True
else:
# Figure should be joined to lees.
self.glass.lees_figure(figure)
return False
def _is_figure_down_moveable(self, figure):
"""
Returns True if figure can be moved down.
"""
bottoms = figure.get_bottom_positions()
for i, y in enumerate(bottoms):
lees_top = self.glass.get_lees_top(figure.x - 2 + i)
if y >= lees_top - 1 or y >= self.glass.height:
return False
return True
def move_figure_right(self, figure):
"""
Move figure one position right.
"""
if self._is_figure_right_moveable(figure):
figure.x += 1
def move_figure_left(self, figure):
"""
Move figure one position left.
"""
if self._is_figure_left_moveable(figure):
figure.x -= 1
def _is_figure_right_moveable(self, figure):
"""
Returns True if this figure can be moved right at least one step.
"""
right_edge = figure.get_right_edge()
sprite = figure.get_sprite()
return right_edge < self.glass.width - 1 and self._is_space_free(figure.y, figure.x + 1, sprite)
def _is_figure_left_moveable(self, figure):
"""
Returns True if this figure can be moved left at least one step.
"""
left_edge = figure.get_left_edge()
sprite = figure.get_sprite()
return left_edge > 0 and self._is_space_free(figure.y, figure.x - 1, sprite)
def rotate_figure_anticlockwise(self, figure):
"""
Rotate figure contraclockwise.
"""
i = figure.get_prev_sprite_index()
# We can't rotate if figure will be outside the glass.
right_edge = figure.get_right_edge(i)
left_edge = figure.get_left_edge(i)
if right_edge >= self.glass.width or left_edge < 0:
return
# Or figure will be under the glass's floor.
bottoms = figure.get_bottom_positions(i)
for y in bottoms:
if y >= self.glass.height:
return
# And we have free place for future sprite.
i = figure.get_prev_sprite_index()
sprite = figure.get_sprite(i)
if not self._is_space_free(figure.y, figure.x, sprite):
return
figure.rotate_anticlockwise()
def rotate_figure_clockwise(self, figure):
"""
Rotate figure clockwise, if it can be rotated.
"""
i = figure.get_next_sprite_index()
# Block rotation if figure will be moved outside the glass.
right_edge = figure.get_right_edge(i)
left_edge = figure.get_left_edge(i)
if right_edge >= self.glass.width or left_edge < 0:
return
# Or under the floor.
bottoms = figure.get_bottom_positions(i)
for y in bottoms:
if y >= self.glass.height:
return
# And we have free place for the next sprite.
i = figure.get_next_sprite_index()
sprite = figure.get_sprite()
if not self._is_space_free(figure.y, figure.x, sprite):
return
figure.rotate_clockwise()
def _is_space_free(self, y, x, sprite):
"""
Returns True if space for the given sprite is fee (without lees) in glass.
"""
for yy, row in enumerate(sprite):
for xx, cel in enumerate(row):
if cel > 0:
if not self.glass.is_space_free(y - 2 + yy, x - 2 + xx):
return False
return True
def delete_full_lines(self):
"""
Remove full lines and returns their count.
"""
return self.glass.delete_full_lines()