-
Notifications
You must be signed in to change notification settings - Fork 23
/
Mines3.py
371 lines (343 loc) · 13.2 KB
/
Mines3.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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# Minesweeper game for iOS in Python (requires Pythonista 3)
# Author: Maurits van der Schee <maurits@vdschee.nl>
# Sprites: http://www.curtisbright.com/msx/
from PIL import Image
from io import *
from base64 import b64decode
import math
from datetime import datetime
from collections import namedtuple
from scene import *
from random import shuffle
from functools import partial
Sprite = namedtuple('Sprite',('name','size'))
class State:
thinking, playing, lost, won = list(range(4))
class Game(Scene):
def setup(self):
self.width = 8
self.height = 8
self.bombs = 10
self.holding = 15
self.scale = self.calculate_scale()
self.sprites = self.load_sprites()
self.layers = self.add_layers()
self.new_game()
def calculate_scale(self):
h_scale = int((self.size.w/(self.width))/16)
v_scale = int((self.size.h/(self.height+3))/16)
return min(h_scale,v_scale)
def load_sprite(self,image,bounds):
width = bounds[2]-bounds[0]
height = bounds[3]-bounds[1]
size = Size(math.trunc(width*self.scale),math.trunc(height*self.scale))
name = load_pil_image(image.crop(bounds).resize((int(size.w),int(size.h))))
return Sprite(name,size)
def load_bg_sprite(self,image):
w = math.trunc(self.width)
h = math.trunc(self.height)
size = Size(w*16+12*2, h*16+11*3+33)
background = Image.new('RGBA',(int(size.w),int(size.h)),"silver")
b = [([0,82,12,93],[0,0,12,11]),
([13,82,14,93],[12,0,12+w*16,11]),
([15,82,27,93],[12+w*16,0,12+w*16+12,11]),
([0,94,12,95],[0,11,12,11+33]),
([15,94,27,95],[12+w*16,11,12+w*16+12,11+33]),
([0,96,12,107],[0,11+33,12,11+33+11]),
([13,96,14,107],[12,11+33,12+w*16,11+33+11]),
([15,96,27,107],[12+w*16,11+33,12+w*16+12,11+33+11]),
([0,108,12,109],[0,11+33+11,12,11+33+11+h*16]),
([15,108,27,109],[12+w*16,11+33+11,12+w*16+12,11+33+11+h*16]),
([0,110,12,121],[0,11+33+11+h*16,12,11+33+11+h*16+11]),
([13,110,14,121],[12,11+33+11+h*16,12+w*16,11+33+11+h*16+11]),
([15,110,27,121],[12+w*16,11+33+11+h*16,12+w*16+12,11+33+11+h*16+11]),
([28,82,69,107],[12+4,11+4,12+4+41,11+4+25]),
([28,82,69,107],[12+w*16-4-41,11+4,12+w*16-4,11+4+25])]
for (s,t) in b:
background.paste(image.crop(s).resize((t[2]-t[0],t[3]-t[1])),t)
size = Size(math.trunc(size.w * self.scale), math.trunc(size.h * self.scale))
background = background.resize((int(size.w),int(size.h)))
name = load_pil_image(background)
return Sprite(name,size)
def load_sprites(self):
data ='''
iVBORw0KGgoAAAANSUhEUgAAAJAAAAB6CAMAAABnRypuAAADAFBMVEUAAACA
AAAAgACAgAAAAICAAIAAgIDAwMCAgID/AAAA/wD//wAAAP//AP8A//////8Q
EBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4f
Hx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0u
Li4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9
PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tM
TExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpb
W1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlq
ampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5
eXl6enp7e3t8fHx9fX1+fn5/f3+AgICBgYGCgoKDg4OEhISFhYWGhoaHh4eI
iIiJiYmKioqLi4uMjIyNjY2Ojo6Pj4+QkJCRkZGSkpKTk5OUlJSVlZWWlpaX
l5eYmJiZmZmampqbm5ucnJydnZ2enp6fn5+goKChoaGioqKjo6OkpKSlpaWm
pqanp6eoqKipqamqqqqrq6usrKytra2urq6vr6+wsLCxsbGysrKzs7O0tLS1
tbW2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr6/v7/AwMDBwcHCwsLDw8PE
xMTFxcXGxsbHx8fIyMjJycnKysrLy8vMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT
09PU1NTV1dXW1tbX19fY2NjZ2dna2trb29vc3Nzd3d3e3t7f39/g4ODh4eHi
4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx
8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7///9Q
NrN6AAAE5UlEQVR4nO2bgZKjIAxAY7ezVu2M//+3pxAgCUHQUnFvzM61QGJ4
Bgjo7sHrYgKvnkvr+t8Ber9D/YGC9cGJt38+n+afrXdeUP+Lgnrwgno3XFtA
bwL0eBAiCjSUAf1mgMIESgO9BVDfu69gH4AMCwNi/hCFj4Dh8kB97750oDcD
QmkJxIfM4TxCnc2hp5eeD1kAYkNGeQ7NIcR5kHoZkJxDv7WAAo4+ZEbCkKFQ
oLXOgZDn0JARnFZA77ef1kXLngOVDJnjKVz2hUAsIgqQ0yuTmgIVzaFm9Rso
CzRzITNMl9r2oxCI7DN3kLUfuOTsFSDmLgkEHki3d3oHBB5o238tILvmjb0t
ArbqQOA2ijpAYI8wplNj75KQtV/KVr82vgyMqRsoY7/S2L3d+XeEByMUzlMG
aOBAS90CmMxtwgPYkALyDQcjtFg5IiVC/dLzvHYPCOTt6Rz6KhCbQ6vezBnY
Aorn0GEgoOJXGXh717XVvwZm74AApP9P5hA9lMdARm+BfITCJEoBuc21EhBf
xvGQRUCR/4+A5CoLfGhvGPA4oq0ybcj6TyIk8pAEkss+ykO1h8w4wG/F3oBA
3/vEWLJ1pIHk/psDiuwzW0fGfwQUnQcSQPQmN+3Fbp+zj4BkQ2sB8PcCS6XD
n6VM2/falJSJHwbkBxuGsXNWpkzbQ3kgNuHajpW5vV4OfXUCCK2WzxHQCsu0
3ZeHIdj4aztgZWGvln1fnYyQTyIrdZctU3tX7tYIkXLOnvrs4ggFI7TZLGsd
2AiFcs6e+rwj9D9ECNztBOqNMrX3ZbvKoggl7TcjxHJPSR7SyjIPablK5qFg
zzM1zbxfyMIl9kURSmXeksilIqRfWziHUnOiZG6VtO9eZa5drpqS1VfSvjsP
uXaZV0ryU0n7HaH/KUKAK5Gfh1LnG2oDiXOP6ic+Y4F+HsrmD5mH9Gy+Pw85
G5mpBycVz9Q7bRjQeDEB+dgUnp/GM1UEaNalf431VUnJAy0PnCnXH6iix2D3
uMuBVBPrurLqrwOZzKm7rqTaBQQwrRLeLI31VQSIwqpA9mrjQbqupyKvYCms
BhQutw6I64oq//aLwypA9HJjFVwvqh/4mfBTqmBaLzWfUqU4fEkeFz4VCMKH
7BVVWq8Ye/0qKxIogo2BYGJAEwTXMLEISRWJkFBZFk8kgNjdJ4AYNev1iMqy
4AcDiu/+DCAz82ynP2Y1CSA+ZgeAACCl2sG6C8jZxbNhWu92MjNIUeEsqQuU
WS/YOmmqjatilQfid5/KQ/5eYyAfoTgPec/KbUiHZJVRniOZOnj+PFNHsM33
soJMHe13393t83sZejjtPJTf7aV8/cS4cR6KfoHkngTqq4pOjKnLl+urq0qA
kpe/xjNVAWi8mLA/aBqXH/Jot/GkyaUfo99UfgFoffSFMpmr4WwAmUdfIC9N
IF2pDeQn+xJ5njeAvFaCdOVEIPLiDdKVUyPkX01CunJyhLBfSFfuCLWJkCEI
2037CCFQPzeO0JaybR6yL2f7mUfo/Ey9pWwcoR73squssn5WgO48dGfqT4Eu
GaGLnYdaZmplc215ptY31yL5JhDby8T/KklLTaCKvqoIj9A4imoDoCa9bgg/
D80XiNDW8aMtkHL8aAwUZ+ob6Aa6geoAhZe1I/+LmkZA5OXxJYDoy+wrAP0D
7BQA5IVg6IYAAAAASUVORK5CYII=
'''
image = Image.open(BytesIO(b64decode(data))).convert('RGBA')
f = partial(self.load_sprite,image)
sprites = {}
sprites['numbers'] = [f([x*16,0,x*16+16,16]) for x in range(9)]
sprites['icons'] = [f([x*16,16,x*16+16,32]) for x in range(8)]
sprites['digits'] = [f([x*12,33,x*12+11,54]) for x in range(11)]
sprites['buttons'] = [f([x*27,55,x*27+26,81]) for x in range(5)]
sprites['background'] = self.load_bg_sprite(image)
return namedtuple('Sprites',list(sprites.keys()))(*list(sprites.values()))
def add_layers(self):
# root layer
self.root_layer = Layer(self.bounds)
# background layer
width = math.trunc(self.sprites.background.size.w)
height = math.trunc(self.sprites.background.size.h)
offset = Point(math.trunc((self.size.w - width)/2),math.trunc((self.size.h - height)/2))
bg = Layer(Rect(offset.x,offset.y,width,height))
bg.image = self.sprites.background.name
self.add_layer(bg)
# help variables
bg_left = offset.x
bg_right = offset.x+width
bg_top = offset.y+height
bg_bottom = offset.y
# smiley button layer
width = self.sprites.buttons[0].size.w
height = self.sprites.buttons[0].size.h
offset_x = (self.size.w - width)/2
offset_y = bg_top-(11+4)*self.scale-height
button = Layer(Rect(offset_x,offset_y,width,height))
button.image = self.sprites.buttons[0].name
self.add_layer(button)
# bomb count layer
width = self.sprites.digits[0].size.w
height = self.sprites.digits[0].size.h
offset_x = bg_left+(12+4+2)*self.scale
offset_y = bg_top-(11+4+2)*self.scale-height
bombs = []
for i in range(3):
x = math.trunc(offset_x+i*(width+2*self.scale))
count = Layer(Rect(x,offset_y,width,height))
count.image = self.sprites.digits[0].name
self.add_layer(count)
bombs.append(count)
# time count layer
width = self.sprites.digits[0].size.w
height = self.sprites.digits[0].size.h
offset_x = bg_right-(12+4+2*3)*self.scale-width*3
offset_y = bg_top-(11+4+2)*self.scale-height
seconds = []
for i in range(3):
x = offset_x+i*(width+2*self.scale)
count = Layer(Rect(x,offset_y,width,height))
count.image = self.sprites.digits[0].name
self.add_layer(count)
seconds.append(count)
# tile layers
tile_width = self.sprites.icons[0].size.w
tile_height = self.sprites.icons[0].size.h
width = math.trunc(self.width * tile_width)
height = math.trunc(self.height * tile_height)
offset_x = math.trunc((self.size.w - width)/2)
offset_y = bg_bottom+11*self.scale
tiles = []
for i in range(self.width*self.height):
x, y = math.trunc(i % self.width), math.trunc(i / self.width)
x, y = offset_x + x * tile_width, offset_y + y * tile_height
tile = Layer(Rect(x, y, tile_width, tile_height))
self.add_layer(tile)
tiles.append(tile)
layers = {}
layers['root'] = self.root_layer
layers['background'] = bg
layers['button'] = button
layers['bombs'] = bombs
layers['seconds'] = seconds
layers['tiles'] = tiles
return namedtuple('Layers',list(layers.keys()))(*list(layers.values()))
def draw(self):
background(.8,.8,.8)
self.layers.root.draw()
self.draw_button()
self.draw_counts()
self.draw_tiles()
def draw_counts(self):
if self.state in [State.thinking, State.playing]:
bombs_left = self.bombs_left()
if bombs_left<0:
self.layers.bombs[0].image = self.sprites.digits[10].name
for i in range(2):
digit = int(abs(bombs_left)/pow(10,i))%10
self.layers.bombs[2-i].image = self.sprites.digits[digit].name
else:
for i in range(3):
digit = math.trunc((bombs_left/pow(10,i))%10)
self.layers.bombs[2-i].image = self.sprites.digits[digit].name
if self.start == None:
seconds_passed = 0
else:
seconds_passed = int((datetime.now() - self.start).total_seconds())
for i in range(3):
digit = math.trunc((seconds_passed/pow(10,i))%10)
self.layers.seconds[2-i].image = self.sprites.digits[digit].name
def draw_tiles(self):
for tile in self.layers.tiles:
self.draw_tile(tile)
def bombs_left(self):
marked = 0
moves = 0
for tile in self.layers.tiles:
if tile.marked:
marked += 1
if tile.bomb:
if tile.open:
self.state = State.lost
else:
if not tile.open:
moves += 1
if moves == 0:
self.state = State.won
return 0
return self.bombs - marked
def draw_button(self):
if self.state in [State.thinking, State.playing]:
self.state = State.thinking
for tile in self.layers.tiles:
if not tile.open and tile.selected:
self.state = State.playing
break
if self.layers.button.selected:
self.layers.button.image = self.sprites.buttons[4].name
else:
self.layers.button.image = self.sprites.buttons[self.state].name
def new_game(self):
self.state = State.thinking
self.layers.button.selected = False
self.start = None
bomb_locations = [True] * self.bombs
bomb_locations+= [False] * (self.width * self.height - self.bombs)
shuffle(bomb_locations)
for i in range(len(self.layers.tiles)):
x, y = i % self.width, i / self.width
tile = self.layers.tiles[i]
tile.bomb = bomb_locations[i]
tile.selected = False
tile.hold = 0
tile.marked = False
tile.open = False
tile.first = False
#tile.image = self.sprites.icons[0].name
tile.score = 0
for neighbour in self.get_neighbours(tile):
if bomb_locations[self.layers.tiles.index(neighbour)]:
tile.score += 1
def get_neighbours(self,tile):
i = self.layers.tiles.index(tile)
x, y = math.trunc(i % self.width), math.trunc(i / self.width)
neighbours = ((-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1))
for (dx,dy) in neighbours:
if x+dx>=0 and x+dx<math.trunc(self.width) and y+dy>=0 and y+dy<math.trunc(self.height):
n = math.trunc((y+dy)*math.trunc(self.width)+x+dx)
yield self.layers.tiles[n]
def draw_tile(self,tile):
if tile.selected:
tile.hold += 1
else:
tile.hold = 0
if tile.open:
if tile.bomb:
tile.image = self.sprites.icons[5].name
else:
tile.image = self.sprites.numbers[tile.score].name
if tile.hold == self.holding:
self.open_neighbours(tile)
else:
if self.state in [State.thinking, State.playing]:
if tile.hold == self.holding:
tile.marked = not tile.marked
if tile.marked:
tile.image = self.sprites.icons[3].name
elif tile.selected:
tile.image = self.sprites.icons[1].name
else:
tile.image = self.sprites.icons[0].name
if self.state == State.lost:
if tile.bomb and not tile.marked:
tile.image = self.sprites.icons[2].name
if not tile.bomb and tile.marked:
tile.image = self.sprites.icons[4].name
if self.state == State.won:
if tile.bomb:
tile.image = self.sprites.icons[3].name
def touch_tile(self, touch, release):
for tile in self.layers.tiles:
tile.selected = touch.location in tile.frame
if tile.selected and release:
tile.selected = False
if not tile.marked and tile.hold < self.holding:
if self.start == None and tile.bomb:
self.new_game()
return self.touch_tile(touch,True)
self.open_tile(tile)
if self.start == None:
self.start = datetime.now()
def open_tile(self,tile):
tile.open = True
if tile.score == 0 and not tile.bomb:
for neighbour in self.get_neighbours(tile):
if not neighbour.open:
self.open_tile(neighbour)
def open_neighbours(self,tile):
for neighbour in self.get_neighbours(tile):
if not neighbour.marked:
neighbour.open = True
if neighbour.score == 0 and not neighbour.bomb:
self.open_tile(neighbour)
def touch_button(self, touch, release):
button = self.layers.button
button.selected = touch.location in button.frame
if button.selected and release:
self.new_game()
def touch_began(self, touch):
if self.state in [State.thinking, State.playing]:
self.touch_tile(touch, False)
self.touch_button(touch, False)
def touch_moved(self,touch):
self.touch_began(touch)
def touch_ended(self, touch):
if self.state in [State.thinking, State.playing]:
self.touch_tile(touch, True)
self.touch_button(touch, True)
run(Game(),PORTRAIT)