diff --git a/src/GUI.py b/src/GUI.py index ed3d67e..9031d4a 100644 --- a/src/GUI.py +++ b/src/GUI.py @@ -1,5 +1,4 @@ import os -from time import sleep from typing import override import pygame @@ -120,7 +119,6 @@ class NetGUI: _ = input() if (not current_solver) or display_solver: self.update_display(events) - sleep(2) NetGUI(5, 5).run_game() diff --git a/src/algorithms/wfc.py b/src/algorithms/wfc.py index dc06671..a4d7508 100644 --- a/src/algorithms/wfc.py +++ b/src/algorithms/wfc.py @@ -1,100 +1,148 @@ from collections.abc import Generator +from dataclasses import dataclass +from enum import Enum, auto from src.netTypes import Direction, PieceType from src.net import NetGame +class ConnectionState(Enum): + DISCONNECTED = auto() + CONNECTED = auto() + UNKNOWN = auto() + + +@dataclass +class PieceConnectionState: + up: ConnectionState = ConnectionState.UNKNOWN + down: ConnectionState = ConnectionState.UNKNOWN + left: ConnectionState = ConnectionState.UNKNOWN + right: ConnectionState = ConnectionState.UNKNOWN + + def directions_with_state(self, state: ConnectionState) -> set[Direction]: + out: set[Direction] = set() + if self.up == state: + out.add(Direction.UP) + if self.down == state: + out.add(Direction.DOWN) + if self.left == state: + out.add(Direction.LEFT) + if self.right == state: + out.add(Direction.RIGHT) + return out + + def connected_or_unknown(self) -> set[Direction]: + """Return wert ist die Richtungen, deren State `CONNECTED` ist. + Falls es keine solche gibt, gibt es die Richtungen mit `UNKNOWN` zurück.""" + if connected := self.directions_with_state(ConnectionState.CONNECTED): + return connected + else: + return self.directions_with_state(ConnectionState.UNKNOWN) + + def set_direction(self, direction: Direction, state: ConnectionState) -> None: + if direction == Direction.UP: + self.up = state + elif direction == Direction.DOWN: + self.down = state + elif direction == Direction.LEFT: + self.left = state + elif direction == Direction.RIGHT: + self.right = state + + class WFCSolver: game: NetGame - observed: list[list[bool]] - possible_connections: list[list[set[Direction]]] + connection_states: list[list[PieceConnectionState]] def __init__(self, game: NetGame) -> None: self.game = game - self.observed = [[False] * self.game.width for _ in range(self.game.height)] - self.possible_connections = [ - [ - {Direction.UP, Direction.RIGHT, Direction.DOWN, Direction.LEFT} - for _ in range(self.game.width) - ] + self.connection_states = [ + [PieceConnectionState() for _ in range(self.game.width)] for _ in range(self.game.width) ] # Es kann nicht mit dem Rand des Feldes eine Verbindung bestehen - for column in self.possible_connections: - column[0].remove(Direction.UP) - column[-1].remove(Direction.DOWN) + for column in self.connection_states: + column[0].up = ConnectionState.DISCONNECTED + column[-1].down = ConnectionState.DISCONNECTED - for i in range(len(self.possible_connections[0])): - self.possible_connections[0][i].remove(Direction.LEFT) + for s in self.connection_states[0]: + s.left = ConnectionState.DISCONNECTED - for i in range(len(self.possible_connections[-1])): - self.possible_connections[-1][i].remove(Direction.RIGHT) + for s in self.connection_states[-1]: + s.right = ConnectionState.DISCONNECTED + + def get_possible_corner_directions(self, x: int, y: int) -> set[Direction]: + cstates = self.connection_states[x][y] + connected = cstates.connected_or_unknown() + possible = connected.copy() + possible.update(map(lambda d: Direction((d - 1) % 4), connected)) + disconnected = cstates.directions_with_state(ConnectionState.DISCONNECTED) + possible.difference_update(disconnected) + possible.difference_update(map(lambda d: Direction((d - 1) % 4), disconnected)) + return possible def get_possible_directions(self, x: int, y: int) -> set[Direction]: ptype = self.game.get_piece(x, y).type - dirs = self.possible_connections[x][y] + cstates = self.connection_states[x][y] + dirs = cstates.connected_or_unknown() if ptype == PieceType.NODE: return dirs elif ptype == PieceType.CORNER: - possible: set[Direction] = set() - if dirs.issuperset({Direction.UP, Direction.RIGHT}): - possible.add(Direction.UP) - if dirs.issuperset({Direction.RIGHT, Direction.DOWN}): - possible.add(Direction.RIGHT) - if dirs.issuperset({Direction.DOWN, Direction.LEFT}): - possible.add(Direction.DOWN) - if dirs.issuperset({Direction.LEFT, Direction.UP}): - possible.add(Direction.LEFT) - return possible + return self.get_possible_corner_directions(x, y) elif ptype == PieceType.STRAIGHT: - if len(dirs) == 4: - return dirs - elif {Direction.UP, Direction.DOWN}.issubset(dirs): - return {Direction.UP} - elif {Direction.LEFT, Direction.RIGHT}.issubset(dirs): - return {Direction.RIGHT} + connected = cstates.directions_with_state(ConnectionState.CONNECTED) + if connected: + return {connected.pop()} + disconnected = cstates.directions_with_state(ConnectionState.DISCONNECTED) + if disconnected: + return {Direction((disconnected.pop() + 1) % 4)} else: - return set() + return cstates.directions_with_state(ConnectionState.UNKNOWN) else: + disconnected = cstates.directions_with_state(ConnectionState.DISCONNECTED) if len(dirs) == 4: return dirs - elif len(dirs) == 3: - not_connected = ( - {Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT} - .difference(dirs) - .pop() - ) - return {Direction((not_connected + 2) % 4)} + elif disconnected: + return {disconnected.pop().flip()} else: - return set() + possible = { + Direction.UP, + Direction.DOWN, + Direction.LEFT, + Direction.RIGHT, + } + for d in dirs: + possible.intersection_update( + {d, Direction((d + 1) % 4), Direction((d - 1) % 4)} + ) + return possible def solve(self) -> Generator[None]: - coordinates = list( + unfixed = list( ((i, j) for i in range(self.game.width) for j in range(self.game.height)) ) while not self.game.solved(): yield - (x, y) = min( - coordinates, key=lambda t: len(self.get_possible_directions(*t)) - ) + (x, y) = min(unfixed, key=lambda t: len(self.get_possible_directions(*t))) if len(self.get_possible_directions(x, y)) == 0: raise NotImplementedError( "Irgendwo eine falsche Richtung gewählt, muss diese Logik noch schreiben" ) + if len(self.get_possible_directions(x, y)) > 1: + print("AAAAAAAA") direction = self.get_possible_directions(x, y).pop() self.game.set_direction(x, y, direction) self.game.lock(x, y) - coordinates.remove((x, y)) + unfixed.remove((x, y)) for nx, ny in self.game.neighbors(x, y): dx = nx - x dy = ny - y ndir = Direction.from_offset(dx, dy) if ndir not in self.game.get_piece(x, y).connected_directions(): - try: - self.possible_connections[nx][ny].remove( - Direction((ndir + 2) % 4) - ) - except KeyError: - pass - elif self.game.get_piece(nx, ny).type == PieceType.NODE: - self.possible_connections[nx][ny] = {Direction((ndir + 2) % 4)} + self.connection_states[nx][ny].set_direction( + ndir.flip(), ConnectionState.DISCONNECTED + ) + else: + self.connection_states[nx][ny].set_direction( + ndir.flip(), ConnectionState.CONNECTED + ) diff --git a/src/netTypes.py b/src/netTypes.py index 9197859..454db9b 100644 --- a/src/netTypes.py +++ b/src/netTypes.py @@ -25,6 +25,9 @@ class Direction(IntEnum): else: return Direction(dy + 1) + def flip(self) -> Direction: + return Direction((self + 2) % 4) + class Piece: type: PieceType