191 lines
6.0 KiB
Python
191 lines
6.0 KiB
Python
import pytest
|
|
from gridedit import (
|
|
cell_column,
|
|
cell_line,
|
|
edit,
|
|
h_segment_column,
|
|
wall_column_left_of,
|
|
wall_line_above,
|
|
)
|
|
|
|
from dojo.board import (
|
|
BOARD_SIZE,
|
|
Board,
|
|
BoardError,
|
|
Pos,
|
|
build_board,
|
|
edge,
|
|
load_board,
|
|
parse_legend,
|
|
quadrant_of,
|
|
rotate,
|
|
)
|
|
from dojo.grid import parse_grid, render_grid
|
|
|
|
|
|
def build(text: str, offsets: dict[str, int] | None = None) -> Board:
|
|
return build_board(parse_grid(text), offsets)
|
|
|
|
|
|
def build_problems(text: str, offsets: dict[str, int] | None = None) -> list[str]:
|
|
with pytest.raises(BoardError) as raised:
|
|
build(text, offsets)
|
|
return raised.value.problems
|
|
|
|
|
|
def test_rotation_is_clockwise_around_the_centre() -> None:
|
|
corner = Pos(0, 0)
|
|
|
|
assert [rotate(corner, k) for k in range(4)] == [(0, 0), (0, 15), (15, 15), (15, 0)]
|
|
assert [quadrant_of(rotate(Pos(3, 5), k)) for k in range(4)] == [0, 1, 2, 3]
|
|
assert rotate(Pos(3, 5), 4) == Pos(3, 5)
|
|
|
|
|
|
def test_board_is_sixteen_by_sixteen_with_central_dojo(board: Board) -> None:
|
|
assert board.size == BOARD_SIZE
|
|
assert board.dojo == {Pos(r, c) for r in range(6, 10) for c in range(6, 10)}
|
|
|
|
|
|
def test_every_cell_matches_its_rotated_source(board: Board, quadrant_text: str) -> None:
|
|
quadrant = parse_grid(quadrant_text)
|
|
|
|
for r in range(8):
|
|
for c in range(8):
|
|
for k in range(4):
|
|
assert board.code(rotate(Pos(r, c), k)) == quadrant.cells[r][c]
|
|
|
|
|
|
def test_walls_are_rotated(board: Board) -> None:
|
|
safe_room_east_wall = edge(Pos(0, 2), Pos(0, 3))
|
|
|
|
for k in range(4):
|
|
a, b = safe_room_east_wall
|
|
assert edge(rotate(a, k), rotate(b, k)) in board.walls
|
|
assert edge(Pos(1, 2), Pos(1, 3)) not in board.walls
|
|
|
|
|
|
def test_neighbours_respect_walls_and_border(board: Board) -> None:
|
|
assert set(board.neighbours(Pos(0, 0))) == {Pos(0, 1), Pos(1, 0)}
|
|
assert Pos(0, 3) not in set(board.neighbours(Pos(0, 2)))
|
|
assert Pos(1, 3) in set(board.neighbours(Pos(1, 2)))
|
|
|
|
|
|
def test_seams_join_neighbouring_quadrants(board: Board) -> None:
|
|
assert Pos(0, 8) in set(board.neighbours(Pos(0, 7)))
|
|
assert Pos(8, 0) in set(board.neighbours(Pos(7, 0)))
|
|
|
|
|
|
def test_to_grid_round_trips_through_render(board: Board) -> None:
|
|
grid = board.to_grid()
|
|
|
|
assert parse_grid(render_grid(grid)) == grid
|
|
|
|
|
|
def test_tunnels_pair_within_the_quadrant_by_default(board: Board) -> None:
|
|
a, b = Pos(1, 6), Pos(3, 4)
|
|
|
|
for k in range(4):
|
|
assert board.tunnels[rotate(a, k)] == rotate(b, k)
|
|
assert board.tunnels[rotate(b, k)] == rotate(a, k)
|
|
assert len(board.tunnels) == 8
|
|
|
|
|
|
@pytest.mark.parametrize("offset", [1, 2, 3])
|
|
def test_tunnel_offset_links_the_first_cell_to_a_later_quadrant(
|
|
quadrant_text: str, offset: int
|
|
) -> None:
|
|
board = build(quadrant_text, {"1": offset})
|
|
a, b = Pos(1, 6), Pos(3, 4)
|
|
|
|
for k in range(4):
|
|
assert board.tunnels[rotate(a, k)] == rotate(b, k + offset)
|
|
assert board.tunnels[rotate(b, k + offset)] == rotate(a, k)
|
|
assert all(board.tunnels[board.tunnels[p]] == p for p in board.tunnels)
|
|
|
|
|
|
def test_quadrant_must_be_eight_by_eight() -> None:
|
|
small = "+---+\n| D |\n+---+\n"
|
|
|
|
assert build_problems(small) == ["quadrant must be 8x8, got 1x1"]
|
|
|
|
|
|
def test_unknown_cell_code(quadrant_text: str) -> None:
|
|
text = edit(quadrant_text, cell_line(4), cell_column(4), "X")
|
|
|
|
assert "cell (4, 4): unknown code 'X'" in build_problems(text)
|
|
|
|
|
|
def test_outer_border_must_be_walled(quadrant_text: str) -> None:
|
|
open_top = edit(quadrant_text, wall_line_above(0), h_segment_column(4), " ")
|
|
open_left = edit(quadrant_text, cell_line(4), wall_column_left_of(0), " ")
|
|
|
|
assert any("top border" in p for p in build_problems(open_top))
|
|
assert any("left border" in p for p in build_problems(open_left))
|
|
|
|
|
|
def test_seams_must_match(quadrant_text: str) -> None:
|
|
text = edit(quadrant_text, cell_line(2), wall_column_left_of(8), "|")
|
|
|
|
assert any("seam mismatch: right border at row 2" in p for p in build_problems(text))
|
|
|
|
|
|
def test_matching_seam_walls_are_accepted(quadrant_text: str) -> None:
|
|
text = edit(quadrant_text, cell_line(2), wall_column_left_of(8), "|")
|
|
text = edit(text, wall_line_above(8), h_segment_column(2), "---")
|
|
|
|
board = build(text)
|
|
|
|
assert edge(Pos(2, 7), Pos(2, 8)) in board.walls
|
|
assert edge(Pos(7, 2), Pos(8, 2)) in board.walls
|
|
|
|
|
|
def test_dojo_cells_are_fixed(quadrant_text: str) -> None:
|
|
text = edit(quadrant_text, cell_line(5), cell_column(7), "D")
|
|
|
|
assert any("Dojo cells must be exactly" in p for p in build_problems(text))
|
|
|
|
|
|
def test_tunnel_needs_exactly_two_cells(quadrant_text: str) -> None:
|
|
text = edit(quadrant_text, cell_line(4), cell_column(4), "1")
|
|
|
|
assert "tunnel '1' must appear on exactly 2 cells, found 3" in build_problems(text)
|
|
|
|
|
|
def test_legend_offset_must_be_valid(quadrant_text: str) -> None:
|
|
problems = build_problems(quadrant_text, {"1": 4, "2": 0})
|
|
|
|
assert "tunnel '1': quadrant_offset must be 0..3, got 4" in problems
|
|
assert "legend links tunnel '2', which is not on the quadrant" in problems
|
|
|
|
|
|
def test_parse_legend() -> None:
|
|
assert parse_legend({"tunnels": {"1": {"quadrant_offset": 2}}}) == {"1": 2}
|
|
assert parse_legend({}) == {}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("data", "message"),
|
|
[
|
|
({"extra": {}}, "unknown sections"),
|
|
({"tunnels": 3}, "must be a table"),
|
|
({"tunnels": {"a": {"quadrant_offset": 1}}}, "must be a digit"),
|
|
({"tunnels": {"1": {"offset": 1}}}, "only quadrant_offset"),
|
|
],
|
|
)
|
|
def test_parse_legend_errors(data: dict[str, object], message: str) -> None:
|
|
with pytest.raises(BoardError) as raised:
|
|
parse_legend(data)
|
|
|
|
assert any(message in p for p in raised.value.problems)
|
|
|
|
|
|
def test_load_board_reads_legend(tmp_path, quadrant_text: str) -> None:
|
|
quadrant = tmp_path / "quadrant.txt"
|
|
legend = tmp_path / "legend.toml"
|
|
quadrant.write_text(quadrant_text, encoding="utf-8")
|
|
legend.write_text("[tunnels.1]\nquadrant_offset = 1\n", encoding="utf-8")
|
|
|
|
board = load_board(quadrant, legend)
|
|
|
|
assert board.tunnels[Pos(1, 6)] == rotate(Pos(3, 4), 1)
|