Painter: draw only squares
This commit is contained in:
parent
221aa0a713
commit
a7c18641a6
5
jam
5
jam
@ -7,8 +7,7 @@ from kivy.config import Config
|
|||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
from cross import Cross
|
from painter import Painter
|
||||||
from painter import Cross_painter
|
|
||||||
from status_bar import Status_bar
|
from status_bar import Status_bar
|
||||||
from command_prompt import Command_prompt
|
from command_prompt import Command_prompt
|
||||||
import filecheck
|
import filecheck
|
||||||
@ -32,7 +31,7 @@ class Jam_app(App):
|
|||||||
layout=BoxLayout(orientation="vertical")
|
layout=BoxLayout(orientation="vertical")
|
||||||
|
|
||||||
# painter
|
# painter
|
||||||
self.painter=Cross_painter(self)
|
self.painter=Painter(self)
|
||||||
# status bar
|
# status bar
|
||||||
self.status_bar=Status_bar(self)
|
self.status_bar=Status_bar(self)
|
||||||
# command prompt
|
# command prompt
|
||||||
|
167
painter.py
167
painter.py
@ -3,21 +3,25 @@ from kivy.uix.widget import Widget
|
|||||||
from kivy.core.window import Window
|
from kivy.core.window import Window
|
||||||
|
|
||||||
from point import Point
|
from point import Point
|
||||||
|
from polyomino import Square
|
||||||
|
|
||||||
# cross painter
|
# painter class
|
||||||
class Cross_painter(Widget):
|
class Painter(Widget):
|
||||||
|
|
||||||
def __init__(self,app,**kwargs):
|
def __init__(self,app,**kwargs):
|
||||||
|
|
||||||
# list of crosses
|
# list of particles
|
||||||
self.crosses=[]
|
self.particles=[]
|
||||||
|
|
||||||
# cross under mouse
|
# particle under mouse
|
||||||
self.undermouse=None
|
self.undermouse=None
|
||||||
|
|
||||||
# list of selected crosses
|
# list of selected particles
|
||||||
self.selected=[]
|
self.selected=[]
|
||||||
|
|
||||||
|
# relative position of mouse when moving
|
||||||
|
self.offset=Point(0,0)
|
||||||
|
|
||||||
# app is used to share information between widgets
|
# app is used to share information between widgets
|
||||||
self.app=app
|
self.app=app
|
||||||
|
|
||||||
@ -25,7 +29,7 @@ class Cross_painter(Widget):
|
|||||||
self.modifiers=[]
|
self.modifiers=[]
|
||||||
|
|
||||||
# init Widget
|
# init Widget
|
||||||
super(Cross_painter,self).__init__(**kwargs)
|
super(Painter,self).__init__(**kwargs)
|
||||||
|
|
||||||
# init keyboard
|
# init keyboard
|
||||||
self.keyboard = Window.request_keyboard(None,self,"text")
|
self.keyboard = Window.request_keyboard(None,self,"text")
|
||||||
@ -33,17 +37,17 @@ class Cross_painter(Widget):
|
|||||||
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.crosses=[]
|
self.particles=[]
|
||||||
self.undermouse=None
|
self.undermouse=None
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
|
||||||
# draw all crosses
|
# draw all particles
|
||||||
def draw(self):
|
def draw(self):
|
||||||
self.canvas.clear()
|
self.canvas.clear()
|
||||||
with self.canvas:
|
with self.canvas:
|
||||||
for cross in self.crosses:
|
for particle in self.particles:
|
||||||
cross.draw()
|
particle.draw()
|
||||||
|
|
||||||
|
|
||||||
# respond to keyboard
|
# respond to keyboard
|
||||||
@ -66,32 +70,36 @@ class Cross_painter(Widget):
|
|||||||
|
|
||||||
# respond to mouse down
|
# respond to mouse down
|
||||||
def on_touch_down(self,touch):
|
def on_touch_down(self,touch):
|
||||||
# only respond to touch in area
|
# only respond to touch in drawing area
|
||||||
if self.collide_point(*touch.pos):
|
if self.collide_point(*touch.pos):
|
||||||
|
|
||||||
# create new cross
|
# create new cross
|
||||||
if touch.button=="right":
|
if touch.button=="right":
|
||||||
if self.check_interaction_any(Point(touch.x/Cross.size,touch.y/Cross.size),None):
|
new=Square(touch.x/Square_element.size,touch.y/Square_element.size)
|
||||||
new=Cross(touch.x/Cross.size,touch.y/Cross.size)
|
if not self.check_interaction_any(new,Point(0,0)):
|
||||||
# add to list
|
# add to list
|
||||||
self.crosses.append(new)
|
self.particles.append(new)
|
||||||
|
|
||||||
# unselect all crosses
|
# unselect all particles
|
||||||
for sel in self.selected:
|
for sel in self.selected:
|
||||||
sel.selected=False
|
sel.selected=False
|
||||||
self.selected=[]
|
self.selected=[]
|
||||||
|
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
# select cross
|
# select particle
|
||||||
if touch.button=="left":
|
if touch.button=="left":
|
||||||
# find cross under touch
|
# find particle under touch
|
||||||
self.undermouse=self.find_cross(Point(touch.x/Cross.size,touch.y/Cross.size))
|
self.undermouse=self.find_particle(Point(touch.x/Square_element.size,touch.y/Square_element.size))
|
||||||
|
|
||||||
|
# record relative position of click with respect to reference
|
||||||
|
if self.undermouse!=None:
|
||||||
|
self.offset=Point(touch.x/Square_element.size,touch.y/Square_element.size)-self.undermouse.squares[0].pos
|
||||||
|
|
||||||
# no modifiers
|
# no modifiers
|
||||||
if self.modifiers==[]:
|
if self.modifiers==[]:
|
||||||
if self.undermouse==None or not self.undermouse in self.selected:
|
if self.undermouse==None or not self.undermouse in self.selected:
|
||||||
# unselect all crosses
|
# unselect all particles
|
||||||
for sel in self.selected:
|
for sel in self.selected:
|
||||||
sel.selected=False
|
sel.selected=False
|
||||||
self.selected=[]
|
self.selected=[]
|
||||||
@ -114,91 +122,92 @@ class Cross_painter(Widget):
|
|||||||
|
|
||||||
# respond to drag
|
# respond to drag
|
||||||
def on_touch_move(self,touch):
|
def on_touch_move(self,touch):
|
||||||
|
# only respond to touch in drawing area
|
||||||
if self.collide_point(*touch.pos):
|
if self.collide_point(*touch.pos):
|
||||||
# only move on left click
|
# only move on left click
|
||||||
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
|
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
|
||||||
# single cross
|
# change in position
|
||||||
if not self.undermouse in self.selected:
|
delta=self.check_move(Point(touch.x/Square_element.size,touch.y/Square_element.size)-(self.offset+self.undermouse.squares[0].pos),self.undermouse)
|
||||||
self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse)
|
# multiple particles
|
||||||
# multiple crosses
|
# TODO: group moves
|
||||||
else:
|
#else:
|
||||||
self.group_move(touch)
|
# self.group_move(touch)
|
||||||
|
|
||||||
# redraw
|
# redraw
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
# move all selected crosses
|
## move all selected particles
|
||||||
def group_move(self,touch):
|
#def group_move(self,touch):
|
||||||
# save position of undermouse (to use it after it has been reset)
|
# # save position of undermouse (to use it after it has been reset)
|
||||||
relative_position=self.undermouse.pos
|
# relative_position=self.undermouse.pos
|
||||||
|
|
||||||
# determine order in which to move
|
# # determine order in which to move
|
||||||
# direction of motion
|
# # direction of motion
|
||||||
direction=Point(touch.x/Cross.size,touch.y/Cross.size)-self.undermouse.pos
|
# direction=Point(touch.x/Square_element.size,touch.y/Square_element.size)-self.undermouse.pos
|
||||||
# sort according to scalar product with direction
|
# # sort according to scalar product with direction
|
||||||
self.selected.sort(key=(lambda cross: direction.dot(cross.pos-self.undermouse.pos)),reverse=True)
|
# self.selected.sort(key=(lambda particle: direction.dot(particle.pos-self.undermouse.pos)),reverse=True)
|
||||||
|
|
||||||
# move
|
# # move
|
||||||
for cross in self.selected:
|
# for particle in self.selected:
|
||||||
cross.pos=self.check_move(Point(touch.x/Cross.size-relative_position.x+cross.pos.x,touch.y/Cross.size-relative_position.y+cross.pos.y),cross)
|
# particle.pos=self.check_move(Point(touch.x/Square_element.size-relative_position.x+particle.pos.x,touch.y/Square_element.size-relative_position.y+particle.pos.y),particle)
|
||||||
|
#
|
||||||
## new position of undermouse
|
# ## new position of undermouse
|
||||||
#self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse)
|
# #self.undermouse.pos=self.check_move(Point(touch.x/Square_element.size,touch.y/Square_element.size),self.undermouse)
|
||||||
## move other crosses by the same amount as undermouse
|
# ## move other particles by the same amount as undermouse
|
||||||
#for cross in self.selected:
|
# #for particle in self.selected:
|
||||||
# if cross!=self.undermouse:
|
# # if particle!=self.undermouse:
|
||||||
# cross.pos=self.check_move(Point(self.undermouse.pos.x-relative_position.x+cross.pos.x,self.undermouse.pos.y-relative_position.y+cross.pos.y),cross)
|
# # particle.pos=self.check_move(Point(self.undermouse.pos.x-relative_position.x+particle.pos.x,self.undermouse.pos.y-relative_position.y+particle.pos.y),particle)
|
||||||
#
|
# #
|
||||||
#for cross in self.selected:
|
# #for particle in self.selected:
|
||||||
# cross.pos=self.check_move(Point(touch.x/Cross.size-relative_position.x+cross.pos.x,touch.y/Cross.size-relative_position.y+cross.pos.y),cross)
|
# # particle.pos=self.check_move(Point(touch.x/Square_element.size-relative_position.x+particle.pos.x,touch.y/Square_element.size-relative_position.y+particle.pos.y),particle)
|
||||||
|
|
||||||
|
|
||||||
# find the cross at position pos
|
# find the particle at position pos
|
||||||
def find_cross(self,pos):
|
def find_particle(self,pos):
|
||||||
for cross in self.crosses:
|
for particle in self.particles:
|
||||||
if cross_distx(pos,cross.pos)<=0.5 or cross_disty(pos,cross.pos)<=0.5:
|
if particle.in_support(pos):
|
||||||
return cross
|
return particle
|
||||||
# none found
|
# none found
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# check whether a position intersects with any of the crosses
|
# check whether a position intersects with any of the particles
|
||||||
def check_interaction_any(self,pos,exception):
|
def check_interaction_any(self,candidate,offset):
|
||||||
for other in self.crosses:
|
for particle in self.particles:
|
||||||
if other!=exception:
|
if particle.check_interaction(candidate,offset):
|
||||||
if other.check_interaction(pos)==False:
|
return True
|
||||||
return False
|
return False
|
||||||
return True
|
|
||||||
|
|
||||||
# check that a cross can move to new position
|
# check that a particle can move by delta, and return the closest allowed relative motion
|
||||||
def check_move(self,newpos,cross):
|
def check_move(self,delta,particle):
|
||||||
# whether newpos is acceptable
|
# whether newpos is acceptable
|
||||||
accept_newpos=True
|
accept_newpos=True
|
||||||
for other in self.crosses:
|
for other in self.particles:
|
||||||
# do not compare a cross to itself
|
# do not compare a particle to itself
|
||||||
if other!=cross:
|
if other!=particle:
|
||||||
# move would make cross overlap with other
|
# move would make particle overlap with other
|
||||||
if other.check_interaction(newpos)==False:
|
if other.check_interaction(particle,delta):
|
||||||
accept_newpos=False
|
accept_newpos=False
|
||||||
# check if cross touches other
|
# check if particle already touches other
|
||||||
if other.check_touch(cross.pos):
|
if other.check_touch(particle):
|
||||||
# move along other while remaining stuck
|
# move along other while remaining stuck
|
||||||
candidate=other.move_along(newpos,cross.pos)
|
# TODO: this assumes other is a square
|
||||||
|
newdelta=other.squares[0].move_along(delta,particle.squares[0].pos)
|
||||||
else:
|
else:
|
||||||
candidate=other.move_on_line_to_stick(cross.pos,newpos-cross.pos)
|
newdelta=other.move_on_line_to_stick(particle.squares[0].pos,delta)
|
||||||
if self.check_interaction_any(candidate,cross):
|
if not self.check_interaction_any(particle,newdelta):
|
||||||
return candidate
|
return newdelta
|
||||||
if accept_newpos:
|
if accept_newpos:
|
||||||
return newpos
|
return delta
|
||||||
else:
|
else:
|
||||||
# cannot move cross at all, try again
|
# cannot move particle at all, try again
|
||||||
return self.check_move(candidate,cross)
|
return self.check_move(newdelta,particle)
|
||||||
|
|
||||||
|
|
||||||
# write configuration to file
|
# write configuration to file
|
||||||
def write(self,file):
|
def write(self,file):
|
||||||
ff=open(file,"w")
|
ff=open(file,"w")
|
||||||
for cross in self.crosses:
|
for particle in self.particles:
|
||||||
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(cross.pos.x,cross.pos.y,cross.color[0],cross.color[1],cross.color[2]))
|
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.pos.x,particle.pos.y,particle.color[0],particle.color[1],particle.color[2]))
|
||||||
ff.close()
|
ff.close()
|
||||||
|
|
||||||
# read configuration from file
|
# read configuration from file
|
||||||
@ -267,7 +276,7 @@ class Cross_painter(Widget):
|
|||||||
|
|
||||||
if self.check_interaction_any(pos,None):
|
if self.check_interaction_any(pos,None):
|
||||||
# add to list
|
# add to list
|
||||||
self.crosses.append(Cross(pos.x,pos.y,color=color))
|
self.particles.append(Cross(pos.x,pos.y,color=color))
|
||||||
else:
|
else:
|
||||||
print("warning: ignoring line "+str(i)+" in file '"+file+"': particle overlaps with existing particles",file=sys.stderr)
|
print("warning: ignoring line "+str(i)+" in file '"+file+"': particle overlaps with existing particles",file=sys.stderr)
|
||||||
|
|
||||||
|
61
polyomino.py
61
polyomino.py
@ -41,12 +41,35 @@ class Polyomino():
|
|||||||
*((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)
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# check whether self touches candidate
|
||||||
|
def check_touch(self,candidate):
|
||||||
|
for square1 in self.squares:
|
||||||
|
for square2 in candidate.squares:
|
||||||
|
if square1.check_touch(square2.pos):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# square
|
# square
|
||||||
class Square(Polyomino):
|
class Square(Polyomino):
|
||||||
def __init__(self,x,y,**kwargs):
|
def __init__(self,x,y,**kwargs):
|
||||||
super(Square,self).__init__(squares=[Square_element(x,y)],kwargs)
|
super(Square,self).__init__(kwargs,squares=[Square_element(x,y)])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -66,7 +89,7 @@ class Square_element():
|
|||||||
|
|
||||||
# check whether a square at pos interacts with square
|
# check whether a square at pos interacts with square
|
||||||
def check_interaction(self,pos):
|
def check_interaction(self,pos):
|
||||||
return l_infinity(pos-self.pos)>=1
|
return l_infinity(pos-self.pos)<1
|
||||||
|
|
||||||
# check whether a square at position pos is touching self
|
# check whether a square at position pos is touching self
|
||||||
def check_touch(self,pos):
|
def check_touch(self,pos):
|
||||||
@ -102,60 +125,58 @@ class Square_element():
|
|||||||
return closest
|
return closest
|
||||||
|
|
||||||
# move along edge of square
|
# move along edge of square
|
||||||
def move_along(self,newpos,pos):
|
def move_along(self,delta,pos):
|
||||||
rel=pos-self.pos
|
rel=pos-self.pos
|
||||||
# check if the particle is stuck in the x direction
|
# check if the particle is stuck in the x direction
|
||||||
if isint_nonzero(rel.x):
|
if isint_nonzero(rel.x):
|
||||||
# check y direction
|
# check y direction
|
||||||
if isint_nonzero(rel.y):
|
if isint_nonzero(rel.y):
|
||||||
# in corner
|
# in corner
|
||||||
if sgn(newpos.y-pos.y)==-sgn(rel.y):
|
if sgn(delta.y)==-sgn(rel.y):
|
||||||
# stuck in x direction
|
# stuck in x direction
|
||||||
return self.move_stuck_x(newpos,pos)
|
return self.move_stuck_x(delta,pos)
|
||||||
elif sgn(newpos.x-pos.x)==-sgn(rel.x):
|
elif sgn(delta.x)==-sgn(rel.x):
|
||||||
# stuck in y direction
|
# stuck in y direction
|
||||||
return self.move_stuck_y(newpos,pos)
|
return self.move_stuck_y(delta,pos)
|
||||||
# stuck in both directions
|
# stuck in both directions
|
||||||
return pos
|
return pos
|
||||||
else:
|
else:
|
||||||
# stuck in x direction
|
# stuck in x direction
|
||||||
return self.move_stuck_x(newpos,pos)
|
return self.move_stuck_x(delta,pos)
|
||||||
elif isint_nonzero(rel.y):
|
elif isint_nonzero(rel.y):
|
||||||
# stuck in y direction
|
# stuck in y direction
|
||||||
return self.move_stuck_y(newpos,pos)
|
return self.move_stuck_y(delta,pos)
|
||||||
# this should never happen
|
# this should never happen
|
||||||
else:
|
else:
|
||||||
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
|
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
|
||||||
exit(-1)
|
exit(-1)
|
||||||
# move when stuck in the x direction
|
# move when stuck in the x direction
|
||||||
def move_stuck_x(self,newpos,pos):
|
def move_stuck_x(self,delta,pos):
|
||||||
# only move in y direction
|
# only move in y direction
|
||||||
candidate=Point(pos.x,newpos.y)
|
candidate=Point(pos.x,pos.x+delta.y)
|
||||||
# do not move past corners
|
# do not move past corners
|
||||||
rel=pos.y-self.pos.y
|
rel=pos.y-self.pos.y
|
||||||
newrel=newpos.y-self.pos.y
|
if delta.y>0:
|
||||||
if newpos.y>pos.y:
|
if rel<math.ceil(rel)-1e-11 and delta.y+rel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
||||||
if rel<math.ceil(rel)-1e-11 and newrel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
|
||||||
# stick to corner
|
# stick to corner
|
||||||
candidate.y=math.ceil(rel)+self.pos.y
|
candidate.y=math.ceil(rel)+self.pos.y
|
||||||
else:
|
else:
|
||||||
if rel>math.floor(rel)+1e-11 and newrel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
if rel>math.floor(rel)+1e-11 and delta.y+rel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
||||||
# stick to corner
|
# stick to corner
|
||||||
candidate.y=math.floor(rel)+self.pos.y
|
candidate.y=math.floor(rel)+self.pos.y
|
||||||
return candidate
|
return candidate
|
||||||
# move when stuck in the y direction
|
# move when stuck in the y direction
|
||||||
def move_stuck_y(self,newpos,pos):
|
def move_stuck_y(self,delta,pos):
|
||||||
# onlx move in x direction
|
# onlx move in x direction
|
||||||
candidate=Point(pos.x,newpos.x)
|
candidate=Point(pos.x,newpos.x)
|
||||||
# do not move past corners
|
# do not move past corners
|
||||||
rel=pos.x-self.pos.x
|
rel=pos.x-self.pos.x
|
||||||
newrel=newpos.x-self.pos.x
|
if delta.x>0:
|
||||||
if newpos.x>pos.x:
|
if rel<math.ceil(rel)-1e-11 and delta.x+rel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
||||||
if rel<math.ceil(rel)-1e-11 and newrel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
|
||||||
# stick to corner
|
# stick to corner
|
||||||
candidate.x=math.ceil(rel)+self.pos.x
|
candidate.x=math.ceil(rel)+self.pos.x
|
||||||
else:
|
else:
|
||||||
if rel>math.floor(rel)+1e-11 and newrel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
if rel>math.floor(rel)+1e-11 and delta.x+rel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
||||||
# stick to corner
|
# stick to corner
|
||||||
candidate.x=math.floor(rel)+self.pos.x
|
candidate.x=math.floor(rel)+self.pos.x
|
||||||
return candidate
|
return candidate
|
||||||
|
Loading…
Reference in New Issue
Block a user