pigeon-magnet-solver/pigeon_magnet_solver/board.py

88 lines
2.2 KiB
Python
Raw Normal View History

2023-08-18 18:05:23 +00:00
from dataclasses import dataclass
@dataclass(kw_only=True, slots=True)
class Board:
rows: list[str]
buttons: tuple[int, ...]
@classmethod
@property
def default_puzzle(cls) -> "Board":
return cls(
rows=["011", "011", "100", "x1x"],
buttons=(3, 4, 5, 7),
)
@property
def height(self) -> int:
return len(self.rows)
@property
def width(self) -> int:
if not self.rows:
return 0
else:
return len(self.rows[0])
def __post_init__(self) -> None:
# check char set
chars = {c for c in "".join(self.rows)}
assert chars.issubset({"0", "1", "x"}), "Invalid char set!"
# check defined width
assert all(
len(row) == self.width
for row in self.rows
), "Inconsistent width!"
# check buttons
length = self.height * self.width
assert all(
2023-08-18 20:12:22 +00:00
button in range(length)
2023-08-18 18:05:23 +00:00
for button in self.buttons
), "Invalid buttons!"
2023-08-18 18:41:58 +00:00
def __str__(self) -> str:
result = "\n".join(self.rows)
result = result\
.replace("0", "⚪️")\
.replace("1", "🟢️")\
.replace("x", "⚫️")
return result
@property
def columns(self) -> tuple[str, ...]:
return tuple(
"".join(row[x] for row in self.rows)
for x in range(self.width)
)
@property
def binary_repr(self) -> tuple[int, ...]:
return tuple(
int(column.replace("x", "0")[::-1], base=2)
for column in self.columns
)
@property
def solution_class(self) -> tuple[int, ...]:
return tuple(
column.count("1")
for column in self.columns
)
2023-08-18 20:12:22 +00:00
def __idx_to_xy(self, index: int) -> tuple[int, int]:
return index % self.width, index // self.width
def is_clickable(self, index: int) -> bool:
# check index
assert index in self.buttons, f"{index} is not a button!"
# check is button
x, y = self.__idx_to_xy(index)
return self.rows[y][x] == "1"
2023-08-18 18:05:23 +00:00
def click(self, index: int) -> None:
2023-08-18 20:12:22 +00:00
assert self.is_clickable(index), f"{index} is not clickable!"