import math import sys from kivy.graphics import Color,Line,Rectangle from point import Point,l_infinity from tools import isint_nonzero,sgn,in_interval # parent class of all polyominos class Polyomino(): def __init__(self,**kwargs): # square elements that make up the polyomino self.squares=kwargs.get("squares",[]) self.color=kwargs.get("color",(0,0,1)) self.selected=False # mesh of background grid (no grid for mesh size 0) self.grid=kwargs.get("grid",0) # draw function def draw(self,painter,**kwargs): alpha=kwargs.get("alpha",1) # set color if not self.selected: Color(*self.color,alpha) else: (r,g,b)=self.color # darken selected Color(r/2,g/2,b/2,alpha) for square in self.squares: Rectangle(pos=(painter.pos_tocoord_x(square.pos.x-0.5*square.size),painter.pos_tocoord_y(square.pos.y-0.5*square.size)),size=(square.size*painter.base_size,square.size*painter.base_size)) # draw boundary self.stroke(painter) # draw boundary (override for connected polyominos) def stroke(self,painter): # convert to graphical coordinates coordx=painter.pos_tocoord_x(square.pos.x) coordy=painter.pos_tocoord_y(square.pos.y) # white Color(1,1,1) for square in self.squares: Line(points=( *(coordx-0.5*square.size*painter.base_size,coordy-0.5*square.size*painter.base_size), *(coordx-0.5*square.size*painter.base_size,coordy+0.5*square.size*painter.base_size), *(coordx+0.5*square.size*painter.base_size,coordy+0.5*square.size*painter.base_size), *(coordx+0.5*square.size*painter.base_size,coordy-0.5*square.size*painter.base_size), *(coordx-0.5*square.size*painter.base_size,coordy-0.5*square.size*painter.base_size) )) # move by delta def move(self,delta): for square in self.squares: square.pos+=delta # whether x is in the support of the polyomino def in_support(self,x): for square in self.squares: if l_infinity(square.pos-x)<=1/2: return True return False # check whether self interacts with candidate if candidate were moved by offset def check_interaction(self,candidate,offset): for square1 in self.squares: for square2 in candidate.squares: # add offset square2.pos+=offset if square1.check_interaction(square2): # reset offset square2.pos-=offset return True # reset offset square2.pos-=offset return False # square class Square(Polyomino): def __init__(self,x,y,**kwargs): super(Square,self).__init__(**kwargs,squares=[Square_element(x,y,size=kwargs.get("size",1.0))]) # cross class Cross(Polyomino): def __init__(self,x,y,**kwargs): super(Cross,self).__init__(**kwargs,squares=[\ Square_element(x,y,1),\ Square_element(x+1,y,1),\ Square_element(x-1,y,1),\ Square_element(x,y+1,1),\ Square_element(x,y-1,1)\ ]) # redefine stroke to avoid lines between touching squares def stroke(self,painter): # convert to graphical coordinates coordx=painter.pos_tocoord_x(self.squares[0].pos.x) coordy=painter.pos_tocoord_y(self.squares[0].pos.y) Color(1,1,1) Line(points=( *(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size), *(coordx-0.5*painter.base_size,coordy-1.5*painter.base_size), *(coordx+0.5*painter.base_size,coordy-1.5*painter.base_size), *(coordx+0.5*painter.base_size,coordy-0.5*painter.base_size), *(coordx+1.5*painter.base_size,coordy-0.5*painter.base_size), *(coordx+1.5*painter.base_size,coordy+0.5*painter.base_size), *(coordx+0.5*painter.base_size,coordy+0.5*painter.base_size), *(coordx+0.5*painter.base_size,coordy+1.5*painter.base_size), *(coordx-0.5*painter.base_size,coordy+1.5*painter.base_size), *(coordx-0.5*painter.base_size,coordy+0.5*painter.base_size), *(coordx-1.5*painter.base_size,coordy+0.5*painter.base_size), *(coordx-1.5*painter.base_size,coordy-0.5*painter.base_size), *(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size), )) # square building block of polyominos class Square_element(): def __init__(self,x,y,size,**kwargs): self.pos=Point(x,y) self.size=size # set position def setpos(self,x,y): self.pos.x=x self.pos.y=y # check whether an element interacts with square def check_interaction(self,element): # allow for error return l_infinity(element.pos-self.pos)<(self.size+element.size)/2-1e-11 # check whether an element is touching self def check_touch(self,element): # allow for error if in_interval(l_infinity(element.pos-self.pos),(self.size+element.size)/2-1e-11,(self.size+element.size)/2+1e-11): return True return False # find position along a line that comes in contact with the line going through element.pos in direction v def move_on_line_to_stick(self,element,v): # compute intersections with four lines making up square if v.x!=0: if v.y!=0: intersections=[\ Point(self.pos.x+(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x+(self.size+element.size)/2-element.pos.x)),\ Point(self.pos.x-(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x-(self.size+element.size)/2-element.pos.x)),\ Point(element.pos.x+v.x/v.y*(self.pos.y+(self.size+element.size)/2-element.pos.y),self.pos.y+(self.size+element.size)/2),\ Point(element.pos.x+v.x/v.y*(self.pos.y-(self.size+element.size)/2-element.pos.y),self.pos.y-(self.size+element.size)/2)\ ] else: intersections=[\ Point(self.pos.x+(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x+(self.size+element.size)/2-element.pos.x)),\ Point(self.pos.x-(self.size+element.size)/2,element.pos.y+v.y/v.x*(self.pos.x-(self.size+element.size)/2-element.pos.x)) ] else: if v.y!=0: intersections=[\ Point(element.pos.x+v.x/v.y*(self.pos.y+(self.size+element.size)/2-element.pos.y),self.pos.y+(self.size+element.size)/2),\ Point(element.pos.x+v.x/v.y*(self.pos.y-(self.size+element.size)/2-element.pos.y),self.pos.y-(self.size+element.size)/2)\ ] else: print("error: move_on_line_to_stick called with v=0, please file a bug report with the developer",file=sys.stderr) exit(-1) # compute closest one, on square closest=None dist=math.inf for i in range(0,len(intersections)): # check that it is on square if abs(intersections[i].x-self.pos.x)<=(self.size+element.size)/2+1e-11 and abs(intersections[i].y-self.pos.y)<=(self.size+element.size)/2+1e-11: if (intersections[i]-element.pos)**20: if relmath.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0: # stick to corner candidate.y=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y else: if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.y+rel0: if relmath.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0: # stick to corner candidate.x=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x else: if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.x+rel