import math import sys from kivy.graphics import Color,Line,Rectangle from point import Point from tools import isint_nonzero,sgn class Cross(): # size of central square size=50 def __init__(self,x,y,**kwargs): self.pos=Point(x,y) self.color=kwargs.get("color",(0,0,1)) self.selected=False # set position def setpos(self,x,y): self.pos.x=x self.pos.y=y def draw(self,**kwargs): # set color if not self.selected: Color(*self.color) else: (r,g,b)=self.color # darken selected Color(r/2,g/2,b/2) Rectangle(pos=((self.pos.x-1.5)*self.size,(self.pos.y-0.5)*self.size),size=(3*self.size,self.size)) Rectangle(pos=((self.pos.x-0.5)*self.size,(self.pos.y-1.5)*self.size),size=(self.size,3*self.size)) # stroke Color(1,1,1) Line(points=( *((self.pos.x-0.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y-1.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y-1.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x+1.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x+1.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y+1.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y+1.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x-1.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x-1.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y-0.5)*self.size), )) # check whether a cross at pos interacts with cross def check_interaction(self,pos): # allow for error return int(pos.x-self.pos.x+sgn(pos.x-self.pos.x)*1e-11)**2+int(pos.y-self.pos.y+sgn(pos.y-self.pos.y)*1e-11)**2>=5 # check whether a cross at position pos is touching self def check_touch(self,pos): rel=pos-self.pos for i in [-3,-2,-1,1,2,3]: # allow for error if abs(rel.x-i)<1e-11 and abs(rel.y)<=4-abs(i)+1e-11 and abs(rel.y)>=3-abs(i)-1e-11: return True if abs(rel.y-i)<1e-11 and abs(rel.x)<=4-abs(i)+1e-11 and abs(rel.x)>=3-abs(i)-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): # relative to cross return self.move_on_line_to_stick_relative(pos-self.pos,v)+self.pos def move_on_line_to_stick_relative(self,x,v): # if x is in the right quadrant if abs(x.y)<=x.x: # find all stuck positions on lines stuck=[] # check intersections with vertical lines if v.x!=0: for i in range(1,4): # candidate y=Point(i,x.y+(i-x.x)*v.y/v.x) # check that it is in the right range if abs(y.y)<=4-i and abs(y.y)>=3-i: stuck.append(y) # check intersections with horizontal lines if v.y!=0: for i in [-3,-2,-1,1,2,3]: # candidate y=Point(x.x+(i-x.y)*v.x/v.y,i) # check that it is in the right range if y.x<=4-abs(i) and y.x>=3-abs(i): stuck.append(y) return x.closest(stuck) # reflect other quadrants to the right one # top quadrant elif abs(x.x)<=x.y: closest=self.move_on_line_to_stick_relative(Point(x.y,x.x),Point(v.y,v.x)) return Point(closest.y,closest.x) # bottom quadrant elif abs(x.x)<=-x.y: closest=self.move_on_line_to_stick_relative(Point(-x.y,x.x),Point(-v.y,v.x)) return Point(closest.y,-closest.x) # left quadrant else: closest=self.move_on_line_to_stick_relative(Point(-x.x,x.y),Point(-v.x,v.y)) return Point(-closest.x,closest.y) # move along edge of cross 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 # two types of corners: |x|_1=3 or |x|_1=4 if abs(rel.x)+abs(rel.y)<3.5: 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) else: 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 self.check_interaction(candidate)==False or (relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0): # in open corner if rel>math.ceil(rel)-1e-11: candidate.y=math.ceil(rel)+1+self.pos.y else: candidate.y=math.ceil(rel)+self.pos.y else: if self.check_interaction(candidate)==False or (rel>math.floor(rel)+1e-11 and newrelpos.x: if self.check_interaction(candidate)==False or (relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0): # in open corner if rel>math.ceil(rel)-1e-11: candidate.x=math.ceil(rel)+1+self.pos.x else: candidate.x=math.ceil(rel)+self.pos.x else: if self.check_interaction(candidate)==False or (rel>math.floor(rel)+1e-11 and newrel