Compare commits
2 Commits
d39e6ecc25
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4ef3c7555 | ||
|
|
8139af00d5 |
@@ -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()
|
||||
|
||||
@@ -7,7 +7,7 @@ from src.net import NetGame
|
||||
|
||||
|
||||
def test_run(i: int) -> float:
|
||||
game = NetGame(5, 5, i)
|
||||
game = NetGame(11, 11, i)
|
||||
solver = WFCSolver(game)
|
||||
a = time.perf_counter()
|
||||
for _ in solver.solve():
|
||||
|
||||
@@ -1,100 +1,191 @@
|
||||
from collections.abc import Generator
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from src.netTypes import Direction, PieceType
|
||||
from src.net import NetGame
|
||||
|
||||
type RollbackType = list[
|
||||
tuple[
|
||||
list[tuple[int, int]],
|
||||
list[list[Direction]],
|
||||
set[Direction],
|
||||
int,
|
||||
int,
|
||||
list[list[PieceConnectionState]],
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
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.directions_with_state(ConnectionState.CONNECTED)
|
||||
if connected:
|
||||
possible = {Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT}
|
||||
for direction in connected:
|
||||
possible.intersection_update(
|
||||
{direction, Direction((direction - 1) % 4)}
|
||||
)
|
||||
else:
|
||||
possible = cstates.directions_with_state(ConnectionState.UNKNOWN)
|
||||
possible.update(map(lambda d: Direction((d - 1) % 4), possible.copy()))
|
||||
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:
|
||||
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 cstates.directions_with_state(ConnectionState.UNKNOWN)
|
||||
else:
|
||||
disconnected = cstates.directions_with_state(ConnectionState.DISCONNECTED)
|
||||
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}
|
||||
elif disconnected:
|
||||
return {disconnected.pop().flip()}
|
||||
else:
|
||||
return set()
|
||||
else:
|
||||
if len(dirs) == 4:
|
||||
return dirs
|
||||
elif len(dirs) == 3:
|
||||
not_connected = (
|
||||
{Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT}
|
||||
.difference(dirs)
|
||||
.pop()
|
||||
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 {Direction((not_connected + 2) % 4)}
|
||||
else:
|
||||
return set()
|
||||
return possible
|
||||
|
||||
def get_field_rotations(self) -> list[list[Direction]]:
|
||||
return [[p.direction for p in col] for col in self.game.get_field()]
|
||||
|
||||
def set_field_rotations(self, rotations: list[list[Direction]]):
|
||||
for x, col in enumerate(rotations):
|
||||
for y, d in enumerate(col):
|
||||
self.game.set_direction(x, y, d)
|
||||
|
||||
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))
|
||||
)
|
||||
rollback: RollbackType = []
|
||||
while not self.game.solved():
|
||||
yield
|
||||
if len(unfixed) == 0:
|
||||
unfixed, rotations, dirs, x, y, self.connection_states = rollback.pop()
|
||||
self.set_field_rotations(rotations)
|
||||
else:
|
||||
(x, y) = min(
|
||||
coordinates, key=lambda t: len(self.get_possible_directions(*t))
|
||||
unfixed, key=lambda t: len(self.get_possible_directions(*t))
|
||||
)
|
||||
dirs = self.get_possible_directions(x, y)
|
||||
if len(dirs) == 0:
|
||||
unfixed, rotations, dirs, x, y, self.connection_states = rollback.pop()
|
||||
self.set_field_rotations(rotations)
|
||||
direction = dirs.pop()
|
||||
if len(dirs) > 0:
|
||||
rollback.append(
|
||||
(
|
||||
unfixed.copy(),
|
||||
self.get_field_rotations(),
|
||||
dirs,
|
||||
x,
|
||||
y,
|
||||
deepcopy(self.connection_states),
|
||||
)
|
||||
if len(self.get_possible_directions(x, y)) == 0:
|
||||
raise NotImplementedError(
|
||||
"Irgendwo eine falsche Richtung gewählt, muss diese Logik noch schreiben"
|
||||
)
|
||||
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)
|
||||
self.connection_states[nx][ny].set_direction(
|
||||
ndir.flip(), ConnectionState.DISCONNECTED
|
||||
)
|
||||
else:
|
||||
self.connection_states[nx][ny].set_direction(
|
||||
ndir.flip(), ConnectionState.CONNECTED
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
elif self.game.get_piece(nx, ny).type == PieceType.NODE:
|
||||
self.possible_connections[nx][ny] = {Direction((ndir + 2) % 4)}
|
||||
|
||||
@@ -20,10 +20,11 @@ class Grid:
|
||||
raise ValueError("Feldgrösse nicht erlaubt")
|
||||
with open(f"descriptions/{width}x{height}.txt") as f:
|
||||
lines = f.readlines()
|
||||
if specific:
|
||||
if specific is not None:
|
||||
selected = lines[specific].strip()
|
||||
else:
|
||||
selected = choice(lines).strip()
|
||||
print(f"Seed: {lines.index(selected + "\n")}")
|
||||
self.pieces = parse_description(selected)
|
||||
|
||||
def neighbors(self, x: int, y: int) -> list[Coordinate]:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user