Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
371 lines (342 sloc) 12.9 KB
# Minesweeper game for iOS in Python (requires Pythonista 2)
# Author: Maurits van der Schee <maurits@vdschee.nl>
# Sprites: http://www.curtisbright.com/msx/
from PIL import Image
from StringIO import *
from base64 import b64decode
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 = 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(width*self.scale,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 = self.width
h = 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(size.w * self.scale, 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(StringIO(b64decode(data))).convert('RGBA')
f = partial(self.load_sprite,image)
sprites = {}
sprites['numbers'] = map(lambda x: f([x*16,0,x*16+16,16]),xrange(9))
sprites['icons'] = map(lambda x: f([x*16,16,x*16+16,32]),xrange(8))
sprites['digits'] = map(lambda x: f([x*12,33,x*12+11,54]),xrange(11))
sprites['buttons'] = map(lambda x: f([x*27,55,x*27+26,81]),xrange(5))
sprites['background'] = self.load_bg_sprite(image)
return namedtuple('Sprites',sprites.keys())(*sprites.values())
def add_layers(self):
# root layer
self.root_layer = Layer(self.bounds)
# background layer
width = self.sprites.background.size.w
height = self.sprites.background.size.h
offset = Point((self.size.w - width)/2,(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 xrange(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)
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 xrange(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 = self.width * tile_width
height = self.height * tile_height
offset_x = (self.size.w - width)/2
offset_y = bg_bottom+11*self.scale
tiles = []
for i in xrange(self.width*self.height):
x, y = i % self.width, 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',layers.keys())(*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 xrange(2):
digit = (abs(bombs_left)/pow(10,i))%10
self.layers.bombs[2-i].image = self.sprites.digits[digit].name
else:
for i in xrange(3):
digit = (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 xrange(3):
digit = (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 xrange(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 = i % self.width, 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<self.width and y+dy>=0 and y+dy<self.height:
n = (y+dy)*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)