Compare commits
6 Commits
d49493b778
...
cf6036f269
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf6036f269 | ||
|
|
5a5283074a | ||
|
|
c3405118d7 | ||
|
|
d21764188d | ||
|
|
5fedf513fc | ||
|
|
a25bd700c9 |
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.envrc
|
||||||
|
.direnv
|
||||||
|
__pycache__/
|
||||||
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.14
|
||||||
BIN
assets/1.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/1on.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/2.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/2on.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/3.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/3on.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/4.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/4on.bmp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777954456,
|
||||||
|
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
33
flake.nix
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {nixpkgs, ...}: let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs {inherit system;};
|
||||||
|
in {
|
||||||
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
|
name = "maturaarbeit";
|
||||||
|
|
||||||
|
packages = with pkgs; [
|
||||||
|
uv
|
||||||
|
python314
|
||||||
|
|
||||||
|
SDL2
|
||||||
|
SDL2_image
|
||||||
|
SDL2_mixer
|
||||||
|
SDL2_ttf
|
||||||
|
|
||||||
|
libX11
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
uv sync
|
||||||
|
. .venv/bin/activate
|
||||||
|
'';
|
||||||
|
|
||||||
|
SDL_VIDEODRIVER = "wayland";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
9
pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "maturaarbeit"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Maturaarbeit Alfred Baumann"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"pygame>=2.6.1",
|
||||||
|
]
|
||||||
87
src/GUI.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import os
|
||||||
|
from typing import override
|
||||||
|
import pygame
|
||||||
|
from src.net import NetGame
|
||||||
|
|
||||||
|
# pyright: reportUnusedCallResult=false, reportAny=false
|
||||||
|
|
||||||
|
LEFT_TURN = pygame.USEREVENT + 1
|
||||||
|
RIGHT_TURN = pygame.USEREVENT + 2
|
||||||
|
|
||||||
|
|
||||||
|
class PieceSprite(pygame.sprite.Sprite):
|
||||||
|
image: pygame.Surface
|
||||||
|
rect: pygame.Rect
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
def __init__(self, x: int, y: int) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.image = pygame.Surface((28, 28))
|
||||||
|
self.rect = self.image.get_rect()
|
||||||
|
self.rect.x = 30 * x + 1
|
||||||
|
self.rect.y = 30 * y + 1
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
@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 = pygame.transform.rotate(image, -90 * piece.direction)
|
||||||
|
self.image.blit(image, (0, 0))
|
||||||
|
for event in events:
|
||||||
|
if event.type == pygame.MOUSEBUTTONUP:
|
||||||
|
if not self.rect.collidepoint(*event.pos):
|
||||||
|
continue
|
||||||
|
if event.button == 1:
|
||||||
|
pygame.event.post(
|
||||||
|
pygame.event.Event(LEFT_TURN, {"x": self.x, "y": self.y})
|
||||||
|
)
|
||||||
|
elif event.button == 3:
|
||||||
|
pygame.event.post(
|
||||||
|
pygame.event.Event(RIGHT_TURN, {"x": self.x, "y": self.y})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NetGUI:
|
||||||
|
game: NetGame
|
||||||
|
window: pygame.Surface
|
||||||
|
pieceSprites: pygame.sprite.Group[PieceSprite]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.game = NetGame(5, 5)
|
||||||
|
pygame.init()
|
||||||
|
self.window = pygame.display.set_mode((5 * 30, 5 * 30))
|
||||||
|
self.window.fill("#ffffff")
|
||||||
|
pygame.display.set_caption("Net")
|
||||||
|
|
||||||
|
self.pieceSprites = pygame.sprite.Group()
|
||||||
|
for x in range(5):
|
||||||
|
for y in range(5):
|
||||||
|
self.pieceSprites.add(PieceSprite(x, y))
|
||||||
|
|
||||||
|
def run_game(self):
|
||||||
|
while True:
|
||||||
|
events = pygame.event.get()
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
raise SystemExit
|
||||||
|
elif event.type == LEFT_TURN:
|
||||||
|
self.game.turn_ccw(event.x, event.y)
|
||||||
|
if self.game.solved():
|
||||||
|
raise SystemExit
|
||||||
|
elif event.type == RIGHT_TURN:
|
||||||
|
self.game.turn_cw(event.x, event.y)
|
||||||
|
if self.game.solved():
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
self.pieceSprites.update(self.game, events)
|
||||||
|
for sprite in self.pieceSprites:
|
||||||
|
self.window.blit(sprite.image, sprite.rect)
|
||||||
|
|
||||||
|
pygame.display.flip()
|
||||||
|
|
||||||
|
|
||||||
|
NetGUI().run_game()
|
||||||
64
src/interface.py
Normal 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
|
||||||
124
src/net.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
"""
|
||||||
|
Kernlogik des Spiels
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.types import Piece, Direction
|
||||||
|
from src.interface import parse_description
|
||||||
|
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
pieces: list[list[Piece]]
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
|
||||||
|
def __init__(self, width: int, height: int) -> None:
|
||||||
|
self.height = height
|
||||||
|
self.width = width
|
||||||
|
self.pieces = (
|
||||||
|
DEMO_FIELD # TODO: Field generation or import from Tatham's version
|
||||||
|
)
|
||||||
|
|
||||||
|
def neighbors(self, x: int, y: int) -> list[Coordinate]:
|
||||||
|
neighbors: list[Coordinate] = []
|
||||||
|
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
|
||||||
|
if (
|
||||||
|
x + dx < 0
|
||||||
|
or x + dx >= self.width
|
||||||
|
or y + dy < 0
|
||||||
|
or y + dy >= self.height
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
neighbors.append((x + dx, y + dy))
|
||||||
|
return neighbors
|
||||||
|
|
||||||
|
def solved(self) -> bool:
|
||||||
|
connected = [([False] * self.height) for _ in range(self.width)]
|
||||||
|
connected = self._solve_floodfill(0, 0, connected)
|
||||||
|
return all(map(lambda x: all(x), connected))
|
||||||
|
|
||||||
|
def _solve_floodfill(
|
||||||
|
self, x: int, y: int, connected: list[list[bool]]
|
||||||
|
) -> list[list[bool]]:
|
||||||
|
connected[x][y] = True
|
||||||
|
|
||||||
|
connectedNeighbors: list[Coordinate] = []
|
||||||
|
for nx, ny in self.neighbors(x, y):
|
||||||
|
dx = x - nx
|
||||||
|
dy = y - ny
|
||||||
|
if (
|
||||||
|
Direction.from_offset(dx, dy)
|
||||||
|
in self.pieces[nx][ny].connected_directions()
|
||||||
|
and Direction.from_offset(-dx, -dy)
|
||||||
|
in self.pieces[x][y].connected_directions()
|
||||||
|
):
|
||||||
|
connectedNeighbors.append((nx, ny))
|
||||||
|
|
||||||
|
for nx, ny in connectedNeighbors:
|
||||||
|
if connected[nx][ny]:
|
||||||
|
continue
|
||||||
|
connected = self._solve_floodfill(nx, ny, connected)
|
||||||
|
|
||||||
|
return connected
|
||||||
|
|
||||||
|
|
||||||
|
class NetGame:
|
||||||
|
_grid: Grid
|
||||||
|
|
||||||
|
def __init__(self, width: int, height: int) -> None:
|
||||||
|
self._grid = Grid(width, height)
|
||||||
|
|
||||||
|
def get_field(self) -> list[list[Piece]]:
|
||||||
|
return self._grid.pieces
|
||||||
|
|
||||||
|
def get_piece(self, x: int, y: int) -> Piece:
|
||||||
|
return self._grid.pieces[x][y]
|
||||||
|
|
||||||
|
def turn_cw(self, x: int, y: int) -> None:
|
||||||
|
self._grid.pieces[x][y].turn_cw()
|
||||||
|
|
||||||
|
def turn_ccw(self, x: int, y: int) -> None:
|
||||||
|
self._grid.pieces[x][y].turn_ccw()
|
||||||
|
|
||||||
|
def solved(self):
|
||||||
|
return self._grid.solved()
|
||||||
57
src/types.py
Normal 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)
|
||||||
29
uv.lock
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maturaarbeit"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pygame" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "pygame", specifier = ">=2.6.1" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygame"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/49/cc/08bba60f00541f62aaa252ce0cfbd60aebd04616c0b9574f755b583e45ae/pygame-2.6.1.tar.gz", hash = "sha256:56fb02ead529cee00d415c3e007f75e0780c655909aaa8e8bf616ee09c9feb1f", size = 14808125, upload-time = "2024-09-29T13:41:34.698Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/91/718acf3e2a9d08a6ddcc96bd02a6f63c99ee7ba14afeaff2a51c987df0b9/pygame-2.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6039f3a55d800db80e8010f387557b528d34d534435e0871326804df2a62f2", size = 13090765, upload-time = "2024-09-29T14:27:02.377Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/c6/9cb315de851a7682d9c7568a41ea042ee98d668cb8deadc1dafcab6116f0/pygame-2.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a3a1288e2e9b1e5834e425bedd5ba01a3cd4902b5c2bff8ed4a740ccfe98171", size = 12381704, upload-time = "2024-09-29T14:27:10.228Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/8f/617a1196e31ae3b46be6949fbaa95b8c93ce15e0544266198c2266cc1b4d/pygame-2.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27eb17e3dc9640e4b4683074f1890e2e879827447770470c2aba9f125f74510b", size = 13581091, upload-time = "2024-09-29T11:30:27.653Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/87/2851a564e40a2dad353f1c6e143465d445dab18a95281f9ea458b94f3608/pygame-2.6.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c1623180e70a03c4a734deb9bac50fc9c82942ae84a3a220779062128e75f3b", size = 14273844, upload-time = "2024-09-29T11:40:04.138Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/b5/aa23aa2e70bcba42c989c02e7228273c30f3b44b9b264abb93eaeff43ad7/pygame-2.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef07c0103d79492c21fced9ad68c11c32efa6801ca1920ebfd0f15fb46c78b1c", size = 13951197, upload-time = "2024-09-29T11:40:06.785Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/06/29e939b34d3f1354738c7d201c51c250ad7abefefaf6f8332d962ff67c4b/pygame-2.6.1-cp313-cp313-win32.whl", hash = "sha256:3acd8c009317190c2bfd81db681ecef47d5eb108c2151d09596d9c7ea9df5c0e", size = 10249309, upload-time = "2024-09-29T11:10:23.329Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/11/17f7f319ca91824b86557e9303e3b7a71991ef17fd45286bf47d7f0a38e6/pygame-2.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:813af4fba5d0b2cb8e58f5d95f7910295c34067dcc290d34f1be59c48bd1ea6a", size = 10620084, upload-time = "2024-09-29T11:48:51.587Z" },
|
||||||
|
]
|
||||||