1import time, random 2tt = time.time() 3from browser import document, window, html 4from browser.timer import set_timeout, request_animation_frame 5#print("brython imports", time.time()-tt) 6import brySVG.polygoncanvas as SVG 7#print("brySVG", time.time()-tt) 8import brywidgets as ws 9#print("brywidgets", time.time()-tt) 10from puzzledata import puzzles 11from ommydata import piecedata, pieceorder 12#print("puzzledata", time.time()-tt) 13 14ws.Global.alertStyle = None 15rotatelefticon = SVG.svg.path(d="M 0,8 a 8,8 0 1 0 -5.66,-13.66 l 0,-4 m 0,4 l 4,1", style={"stroke":"black", "strokeWidth":2, "fill":"none", "strokeLinejoin":"round", "strokeLinecap":"round"}) 16rotaterighticon = SVG.svg.path(d="M 0,8 a 8,8 0 1 1 5.66,-13.66 l 0,-4 m 0,4 l -4,1", style={"stroke":"black", "strokeWidth":2, "fill":"none", "strokeLinejoin":"round", "strokeLinecap":"round"}) 17reflectXicon = SVG.GroupObject([SVG.PolygonObject([(-10,0),(-10,4),(-2,4),(-2,-4),(-6,-4),(-6,0)]),SVG.PolygonObject([(10,0),(10,4),(2,4),(2,-4),(6,-4),(6,0)]),SVG.LineObject([(0,-10),(0,10)],style="faintdash2", linewidth=2)]) 18reflectYicon = SVG.GroupObject([SVG.PolygonObject([(0,-10),(4,-10),(4,-2),(-4,-2),(-4,-6),(0,-6)]),SVG.PolygonObject([(0,10),(4,10),(4,2),(-4,2),(-4,6),(0,6)]),SVG.LineObject([(-10,0),(10,0)],style="faintdash2", linewidth=2)]) 19 20tcombine = [ 21[0,1,2,3,4,5,6,7], 22[1,2,3,0,6,7,5,4], 23[2,3,0,1,5,4,7,6], 24[3,0,1,2,7,6,4,5], 25[4,7,5,6,0,2,3,1], 26[5,6,4,7,2,0,1,3], 27[6,4,7,5,1,3,0,2], 28[7,5,6,4,3,1,2,0] 29] 30 31tinverse = [0,3,2,1,4,5,6,7] 32 33class AddPieceDialog(ws.DialogBox): 34 def __init__(self): 35 super().__init__("Check for solution") 36 self <= html.IMG(src="tick.png") 37 self <= html.P("Success - the puzzle can be solved from this position.") 38 self <= html.P("Would you like any extra pieces placed as a hint?") 39 self.hintcount = ws.SpinControl(1, 1, 5) 40 self <= html.P(["Number of pieces:", self.hintcount]) 41 addbutton = ws.Button("Place extra pieces", self.onaddbutton) 42 closebutton = ws.Button("No thanks", self.close) 43 self <= html.P([addbutton, closebutton]) 44 45 def onaddbutton(self, event): 46 self.hide() 47 puzzlecanvas.addpieces(self.hintcount.currentvalue) 48 49class RemovePieceDialog(ws.DialogBox): 50 def __init__(self): 51 super().__init__("Check for solution") 52 self <= html.IMG(src="cross.png") 53 self <= html.P("Unfortunately the puzzle cannot be solved from this position.") 54 self <= html.P("Would you like some pieces removed in order to get back to a position from which it can be solved?") 55 self.removebutton = ws.Button("Remove pieces", self.onremovebutton) 56 closebutton = ws.Button("No thanks", self.close) 57 self <= html.P([self.removebutton, closebutton]) 58 self.hide() 59 60 def onremovebutton(self, event): 61 self.removebutton.innerHTML = "Please wait" 62 do_after_page_refresh(puzzlecanvas.removepieces) 63 64class ComplexPolygon(SVG.GroupObject): 65 def __init__(self, polygons=[[(0, 0)]], holes=[], linecolour="black", linewidth=1, fillcolour="yellow", holecolour="white", objid=None): 66 self._polygons = [SVG.PolygonObject(pointlist, linecolour, linewidth, fillcolour) for pointlist in polygons] 67 self._holes = [SVG.PolygonObject(pointlist, linecolour, linewidth, holecolour) for pointlist in holes] 68 self.linecolour = linecolour 69 self.linewidth = linewidth 70 self.fillcolour = fillcolour 71 self.holecolour = holecolour 72 super().__init__(self._polygons+self._holes) 73 if objid: self.id = objid 74 75 @property 76 def polygons(self): 77 return self._polygons 78 79 @polygons.setter 80 def polygons(self, polygons): 81 for poly in self._polygons: self.removeObject(poly) 82 self._polygons = [SVG.PolygonObject(pointlist, self.linecolour, self.linewidth, self.fillcolour) for pointlist in polygons] 83 self.addObjects(self._polygons) 84 85 @property 86 def holes(self): 87 return self._holes 88 89 @holes.setter 90 def holes(self, holes): 91 for poly in self._holes: self.removeObject(poly) 92 self._holes = [SVG.PolygonObject(pointlist, self.linecolour, self.line
92width, self.holecolour) for pointlist in holes] 93 self.addObjects(self._holes) 94 95 def getBoundingBox(self): 96 allpoints = sum((poly.pointList for poly in self.polygons), start=[]) 97 return SVG._getboundingbox(allpoints) 98 99class Polyomino(SVG.PolygonObject): 100 def __init__(self, coords=None, offsetcoords=None, colour=None, number=None, startmatrix=None, transformlist=None): 101 super().__init__() 102 self.coords = coords 103 self.offsetcoords = offsetcoords 104 self.colour = colour 105 self.number = number 106 self.startmatrix = startmatrix 107 self.transformlist = transformlist 108 self.currenttransform = 0 109 self.transforms = [self.identity, self.rotateleft, self.rotate180, self.rotateright, self.reflectX, self.reflectY, self.reflectXleft, self.reflectYleft] 110 self.csstransforms = ["", "rotate(-90deg)", "rotate(180deg)", "rotate(90deg)", "scale(-1, 1)", "scale(1, -1)", "scale(-1, 1) rotate(-90deg)", "scale(1, -1) rotate(-90deg)"] 111 #The following attributes are only set once a puzzle has been chosen 112 self.startcoords = self.startcx = self.startcy = self.fixed = None 113 self.matrix = self.mwidth = self.mheight = self.offsets = None 114 115 def reset(self): 116 self.setPointList(self.startcoords) 117 self.matrix = self.startmatrix[:] 118 self.updateoffsets() 119 self.currenttransform = 0 120 self.style.transition = "none" 121 self.style.transform = "translate(0px,0px)" 122 self.style.opacity = 1 123 124 def identity(self): 125 pass 126 127 def rotateleft(self): 128 self.rotate(-90, self.getCentre()) 129 self.matrix = [[self.matrix[j][self.mwidth-1-i] for j in range(self.mheight)] for i in range(self.mwidth)] 130 self.updateoffsets() 131 self.currenttransform = tcombine[self.currenttransform][1] 132 133 def rotate180(self): 134 self.rotate(180, self.getCentre()) 135 self.matrix = list(reversed([list(reversed(row)) for row in self.matrix])) 136 self.updateoffsets() 137 self.currenttransform = tcombine[self.currenttransform][2] 138 139 def rotateright(self): 140 self.rotate(90, self.getCentre()) 141 self.matrix = [[self.matrix[self.mheight-1-j][i] for j in range(self.mheight)] for i in range(self.mwidth)] 142 self.updateoffsets() 143 self.currenttransform = tcombine[self.currenttransform][3] 144 145 def reflectX(self): 146 (cx, cy) = self.getCentre() 147 self.xstretch(-1, cx) 148 self.matrix = [list(reversed(row)) for row in self.matrix] 149 self.updateoffsets() 150 self.currenttransform = tcombine[self.currenttransform][4] 151 152 def reflectY(self): 153 (cx, cy) = self.getCentre() 154 self.ystretch(-1, cy) 155 self.matrix.reverse() 156 self.updateoffsets() 157 self.currenttransform = tcombine[self.currenttransform][5] 158 159 def reflectXleft(self): 160 self.rotateleft() 161 self.reflectX() 162 163 def reflectYleft(self): 164 self.rotateleft() 165 self.reflectY() 166 167 def updateoffsets(self): 168 gridheight = puzzlecanvas.gridheight 169 offsets = [] 170 for i, row in enumerate(self.matrix): 171 for j, square in enumerate(row): 172 if square == 0: offsets.append(j*gridheight+i) 173 offsets.sort() 174 self.offsets = offsets 175 self.mwidth = len(self.matrix[0]) 176 self.mheight = len(self.matrix) 177 178 def gettransformedstartcoords(self, transformno): 179 clone = self.cloneObject() 180 clone.setPointList(self.startcoords) 181 clone.matrix = self.startmatrix[:] 182 clone.updateoffsets() 183 clone.transforms[transformno]() 184 return min(clone.pointList) 185 186class PuzzleCanvas(SVG.CanvasObject): 187 def __init__(self): 188 super().__init__(colour="cyan", objid="puzzlecanvas") 189 self.xcentre, buttontop, unit = 5450, 5300, 8 190 bwidth, ibwidth, bheight, spacer = 8*unit, 3*unit, 3*unit, 2*unit 191 bleft = self.xcentre-29*unit 192 self.pieceareatop = buttontop+5*unit 193 bdata = [("Instructions", self.instructions), ("Choose\nNew Puzzle", self.onmenuclick), 194 (rotatelefticon, self.rotateleftclick), (rotaterighticon, self.rotaterightclick), 195 (reflectXicon, self.reflectXclick), (reflectYicon, self.reflectYclick), 196 ("Restart", self.restartpuzzle), ("Check /\nGet Hint", self.onhintclick)] 197 for (bcontent, onclick) in bdata: 198 if isinstance(bcontent, str): 199 self <= SVG.Button((bleft, buttontop), (bwidth, bheight), bcontent, onclick, fontsize=9, fillcolour="pink") 200 bleft += (bwidth+spacer) 201 else: 202 self <= SVG.ImageButton((bleft, buttontop), (ibwidth, bheight), bcontent, onclick, fillcolour="pink") 203 bleft += (ibwidth+spacer) 204 self.puzzleoutline = ComplexPolygon(fillcolour="white", holecolour="cyan") 205 self.addObject(self.puzzleoutline, fixed=True) 206 207 self.colours = ["peru", "pink", "orange", "blue", "magenta", "lime", "khaki", "yellow", "palegreen", "teal", "darkorchid", "silver"]*3 208 209 self.pieces = [] 210 for i, pieceinfo in enumerate(piecedata[ommysize]): 211 piece = Polyomino(pieceinfo["coords"], pieceinfo["offset"], self.colours[i], i+1, pieceinfo["matrix"], pieceinfo["transforms"]) 212 self.addObject(piece) 213 piece.bind("mousedown", self.onpiececlick) 214 piece.bind("touchstart", self.onpiececlick) 215 self.pieces.append(piece) 216 217 self.selectedpiece = None 218 self.bind("contextmenu", self.onRightClick) 219 self.bind("mouseup", self.onMouseUp) 220 self.bind("touchend", self.onMouseUp) 221 self.style.display="none" 222 self.mouseMode = SVG.MouseMode.DRAG 223 224 def showpuzzle(self, puzzleno): 225 self.style.display = "block" 226 puzzle = puzzles[ommysize][puzzleno] 227 for key in puzzle: setattr(self, key, puzzle[key]) 228 if self.piecestouse != []: 229 self.allowedpieces = [p for p in self.piecestouse if p not in self.fixedpieces] 230 else: 231 self.allowedpieces = [p for p in pieceorder[ommysize] if p not in self.fixedpieces] 232 self.solution = None 233 234 if self.gridheight > self.gridwidth: 235 self.partscoords = [[(y, x) for (x, y) in poly] for poly in self.partscoords] 236 self.holescoords = [[(y, x) for (x, y) in hole] for hole in self.holescoords] 237 self.gridlist = sum([self.gridlist[k:len(self.gridlist):self.gridheight] for k in range(self.gridheight)], []) 238 self.gridwidth, self.gridheight = self.gridheight, self.gridwidth 239 self.puzzleoutline.polygons = self.partscoords 240 self.puzzleoutline.holes = self.holescoords 241 self.outlineleft = self.xcentre-self.gridwidth*15/2 242 self.outlinetop = self.positionpieces() 243 self.puzzleoutline.translate((self.outlineleft,self.outlinetop)) 244 self.fitContents() 245 246 def positionpieces(self): 247 if self.piecestouse: 248 (boxwidth, boxheight) = (90, 60) 249 colcount = 5 250 (xstart, ystart) = (self.xcentre-colcount*boxwidth/2, self.pieceareatop) 251 usedcount = 0 252 for piece in self.pieces: 253 if piece.number in self.piecestouse:
254 row, col = divmod(usedcount, colcount) 255 piece.startcoords = [(x+col*boxwidth+xstart, y+row*boxheight+ystart) for (x, y) in piece.coords] 256 piece.style.display = "inline" 257 usedcount += 1 258 else: 259 piece.style.display = "none" 260 outlinetop = ystart + ((usedcount-1)//colcount + 1)*boxheight 261 else: 262 (pieceareawidth, pieceareaheight) = (270, 75) if ommysize==4 else (300, 105) if ommysize==5 else (465, 255) 263 (xstart, ystart) = (self.xcentre-pieceareawidth/2, self.pieceareatop) 264 for piece in self.pieces: 265 (xoffset, yoffset) = piece.offsetcoords 266 piece.startcoords = [(x+xoffset+xstart, y+yoffset+ystart) for (x,y) in piece.coords] 267 piece.style.display = "inline" 268 outlinetop = self.pieceareatop + pieceareaheight + 15 269 self.restartpuzzle() 270 return outlinetop 271 272 def restartpuzzle(self, event=None): 273 for piece in self.pieces: 274 if self.piecestouse and (piece.number not in self.piecestouse): continue 275 piece.reset() 276 (piece.startcx, piece.startcy) = piece.getCentre() 277 if piece.number in self.fixedpieces: 278 piece.fixed = True 279 piece.style.fill = "white" 280 piece.style.strokeDasharray = 3 281 else: 282 piece.fixed = False 283 piece.style.fill = piece.colour 284 piece.style.strokeDasharray = "none" 285 286 self.usedpieces = [] 287 self.gridlist = [(99 if x==99 else 0) for x in self.gridlist] 288 289 def instructions(self, event): 290 ws.showalert ("""Fill the shape using the pieces. For some puzzles you do not need to use all the pieces. 291If there is a piece which is white with a dashed border, it cannot be used for this puzzle. 292 293To rotate a piece through 90 degrees, 294or reflect it vertically or horizontally, 295select it and then click on the appropriate button. 296Alternatively, right-click on a shape to rotate it clockwise.""", "How to play") 297 298 def onmenuclick(self, event): 299 self.style.display = "none" 300 menucanvas.style.display = "block" 301 302 def onhintclick(self, event): 303 if len(self.usedpieces) == self.piececount: return 304 pleasewaitdialog.show() 305 do_after_page_refresh(self.checkposition) 306 307 def onRightClick(self, event): 308 event.preventDefault() 309 if event.target in self.pieces: 310 self.objectDict[event.target.id].rotateright() 311 set_timeout(self.checkmove, 0) 312 313 def onpiececlick(self, event): 314 event.preventDefault() 315 for piece in self.pieces: 316 piece.style.stroke = "black" 317 self.selectedpiece = self.objectDict[event.target.id] 318 self.selectedpiece.style.stroke = "red" 319 320 def rotateleftclick(self, event): 321 if self.selectedpiece: 322 self.selectedpiece.rotateleft() 323 set_timeout(self.checkmove, 0) 324 325 def rotaterightclick(self, event): 326 if self.selectedpiece: 327 self.selectedpiece.rotateright() 328 set_timeout(self.checkmove, 0) 329 330 def reflectXclick(self, event): 331 if self.selectedpiece: 332 self.selectedpiece.reflectX() 333 set_timeout(self.checkmove, 0) 334 335 def reflectYclick(self, event): 336 if self.selectedpiece: 337 self.selectedpiece.reflectY() 338 set_timeout(self.checkmove, 0) 339 340 def onMouseUp(self, event): 341 if event.type == "mouseup" and event.button > 0: return 342 if self.getSelectedObject(event.target.id) not in self.pieces: return 343 if not self.selectedpiece: return 344 set_timeout(self.checkmove, 0) 345 346 def checkmove(self): 347 piece = self.selectedpiece 348 piece.style.opacity = 1 349 pieceno = piece.number 350 self.gridlist = [(0 if x==pieceno else x) for x in self.gridlist] #If piece was in grid, remove it before replacing 351 352 (x, y) = min(piece.pointList) 353 (i, j1) = (int((x-self.outlineleft)/15+0.4), int((y-self.outlinetop)/15+0.4)) 354 j = j1-piece.offsets[0] 355 if i>=0 and i+piece.mwidth <= self.gridwidth and j>=0 and j+piece.mheight <= self.gridheight: 356 startsquare = i*self.gridheight+j 357 for offset in self.selectedpiece.offsets: 358 if self.gridlist[startsquare+offset] != 0: 359 piece.style.opacity = 0.5 360 break 361 else: 362 for offset in self.selectedpiece.offsets: self.gridlist[startsquare+offset] = pieceno 363 (x1, y1) = (self.outlineleft+i*15, self.outlinetop+j1*15) 364 piece.translate((x1-x, y1-y)) 365 if pieceno not in self.usedpieces: self.usedpieces.append(pieceno) 366 #print("Placed by hand:") 367 #print("\n".join([str(self.gridlist[k:len(self.gridlist):self.gridheight]) for k in range(self.gridheight)])) 368 #print("Current used pieces:", self.usedpieces) 369 if (len(self.usedpieces) == self.piececount) and (0 not in self.gridlist): 370 self.selectedpiece.style.stroke = "black" 371 self.cyclecolours(iter(self.colours[:8])) 372 else: 373 try: 374 self.usedpieces.remove(pieceno) 375 #print("Piece removed from grid:") 376 #print("\n".join([str(self.gridlist[k:len(self.gridlist):self.gridheight]) for k in range(self.gridheight)])) 377 #print("Current used pieces:", self.usedpieces) 378 except ValueError: pass 379 380 def cyclecolours(self, colourlist): 381 def changecolour(): 382 nonlocal colour 383 for piece in self.pieces: 384 piece.style.fill = colour 385 piece.style.strokeDasharray = "none" 386 try: 387 colour = next(colourlist) 388 except: 389 return 390 set_timeout(changecolour,100) 391 colour = next(colourlist) 392 set_timeout(changecolour,0) 393 394 def checkposition(self): 395 def issubgrid(partialgrid, fullgrid): 396 for m, n in zip(partialgrid, fullgrid): 397 if m>0 and m!=n: return False 398 return True 399 400 tt = time.time() 401 success = False 402 if self.solution: 403 (finalgrid, finalused, finaltlist) = self.solution 404 if issubgrid(self.gridlist, finalgrid): 405 #print("Can use previous solution.") 406 success = True 407 finalused = self.usedpieces + [p for p in finalused if p not in self.usedpieces] 408 self.solution = (finalgrid, finalused, finaltlist) 409 if not success: 410 success, finalgrid, finalused, tlist = window.solvepuzzle(ommysize, self.gridwidth, self.gridheight, self.gridlist, self.allowedpieces, self.usedpieces) 411 if success and (0 not in finalgrid): self.storesolution(finalgrid, finalused, tlist) 412 #print(time.time() - tt) 413 pleasewaitdialog.hide() 414 if success and (0 in finalgrid): 415 ws.showalert("Sorry - it is taking too long to check this position.\nYou could add or remove some pieces, and then try again", "Check for solution") 416 #timeoutdialog.show() 417 elif success: 418 addpiecedialog.show() 419 #print("Solution:") 420 #print("\n".join([str(finalgrid[k:len(finalgrid):self.gridheight]) for k in range(self.gridheight)])) 421 #print("Order placed:", finalused) 422 #print("Transform list:", [(i,finaltlist[i]) for i in finalused]) 423 else: 424 removepiecedialog.show() 425 #print("No solution") 426 427 def storesolution(self, finalgrid, finalused, tlist): 428 finaltlist = [0]*(len(self.pieces)+1) 429 for pieceno in finalused: 430 piece = self.pieces[pieceno-1] 431 if pieceno in self.usedpieces: 432 finaltlist[pieceno] = piece.currenttransform 433 else: 434 finaltlist[pieceno] = piece.transformlist[tlist[pieceno]] 435 self.solution = (finalgrid, finalused, finaltlist) 436 437 def addpieces(self, n): 438 def addpiece(pieceno, requiredtransform, newsquare, newposition): 439 piece = self.pieces[pieceno-1] 440 piece.reset() 441 piece.transforms[requiredtransform]() 442 (x0, y0) = min(piece.pointList) 443 (x, y) = newposition 444 piece.translate((x-x0, y-y0)) 445 446 startsquare = newsquare - piece.offsets[0] 447 for offset in piece.offsets: self.gridlist[startsquare+offset] = pieceno 448 self.usedpieces.append(pieceno) 449 #print("After hint:") 450 #print("\n".join([str(self.gridlist[k:len(self.gridlist):self.gridheight]) for k in range(self.gridheight)])) 451 #print("Current used pieces:", self.usedpieces) 452 453 (finalgrid, finalused, finaltlist) = self.solution 454 piecesleft = finalused[len(self.usedpieces):] 455 #print("Pieces left:", piecesleft) 456 if n > len(piecesleft): n = len(piecesleft) 457 piecelist = random.sample(piecesleft, n) 458 #print("Pieces to place", piecelist) 459 for pieceno in piecelist: 460 piece = self.pieces[pieceno-1] 461 462 inversetransform = tinverse[piece.currenttransform] 463 requiredtransform = finaltlist[pieceno] 464 combinedtransform = tcombine[inversetransform][requiredtransform] 465 466 (cx, cy) = piece.getCentre() 467 (dx, dy) = (cx-piece.startcx, cy-piece.startcy) 468 (x0, y0) = piece.gettransformedstartcoords(requiredtransform)
469 square = finalgrid.index(pieceno) 470 (i, j) = divmod(square, self.gridheight) 471 (x, y) = (self.outlineleft+i*15, self.outlinetop+j*15) 472 473 piece.style.transition = "transform 1s" 474 piece.style.transformOrigin = f"{cx}px {cy}px" 475 piece.style.transform = f"translate({x-x0-dx}px,{y-y0-dy}px) " + piece.csstransforms[combinedtransform] 476 477 set_timeout(addpiece, 1000, pieceno, requiredtransform, square, (x, y)) 478 479 def removepieces(self): 480 def removepiece(pieceno): 481 piece = self.pieces[pieceno-1] 482 piece.reset() 483 484 piecesremoved = [] 485 success = False 486 while not success: 487 pieceno = self.usedpieces.pop() 488 piecesremoved.append(pieceno) 489 #print("Removed so far:", piecesremoved) 490 self.gridlist = [(0 if x==pieceno else x) for x in self.gridlist] 491 #print("After removal:") 492 #print("\n".join([str(self.gridlist[k:len(self.gridlist):self.gridheight]) for k in range(self.gridheight)])) 493 success, finalgrid, finalused, tlist = window.solvepuzzle(ommysize, self.gridwidth, self.gridheight, self.gridlist, self.allowedpieces, self.usedpieces) 494 removepiecedialog.removebutton.innerHTML = "Remove pieces" 495 removepiecedialog.hide() 496 497 for pieceno in piecesremoved: 498 piece = self.pieces[pieceno-1] 499 (cx, cy) = piece.getCentre() 500 tno = piece.currenttransform 501 (x0, y0) = piece.gettransformedstartcoords(tno) 502 (x, y) = min(piece.pointList) 503 504 piece.style.transition = "transform 1s" 505 piece.style.transformOrigin = f"{cx}px {cy}px" 506 piece.style.transform = f" translate({x0-x}px,{y0-y}px)" + piece.csstransforms[tinverse[tno]] 507 set_timeout(removepiece, 1000, pieceno) 508 509 if 0 in finalgrid: 510 ws.showalert("Sorry - some pieces have been removed but this new position is taking too long to check.\nYou may still need to remove more pieces") 511 else: 512 self.storesolution(finalgrid, finalused, tlist) 513 514class MenuCanvas(SVG.CanvasObject): 515 def __init__(self): 516 self.bgcolour = "cyan" 517 super().__init__(colour=self.bgcolour, objid="menucanvas") 518 self.style.display = "none" 519 self.pageno = 0 520 521 def displaypage(self, pagenumber): 522 boxsize = 250 523 colcount, rowcount = 5, 2 524 perpage = colcount*rowcount 525 textheight = boxsize*0.1 526 maxshapesize = boxsize - textheight*2.5 527 self.style.display = "block" 528 start, end = perpage*pagenumber, perpage*(pagenumber+1) 529 for i, puzzle in enumerate(puzzles[ommysize][start:end]): 530 xoffset, yoffset = i%colcount*boxsize, i//colcount*boxsize 531 shape = ComplexPolygon(puzzle["partscoords"], puzzle["holescoords"], objid="puzzle"+str(perpage*pagenumber+i)) 532 shape.attrs["cursor"] = "pointer" 533 shape.bind("click", self.choosepuzzle) 534 shape.bind("touchstart", self.choosepuzzle) 535 (xmin, ymin), (xmax, ymax) = shape.getBoundingBox() 536 width, height = xmax-xmin, ymax-ymin 537 maxdim = max(width, height) 538 539 self.addObject(shape, fixed=True) 540 if maxdim > maxshapesize: shape.enlarge(maxshapesize/maxdim, ((xmin+xmax)/2, ymax)) 541 shape.translate((xoffset-xmin+(boxsize-width)/2, yoffset+(boxsize-textheight*1.5-ymax))) 542 title = SVG.TextObject(puzzle["description"], (xoffset+boxsize/2, yoffset+boxsize), anchorposition=8, fontsize=textheight) 543 self <= title 544 heading = "Choose a puzzle by clicking on one of the shapes below." 545 self <= SVG.TextObject(heading, (boxsize*colcount/2, 0), anchorposition=8, fontsize=textheight*1.2) 546 547 nextpageicon = SVG.PolylineObject([(-2,-8), (2,0), (-2,8)], linewidth=2) 548 nextpagebutton = SVG.ImageButton((boxsize*colcount, boxsize*rowcount/2), (boxsize*0.1,boxsize*0.2), nextpageicon, self.shownextpage, fillcolour="pink", canvas=self) 549 self <= nextpagebutton 550 lastpage = (len(puzzles[ommysize])-1)//perpage 551 if self.pageno == lastpage: nextpagebutton.style.visibility = "hidden" 552 prevpageicon = SVG.PolylineObject([(2,-8), (-2,0), (2,8)], linewidth=2) 553 prevpagebutton = SVG.ImageButton((0, boxsize*rowcount/2), (boxsize*0.1,boxsize*0.2), prevpageicon, self.showprevpage, fillcolour="pink", canvas=self) 554 self <= prevpagebutton 555 if self.pageno == 0: prevpagebutton.style.visibility = "hidden" 556 self <= SVG.PointObject((boxsize*colcount, boxsize*rowcount), self.bgcolour, 1) 557 self.fitContents() 558 559 def shownextpage(self, event): 560 self.pageno += 1 561 self.deleteAll() 562 do_after_page_refresh(self.displaypage, self.pageno) 563 564 def showprevpage(self, event): 565 self.pageno -= 1 566 self.deleteAll() 567 do_after_page_refresh(self.displaypage, self.pageno) 568 569 def choosepuzzle(self, event): 570 self.style.display = "none" 571 puzzleno = int(event.currentTarget.id[6:]) 572 do_after_page_refresh(puzzlecanvas.showpuzzle, puzzleno) 573 574def do_after_page_refresh(function, *args): 575 def frame1(timestamp): #Refresh page before function is executed 576 def frame2(timestamp): #Refresh page again after function is executed 577 function(*args) 578 request_animation_frame(frame2) 579 request_animation_frame(frame1) 580 581def onwindowresize(event=None):
582 h = 0.96*window.innerHeight-document['header'].offsetHeight 583 menucanvas.style.height = f"{h}px" 584 puzzlecanvas.style.height = f"{h}px" 585 if puzzlecanvas.style.display == "block": puzzlecanvas.fitContents() 586 587ommysize = document.query.getfirst("size", None) 588if ommysize: ommysize = int(ommysize) 589if not ommysize or ommysize not in {4,5,6}: ommysize = 4 590heading = "Hexomino puzzles" if ommysize==6 else "Pentomino Puzzles" if ommysize==5 else "Tetromino Puzzles" 591document["heading"].text = heading 592 593menucanvas = MenuCanvas() 594puzzlecanvas = PuzzleCanvas() 595document["maindiv"] <= (menucanvas, puzzlecanvas) 596window.bind("resize", onwindowresize) 597onwindowresize() 598 599addpiecedialog = AddPieceDialog() 600removepiecedialog = RemovePieceDialog() 601pleasewaitdialog = ws.DialogBox(title="Checking for solution", content = html.P("Checking for solution - please wait...")) 602#print("Create canvasses and dialogs", time.time()-tt) 603 604puzzleno = document.query.getfirst("puzzle", None) 605if puzzleno: 606 puzzlecanvas.showpuzzle(int(puzzleno)) 607else: 608 menucanvas.displaypage(menucanvas.pageno) 609#print("Show puzzle", time.time()-tt) 610 611
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.