231 lines
8.9 KiB
Python
231 lines
8.9 KiB
Python
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 maje up the polyomino
|
|
self.squares=kwargs.get("squares",[])
|
|
|
|
self.color=kwargs.get("color",(0,0,1))
|
|
self.selected=False
|
|
|
|
# whether to draw a background grid
|
|
self.grid=kwargs.get("grid",False)
|
|
|
|
# draw function
|
|
def draw(self):
|
|
# set color
|
|
if not self.selected:
|
|
Color(*self.color)
|
|
else:
|
|
(r,g,b)=self.color
|
|
# darken selected
|
|
Color(r/2,g/2,b/2)
|
|
|
|
for square in self.squares:
|
|
Rectangle(pos=((square.pos.x-0.5)*square.size,(square.pos.y-0.5)*square.size),size=(square.size,square.size))
|
|
|
|
# draw boundary
|
|
self.stroke()
|
|
|
|
# draw boundary (override for connected polyominos)
|
|
def stroke(self):
|
|
# white
|
|
Color(1,1,1)
|
|
for square in self.squares:
|
|
Line(points=(
|
|
*((square.pos.x-0.5)*square.size,(square.pos.y-0.5)*square.size),
|
|
*((square.pos.x-0.5)*square.size,(square.pos.y+0.5)*square.size),
|
|
*((square.pos.x+0.5)*square.size,(square.pos.y+0.5)*square.size),
|
|
*((square.pos.x+0.5)*square.size,(square.pos.y-0.5)*square.size),
|
|
*((square.pos.x-0.5)*square.size,(square.pos.y-0.5)*square.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:
|
|
if square1.check_interaction(square2.pos+offset):
|
|
return True
|
|
return False
|
|
|
|
# square
|
|
class Square(Polyomino):
|
|
def __init__(self,x,y,**kwargs):
|
|
super(Square,self).__init__(**kwargs,squares=[Square_element(x,y)])
|
|
|
|
# cross
|
|
class Cross(Polyomino):
|
|
def __init__(self,x,y,**kwargs):
|
|
super(Cross,self).__init__(**kwargs,squares=[\
|
|
Square_element(x,y),\
|
|
Square_element(x+1,y),\
|
|
Square_element(x-1,y),\
|
|
Square_element(x,y+1),\
|
|
Square_element(x,y-1)\
|
|
])
|
|
|
|
# redefine stroke to avoid lines between touching squares
|
|
def stroke(self):
|
|
Color(1,1,1)
|
|
Line(points=(
|
|
*((self.squares[0].pos.x-0.5)*Square_element.size,(self.squares[0].pos.y-0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x-0.5)*Square_element.size,(self.squares[0].pos.y-1.5)*Square_element.size),
|
|
*((self.squares[0].pos.x+0.5)*Square_element.size,(self.squares[0].pos.y-1.5)*Square_element.size),
|
|
*((self.squares[0].pos.x+0.5)*Square_element.size,(self.squares[0].pos.y-0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x+1.5)*Square_element.size,(self.squares[0].pos.y-0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x+1.5)*Square_element.size,(self.squares[0].pos.y+0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x+0.5)*Square_element.size,(self.squares[0].pos.y+0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x+0.5)*Square_element.size,(self.squares[0].pos.y+1.5)*Square_element.size),
|
|
*((self.squares[0].pos.x-0.5)*Square_element.size,(self.squares[0].pos.y+1.5)*Square_element.size),
|
|
*((self.squares[0].pos.x-0.5)*Square_element.size,(self.squares[0].pos.y+0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x-1.5)*Square_element.size,(self.squares[0].pos.y+0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x-1.5)*Square_element.size,(self.squares[0].pos.y-0.5)*Square_element.size),
|
|
*((self.squares[0].pos.x-0.5)*Square_element.size,(self.squares[0].pos.y-0.5)*Square_element.size),
|
|
))
|
|
|
|
|
|
|
|
# square building block of polyominos
|
|
class Square_element():
|
|
# size
|
|
size=50
|
|
|
|
def __init__(self,x,y,**kwargs):
|
|
self.pos=Point(x,y)
|
|
|
|
# set position
|
|
def setpos(self,x,y):
|
|
self.pos.x=x
|
|
self.pos.y=y
|
|
|
|
|
|
# check whether a square at pos interacts with square
|
|
def check_interaction(self,pos):
|
|
return l_infinity(pos-self.pos)<1
|
|
|
|
# check whether a square at position pos is touching self
|
|
def check_touch(self,pos):
|
|
# allow for error
|
|
if in_interval(l_infinity(pos-self.pos),1-1e-11,1+1e-11):
|
|
return True
|
|
return False
|
|
|
|
# find position along a line that comes in contact with the line going through pos in direction v
|
|
def move_on_line_to_stick(self,pos,v):
|
|
# compute intersections with four lines making up square
|
|
if v.x!=0:
|
|
if v.y!=0:
|
|
intersections=[\
|
|
Point(self.pos.x+1,pos.y+v.y/v.x*(self.pos.x+1-pos.x)),\
|
|
Point(self.pos.x-1,pos.y+v.y/v.x*(self.pos.x-1-pos.x)),\
|
|
Point(pos.x+v.x/v.y*(self.pos.y+1-pos.y),self.pos.y+1),\
|
|
Point(pos.x+v.x/v.y*(self.pos.y-1-pos.y),self.pos.y-1)\
|
|
]
|
|
else:
|
|
intersections=[\
|
|
Point(self.pos.x+1,pos.y+v.y/v.x*(self.pos.x+1-pos.x)),\
|
|
Point(self.pos.x-1,pos.y+v.y/v.x*(self.pos.x-1-pos.x))
|
|
]
|
|
else:
|
|
if v.y!=0:
|
|
intersections=[\
|
|
Point(pos.x+v.x/v.y*(self.pos.y+1-pos.y),self.pos.y+1),\
|
|
Point(pos.x+v.x/v.y*(self.pos.y-1-pos.y),self.pos.y-1)\
|
|
]
|
|
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)<=1+1e-11 and abs(intersections[i].y-self.pos.y)<=1+1e-11:
|
|
if (intersections[i]-pos)**2<dist:
|
|
closest=intersections[i]
|
|
dist=(intersections[i]-pos)**2
|
|
|
|
if closest==None:
|
|
print("error: cannot move particle at (",pos.x,",",pos.y,") to the boundary of (",self.pos.x,",",self.pos.y,") in direction (",v.x,",",v.y,")",file=sys.stderr)
|
|
exit(-1)
|
|
|
|
# return difference to pos
|
|
return closest-pos
|
|
|
|
# move along edge of square
|
|
def move_along(self,delta,pos):
|
|
rel=pos-self.pos
|
|
# check if the particle is stuck in the x direction
|
|
if isint_nonzero(rel.x):
|
|
# check y direction
|
|
if isint_nonzero(rel.y):
|
|
# in corner
|
|
if sgn(delta.y)==-sgn(rel.y):
|
|
# stuck in x direction
|
|
return self.move_stuck_x(delta,pos)
|
|
elif sgn(delta.x)==-sgn(rel.x):
|
|
# stuck in y direction
|
|
return self.move_stuck_y(delta,pos)
|
|
# stuck in both directions
|
|
return pos
|
|
else:
|
|
# stuck in x direction
|
|
return self.move_stuck_x(delta,pos)
|
|
elif isint_nonzero(rel.y):
|
|
# stuck in y direction
|
|
return self.move_stuck_y(delta,pos)
|
|
# this should never happen
|
|
else:
|
|
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
|
|
exit(-1)
|
|
# move when stuck in the x direction
|
|
def move_stuck_x(self,delta,pos):
|
|
# only move in y direction
|
|
candidate=Point(0,delta.y)
|
|
# do not move past corners
|
|
rel=pos.y-self.pos.y
|
|
if delta.y>0:
|
|
if rel<math.ceil(rel)-1e-11 and delta.y+rel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
|
# stick to corner
|
|
candidate.y=math.ceil(rel)+self.pos.y-pos.y
|
|
else:
|
|
if rel>math.floor(rel)+1e-11 and delta.y+rel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
|
# stick to corner
|
|
candidate.y=math.floor(rel)+self.pos.y-pos.y
|
|
return candidate
|
|
# move when stuck in the y direction
|
|
def move_stuck_y(self,delta,pos):
|
|
# onlx move in x direction
|
|
candidate=Point(delta.x,0)
|
|
# do not move past corners
|
|
rel=pos.x-self.pos.x
|
|
if delta.x>0:
|
|
if rel<math.ceil(rel)-1e-11 and delta.x+rel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
|
# stick to corner
|
|
candidate.x=math.ceil(rel)+self.pos.x-pos.x
|
|
else:
|
|
if rel>math.floor(rel)+1e-11 and delta.x+rel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
|
# stick to corner
|
|
candidate.x=math.floor(rel)+self.pos.x-pos.x
|
|
return candidate
|
|
|