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()