Jam/polyomino.py
2021-11-24 17:51:57 -05:00

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