163 lines
5.6 KiB
Python
163 lines
5.6 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
|
|
|
|
# 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
|
|
class Square(Polyomino):
|
|
def __init__(self,x,y,**kwargs):
|
|
super(Square,self).__init__(squares=[Square_element(x,y)],kwargs)
|
|
|
|
|
|
|
|
# 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-solf.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
|
|
intersections=[\
|
|
Point(self.pos.x+1/2,pos.y+v.y/v.x*(self.pos.x+1/2-pos.x)),\
|
|
Point(self.pos.x-1/2,pos.y+v.y/v.x*(self.pos.x-1/2-pos.x)),\
|
|
Point(pos.x+v.x/v.y*(self.pos.y+1/2-pos.y),self.pos.y+1/2),\
|
|
Point(pos.x+v.x/v.y*(self.pos.y-1/2-pos.y),self.pos.y-1/2)\
|
|
]
|
|
|
|
# compute closest one, on square
|
|
closest=None
|
|
dist=math.inf
|
|
for i in range(0,4):
|
|
# check that it is on square
|
|
if abs(intersections[i].x-self.pos.x)<=1/2+1e-11 and abs(intersections[i].y-self.pos.y)<=1/2+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,")",file=sys.stderr)
|
|
exit(-1)
|
|
|
|
return closest
|
|
|
|
# move along edge of square
|
|
def move_along(self,newpos,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(newpos.y-pos.y)==-sgn(rel.y):
|
|
# stuck in x direction
|
|
return self.move_stuck_x(newpos,pos)
|
|
elif sgn(newpos.x-pos.x)==-sgn(rel.x):
|
|
# stuck in y direction
|
|
return self.move_stuck_y(newpos,pos)
|
|
# stuck in both directions
|
|
return pos
|
|
else:
|
|
# stuck in x direction
|
|
return self.move_stuck_x(newpos,pos)
|
|
elif isint_nonzero(rel.y):
|
|
# stuck in y direction
|
|
return self.move_stuck_y(newpos,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,newpos,pos):
|
|
# only move in y direction
|
|
candidate=Point(pos.x,newpos.y)
|
|
# do not move past corners
|
|
rel=pos.y-self.pos.y
|
|
newrel=newpos.y-self.pos.y
|
|
if newpos.y>pos.y:
|
|
if rel<math.ceil(rel)-1e-11 and newrel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
|
# stick to corner
|
|
candidate.y=math.ceil(rel)+self.pos.y
|
|
else:
|
|
if rel>math.floor(rel)+1e-11 and newrel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
|
# stick to corner
|
|
candidate.y=math.floor(rel)+self.pos.y
|
|
return candidate
|
|
# move when stuck in the y direction
|
|
def move_stuck_y(self,newpos,pos):
|
|
# onlx move in x direction
|
|
candidate=Point(pos.x,newpos.x)
|
|
# do not move past corners
|
|
rel=pos.x-self.pos.x
|
|
newrel=newpos.x-self.pos.x
|
|
if newpos.x>pos.x:
|
|
if rel<math.ceil(rel)-1e-11 and newrel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
|
# stick to corner
|
|
candidate.x=math.ceil(rel)+self.pos.x
|
|
else:
|
|
if rel>math.floor(rel)+1e-11 and newrel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
|
# stick to corner
|
|
candidate.x=math.floor(rel)+self.pos.x
|
|
return candidate
|
|
|