67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
"""funnies crossword construction.
|
|
|
|
Per functional design (BR1.2 / Q1=B): AI-assisted — an injectable clue-drafter
|
|
(typically the cloud model) supplies across/down clues; funnies lays them out
|
|
onto a 10x10 grid (Q2=B). The clue-drafter is injectable so tests stub it (no
|
|
real network in CI). The grid layout is deterministic/dependency-free.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Protocol
|
|
|
|
from .model import Crossword
|
|
|
|
|
|
class ClueDrafter(Protocol):
|
|
"""Interface for the AI-assisted clue drafting (the sanctioned cloud call)."""
|
|
|
|
def draft_clues(
|
|
self, theme_words: list[str], context: str, grid_size: int
|
|
) -> tuple[list[str], list[str]]: ...
|
|
|
|
|
|
class StubClueDrafter:
|
|
"""Deterministic stub used in tests / offline: simple auto-clues."""
|
|
|
|
def draft_clues(self, theme_words, context, grid_size=10):
|
|
across = [f"Word {i + 1}" for i in range(len(theme_words))]
|
|
down = ["Crossing clue"] * min(4, len(theme_words))
|
|
return across, down
|
|
|
|
|
|
GRID_SIZE = 10
|
|
|
|
|
|
def _blank_grid() -> list[list[str]]:
|
|
return [[" " for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
|
|
|
|
|
|
def _lay_out_across(words: list[str], base: list[list[str]]) -> None:
|
|
"""Place theme words horizontally on row r for the grid."""
|
|
for r, word in enumerate(words[:GRID_SIZE]):
|
|
if r >= GRID_SIZE:
|
|
break
|
|
w = word[:GRID_SIZE]
|
|
base[r][: len(w)] = list(w.upper())
|
|
|
|
|
|
def build_crossword(
|
|
theme_words: list[str],
|
|
context: str,
|
|
clue_drafter: ClueDrafter | None = None,
|
|
) -> Crossword:
|
|
"""Build a 10x10 crossword with AI-drafted clues (BR1.2, Q2=B)."""
|
|
drafter = clue_drafter or StubClueDrafter()
|
|
across, down = drafter.draft_clues(theme_words, context, GRID_SIZE)
|
|
grid = _blank_grid()
|
|
_lay_out_across(theme_words, grid)
|
|
solution = [row[:] for row in grid]
|
|
return Crossword(
|
|
crossword_id="xw-1",
|
|
grid=grid,
|
|
clues_across=across,
|
|
clues_down=down,
|
|
solution=solution,
|
|
)
|