Skip to content

Commit 2e07cc7

Browse files
Merge pull request #63 from pavelmachek/m_7_columns
columns: simple falling-blocks game
2 parents 3be52f1 + a996081 commit 2e07cc7

3 files changed

Lines changed: 362 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "Columns",
3+
"publisher": "Pavel Machek",
4+
"short_description": "Falling columns game",
5+
"long_description": "Blocks of 3 colors are falling. Align the colors to make blocks di\
6+
sappear.",
7+
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.columns/icons/cz.ucw.pavel.columns_0.0.1_64x64.png",
8+
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.columns/mpks/cz.ucw.pavel.columns_0.0.1.mpk",
9+
"fullname": "cz.ucw.pavel.columns",
10+
"version": "0.0.1",
11+
"category": "utilities",
12+
"activities": [
13+
{
14+
"entrypoint": "assets/main.py",
15+
"classname": "Main",
16+
"intent_filters": [
17+
{
18+
"action": "main",
19+
"category": "launcher"
20+
}
21+
]
22+
}
23+
]
24+
}
25+
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
import time
2+
import random
3+
4+
"""
5+
Columns -- falling columns game
6+
7+
Possible TODOs:
8+
9+
should blink explosions
10+
explodes while moving?
11+
/ in bottom left part may not explode
12+
13+
smooth moving?
14+
music?
15+
some kind of game over?
16+
17+
more contrast colors?
18+
different shapes?
19+
20+
"""
21+
22+
from mpos import Activity
23+
24+
try:
25+
import lvgl as lv
26+
except ImportError:
27+
pass
28+
29+
class Main(Activity):
30+
31+
COLS = 6
32+
ROWS = 12
33+
34+
COLORS = [
35+
0xE74C3C, # red
36+
0xF1C40F, # yellow
37+
0x2ECC71, # green
38+
0x3498DB, # blue
39+
0x9B59B6, # purple
40+
]
41+
42+
EMPTY = -1
43+
44+
FALL_INTERVAL = 1000 # ms
45+
# I can do 120 in this config :-).
46+
47+
def __init__(self):
48+
super().__init__()
49+
self.board = [[self.EMPTY for _ in range(self.COLS)] for _ in range(self.ROWS)]
50+
self.cells = []
51+
52+
self.active_col = self.COLS // 2
53+
self.active_row = -3
54+
self.active_colors = []
55+
56+
self.timer = None
57+
self.animating = False
58+
59+
# ---------------------------------------------------------------------
60+
61+
def onCreate(self):
62+
self.screen = lv.obj()
63+
self.screen.remove_flag(lv.obj.FLAG.SCROLLABLE)
64+
65+
vert = 60
66+
horiz = 60
67+
font = lv.font_montserrat_20
68+
69+
score = lv.label(self.screen)
70+
score.align(lv.ALIGN.TOP_LEFT, 5, 25)
71+
score.set_text("Score")
72+
score.set_style_text_font(font, 0)
73+
self.lb_score = score
74+
75+
btn_left = lv.button(self.screen)
76+
btn_left.set_size(horiz, vert)
77+
btn_left.align(lv.ALIGN.BOTTOM_LEFT, 5, -10-vert)
78+
btn_left.add_event_cb(lambda e: self.move(-1), lv.EVENT.CLICKED, None)
79+
lc = lv.label(btn_left)
80+
lc.set_style_text_font(font, 0)
81+
lc.set_text("<")
82+
lc.center()
83+
84+
btn_right = lv.button(self.screen)
85+
btn_right.set_size(horiz, vert)
86+
btn_right.align(lv.ALIGN.BOTTOM_RIGHT, -5, -10-vert)
87+
btn_right.add_event_cb(lambda e: self.move(1), lv.EVENT.CLICKED, None)
88+
lc = lv.label(btn_right)
89+
lc.set_style_text_font(font, 0)
90+
lc.set_text(">")
91+
lc.center()
92+
93+
btn_rotate = lv.button(self.screen)
94+
btn_rotate.set_size(horiz, vert)
95+
btn_rotate.align(lv.ALIGN.BOTTOM_RIGHT, -5, -15-vert-vert)
96+
btn_rotate.add_event_cb(lambda e: self.rotate(), lv.EVENT.CLICKED, None)
97+
lc = lv.label(btn_rotate)
98+
lc.set_style_text_font(font, 0)
99+
lc.set_text("R")
100+
lc.center()
101+
102+
btn_down = lv.button(self.screen)
103+
btn_down.set_size(horiz, vert)
104+
btn_down.align(lv.ALIGN.BOTTOM_LEFT, 5, -5)
105+
btn_down.add_event_cb(lambda e: self.tick(0), lv.EVENT.CLICKED, None)
106+
lc = lv.label(btn_down)
107+
lc.set_style_text_font(font, 0)
108+
lc.set_text("v")
109+
lc.center()
110+
111+
d = lv.display_get_default()
112+
self.SCREEN_WIDTH = d.get_horizontal_resolution()
113+
self.SCREEN_HEIGHT = d.get_vertical_resolution()
114+
115+
self.CELL = min(
116+
self.SCREEN_WIDTH // (self.COLS + 1),
117+
self.SCREEN_HEIGHT // (self.ROWS + 1)
118+
)
119+
120+
board_x = (self.SCREEN_WIDTH - self.CELL * self.COLS) // 2
121+
board_y = (self.SCREEN_HEIGHT - self.CELL * self.ROWS) // 2
122+
123+
for r in range(self.ROWS):
124+
row = []
125+
for c in range(self.COLS):
126+
o = lv.obj(self.screen)
127+
o.set_size(self.CELL - 2, self.CELL - 2)
128+
o.set_pos(
129+
board_x + c * self.CELL + 1,
130+
board_y + r * self.CELL + 1
131+
)
132+
o.set_style_radius(4, 0)
133+
o.set_style_bg_color(lv.color_hex(0x1C2833), 0)
134+
o.set_style_border_width(1, 0)
135+
row.append(o)
136+
self.cells.append(row)
137+
138+
# Make screen focusable for keyboard input
139+
focusgroup = lv.group_get_default()
140+
if focusgroup:
141+
focusgroup.add_obj(self.screen)
142+
143+
#self.screen.add_event_cb(self.on_touch, lv.EVENT.CLICKED, None)
144+
self.screen.add_event_cb(self.on_key, lv.EVENT.KEY, None)
145+
146+
self.setContentView(self.screen)
147+
148+
self.new_game()
149+
self.spawn_piece()
150+
151+
152+
def new_game(self):
153+
self.score = 0
154+
# ---------------------------------------------------------------------
155+
156+
def onResume(self, screen):
157+
self.timer = lv.timer_create(self.tick, self.FALL_INTERVAL, None)
158+
159+
def onPause(self, screen):
160+
if self.timer:
161+
self.timer.delete()
162+
self.timer = None
163+
164+
# ---------------------------------------------------------------------
165+
166+
def spawn_piece(self):
167+
self.active_col = self.COLS // 2
168+
self.active_row = -3
169+
self.active_colors = [random.randrange(len(self.COLORS)) for _ in range(3)]
170+
171+
def tick(self, t):
172+
if self.can_fall():
173+
self.active_row += 1
174+
else:
175+
self.lock_piece()
176+
self.clear_matches()
177+
self.spawn_piece()
178+
179+
self.redraw()
180+
181+
# ---------------------------------------------------------------------
182+
183+
def can_fall(self):
184+
for i in range(3):
185+
r = self.active_row + i + 1
186+
c = self.active_col
187+
if r >= self.ROWS:
188+
return False
189+
if r >= 0 and self.board[r][c] != self.EMPTY:
190+
return False
191+
return True
192+
193+
def lock_piece(self):
194+
for i in range(3):
195+
r = self.active_row + i
196+
if r >= 0:
197+
self.board[r][self.active_col] = self.active_colors[i]
198+
199+
# ---------------------------------------------------------------------
200+
201+
def clear_matches(self):
202+
to_clear = set()
203+
score = 0
204+
205+
for r in range(self.ROWS):
206+
for c in range(self.COLS):
207+
color = self.board[r][c]
208+
if color == self.EMPTY:
209+
continue
210+
211+
# horizontal
212+
if c <= self.COLS - 3:
213+
if all(self.board[r][c + i] == color for i in range(3)):
214+
for i in range(3):
215+
to_clear.add((r, c + i))
216+
score += 1
217+
218+
# vertical
219+
if r <= self.ROWS - 3:
220+
if all(self.board[r + i][c] == color for i in range(3)):
221+
for i in range(3):
222+
to_clear.add((r + i, c))
223+
score += 1
224+
225+
# diagonal \
226+
if r <= self.ROWS - 3 and c <= self.COLS - 3:
227+
if all(self.board[r + i][c + i] == color for i in range(3)):
228+
for i in range(3):
229+
to_clear.add((r + i, c + i))
230+
score += 1
231+
232+
# diagonal /
233+
if r <= self.ROWS - 3 and c > 2:
234+
if all(self.board[r + i][c - i] == color for i in range(3)):
235+
for i in range(3):
236+
to_clear.add((r + i, c - i))
237+
score += 1
238+
239+
if not to_clear:
240+
return
241+
242+
print("Score: ", score)
243+
self.score += score
244+
self.lb_score.set_text("Score\n%d" % self.score)
245+
for r, c in to_clear:
246+
self.board[r][c] = self.EMPTY
247+
248+
self.redraw()
249+
time.sleep(.5)
250+
self.apply_gravity()
251+
self.redraw()
252+
time.sleep(.5)
253+
self.clear_matches()
254+
self.redraw()
255+
256+
def apply_gravity(self):
257+
for c in range(self.COLS):
258+
stack = [self.board[r][c] for r in range(self.ROWS) if self.board[r][c] != self.EMPTY]
259+
for r in range(self.ROWS):
260+
self.board[r][c] = self.EMPTY
261+
for i, v in enumerate(reversed(stack)):
262+
self.board[self.ROWS - 1 - i][c] = v
263+
264+
# ---------------------------------------------------------------------
265+
266+
def redraw(self):
267+
# draw board
268+
for r in range(self.ROWS):
269+
for c in range(self.COLS):
270+
v = self.board[r][c]
271+
if v == self.EMPTY:
272+
self.cells[r][c].set_style_bg_color(lv.color_hex(0x1C2833), 0)
273+
else:
274+
self.cells[r][c].set_style_bg_color(
275+
lv.color_hex(self.COLORS[v]), 0
276+
)
277+
278+
# draw active piece
279+
for i in range(3):
280+
r = self.active_row + i
281+
if r >= 0 and r < self.ROWS:
282+
self.cells[r][self.active_col].set_style_bg_color(
283+
lv.color_hex(self.COLORS[self.active_colors[i]]), 0
284+
)
285+
286+
# ---------------------------------------------------------------------
287+
288+
def on_touch(self, e):
289+
return
290+
print("Touch event")
291+
p = lv.indev_get_act().get_point()
292+
x = p.x
293+
294+
if x < self.SCREEN_WIDTH // 3:
295+
self.move(-1)
296+
elif x > self.SCREEN_WIDTH * 2 // 3:
297+
self.move(1)
298+
else:
299+
self.rotate()
300+
301+
def on_key(self, event):
302+
"""Handle keyboard input"""
303+
print("Keyboard event")
304+
key = event.get_key()
305+
if key == ord("a"):
306+
self.move(-1)
307+
return
308+
if key == ord("w"):
309+
self.rotate()
310+
return
311+
if key == ord("d"):
312+
self.move(1)
313+
return
314+
if key == ord("s"):
315+
self.tick(0)
316+
return
317+
318+
#if key == lv.KEY.ENTER or key == lv.KEY.UP or key == ord("A") or key == ord("a"):
319+
print(f"on_key: unhandled key {key}")
320+
321+
def move(self, dx):
322+
nc = self.active_col + dx
323+
if not(0 <= nc < self.COLS):
324+
return
325+
326+
for i in range(3):
327+
r = self.active_row + i
328+
if self.board[r][nc] != self.EMPTY:
329+
return
330+
331+
self.active_col = nc
332+
self.redraw()
333+
334+
def rotate(self):
335+
self.active_colors = self.active_colors[-1:] + self.active_colors[:-1]
336+
self.redraw()
337+
8.61 KB
Loading

0 commit comments

Comments
 (0)