Advertisement
beezing

Minesweeper #2 (modified) - Pythonista

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