Add DAT finale flow, editor, and new soundtrack assets
This commit is contained in:
+160
-28
@@ -2,17 +2,17 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
LEVELS_PER_DAT_FILE = 32
|
||||
DEFAULT_LEVELS_PER_DAT_FILE = 32
|
||||
LEVEL_WIDTH = 32
|
||||
LEVEL_HEIGHT = 32
|
||||
LEVEL_SIZE = LEVEL_WIDTH * LEVEL_HEIGHT
|
||||
EXPECTED_DAT_SIZE = LEVELS_PER_DAT_FILE * LEVEL_SIZE
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_DAT_PATH = PROJECT_ROOT / "assets" / "Rat" / "level.dat"
|
||||
DEFAULT_JSON_PATH = PROJECT_ROOT / "maze.json"
|
||||
MAP_EMPTY = 0
|
||||
MAP_WALL = 1
|
||||
MAP_TUNNEL = 2
|
||||
VALID_TILE_VALUES = {MAP_EMPTY, MAP_WALL, MAP_TUNNEL}
|
||||
|
||||
|
||||
def get_default_map_source():
|
||||
@@ -21,13 +21,165 @@ def get_default_map_source():
|
||||
return DEFAULT_JSON_PATH
|
||||
|
||||
|
||||
def _level_count_from_dat_size(raw_size, source_path):
|
||||
if raw_size <= 0:
|
||||
raise ValueError(f"Invalid DAT size for {source_path}: archive is empty")
|
||||
if raw_size % LEVEL_SIZE != 0:
|
||||
raise ValueError(
|
||||
f"Invalid DAT size for {source_path}: expected a multiple of {LEVEL_SIZE} bytes, got {raw_size}"
|
||||
)
|
||||
return raw_size // LEVEL_SIZE
|
||||
|
||||
|
||||
def get_dat_level_count(source_path):
|
||||
source_path = Path(source_path)
|
||||
return _level_count_from_dat_size(source_path.stat().st_size, source_path)
|
||||
|
||||
|
||||
def get_level_count(source_path):
|
||||
source_path = Path(source_path)
|
||||
if source_path.suffix.lower() == ".dat":
|
||||
return get_dat_level_count(source_path)
|
||||
return 1
|
||||
|
||||
|
||||
def normalize_level_index(level_index, level_count):
|
||||
level_count = int(level_count)
|
||||
if level_count <= 0:
|
||||
raise ValueError(f"Level count must be positive, got {level_count}")
|
||||
return int(level_index) % level_count
|
||||
|
||||
|
||||
def normalize_tiles(tiles, width=None, height=None):
|
||||
if not tiles:
|
||||
raise ValueError("Map data cannot be empty")
|
||||
|
||||
if height is None:
|
||||
height = len(tiles)
|
||||
if height != len(tiles):
|
||||
raise ValueError(f"Invalid map height: expected {height}, got {len(tiles)}")
|
||||
|
||||
if width is None:
|
||||
width = len(tiles[0])
|
||||
if width <= 0:
|
||||
raise ValueError("Map width must be greater than zero")
|
||||
|
||||
normalized = []
|
||||
for row_index, row in enumerate(tiles):
|
||||
if len(row) != width:
|
||||
raise ValueError(
|
||||
f"Invalid row width at row {row_index}: expected {width}, got {len(row)}"
|
||||
)
|
||||
|
||||
normalized_row = []
|
||||
for column_index, cell in enumerate(row):
|
||||
if isinstance(cell, bool):
|
||||
normalized_cell = MAP_WALL if cell else MAP_TUNNEL
|
||||
else:
|
||||
try:
|
||||
normalized_cell = int(cell)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(
|
||||
f"Invalid tile value at ({column_index}, {row_index}): {cell!r}"
|
||||
) from exc
|
||||
|
||||
if normalized_cell not in VALID_TILE_VALUES:
|
||||
raise ValueError(
|
||||
f"Unsupported tile value at ({column_index}, {row_index}): {normalized_cell}"
|
||||
)
|
||||
|
||||
normalized_row.append(normalized_cell)
|
||||
|
||||
normalized.append(normalized_row)
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
def create_level(fill=MAP_EMPTY, width=LEVEL_WIDTH, height=LEVEL_HEIGHT, border_walls=True):
|
||||
if fill not in VALID_TILE_VALUES:
|
||||
raise ValueError(f"Unsupported fill tile: {fill}")
|
||||
|
||||
tiles = [[fill for _ in range(width)] for _ in range(height)]
|
||||
|
||||
if border_walls and width >= 2 and height >= 2:
|
||||
for x in range(width):
|
||||
tiles[0][x] = MAP_WALL
|
||||
tiles[height - 1][x] = MAP_WALL
|
||||
for y in range(height):
|
||||
tiles[y][0] = MAP_WALL
|
||||
tiles[y][width - 1] = MAP_WALL
|
||||
|
||||
return tiles
|
||||
|
||||
|
||||
def load_json_level(source_path):
|
||||
source_path = Path(source_path)
|
||||
with source_path.open("r", encoding="utf-8") as file:
|
||||
matrix = json.load(file)
|
||||
return normalize_tiles(matrix)
|
||||
|
||||
|
||||
def save_json_level(destination_path, tiles, indent=2):
|
||||
destination_path = Path(destination_path)
|
||||
normalized_tiles = normalize_tiles(tiles)
|
||||
with destination_path.open("w", encoding="utf-8") as handle:
|
||||
json.dump(normalized_tiles, handle, indent=indent)
|
||||
handle.write("\n")
|
||||
|
||||
|
||||
def load_dat_levels(source_path):
|
||||
source_path = Path(source_path)
|
||||
raw_data = source_path.read_bytes()
|
||||
level_count = _level_count_from_dat_size(len(raw_data), source_path)
|
||||
|
||||
levels = []
|
||||
for level_index in range(level_count):
|
||||
level_offset = level_index * LEVEL_SIZE
|
||||
level_data = raw_data[level_offset:level_offset + LEVEL_SIZE]
|
||||
|
||||
matrix = []
|
||||
for row in range(LEVEL_HEIGHT):
|
||||
row_start = row * LEVEL_WIDTH
|
||||
raw_row = level_data[row_start:row_start + LEVEL_WIDTH]
|
||||
matrix.append(list(raw_row))
|
||||
levels.append(normalize_tiles(matrix, width=LEVEL_WIDTH, height=LEVEL_HEIGHT))
|
||||
|
||||
return levels
|
||||
|
||||
|
||||
def load_dat_level(source_path, level_index=0):
|
||||
levels = load_dat_levels(source_path)
|
||||
return levels[normalize_level_index(level_index, len(levels))]
|
||||
|
||||
|
||||
def save_dat_levels(destination_path, levels):
|
||||
destination_path = Path(destination_path)
|
||||
if not levels:
|
||||
raise ValueError("DAT archive must contain at least one level")
|
||||
|
||||
output = bytearray()
|
||||
for level_index, level in enumerate(levels):
|
||||
normalized_level = normalize_tiles(level, width=LEVEL_WIDTH, height=LEVEL_HEIGHT)
|
||||
for row in normalized_level:
|
||||
output.extend(row)
|
||||
|
||||
expected_size = len(levels) * LEVEL_SIZE
|
||||
if len(output) != expected_size:
|
||||
raise ValueError(
|
||||
f"Invalid DAT payload size after serialization: expected {expected_size}, got {len(output)}"
|
||||
)
|
||||
|
||||
destination_path.write_bytes(bytes(output))
|
||||
|
||||
|
||||
class Map:
|
||||
"""Classe che rappresenta la mappa del labirinto."""
|
||||
|
||||
def __init__(self, maze_file=None, level_index=0):
|
||||
self.source_path = self._resolve_source_path(maze_file)
|
||||
self.level_index = level_index
|
||||
self.tiles = self._load_tiles(self.source_path, level_index)
|
||||
self.level_count = get_level_count(self.source_path)
|
||||
self.level_index = normalize_level_index(level_index, self.level_count)
|
||||
self.tiles = self._load_tiles(self.source_path, self.level_index)
|
||||
self.matrix = [
|
||||
[cell == MAP_WALL for cell in row]
|
||||
for row in self.tiles
|
||||
@@ -55,34 +207,14 @@ class Map:
|
||||
def _load_tiles(self, source_path, level_index):
|
||||
suffix = source_path.suffix.lower()
|
||||
if suffix == ".dat":
|
||||
return self._load_dat_level(source_path, level_index)
|
||||
return self._load_json_level(source_path)
|
||||
return load_dat_level(source_path, level_index)
|
||||
return load_json_level(source_path)
|
||||
|
||||
def _load_json_level(self, source_path):
|
||||
with source_path.open("r", encoding="utf-8") as file:
|
||||
matrix = json.load(file)
|
||||
return [
|
||||
[MAP_WALL if cell else MAP_TUNNEL for cell in row]
|
||||
for row in matrix
|
||||
]
|
||||
return load_json_level(source_path)
|
||||
|
||||
def _load_dat_level(self, source_path, level_index):
|
||||
raw_data = source_path.read_bytes()
|
||||
if len(raw_data) != EXPECTED_DAT_SIZE:
|
||||
raise ValueError(
|
||||
f"Invalid DAT size for {source_path}: expected {EXPECTED_DAT_SIZE} bytes, got {len(raw_data)}"
|
||||
)
|
||||
|
||||
normalized_level = level_index % LEVELS_PER_DAT_FILE
|
||||
level_offset = normalized_level * LEVEL_SIZE
|
||||
level_data = raw_data[level_offset:level_offset + LEVEL_SIZE]
|
||||
|
||||
matrix = []
|
||||
for row in range(LEVEL_HEIGHT):
|
||||
row_start = row * LEVEL_WIDTH
|
||||
raw_row = level_data[row_start:row_start + LEVEL_WIDTH]
|
||||
matrix.append(list(raw_row))
|
||||
return matrix
|
||||
return load_dat_level(source_path, level_index)
|
||||
|
||||
def in_bounds(self, x, y):
|
||||
return 0 <= x < self.width and 0 <= y < self.height
|
||||
|
||||
Reference in New Issue
Block a user