Last active
August 29, 2015 14:01
-
-
Save git-bee/55c656eabc9a892b93dc to your computer and use it in GitHub Desktop.
Minesweeper - Pythonista
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Minesweeper game for iOS in Python (requires Pythonista) | |
# Author: Maurits van der Schee <maurits@vdschee.nl> | |
# Sprites: http://www.curtisbright.com/msx/ | |
# Further modification: @beezing | |
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')) | |
def txt(x,y,t,s=14,f='HelveticaNeue'): | |
text(t,f,s,x,y) | |
class State: | |
thinking, playing, lost, won = range(4) | |
class Game(Scene): | |
def setup(self): | |
# setup for ipod | |
self.cols = 8 | |
self.rows = 10 | |
self.bombs = 12 | |
self.landscape = False | |
# adjust for landscape | |
if self.size.w > self.size.h: | |
self.cols = 16 | |
self.rows = 6 | |
self.bombs = 15 | |
self.landscape = True | |
self.started = False | |
self.delay = 10 # tap hold delay | |
self.scale = self.calculate_scale() | |
self.sprites = self.load_sprites() | |
self.layers = self.add_layers() | |
self.new_game() | |
# scale tile to fill screen size | |
def calculate_scale(self): | |
h_scale = int((self.size.w/(self.cols))/16) | |
v_scale = int((self.size.h/(self.rows+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(size.as_tuple())) | |
return Sprite(name,size) | |
def load_bg_sprite(self,image): | |
w = self.cols | |
h = self.rows | |
size = Size(w*16+12*2, h*16+11*3+33) | |
background = Image.new('RGBA',size,"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(size.as_tuple()) | |
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.cols * tile_width | |
height = self.rows * tile_height | |
offset_x = (self.size.w - width)/2 | |
offset_y = bg_bottom+11*self.scale | |
tiles = [] | |
for i in xrange(self.cols*self.rows): | |
x, y = i % self.cols, i / self.cols | |
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(.2,.2,.2) | |
if not self.landscape: | |
x = self.size.w/2 | |
y = self.size.h | |
txt(x,y-30,'Minesweeper',22) | |
if not self.started: | |
txt(x,30,'waiting...') | |
elif self.state == State.lost: | |
txt(x,30,'You lost!') | |
elif self.state == State.won: | |
txt(x,30,'You won!') | |
else: | |
txt(x,30,'playing...') | |
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 | |
# ignore timer if no tile has been tapped | |
if self.started: | |
seconds_passed = int((datetime.now() - self.start).total_seconds()) | |
else: | |
seconds_passed = 0 | |
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.started = False | |
self.state = State.thinking | |
self.layers.button.selected = False | |
self.start = datetime.now() | |
bomb_locations = [True] * self.bombs | |
bomb_locations+= [False] * (self.cols * self.rows - self.bombs) | |
shuffle(bomb_locations) | |
for i in xrange(len(self.layers.tiles)): | |
x, y = i % self.cols, i / self.cols | |
tile = self.layers.tiles[i] | |
tile.bomb = bomb_locations[i] | |
tile.selected = False | |
tile.hold = 0 | |
tile.marked = False | |
tile.open = 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.cols, i / self.cols | |
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.cols and y+dy>=0 and y+dy<self.rows: | |
n = (y+dy)*self.cols+x+dx | |
yield self.layers.tiles[n] | |
def draw_tile(self,tile): | |
if tile.open: | |
if tile.bomb: | |
tile.image = self.sprites.icons[5].name | |
else: | |
tile.image = self.sprites.numbers[tile.score].name | |
if self.state in [State.thinking, State.playing]: | |
if tile.selected: | |
tile.hold += 1 | |
else: | |
tile.hold = 0 | |
else: | |
if self.state in [State.thinking, State.playing]: | |
if tile.selected: | |
tile.hold += 1 | |
else: | |
tile.hold = 0 | |
if tile.hold == self.delay: | |
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 | |
# restart if first tile is bomb | |
if not self.started and tile.bomb: | |
self.new_game() | |
self.touch_tile(touch,True) | |
else: | |
# timer started on first tap | |
if not self.started: | |
self.start = datetime.now() | |
self.started = True | |
if not tile.marked and tile.hold < self.delay: | |
self.open_tile(tile) | |
if tile.open and tile.hold >= self.delay: | |
self.open_neighbours(tile) | |
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Minesweeper game for iOS in Python (requires Pythonista) | |
# 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')) | |
def txt(x,y,t,s=14,f='HelveticaNeue'): | |
text(t,f,s,x,y) | |
class State: | |
thinking, playing, lost, won = range(4) | |
class Game(Scene): | |
def setup(self): | |
# setup for ipod | |
self.cols = 8 | |
self.rows = 10 | |
self.bombs = 12 | |
self.landscape = False | |
# adjust for landscape | |
if self.size.w > self.size.h: | |
self.cols = 16 | |
self.rows = 6 | |
self.bombs = 15 | |
self.landscape = True | |
self.started = False | |
self.delay = 10 # tap hold delay | |
self.scale = self.calculate_scale() | |
self.sprites = self.load_sprites() | |
self.layers = self.add_layers() | |
self.new_game() | |
# scale tile to fill screen size | |
def calculate_scale(self): | |
h_scale = int((self.size.w/(self.cols))/16) | |
v_scale = int((self.size.h/(self.rows+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(size.as_tuple())) | |
return Sprite(name,size) | |
def load_bg_sprite(self,image): | |
w = self.cols | |
h = self.rows | |
size = Size(w*16+12*2, h*16+11*3+33) | |
background = Image.new('RGBA',size,"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(size.as_tuple()) | |
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.cols * tile_width | |
height = self.rows * tile_height | |
offset_x = (self.size.w - width)/2 | |
offset_y = bg_bottom+11*self.scale | |
tiles = [] | |
for i in xrange(self.cols*self.rows): | |
x, y = i % self.cols, i / self.cols | |
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(.2,.2,.2) | |
if not self.landscape: | |
x = self.size.w/2 | |
y = self.size.h | |
txt(x,y-30,'Minesweeper',22) | |
if not self.started: | |
txt(x,30,'waiting...') | |
elif self.state == State.lost: | |
txt(x,30,'You lost!') | |
elif self.state == State.won: | |
txt(x,30,'You won!') | |
else: | |
txt(x,30,'playing...') | |
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 | |
# ignore timer if no tile has been tapped | |
if self.started: | |
seconds_passed = int((datetime.now() - self.start).total_seconds()) | |
else: | |
seconds_passed = 0 | |
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.started = False | |
self.state = State.thinking | |
self.layers.button.selected = False | |
self.start = datetime.now() | |
bomb_locations = [True] * self.bombs | |
bomb_locations+= [False] * (self.cols * self.rows - self.bombs) | |
shuffle(bomb_locations) | |
for i in xrange(len(self.layers.tiles)): | |
x, y = i % self.cols, i / self.cols | |
tile = self.layers.tiles[i] | |
tile.bomb = bomb_locations[i] | |
tile.selected = False | |
tile.hold = 0 | |
tile.marked = False | |
tile.open = 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.cols, i / self.cols | |
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.cols and y+dy>=0 and y+dy<self.rows: | |
n = (y+dy)*self.cols+x+dx | |
yield self.layers.tiles[n] | |
def draw_tile(self,tile): | |
if tile.open: | |
if tile.bomb: | |
tile.image = self.sprites.icons[5].name | |
else: | |
tile.image = self.sprites.numbers[tile.score].name | |
if self.state in [State.thinking, State.playing]: | |
if tile.selected: | |
tile.hold += 1 | |
else: | |
tile.hold = 0 | |
else: | |
if self.state in [State.thinking, State.playing]: | |
if tile.selected: | |
tile.hold += 1 | |
else: | |
tile.hold = 0 | |
if tile.hold == self.delay: | |
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 | |
# restart if first tile is bomb | |
if not self.started and tile.bomb: | |
self.new_game() | |
self.touch_tile(touch,True) | |
else: | |
# timer started on first tap | |
if not self.started: | |
self.start = datetime.now() | |
self.started = True | |
if not tile.marked and tile.hold < self.delay: | |
self.open_tile(tile) | |
if tile.open and tile.hold >= self.delay: | |
self.open_neighbours(tile) | |
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()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment