TkShisenSho

Receita: TkShisenSho

Em uma noite de insônia brincando com os joguinhos do KDE, achei o Shisen-Sho, uma variação do Mahjongg. O problema é que fiquei viciado nisso, um dia viajei, em um lugar com computador só com Windows, acabei resolvendo aproveitar melhor algumas horas de insônia e fazer um em Python e Tkinter. Tem alguns bugs, não marca tempo nem placar, mas funciona. O algoritmo para encontrar a rota correta (baseado no A*) pode servir para alguém, e o código é um exemplo razoável do poder do Canvas da Tkinter.

Código

   1 #!/usr/bin/env python
   2 
   3 
   4 import random
   5 import math
   6 
   7 from Tkinter import *
   8 
   9 import Image
  10 import ImageTk
  11 import ImageEnhance
  12 import itertools
  13 
  14 
  15 SIZE = [(16, 8), (20, 10), (26, 14), (28, 16), (32, 18)]
  16 CELLSIZE = 40, 56
  17 IMAGE = 'kmahjongg.bmp'
  18 PATHTIME = 200
  19 GRAVITY = True
  20 
  21 
  22 def load_images():
  23     w, h = 40, 56
  24     base = Image.open(IMAGE)
  25     cells = []
  26     for x in range(0, 9*w, w):
  27         for y in range(0, 3*h, h):
  28             im = base.crop((x, y, x+w, y+h))
  29             cells.append(im)
  30     for x in range(0, 8*w, w):
  31         for y in range(3*h, 4*h, h):
  32             im = base.crop((x, y, x+w, y+h))
  33             cells.append(im)
  34     for x in range(0, 7*w, w):
  35         for y in range(4*h, 5*h, h):
  36             im = base.crop((x, y, x+w, y+h))
  37             cells.append(im)
  38     assert len(cells) == 42
  39     return cells
  40 
  41 
  42 class Cell(object):
  43     def __init__(self, board, x, y):
  44         self.board = board
  45         self.x = x
  46         self.y = y
  47         self.content = None
  48         self.tag = None
  49 
  50         w, h = self.board.cellsize
  51         self.center = (x*w + w/2), (y*h + h/2)
  52         x0 = self.x * w
  53         y0 = self.y * h
  54         x1 = (self.x+1) * w
  55         y1 = (self.y+1) * h
  56         self.coords = (x0, y0, x1, y1)
  57         
  58     def __repr__(self):
  59         return '<%r, %r>'%(self.x, self.y)
  60 
  61     def set(self, content):
  62         self.content = content
  63         self.board.itemconfig(self.tag, image=content[0])
  64 
  65     def clear(self):
  66         self.content = None
  67         self.board.itemconfig(self.tag, image='')
  68 
  69     def light(self):
  70         if self.content is not None:
  71             self.board.itemconfig(self.tag, image=self.content[1])
  72 
  73     def dark(self):
  74         if self.content is not None:
  75             self.board.itemconfig(self.tag, image=self.content[0])
  76 
  77     def draw(self):
  78         if self.tag is None:
  79             self.tag = self.board.create_image(self.center)
  80 
  81     def neighbors(self):
  82         # return adjacent cells 
  83         for v in (-1, 1):
  84             x = self.x + v
  85             y = self.y + v
  86 
  87             if 0 <= x < self.board.w:
  88                 cell = self.board[x, self.y]
  89                 yield cell
  90 
  91             if 0 <= y < self.board.h:
  92                 cell = self.board[self.x, y]
  93                 yield cell
  94 
  95     def routes(self, dest):
  96         # get route from self to dest
  97         
  98         #create a list paths
  99         paths = []
 100         #add the start node self to paths giving it one element
 101         paths.append((self,))
 102 
 103         #Until first path of paths ends with dest, or paths is empty
 104         while paths and paths[0][-1] is not dest:
 105             #extract the first path from paths
 106             first = paths.pop(0)
 107             #extend the path one step to all empty neighbors
 108             #create X new paths
 109             #reject all paths with loops
 110             new = [first+(n,) for n in first[-1].neighbors() if n not in first and (n.content is None or n is dest)]
 111             
 112             #filter out paths with more than n turns
 113             new = filter(self.lines, new)
 114 
 115             #add each remaining new path to paths
 116             paths.extend(new)
 117 
 118             #sort paths by distance to goal
 119             paths.sort(key=lambda p: self.dist(p[-1], dest))
 120             
 121 
 122 
 123         return paths
 124 
 125     def dist(self, s, d):
 126         x = abs(s.x - d.x)
 127         y = abs(s.y - d.y)
 128         h = math.sqrt(x**2 + y**2)
 129         return h
 130         
 131         
 132     def lines(self, path):
 133         n = 3
 134         i = 0
 135 
 136         b = path[0]
 137         x = False
 138         y = False
 139         for a in path[1:]:
 140             if a.x == b.x:
 141                 if not x:
 142                     x = True
 143                     i += 1
 144             else:
 145                 x = False
 146 
 147             if a.y == b.y:
 148                 if not y:
 149                     y = True
 150                     i += 1
 151             else:
 152                 y = False
 153 
 154             b = a
 155 
 156             if i > n:
 157                 return False
 158         return True
 159             
 160 
 161         
 162 
 163 class Board(Canvas):
 164     def __init__(self, parent):
 165         Canvas.__init__(self, parent)
 166         self.parent = parent
 167         self.size = w, h = SIZE[3]
 168         self.w = w
 169         self.h = h
 170         self.cellsize = CELLSIZE
 171 
 172         self.cells = [Cell(self, x, y) for x in range(w) for y in range(h)]
 173         self.tags = {}
 174 
 175         self.src = None
 176 
 177         self._images = None
 178         self.images = None
 179         self.aimages = None
 180 
 181         self.setup()
 182         self.reset()
 183 
 184     def setup(self):
 185         self._images = load_images()
 186         self.images = [ImageTk.PhotoImage(img) for img in self._images]
 187         self.aimages = [ImageTk.PhotoImage(ImageEnhance.Brightness(img).enhance(1.2)) for img in self._images]
 188         
 189         self['width'] = self.cellsize[0] * self.w
 190         self['height'] = self.cellsize[1] * self.h
 191         self['bg'] = '#00aa00'
 192 
 193         for c in self.cells:
 194             c.draw()
 195             self.tags[c.tag] = c
 196 
 197         self.bind('<Button-1>', self.lclick)
 198         self.bind('<Button-3>', self.rclick)
 199         self.parent.bind('h', self.givehint)
 200 
 201     def reset(self):
 202         ncells = (self.w-2) * (self.h-2)
 203         nimgs = ncells/4
 204 
 205         simgs = itertools.cycle(zip(self.images, self.aimages)[:nimgs])
 206         
 207         imgs = []
 208         while len(imgs) < ncells:
 209             x = simgs.next()
 210             imgs.append(x)
 211             imgs.append(x)
 212 
 213         random.shuffle(imgs)
 214 
 215         assert len(imgs) == ncells, (nimgs, len(imgs), ncells)
 216 
 217         for x in range(1, self.w-1):
 218             for y in range(1, self.h-1):
 219                 self[x, y].set(imgs.pop())
 220 
 221         assert len(imgs) == 0
 222 
 223     def __getitem__(self, i):
 224         x, y = i
 225         if not x < self.w:
 226             raise IndexError('x=%s'%x)
 227         if not y < self.h:
 228             raise IndexError('y=%s'%y)
 229         c = y + (self.h * x)
 230         return self.cells[c]
 231 
 232     def draw_path(self, path, fill='red'):
 233         coords = [cell.center for cell in path]
 234         return self.create_line(coords, fill=fill, width=2)
 235 
 236     def clear_cells(self, path, src, dest):
 237         print 'clear'
 238         self.delete(path)
 239         src.dark()
 240         dest.dark()
 241         src.clear()
 242         dest.clear()
 243         if GRAVITY:
 244             self.drop_cells(src.x)
 245             self.drop_cells(dest.x)
 246 
 247     def drop_cells(self, col):
 248         for row in reversed(range(1, self.h-1)):
 249             x, y = col, row
 250             a = self[x, y]
 251             b = self[x, y-1]
 252             if a.content is None and b.content is not None:
 253                 a.set(b.content)
 254                 b.clear()
 255 
 256     def lclick(self, event=None):
 257         x = self.canvasx(event.x)
 258         y = self.canvasy(event.y)
 259         w, h = self.cellsize
 260         rx, ry = int(x/w), int(y/h)
 261         print rx, ry
 262         cell = self[rx, ry]
 263         if cell.content is not None:
 264             self.clickcell(cell)
 265 
 266     def clickcell(self, cell):
 267 
 268         cell.light()
 269 
 270         if self.src is None:
 271             self.src = cell
 272 
 273         elif self.src is cell:
 274             self.src.dark()
 275             self.src = None
 276 
 277         elif self.src.content != cell.content:
 278             print "Contents don't match!"
 279             self.src.dark()
 280             cell.dark()
 281             self.src = None
 282 
 283         else:
 284             paths = self.src.routes(cell)
 285             if not paths:
 286                 print 'No path!'
 287                 self.src.dark()
 288                 cell.dark()
 289             else:
 290                 path = paths[0]
 291                 tag = self.draw_path(path)
 292                 self.after(PATHTIME, self.clear_cells, tag, self.src, cell)
 293             self.src = None
 294         
 295     def rclick(self, event=None):
 296         x = self.canvasx(event.x)
 297         y = self.canvasy(event.y)
 298         w, h = self.cellsize
 299         rx, ry = int(x/w), int(y/h)
 300         print rx, ry
 301         cell = self[rx, ry]
 302         self.cellhint(cell, show=True)
 303 
 304     def cellhint(self, cell, delay=0.5, show=False):
 305         if cell.content is not None:
 306             # find a cell with same contents
 307             for other in self.cells:
 308                 if other is cell:
 309                     continue
 310                 if cell.content == other.content:
 311                     paths = cell.routes(other)
 312                     if paths:
 313                         if show:
 314                             p = paths[0]
 315                             tag = self.draw_path(p, fill='blue')
 316                             self.after(int(delay*1000), self.delete, tag)
 317                         return other
 318             else:
 319                 return False
 320                     
 321     def givehint(self, event=None, show=True):
 322         for cell in self.cells:
 323             if cell.content is not None:
 324                 other = self.cellhint(cell, delay=1, show=show)
 325                 if other:
 326                     return cell, other
 327         else:
 328             print 'No more moves possible. Game Over!'
 329 
 330 
 331 
 332 
 333 
 334 ### Gui
 335 
 336 class MainWindow(Tk):
 337     def __init__(self):
 338         Tk.__init__(self)
 339 
 340         self.build()
 341 
 342 
 343     def build(self):
 344         self.board = Board(self)
 345         self.board.pack(expand=1, fill=BOTH)
 346         
 347 
 348         
 349 
 350 
 351 if __name__ == '__main__':
 352     root = MainWindow()
 353     root.mainloop()
 354 
 355         
 356     

Imagem com as peças, do jogo original para o KDE, liberada sob GPL:

http://www.pedrowerneck.net/img/kmahjongg.bmp

Volta para CookBook.


PedroWerneck

TkShisenSho (editada pela última vez em 2008-09-26 14:05:57 por localhost)