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( button in range(length) for button in self.buttons ), "Invalid buttons!" 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 ) 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" def click(self, index: int) -> None: assert self.is_clickable(index), f"{index} is not clickable!"