Advertisement
beezing

Minesweeper - Pythonista

May 20th, 2014
222
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.72 KB | None | 0 0
  1. # Minesweeper game for iOS in Python (requires Pythonista)
  2. # Author: Maurits van der Schee <maurits@vdschee.nl>
  3. # Sprites: http://www.curtisbright.com/msx/
  4.  
  5. from PIL import Image
  6. from StringIO import *
  7. from base64 import b64decode
  8.  
  9. from datetime import datetime
  10. from collections import namedtuple
  11.  
  12. from scene import *
  13. from random import shuffle
  14. from functools import partial
  15.  
  16. Sprite = namedtuple('Sprite',('name','size'))
  17.  
  18. class State:
  19.     thinking, playing, lost, won = range(4)
  20.  
  21. class Game (Scene):
  22.    
  23.     def setup(self):
  24.         self.width = 8
  25.         self.height = 8
  26.         self.bombs = 10
  27.         self.scale = self.calculate_scale()
  28.         self.sprites = self.load_sprites()
  29.         self.layers = self.add_layers()
  30.         self.new_game()
  31.    
  32.     def calculate_scale(self):
  33.         h_scale = int((self.size.w/(self.width))/16)
  34.         v_scale = int((self.size.h/(self.height+3))/16)
  35.         return min(h_scale,v_scale)
  36.    
  37.     def load_sprite(self,image,bounds):
  38.         width = bounds[2]-bounds[0]
  39.         height = bounds[3]-bounds[1]
  40.         size = Size(width*self.scale,height*self.scale)
  41.         name = load_pil_image(image.crop(bounds).resize(size.as_tuple()))
  42.         return Sprite(name,size)
  43.        
  44.     def load_bg_sprite(self,image):
  45.         w = self.width
  46.         h = self.height
  47.         size = Size(w*16+12*2, h*16+11*3+33)
  48.         background = Image.new('RGBA',size,"silver")
  49.         b = [([0,82,12,93],[0,0,12,11]),
  50.              ([13,82,14,93],[12,0,12+w*16,11]),
  51.              ([15,82,27,93],[12+w*16,0,12+w*16+12,11]),
  52.              ([0,94,12,95],[0,11,12,11+33]),
  53.              ([15,94,27,95],[12+w*16,11,12+w*16+12,11+33]),
  54.              ([0,96,12,107],[0,11+33,12,11+33+11]),
  55.              ([13,96,14,107],[12,11+33,12+w*16,11+33+11]),
  56.              ([15,96,27,107],[12+w*16,11+33,12+w*16+12,11+33+11]),
  57.              ([0,108,12,109],[0,11+33+11,12,11+33+11+h*16]),
  58.              ([15,108,27,109],[12+w*16,11+33+11,12+w*16+12,11+33+11+h*16]),
  59.              ([0,110,12,121],[0,11+33+11+h*16,12,11+33+11+h*16+11]),
  60.              ([13,110,14,121],[12,11+33+11+h*16,12+w*16,11+33+11+h*16+11]),
  61.              ([15,110,27,121],[12+w*16,11+33+11+h*16,12+w*16+12,11+33+11+h*16+11]),
  62.              ([28,82,69,107],[12+4,11+4,12+4+41,11+4+25]),
  63.              ([28,82,69,107],[12+w*16-4-41,11+4,12+w*16-4,11+4+25])]
  64.         for (s,t) in b:
  65.             background.paste(image.crop(s).resize((t[2]-t[0],t[3]-t[1])),t)
  66.         size = Size(size.w * self.scale, size.h * self.scale)
  67.         background = background.resize(size.as_tuple())
  68.         name = load_pil_image(background)
  69.         return Sprite(name,size)
  70.  
  71.     def load_sprites(self):
  72.         data ='''
  73.         iVBORw0KGgoAAAANSUhEUgAAAJAAAAB6CAMAAABnRypuAAADAFBMVEUAAACA
  74.         AAAAgACAgAAAAICAAIAAgIDAwMCAgID/AAAA/wD//wAAAP//AP8A//////8Q
  75.         EBARERESEhITExMUFBQVFRUWFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4f
  76.         Hx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJycoKCgpKSkqKiorKyssLCwtLS0u
  77.         Li4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6Ojo7Ozs8PDw9
  78.         PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tM
  79.         TExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpb
  80.         W1tcXFxdXV1eXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2doaGhpaWlq
  81.         ampra2tsbGxtbW1ubm5vb29wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5
  82.         eXl6enp7e3t8fHx9fX1+fn5/f3+AgICBgYGCgoKDg4OEhISFhYWGhoaHh4eI
  83.         iIiJiYmKioqLi4uMjIyNjY2Ojo6Pj4+QkJCRkZGSkpKTk5OUlJSVlZWWlpaX
  84.         l5eYmJiZmZmampqbm5ucnJydnZ2enp6fn5+goKChoaGioqKjo6OkpKSlpaWm
  85.         pqanp6eoqKipqamqqqqrq6usrKytra2urq6vr6+wsLCxsbGysrKzs7O0tLS1
  86.         tbW2tra3t7e4uLi5ubm6urq7u7u8vLy9vb2+vr6/v7/AwMDBwcHCwsLDw8PE
  87.         xMTFxcXGxsbHx8fIyMjJycnKysrLy8vMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT
  88.         09PU1NTV1dXW1tbX19fY2NjZ2dna2trb29vc3Nzd3d3e3t7f39/g4ODh4eHi
  89.         4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx
  90.         8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7///9Q
  91.         NrN6AAAE5UlEQVR4nO2bgZKjIAxAY7ezVu2M//+3pxAgCUHQUnFvzM61QGJ4
  92.         Bgjo7sHrYgKvnkvr+t8Ber9D/YGC9cGJt38+n+afrXdeUP+Lgnrwgno3XFtA
  93.         bwL0eBAiCjSUAf1mgMIESgO9BVDfu69gH4AMCwNi/hCFj4Dh8kB97750oDcD
  94.         QmkJxIfM4TxCnc2hp5eeD1kAYkNGeQ7NIcR5kHoZkJxDv7WAAo4+ZEbCkKFQ
  95.         oLXOgZDn0JARnFZA77ef1kXLngOVDJnjKVz2hUAsIgqQ0yuTmgIVzaFm9Rso
  96.         CzRzITNMl9r2oxCI7DN3kLUfuOTsFSDmLgkEHki3d3oHBB5o238tILvmjb0t
  97.         ArbqQOA2ijpAYI8wplNj75KQtV/KVr82vgyMqRsoY7/S2L3d+XeEByMUzlMG
  98.         aOBAS90CmMxtwgPYkALyDQcjtFg5IiVC/dLzvHYPCOTt6Rz6KhCbQ6vezBnY
  99.         Aorn0GEgoOJXGXh717XVvwZm74AApP9P5hA9lMdARm+BfITCJEoBuc21EhBf
  100.         xvGQRUCR/4+A5CoLfGhvGPA4oq0ybcj6TyIk8pAEkss+ykO1h8w4wG/F3oBA
  101.         3/vEWLJ1pIHk/psDiuwzW0fGfwQUnQcSQPQmN+3Fbp+zj4BkQ2sB8PcCS6XD
  102.         n6VM2/falJSJHwbkBxuGsXNWpkzbQ3kgNuHajpW5vV4OfXUCCK2WzxHQCsu0
  103.         3ZeHIdj4aztgZWGvln1fnYyQTyIrdZctU3tX7tYIkXLOnvrs4ggFI7TZLGsd
  104.         2AiFcs6e+rwj9D9ECNztBOqNMrX3ZbvKoggl7TcjxHJPSR7SyjIPablK5qFg
  105.         zzM1zbxfyMIl9kURSmXeksilIqRfWziHUnOiZG6VtO9eZa5drpqS1VfSvjsP
  106.         uXaZV0ryU0n7HaH/KUKAK5Gfh1LnG2oDiXOP6ic+Y4F+HsrmD5mH9Gy+Pw85
  107.         G5mpBycVz9Q7bRjQeDEB+dgUnp/GM1UEaNalf431VUnJAy0PnCnXH6iix2D3
  108.         uMuBVBPrurLqrwOZzKm7rqTaBQQwrRLeLI31VQSIwqpA9mrjQbqupyKvYCms
  109.         BhQutw6I64oq//aLwypA9HJjFVwvqh/4mfBTqmBaLzWfUqU4fEkeFz4VCMKH
  110.         7BVVWq8Ye/0qKxIogo2BYGJAEwTXMLEISRWJkFBZFk8kgNjdJ4AYNev1iMqy
  111.         4AcDiu/+DCAz82ynP2Y1CSA+ZgeAACCl2sG6C8jZxbNhWu92MjNIUeEsqQuU
  112.         WS/YOmmqjatilQfid5/KQ/5eYyAfoTgPec/KbUiHZJVRniOZOnj+PFNHsM33
  113.         soJMHe13393t83sZejjtPJTf7aV8/cS4cR6KfoHkngTqq4pOjKnLl+urq0qA
  114.         kpe/xjNVAWi8mLA/aBqXH/Jot/GkyaUfo99UfgFoffSFMpmr4WwAmUdfIC9N
  115.         IF2pDeQn+xJ5njeAvFaCdOVEIPLiDdKVUyPkX01CunJyhLBfSFfuCLWJkCEI
  116.         2037CCFQPzeO0JaybR6yL2f7mUfo/Ey9pWwcoR73squssn5WgO48dGfqT4Eu
  117.         GaGLnYdaZmplc215ptY31yL5JhDby8T/KklLTaCKvqoIj9A4imoDoCa9bgg/
  118.         D80XiNDW8aMtkHL8aAwUZ+ob6Aa6geoAhZe1I/+LmkZA5OXxJYDoy+wrAP0D
  119.         7BQA5IVg6IYAAAAASUVORK5CYII=
  120.         '''
  121.         image = Image.open(StringIO(b64decode(data))).convert('RGBA')
  122.         f = partial(self.load_sprite,image)
  123.         sprites = {}
  124.         sprites['numbers'] = map(lambda x: f([x*16,0,x*16+16,16]),xrange(9))
  125.         sprites['icons'] = map(lambda x: f([x*16,16,x*16+16,32]),xrange(8))
  126.         sprites['digits'] = map(lambda x: f([x*12,33,x*12+11,54]),xrange(11))
  127.         sprites['buttons'] = map(lambda x: f([x*27,55,x*27+26,81]),xrange(5))
  128.         sprites['background'] = self.load_bg_sprite(image)
  129.         return namedtuple('Sprites',sprites.keys())(*sprites.values())
  130.    
  131.     def add_layers(self):
  132.         # root layer
  133.         self.root_layer = Layer(self.bounds)
  134.         # background layer
  135.         width = self.sprites.background.size.w
  136.         height = self.sprites.background.size.h
  137.         offset = Point((self.size.w - width)/2,(self.size.h - height)/2)
  138.         bg = Layer(Rect(offset.x,offset.y,width,height))
  139.         bg.image = self.sprites.background.name
  140.         self.add_layer(bg)
  141.         # help variables
  142.         bg_left = offset.x
  143.         bg_right = offset.x+width
  144.         bg_top = offset.y+height
  145.         bg_bottom = offset.y
  146.         # smiley button layer
  147.         width = self.sprites.buttons[0].size.w
  148.         height = self.sprites.buttons[0].size.h
  149.         offset_x = (self.size.w - width)/2
  150.         offset_y = bg_top-(11+4)*self.scale-height
  151.         button = Layer(Rect(offset_x,offset_y,width,height))
  152.         button.image = self.sprites.buttons[0].name
  153.         self.add_layer(button)
  154.         # bomb count layer
  155.         width = self.sprites.digits[0].size.w
  156.         height = self.sprites.digits[0].size.h
  157.         offset_x = bg_left+(12+4+2)*self.scale
  158.         offset_y = bg_top-(11+4+2)*self.scale-height
  159.         bombs = []
  160.         for i in xrange(3):
  161.             x = offset_x+i*(width+2*self.scale)
  162.             count = Layer(Rect(x,offset_y,width,height))
  163.             count.image = self.sprites.digits[0].name
  164.             self.add_layer(count)
  165.             bombs.append(count)
  166.         # time count layer
  167.         width = self.sprites.digits[0].size.w
  168.         height = self.sprites.digits[0].size.h
  169.         offset_x = bg_right-(12+4+2*3)*self.scale-width*3
  170.         offset_y = bg_top-(11+4+2)*self.scale-height
  171.         seconds = []
  172.         for i in xrange(3):
  173.             x = offset_x+i*(width+2*self.scale)
  174.             count = Layer(Rect(x,offset_y,width,height))
  175.             count.image = self.sprites.digits[0].name
  176.             self.add_layer(count)
  177.             seconds.append(count)
  178.         # tile layers
  179.         tile_width = self.sprites.icons[0].size.w
  180.         tile_height = self.sprites.icons[0].size.h
  181.         width = self.width * tile_width
  182.         height = self.height * tile_height
  183.         offset_x = (self.size.w - width)/2
  184.         offset_y = bg_bottom+11*self.scale
  185.         tiles = []
  186.         for i in xrange(self.width * self.height):
  187.             x, y = i % self.width, i / self.width
  188.             x, y = offset_x + x * tile_width, offset_y + y * tile_height
  189.             tile = Layer(Rect(x, y, tile_width, tile_height))
  190.             self.add_layer(tile)
  191.             tiles.append(tile)
  192.         layers = {}
  193.         layers['root'] = self.root_layer
  194.         layers['background'] = bg
  195.         layers['button'] = button
  196.         layers['bombs'] = bombs
  197.         layers['seconds'] = seconds
  198.         layers['tiles'] = tiles
  199.         return namedtuple('Layers',layers.keys())(*layers.values())
  200.    
  201.     def draw(self):
  202.         background(.2,.2,.2)
  203.         self.layers.root.draw()
  204.         self.draw_button()
  205.         self.draw_counts()
  206.         self.draw_tiles()
  207.        
  208.     def draw_counts(self):
  209.         if self.state in [State.thinking, State.playing]:
  210.             bombs_left = self.bombs_left()
  211.             if bombs_left<0:
  212.                 self.layers.bombs[0].image = self.sprites.digits[10].name
  213.                 for i in xrange(2):
  214.                     digit = (abs(bombs_left)/pow(10,i))%10
  215.                     self.layers.bombs[2-i].image = self.sprites.digits[digit].name
  216.             else:
  217.                 for i in xrange(3):
  218.                     digit = (bombs_left/pow(10,i))%10
  219.                     self.layers.bombs[2-i].image = self.sprites.digits[digit].name
  220.             seconds_passed = int((datetime.now() - self.start).total_seconds())
  221.             for i in xrange(3):
  222.                 digit = (seconds_passed/pow(10,i))%10
  223.                 self.layers.seconds[2-i].image = self.sprites.digits[digit].name
  224.  
  225.     def draw_tiles(self):
  226.         for tile in self.layers.tiles:
  227.             self.draw_tile(tile)
  228.            
  229.     def bombs_left(self):
  230.         marked = 0
  231.         moves = 0
  232.         for tile in self.layers.tiles:
  233.             if tile.marked:
  234.                 marked += 1
  235.             if tile.bomb:
  236.                 if tile.open:
  237.                     self.state = State.lost
  238.             else:
  239.                 if not tile.open:
  240.                     moves += 1
  241.         if moves == 0:
  242.             self.state = State.won
  243.             return 0
  244.         return self.bombs - marked
  245.    
  246.     def draw_button(self):
  247.         if self.state in [State.thinking, State.playing]:
  248.             self.state = State.thinking
  249.             for tile in self.layers.tiles:
  250.                 if not tile.open and tile.selected:
  251.                     self.state = State.playing
  252.                     break
  253.         if self.layers.button.selected:
  254.             self.layers.button.image = self.sprites.buttons[4].name
  255.         else:
  256.             self.layers.button.image = self.sprites.buttons[self.state].name
  257.  
  258.     def new_game(self):
  259.         self.state = State.thinking
  260.         self.layers.button.selected = False
  261.         self.start = datetime.now()
  262.         bomb_locations = [True] * self.bombs
  263.         bomb_locations+= [False] * (self.width * self.height - self.bombs)
  264.         shuffle(bomb_locations)
  265.         for i in xrange(len(self.layers.tiles)):
  266.             x, y = i % self.width, i / self.width
  267.             tile = self.layers.tiles[i]
  268.             tile.bomb = bomb_locations[i]
  269.             tile.selected = False
  270.             tile.hold = 0
  271.             tile.marked = False
  272.             tile.open = False
  273.             tile.image = self.sprites.icons[0].name
  274.             tile.score = 0
  275.             for neighbour in self.get_neighbours(tile):
  276.                 if bomb_locations[self.layers.tiles.index(neighbour)]:
  277.                     tile.score += 1
  278.    
  279.     def get_neighbours(self,tile):
  280.         i = self.layers.tiles.index(tile)
  281.         x, y = i % self.width, i / self.width
  282.         neighbours = ((-1,-1),(0,-1),(1,-1),(-1,0),(1,0),(-1,1),(0,1),(1,1))
  283.         for (dx,dy) in neighbours:
  284.             if x+dx>=0 and x+dx<self.width and y+dy>=0 and y+dy<self.height:
  285.                 n = (y+dy)*self.width+x+dx
  286.                 yield self.layers.tiles[n]
  287.                    
  288.     def draw_tile(self,tile):
  289.         if tile.open:
  290.             if tile.bomb:
  291.                 tile.image = self.sprites.icons[5].name
  292.             else:
  293.                 tile.image = self.sprites.numbers[tile.score].name
  294.         else:
  295.             if self.state in [State.thinking, State.playing]:
  296.                 if tile.selected:
  297.                     tile.hold += 1
  298.                 else:
  299.                     tile.hold = 0
  300.                 if tile.hold == 15:
  301.                         tile.marked = not tile.marked
  302.                 if tile.marked:
  303.                     tile.image = self.sprites.icons[3].name
  304.                 elif tile.selected:
  305.                     tile.image = self.sprites.icons[1].name
  306.                 else:
  307.                     tile.image = self.sprites.icons[0].name
  308.             if self.state == State.lost:
  309.                 if tile.bomb and not tile.marked:
  310.                     tile.image = self.sprites.icons[2].name
  311.                 if not tile.bomb and tile.marked:
  312.                     tile.image = self.sprites.icons[4].name
  313.             if self.state == State.won:
  314.                 if tile.bomb:
  315.                     tile.image = self.sprites.icons[3].name
  316.  
  317.     def touch_tile(self, touch, release):
  318.         for tile in self.layers.tiles:
  319.             tile.selected = touch.location in tile.frame
  320.             if tile.selected and release:
  321.                 tile.selected = False
  322.                 if not tile.marked and tile.hold<15:
  323.                     self.open_tile(tile)
  324.  
  325.     def open_tile(self,tile):
  326.         tile.open = True
  327.         if tile.score == 0 and not tile.bomb:
  328.             for neighbour in self.get_neighbours(tile):
  329.                 if not neighbour.open:
  330.                     self.open_tile(neighbour)
  331.                            
  332.     def touch_button(self, touch, release):
  333.         button = self.layers.button
  334.         button.selected = touch.location in button.frame
  335.         if button.selected and release:
  336.             self.new_game()
  337.        
  338.     def touch_began(self, touch):
  339.         if self.state in [State.thinking, State.playing]:
  340.             self.touch_tile(touch, False)
  341.         self.touch_button(touch, False)
  342.        
  343.     def touch_moved(self,touch):
  344.         self.touch_began(touch)
  345.    
  346.     def touch_ended(self, touch):
  347.         if self.state in [State.thinking, State.playing]:
  348.             self.touch_tile(touch, True)
  349.         self.touch_button(touch, True)
  350.        
  351. run(Game())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement