Skip to content

Commit

Permalink
Merge pull request #81 from llllllllll/closest-hitobject
Browse files Browse the repository at this point in the history
add a method to get the closest hitobject to a time
  • Loading branch information
tybug authored Dec 15, 2020
2 parents 6da272d + 3fa0ba5 commit f1c6550
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 0 deletions.
54 changes: 54 additions & 0 deletions slider/beatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,60 @@ def _resolve_stacking_old(self, hit_objects, ar, cs):

return hit_objects

@lazyval
def _hit_object_times(self):
"""a (sorted) list of hitobject time's, so they can be searched with
``np.searchsorted``
"""
return [hitobj.time for hitobj in self._hit_objects]

def closest_hitobject(self, t, side="left"):
"""The hitobject closest in time to ``t``.
Parameters
----------
t : datetime.timedelta
The time to find the hitobject closest to.
side : {"left", "right"}
Whether to prefer the earlier (left) or later (right) hitobject
when breaking ties.
Returns
-------
hit_object : HitObject
The closest hitobject in time to ``t``.
None
If the beatmap has no hitobjects.
"""
if len(self._hit_objects) == 0:
raise ValueError(f"The beatmap {self!r} must have at least one "
"hit object to determine the closest hitobject.")
if len(self._hit_objects) == 1:
return self._hit_objects[0]

i = np.searchsorted(self._hit_object_times, t)
# if ``t`` is after the last hitobject, an index of
# len(self._hit_objects) will be returned. The last hitobject will
# always be the closest hitobject in this case.
if i == len(self._hit_objects):
return self._hit_objects[-1]
# similar logic follows for the first hitobject.
if i == 0:
return self._hit_objects[0]

# searchsorted tells us the two closest hitobjects, but not which is
# closer. Check both candidates.
hitobj1 = self._hit_objects[i - 1]
hitobj2 = self._hit_objects[i]
dist1 = abs(hitobj1.time - t)
dist2 = abs(hitobj2.time - t)

hitobj1_closer = dist1 <= dist2 if side == "left" else dist1 < dist1

if hitobj1_closer:
return hitobj1
return hitobj2

@lazyval
def max_combo(self):
"""The highest combo that can be achieved on this beatmap.
Expand Down
20 changes: 20 additions & 0 deletions slider/tests/test_beatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,26 @@ def test_hit_objects_hard_rock(beatmap):
Position(x=301, y=209)]


def test_closest_hitobject():
beatmap = slider.example_data.beatmaps.miiro_vs_ai_no_scenario('Beginner')
hit_object1 = beatmap.hit_objects()[4]
hit_object2 = beatmap.hit_objects()[5]
hit_object3 = beatmap.hit_objects()[6]

middle_t = timedelta(milliseconds=11076 - ((11076 - 9692) / 2))

assert hit_object1.time == timedelta(milliseconds=8615)
assert hit_object2.time == timedelta(milliseconds=9692)
assert hit_object3.time == timedelta(milliseconds=11076)

assert beatmap.closest_hitobject(timedelta(milliseconds=8615)) == \
hit_object1
assert beatmap.closest_hitobject(timedelta(milliseconds=(8615 - 30))) == \
hit_object1
assert beatmap.closest_hitobject(middle_t) == hit_object2
assert beatmap.closest_hitobject(middle_t, side="right") == hit_object3


def test_ar(beatmap):
assert beatmap.ar() == 9.5

Expand Down

0 comments on commit f1c6550

Please sign in to comment.