| | import json
|
| | import sys
|
| | import os
|
| | from collections import Counter
|
| |
|
| |
|
| |
|
| |
|
| | coarse_locations = True
|
| | coarse_counts = True
|
| | pluralize = True
|
| | give_staircase_lengths = False
|
| |
|
| | def describe_size(count):
|
| | if count <= 4: return "small"
|
| | else: return "big"
|
| |
|
| | def describe_quantity(count):
|
| | if count == 0: return "no"
|
| | elif count == 1: return "one"
|
| | elif count == 2: return "two"
|
| | elif count < 5: return "a few"
|
| | elif count < 10: return "several"
|
| | else: return "many"
|
| |
|
| | def get_tile_descriptors(tileset):
|
| | """Creates a mapping from tile character to its list of descriptors."""
|
| | result = {char: set(attrs) for char, attrs in tileset["tiles"].items()}
|
| |
|
| | result["!"] = {"passable"}
|
| | result["*"] = {"passable"}
|
| | return result
|
| |
|
| | def analyze_floor(scene, id_to_char, tile_descriptors, describe_absence):
|
| | """Analyzes the last row of the 32x32 scene and generates a floor description."""
|
| | WIDTH = len(scene[0])
|
| | last_row = scene[-1]
|
| | solid_count = sum(
|
| | 1 for tile in last_row
|
| | if tile in id_to_char and (
|
| | "solid" in tile_descriptors.get(id_to_char[tile], []) or
|
| | "diggable" in tile_descriptors.get(id_to_char[tile], [])
|
| | )
|
| | )
|
| | passable_count = sum(
|
| | 1 for tile in last_row if "passable" in tile_descriptors.get(id_to_char[tile], [])
|
| | )
|
| |
|
| | if solid_count == WIDTH:
|
| | return "full floor"
|
| | elif passable_count == WIDTH:
|
| | if describe_absence:
|
| | return "no floor"
|
| | else:
|
| | return ""
|
| | elif solid_count > passable_count:
|
| |
|
| | gaps = 0
|
| | in_gap = False
|
| | for tile in last_row:
|
| |
|
| | if "passable" in tile_descriptors.get(id_to_char[tile], []) or "enemy" in tile_descriptors.get(id_to_char[tile], []):
|
| | if not in_gap:
|
| | gaps += 1
|
| | in_gap = True
|
| | elif "solid" in tile_descriptors.get(id_to_char[tile], []):
|
| | in_gap = False
|
| | else:
|
| | print("error")
|
| | print(tile)
|
| | print(id_to_char[tile])
|
| | print(tile_descriptors)
|
| | print(tile_descriptors.get(id_to_char[tile], []))
|
| | raise ValueError("Every tile should be passable, solid, or enemy")
|
| | return f"floor with {describe_quantity(gaps) if coarse_counts else gaps} gap" + ("s" if pluralize and gaps != 1 else "")
|
| | else:
|
| |
|
| | chunks = 0
|
| | in_chunk = False
|
| | for tile in last_row:
|
| | if "solid" in tile_descriptors.get(id_to_char[tile], []):
|
| | if not in_chunk:
|
| | chunks += 1
|
| | in_chunk = True
|
| | elif "passable" in tile_descriptors.get(id_to_char[tile], []) or "enemy" in tile_descriptors.get(id_to_char[tile], []):
|
| | in_chunk = False
|
| | else:
|
| | print("error")
|
| | print(tile)
|
| | print(tile_descriptors)
|
| | print(tile_descriptors.get(tile, []))
|
| | raise ValueError("Every tile should be either passable or solid")
|
| | return f"giant gap with {describe_quantity(chunks) if coarse_counts else chunks} chunk"+("s" if pluralize and chunks != 1 else "")+" of floor"
|
| |
|
| | def count_in_scene(scene, tiles, exclude=set()):
|
| | """ counts standalone tiles, unless they are in the exclude set """
|
| | count = 0
|
| | for r, row in enumerate(scene):
|
| | for c, t in enumerate(row):
|
| |
|
| | if (r,c) not in exclude and t in tiles:
|
| |
|
| | count += 1
|
| |
|
| | return count
|
| |
|
| | def count_caption_phrase(scene, tiles, name, names, offset = 0, describe_absence=False, exclude=set()):
|
| | """ offset modifies count used in caption """
|
| | count = offset + count_in_scene(scene, tiles, exclude)
|
| |
|
| | if count > 0:
|
| | return f" {describe_quantity(count) if coarse_counts else count} " + (names if pluralize and count > 1 else name) + "."
|
| | elif describe_absence:
|
| | return f" no {names}."
|
| | else:
|
| | return ""
|
| |
|
| | def in_column(scene, x, tile):
|
| | for row in scene:
|
| | if row[x] == tile:
|
| | return True
|
| |
|
| | return False
|
| |
|
| | def analyze_ceiling(scene, id_to_char, tile_descriptors, describe_absence, ceiling_row = 1):
|
| | """
|
| | Analyzes ceiling row (0-based index) to detect a ceiling.
|
| | Returns a caption phrase or an empty string if no ceiling is detected.
|
| | """
|
| | WIDTH = len(scene[0])
|
| |
|
| | row = scene[ceiling_row]
|
| | solid_count = sum(1 for tile in row if "solid" in tile_descriptors.get(id_to_char[tile], []))
|
| |
|
| | if solid_count == WIDTH:
|
| | return " full ceiling."
|
| | elif solid_count > WIDTH//2:
|
| |
|
| | gaps = 0
|
| | in_gap = False
|
| | for tile in row:
|
| |
|
| | if "passable" in tile_descriptors.get(id_to_char[tile], []) or "moving" in tile_descriptors.get(id_to_char[tile], []):
|
| | if not in_gap:
|
| | gaps += 1
|
| | in_gap = True
|
| | else:
|
| | in_gap = False
|
| | result = f" ceiling with {describe_quantity(gaps) if coarse_counts else gaps} gap" + ("s" if pluralize and gaps != 1 else "") + "."
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | return result
|
| | elif describe_absence:
|
| | return " no ceiling."
|
| | else:
|
| | return ""
|
| |
|
| | def extract_tileset(tileset_path):
|
| |
|
| | with open(tileset_path, "r") as f:
|
| | tileset = json.load(f)
|
| |
|
| | tile_chars = sorted(tileset['tiles'].keys())
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | id_to_char = {idx: char for idx, char in enumerate(tile_chars)}
|
| |
|
| | char_to_id = {char: idx for idx, char in enumerate(tile_chars)}
|
| |
|
| | tile_descriptors = get_tile_descriptors(tileset)
|
| |
|
| |
|
| | return tile_chars, id_to_char, char_to_id, tile_descriptors
|
| |
|
| | def flood_fill(scene, visited, start_row, start_col, id_to_char, tile_descriptors, excluded, pipes=False, target_descriptor=None):
|
| | stack = [(start_row, start_col)]
|
| | structure = []
|
| |
|
| | while stack:
|
| | row, col = stack.pop()
|
| | if (row, col) in visited or (row, col) in excluded:
|
| | continue
|
| | tile = scene[row][col]
|
| | descriptors = tile_descriptors.get(id_to_char[tile], [])
|
| |
|
| | if target_descriptor is not None:
|
| | if target_descriptor not in descriptors:
|
| | continue
|
| | else:
|
| | if "solid" not in descriptors or (not pipes and "pipe" in descriptors) or (pipes and "pipe" not in descriptors):
|
| | continue
|
| |
|
| | visited.add((row, col))
|
| | structure.append((row, col))
|
| |
|
| |
|
| | for d_row, d_col in [(-1,0), (1,0), (0,-1), (0,1)]:
|
| |
|
| | if (id_to_char[tile] == '>' or id_to_char[tile] == ']') and d_col == 1:
|
| | continue
|
| | if (id_to_char[tile] == '<' or id_to_char[tile] == '[') and d_col == -1:
|
| | continue
|
| |
|
| | n_row, n_col = row + d_row, col + d_col
|
| | if 0 <= n_row < len(scene) and 0 <= n_col < len(scene[0]):
|
| | stack.append((n_row, n_col))
|
| |
|
| | return structure
|
| |
|