diff --git a/src/net.py b/src/net.py new file mode 100644 index 0000000..9ddb893 --- /dev/null +++ b/src/net.py @@ -0,0 +1,178 @@ +""" +Kernlogik des Spiels +""" + +from enum import IntEnum, auto + +type Coordinate = tuple[int, int] + + +class PieceType(IntEnum): + T_JUNCTION = auto() # Direction is the piece at the "stem" of the T + STRAIGHT = auto() + CORNER = auto() # Direction is the more counter-clockwise connection + NODE = auto() + + +class Direction(IntEnum): + UP = auto() + LEFT = auto() + DOWN = auto() + RIGHT = auto() + + @staticmethod + def from_offset(dx: int, dy: int) -> Direction: + if (dx != 0 and dy != 0) or abs(dx + dy) != 1: + raise ValueError(f"({dx}, {dy}) is not a valid direction offset") + if dx != 0: + return Direction(dx % 4) + else: + return Direction(dy + 1) + + +class Piece: + type: PieceType + direction: Direction = Direction.UP + locked: bool = False + + def __init__(self, type: PieceType) -> None: + self.type = type + + def connected_directions(self) -> list[Direction]: + match self.type: + case PieceType.NODE: + return [self.direction] + case PieceType.CORNER: + return [self.direction, Direction((self.direction + 1) % 4)] + case PieceType.STRAIGHT: + return [self.direction, Direction((self.direction + 2) % 4)] + case PieceType.T_JUNCTION: + return [ + self.direction, + Direction((self.direction + 1) % 4), + Direction((self.direction - 1) % 4), + ] + + def turn_cw(self) -> None: + self.direction = Direction((self.direction + 1) % 4) + + def turn_ccw(self) -> None: + self.direction = Direction((self.direction - 1) % 4) + + +DEMO_FIELD = [ + [ + Piece(PieceType.NODE), + Piece(PieceType.NODE), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.NODE), + Piece(PieceType.NODE), + ], + [ + Piece(PieceType.STRAIGHT), + Piece(PieceType.NODE), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.STRAIGHT), + Piece(PieceType.CORNER), + ], + [ + Piece(PieceType.T_JUNCTION), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.NODE), + ], + [ + Piece(PieceType.STRAIGHT), + Piece(PieceType.NODE), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.T_JUNCTION), + Piece(PieceType.NODE), + ], + [ + Piece(PieceType.NODE), + Piece(PieceType.NODE), + Piece(PieceType.CORNER), + Piece(PieceType.CORNER), + Piece(PieceType.CORNER), + ], +] + + +class Grid: + pieces: list[list[Piece]] + width: int + height: int + + def __init__(self, width: int, height: int) -> None: + self.height = height + self.width = width + self.pieces = ( + DEMO_FIELD # TODO: Field generation or import from Tatham's version + ) + + def neighbors(self, x: int, y: int) -> list[Coordinate]: + neighbors: list[Coordinate] = [] + for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): + if ( + x + dx < 0 + or x + dx >= self.width + or y + dy < 0 + or y + dy >= self.height + ): + continue + neighbors.append((x + dx, y + dy)) + return neighbors + + def solved(self) -> bool: + connected = [([False] * self.height) for _ in range(self.width)] + connected = self._solve_floodfill(0, 0, connected) + return all(map(lambda x: all(x), connected)) + + def _solve_floodfill( + self, x: int, y: int, connected: list[list[bool]] + ) -> list[list[bool]]: + connected[x][y] = True + + connectedNeighbors: list[Coordinate] = [] + for nx, ny in self.neighbors(x, y): + dx = x - nx + dy = y - ny + if ( + Direction.from_offset(-dx, -dy) + in self.pieces[x + dx][y + dy].connected_directions() + ): + connectedNeighbors.append((x + dx, y + dy)) + + for nx, ny in connectedNeighbors: + if connected[nx][ny]: + continue + connected = self._solve_floodfill(nx, ny, connected) + + return connected + + +class NetGame: + _grid: Grid + + def __init__(self, width: int, height: int) -> None: + self._grid = Grid(width, height) + + def get_field(self) -> list[list[Piece]]: + return self._grid.pieces + + def get_piece(self, x: int, y: int) -> Piece: + return self._grid.pieces[x][y] + + def turn_cw(self, x: int, y: int) -> None: + self._grid.pieces[x][y].turn_cw() + + def turn_ccw(self, x: int, y: int) -> None: + self._grid.pieces[x][y].turn_ccw() + + def solved(self): + return self._grid.solved() + + +g = NetGame(5, 5) +print(g.solved())