Skip to content

Commit

Permalink
Fix broken tests in test_puzzle_pair.py.
Browse files Browse the repository at this point in the history
  • Loading branch information
donkirkby committed Jan 7, 2025
1 parent 948a57e commit 3fb2b73
Show file tree
Hide file tree
Showing 6 changed files with 464 additions and 440 deletions.
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ verify_ssl = true
name = "pypi"

[packages]
PySide6 = "*"
four-letter-blocks = {editable = true, path = "."}
beautifulsoup4 = "*"
scipy = "*"
pyside6 = "*"

[dev-packages]
four-letter-blocks = {editable = true, extras = ["dev"], path = "."}
Expand Down
580 changes: 328 additions & 252 deletions Pipfile.lock

Large diffs are not rendered by default.

23 changes: 19 additions & 4 deletions four_letter_blocks/block_packer.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def __init__(self,
# True if self.state should be set, even with a partial filling
self.are_partials_saved = False

self.extra_gaps = -1
self.fewest_unused: int | None = None
self.slot_coverage = self.state

Expand Down Expand Up @@ -114,18 +115,28 @@ def calculate_max_shape_counts(self):
return {shape: math.ceil(multiplier.get(shape[0], 1) * block_count / 28)
for shape in shape_names}

def find_slots(self) -> dict[str, np.ndarray]:
def find_slots(
self,
shape_counts: typing.Counter[str] | None = None) -> dict[str, np.ndarray]:
""" Find slots where each shape rotation can fit.
If you allow rotations, you have to combine the slots for each rotation.
Any spaces that are already filled have coverage 255.
:param shape_counts: shapes that we're trying to pack.
:return: {shape: bitmap}
"""
if self.state is None:
raise RuntimeError('Cannot find slots with invalid state.')

# Track spaces that are already filled, or how many slots cover them.
non_gaps = self.state.astype(bool)
if self.extra_gaps < 0:
if shape_counts is None:
self.extra_gaps = 0
else:
gap_count = self.width * self.height - non_gaps.sum()
needed_gaps = sum(shape_counts.values()) * 4
self.extra_gaps = max(0, gap_count - needed_gaps)
slot_coverage = non_gaps.astype(np.uint8) * 255
all_masks = build_masks(self.width, self.height)
shape_heights = get_shape_heights()
Expand Down Expand Up @@ -167,7 +178,10 @@ def find_slots(self) -> dict[str, np.ndarray]:
slot_coverage += shape_coverage
slots[shape] = usable_slots
self.slot_coverage = slot_coverage
if self.are_partials_saved or slot_coverage.all():
# noinspection PyTypeChecker
uncovered: np.ndarray = self.slot_coverage == 0
uncovered_count = uncovered.sum()
if self.are_partials_saved or uncovered_count <= self.extra_gaps:
return slots

# Some unfilled spaces weren't covered by any usable slots, return empty.
Expand Down Expand Up @@ -259,7 +273,8 @@ def fill(self, shape_counts: typing.Counter[str]) -> bool:
:param shape_counts: number of blocks of each shape, disables rotation
if any of the shapes contain a letter and rotation number. Adjusted
to remaining counts, if self.are_partials_saved is True.
:return: True, if successful, otherwise False.
:return: True, if all requested shapes have been placed, or if no gaps
are left, otherwise False.
"""
are_slots_shuffled = self.are_slots_shuffled
are_partials_saved = self.are_partials_saved
Expand All @@ -277,7 +292,7 @@ def fill(self, shape_counts: typing.Counter[str]) -> bool:
next_block = self.find_next_block()
fewest_rows = start_state.shape[0]+1

slots = self.find_slots()
slots = self.find_slots(shape_counts)
is_rotation_allowed = all(len(shape) == 1 for shape in shape_counts)
raw_slot_counts = {shape: shape_slots.sum()
for shape, shape_slots in slots.items()}
Expand Down
27 changes: 27 additions & 0 deletions tests/test_block_packer.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,22 @@ def test_no_rotations_needs_gap():
assert packer.display() == expected_display


def test_extra_gaps():
width = height = 5
shape_counts = Counter({'O': 2, 'S0': 1, 'T0': 2})
expected_display = dedent("""\
AAABB
.A.BB
.EEE.
DDECC
DDCC.""")
packer = BlockPacker(width, height, tries=100, min_tries=1)
is_filled = packer.fill(shape_counts)

assert is_filled
assert packer.display() == expected_display


def test_random_fill():
for _ in range(100):
shape_counts = Counter({'O': 5})
Expand Down Expand Up @@ -489,6 +505,17 @@ def test_has_slot_coverage_fails():
assert not slots


# noinspection DuplicatedCode
def test_fill_with_extra_blocks():
packer = BlockPacker(start_text=dedent("""\
..
.."""))

is_filled = packer.fill(Counter({'O': 2}))

assert is_filled


def test_shape_counts_7x7():
packer = BlockPacker(start_text=dedent("""\
#.....#
Expand Down
Loading

0 comments on commit 3fb2b73

Please sign in to comment.