Files
cruciverba_1/main.py

132 lines
4.1 KiB
Python

from __future__ import annotations
import argparse
from pathlib import Path
from typing import List
from build_vocabulary import (
FILTERED_OUTPUT_PATH,
METADATA_OUTPUT_PATH,
OUTPUT_PATH,
build_vocabulary,
)
from crossword_filler import CrosswordFiller, load_vocabulary, load_vocabulary_metadata
from crossword_generator import CrosswordGenerator, WORDS, render_grid
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generatore e filler di cruciverba.")
parser.add_argument(
"--build-vocabulary",
action="store_true",
help="Rigenera il vocabolario esteso, filtrato e i metadati prima dell'esecuzione.",
)
parser.add_argument(
"--skip-fill",
action="store_true",
help="Genera solo la griglia iniziale senza eseguire il filler.",
)
parser.add_argument(
"--vocabulary",
type=Path,
default=None,
help="Percorso opzionale a un vocabolario personalizzato.",
)
parser.add_argument(
"--target-empty-ratio",
type=float,
default=1 / 6,
help="Rapporto target di celle vuote residue dopo il filler.",
)
parser.add_argument(
"--time-limit",
type=float,
default=8.0,
help="Tempo massimo in secondi per la fase di generazione iniziale.",
)
parser.add_argument(
"--max-candidates",
type=int,
default=12,
help="Numero massimo di candidati esplorati per parola nella generazione iniziale.",
)
parser.add_argument(
"--diffxy",
type=int,
default=7,
help="Differenza massima preferita tra larghezza e altezza della griglia.",
)
return parser.parse_args()
def ensure_vocabulary(args: argparse.Namespace) -> None:
needs_build = args.build_vocabulary or not FILTERED_OUTPUT_PATH.exists() or not METADATA_OUTPUT_PATH.exists()
if not needs_build:
return
totals = build_vocabulary()
print("Vocabolario rigenerato")
print(f"- esteso: {OUTPUT_PATH}")
print(f"- filtrato: {FILTERED_OUTPUT_PATH}")
print(f"- metadati: {METADATA_OUTPUT_PATH}")
print(f"- parole estese: {totals['extended_words']}")
print(f"- parole filtrate: {totals['filtered_words']}")
def load_selected_vocabulary(path: Path | None) -> List[str]:
if path is None:
return load_vocabulary()
return path.read_text(encoding="utf-8").splitlines()
def main() -> None:
args = parse_args()
ensure_vocabulary(args)
generator = CrosswordGenerator(
WORDS,
diffxy=args.diffxy,
time_limit_seconds=args.time_limit,
max_candidates_per_word=args.max_candidates,
)
initial_state = generator.solve()
print("Griglia iniziale")
print(f"Parole inserite: {initial_state.placed_words}/{len(generator.words)}")
print(f"Intersezioni: {initial_state.intersections}")
print(f"Dimensioni: {initial_state.width()} x {initial_state.height()} (diff={initial_state.shape_difference()})")
print()
print(render_grid(initial_state.grid, initial_state.placements))
if args.skip_fill:
return
vocabulary = load_selected_vocabulary(args.vocabulary)
metadata = load_vocabulary_metadata()
filler = CrosswordFiller(
initial_state,
vocabulary,
target_empty_ratio=args.target_empty_ratio,
vocabulary_metadata=metadata,
)
final_state = filler.fill()
print()
print("Griglia riempita")
print(f"Parole totali: {final_state.placed_words}")
print(f"Intersezioni totali: {final_state.intersections}")
print(f"Dimensioni: {final_state.width()} x {final_state.height()} (diff={final_state.shape_difference()})")
print()
print(render_grid(final_state.grid, final_state.placements))
if filler.added_words:
print()
print("Parole aggiunte dal filler:")
for index, placement in enumerate(filler.added_words, start=1):
direction = "orizzontale" if placement.direction == "H" else "verticale"
print(f"{index:>2}. {placement.word} ({placement.x}, {placement.y}) {direction}")
if __name__ == "__main__":
main()