Jam/src/polyomino.py

246 lines
11 KiB
Python
Raw Normal View History

2021-11-24 22:18:27 +00:00
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
2021-11-24 22:51:57 +00:00
# parent class of all polyominos
class Polyomino():
def __init__(self,**kwargs):
2022-09-23 16:58:48 +00:00
# square elements that make up the polyomino
2021-11-24 22:51:57 +00:00
self.squares=kwargs.get("squares",[])
2021-11-24 22:18:27 +00:00
self.color=kwargs.get("color",(0,0,1))
self.selected=False
2021-12-01 23:49:35 +00:00
# mesh of background grid (no grid for mesh size 0)
self.grid=kwargs.get("grid",0)
2021-12-01 20:37:07 +00:00
2021-11-24 22:51:57 +00:00
# draw function
def draw(self,painter,**kwargs):
2021-12-10 15:55:04 +00:00
alpha=kwargs.get("alpha",1)
2021-11-24 22:18:27 +00:00
# set color
if not self.selected:
2021-12-10 15:55:04 +00:00
Color(*self.color,alpha)
2021-11-24 22:18:27 +00:00
else:
(r,g,b)=self.color
# darken selected
2021-12-10 15:55:04 +00:00
Color(r/2,g/2,b/2,alpha)
2021-11-24 22:18:27 +00:00
2021-11-24 22:51:57 +00:00
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))
2021-11-24 22:51:57 +00:00
# draw boundary
self.stroke(painter)
2021-11-24 22:51:57 +00:00
# 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)
2021-11-24 22:51:57 +00:00
# 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)
2021-11-24 22:51:57 +00:00
))
2021-11-25 00:22:05 +00:00
2021-11-25 01:30:51 +00:00
# move by delta
def move(self,delta):
for square in self.squares:
square.pos+=delta
2021-11-25 00:22:05 +00:00
# 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
2021-11-25 00:22:05 +00:00
return True
# reset offset
square2.pos-=offset
2021-11-25 00:22:05 +00:00
return False
2021-11-24 22:51:57 +00:00
# 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))])
2021-11-24 22:51:57 +00:00
2021-11-25 03:46:47 +00:00
# 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)\
2021-11-25 03:46:47 +00:00
])
# 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)
2021-11-25 03:46:47 +00:00
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),
2021-11-25 03:46:47 +00:00
))
2021-11-24 22:51:57 +00:00
# square building block of polyominos
class Square_element():
def __init__(self,x,y,size,**kwargs):
2021-11-24 22:51:57 +00:00
self.pos=Point(x,y)
self.size=size
2021-11-24 22:51:57 +00:00
# set position
def setpos(self,x,y):
self.pos.x=x
self.pos.y=y
2021-11-24 22:18:27 +00:00
# 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
2021-11-24 22:18:27 +00:00
# check whether an element is touching self
def check_touch(self,element):
2021-11-24 22:18:27 +00:00
# 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):
2021-11-24 22:18:27 +00:00
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):
2021-11-24 22:18:27 +00:00
# compute intersections with four lines making up square
2021-11-25 01:30:51 +00:00
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)\
2021-11-25 01:30:51 +00:00
]
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))
2021-11-25 01:30:51 +00:00
]
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)\
2021-11-25 01:30:51 +00:00
]
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)
2021-11-24 22:18:27 +00:00
# compute closest one, on square
closest=None
dist=math.inf
2021-11-25 01:30:51 +00:00
for i in range(0,len(intersections)):
2021-11-24 22:18:27 +00:00
# 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)**2<dist:
2021-11-24 22:18:27 +00:00
closest=intersections[i]
dist=(intersections[i]-element.pos)**2
2021-11-24 22:18:27 +00:00
if closest==None:
2021-11-25 01:30:51 +00:00
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)
2021-11-24 22:18:27 +00:00
exit(-1)
2021-11-25 01:30:51 +00:00
# return difference to pos
return closest-element.pos
2021-11-24 22:18:27 +00:00
# move along edge of square
def move_along(self,delta,element):
rel=element.pos-self.pos
2021-11-24 22:18:27 +00:00
# check if the particle is stuck in the x direction
if isint_nonzero(rel.x/((self.size+element.size)/2)):
2021-11-24 22:18:27 +00:00
# check y direction
if isint_nonzero(rel.y/((self.size+element.size)/2)):
2021-11-24 22:18:27 +00:00
# in corner
2021-11-25 00:22:05 +00:00
if sgn(delta.y)==-sgn(rel.y):
2021-11-24 22:18:27 +00:00
# stuck in x direction
return self.move_stuck_x(delta,element)
2021-11-25 00:22:05 +00:00
elif sgn(delta.x)==-sgn(rel.x):
2021-11-24 22:18:27 +00:00
# stuck in y direction
return self.move_stuck_y(delta,element)
2021-11-24 22:18:27 +00:00
# stuck in both directions
return element.pos
2021-11-24 22:18:27 +00:00
else:
# stuck in x direction
return self.move_stuck_x(delta,element)
elif isint_nonzero(rel.y/((self.size+element.size)/2)):
2021-11-24 22:18:27 +00:00
# stuck in y direction
return self.move_stuck_y(delta,element)
2021-11-24 22:18:27 +00:00
# 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,element):
2021-11-24 22:18:27 +00:00
# only move in y direction
2021-11-25 01:30:51 +00:00
candidate=Point(0,delta.y)
2021-11-24 22:18:27 +00:00
# do not move past corners
rel=element.pos.y-self.pos.y
2021-11-25 00:22:05 +00:00
if delta.y>0:
if rel<math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and delta.y+rel>math.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:
2021-11-24 22:18:27 +00:00
# stick to corner
candidate.y=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y
2021-11-24 22:18:27 +00:00
else:
if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.y+rel<math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
2021-11-24 22:18:27 +00:00
# stick to corner
candidate.y=math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.y-element.pos.y
2021-11-24 22:18:27 +00:00
return candidate
# move when stuck in the y direction
def move_stuck_y(self,delta,element):
2021-11-24 22:18:27 +00:00
# onlx move in x direction
2021-11-25 01:30:51 +00:00
candidate=Point(delta.x,0)
2021-11-24 22:18:27 +00:00
# do not move past corners
rel=element.pos.x-self.pos.x
2021-11-25 00:22:05 +00:00
if delta.x>0:
if rel<math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and delta.x+rel>math.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:
2021-11-24 22:18:27 +00:00
# stick to corner
candidate.x=math.ceil(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x
2021-11-24 22:18:27 +00:00
else:
if rel>math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+1e-11 and delta.x+rel<math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)-1e-11 and math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)!=0:
2021-11-24 22:18:27 +00:00
# stick to corner
candidate.x=math.floor(rel/((self.size+element.size)/2))*((self.size+element.size)/2)+self.pos.x-element.pos.x
2021-11-24 22:18:27 +00:00
return candidate