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
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()

View File

@@ -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
)

View File

@@ -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