parser für die Descriptions, die Tatham's version generiert

This commit is contained in:
Alfred Baumann
2026-05-15 21:27:13 +02:00
parent a7fa8ca9c3
commit d49493b778
3 changed files with 161 additions and 92 deletions

64
src/interface.py Normal file
View File

@@ -0,0 +1,64 @@
from src.types import Piece, Direction, PieceType
# Tatham's board description format:
# {width}x{height}:{board}
# the board is a string of single-digit hex numbers
# where each number is a bitfield of which sides the piece is connected to
RIGHT = 0x1
UP = 0x2
LEFT = 0x4
DOWN = 0x8
def flip_direction(d: int) -> int:
return (d >> 2) | ((d << 2) & 0xC)
def direction_from_tatham(d: int) -> Direction:
if d == RIGHT:
return Direction.RIGHT
elif d == UP:
return Direction.UP
elif d == LEFT:
return Direction.LEFT
else:
return Direction.DOWN
def corner_direction_from_sides(sides: int) -> Direction:
if sides == 0x3:
return Direction.UP
elif sides == 0x6:
return Direction.LEFT
elif sides == 0xC:
return Direction.DOWN
else:
return Direction.RIGHT
def parse_description(desc: str) -> list[list[Piece]]:
dimensions, field = desc.split(":")
width, height = map(int, dimensions.split("x"))
parsed: list[list[Piece]] = []
for y in range(height):
line: list[Piece] = []
for x in range(width):
value = int(field[x * width + y], 16)
connected_sides = value.bit_count()
if connected_sides == 1:
ptype = PieceType.NODE
direction = direction_from_tatham(value)
elif connected_sides == 2:
if value == 0x5 or value >> 1 == 0x5:
ptype = PieceType.STRAIGHT
direction = direction_from_tatham(value & 0x3)
else:
ptype = PieceType.CORNER
direction = corner_direction_from_sides(value)
else:
ptype = PieceType.T_JUNCTION
direction = direction_from_tatham(flip_direction(~value & 0xF))
line.append(Piece(ptype, direction))
parsed.append(line)
return parsed

View File

@@ -2,101 +2,49 @@
Kernlogik des Spiels
"""
from enum import IntEnum, auto
type Coordinate = tuple[int, int]
from src.types import Piece, Direction
from src.interface import parse_description
class PieceType(IntEnum):
T_JUNCTION = auto() # Direction is the piece at the "stem" of the T
STRAIGHT = auto()
CORNER = auto() # Direction is the more counter-clockwise connection
NODE = auto()
# 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),
# ],
# ]
class Direction(IntEnum):
UP = 0
RIGHT = auto()
DOWN = auto()
LEFT = auto()
@staticmethod
def from_offset(dx: int, dy: int) -> Direction:
if (dx != 0 and dy != 0) or abs(dx + dy) != 1:
raise ValueError(f"({dx}, {dy}) is not a valid direction offset")
if dx != 0:
return Direction(dx % 4)
else:
return Direction(dy + 1)
class Piece:
type: PieceType
direction: Direction = Direction.UP
locked: bool = False
def __init__(self, type: PieceType) -> None:
self.type = type
def connected_directions(self) -> list[Direction]:
match self.type:
case PieceType.NODE:
return [self.direction]
case PieceType.CORNER:
return [self.direction, Direction((self.direction + 1) % 4)]
case PieceType.STRAIGHT:
return [self.direction, Direction((self.direction + 2) % 4)]
case PieceType.T_JUNCTION:
return [
self.direction,
Direction((self.direction + 1) % 4),
Direction((self.direction - 1) % 4),
]
def turn_cw(self) -> None:
self.direction = Direction((self.direction + 1) % 4)
def turn_ccw(self) -> None:
self.direction = Direction((self.direction - 1) % 4)
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:

57
src/types.py Normal file
View File

@@ -0,0 +1,57 @@
from enum import IntEnum, auto
type Coordinate = tuple[int, int]
class PieceType(IntEnum):
T_JUNCTION = auto() # Direction is the piece at the "stem" of the T
STRAIGHT = auto()
CORNER = auto() # Direction is the more counter-clockwise connection
NODE = auto()
class Direction(IntEnum):
UP = 0
RIGHT = auto()
DOWN = auto()
LEFT = auto()
@staticmethod
def from_offset(dx: int, dy: int) -> Direction:
if (dx != 0 and dy != 0) or abs(dx + dy) != 1:
raise ValueError(f"({dx}, {dy}) is not a valid direction offset")
if dx != 0:
return Direction(dx % 4)
else:
return Direction(dy + 1)
class Piece:
type: PieceType
direction: Direction
locked: bool = False
def __init__(self, type: PieceType, direction: Direction = Direction.UP) -> None:
self.type = type
self.direction = direction
def connected_directions(self) -> list[Direction]:
match self.type:
case PieceType.NODE:
return [self.direction]
case PieceType.CORNER:
return [self.direction, Direction((self.direction + 1) % 4)]
case PieceType.STRAIGHT:
return [self.direction, Direction((self.direction + 2) % 4)]
case PieceType.T_JUNCTION:
return [
self.direction,
Direction((self.direction + 1) % 4),
Direction((self.direction - 1) % 4),
]
def turn_cw(self) -> None:
self.direction = Direction((self.direction + 1) % 4)
def turn_ccw(self) -> None:
self.direction = Direction((self.direction - 1) % 4)