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
2022-09-21 21:37:05 +00:00
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 :
2022-09-24 00:49:58 +00:00
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
2022-09-21 21:37:05 +00:00
self . stroke ( painter )
2021-11-24 22:51:57 +00:00
# draw boundary (override for connected polyominos)
2022-09-21 21:37:05 +00:00
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 = (
2022-09-23 23:33:05 +00:00
* ( 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 :
2022-09-24 00:49:58 +00:00
# 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
2022-09-24 00:49:58 +00:00
# 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 ) :
2022-09-23 23:33:05 +00:00
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 = [ \
2022-09-23 23:33:05 +00:00
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
2022-09-21 21:37:05 +00:00
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 = (
2022-09-23 23:33:05 +00:00
* ( 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 ( ) :
2022-09-23 23:33:05 +00:00
def __init__ ( self , x , y , size , * * kwargs ) :
2021-11-24 22:51:57 +00:00
self . pos = Point ( x , y )
2022-09-23 23:33:05 +00:00
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
2022-09-24 00:49:58 +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
2022-09-24 00:49:58 +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
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
# 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 = [ \
2022-09-24 00:49:58 +00:00
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 = [ \
2022-09-24 00:49:58 +00:00
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 = [ \
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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 ]
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
return closest - element . pos
2021-11-24 22:18:27 +00:00
# move along edge of square
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
if isint_nonzero ( rel . x / ( ( self . size + element . size ) / 2 ) ) :
2021-11-24 22:18:27 +00:00
# check y direction
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
return self . move_stuck_y ( delta , element )
2021-11-24 22:18:27 +00:00
# stuck in both directions
2022-09-24 00:49:58 +00:00
return element . pos
2021-11-24 22:18:27 +00:00
else :
# stuck in x direction
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
rel = element . pos . y - self . pos . y
2021-11-25 00:22:05 +00:00
if delta . y > 0 :
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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 :
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
rel = element . pos . x - self . pos . x
2021-11-25 00:22:05 +00:00
if delta . x > 0 :
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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 :
2022-09-24 00:49:58 +00:00
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
2022-09-24 00:49:58 +00:00
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