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 ) :
# square elements that maje up the polyomino
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-11-24 22:51:57 +00:00
# draw function
def draw ( self ) :
2021-11-24 22:18:27 +00:00
# set color
if not self . selected :
Color ( * self . color )
else :
( r , g , b ) = self . color
# darken selected
Color ( r / 2 , g / 2 , b / 2 )
2021-11-24 22:51:57 +00:00
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 ) ,
2021-11-25 01:30:51 +00:00
* ( ( 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 )
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 :
if square1 . check_interaction ( square2 . pos + offset ) :
return True
return False
2021-11-24 22:51:57 +00:00
# square
class Square ( Polyomino ) :
def __init__ ( self , x , y , * * kwargs ) :
2021-11-25 01:30:51 +00:00
super ( Square , self ) . __init__ ( * * kwargs , squares = [ Square_element ( x , y ) ] )
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 ) , \
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 ) ,
) )
2021-11-24 22:51:57 +00:00
# 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
2021-11-24 22:18:27 +00:00
# check whether a square at pos interacts with square
def check_interaction ( self , pos ) :
2021-11-25 00:22:05 +00:00
return l_infinity ( pos - self . pos ) < 1
2021-11-24 22:18:27 +00:00
# check whether a square at position pos is touching self
def check_touch ( self , pos ) :
# allow for error
2021-11-25 01:30:51 +00:00
if in_interval ( l_infinity ( pos - self . pos ) , 1 - 1e-11 , 1 + 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 pos in direction v
def move_on_line_to_stick ( self , pos , v ) :
# 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 + 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 )
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
2021-11-25 01:30:51 +00:00
if abs ( intersections [ i ] . x - self . pos . x ) < = 1 + 1e-11 and abs ( intersections [ i ] . y - self . pos . y ) < = 1 + 1e-11 :
2021-11-24 22:18:27 +00:00
if ( intersections [ i ] - pos ) * * 2 < dist :
closest = intersections [ i ]
dist = ( intersections [ i ] - pos ) * * 2
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 - pos
2021-11-24 22:18:27 +00:00
# move along edge of square
2021-11-25 00:22:05 +00:00
def move_along ( self , delta , pos ) :
2021-11-24 22:18:27 +00:00
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
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
2021-11-25 00:22:05 +00:00
return self . move_stuck_x ( delta , pos )
elif sgn ( delta . x ) == - sgn ( rel . x ) :
2021-11-24 22:18:27 +00:00
# stuck in y direction
2021-11-25 00:22:05 +00:00
return self . move_stuck_y ( delta , pos )
2021-11-24 22:18:27 +00:00
# stuck in both directions
return pos
else :
# stuck in x direction
2021-11-25 00:22:05 +00:00
return self . move_stuck_x ( delta , pos )
2021-11-24 22:18:27 +00:00
elif isint_nonzero ( rel . y ) :
# stuck in y direction
2021-11-25 00:22:05 +00:00
return self . move_stuck_y ( delta , pos )
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
2021-11-25 00:22:05 +00:00
def move_stuck_x ( self , delta , pos ) :
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 = pos . y - self . pos . y
2021-11-25 00:22:05 +00:00
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 :
2021-11-24 22:18:27 +00:00
# stick to corner
2021-11-25 01:30:51 +00:00
candidate . y = math . ceil ( rel ) + self . pos . y - pos . y
2021-11-24 22:18:27 +00:00
else :
2021-11-25 00:22:05 +00:00
if rel > math . floor ( rel ) + 1e-11 and delta . y + rel < math . floor ( rel ) - 1e-11 and math . floor ( rel ) != 0 :
2021-11-24 22:18:27 +00:00
# stick to corner
2021-11-25 01:30:51 +00:00
candidate . y = math . floor ( rel ) + self . pos . y - pos . y
2021-11-24 22:18:27 +00:00
return candidate
# move when stuck in the y direction
2021-11-25 00:22:05 +00:00
def move_stuck_y ( self , delta , pos ) :
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 = pos . x - self . pos . x
2021-11-25 00:22:05 +00:00
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 :
2021-11-24 22:18:27 +00:00
# stick to corner
2021-11-25 01:30:51 +00:00
candidate . x = math . ceil ( rel ) + self . pos . x - pos . x
2021-11-24 22:18:27 +00:00
else :
2021-11-25 00:22:05 +00:00
if rel > math . floor ( rel ) + 1e-11 and delta . x + rel < math . floor ( rel ) - 1e-11 and math . floor ( rel ) != 0 :
2021-11-24 22:18:27 +00:00
# stick to corner
2021-11-25 01:30:51 +00:00
candidate . x = math . floor ( rel ) + self . pos . x - pos . x
2021-11-24 22:18:27 +00:00
return candidate