Add project structure with maze solver, simulator, and tests
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Failing after 3s

This commit is contained in:
2026-10-04 12:07:00 +08:00
commit bca920c62c
18 changed files with 1367 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
"""!
@file test_maze_solver.py
@brief Runs the solver on every provided world from all four start headings.
"""
from pathlib import Path
import pytest
from reeborg_maze import SimulatedRobot, load_world, solve_maze
from reeborg_maze.constants import BLOCKING_TILES, HEADING_COUNT
WORLDS = sorted((Path(__file__).parent / "worlds").glob("*.json"))
def free_starts() -> list[tuple[str, int, int, int]]:
"""!
@brief Every (world, x, y, heading) where the robot can start.
"""
starts = []
for path in WORLDS:
world = load_world(path)
for x in range(1, world["cols"] + 1):
for y in range(1, world["rows"] + 1):
if BLOCKING_TILES.intersection(world["tiles"].get(f"{x},{y}", [])):
continue
for heading in range(HEADING_COUNT):
starts.append((path.name, x, y, heading))
return starts
def test_worlds_exist() -> None:
assert WORLDS
@pytest.mark.parametrize("path", WORLDS, ids=lambda p: p.name)
def test_reaches_goal_from_file_start(path: Path) -> None:
robot = SimulatedRobot(load_world(path))
solve_maze(robot)
assert robot.at_goal()
@pytest.mark.parametrize(("name", "x", "y", "heading"), free_starts())
def test_reaches_goal_from_every_free_cell_and_heading(
name: str, x: int, y: int, heading: int
) -> None:
robot = SimulatedRobot(load_world(Path(__file__).parent / "worlds" / name), x, y, heading)
solve_maze(robot)
assert robot.at_goal()
+248
View File
@@ -0,0 +1,248 @@
{
"walls": {
"1,1": [
"east"
],
"1,2": [
"east"
],
"1,3": [
"north"
],
"2,5": [
"north"
],
"1,5": [
"north"
],
"3,5": [
"north"
],
"4,5": [
"north"
],
"5,5": [
"north",
"east"
],
"2,3": [
"north",
"east"
],
"2,2": [
"east"
],
"3,2": [
"east"
],
"5,1": [
"east"
],
"5,3": [
"east",
"north"
],
"4,4": [
"east",
"north"
],
"4,2": [
"east"
],
"1,4": [
"east"
],
"5,2": [
"east",
"north"
],
"3,1": [
"north"
]
},
"small_tiles": false,
"rows": 6,
"cols": 6,
"tiles": {
"6,1": [
"mud"
],
"6,2": [
"mud"
],
"6,3": [
"mud"
],
"6,5": [
"mud"
],
"6,6": [
"mud"
],
"5,6": [
"mud"
],
"4,6": [
"mud"
],
"3,6": [
"mud"
],
"2,6": [
"mud"
],
"1,6": [
"mud"
],
"6,4": [
"grass"
],
"5,4": [
"#000000"
],
"5,5": [
"#000000"
],
"4,5": [
"#000000"
],
"3,5": [
"#000000"
],
"3,4": [
"#000000"
],
"4,4": [
"#000000"
],
"4,3": [
"#000000"
],
"3,3": [
"#000000"
],
"3,2": [
"#000000"
],
"4,2": [
"#000000"
],
"5,3": [
"#000000"
],
"5,2": [
"#000000"
],
"5,1": [
"#000000"
],
"4,1": [
"#000000"
],
"3,1": [
"#000000"
],
"2,1": [
"#000000"
],
"2,2": [
"#000000"
],
"2,3": [
"#000000"
],
"2,4": [
"#000000"
],
"2,5": [
"#000000"
],
"1,5": [
"#000000"
],
"1,4": [
"#000000"
],
"1,3": [
"#000000"
],
"1,2": [
"#000000"
],
"1,1": [
"#000000"
]
},
"goal": {
"possible_final_positions": [
[
6,
4
]
],
"position": {
"image": "racing_flag",
"x": 6,
"y": 4
}
},
"robots": [
{
"x": 3,
"y": 4,
"objects": {},
"model": "classic",
"_orientation": 3,
"_is_leaky": true,
"_prev_x": 3,
"_prev_y": 3,
"_prev_orientation": 2,
"_trace_history": [
{
"color": "seagreen",
"thickness": 2,
"prev_x": 140,
"x": 140,
"prev_y": 170,
"y": 210,
"grid_x": 3,
"grid_y": 3
},
{
"color": "seagreen",
"thickness": 2,
"prev_x": 140,
"x": 140,
"prev_y": 200,
"y": 210,
"grid_x": 3,
"grid_y": 3
}
],
"_trace_style": "default",
"_trace_color": "seagreen",
"__id": 80,
"initial_position": [
3,
3
]
}
],
"description": [
"",
"<h1>Lost in a maze</h1>",
"<p>Reeborg was exploring a dark maze and the battery in its flashlight ran out.</p>",
"<p>Write a program using an <code>if/elif/else</code> statement so Reeborg can find the exit. ",
"The secret is to have Reeborg follow along the right edge of the maze, ",
"turning right if it can, going straight ahead if it ",
"can’t turn right, or turning left as a last resort.</p>",
"<h3>What you need to know</h3>",
"<ul><li>The functions <code>move()</code> and <code>turn_left()</code>.</li>",
"<li>Either the test <code>front_is_clear()</code> or <code>wall_in_front()</code>,",
"<code>right_is_clear()</code> or <code>wall_on_right()</code>, and <code>at_goal()</code>.</li>",
"<li>How to use a <code>while</code> loop and <code>if/elif/else</code> statements.</li>",
"<li>It might be useful to know how to use the negation of a test (<code>not</code> in Python).</li></ul>",
"DIFFICULTY4",
""
]
}
+248
View File
@@ -0,0 +1,248 @@
{
"walls": {
"1,1": [
"east"
],
"1,2": [
"east"
],
"1,3": [
"north"
],
"2,5": [
"north"
],
"1,5": [
"north"
],
"3,5": [
"north"
],
"4,5": [
"north"
],
"5,5": [
"north",
"east"
],
"2,3": [
"north",
"east"
],
"2,2": [
"east"
],
"3,2": [
"east"
],
"5,1": [
"east"
],
"5,3": [
"east",
"north"
],
"4,4": [
"east",
"north"
],
"4,2": [
"east"
],
"1,4": [
"east"
],
"5,2": [
"east",
"north"
],
"3,1": [
"north"
]
},
"small_tiles": false,
"rows": 6,
"cols": 6,
"tiles": {
"6,1": [
"mud"
],
"6,2": [
"mud"
],
"6,3": [
"mud"
],
"6,5": [
"mud"
],
"6,6": [
"mud"
],
"5,6": [
"mud"
],
"4,6": [
"mud"
],
"3,6": [
"mud"
],
"2,6": [
"mud"
],
"1,6": [
"mud"
],
"6,4": [
"grass"
],
"5,4": [
"#000000"
],
"5,5": [
"#000000"
],
"4,5": [
"#000000"
],
"3,5": [
"#000000"
],
"3,4": [
"#000000"
],
"4,4": [
"#000000"
],
"4,3": [
"#000000"
],
"3,3": [
"#000000"
],
"3,2": [
"#000000"
],
"4,2": [
"#000000"
],
"5,3": [
"#000000"
],
"5,2": [
"#000000"
],
"5,1": [
"#000000"
],
"4,1": [
"#000000"
],
"3,1": [
"#000000"
],
"2,1": [
"#000000"
],
"2,2": [
"#000000"
],
"2,3": [
"#000000"
],
"2,4": [
"#000000"
],
"2,5": [
"#000000"
],
"1,5": [
"#000000"
],
"1,4": [
"#000000"
],
"1,3": [
"#000000"
],
"1,2": [
"#000000"
],
"1,1": [
"#000000"
]
},
"goal": {
"possible_final_positions": [
[
6,
4
]
],
"position": {
"image": "racing_flag",
"x": 6,
"y": 4
}
},
"robots": [
{
"x": 3,
"y": 4,
"objects": {},
"model": "classic",
"_orientation": 1,
"_is_leaky": true,
"_prev_x": 3,
"_prev_y": 3,
"_prev_orientation": 2,
"_trace_history": [
{
"color": "seagreen",
"thickness": 2,
"prev_x": 140,
"x": 140,
"prev_y": 170,
"y": 210,
"grid_x": 3,
"grid_y": 3
},
{
"color": "seagreen",
"thickness": 2,
"prev_x": 140,
"x": 140,
"prev_y": 200,
"y": 210,
"grid_x": 3,
"grid_y": 3
}
],
"_trace_style": "default",
"_trace_color": "seagreen",
"__id": 80,
"initial_position": [
3,
3
]
}
],
"description": [
"",
"<h1>Lost in a maze</h1>",
"<p>Reeborg was exploring a dark maze and the battery in its flashlight ran out.</p>",
"<p>Write a program using an <code>if/elif/else</code> statement so Reeborg can find the exit. ",
"The secret is to have Reeborg follow along the right edge of the maze, ",
"turning right if it can, going straight ahead if it ",
"can’t turn right, or turning left as a last resort.</p>",
"<h3>What you need to know</h3>",
"<ul><li>The functions <code>move()</code> and <code>turn_left()</code>.</li>",
"<li>Either the test <code>front_is_clear()</code> or <code>wall_in_front()</code>,",
"<code>right_is_clear()</code> or <code>wall_on_right()</code>, and <code>at_goal()</code>.</li>",
"<li>How to use a <code>while</code> loop and <code>if/elif/else</code> statements.</li>",
"<li>It might be useful to know how to use the negation of a test (<code>not</code> in Python).</li></ul>",
"DIFFICULTY4",
""
]
}
+248
View File
@@ -0,0 +1,248 @@
{
"walls": {
"1,1": [
"east"
],
"1,2": [
"east"
],
"1,3": [
"north"
],
"2,5": [
"north"
],
"1,5": [
"north"
],
"3,5": [
"north"
],
"4,5": [
"north"
],
"5,5": [
"north",
"east"
],
"2,3": [
"north",
"east"
],
"2,2": [
"east"
],
"3,2": [
"east"
],
"5,1": [
"east"
],
"5,3": [
"east",
"north"
],
"4,4": [
"east",
"north"
],
"4,2": [
"east"
],
"1,4": [
"east"
],
"5,2": [
"east",
"north"
],
"3,1": [
"north"
]
},
"small_tiles": false,
"rows": 6,
"cols": 6,
"tiles": {
"6,1": [
"mud"
],
"6,2": [
"mud"
],
"6,3": [
"mud"
],
"6,5": [
"mud"
],
"6,6": [
"mud"
],
"5,6": [
"mud"
],
"4,6": [
"mud"
],
"3,6": [
"mud"
],
"2,6": [
"mud"
],
"1,6": [
"mud"
],
"6,4": [
"grass"
],
"5,4": [
"#000000"
],
"5,5": [
"#000000"
],
"4,5": [
"#000000"
],
"3,5": [
"#000000"
],
"3,4": [
"#000000"
],
"4,4": [
"#000000"
],
"4,3": [
"#000000"
],
"3,3": [
"#000000"
],
"3,2": [
"#000000"
],
"4,2": [
"#000000"
],
"5,3": [
"#000000"
],
"5,2": [
"#000000"
],
"5,1": [
"#000000"
],
"4,1": [
"#000000"
],
"3,1": [
"#000000"
],
"2,1": [
"#000000"
],
"2,2": [
"#000000"
],
"2,3": [
"#000000"
],
"2,4": [
"#000000"
],
"2,5": [
"#000000"
],
"1,5": [
"#000000"
],
"1,4": [
"#000000"
],
"1,3": [
"#000000"
],
"1,2": [
"#000000"
],
"1,1": [
"#000000"
]
},
"goal": {
"possible_final_positions": [
[
6,
4
]
],
"position": {
"image": "racing_flag",
"x": 6,
"y": 4
}
},
"robots": [
{
"x": 3,
"y": 4,
"objects": {},
"model": "classic",
"_orientation": 2,
"_is_leaky": true,
"_prev_x": 3,
"_prev_y": 3,
"_prev_orientation": 2,
"_trace_history": [
{
"color": "seagreen",
"thickness": 2,
"prev_x": 140,
"x": 140,
"prev_y": 170,
"y": 210,
"grid_x": 3,
"grid_y": 3
},
{
"color": "seagreen",
"thickness": 2,
"prev_x": 140,
"x": 140,
"prev_y": 200,
"y": 210,
"grid_x": 3,
"grid_y": 3
}
],
"_trace_style": "default",
"_trace_color": "seagreen",
"__id": 80,
"initial_position": [
3,
3
]
}
],
"description": [
"",
"<h1>Lost in a maze</h1>",
"<p>Reeborg was exploring a dark maze and the battery in its flashlight ran out.</p>",
"<p>Write a program using an <code>if/elif/else</code> statement so Reeborg can find the exit. ",
"The secret is to have Reeborg follow along the right edge of the maze, ",
"turning right if it can, going straight ahead if it ",
"can’t turn right, or turning left as a last resort.</p>",
"<h3>What you need to know</h3>",
"<ul><li>The functions <code>move()</code> and <code>turn_left()</code>.</li>",
"<li>Either the test <code>front_is_clear()</code> or <code>wall_in_front()</code>,",
"<code>right_is_clear()</code> or <code>wall_on_right()</code>, and <code>at_goal()</code>.</li>",
"<li>How to use a <code>while</code> loop and <code>if/elif/else</code> statements.</li>",
"<li>It might be useful to know how to use the negation of a test (<code>not</code> in Python).</li></ul>",
"DIFFICULTY4",
""
]
}