"""Tests des Schlafmodus (Inseln, Einschlafen, Aufwecken).""" from dataclasses import replace from sandbox.config import PHYSICS from sandbox.physics import Body, Contact, Vector2, collide from sandbox.physics.sleep import UnionFind, find_islands, update_sleep, wake_touched_bodies from tests.helpers import material def _circle(x: float, y: float = 0.0, radius: float = 10.0) -> Body: return Body.circle(Vector2(x, y), radius, material()) def _contacts(*pairs: tuple[Body, Body]) -> list[Contact]: contacts = [collide(a, b) for a, b in pairs] assert all(contact is not None for contact in contacts) return [contact for contact in contacts if contact is not None] def test_union_find_merges_sets() -> None: union_find = UnionFind([1, 2, 3, 4, 5]) union_find.union(1, 2) union_find.union(3, 4) union_find.union(2, 4) assert union_find.find(1) == union_find.find(3) == union_find.find(4) assert union_find.find(5) != union_find.find(1) def test_touching_bodies_form_islands_but_walls_do_not_connect_them() -> None: a, b, c, d = _circle(0), _circle(15), _circle(30), _circle(200) floor = Body.wall(Vector2(0, -1), -5, material()) # berührt alle Kreise contacts = _contacts((a, b), (b, c), (a, floor), (d, floor)) islands = find_islands([a, b, c, d], contacts) assert [set(island) for island in islands] == [{a, b, c}, {d}] def test_an_island_sleeps_only_when_all_its_bodies_rest() -> None: config = replace(PHYSICS, time_to_sleep=0.5) resting, moving = _circle(0), _circle(15) moving.velocity = Vector2(50, 0) contacts = _contacts((resting, moving)) for _ in range(100): update_sleep([resting, moving], contacts, 0.01, config) assert resting.sleep_time >= 0.5 assert not resting.sleeping # der bewegte Nachbar hält die Insel wach moving.velocity = Vector2(0, 0) for _ in range(60): update_sleep([resting, moving], contacts, 0.01, config) assert resting.sleeping assert moving.sleeping def test_waking_one_body_wakes_its_whole_island() -> None: a, b, c, outsider = _circle(0), _circle(15), _circle(30), _circle(300) island = [a, b, c] for body in island: body.put_to_sleep(island) outsider.put_to_sleep() c.wake() assert not any(body.sleeping for body in island) assert all(body.sleep_island is None for body in island) assert outsider.sleeping def test_touching_a_sleeper_wakes_and_reports_its_whole_island() -> None: a, b, c = _circle(0), _circle(15), _circle(30) island = [b, c] for body in island: body.put_to_sleep(island) woken = wake_touched_bodies(_contacts((a, b))) assert set(woken) == {b, c} assert not c.sleeping def test_held_bodies_keep_their_island_awake() -> None: held, other = _circle(0), _circle(15) held.held = True contacts = _contacts((held, other)) for _ in range(200): update_sleep([held, other], contacts, 0.01, PHYSICS) assert not held.sleeping assert not other.sleeping assert held.sleep_time == 0 def test_sleepers_touched_by_awake_bodies_are_woken_but_not_by_walls() -> None: sleeper, awake = _circle(0), _circle(15) sleeper.put_to_sleep() floor = Body.wall(Vector2(0, -1), -5, material()) lonely = _circle(300) lonely.put_to_sleep() both_asleep = _circle(500) both_asleep.put_to_sleep() neighbour = _circle(515) neighbour.put_to_sleep() woken = wake_touched_bodies( _contacts((sleeper, awake), (lonely, floor), (both_asleep, neighbour)) ) assert woken == [sleeper] assert not sleeper.sleeping assert lonely.sleeping assert both_asleep.sleeping