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

View File

@@ -1,13 +1,14 @@
import time import time
from multiprocessing import Pool from multiprocessing import Pool
from src.algorithms.wfc import WFCSolver
from src.algorithms.bruteforce import BruteForceSolver from src.algorithms.bruteforce import BruteForceSolver
from src.net import NetGame from src.net import NetGame
def test_run() -> float: def test_run(i: int) -> float:
game = NetGame(3, 3) game = NetGame(5, 5, i)
solver = BruteForceSolver(game) solver = WFCSolver(game)
a = time.perf_counter() a = time.perf_counter()
for _ in solver.solve(): for _ in solver.solve():
pass pass
@@ -18,7 +19,7 @@ def test_run() -> float:
if __name__ == "__main__": if __name__ == "__main__":
total = 0 total = 0
with Pool() as p: 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: for proc in processes:
total += proc.get() 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 from src.net import NetGame
@@ -36,13 +37,13 @@ class WFCSolver:
return dirs return dirs
elif ptype == PieceType.CORNER: elif ptype == PieceType.CORNER:
possible: set[Direction] = set() possible: set[Direction] = set()
if dirs.union({Direction.UP, Direction.RIGHT}): if dirs.issuperset({Direction.UP, Direction.RIGHT}):
possible.add(Direction.UP) possible.add(Direction.UP)
if dirs.union({Direction.RIGHT, Direction.DOWN}): if dirs.issuperset({Direction.RIGHT, Direction.DOWN}):
possible.add(Direction.RIGHT) possible.add(Direction.RIGHT)
if dirs.union({Direction.DOWN, Direction.LEFT}): if dirs.issuperset({Direction.DOWN, Direction.LEFT}):
possible.add(Direction.DOWN) possible.add(Direction.DOWN)
if dirs.union({Direction.LEFT, Direction.UP}): if dirs.issuperset({Direction.LEFT, Direction.UP}):
possible.add(Direction.LEFT) possible.add(Direction.LEFT)
return possible return possible
elif ptype == PieceType.STRAIGHT: elif ptype == PieceType.STRAIGHT:
@@ -67,56 +68,33 @@ class WFCSolver:
else: else:
return set() return set()
def get_possible_direction_count(self, x: int, y: int) -> int: def solve(self) -> Generator[None]:
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:
coordinates = list( coordinates = 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
(x, y) = min( (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( raise NotImplementedError(
"Irgendwo eine falsche Richtung gewählt, muss diese Logik noch schreiben" "Irgendwo eine falsche Richtung gewählt, muss diese Logik noch schreiben"
) )
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)
# 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 width: int
height: 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.height = height
self.width = width self.width = width
if width != height or width not in [3, 5, 7, 9, 11, 13]: if width != height or width not in [3, 5, 7, 9, 11, 13]:
raise ValueError("Feldgrösse nicht erlaubt") raise ValueError("Feldgrösse nicht erlaubt")
with open(f"descriptions/{width}x{height}.txt") as f: with open(f"descriptions/{width}x{height}.txt") as f:
lines = f.readlines() lines = f.readlines()
selected = choice(lines).strip() if specific:
selected = lines[specific].strip()
else:
selected = choice(lines).strip()
self.pieces = parse_description(selected) self.pieces = parse_description(selected)
def neighbors(self, x: int, y: int) -> list[Coordinate]: def neighbors(self, x: int, y: int) -> list[Coordinate]:
@@ -71,8 +74,8 @@ class NetGame:
width: int width: int
height: int height: int
def __init__(self, width: int, height: int) -> None: def __init__(self, width: int, height: int, specific: int | None = None) -> None:
self._grid = Grid(width, height) self._grid = Grid(width, height, specific)
self.width = width self.width = width
self.height = height self.height = height
@@ -94,5 +97,8 @@ class NetGame:
def set_direction(self, x: int, y: int, dir: Direction) -> None: def set_direction(self, x: int, y: int, dir: Direction) -> None:
self._grid.pieces[x][y].direction = dir 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): def solved(self):
return self._grid.solved() return self._grid.solved()