Compare commits

...

2 Commits

Author SHA1 Message Date
Alfred Baumann
d39e6ecc25 simples wfc, funktioniert noch nicht immer 2026-06-10 14:41:34 +02:00
Alfred Baumann
62864b7c00 anzeigen von locked felder flicken 2026-06-10 14:41:00 +02:00
12 changed files with 54 additions and 60 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -4,6 +4,7 @@ from typing import override
import pygame
from src.algorithms.wfc import WFCSolver
from src.algorithms.bruteforce import BruteForceSolver
from src.net import NetGame
@@ -32,8 +33,9 @@ class PieceSprite(pygame.sprite.Sprite):
@override
def update(self, game: NetGame, events: list[pygame.event.Event]):
piece = game.get_piece(self.x, self.y)
image = pygame.image.load(os.path.join("assets", f"{piece.type}.bmp")).convert()
image.set_colorkey("#ffffff")
image = pygame.image.load(
os.path.join("assets", f"{piece.type}.bmp")
).convert_alpha()
image = pygame.transform.rotate(image, -90 * int(piece.direction))
if piece.locked:
self.image.fill("#a0a0a0")
@@ -87,6 +89,7 @@ class NetGUI:
def run_game(self):
current_solver = None
display_solver = False
step_solver = False
while not self.game.solved():
events = pygame.event.get()
@@ -102,16 +105,22 @@ class NetGUI:
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_b:
current_solver = BruteForceSolver(self.game).solve()
if event.key == pygame.K_d:
elif event.key == pygame.K_d:
display_solver = not display_solver
elif event.key == pygame.K_s:
step_solver = not step_solver
elif event.key == pygame.K_w:
current_solver = WFCSolver(self.game).solve()
if current_solver:
try:
_ = next(current_solver)
except StopIteration:
current_solver = None
if step_solver:
_ = input()
if (not current_solver) or display_solver:
self.update_display(events)
sleep(2)
NetGUI(3, 3).run_game()
NetGUI(5, 5).run_game()

View File

@@ -1,13 +1,14 @@
import time
from multiprocessing import Pool
from src.algorithms.wfc import WFCSolver
from src.algorithms.bruteforce import BruteForceSolver
from src.net import NetGame
def test_run() -> float:
game = NetGame(3, 3)
solver = BruteForceSolver(game)
def test_run(i: int) -> float:
game = NetGame(5, 5, i)
solver = WFCSolver(game)
a = time.perf_counter()
for _ in solver.solve():
pass
@@ -18,7 +19,7 @@ def test_run() -> float:
if __name__ == "__main__":
total = 0
with Pool() as p:
processes = [p.apply_async(test_run) for _ in range(100)]
processes = [p.apply_async(test_run, (i,)) for i in range(1000)]
for proc in processes:
total += proc.get()
print(total)
print(total / 1000)

View File

@@ -1,4 +1,5 @@
from netTypes import Direction, PieceType
from collections.abc import Generator
from src.netTypes import Direction, PieceType
from src.net import NetGame
@@ -36,13 +37,13 @@ class WFCSolver:
return dirs
elif ptype == PieceType.CORNER:
possible: set[Direction] = set()
if dirs.union({Direction.UP, Direction.RIGHT}):
if dirs.issuperset({Direction.UP, Direction.RIGHT}):
possible.add(Direction.UP)
if dirs.union({Direction.RIGHT, Direction.DOWN}):
if dirs.issuperset({Direction.RIGHT, Direction.DOWN}):
possible.add(Direction.RIGHT)
if dirs.union({Direction.DOWN, Direction.LEFT}):
if dirs.issuperset({Direction.DOWN, Direction.LEFT}):
possible.add(Direction.DOWN)
if dirs.union({Direction.LEFT, Direction.UP}):
if dirs.issuperset({Direction.LEFT, Direction.UP}):
possible.add(Direction.LEFT)
return possible
elif ptype == PieceType.STRAIGHT:
@@ -67,56 +68,33 @@ class WFCSolver:
else:
return set()
def get_possible_direction_count(self, x: int, y: int) -> int:
ptype = self.game.get_piece(x, y).type
connectable = self.possible_connections[x][y]
camount = len(connectable)
if camount == 4:
return 4
if ptype == PieceType.NODE:
return camount
elif ptype == PieceType.CORNER:
if camount == 3:
return 2
elif (
camount == 2
and connectable != {Direction.UP, Direction.DOWN}
and connectable != {Direction.LEFT, Direction.RIGHT}
): # 2 nicht-gegenüberliegende seiten
return 1
else:
return 0
elif ptype == PieceType.STRAIGHT:
if camount == 4:
return 4
elif (
camount == 3
or connectable == {Direction.UP, Direction.DOWN}
or connectable == {Direction.LEFT, Direction.RIGHT}
):
return 1
else:
return 0
else: # T-JUNCTION
if camount == 4:
return 4
elif camount == 3:
return 1
else:
return 0
def solve(self) -> None:
def solve(self) -> Generator[None]:
coordinates = 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: self.get_possible_direction_count(*t)
coordinates, key=lambda t: len(self.get_possible_directions(*t))
)
if self.get_possible_direction_count(x, y) == 0:
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)
# TODO: Nachbaren updaten
self.game.lock(x, y)
coordinates.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)}

View File

@@ -13,14 +13,17 @@ class Grid:
width: int
height: int
def __init__(self, width: int, height: int) -> None:
def __init__(self, width: int, height: int, specific: int | None = None) -> None:
self.height = height
self.width = width
if width != height or width not in [3, 5, 7, 9, 11, 13]:
raise ValueError("Feldgrösse nicht erlaubt")
with open(f"descriptions/{width}x{height}.txt") as f:
lines = f.readlines()
selected = choice(lines).strip()
if specific:
selected = lines[specific].strip()
else:
selected = choice(lines).strip()
self.pieces = parse_description(selected)
def neighbors(self, x: int, y: int) -> list[Coordinate]:
@@ -71,8 +74,8 @@ class NetGame:
width: int
height: int
def __init__(self, width: int, height: int) -> None:
self._grid = Grid(width, height)
def __init__(self, width: int, height: int, specific: int | None = None) -> None:
self._grid = Grid(width, height, specific)
self.width = width
self.height = height
@@ -94,5 +97,8 @@ class NetGame:
def set_direction(self, x: int, y: int, dir: Direction) -> None:
self._grid.pieces[x][y].direction = dir
def neighbors(self, x: int, y: int) -> list[Coordinate]:
return self._grid.neighbors(x, y)
def solved(self):
return self._grid.solved()