Anfang kompliziertere Version vom wfc

This commit is contained in:
Alfred Baumann
2026-06-10 17:19:22 +02:00
parent d39e6ecc25
commit 8139af00d5
3 changed files with 105 additions and 56 deletions

View File

@@ -1,5 +1,4 @@
import os import os
from time import sleep
from typing import override from typing import override
import pygame import pygame
@@ -120,7 +119,6 @@ class NetGUI:
_ = input() _ = input()
if (not current_solver) or display_solver: if (not current_solver) or display_solver:
self.update_display(events) self.update_display(events)
sleep(2)
NetGUI(5, 5).run_game() NetGUI(5, 5).run_game()

View File

@@ -1,100 +1,148 @@
from collections.abc import Generator from collections.abc import Generator
from dataclasses import dataclass
from enum import Enum, auto
from src.netTypes import Direction, PieceType from src.netTypes import Direction, PieceType
from src.net import NetGame 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: class WFCSolver:
game: NetGame game: NetGame
observed: list[list[bool]] connection_states: list[list[PieceConnectionState]]
possible_connections: list[list[set[Direction]]]
def __init__(self, game: NetGame) -> None: def __init__(self, game: NetGame) -> None:
self.game = game self.game = game
self.observed = [[False] * self.game.width for _ in range(self.game.height)] self.connection_states = [
self.possible_connections = [ [PieceConnectionState() for _ in range(self.game.width)]
[
{Direction.UP, Direction.RIGHT, Direction.DOWN, Direction.LEFT}
for _ in range(self.game.width)
]
for _ in range(self.game.width) for _ in range(self.game.width)
] ]
# Es kann nicht mit dem Rand des Feldes eine Verbindung bestehen # Es kann nicht mit dem Rand des Feldes eine Verbindung bestehen
for column in self.possible_connections: for column in self.connection_states:
column[0].remove(Direction.UP) column[0].up = ConnectionState.DISCONNECTED
column[-1].remove(Direction.DOWN) column[-1].down = ConnectionState.DISCONNECTED
for i in range(len(self.possible_connections[0])): for s in self.connection_states[0]:
self.possible_connections[0][i].remove(Direction.LEFT) s.left = ConnectionState.DISCONNECTED
for i in range(len(self.possible_connections[-1])): for s in self.connection_states[-1]:
self.possible_connections[-1][i].remove(Direction.RIGHT) 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]: def get_possible_directions(self, x: int, y: int) -> set[Direction]:
ptype = self.game.get_piece(x, y).type 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: if ptype == PieceType.NODE:
return dirs return dirs
elif ptype == PieceType.CORNER: elif ptype == PieceType.CORNER:
possible: set[Direction] = set() return self.get_possible_corner_directions(x, y)
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
elif ptype == PieceType.STRAIGHT: elif ptype == PieceType.STRAIGHT:
if len(dirs) == 4: connected = cstates.directions_with_state(ConnectionState.CONNECTED)
return dirs if connected:
elif {Direction.UP, Direction.DOWN}.issubset(dirs): return {connected.pop()}
return {Direction.UP} disconnected = cstates.directions_with_state(ConnectionState.DISCONNECTED)
elif {Direction.LEFT, Direction.RIGHT}.issubset(dirs): if disconnected:
return {Direction.RIGHT} return {Direction((disconnected.pop() + 1) % 4)}
else: else:
return set() return cstates.directions_with_state(ConnectionState.UNKNOWN)
else: else:
disconnected = cstates.directions_with_state(ConnectionState.DISCONNECTED)
if len(dirs) == 4: if len(dirs) == 4:
return dirs return dirs
elif len(dirs) == 3: elif disconnected:
not_connected = ( return {disconnected.pop().flip()}
{Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT}
.difference(dirs)
.pop()
)
return {Direction((not_connected + 2) % 4)}
else: 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]: 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)) ((i, j) for i in range(self.game.width) for j in range(self.game.height))
) )
while not self.game.solved(): while not self.game.solved():
yield yield
(x, y) = min( (x, y) = min(unfixed, key=lambda t: len(self.get_possible_directions(*t)))
coordinates, key=lambda t: len(self.get_possible_directions(*t))
)
if len(self.get_possible_directions(x, y)) == 0: if len(self.get_possible_directions(x, y)) == 0:
raise NotImplementedError( raise NotImplementedError(
"Irgendwo eine falsche Richtung gewählt, muss diese Logik noch schreiben" "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() direction = self.get_possible_directions(x, y).pop()
self.game.set_direction(x, y, direction) self.game.set_direction(x, y, direction)
self.game.lock(x, y) self.game.lock(x, y)
coordinates.remove((x, y)) unfixed.remove((x, y))
for nx, ny in self.game.neighbors(x, y): for nx, ny in self.game.neighbors(x, y):
dx = nx - x dx = nx - x
dy = ny - y dy = ny - y
ndir = Direction.from_offset(dx, dy) ndir = Direction.from_offset(dx, dy)
if ndir not in self.game.get_piece(x, y).connected_directions(): if ndir not in self.game.get_piece(x, y).connected_directions():
try: self.connection_states[nx][ny].set_direction(
self.possible_connections[nx][ny].remove( ndir.flip(), ConnectionState.DISCONNECTED
Direction((ndir + 2) % 4) )
) else:
except KeyError: self.connection_states[nx][ny].set_direction(
pass ndir.flip(), ConnectionState.CONNECTED
elif self.game.get_piece(nx, ny).type == PieceType.NODE: )
self.possible_connections[nx][ny] = {Direction((ndir + 2) % 4)}

View File

@@ -25,6 +25,9 @@ class Direction(IntEnum):
else: else:
return Direction(dy + 1) return Direction(dy + 1)
def flip(self) -> Direction:
return Direction((self + 2) % 4)
class Piece: class Piece:
type: PieceType type: PieceType