-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathnot-bejewelled.py
256 lines (208 loc) · 8.7 KB
/
not-bejewelled.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
#!/usr/bin/env python
# - coding: utf-8 -
# Copyright (C) 2017 Toms Baugis <[email protected]>
import math
import random
from gi.repository import Gtk as gtk
from gi.repository import GObject as gobject
from lib import graphics
from lib.pytweener import Easing
class Piece(graphics.Sprite):
def __init__(self, col, row, shape=None):
graphics.Sprite.__init__(self)
self.interactive = True
self.col = col
self.row = row
self.shape = shape
self.selected = False
self.reacting = False
self.special = None
self.recently_moved = False
self.connect("on-render", self.on_render)
def on_render(self, sprite):
if self.shape == 0:
self.graphics.rectangle(-15, -15, 30, 30)
self.graphics.fill_preserve("#ff0000")
elif self.shape == 1:
self.graphics.circle(0, 0, 15)
self.graphics.fill_preserve("#00ff00")
elif self.shape == 2:
self.graphics.rotate(math.radians(180))
self.graphics.triangle(-15, -15, 30, 30)
self.graphics.fill_preserve("#0000ff")
elif self.shape == 3:
self.graphics.rotate(math.radians(45))
self.graphics.rectangle(-15, -15, 30, 30)
self.graphics.fill_preserve("#ff00ff")
elif self.shape == 4:
self.graphics.rotate(math.radians(45))
self.graphics.rectangle(-15, -15, 30, 30)
self.graphics.fill_preserve("#ffffff")
elif self.shape == 5:
self.graphics.rectangle(-15, -15, 30, 30)
self.graphics.fill_preserve("#00ffff")
elif self.shape == 6:
self.graphics.rectangle(-15, -15, 30, 30)
self.graphics.fill_preserve("#ffff00")
if self.selected:
self.graphics.set_line_style(width=5)
if self.reacting:
self.graphics.set_line_style(width=5)
self.graphics.stroke("#f00")
else:
self.graphics.stroke("#333")
if self.special == "explode":
self.graphics.circle(0, 0, 20)
self.graphics.set_line_style(width=5)
self.graphics.stroke("#f00")
class Board(graphics.Sprite):
"""
we create a 10x10 board and fill it with pieces
board consists of logic checking for consecutive pieces in rows and columns
there are 6 different type of pieces and the board has to be initiated
then again it would make sense to think about these as horizontally
positioned buckets next to each other, and do a horiz scan
the board controls the position of each piece
"""
def __init__(self):
# our board is an {(col, row): piece} dict
graphics.Sprite.__init__(self)
self.reset_board()
self.move_pieces()
self.interactive = True
self.dummy = 1
self.frame = 0
self.cursor_item = None
def on_click(self, piece, evt):
if not piece:
return
selected = [sel for sel in self.sprites if sel.selected]
selected = selected[0] if selected else None
proximity = 10
if selected:
proximity = abs(selected.col - piece.col) + abs(selected.row - piece.row)
if piece == selected:
piece.selected = False
if proximity == 1 and piece.shape != selected.shape:
# swap
piece.col, selected.col = selected.col, piece.col
piece.row, selected.row = selected.row, piece.row
piece.recently_moved, selected.recently_moved = True, True
reactions = self.get_reactions()
if not reactions:
# undo if the move doesn't do anything
piece.col, selected.col = selected.col, piece.col
piece.row, selected.row = selected.row, piece.row
else:
piece.selected, selected.selected = False, False
self.move_pieces(0.3)
self.animate(duration=0.3, dummy=1, on_complete=self.check_board)
else:
self.select_item(piece)
if selected:
selected.selected = False
def check_board(self, board=None):
# this process can repeat several times via cleanup and fill
reactions = self.get_reactions()
if reactions:
for group in reactions:
special_piece = [piece for piece in group if piece.recently_moved]
special_piece = special_piece[0] if special_piece else group[1]
if len(group) == 4:
# XXX in bejeweled the special one is the one you moved or the merger point
special_piece.special = "explode"
elif len(group) == 5:
group[1].special = "electricity"
for piece in group:
if not piece.special:
piece.reacting = True
piece.animate(opacity=0, duration=0.4)
self.animate(duration=0.4, dummy=1, on_complete=self.cleanup_and_fill)
for piece in self.sprites:
piece.recently_moved = False
def cleanup_and_fill(self, board):
for col in range(8):
cur_idx = 7
for piece in reversed(self._get_line(col, horiz=False)):
if not piece.reacting:
if piece.row != cur_idx:
piece.row = cur_idx
piece.recently_moved = True
cur_idx -= 1
else:
self.remove_child(piece)
missing = 8 - len(self._get_line(col, horiz=False))
for i in range(missing):
piece = Piece(col, i, random.randint(0, 6))
piece.x = 50 + col * 50
piece.y = 50 + (i - missing) * 50
self.add_child(piece)
piece.connect("on-click", self.on_click)
self.move_pieces()
self.check_board()
def reset_board(self):
"""renders a new board that doesn't have any explody conditions
it does so by filling the buckets 1-by-1 horizontally and then performs
scan on the whole board.
add piece -> check for explosions. if explosions do not actually add the piece
"""
for col in range(8):
for row in range(8):
piece = Piece(col, row, random.randint(0, 6))
self.add_child(piece)
piece.connect("on-click", self.on_click)
while self.get_reactions():
piece.shape = random.randint(0, 6)
def move_pieces(self, duration=0.4):
for piece in self.sprites:
piece.animate(x=50 + piece.col * 50, y = 50 + piece.row * 50, duration=duration, easing=Easing.Sine.ease_out)
def get_reactions(self):
"""runs through the board and returns list of pieces that have to be destroyed.
columns are actually rows pivoted - a plain list, so we don't need special cases
just a getter func
"""
reactions = []
def check_sequence(sequence):
if len(sequence) >= 3:
reactions.append(sequence)
for row in [True, False]:
for i in range(8):
sequence = []
for piece in self._get_line(i, row):
if not sequence or sequence[-1].shape != piece.shape:
check_sequence(sequence)
sequence = [piece]
else:
sequence.append(piece)
check_sequence(sequence)
return reactions
def _get_line(self, line_no, horiz=True):
line_items = []
for piece in self.sprites:
if (not horiz and piece.col == line_no) or (horiz and piece.row == line_no):
line_items.append(piece)
return sorted(line_items, key=lambda rec:rec.col if horiz else rec.row)
def select_item(self, item):
selected = [sel for sel in self.sprites if sel.selected]
selected = selected[0] if selected else None
if selected:
selected.selected = False
item.selected = item != selected
self.cursor_item = item if item.selected else None
class Scene(graphics.Scene):
def __init__(self):
graphics.Scene.__init__(self)
self.board = Board()
self.add_child(self.board)
class BasicWindow:
def __init__(self):
window = gtk.Window()
window.set_default_size(450, 450)
window.connect("delete_event", lambda *args: gtk.main_quit())
window.add(Scene())
window.show_all()
if __name__ == '__main__':
window = BasicWindow()
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL) # gtk3 screws up ctrl+c
gtk.main()