setup
Initialize a crossword puzzle by loading grid layout and clue definitions to prepare for solving. Accepts grid text with cell symbols and JSON-formatted clues for across and down directions.
Instructions
クロスワードの盤面とカギ定義を読み込み、状態を初期化する。
Args:
grid_text (str): 行番号つきの盤面テキスト。列・行番号は全角数字で表記し、
文字が入るマスは "?"、黒マスは "#" で記述する。各行のマス数が一致している
必要がある。
clue_text (str): JSON Lines 形式のカギ定義。各行は id/direction/row/col
/length/clue を持つ辞書で、direction は "across" か "down"。row と
col は 1 起点の正整数。
Returns: list[list[str]]: 正規化済みセル行列。各要素は "?" または "#" のシンボル。
Raises: ValueError: 盤面の行長不一致・未知のセル記号・カギ定義の欠損や不正値など、 入力内容が検証に失敗した場合。
Notes: この関数を呼び出すと既存の候補リストは破棄され、状態が再初期化される。
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| grid_text | Yes | ||
| clue_text | Yes |
Implementation Reference
- src/server.py:172-202 (handler)The primary handler for the MCP 'setup' tool. Decorated with @mcp.tool() for registration. Parses input grid_text and clue_text using helpers, initializes the global PuzzleState, clears candidates, and returns the loaded grid.@mcp.tool() async def setup(grid_text: str, clue_text: str) -> list[list[str]]: """クロスワードの盤面とカギ定義を読み込み、状態を初期化する。 Args: grid_text (str): 行番号つきの盤面テキスト。列・行番号は全角数字で表記し、 文字が入るマスは "?"、黒マスは "#" で記述する。各行のマス数が一致している 必要がある。 clue_text (str): JSON Lines 形式のカギ定義。各行は `id`/`direction`/`row`/`col` /`length`/`clue` を持つ辞書で、`direction` は "across" か "down"。`row` と `col` は 1 起点の正整数。 Returns: list[list[str]]: 正規化済みセル行列。各要素は "?" または "#" のシンボル。 Raises: ValueError: 盤面の行長不一致・未知のセル記号・カギ定義の欠損や不正値など、 入力内容が検証に失敗した場合。 Notes: この関数を呼び出すと既存の候補リストは破棄され、状態が再初期化される。 """ grid = _load_grid(grid_text) clues = _load_clues(clue_text, grid) state.grid = grid state.clues = clues state.candidates.clear() return grid
- src/server.py:44-73 (helper)Helper function called by setup to parse and validate the grid_text into a 2D list of fillable ('?') and block ('#') cells, handling full-width numbers and row consistency.def _load_grid(grid_text: str) -> list[list[str]]: "グリッド文字列を読み込み、全角表記の入力からセル行列を構築する" grid: list[list[str]] = [] for raw_line in grid_text.splitlines(): if FILLABLE_CELL not in raw_line and BLOCK_CELL not in raw_line: continue normalized_line = raw_line.translate(_FULLWIDTH_DIGIT_TO_ASCII) parts = normalized_line.strip().split() if not parts: continue if parts[0].isdigit(): tokens = parts[1:] else: tokens = parts if not tokens: continue for token in tokens: if token not in {FILLABLE_CELL, BLOCK_CELL}: raise ValueError(f"未知のマス表現を検出しました: {token}") if grid and len(tokens) != len(grid[0]): raise ValueError("行ごとのマス数が一致しません。入力を確認してください。") grid.append(tokens) return grid
- src/server.py:140-162 (helper)Helper function called by setup to parse JSON Lines clue_text into a dictionary of validated Clue objects, using _validate_clue_payload for each.def _load_clues(clue_text: str, grid: list[list[str]]) -> dict[str, Clue]: "JSON Lines 形式のカギ定義を読み込み、検証した Clue オブジェクトへ変換する" lines = [line.strip() for line in clue_text.splitlines() if line.strip()] if not lines: raise ValueError("カギ情報が存在しません。") seen_ids: set[str] = set() clues: dict[str, Clue] = {} for line_no, raw in enumerate(lines, start=1): try: payload = json.loads(raw) except json.JSONDecodeError as exc: raise ValueError(f"{line_no} 行目のカギ情報を JSON として読み込めません。") from exc if not isinstance(payload, dict): raise ValueError(f"{line_no} 行目のカギ情報が辞書形式ではありません。") clue = _validate_clue_payload(payload, grid, seen_ids) clues[clue.clue_id] = clue return clues
- src/server.py:76-137 (helper)Helper function used by _load_clues to validate individual clue payloads, compute positions on grid, and create Clue dataclass instances.def _validate_clue_payload( payload: dict[str, object], grid: list[list[str]], seen_ids: set[str], ) -> Clue: "JSON 行から取得したカギ情報を検証し、内部で扱いやすい形へ変換する" required_keys = ("id", "direction", "row", "col", "length", "clue") for key in required_keys: if key not in payload: raise ValueError(f"カギ情報に {key} がありません。") clue_id = str(payload["id"]).strip() if not clue_id: raise ValueError("clue_id が空です。") if clue_id in seen_ids: raise ValueError(f"clue_id={clue_id} が重複しています。") direction = str(payload["direction"]).strip().lower() if direction not in {"across", "down"}: raise ValueError(f"clue_id={clue_id} の direction が不正です: {payload['direction']}") try: row = int(payload["row"]) col = int(payload["col"]) length = int(payload["length"]) except (TypeError, ValueError) as exc: raise ValueError(f"clue_id={clue_id} の row/col/length を整数に変換できません。") from exc if row <= 0 or col <= 0 or length <= 0: raise ValueError(f"clue_id={clue_id} の row/col/length が不正です。") rows = len(grid) cols = len(grid[0]) if rows else 0 start_r = row - 1 start_c = col - 1 if start_r >= rows or start_c >= cols: raise ValueError(f"clue_id={clue_id} の開始位置が盤面外です。") positions: list[tuple[int, int]] = [] for offset in range(length): r = start_r + (offset if direction == "down" else 0) c = start_c + (offset if direction == "across" else 0) if r >= rows or c >= cols: raise ValueError(f"clue_id={clue_id} の単語が盤面外にはみ出します。") if grid[r][c] == BLOCK_CELL: raise ValueError(f"clue_id={clue_id} が黒マスを含んでいます。") positions.append((r, c)) clue_text = str(payload["clue"]).strip() if not clue_text: raise ValueError(f"clue_id={clue_id} の clue が空です。") seen_ids.add(clue_id) return Clue( clue_id=clue_id, direction=direction, length=length, positions=tuple(positions), text=clue_text, )
- src/server.py:12-21 (schema)Dataclass defining the structure for validated clues, used in PuzzleState by setup.@dataclass(frozen=True) class Clue: "探索ロジックが参照する検証済みのカギ定義" clue_id: str direction: str length: int positions: tuple[tuple[int, int], ...] text: str