sparc-puzzle 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: sparc-puzzle
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: larskaesberg
|
|
6
|
+
Author-email: larskaesberg@gmail.com
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: datasets (>=3.6.0,<4.0.0)
|
|
14
|
+
Requires-Dist: openai (>=1.86.0,<2.0.0)
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="logo.png" alt="SPaRC Logo" width="128"/>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
# SPaRC Dataset
|
|
22
|
+
|
|
23
|
+
**SPaRC: A Spatial Pathfinding and Reasoning Challenge**
|
|
24
|
+
|
|
25
|
+
<div align="center">
|
|
26
|
+
<a href="https://sparc.gipplab.org/" style="margin-right:1em; text-decoration:none; font-size:1.1em;">
|
|
27
|
+
<strong>🌐 Visit the Website</strong>
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
## Overview
|
|
32
|
+
|
|
33
|
+
This central repository aggregates all SPaRC-related projects and provides quick access to the visualization front-end, solver utilities, and puzzle generator. Whether you're looking to run the web app, extend solver functionality, or create new puzzles, start here!
|
|
34
|
+
|
|
35
|
+
## Repositories
|
|
36
|
+
|
|
37
|
+
* **Website (Visualizer Front-End)**
|
|
38
|
+
|
|
39
|
+
* [SPaRCWebsite](https://github.com/lkaesberg/SPaRCWebsite)
|
|
40
|
+
* **Solver**
|
|
41
|
+
|
|
42
|
+
* [SPaRCSolver](https://github.com/lkaesberg/SPaRCSolver)
|
|
43
|
+
* **Puzzle Generator**
|
|
44
|
+
|
|
45
|
+
* [SPaRCPuzzleGenerator](https://github.com/lkaesberg/SPaRCPuzzleGenerator)
|
|
46
|
+
|
|
47
|
+
## Live Demo & Dataset
|
|
48
|
+
|
|
49
|
+
Interact with SPaRC puzzles and explore the visualization and dataset online:
|
|
50
|
+
|
|
51
|
+
* [🔗 Live Demo & App](https://sparc.gipplab.org/)
|
|
52
|
+
* [📚 SPaRC Dataset on Hugging Face](https://huggingface.co/datasets/lkaesberg/SPaRC)
|
|
53
|
+
|
|
54
|
+
## Contributing
|
|
55
|
+
|
|
56
|
+
Contributions, issues, and feature requests are welcome! Please open an issue or submit a pull request in the relevant repository.
|
|
57
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.png" alt="SPaRC Logo" width="128"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# SPaRC Dataset
|
|
6
|
+
|
|
7
|
+
**SPaRC: A Spatial Pathfinding and Reasoning Challenge**
|
|
8
|
+
|
|
9
|
+
<div align="center">
|
|
10
|
+
<a href="https://sparc.gipplab.org/" style="margin-right:1em; text-decoration:none; font-size:1.1em;">
|
|
11
|
+
<strong>🌐 Visit the Website</strong>
|
|
12
|
+
</a>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
This central repository aggregates all SPaRC-related projects and provides quick access to the visualization front-end, solver utilities, and puzzle generator. Whether you're looking to run the web app, extend solver functionality, or create new puzzles, start here!
|
|
18
|
+
|
|
19
|
+
## Repositories
|
|
20
|
+
|
|
21
|
+
* **Website (Visualizer Front-End)**
|
|
22
|
+
|
|
23
|
+
* [SPaRCWebsite](https://github.com/lkaesberg/SPaRCWebsite)
|
|
24
|
+
* **Solver**
|
|
25
|
+
|
|
26
|
+
* [SPaRCSolver](https://github.com/lkaesberg/SPaRCSolver)
|
|
27
|
+
* **Puzzle Generator**
|
|
28
|
+
|
|
29
|
+
* [SPaRCPuzzleGenerator](https://github.com/lkaesberg/SPaRCPuzzleGenerator)
|
|
30
|
+
|
|
31
|
+
## Live Demo & Dataset
|
|
32
|
+
|
|
33
|
+
Interact with SPaRC puzzles and explore the visualization and dataset online:
|
|
34
|
+
|
|
35
|
+
* [🔗 Live Demo & App](https://sparc.gipplab.org/)
|
|
36
|
+
* [📚 SPaRC Dataset on Hugging Face](https://huggingface.co/datasets/lkaesberg/SPaRC)
|
|
37
|
+
|
|
38
|
+
## Contributing
|
|
39
|
+
|
|
40
|
+
Contributions, issues, and feature requests are welcome! Please open an issue or submit a pull request in the relevant repository.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sparc-puzzle"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "larskaesberg",email = "larskaesberg@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"datasets (>=3.6.0,<4.0.0)",
|
|
12
|
+
"openai (>=1.86.0,<2.0.0)"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
sparc = "sparc.sparc:main"
|
|
17
|
+
|
|
18
|
+
[tool.poetry]
|
|
19
|
+
name = "sparc-puzzle"
|
|
20
|
+
version = "0.1.0"
|
|
21
|
+
description = ""
|
|
22
|
+
authors = ["larskaesberg <larskaesberg@gmail.com>"]
|
|
23
|
+
readme = "README.md"
|
|
24
|
+
packages = [{include = "sparc"}]
|
|
25
|
+
|
|
26
|
+
[tool.poetry.dependencies]
|
|
27
|
+
python = "^3.10"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
31
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def prompt(puzzle_data: Dict) -> str:
|
|
6
|
+
grid_size = puzzle_data.get("grid_size", {"width": 0, "height": 0})
|
|
7
|
+
puzzle_array = puzzle_data.get("puzzle_array", [])
|
|
8
|
+
grid_str = "\n".join(map(str, puzzle_array))
|
|
9
|
+
start_pos = None
|
|
10
|
+
end_pos = None
|
|
11
|
+
for y, row in enumerate(puzzle_array):
|
|
12
|
+
for x, cell in enumerate(row):
|
|
13
|
+
if cell == "S":
|
|
14
|
+
start_pos = f"({x}, {y})"
|
|
15
|
+
elif cell == "E":
|
|
16
|
+
end_pos = f"({x}, {y})"
|
|
17
|
+
|
|
18
|
+
polyshapes_str = ""
|
|
19
|
+
if "polyshapes" in puzzle_data and puzzle_data["polyshapes"]:
|
|
20
|
+
polyshapes_str = "POLYSHAPES DEFINITIONS:\n"
|
|
21
|
+
polyshapes_json = json.loads(puzzle_data["polyshapes"])
|
|
22
|
+
for shape_id, shape_def in polyshapes_json.items():
|
|
23
|
+
polyshapes_str += f"Shape {shape_id}:\n{"\n".join(map(str, shape_def))}\n\n"
|
|
24
|
+
return f"""
|
|
25
|
+
## Objective
|
|
26
|
+
You are a specialized AI proficient in spatial reasoning and solving puzzles from the game 'The Witness'. Your goal is to find a valid path (a continuous line) from the specified Start Node to the End Node on the provided grid, adhering to all puzzle rules.
|
|
27
|
+
|
|
28
|
+
## Core Concepts & Grid Basics
|
|
29
|
+
* **Grid Dimensions:** The puzzle grid has {grid_size['width']} columns and {grid_size['height']} rows.
|
|
30
|
+
* **Coordinate System:** Nodes are identified by `(x, y)` coordinates. `(0,0)` is the top-left node. `x` increases to the right, `y` increases downwards.
|
|
31
|
+
* **Path:** The solution is a single, continuous line connecting adjacent nodes either horizontally or vertically.
|
|
32
|
+
* **No Revisits:** The path **CANNOT** visit the same node more than once.
|
|
33
|
+
* **Valid Path Cells:** The path travels along the grid lines (edges between nodes). It can only occupy positions marked `+` or `.` in the grid layout (these correspond to positions with at least one even coordinate).
|
|
34
|
+
* **Rule Cells:** Cells containing rule symbols (squares, stars, etc.) have coordinates where both `x` and `y` are odd. The path goes *around* these rule cells, never *on* them.
|
|
35
|
+
* **Regions:** The drawn path divides the grid cells into one or more distinct enclosed areas (regions). Many rules apply based on the contents of these regions.
|
|
36
|
+
|
|
37
|
+
## Symbol Legend (Grid Notation)
|
|
38
|
+
* `S`: **Start Node** (Path begins here)
|
|
39
|
+
* `E`: **End Node** (Path ends here)
|
|
40
|
+
* `+`: Valid cell for the path to occupy
|
|
41
|
+
* `N`: Empty rule cell (no rule)
|
|
42
|
+
* `G`: **Gap** (Path **CANNOT** cross this cell)
|
|
43
|
+
* `.`: **Dot** (Path **MUST** pass through this cell)
|
|
44
|
+
* `o-X`: **Square** of color X
|
|
45
|
+
* `*-X`: **Star** of color X
|
|
46
|
+
* `A-X`: **Triangle** (touch 1 edge)
|
|
47
|
+
* `B-X`: **Triangle** (touch 2 edges)
|
|
48
|
+
* `C-X`: **Triangle** (touch 3 edges)
|
|
49
|
+
* `D-X`: **Triangle** (touch 4 edges)
|
|
50
|
+
* `P-X-Y`: **Polyshape** (positive) of color X and shape ID Y
|
|
51
|
+
* `Y-X-Y`: **Negative Polyshape** (ylop) of color X and shape ID Y
|
|
52
|
+
|
|
53
|
+
**Color Codes:** R=Red, B=Blue, G=Green, Y=Yellow, W=White, O=Orange, P=Purple, K=Black
|
|
54
|
+
|
|
55
|
+
## Detailed Solving Rules
|
|
56
|
+
The drawn path must satisfy **ALL** applicable constraints:
|
|
57
|
+
|
|
58
|
+
1. **Path Constraints:**
|
|
59
|
+
* Path **MUST** start at `S` and end at `E`.
|
|
60
|
+
* Path connects adjacent nodes (horizontal/vertical moves only).
|
|
61
|
+
* Nodes **CANNOT** be revisited.
|
|
62
|
+
* Path **MUST** pass through all Dot (`.`) cells.
|
|
63
|
+
* Path **CANNOT** pass through any Gap (`G`) cells.
|
|
64
|
+
|
|
65
|
+
2. **Region-Based Rules** (Apply to areas enclosed by the path):
|
|
66
|
+
* **Squares (`o-X`):** All squares within a single region **MUST** be the same color. Squares of different colors **MUST** be separated into different regions by the path.
|
|
67
|
+
* **Stars (`*-X`):** Within a single region, each star symbol **MUST** be paired with exactly **ONE** other element (star or square) *of the same color*. Other colors within the region are irrelevant to this specific star's rule.
|
|
68
|
+
* **Polyshapes (`P-X-Y`):** The region containing this symbol **MUST** be able to contain the specified shape (defined in Polyshape Definitions). The shape must fit entirely within the region's boundaries. If multiple positive polyshapes are in one region, the region must accommodate their combined, non-overlapping forms. Rotation of polyshapes is generally allowed unless context implies otherwise.
|
|
69
|
+
* **Negative Polyshapes (`Y-X-Y`):** These "subtract" shape requirements, typically within the same region as corresponding positive polyshapes. A negative polyshape cancels out a positive polyshape of the exact same shape and color within that region. If all positive shapes are canceled, the region has no shape constraint. A negative shape is only considered 'used' if it cancels a positive one. Negative shapes can sometimes rationalize apparent overlaps or boundary violations of positive shapes if interpreted as cancellations.
|
|
70
|
+
|
|
71
|
+
3. **Path-Based Rules (Edge Touching):**
|
|
72
|
+
* **Triangles (`A-X`, `B-X`, `C-X`, `D-X`):** The path **MUST** touch a specific number of edges of the cell containing the triangle symbol.
|
|
73
|
+
* `A-X` (1): Path touches **EXACTLY 1** edge of the triangle's cell.
|
|
74
|
+
* `B-X` (2): Path touches **EXACTLY 2** edges of the triangle's cell.
|
|
75
|
+
* `C-X` (3): Path touches **EXACTLY 3** edges of the triangle's cell.
|
|
76
|
+
* `D-X` (4): Path touches **EXACTLY 4** edges (fully surrounds) the triangle's cell.
|
|
77
|
+
|
|
78
|
+
## EXAMPLE PUZZLE GRID:
|
|
79
|
+
|
|
80
|
+
["+",".","+","+","+","E","+"]
|
|
81
|
+
["+","C-R","+","o-K","+","o-K","+"]
|
|
82
|
+
["S","+","+","+","+","+","+"]
|
|
83
|
+
["+","P-G-112","+","*-G","+","P-B-624","+"]
|
|
84
|
+
["+","+","+","+","+","+","+"]
|
|
85
|
+
["+","*-G","+","*-G","+","o-K","+"]
|
|
86
|
+
["+","+","+",".","+","+","+"]
|
|
87
|
+
|
|
88
|
+
EXAMPLE POLYSHAPE DEFINITIONS:
|
|
89
|
+
Shape 112:
|
|
90
|
+
[0,1,0,0]
|
|
91
|
+
[0,1,0,0]
|
|
92
|
+
[0,1,0,0]
|
|
93
|
+
[0,0,0,0]
|
|
94
|
+
|
|
95
|
+
Shape 624:
|
|
96
|
+
[0,1,0,0]
|
|
97
|
+
[0,1,1,0]
|
|
98
|
+
[0,1,0,0]
|
|
99
|
+
[0,0,0,0]
|
|
100
|
+
|
|
101
|
+
EXAMPLE SOLUTION:
|
|
102
|
+
|
|
103
|
+
We start at (0,2) and draw a line to (0,0).
|
|
104
|
+
We then draw a line to (2,0) to reach the dot at (1,0) and surround the 3 count triangle.
|
|
105
|
+
We then draw a line to (2,2) here we go down to touch the third side of the triangle cell and therefore validate the 3 count triangle.
|
|
106
|
+
We continue down to (2,6) to validate the polyshape 112 and also the green star with the green polyshape
|
|
107
|
+
After this we draw a line to (4,6) to start validating the polyshape 624 by surrounding it.
|
|
108
|
+
Therefore we have to draw a line to (6,4) over (4,4) which creates a region for the stone at (5,5) which validates the stone.
|
|
109
|
+
We continue up to (6,2) for the polyshape 624 and then go to (4,2) and after this to (4,0) to finaly validate the polyshape 624.
|
|
110
|
+
This also validates the two green stars at (3,3) and (3,5) with each other and the black stone at (3,1) because its the only stone in its region.
|
|
111
|
+
This line also creates a region for the black stone at (5,1) because its the only stone in its region.
|
|
112
|
+
Now we can draw a line to (5,0) to reach the end node.
|
|
113
|
+
|
|
114
|
+
#### (0,2),(0,1),(0,0),(1,0),(2,0),(2,1),(2,2),(2,3),(2,4),(2,5),(2,6),(3,6),(4,6),(4,5),(4,4),(5,4),(6,4),(6,3),(6,2),(5,2),(4,2),(4,1),(4,0),(5,0)
|
|
115
|
+
|
|
116
|
+
## Puzzle Input Data
|
|
117
|
+
* **Start Node:** {start_pos}
|
|
118
|
+
* **End Node:** {end_pos}
|
|
119
|
+
* **Grid Layout:**
|
|
120
|
+
```
|
|
121
|
+
{grid_str}
|
|
122
|
+
```
|
|
123
|
+
* **Polyshape Definitions (if applicable):**
|
|
124
|
+
* Shapes are defined by 2D arrays where '1' indicates an occupied cell and '0' indicates an empty cell.
|
|
125
|
+
```
|
|
126
|
+
{polyshapes_str}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Task & Output Format
|
|
130
|
+
1. **Solve the Puzzle:** Determine the valid path from the Start Node to the End Node that satisfies all rules.
|
|
131
|
+
2. **Explain Reasoning:** Provide a step-by-step explanation of your thought process. Detail key deductions, how constraints were applied, and any backtracking or choices made.
|
|
132
|
+
3. **Provide Solution Path:** After the reasoning, output the exact marker string `####` followed immediately by the solution path as a list of node coordinates `(x, y)`. Include all intermediate nodes from start to end.
|
|
133
|
+
|
|
134
|
+
**Example Solution Path Format:**
|
|
135
|
+
####
|
|
136
|
+
[(0, 0), (1, 0), (2, 0), (2, 1), ...]
|
|
137
|
+
"""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
from sparc.prompt import prompt
|
|
3
|
+
from sparc.validation import extract_solution_path, validate_solution, analyze_path
|
|
4
|
+
from datasets import load_dataset
|
|
5
|
+
from openai import OpenAI
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
dataset = load_dataset("lkaesberg/SPaRC", "all", split="test")
|
|
10
|
+
|
|
11
|
+
client = OpenAI(
|
|
12
|
+
api_key="9c89c616-649e-4d77-a6ad-1b1e525f94b5",
|
|
13
|
+
base_url="http://services.gipplab.org:8080/v1",
|
|
14
|
+
)
|
|
15
|
+
response = client.chat.completions.create(
|
|
16
|
+
model="meta-llama/Llama-3.3-70B-Instruct",
|
|
17
|
+
messages=[
|
|
18
|
+
{
|
|
19
|
+
"role": "system",
|
|
20
|
+
"content": "You are an expert at solving puzzles games.",
|
|
21
|
+
},
|
|
22
|
+
{"role": "user", "content": prompt(dataset[0])},
|
|
23
|
+
],
|
|
24
|
+
temperature=0.7,
|
|
25
|
+
)
|
|
26
|
+
message = response.choices[0].message.content
|
|
27
|
+
extracted_path = extract_solution_path(message, dataset[0])
|
|
28
|
+
|
|
29
|
+
solved = validate_solution(extracted_path, dataset[0])
|
|
30
|
+
analysis = analyze_path(extracted_path, dataset[0])
|
|
31
|
+
|
|
32
|
+
print(f"Extracted Path: {extracted_path}")
|
|
33
|
+
print(f"Solved: {solved}")
|
|
34
|
+
print(f"Analysis: {analysis}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List, Optional, Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def extract_solution_path(
|
|
6
|
+
solution_text: str, puzzle_data: Dict = None
|
|
7
|
+
) -> Optional[List[Dict[str, int]]]:
|
|
8
|
+
"""Extract solution path from LLM's response
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
solution_text: Text response from the LLM
|
|
12
|
+
puzzle_data: Optional puzzle data dict to extract end point from
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
List of coordinate dicts or None if no path found
|
|
16
|
+
"""
|
|
17
|
+
# First, check if "Solution" appears in the text
|
|
18
|
+
solution_marker = "####"
|
|
19
|
+
if solution_marker in solution_text:
|
|
20
|
+
# Only process text after "####"
|
|
21
|
+
solution_part = solution_text.split(solution_marker, 1)[-1]
|
|
22
|
+
else:
|
|
23
|
+
# If no solution marker, use the full text
|
|
24
|
+
solution_part = solution_text
|
|
25
|
+
|
|
26
|
+
# Look for coordinate patterns like (0,0) -> (0,1) or similar
|
|
27
|
+
# Pattern for (x,y) or (x, y) coordinates
|
|
28
|
+
coord_pattern = r"\((\d+),\s*(\d+)\)"
|
|
29
|
+
coords = re.findall(coord_pattern, solution_part)
|
|
30
|
+
|
|
31
|
+
if coords:
|
|
32
|
+
# Extract end point from puzzle data if provided
|
|
33
|
+
end_point = None
|
|
34
|
+
if puzzle_data:
|
|
35
|
+
puzzle_array = puzzle_data.get("puzzle_array", [])
|
|
36
|
+
for y, row in enumerate(puzzle_array):
|
|
37
|
+
for x, cell in enumerate(row):
|
|
38
|
+
if cell == "E":
|
|
39
|
+
end_point = {"x": x, "y": y}
|
|
40
|
+
break
|
|
41
|
+
if end_point:
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
# Convert string coordinates to integer dicts
|
|
45
|
+
path = []
|
|
46
|
+
for x, y in coords:
|
|
47
|
+
point = {"x": int(x), "y": int(y)}
|
|
48
|
+
path.append(point)
|
|
49
|
+
|
|
50
|
+
# Stop extracting if we've reached the end point
|
|
51
|
+
if (
|
|
52
|
+
end_point
|
|
53
|
+
and point["x"] == end_point["x"]
|
|
54
|
+
and point["y"] == end_point["y"]
|
|
55
|
+
):
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
return path
|
|
59
|
+
|
|
60
|
+
# If no coordinates found, return None
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_solution(extracted_path: List[Dict[str, int]], puzzle_data: Dict) -> bool:
|
|
65
|
+
"""Validate if the solution is valid by comparing with known solutions
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
extracted_path: List of coordinate dicts
|
|
69
|
+
puzzle_data: Dictionary containing puzzle data including solutions
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Boolean indicating if the solution is valid
|
|
73
|
+
"""
|
|
74
|
+
extracted_path = [(p["x"], p["y"]) for p in extracted_path]
|
|
75
|
+
if not extracted_path or len(extracted_path) < 2:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# Check against all valid solutions in the database
|
|
79
|
+
all_solutions = puzzle_data.get("solutions", [])
|
|
80
|
+
if not all_solutions:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
# For each solution in the database, check if our path matches
|
|
84
|
+
for solution in all_solutions:
|
|
85
|
+
solution_path = [(p["x"], p["y"]) for p in solution["path"]]
|
|
86
|
+
|
|
87
|
+
# Check if the paths match exactly
|
|
88
|
+
if extracted_path == solution_path:
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def analyze_path(solution_path: List[Dict[str, int]], puzzle: Dict) -> Dict:
|
|
95
|
+
"""Analyze the solution path for detailed validation metrics
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
solution_path: List of coordinate dicts representing the solution path
|
|
99
|
+
puzzle: Puzzle dictionary with puzzle array and metadata
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Dictionary with detailed path analysis results
|
|
103
|
+
"""
|
|
104
|
+
solution_path = [(p["x"], p["y"]) for p in solution_path]
|
|
105
|
+
if not solution_path:
|
|
106
|
+
return {
|
|
107
|
+
"starts_at_start_ends_at_exit": False,
|
|
108
|
+
"connected_line": False,
|
|
109
|
+
"non_intersecting_line": False,
|
|
110
|
+
"start_to_exit_connected": False,
|
|
111
|
+
"no_rule_crossing": False,
|
|
112
|
+
"fully_valid_path": False,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Get puzzle information
|
|
116
|
+
puzzle_array = puzzle.get("puzzle_array", [])
|
|
117
|
+
if not puzzle_array:
|
|
118
|
+
return {
|
|
119
|
+
"starts_at_start_ends_at_exit": False,
|
|
120
|
+
"connected_line": False,
|
|
121
|
+
"non_intersecting_line": False,
|
|
122
|
+
"start_to_exit_connected": False,
|
|
123
|
+
"no_rule_crossing": False,
|
|
124
|
+
"fully_valid_path": False,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Find start and end points
|
|
128
|
+
start_point = None
|
|
129
|
+
end_point = None
|
|
130
|
+
for y, row in enumerate(puzzle_array):
|
|
131
|
+
for x, cell in enumerate(row):
|
|
132
|
+
if cell == "S":
|
|
133
|
+
start_point = (x, y)
|
|
134
|
+
elif cell == "E":
|
|
135
|
+
end_point = (x, y)
|
|
136
|
+
|
|
137
|
+
if not start_point or not end_point:
|
|
138
|
+
return {
|
|
139
|
+
"starts_at_start_ends_at_exit": False,
|
|
140
|
+
"connected_line": False,
|
|
141
|
+
"non_intersecting_line": False,
|
|
142
|
+
"start_to_exit_connected": False,
|
|
143
|
+
"no_rule_crossing": False,
|
|
144
|
+
"fully_valid_path": False,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# Check if path starts at start and ends at exit
|
|
148
|
+
starts_at_start = len(solution_path) > 0 and solution_path[0] == start_point
|
|
149
|
+
ends_at_exit = len(solution_path) > 0 and solution_path[-1] == end_point
|
|
150
|
+
starts_at_start_ends_at_exit = starts_at_start and ends_at_exit
|
|
151
|
+
|
|
152
|
+
# Check if path is connected (no gaps)
|
|
153
|
+
connected_line = True
|
|
154
|
+
for i in range(1, len(solution_path)):
|
|
155
|
+
prev_x, prev_y = solution_path[i - 1]
|
|
156
|
+
curr_x, curr_y = solution_path[i]
|
|
157
|
+
# Check if adjacent (Manhattan distance of 1)
|
|
158
|
+
if abs(prev_x - curr_x) + abs(prev_y - curr_y) != 1:
|
|
159
|
+
connected_line = False
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
# Check if path doesn't intersect with itself
|
|
163
|
+
non_intersecting_line = len(set(solution_path)) == len(solution_path)
|
|
164
|
+
|
|
165
|
+
# Check if there's a connected path from start to exit
|
|
166
|
+
start_to_exit_connected = starts_at_start_ends_at_exit and connected_line
|
|
167
|
+
|
|
168
|
+
# Identify rule cells as cells where both coordinates are odd
|
|
169
|
+
rule_cells = set()
|
|
170
|
+
for y in range(len(puzzle_array)):
|
|
171
|
+
for x in range(len(puzzle_array[0]) if len(puzzle_array) > 0 else 0):
|
|
172
|
+
if x % 2 == 1 and y % 2 == 1:
|
|
173
|
+
rule_cells.add((x, y))
|
|
174
|
+
|
|
175
|
+
# Check if path crosses rule cells
|
|
176
|
+
no_rule_crossing = not any((x, y) in rule_cells for x, y in solution_path[1:-1])
|
|
177
|
+
|
|
178
|
+
# Check if path is fully valid
|
|
179
|
+
fully_valid_path = (
|
|
180
|
+
starts_at_start_ends_at_exit
|
|
181
|
+
and connected_line
|
|
182
|
+
and non_intersecting_line
|
|
183
|
+
and no_rule_crossing
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"starts_at_start_ends_at_exit": starts_at_start_ends_at_exit,
|
|
188
|
+
"connected_line": connected_line,
|
|
189
|
+
"non_intersecting_line": non_intersecting_line,
|
|
190
|
+
"start_to_exit_connected": start_to_exit_connected,
|
|
191
|
+
"no_rule_crossing": no_rule_crossing,
|
|
192
|
+
"fully_valid_path": fully_valid_path,
|
|
193
|
+
}
|