Compare commits
6 Commits
main
...
cf6036f269
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf6036f269 | ||
|
|
5a5283074a | ||
|
|
c3405118d7 | ||
|
|
d21764188d | ||
|
|
5fedf513fc | ||
|
|
a25bd700c9 |
BIN
assets/1.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/1on.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/2.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/2on.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/3.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/3on.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/4.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/4on.bmp
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
1000
descriptions/3x3.txt
1000
descriptions/5x5.txt
1000
descriptions/7x7.txt
1000
descriptions/9x9.txt
@@ -7,8 +7,3 @@ requires-python = ">=3.13"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"pygame>=2.6.1",
|
"pygame>=2.6.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
|
||||||
dev = [
|
|
||||||
"basedpyright>=1.39.6",
|
|
||||||
]
|
|
||||||
|
|||||||
77
src/GUI.py
@@ -1,17 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from src.algorithms.wfc import WFCSolver
|
|
||||||
from src.algorithms.bruteforce import BruteForceSolver
|
|
||||||
from src.net import NetGame
|
from src.net import NetGame
|
||||||
|
|
||||||
# pyright: reportUnusedCallResult=false, reportAny=false
|
# pyright: reportUnusedCallResult=false, reportAny=false
|
||||||
|
|
||||||
LEFT_TURN = pygame.USEREVENT + 1
|
LEFT_TURN = pygame.USEREVENT + 1
|
||||||
RIGHT_TURN = pygame.USEREVENT + 2
|
RIGHT_TURN = pygame.USEREVENT + 2
|
||||||
LOCK = pygame.USEREVENT + 3
|
|
||||||
|
|
||||||
|
|
||||||
class PieceSprite(pygame.sprite.Sprite):
|
class PieceSprite(pygame.sprite.Sprite):
|
||||||
@@ -32,14 +27,8 @@ 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(
|
image = pygame.image.load(os.path.join("assets", f"{piece.type}.bmp")).convert()
|
||||||
os.path.join("assets", f"{piece.type}.bmp")
|
image = pygame.transform.rotate(image, -90 * piece.direction)
|
||||||
).convert_alpha()
|
|
||||||
image = pygame.transform.rotate(image, -90 * int(piece.direction))
|
|
||||||
if piece.locked:
|
|
||||||
self.image.fill("#a0a0a0")
|
|
||||||
else:
|
|
||||||
self.image.fill("#ffffff")
|
|
||||||
self.image.blit(image, (0, 0))
|
self.image.blit(image, (0, 0))
|
||||||
for event in events:
|
for event in events:
|
||||||
if event.type == pygame.MOUSEBUTTONUP:
|
if event.type == pygame.MOUSEBUTTONUP:
|
||||||
@@ -49,10 +38,6 @@ class PieceSprite(pygame.sprite.Sprite):
|
|||||||
pygame.event.post(
|
pygame.event.post(
|
||||||
pygame.event.Event(LEFT_TURN, {"x": self.x, "y": self.y})
|
pygame.event.Event(LEFT_TURN, {"x": self.x, "y": self.y})
|
||||||
)
|
)
|
||||||
elif event.button == 2:
|
|
||||||
pygame.event.post(
|
|
||||||
pygame.event.Event(LOCK, {"x": self.x, "y": self.y})
|
|
||||||
)
|
|
||||||
elif event.button == 3:
|
elif event.button == 3:
|
||||||
pygame.event.post(
|
pygame.event.post(
|
||||||
pygame.event.Event(RIGHT_TURN, {"x": self.x, "y": self.y})
|
pygame.event.Event(RIGHT_TURN, {"x": self.x, "y": self.y})
|
||||||
@@ -62,34 +47,22 @@ class PieceSprite(pygame.sprite.Sprite):
|
|||||||
class NetGUI:
|
class NetGUI:
|
||||||
game: NetGame
|
game: NetGame
|
||||||
window: pygame.Surface
|
window: pygame.Surface
|
||||||
pieceSprites: pygame.sprite.Group[
|
pieceSprites: pygame.sprite.Group[PieceSprite]
|
||||||
PieceSprite # pyright: ignore[reportInvalidTypeArguments]
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, width: int, height: int):
|
def __init__(self):
|
||||||
self.game = NetGame(width, height)
|
self.game = NetGame(5, 5)
|
||||||
pygame.init()
|
pygame.init()
|
||||||
self.window = pygame.display.set_mode((width * 30, height * 30))
|
self.window = pygame.display.set_mode((5 * 30, 5 * 30))
|
||||||
self.window.fill("#ffffff")
|
self.window.fill("#ffffff")
|
||||||
pygame.display.set_caption("Net")
|
pygame.display.set_caption("Net")
|
||||||
|
|
||||||
self.pieceSprites = pygame.sprite.Group()
|
self.pieceSprites = pygame.sprite.Group()
|
||||||
for x in range(width):
|
for x in range(5):
|
||||||
for y in range(height):
|
for y in range(5):
|
||||||
self.pieceSprites.add(PieceSprite(x, y))
|
self.pieceSprites.add(PieceSprite(x, y))
|
||||||
|
|
||||||
def update_display(self, events: list[pygame.event.Event]):
|
|
||||||
self.pieceSprites.update(self.game, events)
|
|
||||||
for sprite in self.pieceSprites:
|
|
||||||
self.window.blit(sprite.image, sprite.rect)
|
|
||||||
|
|
||||||
pygame.display.flip()
|
|
||||||
|
|
||||||
def run_game(self):
|
def run_game(self):
|
||||||
current_solver = None
|
while True:
|
||||||
display_solver = False
|
|
||||||
step_solver = False
|
|
||||||
while not self.game.solved():
|
|
||||||
events = pygame.event.get()
|
events = pygame.event.get()
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
@@ -97,28 +70,18 @@ class NetGUI:
|
|||||||
raise SystemExit
|
raise SystemExit
|
||||||
elif event.type == LEFT_TURN:
|
elif event.type == LEFT_TURN:
|
||||||
self.game.turn_ccw(event.x, event.y)
|
self.game.turn_ccw(event.x, event.y)
|
||||||
|
if self.game.solved():
|
||||||
|
raise SystemExit
|
||||||
elif event.type == RIGHT_TURN:
|
elif event.type == RIGHT_TURN:
|
||||||
self.game.turn_cw(event.x, event.y)
|
self.game.turn_cw(event.x, event.y)
|
||||||
elif event.type == LOCK:
|
if self.game.solved():
|
||||||
self.game.lock(event.x, event.y)
|
raise SystemExit
|
||||||
elif event.type == pygame.KEYDOWN:
|
|
||||||
if event.key == pygame.K_b:
|
self.pieceSprites.update(self.game, events)
|
||||||
current_solver = BruteForceSolver(self.game).solve()
|
for sprite in self.pieceSprites:
|
||||||
elif event.key == pygame.K_d:
|
self.window.blit(sprite.image, sprite.rect)
|
||||||
display_solver = not display_solver
|
|
||||||
elif event.key == pygame.K_s:
|
pygame.display.flip()
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
NetGUI(5, 5).run_game()
|
NetGUI().run_game()
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
from collections.abc import Generator
|
|
||||||
from itertools import chain, pairwise
|
|
||||||
|
|
||||||
from src.net import NetGame
|
|
||||||
from src.netTypes import Direction
|
|
||||||
|
|
||||||
|
|
||||||
class BruteForceSolver:
|
|
||||||
game: NetGame
|
|
||||||
|
|
||||||
def __init__(self, game: NetGame) -> None:
|
|
||||||
self.game = game
|
|
||||||
for x in range(game.height):
|
|
||||||
for y in range(game.width):
|
|
||||||
self.game.set_direction(x, y, Direction.UP)
|
|
||||||
|
|
||||||
def solve(self) -> Generator[None]:
|
|
||||||
while not self.game.solved():
|
|
||||||
yield
|
|
||||||
self.game.turn_cw(0, 0)
|
|
||||||
for prev, curr in pairwise(
|
|
||||||
chain(
|
|
||||||
*self.game.get_field()
|
|
||||||
) # 2d-liste zu 1d machen, damit man einfacher über alle zellen iterieren kann
|
|
||||||
):
|
|
||||||
if prev.direction == Direction.UP:
|
|
||||||
curr.turn_cw()
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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(i: int) -> float:
|
|
||||||
game = NetGame(11, 11, i)
|
|
||||||
solver = WFCSolver(game)
|
|
||||||
a = time.perf_counter()
|
|
||||||
for _ in solver.solve():
|
|
||||||
pass
|
|
||||||
b = time.perf_counter()
|
|
||||||
return b - a
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
total = 0
|
|
||||||
with Pool() as p:
|
|
||||||
processes = [p.apply_async(test_run, (i,)) for i in range(1000)]
|
|
||||||
for proc in processes:
|
|
||||||
total += proc.get()
|
|
||||||
print(total / 1000)
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
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
|
|
||||||
connection_states: list[list[PieceConnectionState]]
|
|
||||||
|
|
||||||
def __init__(self, game: NetGame) -> None:
|
|
||||||
self.game = game
|
|
||||||
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.connection_states:
|
|
||||||
column[0].up = ConnectionState.DISCONNECTED
|
|
||||||
column[-1].down = ConnectionState.DISCONNECTED
|
|
||||||
|
|
||||||
for s in self.connection_states[0]:
|
|
||||||
s.left = ConnectionState.DISCONNECTED
|
|
||||||
|
|
||||||
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
|
|
||||||
cstates = self.connection_states[x][y]
|
|
||||||
dirs = cstates.connected_or_unknown()
|
|
||||||
if ptype == PieceType.NODE:
|
|
||||||
return dirs
|
|
||||||
elif ptype == PieceType.CORNER:
|
|
||||||
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 disconnected:
|
|
||||||
return {disconnected.pop().flip()}
|
|
||||||
else:
|
|
||||||
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 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]:
|
|
||||||
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(
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.game.set_direction(x, y, direction)
|
|
||||||
self.game.lock(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():
|
|
||||||
self.connection_states[nx][ny].set_direction(
|
|
||||||
ndir.flip(), ConnectionState.DISCONNECTED
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.connection_states[nx][ny].set_direction(
|
|
||||||
ndir.flip(), ConnectionState.CONNECTED
|
|
||||||
)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from src.netTypes import Piece, Direction, PieceType
|
from src.types import Piece, Direction, PieceType
|
||||||
|
|
||||||
|
|
||||||
# Tatham's board description format:
|
# Tatham's board description format:
|
||||||
|
|||||||
83
src/net.py
@@ -2,10 +2,49 @@
|
|||||||
Kernlogik des Spiels
|
Kernlogik des Spiels
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from random import choice
|
from src.types import Piece, Direction
|
||||||
|
|
||||||
from src.interface import parse_description
|
from src.interface import parse_description
|
||||||
from src.netTypes import Piece, Direction, Coordinate
|
|
||||||
|
|
||||||
|
# DEMO_FIELD = [
|
||||||
|
# [
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# ],
|
||||||
|
# [
|
||||||
|
# Piece(PieceType.STRAIGHT),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.STRAIGHT),
|
||||||
|
# Piece(PieceType.CORNER),
|
||||||
|
# ],
|
||||||
|
# [
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# ],
|
||||||
|
# [
|
||||||
|
# Piece(PieceType.STRAIGHT),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.T_JUNCTION),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# ],
|
||||||
|
# [
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.NODE),
|
||||||
|
# Piece(PieceType.CORNER),
|
||||||
|
# Piece(PieceType.CORNER),
|
||||||
|
# Piece(PieceType.CORNER),
|
||||||
|
# ],
|
||||||
|
# ]
|
||||||
|
|
||||||
|
DEMO_FIELD = parse_description("5x5:c7634887c213e5b8db3e69282")
|
||||||
|
|
||||||
|
|
||||||
class Grid:
|
class Grid:
|
||||||
@@ -13,19 +52,12 @@ class Grid:
|
|||||||
width: int
|
width: int
|
||||||
height: int
|
height: int
|
||||||
|
|
||||||
def __init__(self, width: int, height: int, specific: int | None = None) -> None:
|
def __init__(self, width: int, height: int) -> 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]:
|
self.pieces = (
|
||||||
raise ValueError("Feldgrösse nicht erlaubt")
|
DEMO_FIELD # TODO: Field generation or import from Tatham's version
|
||||||
with open(f"descriptions/{width}x{height}.txt") as f:
|
)
|
||||||
lines = f.readlines()
|
|
||||||
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]:
|
def neighbors(self, x: int, y: int) -> list[Coordinate]:
|
||||||
neighbors: list[Coordinate] = []
|
neighbors: list[Coordinate] = []
|
||||||
@@ -50,7 +82,7 @@ class Grid:
|
|||||||
) -> list[list[bool]]:
|
) -> list[list[bool]]:
|
||||||
connected[x][y] = True
|
connected[x][y] = True
|
||||||
|
|
||||||
connected_neighbors: list[Coordinate] = []
|
connectedNeighbors: list[Coordinate] = []
|
||||||
for nx, ny in self.neighbors(x, y):
|
for nx, ny in self.neighbors(x, y):
|
||||||
dx = x - nx
|
dx = x - nx
|
||||||
dy = y - ny
|
dy = y - ny
|
||||||
@@ -60,9 +92,9 @@ class Grid:
|
|||||||
and Direction.from_offset(-dx, -dy)
|
and Direction.from_offset(-dx, -dy)
|
||||||
in self.pieces[x][y].connected_directions()
|
in self.pieces[x][y].connected_directions()
|
||||||
):
|
):
|
||||||
connected_neighbors.append((nx, ny))
|
connectedNeighbors.append((nx, ny))
|
||||||
|
|
||||||
for nx, ny in connected_neighbors:
|
for nx, ny in connectedNeighbors:
|
||||||
if connected[nx][ny]:
|
if connected[nx][ny]:
|
||||||
continue
|
continue
|
||||||
connected = self._solve_floodfill(nx, ny, connected)
|
connected = self._solve_floodfill(nx, ny, connected)
|
||||||
@@ -72,13 +104,9 @@ class Grid:
|
|||||||
|
|
||||||
class NetGame:
|
class NetGame:
|
||||||
_grid: Grid
|
_grid: Grid
|
||||||
width: int
|
|
||||||
height: int
|
|
||||||
|
|
||||||
def __init__(self, width: int, height: int, specific: int | None = None) -> None:
|
def __init__(self, width: int, height: int) -> None:
|
||||||
self._grid = Grid(width, height, specific)
|
self._grid = Grid(width, height)
|
||||||
self.width = width
|
|
||||||
self.height = height
|
|
||||||
|
|
||||||
def get_field(self) -> list[list[Piece]]:
|
def get_field(self) -> list[list[Piece]]:
|
||||||
return self._grid.pieces
|
return self._grid.pieces
|
||||||
@@ -92,14 +120,5 @@ class NetGame:
|
|||||||
def turn_ccw(self, x: int, y: int) -> None:
|
def turn_ccw(self, x: int, y: int) -> None:
|
||||||
self._grid.pieces[x][y].turn_ccw()
|
self._grid.pieces[x][y].turn_ccw()
|
||||||
|
|
||||||
def lock(self, x: int, y: int) -> None:
|
|
||||||
self._grid.pieces[x][y].locked = not self._grid.pieces[x][y].locked
|
|
||||||
|
|
||||||
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):
|
def solved(self):
|
||||||
return self._grid.solved()
|
return self._grid.solved()
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ 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
|
||||||
@@ -54,11 +51,7 @@ class Piece:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def turn_cw(self) -> None:
|
def turn_cw(self) -> None:
|
||||||
if self.locked:
|
|
||||||
return
|
|
||||||
self.direction = Direction((self.direction + 1) % 4)
|
self.direction = Direction((self.direction + 1) % 4)
|
||||||
|
|
||||||
def turn_ccw(self) -> None:
|
def turn_ccw(self) -> None:
|
||||||
if self.locked:
|
|
||||||
return
|
|
||||||
self.direction = Direction((self.direction - 1) % 4)
|
self.direction = Direction((self.direction - 1) % 4)
|
||||||
36
uv.lock
generated
@@ -2,18 +2,6 @@ version = 1
|
|||||||
revision = 3
|
revision = 3
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "basedpyright"
|
|
||||||
version = "1.39.6"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "nodejs-wheel-binaries" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/1a/48296b4479ccc9051eb9617a6507a69a68f5b68693fb6a118cfe08199270/basedpyright-1.39.6.tar.gz", hash = "sha256:d00ec5f8ba4e1a67dfc2fa3a9474229c89f61f207d14c02d320db78f57aa16ef", size = 25504244, upload-time = "2026-05-24T07:44:41.864Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/07/6d1b3192715d42e8c9887876684a941eff28ec5d79c23a0f3758377e2182/basedpyright-1.39.6-py3-none-any.whl", hash = "sha256:5e0b9befbae6b26d0fbcc6645ac26923725e749d1224539e24f05ab07f9365ad", size = 13182122, upload-time = "2026-05-24T07:44:47.086Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maturaarbeit"
|
name = "maturaarbeit"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -22,33 +10,9 @@ dependencies = [
|
|||||||
{ name = "pygame" },
|
{ name = "pygame" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "basedpyright" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [{ name = "pygame", specifier = ">=2.6.1" }]
|
requires-dist = [{ name = "pygame", specifier = ">=2.6.1" }]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
|
||||||
dev = [{ name = "basedpyright", specifier = ">=1.39.6" }]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nodejs-wheel-binaries"
|
|
||||||
version = "24.16.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/22/2a5beb4e21417c73233d9f65cf6f3e96e891b80d2f550a8f630ebc6b88c6/nodejs_wheel_binaries-24.16.0.tar.gz", hash = "sha256:c973cb69dc5fd16e6f6dc6e579e2c3d5534e2a1f57619dddf5ba070efa7dde37", size = 8056, upload-time = "2026-05-30T16:52:09.807Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/d1/68b43b53cd0fa83ae6fd406705023ca988d9e0ca41c724d82e66fbeb2ef6/nodejs_wheel_binaries-24.16.0-py2.py3-none-macosx_13_0_arm64.whl", hash = "sha256:d9f8f677dcf30e37ac244f07869726abe043f01eb0f45722b1df31cc2af7093c", size = 55666374, upload-time = "2026-05-30T16:51:39.588Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e9/b2/40a989159599080da485de966c4c2d207e852ac7aa7864702626d96c8bf5/nodejs_wheel_binaries-24.16.0-py2.py3-none-macosx_13_0_x86_64.whl", hash = "sha256:3d0370fe7120ce9697a4f60d40480d2bd8808d9f30131458d5afc0040d4e5a51", size = 55838487, upload-time = "2026-05-30T16:51:43.383Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d7/a7/cd42174fb5ff6faff7fa8d326a18914d8f232098ab5de055b57c16fa13ca/nodejs_wheel_binaries-24.16.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:85dc92bbb79c851569c5925dcc2a4c915a034efab375f99e4e7e6bbe9cca8342", size = 60179540, upload-time = "2026-05-30T16:51:47.036Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2b/95/c8a1f9ae140aa28df8744d984d01d4b3af7cdd6555af12127f40ceb45a7d/nodejs_wheel_binaries-24.16.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:2f3036292811514ba847b3708492644764f88a833ac425c5f55007014308ddfd", size = 60716262, upload-time = "2026-05-30T16:51:50.711Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/c9/7c35b3737f59e36d0249c265397b7bff570519b95301d6e16ea361e904ad/nodejs_wheel_binaries-24.16.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:db8a8a76ebd2b28ecbfc9ad464baa3707241b9e050a30e2efdf6f60c0f886502", size = 62230592, upload-time = "2026-05-30T16:51:55Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/96/d931255cf9d11a84d6b54d882dba7434646467d568ccf070ea3418638df3/nodejs_wheel_binaries-24.16.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f1a3d8f7b4491cbbd023ba3fc4e901fcca2d9fb80d57f24ba3890de8b1dbac03", size = 62841759, upload-time = "2026-05-30T16:51:59.407Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/7b/8b7a3f41bc255411be30b6d7d288aab8ffd9ea2055db8555ced3548007b9/nodejs_wheel_binaries-24.16.0-py2.py3-none-win_amd64.whl", hash = "sha256:bb136be9944f0662dcf1120f45193a6b75b13fac378971a95cc42c9f879a81aa", size = 42027734, upload-time = "2026-05-30T16:52:03.348Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/17/66/1ed71f1f529b8ca727d42c7ceb9db0bef145ce4a13dfc86fb50aa44f3be6/nodejs_wheel_binaries-24.16.0-py2.py3-none-win_arm64.whl", hash = "sha256:8308940b5edd0a50dc5267ea36ba21c9f668e83fe0d9f293937174d3a7e31c36", size = 39714528, upload-time = "2026-05-30T16:52:06.421Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygame"
|
name = "pygame"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|||||||