Skip to content

Commit

Permalink
Refactor move_as_close_as_possible() + line stuff
Browse files Browse the repository at this point in the history
Introduce some scaffolding for line-based heuristics in collision
detection. This commit mostly refactors move_as_close_as_possible()
to not be a complete mess.
  • Loading branch information
kawa-kokosowa committed Sep 6, 2016
1 parent a781b45 commit 07c917d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 42 deletions.
109 changes: 68 additions & 41 deletions sappho/collide.py
Expand Up @@ -75,9 +75,16 @@ def update(self, timedelta):
if hasattr(self, 'mask'):
self.mask = self.sprite.mask

# TODO: is not used. how does spritecollide work?
def collides_rect(self, sprite_group):
return pygame.sprite.spritecollide(self, sprite_group, False,
collided=pygame.sprite.collide_rect)
"""
Boilerplate for checking rectangular collision of this
ColliderSprite against sprites in the provided sprite_group.
"""

return pygame.sprite.spritecollide(self, sprite_group, False)

def collides_rect_mask(self, sprite_group):
"""See if collides by rect first, then check by mask if exists.
Expand Down Expand Up @@ -107,8 +114,7 @@ def collides_rect_mask(self, sprite_group):
elif (not hasattr(self, 'mask')) and (not hasattr(self, 'mask')):
return sprite_whose_rect_collides

else:
return None
return None

# TODO:
# could be called something like rect_mask_path and iterate
Expand Down Expand Up @@ -138,21 +144,41 @@ def try_to_move(self, new_coord, sprite_group):
self.rect.topleft = old_topleft
raise Collision("some side...")

# TODO, FIXME
# lacks any heuristics
def move_as_close_as_possible(self, destination, sprite_group):
"""Move as close as possible to `destination` without collision.
"""Move along a line to destination coordinate, stopping before
potential collision.
"""
Move as close as possible to `destination` without collision.
goal_x, goal_y = destination
current_x, current_y = self.rect.topleft
Warning:
This isn't fast! It lacks any decent heuristics, such as
line-based collisions. I'll be benchmarking this method
against line-based collision heuristics in the future.
# FIRST: figure out the x and y increments
Arguments:
destination (tuple[x, y]): The goal coordinate to move to, or
at least as close to as possible before colliding.
sprite_group (pygame.sprite.Group): Pygame sprite group, whose
sprites are check each time we move one pixel
toward the destination.
Returns:
pygame.Sprite: The first sprite detected which prevented
moving further in the path.
None: Moved to destination without collision.
"""

# Figure out the x and y increments!
#
# I use "increment" herein to mean "step which approaches,
# by one, the destination.
#
# For both the x and y axis, get the "step"
# (increment or decrement) which will
# eventually bring us to the goal.
goal_x, goal_y = destination

if goal_x > self.rect.left:
x_increment = 1
elif goal_x < self.rect.left:
Expand All @@ -167,53 +193,29 @@ def move_as_close_as_possible(self, destination, sprite_group):
else:
y_increment = 0

# Ye dangerous infinite loop: exits when either a collision
# is detected, or we have arrived at the goal/destination.
while True:
# This loop will return the first sprite it finds to collide,
# or we'll return None (later) if it can't find anything!
while self.rect.topleft != (goal_x, goal_y):
# last_safe_topleft allows us to reset to the last known
# good/noncolliding coordinate in the event of a collision
last_safe_topleft = self.rect.topleft

# If y is already at its goal, there's no need to increment
if not self.rect.top == goal_y:
if self.rect.top != goal_y:
self.rect.top += y_increment

# ... same thing for x and its goal.
if not self.rect.left == goal_x:
if self.rect.left != goal_x:
self.rect.left += x_increment

colliding_with = self.collides_rect_mask(sprite_group)

if colliding_with:
self.rect.topleft = last_safe_topleft
return colliding_with
elif self.rect.topleft == (goal_x, goal_y):
# we're at our goal, we've not encountered
# a collision.
return None

# one last check for the last point

# mathematically, if we
if position_difference_x != 0:
position_difference_x += x_modifier
return None

if position_difference_y != 0:
position_difference_y += y_modifier

old_topleft = self.rect.topleft
new_coord = [position_difference_x + self.rect.topleft[0],
position_difference_y + self.rect.topleft[1]]
self.rect.topleft = new_coord
colliding_with = self.collides_rect_mask(sprite_group)

if colliding_with:
self.rect.topleft = old_coord
return colliding_with
else:
return None

# TODO: what if I want diagonal!?
def sprites_in_orthogonal_path(self, new_coord, sprite_group):
"""Return the sprites this ColliderSprite would "run through"
and thus collide with if it moved to new_coord.
Expand All @@ -238,3 +240,28 @@ def sprites_in_orthogonal_path(self, new_coord, sprite_group):
colliding_with = self.collides_rect(sprite_group)
self.rect = current_rect
return colliding_with

def collides_line(self, line_point_a, line_point_b, sprite_group):
"""Efficiently check if any sprites along a line collide,
return True on first result, False if no collision.
Arguments:
line_point_a (tuple[int, int]): --
"""

pass


def lines_intersection(line_a, line_b):
"""Return the point in which lines intersect, else
return None.
Arguments:
line_a (tuple[int, int]): --
http://webcache.googleusercontent.com/search?q=cache:Ur-EPX41x00J:devmag.org.za/2009/04/17/basic-collision-detection-in-2d-part-2/+&cd=1&hl=en&ct=clnk&gl=us&client=ubuntu
"""

pass
23 changes: 22 additions & 1 deletion tests/test_collide.py
Expand Up @@ -5,7 +5,7 @@
from sappho import collide, animate


class TestColliderSprites(object):
class TestColliderSprite(object):

# NOTE, TODO: this is a pretty bad test. Ideally, it
# would do something more specific in addition to retesting
Expand All @@ -24,3 +24,24 @@ def test_basic_attributes(self):
assert collision_sprite.rect.size == (10, 10)
assert hasattr(collision_sprite, 'mask')
# TODO: should test after updating sprite

def mock_sprite_group(self):
pass

def test_collides_rect(self):
pass

def test_collides_rect_mask(self):
pass


# The below pattern can be used for collides_rect, collides_Rect_mask,
# try_to_move

# Create a group of sprites with various unique positions

# Create a sprite which will be checked for collisions
# against the group of sprites created in the last step.
# We intentionally place this collidersprite somewhere that'll
# collide with at least one colliddersprite from the group of
# the last step

0 comments on commit 07c917d

Please sign in to comment.