diff --git a/.gitignore b/.gitignore index bee95a6..dc85509 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,11 @@ test-results dataset/private/ *.local +# Local-only Masyu assist diagnostics +AUX_Puzzlink_Assistance.js +scripts/compare-masyu-assist.ts +src/domain/diagnostics/ + # Editor directories and files .vscode/* !.vscode/extensions.json @@ -29,3 +34,4 @@ dataset/private/ *.sln *.sw? Puzzlink_Assistance.js +docs/legacy/* \ No newline at end of file diff --git a/README.md b/README.md index fb679c7..f312e5f 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,29 @@ # PuzzleKit Web -[PuzzleKit Web](https://smilingwayne.github.io/puzzlekit-web/) provides step-wise and explainable inference flow for logical puzzles, now supporting only slitherlink (with better perf) and Masyu (with poor perf). The core goal is not just to output a final answer, but to make each deduction step explicit: what changed, why it changed, and which rule produced this change. +[PuzzleKit Web](https://smilingwayne.github.io/puzzlekit-web/) is a pure frontend logic-puzzle tool for step-wise, explainable solving. It currently focuses on **Slitherlink** and **Masyu**. -Current focus: +The goal is not only to produce a final answer. The app shows each deduction step: what changed, which rule changed it, and how the board can be replayed forward or backward. -- Pure web-based solving experience -- Rule-driven, explainable, replayable deduction flow -- Modular Slitherlink rule architecture (including strong-inference fallback) -- Practical interoperability with common puzzle URL formats (currently centered on puzz.link) +## What It Does ---- +- Runs entirely in the browser. +- Imports Slitherlink and Masyu puzzles from puzz.link-compatible URLs and Penpa+ URLs. +- Provides an explainable solver workspace with step replay, highlights, rule messages, and live stats. +- Provides an editor workspace for building Slitherlink and Masyu boards, importing URLs, and loading the result into the solver. +- Provides a small Dataset page for curated examples and benchmark-oriented puzzle sets. +- Exports supported puzzle states back to puzz.link-style URLs where available. -## 1) Project Introduction +Slitherlink has the more mature rule stack. Masyu is now a first-class puzzle family with editor, import/export, rendering, replay, completion analysis, and an actively evolving solver. -### 1.1 Positioning +![](https://cdn.jsdelivr.net/gh/SmilingWayne/picsrepo/20260604213830574.png) -PuzzleKit Web is a reasoning-engine-centered puzzle tool: +## Getting Started -- `domain` handles puzzle IR, rules, inference, and replay semantics -- `features` handles controls, board rendering, step explanations, and stats views -- The UI is not a black-box "give me the answer" interface, but a visualization layer for deduction +Requirements: -### 1.2 Current Focus: Slitherlink - -Slitherlink rules are modularized under `src/domain/rules/slither/rules/`, including: - -- Pattern rules (`patterns.ts`) -- Core constraints (`core.ts`) -- Color-based inference chain (`color.ts`) -- Sector inference and propagation (`sectorInference.ts`, `sectorPropagation.ts`) -- Conservative branch-based strong inference (`strongInference.ts`) -- Shared helpers (`shared.ts`) - -Execution order is centrally managed in `src/domain/rules/slither/rules.ts`: deterministic rules first, then strong inference. - ---- - -## 2) Getting Started - -### 2.1 Requirements - -- Node.js 20 (see `.nvmrc`) +- Node.js 20, matching `.nvmrc` - Corepack with pnpm 10.33.0 -### 2.2 Install and Start - ```bash corepack enable pnpm install @@ -53,94 +32,65 @@ pnpm dev This starts the local Vite development server. -![](https://cdn.jsdelivr.net/gh/SmilingWayne/picsrepo/20260514010419047.png) - -### 2.3 Common Commands +Common commands: ```bash pnpm lint # ESLint pnpm test:run # Vitest unit tests pnpm build # TypeScript + Vite production build pnpm test:e2e # Playwright end-to-end tests +pnpm benchmark:solve ``` -### 2.4 Release and Deployment - -GitHub Pages deployment is handled by GitHub Actions. Push a `v*` tag to run -linting, unit tests, and the production build, then publish `dist/` to Pages. - ---- - -## 3) Features Implemented So Far - -### 3.1 Input and Puzzle Construction - -- Import puzzle state from URL -- Create custom blank Slitherlink grids (rows/cols configurable) -- Edit Slitherlink clues directly in cells (`0` to `3` and `?`) - -> URL support note: URL import is currently focused on `puzz.link`. `penpa`-style URL support is planned next. - -### 3.2 Solving and Replay - -- `Next Step`: apply one inference step -- `Previous Step`: rewind one step -- `Solve Next 100 Steps`: auto-advance 100 steps until no more progress (or limit reached) -- `Reset Replay`: return to initial puzzle state -- Each rule step stores message + diffs + affected regions for replay and explanation - -### 3.3 Explainability and Visualization - -- Reasoning timeline in the `Reasoning Steps` panel -- Toggle between latest 30 steps and full history -- Vertex numbering overlay for board analysis - -### 3.4 Live Stats and Terminal Report - -- Live metrics: total steps, total modifications, unique techniques, draft difficulty score -- Terminal report when solving stalls: decided-edge ratio, unknown-edge count, blocker reasons -- Rule usage statistics to analyze deduction paths - -### 3.5 Export - -- Export current puzzle state (including puzz.link encoding attempts) -- One-click copy to clipboard - ---- - -## 4) Roadmap - -- `penpa`-style URL support -- Configurable solver parameters (for example: step limits, strong-inference budget) -- Broader Slitherlink rule coverage -- More puzzle type adapters (e.g. Masyu, Nonogram) with stronger plugin support -- Unique-solution checking and diagnostics +## App Surfaces + +- **Solver** (`/`): import a puzzle, run one deduction at a time, jump through the replay timeline, inspect explanations, view stats, and export supported states. +- **Editor** (`/editor`): create custom Slitherlink or Masyu grids, edit clues/pearls and marks, import supported URLs, then hand the puzzle to the solver. +- **Dataset** (`/dataset`): browse curated public dataset manifests, preview puzzles, and load them into Solver or Editor. + +## Architecture Snapshot + +```text +src/ + app/ page composition and routing + domain/ IR, parsers, exporters, plugins, rule engine, benchmark logic + features/ board rendering, solver controls, editor, stats, explanations + test/ test setup helpers +dataset/ + public/ small committed dataset manifests + private/ local-only manifests ignored by git +docs/ + techniques/ puzzle-specific technique notes +``` ---- +Puzzle families are registered as plugins. A plugin owns parsing, exporting, help/legend content, stats, and the ordered rule list used by the solver. The shared rule engine applies explicit diffs so every step can be replayed and reverted deterministically. -## 5) Why This Project Exists +## Deployment -This project is built around one idea: solving is not only about the final answer, but also about understanding and replaying the reasoning process. +CI runs on pushes and pull requests targeting `main`: -1. Most solvers focus on "what is the final solution", but not "how the solution is obtained" or "what should be deduced next". -2. By supporting `puzz.link` URLs, the tool can better fit into existing puzzle-sharing workflows and community standards. -3. It offers a lightweight, browser-native alternative to mobile games and desktop software. +```bash +pnpm lint +pnpm test:run +pnpm build +``` -The long-term direction is to build a puzzle reasoning tool that is: +GitHub Pages deployment is triggered by pushing a `v*` tag. The Pages workflow builds `dist/`, adds an SPA fallback, and publishes the result. -- Pure web implementation -- Explainable -- Step-by-step replayable -- Progressively deduction-oriented +## Documentation ---- +- `docs/PROJECT_GUIDE_EN.md`: compact project guide for AI agents and maintainers. +- `docs/MASYU_AGENT_BRIEF.md`: active low-token Masyu onboarding brief. +- `docs/techniques/slitherlink.md`: Slitherlink technique notes. +- `docs/techniques/masyu.md`: Masyu technique notes. +- `docs/legacy/`: older plans and research notes kept for reference only. -## 6) Acknowledgements +## Why This Project Exists -- Thanks to AI vibe-coding tools such as `Codex` and `Cursor` for helping accelerate development and refactoring. +Most puzzle solvers emphasize the final solution. PuzzleKit Web is built around the solving trace: each step should be inspectable, explainable, and replayable. The long-term direction is a browser-native reasoning workbench for logic puzzles, with puzzle-family support growing incrementally through small, explicit rules. -## 7) References +## References -This repo is inspired by the browser plugin [Puzzlink_Assistance](https://github.com/LeavingLeaves/Puzzlink_Assistance), which helps with trivial inference for puzz.link-style puzzles. +This repo is inspired by [Puzzlink_Assistance](https://github.com/LeavingLeaves/Puzzlink_Assistance), a browser plugin for puzz.link-style puzzles. -The detailed inference techniques can be found in [How slitherlink should be solved](https://jonathanolson.net/slitherlink/). +Slitherlink technique references include [How slitherlink should be solved](https://jonathanolson.net/slitherlink/). diff --git a/docs/ADDING_PUZZLE_FAMILY_EN.md b/docs/ADDING_PUZZLE_FAMILY_EN.md deleted file mode 100644 index 9ec1f81..0000000 --- a/docs/ADDING_PUZZLE_FAMILY_EN.md +++ /dev/null @@ -1,166 +0,0 @@ -# Adding a Puzzle Family - -This guide is for developers and AI agents adding a new puzzle type such as -Nonogram or Masyu. Build a small vertical slice first: parse or create one -puzzle, render it, run a few explainable rules, and prove replay works. - -PuzzleKit is a reasoning engine with a UI. Keep puzzle logic in `domain/`, keep -rendering/orchestration in `features/` and `app/`, and connect them through -`PuzzleIR` plus `PuzzlePlugin`. - ---- - -## 1. Understand the Core Mechanisms - -Read these before writing code: - -- `src/domain/ir/types.ts` - shared `PuzzleIR`, cell/edge/sector/vertex state. -- `src/domain/ir/keys.ts` - stable keys for cells, edges, sectors, and vertices. -- `src/domain/rules/types.ts` - `Rule`, `RuleApplication`, `RuleStep`, and `RuleDiff`. -- `src/domain/rules/engine.ts` - applies and reverts rule diffs. -- `src/features/solver/solverStore.ts` - loads puzzles, runs plugin rules, replays steps with checkpoints, and builds terminal reports. -- `src/features/editor/editorStore.ts` - current editor state model and Slitherlink editing pattern. -- `src/domain/plugins/types.ts` and `registry.ts` - plugin boundary for puzzle families. -- `src/domain/benchmark/*` and `dataset/public/*` - dataset and benchmark flow. - -Replay safety is the central contract. A rule must return explicit diffs; the -engine and solver store must be able to apply and undo those diffs without -hidden mutation. The solver now uses incremental replay plus periodic -checkpoints, so every puzzle family must keep `RuleDiff` forward and reverse -semantics deterministic. - ---- - -## 2. Define the Puzzle Boundary - -Start by deciding how the new puzzle maps into `PuzzleIR`: - -- Use `cells` for clue values, fills, shaded states, or symbols. -- Use `edges` for line-like or wall-like decisions. -- Use `sectors` only when the puzzle needs Slitherlink-style corner constraints. -- Use `vertices` only when vertex candidate sets are part of the reasoning model. -- Put puzzle-specific metadata in `metadata`, but prefer typed shared fields when they fit. - -Then add or update a plugin in `src/domain/plugins/`: - -- `id` and `displayName` -- `parse(input)` for supported input -- `encode(puzzle)` when export is available -- `getRules()` in deterministic execution order -- optional `help`, `legend`, and `getStats(puzzle)` for UI affordances - -Register the plugin in `src/domain/plugins/registry.ts`. Planned stubs are fine, -but do not make UI or docs imply a puzzle is implemented until it can parse, -render, and run at least a minimal rule path. - ---- - -## 3. Build the First Vertical Slice - -Recommended order: - -1. **IR factory and parser** - - Add a puzzle factory similar to `createSlitherPuzzle` if blank puzzles are needed. - - Add parser tests with small, readable fixtures. - - If full URL support is too large, add a minimal loader path first and document the limit. - -2. **Renderer** - - Add a puzzle-specific board renderer or make an existing renderer safely plugin-aware. - - Render actual puzzle state, not placeholder marketing UI. - - Keep dimensions stable so large boards and zoom do not shift layout. - -3. **Rules** - - Add a puzzle-specific rule folder under `src/domain/rules//`. - - Start with small deterministic rules that produce clear messages and explicit `RuleDiff`s. - - Keep rule order in one aggregation file, like Slitherlink's `rules.ts`. - -4. **Solver integration** - - Ensure `getRules()` returns the new ordered rules. - - Add replay tests proving `nextStep`, `prevStep`, small `goToStep` moves, and large checkpoint-backed `goToStep` jumps rebuild the same state. - - Add terminal/completion analysis when the puzzle has a meaningful solved/stalled report. - -5. **Editor and export** - - Add editor tools only after parsing/rendering/rules are stable. - - Keep editor state normalized as `PuzzleIR` so it can load directly into the solver. - - Add export only for formats that can round-trip reliably. - -6. **Dataset and benchmark** - - Add small public fixtures only when they are useful and stable. - - Use private datasets for local experiments. - - Benchmark reports should summarize status, step count, timing, and rule usage. - ---- - -## 4. UI Integration Checklist - -For a puzzle family to feel first-class, decide which of these it owns: - -- Solver board rendering and highlights. -- Live Stats coverage semantics for its chosen IR fields. -- Editor board rendering and input tools. -- Puzzle type controls in Solver, Editor, and Dataset pages. -- `PuzzleInfoButton` content via plugin `help`. -- `BoardLegendButton` content via plugin `legend`. -- Board-title statistics via plugin `getStats`. -- Dataset preview rendering. -- Export controls and error messages. - -Prefer plugin-aware shared components when the behavior is generic. Prefer -puzzle-specific components when the interaction model is genuinely different -from Slitherlink. - -Live Stats currently derives board progress and coverage from common IR fields -such as decided edges, filled cells, and narrowed vertices. If a new puzzle uses -different state primitives, either make those primitives fit the shared -coverage model or add a small plugin-aware adapter before presenting the stats -as meaningful. - ---- - -## 5. Suggested Roadmap - -**Milestone 1: Parse, render, sample puzzle** - -- A sample puzzle can load through the plugin. -- The board displays the puzzle accurately. -- Tests cover parser basics and rendering smoke behavior. - -**Milestone 2: Deterministic starter rules** - -- Add a small ordered rule set. -- Each rule returns explainable messages and explicit diffs. -- Replay tests prove forward/backward timeline behavior, including timeline jumps. -- Live Stats shows sane active-prefix counts for the generated trace. - -**Milestone 3: Editor and export** - -- Add minimal editor tools for the puzzle's givens and user-editable state. -- Solver can load the editor puzzle without conversion hacks. -- Export round-trips when supported. - -**Milestone 4: Completion, datasets, UI polish** - -- Add solved/stalled analysis for terminal reports. -- Add curated public dataset entries and benchmark coverage. -- Add help, legend, and stats where they clarify the puzzle. - -**Milestone 5: Stronger inference** - -- Add advanced or branch-based inference only after deterministic rules are stable. -- Inject deterministic rule dependencies instead of self-referencing exported rule arrays. -- Keep branch reasoning conservative and test contradiction cases carefully. - ---- - -## 6. Implementation Cautions - -- Do not put puzzle-specific rules into shared solver orchestration. -- Do not mutate puzzle state inside a rule; return `RuleDiff`s. -- Do not change diff semantics without updating engine behavior, checkpoint replay, and replay tests. -- Do not hide non-determinism behind rule ordering or object iteration. -- Do not rely on object identity or hidden mutation for replay or stats; caches may be reused across timeline browsing. -- Do not overfit UI to Slitherlink if the next puzzle needs different primitives. -- Do not claim full support in docs, dropdowns, or datasets until parse/render/solve basics exist. - -The best first version is small, explainable, and replay-safe. Coverage can grow -incrementally once that spine is solid. diff --git a/docs/MASYU_AGENT_BRIEF.md b/docs/MASYU_AGENT_BRIEF.md index 17a1f90..1de144a 100644 --- a/docs/MASYU_AGENT_BRIEF.md +++ b/docs/MASYU_AGENT_BRIEF.md @@ -1,117 +1,91 @@ # Masyu Agent Brief -This is the lightweight starting point for AI agents working on Masyu in PuzzleKit Web. Read this first. Only open the longer docs if the task needs them. +This is the low-token starting point for AI agents working on Masyu in PuzzleKit Web. Read this first, then inspect the exact source files for the task. -## Read-On-Demand Route +## Current State -- Implement a Masyu rule: read this brief, then inspect the relevant `src/domain/rules/masyu/*` files. -- Research Puzzlink Assistance strategy: also read `docs/MASYU_ASSIST_STRATEGIES_CN.md`. -- Design a new rule family: also read `docs/MASYU_RULE_ABSTRACTIONS.md`. -- Check historical context: also read `docs/MASYU_CHANGELOG.md`. -- Change plugin, IR, replay, stats, or app-wide architecture: also read `docs/PROJECT_GUIDE_EN.md`. +Masyu is a first-class puzzle family with: -## Current State +- puzz.link-compatible import/export for `masyu`, `mashu`, and `pearl` URLs. +- Penpa+ import. +- Solver rendering, replay, explanations, stats, legend/help content, and completion analysis. +- Editor support for blank custom grids, URL import, black/white pearl placement, and load-to-solver flow. +- Deterministic rules plus bounded, explainable strong-inference rules. +- Tile-color topology support for inside/outside reasoning. -Masyu is implemented as a first-class puzzle family with import, rendering, replay-safe rules, completion analysis, and tile-color topology support. +Public Masyu dataset manifests are not yet committed to `dataset/public`; use private manifests for local experiments until curated public examples are added. -Canonical model: +## Canonical Model - `PuzzleIR.cells`: pearl clues, stored as `{ kind: "pearl"; color: "white" | "black" }`. -- `PuzzleIR.lines`: canonical Masyu loop decisions. These are center-to-center line segments between orthogonally adjacent cells. -- `PuzzleIR.tiles`: vertex-centered region-color units for Masyu inside/outside reasoning. +- `PuzzleIR.lines`: canonical Masyu loop decisions, connecting orthogonally adjacent cell centers. +- `PuzzleIR.tiles`: vertex-centered region-color units for inside/outside reasoning. - `PuzzleIR.edges`: Slitherlink edge state. Do not use it as Masyu loop state. -Important coordinate convention: +Coordinate conventions: - Cell keys are `row,col`, zero-based. -- Masyu line keys connect cells: `lineKey([r, c], [nr, nc])`. -- Masyu tile keys are grid vertices: `tileKey(row, col)` where `row = 0..rows`, `col = 0..cols`. - -## Current Rule Stack - -Registered rule order: - -1. `White Circle Rule` -2. `Black Circle Rule` -3. `Black Facing Consecutive Whites` -4. `Black Diagonal White Pinch` -5. `Consecutive White Pearls Straight` -6. `Double Black Squeeze` -7. `Masyu Tile Color Propagation` -8. `Masyu Color-Pearl Propagation` -9. `Masyu Color-Line Propagation` -10. `Masyu Tile Connectivity Cut Coloring` -11. `Masyu Candidate Bridge Line` -12. `Prevent Premature Loop` -13. `Black Pearl Candidate Pruning` -14. `Pearl Completion` -15. `Cell Completion` -16. `Black Pearl Strong Inference` - -Implemented rule areas: +- Masyu line keys connect cells with `lineKey([r, c], [nr, nc])`. +- Masyu tile keys are grid vertices with `tileKey(row, col)` where `row = 0..rows` and `col = 0..cols`. + +## Rule Stack + +Rule registration lives in `src/domain/rules/masyu/rules.ts`. Verify the current order there before changing or describing exact execution order. + +Implemented rule areas include: - Pearl-local rules for white straight-through and black turn/extension behavior. -- Local pattern rules derived from common Masyu situations. -- Premature loop prevention over `PuzzleIR.lines`. -- Black pearl candidate pruning with shallow feasibility checks. -- Black pearl strong inference with bounded trial propagation that crosses out an exit when that exit's two-step assumption leads to a hard contradiction. -- Completion rules for pearl and non-pearl cells. -- Tile color propagation: - - boundary tiles are `yellow` / outside; - - known `blank` lines imply same-color adjacent tiles; - - known `line` lines imply opposite-color adjacent tiles; - - white pearl diagonal tiles imply opposite colors; - - same-color adjacent tiles imply a `blank` Masyu line; - - opposite-color adjacent tiles imply a `line` Masyu line; - - tile connectivity cuts color articulation regions needed to connect known inside/outside regions; - - regions unreachable from outside/yellow through non-line passages become inside/green; - - tile fills are replay-safe via `TileDiff`; - - Masyu tile colors render on the board as full-size vertex-centered tiles. +- Local named patterns around black/white pearl interactions. +- Line graph constraints, cell-exit completion, and premature-loop prevention. +- Candidate bridge reasoning and pearl candidate pruning. +- Adjacent white pearl lookahead. +- Tile color propagation, color-line implications, color-pearl implications, and connectivity cut coloring. +- Bounded strong inference for black pearls, white pearls, and empty cells. + +Default expectation: deterministic rules run first, then bounded strong-inference rules reuse the deterministic rule list for local propagation. ## Architecture Hotspots Use these files first: -- Masyu rule registration: `src/domain/rules/masyu/rules.ts` -- Masyu geometry helpers: `src/domain/rules/masyu/rules/shared.ts` +- Rule registration: `src/domain/rules/masyu/rules.ts` +- Shared geometry and state helpers: `src/domain/rules/masyu/rules/shared.ts` - Pearl rules: `src/domain/rules/masyu/rules/pearls.ts` - Pattern rules: `src/domain/rules/masyu/rules/patterns.ts` - Loop rules: `src/domain/rules/masyu/rules/loop.ts` - Tile color rules: `src/domain/rules/masyu/rules/color.ts` - Tile connectivity rules: `src/domain/rules/masyu/rules/connectivity.ts` -- Candidate bridge rules: `src/domain/rules/masyu/rules/bridges.ts` +- Candidate bridge and pruning rules: `src/domain/rules/masyu/rules/bridges.ts`, `src/domain/rules/masyu/rules/candidates.ts` - Lookahead helpers: `src/domain/rules/masyu/rules/lookahead*.ts` -- Tests: `src/domain/rules/masyu/rules.test.ts` +- Masyu parser/exporter: `src/domain/parsers/puzzlink/masyuPuzzlink.ts`, `src/domain/parsers/penpa/index.ts` +- Masyu editor: `src/features/editor/MasyuEditorBoard.tsx`, `src/features/editor/editorStore.ts` +- Tests: `src/domain/rules/masyu/rules.test.ts`, `src/domain/ir/masyu.test.ts`, parser tests Replay and rendering plumbing: - Rule diffs: `src/domain/rules/types.ts` - Diff application: `src/domain/rules/engine.ts` +- Solver timeline/checkpoints: `src/features/solver/solverStore.ts` - Board rendering: `src/features/board/CanvasBoard.tsx` -- Solver timeline/highlights: `src/features/solver/solverStore.ts` - -## Current Development Direction -Near-term Masyu work should focus on making tile color useful beyond connectivity coloring: +## Development Direction -1. Add pearl-local color implications: - - migrate selected Puzzlink in/out tricks only when they can be explained as small Masyu tile parity rules. +Near-term Masyu work should favor small, maintainable rule improvements over broad rewrites: -2. Keep rule granularity small: - - one reasoning idea per rule; - - explicit diffs; - - concise explanation message; - - focused fixture tests. +- Consolidate repeated line, tile, candidate, and graph primitives when duplication becomes costly. +- Prefer named deterministic rules before adding broader assumption search. +- Keep bounded inference explainable through contradiction or common-conclusion messages. +- Add focused fixture tests whenever a rule changes. -## How To Start A Task +Historical Masyu planning notes live under `docs/legacy/`. They are reference material, not the current source of truth. -Default workflow: +## Default Workflow 1. Read this brief. -2. Inspect the exact rule/helper files touched by the task. -3. Search existing tests before writing a new rule. -4. Prefer extending local Masyu helpers over copying Slither code directly. -5. Run focused tests first, then build. +2. Inspect `src/domain/rules/masyu/rules.ts` and the exact helper files touched by the task. +3. Search existing tests before adding a new fixture. +4. Prefer extending local Masyu helpers over copying Slitherlink code directly. +5. Run focused tests first, then build when the change has wider impact. Useful commands: @@ -121,28 +95,11 @@ pnpm test:run src/domain/rules/engine.test.ts src/features/solver/solverStore.te pnpm build ``` -## When To Read More - -Read `docs/MASYU_RULE_ABSTRACTIONS.md` when designing a new rule family or checking intended rule taxonomy. - -Read `docs/MASYU_ASSIST_STRATEGIES_CN.md` only when tracing a deduction back to Puzzlink Assistance. It is research/provenance, not the implementation source of truth. - -Read `docs/MASYU_CHANGELOG.md` only when historical context matters. - -Read `docs/PROJECT_GUIDE_EN.md` when changing plugin contracts, IR conventions, replay, stats, or app-wide architecture. - -## Maintenance Rules - -- Update this brief whenever rule order, canonical state, or next development direction changes. -- Keep this brief current, not historical. Move history to `docs/MASYU_CHANGELOG.md`. -- Keep this brief short enough that it can be pasted into an AI context without drowning the actual task. -- Prefer links and routing over duplicating long explanations. - ## Guardrails - Do not use `PuzzleIR.edges` for Masyu loop deductions. - Do not mutate `PuzzleIR` inside rule inspection. - Do not batch unrelated reasoning into one rule. - Do not overwrite already-decided line/tile state with the opposite value. -- Do not make long Puzzlink-style monolithic rules; keep steps explainable. -- If a doc disagrees with current code, trust current code and update this brief. +- Do not make unbounded search part of the default solver. +- If a doc disagrees with current code, trust current code and update the doc. diff --git a/docs/MASYU_ASSIST_STRATEGIES_CN.md b/docs/MASYU_ASSIST_STRATEGIES_CN.md deleted file mode 100644 index 4ec54d6..0000000 --- a/docs/MASYU_ASSIST_STRATEGIES_CN.md +++ /dev/null @@ -1,553 +0,0 @@ -# Puzzlink Assistance 中的 Masyu 求解策略调研 - -本文调研油猴脚本 `Puzzlink_Assistance.js` 里作者对 Masyu 的自动辅助策略,重点比较 `SingleLoopInCell` 与 `SingleLoopInBorder` 这两个通用单环推理框架。 - -结论先行: - -- `SingleLoopInCell` 适合 Masyu 这类“环经过格子中心”的谜题。它把格子当作图的顶点,把相邻格子之间的 border 当作边,目标是形成一条经过指定格子的单一回路。 -- `SingleLoopInBorder` 适合 Slitherlink 这类“环画在格子边界上”的谜题。它把格点 cross 当作图的顶点,把 border 当作边,同时用格子染色表示环的内外侧。 -- 二者共同解决的是单环基础约束:不能分叉、不能死端、不能提前形成小环、所有线段最终必须属于同一个连通分量。 -- Masyu 的珠子规则不是全部塞进通用单环框架,而是在 `SingleLoopInCell({ isPass: c => c.qnum !== CQNUM.none })` 之后追加一组白珠、黑珠和局部图形模式。 - -## 脚本中的基础状态 - -脚本基于 puzz.link / pzpr 的对象模型工作。几个状态很关键: - -- `border.line === 1`:这条边已经确定为线,脚本用 `isLine(b)` 判断,用 `add_line(b)` 写入。 -- `border.qsub === BQSUB.cross`:这条边已经确定不能走线,脚本用 `isCross(b)` 判断,用 `add_cross(b)` 写入。 -- `cell.lcnt`:一个格子周围已经接入的线数。对 Masyu 的格心路径来说,它就是该格子的当前度数。 -- `cross.qsub === CRQSUB.in / out`:交叉点上的隐式内外侧标记。`SingleLoopInCell` 会把这些标记用作“线两侧”的拓扑约束。 -- `cell.qsub === CQSUB.green / yellow`:格子背景色。`SingleLoopInBorder` 用它区分 Slitherlink 环内外。 -- `cell.path`、`cross.path`、`board.linegraph.components.length`:puzz.link 已经维护好的线段连通分量信息。脚本用它判断“如果现在连上这条边,是否会提前闭合一个小环”。 - -所有规则都不是一次性求完整解,而是在 `assist()` 的循环中反复调用。每次 `add_line`、`add_cross`、`add_inout` 等写入一个确定状态后,`stepcheck()` 增加推理步数;循环继续运行,直到没有新结论、达到步数上限或超时。 - -## `SingleLoopInCell`:格心路径模型 - -`MasyuAssist()` 的第一步是: - -```js -SingleLoopInCell({ - isPass: c => c.qnum !== CQNUM.none, -}); -``` - -这里的 `isPass` 表示“这个格子最终必须被环经过”。对 Masyu 来说,所有白珠、黑珠格都必须被环经过,普通空格则可以经过也可以不经过。 - -### 图模型 - -`SingleLoopInCell` 把棋盘看作如下图: - -```text -cell --border-- cell --border-- cell -``` - -- 图的顶点是格子中心。 -- 图的边是两个相邻格子之间的 border。 -- 一条 `line` 表示环从一个格子中心走到相邻格子中心。 -- 一个 `cross` 表示这两个格子中心不能直接相连。 - -这与 Masyu 规则天然一致:Masyu 的线是穿过格子中心的正交路径,珠子对“路径在该格怎么进出、下一格怎么走”施加约束。 - -### 可配置参数的意义 - -`SingleLoopInCell` 是通用函数,不只服务 Masyu。它的参数给不同谜题留了接口: - -- `isPassable(cell)`:这个格子是否允许被路径经过。默认所有格子都可经过。 -- `isPathable(border)`:这条边是否还可能成为路径。默认只要不是 `cross` 就可走。 -- `isPass(cell)`:这个格子是否已经或必须被路径经过。Masyu 传入“有珠子的格子”。 -- `isPath(border)`:这条边当前是否是路径。默认是 `isLine`。 -- `add_notpass(cell)` / `add_pass(cell)`:推出格子不经过或经过时的写入动作。Masyu 没有显式写格子状态,所以基本不用。 -- `add_notpath(border)` / `add_path(border)`:推出边不能走或必须走时的写入动作。默认分别是 `add_cross` 和 `add_line`。 -- `Directed`:用于需要方向箭头的单环谜题。Masyu 不使用。 -- `hasCross`:用于允许路径在格内交叉的变体。Masyu 不使用。 - -### 推理流程 - -`SingleLoopInCell` 大致按以下层次工作。 - -### 1. 内外侧传播 - -在没有冰块、没有交叉路径的普通情形下,函数先在 `cross` 上维护 `CRQSUB.in / out`。直观上,格心路径的一条线会把相邻两个 cross 分在环的两侧;一条禁止线的边则不会穿过环,两侧保持同侧。 - -核心关系是: - -- 如果两个相邻 cross 同为 `in` 或同为 `out`,它们之间的 border 不能是线,应当为 `cross`。 -- 如果两个相邻 cross 一内一外,它们之间的 border 必须是线。 -- 如果 border 已经是 `cross`,两端 cross 的内外侧应相同。 -- 如果 border 已经是 `line`,两端 cross 的内外侧应相反。 - -代码最后用 DFS 从已经有 `in/out` 的 cross 出发传播这些关系: - -```js -if (cr.qsub === ncr.qsub) { add_cross(b); } -if (cr.qsub !== ncr.qsub) { add_line(b); } -if (isntLine(b)) { add_inout(ncr, cr.qsub); } -if (isLine(b)) { add_inout(ncr, cr.qsub ^ CRQSUB.inout); } -``` - -这是一种很强的拓扑推理:它不直接看珠子,而是把“单环把平面分成内外”这个事实编码到 cross 的二染色里。 - -### 2. 不能提前闭合小环 - -函数会遍历每个已有线段分量的端点。若某条候选边会连接同一个 `path` 分量的两个端点,并且棋盘上还有其他线段分量或还有必须经过但尚未经过的格子,那么这条边会形成一个提前闭合的小环,必须打叉。 - -对 Masyu 来说,这条规则非常重要。因为 Masyu 最终只能有一个环,不能先在局部珠子附近形成一个已经闭合的圈,然后让其他珠子留在圈外。 - -可以理解为: - -```text -如果 A 和 B 已经属于同一条未完成路径, -且 A--B 这条候选边会闭合它, -但还有其他必须纳入的内容, -那么 A--B = cross。 -``` - -### 3. 度数约束:不能分叉,不能死端 - -对每个格子,函数统计: - -- `linecnt`:四周已有多少条线接入。 -- `emptycnt`:四周还有多少条可走候选边。 - -随后推出: - -- 如果某格已有 2 条线接入,它在单环中度数已经满了,其他方向全部 `cross`。 -- 如果某格可走方向小于等于 1,那么它不可能成为环上的普通点,周围候选边全部 `cross`。 -- 如果某格必须经过,或者已经有 1 条线接入,而可走方向只剩 2 条,则这两条可走边必须都成为 `line`。 - -这就是单环题最基础的“每个经过点度数为 2”。 - -### 4. 连通分量阻断 - -如果一个格子尚未接线,但它周围所有可走邻居都已经属于同一个路径分量,那么从这个格子进入再出去会把同一分量提前闭合。函数会把相关候选边打叉。 - -相反,如果某个必须经过的格子周围候选边被分成两个路径分量,并且每个分量只能通过某些边进入这个格子,那么这些边会被临时标为 `lineaux`,再进一步推出必须走的边。这部分代码相当于一种“弹回 / bouncing”推理:为了避免把两个入口选择错成同一分量,必须让路径穿过这个格子把两个分量连起来。 - -### 5. 可选方向推理 - -`Directed` 分支会在边上记录箭头,用于带方向的环题。它可以推出: - -- 一个度数为 2 的格子,如果一条线方向已知,另一条线方向也能确定。 -- 两个只有一个端口的路径端点如果具有相同的入/出方向,则不能相连。 -- 根据环的整体顺逆时针方向,把箭头和 `in/out` 关系互相传播。 - -Masyu 没有传 `Directed`,所以这些不是 Masyu 当前使用的核心策略,但它说明作者把 `SingleLoopInCell` 做成了较通用的“格心单环引擎”。 - -## `SingleLoopInBorder`:边界路径模型 - -`SlitherlinkAssist()` 的第一步是: - -```js -SingleLoopInBorder(); -``` - -Slitherlink 的线不穿过格子中心,而是画在格子边界上。因此它的自然图模型与 Masyu 不同: - -```text -cross --border-- cross - | cell | -cross --border-- cross -``` - -- 图的顶点是格点 cross。 -- 图的边是 border。 -- 数字格约束的是围绕该 cell 的四条 border 有几条是线。 -- 格子本身不在路径上,而是在环的内侧或外侧。 - -### 1. 格子内外染色 - -`SingleLoopInBorder` 先用 `CellConnected` 对格子做两次连通性搜索: - -- 绿色 `green` 表示一侧区域。 -- 黄色 `yellow` 表示另一侧区域,且棋盘外部视为黄色。 -- `line` 是内外区域之间的边界,不可穿过。 -- `cross` 表示这条边不是环,两个相邻格子在同一区域,可以连通。 - -于是有两条核心规则: - -- 相邻两格颜色不同,则它们之间必须有线。 -- 相邻两格颜色相同,则它们之间必须不是线,即 `cross`。 - -反过来也成立: - -- 若一条边是 `line`,两侧格子颜色应不同。 -- 若一条边是 `cross`,两侧格子颜色应相同。 - -这与 `SingleLoopInCell` 的 cross 内外侧传播很像,但作用对象不同。Masyu 的 `in/out` 标在格点 cross 上;Slitherlink 的 `green/yellow` 标在格子 cell 上。 - -### 2. NoCheckerCell:排除棋盘格状矛盾 - -`SingleLoopInBorder` 调用了 `NoCheckerCell`,目标是避免内外染色出现棋盘格式的局部矛盾。直观上,如果四个格子围绕一个交点交替内外,那么交点附近会要求四条边全部成为边界,导致顶点度数不符合单环规则。该规则把“内外区域不能以不合法方式交错”的拓扑事实变成染色推理。 - -### 3. 顶点候选集:cross 上只允许 0 条或 2 条线 - -Slitherlink 的每个 cross 是路径顶点。单环经过一个顶点时,必须恰好有 2 条 incident border 是线;如果不经过,则 0 条是线。 - -`SingleLoopInBorder` 用 `cross.qsub` 存一个 JSON 候选集: - -- 初始候选包含 `[]`,表示这个 cross 不被路径经过。 -- 还包含所有从四条 incident border 中任选两条的组合,表示路径从其中两条边进出。 -- 已经打叉的边会从候选集中删除。 -- 已经画线的边必须出现在候选中。 -- 如果所有候选都包含某条边,则该边必为 `line`。 -- 如果所有候选都不包含某条边,则该边必为 `cross`。 - -这相当于一个很小的局部约束传播器。它比单纯的“已有两条线就打叉、已有三条叉就补线”更强,因为它能把数字格和顶点候选联动起来。 - -### 4. 顶点度数的直接推理 - -除了候选集,函数还做直接计数: - -- 如果某 cross 已有 2 条线,则其他 incident border 都必须 `cross`。 -- 如果某 cross 已有 3 条 `cross`,则剩下那条也必须 `cross`,因为不能形成度数 1 的死端。 -- 如果某 cross 已有 1 条线且已有 2 条 `cross`,则剩下那条必须 `line`,补足度数 2。 - -### 5. 禁止提前闭环 - -对每条尚未决定的 border,如果它两端 cross 已经在同一个 `path` 分量中,并且棋盘上还有其他线段分量,那么画这条线会提前闭合小环。函数把它打叉。 - -这与 `SingleLoopInCell` 的提前闭环规则同源,只是判断对象从 cell path 改成 cross path。 - -## 二者的相同点与差异 - -### 相同点 - -- 都是“单一回路”推理引擎,不负责猜测,专门推出局部必然结论。 -- 都把未知边逐步决定为 `line` 或 `cross`。 -- 都维护度数约束:路径上的节点度数必须为 2,不能出现度数 1 的死端,也不能出现度数超过 2 的分叉。 -- 都利用 puzz.link 的 `linegraph.components` / `path` 信息禁止提前形成小环。 -- 都引入内外侧思想:单环会把平面分成内外两侧,颜色或 `in/out` 可继续反推线与叉。 -- 都作为题型专用规则的底座。Masyu 在其上加珠子规则,Slitherlink 在其上加数字格规则。 - -### 差异 - -| 维度 | `SingleLoopInCell` | `SingleLoopInBorder` | -| --- | --- | --- | -| 典型题型 | Masyu | Slitherlink | -| 路径位置 | 穿过格子中心 | 画在格子边界 | -| 图顶点 | cell | cross | -| 图边 | 相邻 cell 之间的 border | 相邻 cross 之间的 border | -| 约束核心 | 哪些格子必须被经过,经过格度数为 2 | 每个顶点度数为 0 或 2,每个数字格周围线数匹配数字 | -| 内外标记 | `cross.qsub = in/out` | `cell.qsub = green/yellow` | -| 候选传播 | 主要依靠格子度数、连通分量、内外侧传播 | 额外在每个 cross 上保存 incident edge 组合候选 | -| 提前闭环判断 | 连接同一 cell path 会不会闭合小环 | 连接同一 cross path 会不会闭合小环 | -| 题型局部规则 | 白珠直行并邻格转弯,黑珠转弯并两侧直行 | 数字格周围线数,常见 2/3/3-3 模式,区域染色 | - -一个简化理解是:`SingleLoopInCell` 解的是“哪些格子连成环”;`SingleLoopInBorder` 解的是“哪些边界围成环”。两者的 border 都是最终画线的对象,但图论意义不同。 - -## MasyuAssist 的专用策略 - -`MasyuAssist()` 在通用单环推理后,定义: - -```js -let isBlack = c => !c.isnull && c.qnum === CQNUM.bcir; -let isWhite = c => !c.isnull && c.qnum === CQNUM.wcir; -let isPathable = b => !b.isnull && !isCross(b); -``` - -然后对每个 cell、每个方向 `d` 套用一组旋转对称的局部规则。下面按策略含义解释。 - -### 1. 白珠与内外侧的关系 - -白珠规则:线必须直行通过白珠,并且至少在白珠相邻的一侧转弯。 - -代码中: - -```js -if (isWhite(cell) && offset(cell, .5, .5, d).qsub !== CRQSUB.none) { - add_inout(offset(cell, -.5, -.5, d), offset(cell, .5, .5, d).qsub ^ 1); -} -``` - -这是在利用白珠的“直行”性质传播对角 cross 的内外侧。白珠直行通过时,对角上的两侧关系可以被确定:一个角的 `in/out` 已知,另一个对角也能推出。它不直接画线,但会让后续内外侧传播推出 `line` 或 `cross`。 - -### 2. 黑珠夹在两个白珠对角之间时的内外侧关系 - -黑珠规则:线必须在黑珠处转弯,并且离开黑珠后的下一段仍要直行。 - -代码检查黑珠两侧对角位置有白珠: - -```js -if (isBlack(cell) && isWhite(offset(cell, -1, -1, d)) && isWhite(offset(cell, 1, 1, d)) && - offset(cell, .5, .5, d).qsub !== CRQSUB.none) { - add_inout(offset(cell, -.5, -.5, d), offset(cell, .5, .5, d).qsub); -} -``` - -以及另一个镜像关系。含义是:黑珠必须转弯,附近两个白珠又会要求直行并在邻格转弯,这个组合会固定某些角点的内外侧同异关系。作者把它作为拓扑传播规则,而不是显式枚举所有线型。 - -### 3. 两个黑珠中间一侧被堵:另一侧不能走 - -图形注释: - -```text - +×+ +×+ -● ● -> ● ● - + + +×+ -``` - -若一个空位左右各有黑珠,并且上方边已经不可走,那么下方边也必须不可走。 - -原因是黑珠如果朝中间延伸,会要求离开黑珠后继续直行;两个黑珠同时受限时,中间路径会造成无法满足的进出方式。作者用这个模式提前打叉,减少后续搜索。 - -### 4. 白珠已有一侧线,或垂直方向被堵:强制横向直行 - -图形注释: - -```text -+ + +×+ -━○ -> ━○━ -+ + +×+ -``` - -如果白珠左侧已有线,那么它必须从右侧出去,且上下不能走。或者如果上方不可走,白珠不能选择竖直轴,只能选择水平轴。于是: - -- 左右两侧加线。 -- 上下两侧打叉。 - -这是最基础的白珠补全:白珠必须直行,不能转弯经过。 - -### 5. 白珠一侧已经连续直行两段:另一侧不能继续直行太远 - -图形注释: - -```text -+ + + + + + + + -━━━○━╸ -> ━━━○━╸× -+ + + + + + + + -``` - -白珠要求“至少一侧相邻格要转弯”。如果白珠左侧已经出现连续两段直线,说明左侧相邻格没有转弯,那么右侧相邻格必须承担转弯义务,不能继续向右直行第二段。因此右侧远端边打叉。 - -这是白珠规则中容易漏掉的二阶约束:不仅白珠本身直行,白珠两侧的下一格还要至少一边发生转弯。 - -### 6. 连续白珠或两侧都无法承担直行轴:强制改用垂直轴 - -图形注释: - -```text -+ + + + + +┃+ + -━╸ ○ ○ -> ━╸×○×○ -+ + + + + +┃+ + -``` - -代码判断白珠左右两侧是否都不适合作为直行通过方向。触发条件包括: - -- 某侧已经有远端直线,使该侧无法满足白珠邻格转弯要求。 -- 某侧相邻格也是白珠,连续白珠会互相限制直行轴。 -- 某侧上下转弯候选都不可用,或被黑珠局部规则堵住。 - -当左右轴无法满足白珠要求时,白珠只能使用上下轴: - -- 左右打叉。 -- 上下加线。 - -这个规则体现了作者的模式化思路:不是只看白珠当前四边,而是看相邻一格、两格范围内的“这条轴能否合法完成白珠条件”。 - -### 7. 黑珠一侧不能作为出口:强制从另一侧进入,并堵掉坏方向 - -图形注释把多个情形合并: - -```text -+ + + : + + + : + +┃+ : + + + : + + + + + + -━● : ●× : ● ╹ : ● ● : ● × -> ━●× -+ + + ; + + + ; + + + ; + + + ; + + + + + + -``` - -对黑珠来说,若向右作为出口不合法,则左侧必须是线,右侧必须打叉。右侧不合法的原因包括: - -- 黑珠右侧边已经不可走。 -- 从黑珠向右走后,下一格不能继续直行。 -- 右侧相邻位置有黑珠,会冲突。 -- 右侧第二段不可走。 -- 某些相邻已有线会迫使黑珠不能按规则转弯后直行。 - -这对应黑珠基本规则:黑珠必须转弯,且离开黑珠的两个方向都要至少延伸一格。若某个方向不能满足“离开后继续直行”,该方向不能作为黑珠出口。 - -### 8. 黑珠已有一侧线:同方向第二段强制为线 - -图形注释: - -```text -+ + + + + + - ●━╸ -> ●━━━ -+ + + + + + -``` - -如果黑珠右侧已有线,那么这条线离开黑珠后必须继续直行一段,所以右侧第二段强制为线。 - -这正是黑珠“转弯后两边必须直行一格”的补全规则。 - -### 9. 黑珠面向两个连续白珠:反方向强制为线 - -图形注释: - -```text -+ + + + + + + + + + - ● ○ ○ -> ━● ○ ○ -+ + + + + + + + + + -``` - -如果黑珠某方向上隔一个空格后出现两个连续白珠,那么黑珠不能朝那边延伸。因为连续白珠会要求直行与邻格转弯,和黑珠离开后继续直行的需求冲突。于是黑珠必须选择反方向的一条线。 - -这个模式已经在当前项目的 `createBlackFacingConsecutiveWhitesRule()` 中有对应实现。 - -### 10. 黑珠被两个斜向白珠夹住:朝另一侧强制为线 - -图形注释: - -```text -+ + + + + + + + - ○ ○ ○ ○ -+ + + + -> + + + + - ● ● -+ + + + + +┃+ + -``` - -如果黑珠某侧的两个斜对角都是白珠,那么黑珠朝这一侧转弯/延伸会让两个白珠的转弯义务变得不可满足。于是黑珠必须朝相反方向延伸。 - -这个模式对应当前项目里的 `createBlackDiagonalWhitePinchRule()`。 - -### 11. 已有路径形状配合白珠/黑珠避免小环 - -后面几段以四条已有线构成一个拐角路径为前提: - -```js -[[0, .5], [0, 1.5], [.5, 0], [1.5, 0]].every(([dx, dy]) => isLine(offset(cell, dx, dy, d))) -``` - -这表示局部已经有一个 `┏` 形或类似的路径框架。若附近出现特定白珠或黑珠,且当前线图还有多个连通分量,作者会强制补出某些线,避免该路径框架闭合成局部小环或让珠子规则无解。 - -这些规则更像“模式库”: - -- 拐角路径旁有两个白珠时,补出穿过白珠的直线与转弯结构。 -- 拐角路径旁有黑珠与白珠时,补出黑珠离开后的直线延伸。 -- 若不补这些线,就会导致已有路径分量只能错误闭合,或违反白/黑珠的局部形态。 - -这些规则的解释性要求比较高。迁移时不建议只照搬坐标模式,而应把它们转写成“假设不走这条边会造成提前闭环或珠子不可满足”的小型反证规则。 - -### 12. 基于连通性的黑珠强制 - -代码: - -```js -if (isBlack(cell) && cell.path !== null && cell.path === offset(cell, 2, 0, d).path && - offset(cell, 1, 0, d).path === null && board.linegraph.components.length > 1) { - add_line(offset(cell, -.5, 0, d)); -} -``` - -含义是:黑珠与同方向隔两格的 cell 已经属于同一条路径分量,而中间格还不在路径上。如果黑珠再朝这个方向走,可能会把同一分量过早接回,且黑珠还需要转弯后直行。因此反方向边被强制为线。 - -它把黑珠局部规则和全局单环连通性结合起来。 - -### 13. 基于连通性的白珠强制 - -代码: - -```js -if (isWhite(cell) && offset(cell, -1, 0, d).path !== null && offset(cell, -1, 0, d).path === offset(cell, +1, 0, d).path && - board.linegraph.components.length > 1) { - add_line(offset(cell, 0, +.5, d)); - add_line(offset(cell, 0, -.5, d)); -} -``` - -如果白珠左右两侧已经属于同一路径分量,那么让白珠横向直行会提前闭合该分量。因此白珠必须改走垂直方向,上下两边强制为线。 - -这是很典型的“白珠必须直行,但直行轴不能造成小环,所以选择另一条轴”的解释型规则。 - -## SlitherlinkAssist 在 `SingleLoopInBorder` 上追加的策略 - -为了对比,Slitherlink 的题型规则主要围绕数字格展开: - -- 若某数字格周围已有线数等于数字,其余边全部打叉。 -- 若某数字格周围未打叉的边数等于数字,所有未打叉边全部画线。 -- 利用 cross 上的候选集与数字格组合过滤,进一步收缩顶点候选。 -- 对常见局部形状加入模式,例如相邻 `3-3`、`2-3` 在特定边已叉时强制画线和打叉。 -- 基于内外染色,数字格周围相邻格的颜色数量可以反推该格是内侧还是外侧,也可以反推边是线还是叉。 - -因此 Slitherlink 的求解重心是“边界数量 + 顶点度数 + 内外区域”;Masyu 的求解重心是“格心路径 + 珠子局部几何 + 单环连通性”。 - -## 对可解释分步求解器的迁移建议 - -### 1. 把通用单环层和题型规则层拆开 - -建议将 Masyu 求解器分成两层: - -- 通用格心单环层:负责度数、死端、提前闭环、连通分量、必须经过格子的基础约束。 -- Masyu 珠子层:负责白珠直行并邻格转弯、黑珠转弯并两侧延伸,以及由珠子组合产生的模式。 - -这样每一步解释可以清楚地区分: - -- “这是所有单环题都成立的拓扑理由。” -- “这是 Masyu 珠子规则导致的局部必然。” - -### 2. 用 IR 中的 line/cell 图显式表达 `SingleLoopInCell` - -当前项目已经在 Masyu completion 和 rules 中把 line 视为相邻 cell 的连接。后续可以继续沿用: - -- 顶点:`cellKey(row, col)`。 -- 边:`lineKey([row1, col1], [row2, col2])`。 -- 度数:某 cell incident line 数。 -- 连通性:并查集维护 line 连接的 cell 分量。 -- 提前闭环:候选 line 两端已在同一分量,且还有其他 line 分量或未满足 pearl 时,候选 line 为 blank。 - -这与油猴脚本里的 `cell.path` / `linegraph.components` 思路一致,但在项目 IR 中更容易生成解释文本。 - -### 3. 每条规则都输出“局部事实 + 结论” - -例如白珠规则可以解释成: - -```text -白珠 (R3, C4) 必须直行通过。左侧已有线,因此右侧也必须为线,上下两侧不能走。 -``` - -黑珠规则可以解释成: - -```text -黑珠 (R5, C2) 已经向东连线。黑珠离开后必须继续直行一格,因此东侧第二段也必须为线。 -``` - -提前闭环规则可以解释成: - -```text -若画上 (R2, C2)-(R2, C3),会闭合同一个路径分量;但仍有其他线段或未满足珠子在环外,所以该线必须为空。 -``` - -这种解释比“匹配了某某模式”更适合分步可解释求解器。 - -### 4. 谨慎迁移坐标模式库 - -`MasyuAssist()` 中一些图形注释规则很强,但它们是以旋转坐标和已有线形写死的模式。迁移时建议分三类: - -- 基础珠子规则:优先迁移,解释清楚,风险低。 -- 珠子组合规则:例如黑珠面对连续白珠、黑珠斜向被白珠夹住,适合迁移为命名模式。 -- 复杂路径框架规则:应先写测试,最好用小型 lookahead 或反证表达,不要只照搬坐标。 - -### 5. 保留内外侧推理作为增强层 - -`SingleLoopInCell` 的 cross `in/out` 推理非常有价值,但对解释系统来说需要谨慎包装。它本质上是平面拓扑二染色: - -- 非线边两侧同色。 -- 线边两侧异色。 -- 同色推出非线。 -- 异色推出线。 - -若未来要实现,应把它作为“环内外侧标记”或“拓扑染色”规则,并在 UI 中可视化内外侧,否则用户可能难以理解为什么一个远处角点颜色能推出某条线。 - -## 最适合优先内化的策略清单 - -优先级从高到低: - -1. 格心单环基础:经过点度数为 2、不能死端、不能分叉。 -2. Masyu 基础珠子:白珠直行并至少一侧邻格转弯;黑珠转弯并两侧延伸一格。 -3. 提前闭环禁止:候选线连接同一分量且仍有其他内容未纳入时打叉。 -4. 白珠轴选择:某一轴被堵或无法满足邻格转弯时,强制另一轴。 -5. 黑珠出口可用性:某方向无法延伸一格时,禁止该方向。 -6. 黑珠面对连续白珠、黑珠被斜向白珠夹住等命名模式。 -7. 内外侧拓扑染色。 -8. 复杂路径框架 + 珠子组合的反证规则。 - -总体来看,作者的 Masyu 求解不是单纯堆局部珠子口诀,而是“通用单环拓扑引擎 + Masyu 珠子几何模式 + 少量连通性反证”的组合。这一点很适合迁移到分步可解释求解器:先让通用单环层稳定地产生基础结论,再逐步加入可命名、可测试、可解释的珠子策略。 diff --git a/docs/MASYU_CHANGELOG.md b/docs/MASYU_CHANGELOG.md deleted file mode 100644 index 2aff625..0000000 --- a/docs/MASYU_CHANGELOG.md +++ /dev/null @@ -1,214 +0,0 @@ -# Masyu Implementation Changelog - -## 2026-05-17 Deterministic Rule Increment - -This update adds the first replay-safe Masyu solving rules. The goal is still -incremental: keep each rule local, deterministic, explainable, and backed by -small fixtures before moving toward graph or coloring techniques. - -## Implemented - -- Added Masyu rule helpers in `src/domain/rules/masyu/rules/shared.ts`: - - Cardinal directions, opposite/turn checks, and direction offsets. - - Directional center-line lookup from a cell. - - Two-step line lookup for pearl extension logic. - - Line-decision collection helpers that avoid overwriting decided marks. -- Added pearl-local rules in `src/domain/rules/masyu/rules/pearls.ts`: - - `White Circle Rule`: white pearls go straight through the pearl, reject - blocked axes, blank perpendicular turn exits, and now enforce the adjacent - turn requirement when one side already runs straight for two segments. - - `Black Circle Rule`: black pearls turn, extend any known exit straight one - more cell, reject impossible exits, and blank the opposite side of a known - exit on the same axis. -- Added generic center-line completion in - `src/domain/rules/masyu/rules/completion.ts`: - - `Pearl Completion`: pearl cells get their own completion pass, separate - from ordinary cells. White pearls only complete straight-through exits; - black pearls only complete turn exits and try to extend confirmed exits one - more cell. - - Non-pearl cells with degree 2 blank every other candidate. - - Non-pearl cells with one known line and one remaining candidate force that - candidate as a line. - - Non-pearl dead-end candidates are blanked. -- Added graph and candidate look-ahead rules: - - `Prevent Premature Loop`: center-line loop components reject unknown lines - that would close a smaller loop while other confirmed lines remain outside. - - `Black Pearl Candidate Pruning`: enumerates the four black-pearl turns, - applies a shallow non-recursive feasibility check, and keeps only compatible - candidates before forcing shared exits, shared extensions, and excluded - adjacent exits. This rule is intentionally single-target per step, and its - white-pearl look-ahead validates an actual adjacent-turn overlay before - rejecting a candidate. - - `White Circle Rule` line forcing now checks both endpoints before adding a - line, so it will not push a neighboring cell above degree 2. -- Added local pattern rules in `src/domain/rules/masyu/rules/patterns.ts`: - - `Black Facing Consecutive Whites`: a black pearl facing two consecutive - white pearls two and three cells away is forced to leave the opposite way. - - `Black Diagonal White Pinch`: two diagonal white pearls on one side of a - black pearl force the black pearl away from that side. - - `Consecutive White Pearls Straight`: a run of three or more adjacent white - pearls is forced to pass perpendicular to the run. - - `Double Black Squeeze`: two black pearls with one middle cell between them - force the opposite perpendicular exit blank when the other perpendicular - exit is already blank. -- Registered the current Masyu rule order: - 1. `White Circle Rule` - 2. `Black Circle Rule` - 3. `Black Facing Consecutive Whites` - 4. `Black Diagonal White Pinch` - 5. `Consecutive White Pearls Straight` - 6. `Double Black Squeeze` - 7. `Prevent Premature Loop` - 8. `Black Pearl Candidate Pruning` - 9. `Pearl Completion` - 10. `Cell Completion` - -## Validation - -Focused tests live in `src/domain/rules/masyu/rules.test.ts` and cover: - -- White pearl straight-through, blocked-axis, and adjacent-turn deductions. -- Black pearl turn, extension, and impossible-exit deductions. -- Black-pearl local patterns. -- Consecutive white-pearl run patterns. -- Pearl-specific completion and double-black squeeze completion. -- Premature-loop prevention and black-pearl candidate pruning. -- Regression coverage for - `https://puzz.link/p?mashu/10/6/0000b6103260i0902216`, ensuring candidate - pruning does not batch multiple black pearls into one unsafe step. -- Regression coverage for a larger `mashu/49/39` puzzle where white-pearl - straight forcing previously created a degree-3 neighbor. -- Registration order and line-diff application on the sample Masyu puzzle. - -Commands run successfully: - -```bash -pnpm test:run src/domain/rules/masyu/rules.test.ts -pnpm lint -``` - -Focused result at implementation time: - -- 1 test file passed. -- 56 Masyu rule tests passed. - -## Notes For Future Agents - -- Keep using `PuzzleIR.lines` as the canonical Masyu loop state. -- New Masyu rules should continue to return explicit `LineDiff`s only unless a - future feature deliberately introduces replay support for another state field. -- Avoid contradiction masking: if a target line is already decided as the - opposite mark, skip the inference and leave invalidity reporting to a later - completion/analysis layer. -- The next useful rule families are graph-level single-loop constraints - (`premature loop prevention`, `candidate bridge`) and more white-pearl axis - elimination, before any Masyu coloring work. - -## 2026-05-16 Initial Import And Display Increment - -This update adds the first real Masyu support path to PuzzleKit Web. The goal of -this increment is intentionally narrow: import a Masyu `puzz.link` URL, preserve -the existing Slitherlink architecture, and render the imported board in the main -solver workspace. - -## Implemented - -- Added first-class Masyu IR fields: - - `PuzzleIR.lines`: canonical center-to-center loop decisions for Masyu. - - `PuzzleIR.tiles`: future vertex-centered coloring units. - - Pearl clues as `Clue { kind: "pearl"; color: "white" | "black" }`. -- Added Masyu key helpers: - - `lineKey`, `parseLineKey`, `getCellLineKeys`. - - `tileKey`, `parseTileKey`. -- Added `createMasyuPuzzle(rows, cols)`: - - Creates one unknown line for each orthogonally adjacent cell-center pair. - - Creates tiles at original grid vertex coordinates, `0..rows` and `0..cols`. - - Leaves Slitherlink-style `edges` and `sectors` empty. -- Added `decodeMasyuFromPuzzlink`: - - Accepts `masyu`, `mashu`, and `pearl`. - - Supports optional `v:` and `b` header segments. - - Decodes `number3` trits according to `docs/MASYU_ENCODE_METHOD.md`. - - Verified sample: - `https://puzz.link/p?mashu/5/5/001390360`. -- Updated `masyuPlugin`: - - Display name is now `Masyu`. - - Parser is wired to the new puzz.link decoder. - - Export intentionally throws: Masyu puzz.link export is not implemented yet. - - Rule/help text is present. - - Legend is a placeholder. - - Stats show board size and pearl distribution. -- Extended replay and stats plumbing: - - Added `LineDiff`. - - Rule engine can apply and revert line diffs. - - Rule steps may carry `affectedLines`. - - Trace stats treat Masyu line decisions as board progress. - - Slitherlink edge behavior remains unchanged. -- Added Masyu rendering in the solver board: - - Thin dashed inner grid. - - Thick solid outer border. - - Existing `R` / `C` coordinate labels. - - Centered white and black pearls. - - Center-to-center lines and crosses from `PuzzleIR.lines`. - -## Not Implemented Yet - -- Masyu solving rules. -- Masyu editor. -- Masyu dataset flow. -- Masyu-specific Live Stats labels. -- Masyu export back to puzz.link. -- Rule examples and rich legend diagrams. - -## Validation - -Use a modern local Node runtime. Debugging with local Node `v24.13.1` should be -fine. In this Codex environment, the bundled Node runtime was required because -the default shell Node was too old for the current `pnpm`. - -Commands run successfully: - -```bash -pnpm lint -pnpm build -pnpm test:run -``` - -Full test result at implementation time: - -- 16 test files passed. -- 278 tests passed. - -Focused tests added: - -- `src/domain/ir/masyu.test.ts` -- `src/domain/parsers/puzzlink/masyuPuzzlink.test.ts` -- Line diff coverage in `src/domain/rules/engine.test.ts` -- Masyu line progress coverage in `src/domain/difficulty/traceStats.test.ts` - -## Architecture Notes For Future Agents - -- `lines` is the canonical Masyu decision state. Do not reuse Slitherlink - `edges` for Masyu loop segments. -- `edges` remains Slitherlink-style vertex-to-vertex grid-edge state. -- `tiles` is reserved for future Masyu coloring over vertex-centered middle - cells. It is not currently rendered or inferred. -- Masyu line keys use cell coordinates, not vertex coordinates: - `lineKey([row, col], [neighborRow, neighborCol])`. -- `rows × cols` in the UI means the user-operated cell board size. -- Existing Slitherlink rules should not be generalized unless a Masyu feature - needs shared infrastructure. - -## Next Work Center - -The next development center should be stronger deterministic Masyu solving -rules. Use `docs/MASYU_RULE_ABSTRACTIONS.md` as the implementation-oriented -taxonomy and `docs/MASYU_ASSIST_STRATEGIES_CN.md` as provenance for the original -strategy source. - -Recommended next steps: - -- Add premature-loop prevention over Masyu `lines`. -- Add candidate-graph bridge inference over non-blank center-line candidates. -- Continue expanding local white-pearl axis elimination and optional pattern - rules with focused fixtures. -- Keep each rule small, named, deterministic, and backed by focused tests. diff --git a/docs/MASYU_RULE_ABSTRACTIONS.md b/docs/MASYU_RULE_ABSTRACTIONS.md deleted file mode 100644 index 114f07b..0000000 --- a/docs/MASYU_RULE_ABSTRACTIONS.md +++ /dev/null @@ -1,743 +0,0 @@ -# Masyu Rule Abstractions - -This document is an implementation-oriented rule taxonomy for adding deterministic Masyu solving to PuzzleKit. It abstracts the current Puzzlink Assistance strategy notes into rules that do not depend on that userscript's object model. - -The target reader is a developer or AI agent implementing `src/domain/rules/masyu/*` rules that return replay-safe `RuleDiff`s. Use `docs/MASYU_ASSIST_STRATEGIES_CN.md` only as provenance for the original strategy source; use this document as the implementation spec. - -## Current PuzzleKit Model - -Masyu currently uses a center-line model: - -- `PuzzleIR.cells`: stores pearl clues as `{ kind: "pearl", color: "white" | "black" }`. -- `PuzzleIR.lines`: stores center-to-center loop decisions using `LineState.mark`. -- `LineMark`: `unknown`, `line`, or `blank`. -- `PuzzleIR.tiles`: reserved for future region/corner coloring units. -- `PuzzleIR.edges`: remains available for Slitherlink-style vertex-to-vertex edges, but should not be the canonical Masyu loop state. - -Implementation should prefer small helpers that hide geometry details: - -- `getMasyuNeighborCells(puzzle, cellKey)`: orthogonal in-bounds neighbor cell keys. -- `getMasyuIncidentLineKeys(puzzle, cellKey)`: up to four center-line keys incident to a cell. -- `getMasyuDirectionalLine(puzzle, cellKey, direction, distance)`: the line at a directional offset, for pearl-local rules. -- `getMasyuCellDegree(puzzle, cellKey)`: count incident lines marked `line`. -- `getMasyuUnknownExits(puzzle, cellKey)`: incident lines still marked `unknown`. -- `buildMasyuLineComponents(puzzle)`: connected components of cells joined by `line` marks. -- `buildMasyuCandidateGraph(puzzle)`: graph of cells connected by lines that are not `blank`. - -Rules should produce explicit diffs: - -- Use `LineDiff` for loop decisions. -- Use `CellDiff` only if a future rule stores visible cell color/fill state. -- If Masyu coloring is added as a first-class hidden or visible state, prefer a dedicated IR field only after deciding how replay and rendering should expose it. Until then, treat coloring rules in this document as design guidance. - -## Rule Design Principles - -Each rule should be deterministic, local where possible, and replay-safe. - -- Do not mutate `PuzzleIR` while inspecting it. -- Collect all compatible updates in local maps, then return diffs. -- If a rule can infer both `line` and `blank`, reject or skip contradictory updates instead of masking them. -- Report a concise message with the first clear example and a total count. -- Keep each rule narrow enough that a failing test points to one reasoning idea. - -Recommended rule card fields: - -- Intent: what the rule proves. -- Input state: what facts it reads. -- Algorithm: implementation steps. -- Output diffs: what it can write. -- Explanation message: user-facing wording pattern. -- Tests: focused fixtures that prove the rule and replay. - -## Rule Family 1: Generic Single Loop In Cell - -These rules are puzzle-generic for loops that pass through cell centers. They should be implemented before pearl-specific rules because black and white pearl deductions depend on them. - -### Rule: Pearl Pass-Through Degree - -Intent: every pearl cell must have loop degree 2. - -Input state: - -- Pearl cells from `PuzzleIR.cells`. -- Incident center lines from `PuzzleIR.lines`. - -Algorithm: - -1. For each pearl cell, count incident `line`, `blank`, and `unknown` lines. -2. If two incident lines are already `line`, mark all remaining unknown incident lines `blank`. -3. If exactly two incident lines are not `blank`, mark both as `line`. -4. If fewer than two incident lines are available, return no inference; completion analysis should report invalidity later. - -Output diffs: `LineDiff` from `unknown` to `line` or `blank`. - -Explanation message: `Pearl (Rr, Cc) must have degree 2, so the only two available exits are lines.` - -Tests: - -- Pearl with two unknown exits and two blanks. -- Pearl with two lines and two unknown exits. -- Border/corner pearl with only two possible exits. - -### Rule: Center-Line Degree - -Intent: the loop has degree 0 or 2 at every cell center, and degree 2 at pearl cells. - -Input state: - -- All cells, not only pearls. -- Incident center lines. - -Algorithm: - -1. For each cell, count incident `line` and `unknown` lines. -2. If `lineCount === 2`, mark remaining unknown incident lines `blank`. -3. If `lineCount === 1 && unknownCount === 1`, mark the only unknown line `line`. -4. If `lineCount === 0 && unknownCount === 1` for a non-pearl cell, mark the only unknown line `blank`; using it would create a dead end. -5. For pearl cells, delegate forced two-exit logic to `Pearl Pass-Through Degree` or share a helper. - -Output diffs: `LineDiff`. - -Explanation message: `Cell (Rr, Cc) already has two loop lines, so every other exit is blank.` - -Tests: - -- A path entering a cell with only one unknown continuation. -- A completed degree-2 cell. -- A non-pearl dead-end candidate. - -### Rule: Premature Loop Prevention - -Intent: do not close a smaller loop before all required pearls are in the final loop. - -Input state: - -- Current `line` graph on cell centers. -- Unknown center lines. -- Pearl cells that still need to be included. - -Algorithm: - -1. Build a union-find over cell centers connected by existing `line` marks. -2. For each unknown line between cells `a` and `b`, check whether `a` and `b` are already in the same component. -3. If they are in the same component, adding this line closes a cycle. -4. Mark the line `blank` unless this closure would be the final valid loop containing all required pearl cells and no unresolved line component remains. A first implementation can conservatively blank same-component closures whenever any pearl has degree less than 2 or there is more than one active line component. - -Output diffs: `LineDiff` to `blank`. - -Explanation message: `Line (Rr, Cc)-(Rr2, Cc2) would close the current path before all pearls are connected, so it is blank.` - -Tests: - -- A nearly closed small loop with an outside pearl. -- A same-component candidate when multiple active components exist. -- A fully solved final loop should not be processed by this rule as a new inference. - -Implementation analogy: this is the center-line counterpart of Slitherlink's `createPreventPrematureLoopRule()`, but vertices become cell centers and `edges` become `lines`. - -### Rule: Candidate-Graph Bridge Line - -Intent: if all required loop material can stay connected only through a candidate line, that line must be used. - -Input state: - -- Candidate graph where every non-blank line is an edge. -- Source nodes: pearl cells and cells already incident to a line. -- Current known line components. - -Algorithm: - -1. Build the candidate graph from all cell centers and lines whose mark is not `blank`. -2. Treat existing line components and pearl cells as required sources. -3. Run Tarjan low-link analysis to find bridges or articulation structures that separate required sources. -4. If a candidate line is the only connection between two required-source sides, mark it `line`. -5. Keep the first version conservative: only infer when a single unknown line is the bridge between two source-containing components. - -Output diffs: `LineDiff` to `line`. - -Explanation message: `Line (Rr, Cc)-(Rr2, Cc2) is the only candidate connection between required loop regions, so it is a line.` - -Tests: - -- Two pearl groups connected by a one-cell-wide corridor. -- Existing line endpoint that can reach the rest of the puzzle through only one unknown line. -- No inference when two independent candidate corridors exist. - -Implementation analogy: this rule uses the same low-link idea as Slitherlink color connectivity cut coloring, but the graph is the Masyu candidate line graph rather than a colored-cell region graph. - -## Rule Family 2: Pearl-Local Rules - -These rules encode the Masyu clue semantics directly. They should work from directional axes rather than from copied pattern diagrams. - -Use directions `N`, `E`, `S`, `W`. An axis is an opposite pair, such as `E-W` or `N-S`. - -### Rule: Black Pearl Turn - -Intent: a black pearl must turn at the pearl cell. - -Input state: - -- Black pearl cell. -- Incident lines and blanks. -- Two axis groups: north-south and east-west. - -Algorithm: - -1. For each axis group, count incident `line` and non-blank candidate exits. -2. A valid black pearl uses exactly one exit from each axis group. -3. If one direction in an axis group is already `line`, mark the opposite direction in that axis group `blank`. -4. If an axis group has exactly one non-blank candidate and no line yet, mark that candidate `line`. -5. If an axis group has zero candidates, return no inference; completion analysis should report the contradiction. - -Output diffs: `LineDiff` to `blank` or `line`. - -Explanation message: `Black pearl (Rr, Cc) must use one vertical and one horizontal exit, so the only remaining vertical candidate is a line.` - -Tests: - -- Black pearl with an east line forces west blank. -- Black pearl with only one vertical candidate forces that vertical candidate to line. -- Black pearl with no vertical candidates produces no diff and is left for completion analysis. - -### Rule: Black Pearl Straight Extension - -Intent: after leaving a black pearl, the loop must continue straight for at least one more cell. - -Input state: - -- Black pearl cell. -- A known incident line in direction `d`. -- The next line in direction `d` from the neighboring cell. - -Algorithm: - -1. For each black pearl and direction `d`, if the incident line in `d` is `line`, find the next forward line beyond the adjacent cell. -2. If that next line is unknown, mark it `line`. -3. If that next line is already `blank`, do not infer here; invalidity belongs to completion analysis. - -Output diffs: `LineDiff` to `line`. - -Explanation message: `Black pearl (Rr, Cc) exits east, so the line must continue straight through the next cell.` - -Tests: - -- Each direction extension. -- Extension at board boundary should produce no diff. -- Existing extension line should be ignored. - -### Rule: Black Pearl Impossible Exit - -Intent: remove any black-pearl exit direction that cannot satisfy the turn-and-extension constraint. - -Input state: - -- Black pearl cell. -- Candidate incident direction `d`. -- Neighbor cell and forward extension line in direction `d`. -- Side lines through the neighbor cell. -- Nearby pearl clues. - -Algorithm: - -For each candidate direction `d`, mark the incident line `blank` if any of these are true: - -- The incident line is already unavailable. -- The forward extension line beyond the neighbor is unavailable. -- The neighbor already has a perpendicular line that would prevent straight extension. -- The neighbor is another black pearl, making the required straight continuation incompatible with that pearl's turn. -- Taking direction `d` would force the black pearl to go straight through the pearl cell. - -After blanking an impossible exit, ordinary degree rules can force the remaining exits. - -Output diffs: `LineDiff` to `blank`. - -Explanation message: `Black pearl (Rr, Cc) cannot exit east because the required straight extension is blocked.` - -Tests: - -- Blocked forward extension. -- Neighboring black pearl. -- Perpendicular line at the required extension cell. - -### Rule: White Pearl Straight Through - -Intent: a white pearl must go straight through the pearl cell. - -Input state: - -- White pearl cell. -- Incident lines and blanks. - -Algorithm: - -1. If an incident line on one side of an axis is `line`, mark the opposite side of that axis `line`. -2. Mark both perpendicular incident lines `blank`. -3. If both sides of one axis are unavailable, mark both sides of the other axis `line`. -4. If exactly one axis remains possible, force it. - -Output diffs: `LineDiff` to `line` and `blank`. - -Explanation message: `White pearl (Rr, Cc) must go straight, so the opposite exit is also a line and perpendicular exits are blank.` - -Tests: - -- Known east line forces west line and north/south blank. -- North/south blocked forces east-west line. -- One remaining possible axis. - -### Rule: White Pearl Adjacent Turn Requirement - -Intent: a white pearl must turn in at least one adjacent cell immediately before or after the pearl. - -Input state: - -- White pearl cell. -- A chosen or implied straight axis. -- Lines one and two steps away along each side of that axis. - -Algorithm: - -1. If one side of the white pearl already continues straight through the adjacent cell, then the opposite side must provide the required adjacent turn. -2. Concretely, if the line entering the pearl from west and the line west of the neighboring west cell are both `line`, mark the far east continuation line `blank`. -3. Apply symmetrically for all axes and directions. - -Output diffs: `LineDiff` to `blank`. - -Explanation message: `White pearl (Rr, Cc) already continues straight on one side, so the other adjacent cell must turn.` - -Tests: - -- Two consecutive line segments on one side of a white pearl. -- Symmetry across all four directions. - -### Rule: White Pearl Axis Elimination - -Intent: eliminate a white-pearl straight axis when either side cannot provide a valid adjacent turn condition. - -Input state: - -- White pearl cell. -- Candidate axis. -- Neighbor cells on both sides of the axis. -- Local line/blank states around those neighbors. -- Nearby pearl clues that constrain those neighbors. - -Algorithm: - -1. For each side of a candidate axis, determine whether that side can still satisfy the white pearl's adjacent-turn requirement. -2. A side cannot satisfy the requirement if the adjacent cell is forced to continue straight, is blocked from turning, or is itself a pearl whose constraints conflict with the required turn. -3. If both sides of an axis cannot satisfy the requirement, mark the two incident lines of that axis `blank` and force the perpendicular axis through `White Pearl Straight Through`. -4. Keep individual blockers as separate helper predicates so explanations stay short. - -Output diffs: `LineDiff` to `blank` and possibly `line`. - -Explanation message: `White pearl (Rr, Cc) cannot use the east-west axis because neither adjacent side can turn, so it must use the north-south axis.` - -Tests: - -- Adjacent white pearl blocks an axis. -- Existing far straight line blocks the adjacent-turn requirement. -- Both adjacent turn positions blocked. - -## Rule Family 3: Local Pattern Rules - -These are deterministic pattern rules derived from common Masyu shapes. Implement them after the core pearl-local rules, and keep each one optional and independently tested. - -### Rule: Double-Black Squeeze - -Intent: two black pearls on opposite sides of a cell can eliminate a perpendicular singleton exit. - -Input state: - -- A middle cell. -- Two black pearls on opposite sides along an axis. -- One perpendicular incident line from the middle cell already blank. - -Algorithm: - -1. For each cell and axis, check whether the two opposite neighbor cells are black pearls. -2. If one perpendicular line from the middle cell is `blank`, mark the other perpendicular line `blank`. -3. Rely on degree and black-pearl extension rules to handle any forced axis lines afterward. - -Output diffs: `LineDiff` to `blank`. - -Explanation message: `The cell between two black pearls cannot use a single perpendicular exit, so the opposite perpendicular exit is blank.` - -Tests: - -- Horizontal black pair with north blocked implies south blank. -- Vertical black pair with east blocked implies west blank. - -### Rule: Black Facing Consecutive Whites - -Intent: a black pearl may be forced away from a direction that leads into two consecutive white pearls. - -Input state: - -- Black pearl. -- Two white pearls at offsets two and three cells in direction `d`. - -Algorithm: - -1. For each black pearl and direction `d`, inspect the two cells at distance 2 and 3. -2. If both are white pearls, mark the opposite incident line from the black pearl `line`. -3. Let black pearl turn and extension rules clean up the remaining exits. - -Output diffs: `LineDiff` to `line`. - -Explanation message: `Black pearl (Rr, Cc) cannot satisfy its exit toward two consecutive white pearls, so it must extend the opposite way.` - -Tests: - -- Horizontal and vertical examples. -- No inference when only one white pearl exists. - -### Rule: Black Diagonal-White Pinch - -Intent: two diagonal white pearls on the same side of a black pearl force the black pearl away from that side. - -Input state: - -- Black pearl. -- Two white pearls at the diagonal cells on one side. - -Algorithm: - -1. For each black pearl and side `s`, inspect the two diagonal cells in front-left and front-right of that side. -2. If both are white pearls, mark the incident line opposite side `s` as `line`. -3. Use normal black-pearl rules to infer the turn partner and extension. - -Output diffs: `LineDiff` to `line`. - -Explanation message: `Two diagonal white pearls pinch black pearl (Rr, Cc), forcing it to leave away from them.` - -Tests: - -- All four orientations. -- No inference when one diagonal is empty or black. - -### Rule: White Neighbor Axis Exclusion - -Intent: neighboring white pearls and blocked turn cells can eliminate a white pearl axis. - -Input state: - -- White pearl. -- Candidate straight axis. -- Adjacent cells along that axis. -- Pearl clues and line states around those adjacent cells. - -Algorithm: - -1. For each candidate axis, evaluate both adjacent side cells. -2. Mark a side as axis-hostile when it is a white pearl in a position that would require incompatible straight-through behavior, or when both turn exits around that side are blocked. -3. If both sides are axis-hostile, blank the candidate axis and force the perpendicular axis. - -Output diffs: `LineDiff`. - -Explanation message: `White pearl (Rr, Cc) cannot use the east-west axis because both neighboring turn positions are unavailable.` - -Tests: - -- Consecutive white pearls on both sides. -- Turn exits blocked by blanks. -- Mixed blockers on the two sides. - -### Rule: L-Path Pearl Continuation - -Intent: an existing L-shaped partial path plus nearby pearl constraints can force continuation lines. - -Input state: - -- A local L shape made of four known center lines around a corner. -- Nearby white or black pearls at fixed offsets. -- More than one active line component, or an otherwise unresolved loop. - -Algorithm: - -1. Detect a local L-shaped path segment using center-line geometry. -2. Check for specific pearl configurations around the open ends: - - two white pearls near the diagonal continuation, - - a black pearl near one end and a white pearl near the other, - - a black pearl that must extend away from the L shape. -3. Add the continuation lines that are forced by pearl semantics and by avoiding a premature local loop. -4. Implement each pearl configuration as a named subrule rather than one large pattern. - -Output diffs: `LineDiff` to `line`. - -Explanation message: `The L-shaped path around (Rr, Cc) and nearby pearls force this continuation to keep the loop connected.` - -Tests: - -- One fixture per subrule. -- Regression test ensuring the rule does not fire after the loop is already complete. - -## Rule Family 4: Masyu Coloring - -Masyu can use a coloring strategy analogous to Slitherlink, but the colored objects differ. - -In Slitherlink, the loop runs on grid edges, so coloring cells as inside/outside works directly: same-colored adjacent cells imply a blank edge; opposite-colored adjacent cells imply a line edge. - -In Masyu, the loop runs between cell centers. The natural colored objects are the small regions around grid corners, not the puzzle cells themselves. This project already reserves `PuzzleIR.tiles` for future vertex-centered coloring units. A Masyu coloring implementation should decide whether `tiles` represent these corner regions. - -### Coloring State - -Use two colors: - -- `inside` -- `outside` - -For implementation parity with Slitherlink, these could be stored as tile fills such as `green` and `yellow`, but the semantic names in rule code should remain `inside` and `outside`. - -Adjacency relation: - -- Two neighboring corner regions separated by no loop crossing have the same color. -- Two neighboring corner regions separated by a Masyu center line have opposite colors. - -Line relation: - -- If adjacent regions are same color, the separating center-line candidate is `blank`. -- If adjacent regions are opposite colors, the separating center-line candidate is `line`. -- If a center line is `line`, adjacent regions become opposite colors. -- If a center line is `blank`, adjacent regions become same color. - -This is the Masyu counterpart of `createColorEdgePropagationRule()`. - -### Rule: Masyu Outside Seeding - -Intent: seed the unbounded exterior region as outside. - -Input state: - -- Tile/corner-region graph. -- Border-adjacent regions. - -Algorithm: - -1. Identify corner regions connected to the board exterior without crossing a known line. -2. Mark them `outside`. -3. Propagate through known blank separations if those are represented. - -Output diffs: future tile/color diffs, or no implementation until the IR supports them. - -Explanation message: `The exterior region is outside, so connected border regions are outside.` - -Implementation analogy: Slitherlink `createColorOutsideSeedingRule()`. - -### Rule: Masyu Line-Color Propagation - -Intent: known line and blank marks propagate region color parity. - -Input state: - -- Known line marks. -- Known region colors. -- Region adjacency separated by each center-line candidate. - -Algorithm: - -1. For each known line, require opposite colors on its adjacent regions. -2. For each known blank, require same colors on its adjacent regions. -3. Use a parity union-find if many implications are processed together. - -Output diffs: future tile/color diffs. - -Explanation message: `This line is part of the loop, so the regions on its sides have opposite colors.` - -Implementation analogy: the second half of `createColorEdgePropagationRule()`. - -### Rule: Masyu Color-Line Propagation - -Intent: known region colors decide line marks. - -Input state: - -- Adjacent region colors. -- Unknown center-line candidate separating them. - -Algorithm: - -1. If two adjacent regions have the same color, mark the separating line `blank`. -2. If two adjacent regions have opposite colors, mark the separating line `line`. - -Output diffs: `LineDiff`. - -Explanation message: `The regions on both sides have opposite colors, so the separating Masyu line is part of the loop.` - -Implementation analogy: the first half of `createColorEdgePropagationRule()`. - -### Rule: Pearl-Local Color Implications - -Intent: pearl semantics can imply region color parity even before line marks are known. - -Input state: - -- Pearl type. -- Local region colors around the pearl. -- Candidate pearl axes. - -Algorithm: - -1. White pearl straight-through behavior implies consistent parity between diagonal corner regions around its straight axis. -2. Black pearl turn behavior implies parity relations across the turn quadrant and straight-extension cells. -3. Encode each implication as a small local color rule only when the geometry is unambiguous. - -Output diffs: future tile/color diffs, or `LineDiff` if colors decide lines immediately. - -Explanation message: `White pearl (Rr, Cc) must go straight, so these two corner regions have opposite parity.` - -Note: these are the algorithmic version of the original assist script's pearl-specific `in/out` propagation. Implement them after basic coloring works. - -### Rule: Masyu Connectivity Cut Coloring - -Intent: Tarjan cut analysis can force unknown regions to become inside or outside when they are required to preserve color-region connectivity. - -Input state: - -- Region graph where known lines are barriers and known blanks are passable connections. -- Known inside or outside source regions. -- Optional exterior source for outside. - -Algorithm: - -1. Compress already-connected same-color regions with union-find. -2. Build a candidate connectivity graph between color components through non-barrier adjacencies. -3. For a target color, treat known target-colored components as sources. -4. Run Tarjan DFS with `discovery` and `low` values. -5. If a component is an articulation point separating target-color sources, color it with the target color. -6. If a component is unreachable from any target source, color it with the opposite color. -7. Run once for inside and once for outside. - -Output diffs: future tile/color diffs. - -Explanation message: `Region connectivity forces this component to be inside because it is a cut between inside sources.` - -Implementation analogy: this is the direct Masyu-region version of Slitherlink's `createColorConnectivityCutColoringRule()` and `findConnectivityColorUpdates()`. - -## Suggested Rule Order - -Start with rules that use only current IR fields: - -1. `masyu-pearl-pass-through-degree` -2. `masyu-center-line-degree` -3. `masyu-white-straight-through` -4. `masyu-black-turn` -5. `masyu-black-straight-extension` -6. `masyu-black-impossible-exit` -7. `masyu-white-adjacent-turn` -8. `masyu-white-axis-elimination` -9. `masyu-premature-loop-prevention` -10. `masyu-candidate-graph-bridge-line` -11. Selected local pattern rules - -Add coloring after the IR has an agreed representation for Masyu regions: - -1. `masyu-outside-seeding` -2. `masyu-line-color-propagation` -3. `masyu-color-line-propagation` -4. `masyu-pearl-local-color-implications` -5. `masyu-connectivity-cut-coloring` - -Keep branch-based or contradiction-based inference out of the first implementation pass. Masyu should first reach parity with deterministic local and graph rules. - -## Implementation Milestones - -Milestone 1: geometry helpers. - -- Add Masyu line-direction helpers. -- Add degree and candidate-exit helpers. -- Add line component builder. -- Add formatter helpers for cells and center lines. - -Milestone 2: generic loop rules. - -- Implement degree rules and premature loop prevention. -- Add focused unit tests for each. -- Register rules through `masyuPlugin.getRules()`. - -Milestone 3: pearl-local rules. - -- Implement black and white pearl rules as separate files or separate factory functions. -- Use small fixtures for each direction and axis. -- Keep explanation messages specific. - -Milestone 4: graph connectivity. - -- Implement candidate-graph bridge/cut inference. -- Use Tarjan low-link helpers if they can be shared cleanly with Slitherlink; otherwise keep a Masyu-local graph helper. - -Milestone 5: optional coloring. - -- Decide whether `PuzzleIR.tiles` should store Masyu region colors. -- Implement color propagation and cut coloring only after replay and rendering semantics are clear. - -## Test Strategy - -Every rule should have tests that cover: - -- One minimal positive fixture. -- One non-firing fixture with a near miss. -- Replay safety: apply the rule, undo via engine replay, and reapply deterministically where existing test harnesses support this. -- Directional symmetry for local rules. -- Conflict avoidance: a rule must not emit a diff from a non-unknown line to a different mark. - -For graph rules, include: - -- A single forced bridge. -- Two alternative corridors where no bridge is forced. -- A same-component premature loop candidate. -- A final-loop-like position that should not be incorrectly blanked. - -For coloring rules, include: - -- Same color implies blank. -- Opposite color implies line. -- Line implies opposite color. -- Blank implies same color. -- Tarjan cut component between two target-color sources. -- Unreachable component becomes the opposite color. - -## Mapping From Strategy Notes To Rule Names - -Use these names when migrating from the older strategy document: - -- `珠子必须被回路经过`: `masyu-pearl-pass-through-degree` -- `度数为二`: `masyu-center-line-degree` -- `无死端`: `masyu-center-line-degree` -- `双出口强制成线`: `masyu-pearl-pass-through-degree` -- `禁止提前闭环`: `masyu-premature-loop-prevention` -- `单环连通桥成线`: `masyu-candidate-graph-bridge-line` -- `白珠被迫直行`: `masyu-white-straight-through` -- `白珠至少一侧转弯`: `masyu-white-adjacent-turn` -- `白珠轴线排除`: `masyu-white-axis-elimination` -- `黑珠非法出口排除`: `masyu-black-impossible-exit` -- `黑珠出口延伸`: `masyu-black-straight-extension` -- `黑珠避开连续白珠`: `masyu-black-facing-consecutive-whites` -- `黑珠斜白夹逼`: `masyu-black-diagonal-white-pinch` -- `双黑夹格垂直禁线`: `masyu-double-black-squeeze` -- `白珠同路径改轴`: `masyu-white-same-component-axis-elimination` -- `黑珠同路径避闭环`: `masyu-black-same-component-exit-elimination` -- `L形路径连通补线`: `masyu-l-path-pearl-continuation` -- `内外异色成线`: `masyu-color-line-propagation` -- `内外同色禁线`: `masyu-color-line-propagation` -- `线段翻转内外`: `masyu-line-color-propagation` -- `禁线保持内外`: `masyu-line-color-propagation` - -## Notes On Reusing Slitherlink Infrastructure - -The Slitherlink color rules are useful templates, not drop-in Masyu rules. - -Reusable ideas: - -- Parity union-find for color constraints. -- Low-link Tarjan traversal for articulation/cut coloring. -- Rule factories that collect local decisions before returning diffs. -- First-example explanation messages with aggregate counts. - -Do not reuse directly without adapting geometry: - -- Slitherlink `edges` are vertex-to-vertex loop edges; Masyu loop decisions are `lines` between cell centers. -- Slitherlink cell colors represent regions separated by grid edges; Masyu region colors should represent corner/tile regions separated by center lines. -- Slitherlink vertex degree is not the same as Masyu cell-center degree. - -The desired end state is conceptual reuse with Masyu-specific helpers, not shared code that hides different geometry behind misleading names. diff --git a/docs/PROJECT_GUIDE_EN.md b/docs/PROJECT_GUIDE_EN.md index ce90f61..0232572 100644 --- a/docs/PROJECT_GUIDE_EN.md +++ b/docs/PROJECT_GUIDE_EN.md @@ -1,128 +1,126 @@ # PuzzleKit Web Project Guide +This guide is the compact maintainer and AI-agent reference for PuzzleKit Web. The README explains the project at a public level; this file records the working architecture, repo contracts, and current capability boundaries. + ## 1. Project Intent -PuzzleKit Web is a frontend-first, rule-based logic puzzle solver focused on -machine reasoning quality rather than maximum solve rate. +PuzzleKit Web is a frontend-first, rule-based logic puzzle solver. It values reasoning quality over maximum solve rate. Core principles: -- Prefer explicit deduction over black-box search or SAT solving. -- Make every step replayable, inspectable, and explainable. -- Accept that some puzzles may stop at a stable incomplete state. -- Grow solver strength incrementally by adding deterministic, human-readable - inference rules. - -In short: this project is a logic reasoning engine with a UI, not a UI-first -puzzle editor. +- Prefer explicit deductions over black-box search. +- Keep every step replayable, inspectable, and explainable. +- Accept stable incomplete states when the current rules cannot continue. +- Grow solver strength through deterministic, human-readable rules plus bounded, explainable assumption rules. ## 2. Architecture Map ```text src/ - app/ # page composition and top-level routing/layout + app/ # routes, pages, and top-level layout domain/ # puzzle logic source of truth - benchmark/ # dataset validation and solver benchmark runner + benchmark/ # dataset validation and benchmark runner difficulty/ # trace statistics and difficulty snapshots exporters/ # export adapters - ir/ # puzzle IR schemas, key utilities, normalize/clone - parsers/ # puzz.link/penpa adapters + ir/ # PuzzleIR schemas, key helpers, normalize/clone + parsers/ # puzz.link-compatible and Penpa+ adapters plugins/ # plugin contracts and registry - rules/ # rule contracts, step engine, puzzle-specific rules - features/ # board rendering, solver controls, editor, stats, explanation + rules/ # rule contracts, rule engine, puzzle-specific rules + features/ # board rendering, solver controls, editor, stats, explanations test/ # test setup/runtime helpers dataset/ - public/ # committed benchmark/dataset manifests + public/ # committed curated manifests private/ # local-only manifests, ignored by git scripts/ benchmark-solve.ts docs/ - techniques/ # puzzle-specific solving technique notes + techniques/ # puzzle-specific technique notes + legacy/ # old plans and research notes ``` Boundary rule: - UI renders and orchestrates. -- Domain code owns parsing, IR, rules, replay semantics, and exports. -- Solver and editor are separate product surfaces that exchange normalized - `PuzzleIR`. -- Puzzle-family behavior enters through `PuzzlePlugin`, puzzle-specific domain - modules, or explicit renderer branches. - -## 3. Data Flow - -1. Parser converts URL/input into `PuzzleIR`. -2. Optional editor tooling creates or modifies initial IR. -3. Solver store loads the initial IR and resets replay state. -4. Rule engine runs ordered rules and returns one step at a time. -5. Each step stores rule metadata plus explicit diffs. +- Domain code owns parsing, IR, rules, replay semantics, completion analysis, and exports. +- Solver and editor are separate product surfaces that exchange normalized `PuzzleIR`. +- Puzzle-family behavior enters through `PuzzlePlugin`, puzzle-specific domain modules, or explicit renderer/editor branches. + +## 3. Core Data Flow + +1. Parser or editor creates a normalized `PuzzleIR`. +2. Solver store loads the initial IR and resets replay state. +3. The plugin supplies an ordered rule list. +4. The rule engine returns one `RuleApplication` at a time. +5. Each step stores rule metadata, explanation text, highlights, and explicit diffs. 6. Replay applies or reverts diffs, with checkpoints for large timeline jumps. 7. Board, stats, and explanation panels render the active replay state. -This contract is the heart of the app: solver output must remain deterministic, -replay-safe, and explainable. +This contract is central: solver output must remain deterministic, replay-safe, and explainable. ## 4. Plugin Contract Puzzle families are registered in `src/domain/plugins/registry.ts`. -Each `PuzzlePlugin` owns its family boundary: +Each `PuzzlePlugin` may provide: -- `parse(input)` converts supported input into normalized `PuzzleIR`. -- `encode(puzzle)` exports a puzzle to a supported URL/string format. -- `getRules()` returns the ordered rule list used by the solver. -- `help` powers the puzzle rules popout. -- `legend` powers board legend examples. -- `getStats(puzzle)` powers compact board-title puzzle stats. +- `parse(input)` for supported URLs or strings. +- `encode(puzzle)` for supported exports. +- `getRules()` for the ordered solver rule list. +- `help` for the puzzle rules popout. +- `legend` for board legend examples. +- `getStats(puzzle)` for compact board-title stats. +- `displayOptions` for puzzle-specific board toggles. Current families: -- Slitherlink: parser, renderer, editor, rules, stats, completion analysis, and - export support are implemented. -- Masyu: puzz.link import, IR, renderer, stats, help, and replay plumbing are - implemented; solving rules and export are still planned. -- Nonogram: visible as a planned plugin stub. +- **Slitherlink**: parser, Penpa+ import, renderer, editor, rules, stats, completion analysis, puzz.link export, datasets, and benchmark flow are implemented. +- **Masyu**: puzz.link-compatible import/export, Penpa+ import, renderer, editor, stats, help/legend, replay plumbing, completion analysis, deterministic rules, tile-color topology, and bounded strong inference are implemented. Solver strength is still evolving. +- **Nonogram**: planned plugin stub only. ## 5. IR And Diff Conventions -`PuzzleIR` is the shared normalized state between parser, rules, replay, board, -editor, and exporters. +`PuzzleIR` is the shared state between parser, editor, solver, rules, board rendering, exporters, and datasets. Important state buckets: -- `cells`: cell clues and cell-local visual state. +- `cells`: clues and cell-local visual state. - `edges`: Slitherlink-style vertex-to-vertex grid-edge decisions. -- `lines`: Masyu-style cell-center-to-cell-center line decisions. +- `lines`: Masyu-style center-to-center line decisions. - `sectors`: Slitherlink corner-sector constraints. -- `tiles`: future vertex-centered coloring units, currently introduced for - Masyu. -- `vertices`: vertex candidate state for Slitherlink inference. +- `tiles`: vertex-centered coloring units used by Masyu inside/outside reasoning. +- `vertices`: Slitherlink vertex candidate state. -`RuleDiff` is the replay contract. If a rule mutates a new IR bucket, add a diff -type and update both: +`RuleDiff` is the replay contract. If a rule mutates a new IR bucket, update both: - `src/domain/rules/engine.ts` - `src/features/solver/solverStore.ts` -Keep forward and reverse replay behavior aligned. Timeline replay must not -diverge from direct rule execution. +Forward and reverse replay behavior must stay aligned. Timeline replay must not diverge from direct rule execution. -## 6. Puzzle Techniques +## 6. Current Capability Snapshot -Do not put puzzle-specific solving techniques in this project guide. Use the -technique notes instead: +Implemented: -- `docs/techniques/masyu.md` -- `docs/techniques/slitherlink.md` +- Solver workspace for import, step solving, replay, explanations, live stats, terminal reports, and export. +- Editor workspace for Slitherlink and Masyu custom boards, URL import, and load-to-solver flow. +- Dataset page with filters, previews, and load-to-Solver/Editor actions. +- Plugin-powered rules help, board legend, board display toggles, and compact puzzle stats. +- Slitherlink puzz.link-compatible parse/encode, Penpa+ import, deterministic rules, branch-based inference, completion analysis, editor tools, and public dataset example. +- Masyu puzz.link-compatible parse/encode, Penpa+ import, renderer, editor, deterministic rules, completion analysis, tile-color propagation, candidate pruning, bridge reasoning, premature-loop prevention, and bounded strong inference. +- Public/private benchmark manifest workflow. +- GitHub Pages release workflow for tagged builds. -That file points to the current Slitherlink rule modules, the Masyu changelog, -the Masyu URL encoding reference, and the Masyu strategy research document: +Partial or planned: +- Public Masyu dataset manifests are not yet committed to `dataset/public`. +- Penpa+ support is import-oriented; export completeness is not a current guarantee. +- Puzzle-specific Live Stats wording can still be refined. +- Difficulty modeling is draft-level. +- Nonogram remains planned. ## 7. Benchmark And Dataset Flow -Benchmarks evaluate solver behavior across JSON dataset manifests. They are for -solver quality and rule-usage analysis, not for unit-test correctness. +Benchmarks evaluate solver behavior across JSON dataset manifests. They are for solver-quality and rule-usage analysis, not unit-test correctness. Data locations: @@ -136,93 +134,47 @@ Run: pnpm benchmark:solve ``` -The benchmark runner scans public/private manifests, runs each puzzle with the -default plugin rule order, and writes reports to -`benchmark-results/.report.json`. +The benchmark runner scans public/private manifests, runs each puzzle with the default plugin rule order, and writes reports to `benchmark-results/.report.json`. -Current defaults: +Default benchmark settings: - `maxSteps = 2000` - `timeoutMs = 60000` - `ruleProfile = "default"` -Report intent: +Reports include per-puzzle status, step count, duration, terminal completion report, `ruleUsage`, and compact `ruleSteps`. Full `steps` are intentionally omitted to keep reports small. -- Per puzzle: status, step count, duration, terminal completion report, - `ruleUsage`, and compact `ruleSteps`. -- `steps` is intentionally empty for now to keep reports small. -- `ruleSteps[ruleId] = [stepNumbers...]` records where each rule fired. +## 8. AI Agent Quick Start -## 8. Current Capability Snapshot - -Implemented: +Read in this order for broad context: -- Solver workspace for import, solving, replay, explanation, live stats, - terminal reports, and export. -- Editor workspace for constructing Slitherlink puzzles before loading into the - solver. -- Public Dataset page with filters, previews, and load-to-Solver/Editor actions. -- Plugin-powered rule help, board legend, and compact board-title stats. -- Slitherlink puzz.link parse/encode and Penpa import. -- Slitherlink editor tools for clues, line edges, crosses, erasing, custom sizes, - and built-in presets. -- Slitherlink deterministic and branch-based inference pipeline. -- Slitherlink completion analysis. -- Masyu puzz.link import for `masyu`, `mashu`, and `pearl`. -- Masyu IR support through `lines`, `tiles`, and pearl clues. -- Masyu solver-board rendering for dashed grids, pearls, center lines, and - crosses. -- Replay support for both edge diffs and line diffs. -- Live Stats trace cache for step-prefix summaries, chart progress, and rule - usage. -- Public/private benchmark manifest workflow. -- GitHub Pages release workflow for tagged builds. - -Planned or partial: - -- Masyu deterministic solving rules. -- Masyu editor, dataset flow, completion analysis, and URL export. -- Nonogram parser, renderer, editor, and rules. -- Puzzle-specific Live Stats wording beyond the current shared labels. -- Penpa adapter/export completeness. -- Better calibrated difficulty modeling. - -## 9. AI Agent Quick Start - -If you are an AI agent onboarding this repository, read in this order: - -1. `src/domain/rules/types.ts` -2. `src/domain/rules/engine.ts` -3. `src/domain/ir/types.ts` +1. `src/domain/ir/types.ts` +2. `src/domain/rules/types.ts` +3. `src/domain/rules/engine.ts` 4. `src/domain/plugins/types.ts` 5. `src/domain/plugins/registry.ts` 6. `src/features/solver/solverStore.ts` -7. `docs/techniques/PUZZLE_TECHNIQUES_EN.md` for puzzle-specific rule work +7. The relevant note in `docs/techniques/` For targeted work: - Slitherlink rules: start at `src/domain/rules/slither/rules.ts`. -- Masyu import/display: start at `docs/MASYU_CHANGELOG.md`. -- Masyu future rules: start at `docs/MASYU_ASSIST_STRATEGIES_CN.md`. -- Editor/UI work: inspect the relevant `src/features/*` component and page test. -- Benchmark work: read `src/domain/benchmark/runner.ts` and - `scripts/benchmark-solve.ts`. +- Masyu rules: start at `docs/MASYU_AGENT_BRIEF.md`, then inspect `src/domain/rules/masyu/rules.ts`. +- Editor/UI work: inspect the relevant `src/features/*` component plus page tests. +- Benchmark work: read `src/domain/benchmark/runner.ts` and `scripts/benchmark-solve.ts`. +- Historical Masyu plans: check `docs/legacy/` only when old design context is useful. When editing: - Keep changes domain-first and minimally scoped. - Preserve deterministic replay semantics. -- Preserve explainable rule messages and explicit diffs. -- Add or adjust tests alongside parser, IR, replay, or rule changes. +- Preserve explicit diffs and explainable rule messages. +- Add or adjust tests beside parser, IR, replay, or rule changes. - Do not commit private datasets or generated benchmark reports. -## 10. Development Commands - -Use a modern Node runtime. Local Node `v24.13.1` is suitable for current -development. Older Node versions may fail before project scripts start because -the configured pnpm version requires a newer runtime. +## 9. Commands And Release Flow -Commands: +Use Node.js 20 from `.nvmrc` and pnpm 10.33.0 from `package.json`. ```bash pnpm install @@ -234,13 +186,6 @@ pnpm benchmark:solve pnpm test:e2e ``` -## 11. Deployment And Release Flow +CI runs linting, unit tests, and build on pushes and pull requests targeting `main`. -- Package management is standardized on pnpm 10.33.0 via `packageManager` in - `package.json`. -- CI runs on pushes and pull requests targeting `main`. -- CI installs with `pnpm install --frozen-lockfile`, then runs linting, unit - tests, and build. -- GitHub Pages deployment is triggered by pushing a `v*` tag. -- The deployment workflow builds `dist/`, copies `dist/index.html` to - `dist/404.html` for SPA fallback, then publishes `dist/`. +GitHub Pages deployment is triggered by pushing a `v*` tag. The deployment workflow builds `dist/`, copies `dist/index.html` to `dist/404.html` for SPA fallback, then publishes `dist/`. diff --git a/docs/techniques/masyu.md b/docs/techniques/masyu.md index b7e72ec..3b76969 100644 --- a/docs/techniques/masyu.md +++ b/docs/techniques/masyu.md @@ -1,37 +1,400 @@ # Masyu -Masyu is a loop puzzle with black and white pearls. The final answer is one continuous loop that passes through every pearl. +Masyu is a loop puzzle with black and white pearls. The final answer is one +continuous loop that passes through every pearl. -This page is a short user-facing technique note. AI agents and developers should start from `docs/MASYU_AGENT_BRIEF.md` instead. +This page describes the Masyu solving rules currently implemented in PuzzleKit. +It is organized by reasoning family rather than by the exact execution order in +the solver. -## Core Rules +## Core Puzzle Rules - The loop travels between cell centers and never branches. - Every pearl is visited by the loop. -- A white pearl is passed through straight, and the loop must turn in at least one adjacent cell before or after it. -- A black pearl is turned on, and the loop must go straight for at least one cell before and after the turn. -- The final loop must be a single loop, not several disconnected loops or an early small loop. +- A white pearl is passed through straight, and the loop must turn in at least + one adjacent cell before or after it. +- A black pearl is turned on, and the loop must go straight for at least one + cell before and after the turn. +- The final loop must be a single loop, not several disconnected loops or an + early small loop. -## PuzzleKit Model +## PuzzleKit Model And Notation PuzzleKit represents Masyu with a center-line model: -- `PuzzleIR.lines` stores the Masyu loop decisions. +- `PuzzleIR.lines` stores Masyu loop decisions. These are center-to-center + segments between orthogonally adjacent cells. - `PuzzleIR.cells` stores pearl clues. -- `PuzzleIR.tiles` stores vertex-centered inside/outside colors used by Masyu coloring rules. +- `PuzzleIR.tiles` stores vertex-centered inside/outside colors used by Masyu + coloring rules. - `PuzzleIR.edges` is Slitherlink state and is not the Masyu loop model. -## Current Support +Notation used below: -- Import from Masyu-family `puzz.link` URLs. -- Render pearls, center-to-center loop segments, crosses, and tile colors. -- Replay deterministic solving steps with explanations. -- Analyze completion for a single valid loop and satisfied pearl constraints. -- Apply local pearl rules, selected local patterns, premature-loop prevention, candidate pruning, completion rules, and Masyu tile-color propagation. +- `W` = white pearl. +- `B` = black pearl. +- `?` = unknown line segment. +- `=` or `|` = confirmed loop line. +- `x` = crossed-out line segment. +- `Y` = outside/yellow tile. +- `G` = inside/green tile. -## Developer Pointers +Line diagrams show decisions between cell centers. Tile-color diagrams describe +the vertex-centered tile colors that sit around those lines. -- Start here for Masyu development: `docs/MASYU_AGENT_BRIEF.md` -- Current rule taxonomy: `docs/MASYU_RULE_ABSTRACTIONS.md` -- Original strategy research: `docs/MASYU_ASSIST_STRATEGIES_CN.md` -- Historical implementation notes: `docs/MASYU_CHANGELOG.md` +## Core Pearl Rules + +### White Pearl Rule + +A white pearl must be passed through straight, and at least one adjacent cell on +the path must turn. + +This rule can decide line and crossed-out line segments around a white pearl: + +- If one incident side is already a line, the opposite side is forced as a line + and the turn-direction exits are crossed out where possible. +- If both straight exits are already known, the perpendicular exits are crossed + out. +- If one straight axis is blocked, or if that axis cannot still satisfy the + required adjacent turn, the other straight axis is forced. +- If one side already continues straight for two segments, the opposite side's + straight continuation is crossed out so the white pearl can still turn near + the pearl. + +Example: a white pearl with one horizontal exit already used must continue +horizontally through the pearl. + +```text +? x ? + | +== W == forces the opposite horizontal line and rejects vertical exits + | +? x ? +``` + +### Black Pearl Rule + +A black pearl must turn on the pearl, and both exits from the pearl must continue +straight for one more segment. + +This rule can decide lines and crossed-out lines near a black pearl: + +- If one exit is already known, the opposite exit is crossed out and the next + segment in the known exit direction is forced. +- If two turning exits are already known, all remaining exits are crossed out + and both straight extensions are forced. +- If an exit direction cannot provide the required two-step path, that exit is + crossed out. +- When only the opposite direction remains viable for a blocked direction pair, + the first and second segments in the viable direction are forced. + +Example: once a black pearl exits upward, it cannot also exit downward, and the +upward path must continue straight for one more segment. + +```text + | + | + B + x +``` + +## Local Pearl Patterns + +### Black Facing Consecutive Whites + +If a black pearl faces two consecutive white pearls with one gap between the +black pearl and the first white pearl, the black pearl cannot exit toward that +white pair. It is forced to exit away from the pair. + +This rule determines a confirmed line segment from the black pearl in the +opposite direction. + +```text +B ? . W W forces the black pearl away from the whites +``` + +### Black Diagonal White Pinch + +If two white pearls sit diagonally on the same side of a black pearl, they pinch +that side: the black pearl cannot use exits toward them while still satisfying +the local pearl constraints. + +This rule determines a confirmed line segment from the black pearl away from the +pinched side. + +```text +W W + B forces B away from the two diagonal whites +``` + +### Consecutive White Pearls Straight + +A run of three or more consecutive white pearls forces each pearl in the run to +pass perpendicular to the run. + +This rule determines confirmed line segments through the pearls on the +perpendicular axis. + +```text +W ? W ? W horizontal run + +| | | +W W W each pearl is forced vertically +| | | +``` + +The same logic applies to vertical runs, forcing horizontal passage through the +white pearls. + +### White Corridor + +If an existing L-shaped corridor has two white pearls tucked just inside the +corner, and there are still multiple known line components, the corridor cannot +locally close or turn away from both pearls. The two white pearls are forced to +go straight through the corner-facing axes. + +This rule determines four confirmed line segments: the first white pearl passes +parallel to one corridor arm, and the second white pearl passes parallel to the +other arm. + +```text +--. + | + W forces the two nearby whites straight through +. W + | +``` + +### Double Black Squeeze + +If a non-pearl cell sits between two black pearls, that middle cell cannot keep +exactly one perpendicular exit. If one perpendicular exit is already crossed +out or points off-board, the other perpendicular exit is crossed out too. + +This rule determines crossed-out line segments at the middle cell. + +```text + ? +B ? . ? B + x + +If one vertical exit of the middle cell is x or off-board, the other vertical +exit is x too. +``` + +## Completion Rules + +### Cell Exit Completion + +Cell Exit Completion applies local exit completion to every cell. It uses +ordinary loop-degree logic for empty cells and pearl-shape logic for pearl cells. + +This rule can determine confirmed lines and crossed-out lines: + +- For ordinary cells, degree 2 crosses out every other unknown exit; degree 1 + with one unknown exit forces continuation; degree 0 with one unknown exit + crosses out that dead-end candidate. +- For white pearls, exits must be opposite. A known exit keeps only its opposite + exit, and a single remaining straight axis is completed. +- For black pearls, exits must be adjacent. A known exit crosses out its + opposite exit, and a single remaining turn pair is completed. +- Black pearl straight extensions are handled by Black Pearl Rule, not by this + completion rule. + +```text + | +-- . ? degree 2 already reached, so the remaining ? becomes x +``` + +## Loop And Connectivity Rules + +### Prevent Premature Loop + +The final answer must be one continuous loop. An unknown segment is crossed out +if adding it would close a smaller loop while other confirmed line segments +remain outside that loop. + +This rule determines crossed-out line segments. It only blocks premature loop +closures; it does not complete the final loop by itself. + +The same premature-loop check also applies to ordinary empty cells with exactly +two remaining unknown exits and no confirmed exits. If using both exits would +close a smaller loop while other confirmed line segments remain outside it, both +exits are crossed out. + +### Masyu Candidate Bridge Line + +The solver builds a candidate graph from all confirmed and still-unknown Masyu +lines. Pearls and existing confirmed line components are treated as required +regions that must eventually belong to one loop. + +If an unknown candidate line is the only remaining bridge between required loop +regions, that line is forced. + +This rule determines confirmed line segments. + +```text +required region == ? == required region + +If the ? segment is the only candidate bridge, it becomes a line. +``` + +## Candidate And Lookahead Rules + +### Black Pearl Candidate Pruning + +For each black pearl, the solver enumerates feasible local turn candidates. A +black candidate consists of two turning exits, their required straight +extensions, and the crossed-out non-exits. + +This rule determines: + +- confirmed lines that appear in every feasible black pearl candidate; +- crossed-out exits that appear in no feasible black pearl candidate. + +Candidate feasibility checks local line consistency, cell degree, affected pearl +possibility, and premature-loop creation. + +### White Pearl Candidate Pruning + +For each white pearl, the solver enumerates feasible straight-axis candidates. A +white candidate consists of one straight axis, crossed-out perpendicular exits, +and at least one side where the required adjacent turn can still happen. + +This rule determines: + +- confirmed lines that appear in every feasible white pearl candidate; +- crossed-out exits that appear in no feasible white pearl candidate. + +Candidate feasibility checks local line consistency, cell degree, affected pearl +possibility, and whether the white pearl can still satisfy its adjacent-turn +requirement. + +### Adjacent White Pearls LookAhead + +For adjacent white pearls, the solver compares two local modes: + +- both pearls pass straight through on parallel paths; +- one straight line passes through both pearls. + +If only one of those modes remains locally feasible, the rule applies that +mode's line and crossed-out-line decisions. + +```text +W ? W + +Mode 1: one line passes through both pearls. +Mode 2: both pearls use parallel perpendicular paths. +Only the feasible mode is kept. +``` + +## Tile Color Rules + +Tile colors are vertex-centered region markers used for inside/outside +reasoning. In the current UI, yellow represents outside and green represents +inside. + +### Masyu Tile Color Propagation + +Known Masyu lines and crosses create parity constraints between neighboring +tiles: + +- boundary tiles are yellow; +- a confirmed line separates opposite tile colors; +- a crossed-out line connects same-colored tiles. + +This rule determines tile colors when those parity constraints imply a unique +green or yellow fill. + +```text +Y x Y same color across a crossed-out line +G | Y opposite colors across a confirmed line +``` + +### Masyu Color-Pearl Propagation + +A white pearl is passed through straight, so opposite diagonal tiles around that +white pearl must have opposite colors. + +If one diagonal tile color is known, this rule determines the opposite diagonal +tile color. + +```text +G ? + W +? Y opposite diagonals around W must differ +``` + +### Masyu Color-Line Propagation + +Known neighboring tile colors can be converted back into line decisions: + +- same-colored neighboring tiles force the separating Masyu line to be crossed + out; +- different-colored neighboring tiles force the separating Masyu line to be a + confirmed loop segment. + +This rule determines line and crossed-out-line segments from tile colors. + +### Masyu Tile Connectivity Cut Coloring + +The inside and outside tile regions must remain connected through passages that +are not blocked by confirmed loop lines. + +This rule determines tile colors in two cases: + +- a tile component is an articulation or cut region needed to keep known inside + regions connected, or needed to keep known outside regions connected; +- a tile component is unreachable from the relevant source, so it is colored as + the opposite region. + +The rule can color both green and yellow tiles. + +## Bounded Strong Inference + +Strong inference rules run bounded trial branches. They temporarily assume a +local choice, apply the deterministic Masyu rules to a fixpoint or limit, and +reject the assumption if it reaches a hard contradiction. + +The trial is capped by candidate count, trial steps, and elapsed time. Hard +contradictions include: + +- cell degree contradictions; +- pearl shape contradictions; +- premature or multiple-loop contradictions; +- tile-color parity conflicts; +- assumptions that conflict with already decided lines. + +### Black Pearl Strong Inference + +For a black pearl, the solver assumes one possible exit direction together with +that exit's required straight extension and the opposite exit crossed out. + +If deterministic propagation reaches a contradiction, the assumed first exit is +crossed out in the real puzzle. + +This rule determines crossed-out line segments. + +### White Pearl Strong Inference + +For a white pearl with exactly two feasible straight-axis candidates, the solver +assumes one axis. + +If deterministic propagation reaches a contradiction, the opposite axis is +forced in the real puzzle. + +This rule determines the confirmed lines and crossed-out lines belonging to the +forced white-pearl axis. + +### Empty Cell Strong Inference + +For a non-pearl cell, the solver can test bounded degree choices when the local +state has either one known line with exactly two unknown exits, or no known lines +with exactly two unknown exits. + +In the degree-1 case, it assumes each possible continuation in turn. If one +continuation reaches a contradiction, the other continuation is forced. + +In the degree-0 case, it tests the two local modes: both exits are used, or both +exits are crossed out. If one mode reaches a contradiction, the other mode is +forced in the real puzzle. diff --git a/docs/techniques/slitherlink.md b/docs/techniques/slitherlink.md index e444127..1d9ecb9 100644 --- a/docs/techniques/slitherlink.md +++ b/docs/techniques/slitherlink.md @@ -1,39 +1,34 @@ # Slitherlink -Current implementation location: - -- Rule aggregator: `src/domain/rules/slither/rules.ts` -- Rule modules: `src/domain/rules/slither/rules/` -- Completion analysis: `src/domain/rules/slither/completion.ts` -- Tests: `src/domain/rules/slither/rules.test.ts` - -Current rule organization: - -- `patterns.ts`: clue pattern rules, such as contiguous 3-runs and diagonal - adjacent 3s. -- `core.ts`: generic Slitherlink constraints, including clue edge counts, - vertex degree, and premature loop prevention. -- `color.ts`: cell color seeding and propagation. -- `sectorInference.ts`: corner-sector inference from local edge, vertex, and - cell evidence. -- `sectorPropagation.ts`: sector-to-sector and sector-to-edge propagation. -- `colorAssumptionInference.ts`: conservative color-branch contradiction - inference. -- `sectorParityInference.ts`: conservative sector-parity contradiction - inference. -- `strongInference.ts`: conservative branch-based contradiction inference. -- `shared.ts`: reusable geometry, clue, color, and mask helpers. - -Important Slitherlink model note: - -- Sector state is a bitmask of allowed corner line counts `{0,1,2}`. -- Sector diffs use `fromMask -> toMask`. -- Rule semantics narrow masks by intersection and then propagate strict masks. -- Do not revert sectors to old single-label semantics. - -Branch inference note: - -- Branch-based rules should not self-reference the exported `slitherRules` - array. -- Use dependency injection, for example - `createStrongInferenceRule(() => deterministicSlitherRules)`. +Slitherlink is a loop puzzle played on a grid of dots. The final answer is one +continuous loop drawn along grid edges. + +## Core Rules + +- The loop never branches or crosses itself. +- Every grid vertex touched by the loop has exactly two used edges. +- A numbered cell tells how many of its four surrounding edges are part of the + loop. +- Empty cells have no direct number clue, but they still obey the single-loop + rules. +- The final loop must be one connected loop, not several smaller loops. + +## PuzzleKit Model + +PuzzleKit represents Slitherlink with a grid-edge model: + +- `PuzzleIR.edges` stores line and cross decisions. +- `PuzzleIR.cells` stores numbered clues. +- `PuzzleIR.sectors` stores corner-sector constraints used by advanced + reasoning. +- `PuzzleIR.vertices` stores vertex candidate information. + +## Current Support + +- Import from `puzz.link` Slitherlink URLs and Penpa inputs. +- Create and edit Slitherlink puzzles in the editor. +- Render clues, lines, crosses, colors, sectors, and solver highlights. +- Replay deterministic and conservative branch-based solving steps with + explanations. +- Analyze completion for one valid loop satisfying all numbered clues. + diff --git a/src/app/DatasetPage.test.tsx b/src/app/DatasetPage.test.tsx index 304e9d8..03a0ea4 100644 --- a/src/app/DatasetPage.test.tsx +++ b/src/app/DatasetPage.test.tsx @@ -40,6 +40,18 @@ describe('DatasetPage', () => { expect(within(nav).getByRole('link', { name: /dataset/i })).toHaveAttribute('aria-current', 'page') expect(within(nav).getByRole('link', { name: /solver/i })).toHaveAttribute('href', '/') expect(within(nav).getByRole('link', { name: /editor/i })).toHaveAttribute('href', '/editor') + expect(within(nav).getByRole('link', { name: /open puzzlekit web on github/i })).toHaveAttribute( + 'href', + 'https://github.com/SmilingWayne/puzzlekit-web', + ) + expect(within(nav).getByRole('link', { name: /open puzzlekit web on github/i })).toHaveAttribute( + 'target', + '_blank', + ) + expect(within(nav).getByRole('link', { name: /open puzzlekit web on github/i })).toHaveAttribute( + 'rel', + 'noreferrer', + ) expect(screen.getByRole('heading', { name: /dataset controls/i })).toBeInTheDocument() expect(screen.getByRole('heading', { name: 'slitherlink-10x10-0001' })).toBeInTheDocument() expect(screen.getByLabelText(/slitherlink-10x10-0001 dataset preview/i)).toHaveClass( diff --git a/src/app/DatasetPage.tsx b/src/app/DatasetPage.tsx index b28c498..66f8a91 100644 --- a/src/app/DatasetPage.tsx +++ b/src/app/DatasetPage.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from 'react' -import { Link, useNavigate } from 'react-router-dom' +import { useNavigate } from 'react-router-dom' import type { BenchmarkDatasetItem } from '../domain/benchmark/types' import { puzzleRegistry } from '../domain/plugins/registry' import { BoardLegendButton } from '../features/board/BoardLegendButton' @@ -8,6 +8,7 @@ import { useEditorStore } from '../features/editor/editorStore' import { PuzzleInfoButton } from '../features/puzzleInfo/PuzzleInfoButton' import { PuzzlePreviewBoard } from '../features/puzzlePreview/PuzzlePreviewBoard' import { useSolverStore } from '../features/solver/solverStore' +import { WorkspaceHeader } from './WorkspaceHeader' import './workspace.css' type DatasetPuzzleCard = BenchmarkDatasetItem & { @@ -16,6 +17,8 @@ type DatasetPuzzleCard = BenchmarkDatasetItem & { description: string } +type ActivePuzzlePopover = 'rules' | 'legend' | null + const DATASET_PREVIEW_SIZE = 136 const datasetCards: DatasetPuzzleCard[] = publicDatasetManifests.flatMap((manifest) => @@ -72,6 +75,7 @@ export const DatasetPage = () => { const [query, setQuery] = useState('') const [sizeFilter, setSizeFilter] = useState('all') const [activeTag, setActiveTag] = useState(null) + const [activePuzzlePopover, setActivePuzzlePopover] = useState(null) const [actionError, setActionError] = useState('') const tags = useMemo( @@ -136,19 +140,11 @@ export const DatasetPage = () => {
-
-
-

PuzzleKit Dataset

-

Browse public Slitherlink puzzles and load them into the workspace.

-
- -
+
@@ -214,15 +210,35 @@ export const DatasetPage = () => {
Puzzle Type
- { + setActivePuzzlePopover(null) + setPluginId(event.target.value) + }} + > {puzzleRegistry.all().map((plugin) => ( ))} - - + + setActivePuzzlePopover((current) => (current === 'rules' ? null : 'rules')) + } + onClose={() => setActivePuzzlePopover(null)} + /> + + setActivePuzzlePopover((current) => (current === 'legend' ? null : 'legend')) + } + onClose={() => setActivePuzzlePopover(null)} + />