added custom puzzle page
Browse files- backend/Example.txt +7 -7
- backend/Example_multi.txt +164 -0
- backend/app.py +20 -1
- backend/solver.py +82 -0
- frontend/src/App.css +278 -0
- frontend/src/App.js +30 -292
- frontend/src/components/SolutionNavigator.js +102 -0
- frontend/src/pages/CustomPuzzlePage.js +321 -0
- frontend/src/pages/PredefinedPuzzlePage.js +304 -0
backend/Example.txt
CHANGED
|
@@ -153,13 +153,13 @@ if s.check() == sat:
|
|
| 153 |
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
| 154 |
s.add(Or(block))
|
| 155 |
while s.check() == sat:
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
|
| 164 |
if cnt == 1:
|
| 165 |
# Output the solution as a JSON string.
|
|
|
|
| 153 |
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
| 154 |
s.add(Or(block))
|
| 155 |
while s.check() == sat:
|
| 156 |
+
m = s.model()
|
| 157 |
+
cnt += 1
|
| 158 |
+
block = []
|
| 159 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
| 160 |
+
for i in range(NUM_POSITIONS):
|
| 161 |
+
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
| 162 |
+
s.add(Or(block))
|
| 163 |
|
| 164 |
if cnt == 1:
|
| 165 |
# Output the solution as a JSON string.
|
backend/Example_multi.txt
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This is an example of representing a Zebra Puzzle using a Z3-based Python code:
|
| 2 |
+
|
| 3 |
+
There are 5 Producers in this puzzle.
|
| 4 |
+
|
| 5 |
+
Categories and possible items:
|
| 6 |
+
- Shirt: black, green, orange, pink, red
|
| 7 |
+
- Name: Barbara, Isabella, Jane, Nicole, Rachel
|
| 8 |
+
- Jam: apricot, cherry, fig, raspberry, watermelon
|
| 9 |
+
- Size: 10 oz, 12 oz, 14 oz, 20 oz, 6 oz
|
| 10 |
+
- Source: backyard, farmers' coop, local grocer, organic farm, wild pickings
|
| 11 |
+
|
| 12 |
+
Clues:
|
| 13 |
+
- The producer whose jam comes from Wild pickings is in the fourth position.
|
| 14 |
+
- The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
|
| 15 |
+
- Isabella is selling Cherry jam.
|
| 16 |
+
- The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
|
| 17 |
+
- The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
|
| 18 |
+
- The producer with 14 oz jars is next to the one selling Raspberry jam.
|
| 19 |
+
- The Raspberry jam producer is positioned at one of the ends.
|
| 20 |
+
- Barbara is next to the producer whose jam source is the Organic farm.
|
| 21 |
+
- Jane is located somewhere between Nicole and Isabella, in that order.
|
| 22 |
+
- The producer of Fig jam sources the fruit from an Organic farm.
|
| 23 |
+
- The producer with 10 oz jars is at one of the ends.
|
| 24 |
+
- The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
|
| 25 |
+
- The producer with 12 oz jars is next to the one who has 6 oz jars.
|
| 26 |
+
- Isabella is next to the producer wearing a Black shirt.
|
| 27 |
+
- The producer with 6 oz jars is next to the one whose source is the Local grocer.
|
| 28 |
+
- The producer with 6 oz jars is in the second position.
|
| 29 |
+
- Rachel's source of fruit is the Farmers' coop.
|
| 30 |
+
- Barbara is next to Nicole.
|
| 31 |
+
- The producer wearing an Orange shirt gets her fruit from the Backyard.
|
| 32 |
+
- The producer with 12 oz jars is in the very first position.
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
from z3 import *
|
| 36 |
+
import json
|
| 37 |
+
import sys
|
| 38 |
+
|
| 39 |
+
# Define all categories in a single list of tuples:
|
| 40 |
+
# (House is implicit; each row's first column will be the house number.)
|
| 41 |
+
categories = [
|
| 42 |
+
("Shirt", ["black", "green", "orange", "pink", "red"]),
|
| 43 |
+
("Name", ["Barbara", "Isabella", "Jane", "Nicole", "Rachel"]),
|
| 44 |
+
("Jam", ["apricot", "cherry", "fig", "raspberry", "watermelon"]),
|
| 45 |
+
("Size", ["10 oz", "12 oz", "14 oz", "20 oz", "6 oz"]),
|
| 46 |
+
("Source", ["backyard", "farmers' coop", "local grocer", "organic farm", "wild pickings"])
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
# No need to change here, automatically processing
|
| 50 |
+
NUM_POSITIONS = len(categories[0][1])
|
| 51 |
+
|
| 52 |
+
item_to_cat_and_index = {}
|
| 53 |
+
for cat_idx, (cat_str, item_list) in enumerate(categories):
|
| 54 |
+
for item_idx, item_str in enumerate(item_list):
|
| 55 |
+
item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
|
| 56 |
+
|
| 57 |
+
Vars = []
|
| 58 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
| 59 |
+
var = IntVector(cat_name, len(item_list))
|
| 60 |
+
Vars.append(var)
|
| 61 |
+
|
| 62 |
+
s = Solver()
|
| 63 |
+
|
| 64 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
| 65 |
+
for item_idx, item_str in enumerate(item_list):
|
| 66 |
+
s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
|
| 67 |
+
s.add(Distinct(Vars[cat_idx]))
|
| 68 |
+
|
| 69 |
+
def pos(cat_str, item_str):
|
| 70 |
+
(cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
|
| 71 |
+
return Vars[cat_idx][item_idx]
|
| 72 |
+
|
| 73 |
+
# All clues here
|
| 74 |
+
# The producer whose jam comes from Wild pickings is in the fourth position.
|
| 75 |
+
s.add(pos("Source", "wild pickings") == 4)
|
| 76 |
+
|
| 77 |
+
# The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
|
| 78 |
+
s.add(pos("Jam", "cherry") > pos("Shirt", "green"))
|
| 79 |
+
|
| 80 |
+
# Isabella is selling Cherry jam.
|
| 81 |
+
s.add(pos("Name", "Isabella") == pos("Jam", "cherry"))
|
| 82 |
+
|
| 83 |
+
# The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
|
| 84 |
+
s.add(And(pos("Source", "backyard") < pos("Shirt", "pink"), pos("Shirt", "pink") < pos("Jam", "watermelon")))
|
| 85 |
+
|
| 86 |
+
# The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
|
| 87 |
+
s.add(pos("Size", "14 oz") > pos("Shirt", "green"))
|
| 88 |
+
|
| 89 |
+
# The producer with 14 oz jars is next to the one selling Raspberry jam.
|
| 90 |
+
s.add(Abs(pos("Size", "14 oz") - pos("Jam", "raspberry")) == 1)
|
| 91 |
+
|
| 92 |
+
# The Raspberry jam producer is positioned at one of the ends.
|
| 93 |
+
s.add(Or(pos("Jam", "raspberry") == 1, pos("Jam", "raspberry") == NUM_POSITIONS))
|
| 94 |
+
|
| 95 |
+
# Barbara is next to the producer whose jam source is the Organic farm.
|
| 96 |
+
s.add(Abs(pos("Name", "Barbara") - pos("Source", "organic farm")) == 1)
|
| 97 |
+
|
| 98 |
+
# Jane is located somewhere between Nicole and Isabella, in that order.
|
| 99 |
+
s.add(And(pos("Name", "Nicole") < pos("Name", "Jane"), pos("Name", "Jane") < pos("Name", "Isabella")))
|
| 100 |
+
|
| 101 |
+
# The producer of Fig jam sources the fruit from an Organic farm.
|
| 102 |
+
s.add(pos("Jam", "fig") == pos("Source", "organic farm"))
|
| 103 |
+
|
| 104 |
+
# The producer with 10 oz jars is at one of the ends.
|
| 105 |
+
s.add(Or(pos("Size", "10 oz") == 1, pos("Size", "10 oz") == NUM_POSITIONS))
|
| 106 |
+
|
| 107 |
+
# The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
|
| 108 |
+
s.add(pos("Jam", "raspberry") > pos("Shirt", "green"))
|
| 109 |
+
|
| 110 |
+
# The producer with 12 oz jars is next to the one who has 6 oz jars.
|
| 111 |
+
s.add(Abs(pos("Size", "12 oz") - pos("Size", "6 oz")) == 1)
|
| 112 |
+
|
| 113 |
+
# Isabella is next to the producer wearing a Black shirt.
|
| 114 |
+
s.add(Abs(pos("Name", "Isabella") - pos("Shirt", "black")) == 1)
|
| 115 |
+
|
| 116 |
+
# The producer with 6 oz jars is next to the one whose source is the Local grocer.
|
| 117 |
+
s.add(Abs(pos("Size", "6 oz") - pos("Source", "local grocer")) == 1)
|
| 118 |
+
|
| 119 |
+
# The producer with 6 oz jars is in the second position.
|
| 120 |
+
s.add(pos("Size", "6 oz") == 2)
|
| 121 |
+
|
| 122 |
+
# Rachel's source of fruit is the Farmers' coop.
|
| 123 |
+
s.add(pos("Name", "Rachel") == pos("Source", "farmers' coop"))
|
| 124 |
+
|
| 125 |
+
# Barbara is next to Nicole.
|
| 126 |
+
s.add(Abs(pos("Name", "Barbara") - pos("Name", "Nicole")) == 1)
|
| 127 |
+
|
| 128 |
+
# The producer wearing an Orange shirt gets her fruit from the Backyard.
|
| 129 |
+
s.add(pos("Shirt", "orange") == pos("Source", "backyard"))
|
| 130 |
+
|
| 131 |
+
# The producer with 12 oz jars is in the very first position.
|
| 132 |
+
s.add(pos("Size", "12 oz") == 1)
|
| 133 |
+
|
| 134 |
+
# Solve the puzzle
|
| 135 |
+
if s.check() == sat:
|
| 136 |
+
while s.check() == sat:
|
| 137 |
+
m = s.model()
|
| 138 |
+
rows = []
|
| 139 |
+
header = ["House"] + [cat_name for cat_name, _ in categories]
|
| 140 |
+
for position in range(1, NUM_POSITIONS + 1):
|
| 141 |
+
row = [str(position)]
|
| 142 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
| 143 |
+
for item_idx, item_str in enumerate(item_list):
|
| 144 |
+
if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
|
| 145 |
+
row.append(item_str)
|
| 146 |
+
break
|
| 147 |
+
rows.append(row)
|
| 148 |
+
result_dict = {"header": header, "rows": rows}
|
| 149 |
+
print(json.dumps(result_dict))
|
| 150 |
+
|
| 151 |
+
block = []
|
| 152 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
| 153 |
+
for i in range(NUM_POSITIONS):
|
| 154 |
+
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
| 155 |
+
s.add(Or(block))
|
| 156 |
+
|
| 157 |
+
if len(solutions) >= 10:
|
| 158 |
+
break
|
| 159 |
+
else:
|
| 160 |
+
print("NO_SOLUTION")
|
| 161 |
+
sys.stdout.flush() # Force immediate output
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
Based on this example, write a similar Z3-based Python code by changing only categories and constraints to represent the puzzle given below.
|
backend/app.py
CHANGED
|
@@ -3,12 +3,15 @@ from flask_cors import CORS
|
|
| 3 |
import os
|
| 4 |
|
| 5 |
from puzzle_dataset import get_puzzle_by_index
|
| 6 |
-
from solver import solve_puzzle
|
| 7 |
|
| 8 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
example_path = os.path.join(BASE_DIR, "Example.txt")
|
| 10 |
with open(example_path, "r", encoding="utf-8") as f:
|
| 11 |
DEFAULT_SYS_CONTENT = f.read()
|
|
|
|
|
|
|
|
|
|
| 12 |
sat_cnt_path = os.path.join(BASE_DIR, "Sat_cnt.txt")
|
| 13 |
with open(sat_cnt_path, "r", encoding="utf-8") as f:
|
| 14 |
SAT_CNT_CONTENT = f.read()
|
|
@@ -55,9 +58,25 @@ def solve():
|
|
| 55 |
result = solve_puzzle(puzzle_index, puzzle_text, expected_solution, sys_content, SAT_CNT_CONTENT)
|
| 56 |
return jsonify({"success": True, "result": result})
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
@app.route("/default_sys_content", methods=["GET"])
|
| 59 |
def get_default_sys_content():
|
| 60 |
return jsonify({"success": True, "sysContent": DEFAULT_SYS_CONTENT})
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
if __name__ == "__main__":
|
| 63 |
app.run(host="0.0.0.0", port=7860, debug=False)
|
|
|
|
| 3 |
import os
|
| 4 |
|
| 5 |
from puzzle_dataset import get_puzzle_by_index
|
| 6 |
+
from solver import solve_puzzle, solve_custom_puzzle
|
| 7 |
|
| 8 |
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 9 |
example_path = os.path.join(BASE_DIR, "Example.txt")
|
| 10 |
with open(example_path, "r", encoding="utf-8") as f:
|
| 11 |
DEFAULT_SYS_CONTENT = f.read()
|
| 12 |
+
example_multi_path = os.path.join(BASE_DIR, "Example_multi.txt")
|
| 13 |
+
with open(example_multi_path, "r", encoding="utf-8") as f:
|
| 14 |
+
DEFAULT_SYS_CONTENT_MULTI = f.read()
|
| 15 |
sat_cnt_path = os.path.join(BASE_DIR, "Sat_cnt.txt")
|
| 16 |
with open(sat_cnt_path, "r", encoding="utf-8") as f:
|
| 17 |
SAT_CNT_CONTENT = f.read()
|
|
|
|
| 58 |
result = solve_puzzle(puzzle_index, puzzle_text, expected_solution, sys_content, SAT_CNT_CONTENT)
|
| 59 |
return jsonify({"success": True, "result": result})
|
| 60 |
|
| 61 |
+
@app.route("/solve_custom", methods=["POST"])
|
| 62 |
+
def solve_custom():
|
| 63 |
+
data = request.get_json()
|
| 64 |
+
puzzle_text = data.get("puzzle")
|
| 65 |
+
sys_content = data.get("sys_content", DEFAULT_SYS_CONTENT_MULTI)
|
| 66 |
+
|
| 67 |
+
if not puzzle_text:
|
| 68 |
+
return jsonify({"success": False, "error": "Missing puzzle text"}), 400
|
| 69 |
+
|
| 70 |
+
result = solve_custom_puzzle(puzzle_text, sys_content, SAT_CNT_CONTENT)
|
| 71 |
+
return jsonify({"success": True, "result": result})
|
| 72 |
+
|
| 73 |
@app.route("/default_sys_content", methods=["GET"])
|
| 74 |
def get_default_sys_content():
|
| 75 |
return jsonify({"success": True, "sysContent": DEFAULT_SYS_CONTENT})
|
| 76 |
|
| 77 |
+
@app.route("/default_sys_content_multi", methods=["GET"])
|
| 78 |
+
def get_default_sys_content_multi():
|
| 79 |
+
return jsonify({"success": True, "sysContent": DEFAULT_SYS_CONTENT_MULTI})
|
| 80 |
+
|
| 81 |
if __name__ == "__main__":
|
| 82 |
app.run(host="0.0.0.0", port=7860, debug=False)
|
backend/solver.py
CHANGED
|
@@ -206,3 +206,85 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content, sat_cnt_content)
|
|
| 206 |
"modelResponse": content,
|
| 207 |
"problematicConstraints": problematic_constraints
|
| 208 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
"modelResponse": content,
|
| 207 |
"problematicConstraints": problematic_constraints
|
| 208 |
}
|
| 209 |
+
|
| 210 |
+
def solve_custom_puzzle(puzzle, sys_content, sat_cnt_content):
|
| 211 |
+
"""
|
| 212 |
+
解决自定义谜题,返回所有可能的解
|
| 213 |
+
"""
|
| 214 |
+
load_dotenv()
|
| 215 |
+
client = OpenAI(
|
| 216 |
+
api_key=os.getenv("GEMINI_API_KEY"),
|
| 217 |
+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
messages = [
|
| 221 |
+
{"role": "user", "content": sys_content},
|
| 222 |
+
{"role": "user", "content": puzzle},
|
| 223 |
+
]
|
| 224 |
+
|
| 225 |
+
attempts = 0
|
| 226 |
+
while attempts < 3:
|
| 227 |
+
messages.append({"role": "user", "content": "Make sure you stick to the given categories with strictly identical names otherwise there will be a critical error."})
|
| 228 |
+
attempts += 1
|
| 229 |
+
response = client.chat.completions.create(
|
| 230 |
+
model="gemini-2.0-flash",
|
| 231 |
+
messages=messages,
|
| 232 |
+
temperature=0.0,
|
| 233 |
+
stream=False
|
| 234 |
+
)
|
| 235 |
+
content = response.choices[0].message.content
|
| 236 |
+
messages.append(response.choices[0].message)
|
| 237 |
+
|
| 238 |
+
# 提取代码
|
| 239 |
+
code_blocks = re.findall(r"```(?:python)?(.*?)```", content, re.DOTALL)
|
| 240 |
+
if not code_blocks:
|
| 241 |
+
messages.append({"role": "user", "content": "Please write a complete Python code in your response. Try again."})
|
| 242 |
+
continue
|
| 243 |
+
|
| 244 |
+
code_to_run = code_blocks[-1].strip()
|
| 245 |
+
result = subprocess.run(
|
| 246 |
+
[sys.executable, "-c", code_to_run],
|
| 247 |
+
stdout=subprocess.PIPE,
|
| 248 |
+
stderr=subprocess.PIPE,
|
| 249 |
+
text=True
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
if result.stderr.strip():
|
| 253 |
+
messages.append({"role": "user", "content": f"Your code has errors: {result.stderr.strip()}. Please check your code and provide the complete code in the end."})
|
| 254 |
+
continue
|
| 255 |
+
|
| 256 |
+
output = result.stdout.strip()
|
| 257 |
+
try:
|
| 258 |
+
if output.startswith("NO_SOLUTION"):
|
| 259 |
+
return {
|
| 260 |
+
"no_solution": True,
|
| 261 |
+
"message": "No solution exists for this puzzle",
|
| 262 |
+
"generatedCode": code_to_run
|
| 263 |
+
}
|
| 264 |
+
else:
|
| 265 |
+
solutions = []
|
| 266 |
+
for line in output.split('\n'):
|
| 267 |
+
if line.strip():
|
| 268 |
+
try:
|
| 269 |
+
solution = json.loads(line)
|
| 270 |
+
solutions.append(solution)
|
| 271 |
+
except json.JSONDecodeError:
|
| 272 |
+
continue
|
| 273 |
+
|
| 274 |
+
if solutions:
|
| 275 |
+
return {
|
| 276 |
+
"solutions": solutions,
|
| 277 |
+
"generatedCode": code_to_run
|
| 278 |
+
}
|
| 279 |
+
else:
|
| 280 |
+
messages.append({"role": "user", "content": "Your output is not valid JSON. Please ensure your code prints solutions as JSON dictionaries."})
|
| 281 |
+
continue
|
| 282 |
+
except Exception as e:
|
| 283 |
+
messages.append({"role": "user", "content": f"Error processing output: {str(e)}. Please check your code format."})
|
| 284 |
+
continue
|
| 285 |
+
|
| 286 |
+
return {
|
| 287 |
+
"error": "Failed to solve puzzle after 3 attempts",
|
| 288 |
+
"generatedCode": code_to_run if 'code_to_run' in locals() else ""
|
| 289 |
+
}
|
| 290 |
+
|
frontend/src/App.css
CHANGED
|
@@ -431,6 +431,243 @@
|
|
| 431 |
backdrop-filter: blur(10px);
|
| 432 |
}
|
| 433 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
/* Responsive Design */
|
| 435 |
@media (max-width: 768px) {
|
| 436 |
.app-header h1 {
|
|
@@ -460,6 +697,33 @@
|
|
| 460 |
flex-direction: column;
|
| 461 |
gap: 1rem;
|
| 462 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
}
|
| 464 |
|
| 465 |
@media (max-width: 480px) {
|
|
@@ -475,4 +739,18 @@
|
|
| 475 |
padding: 0.5rem 1rem;
|
| 476 |
font-size: 0.9rem;
|
| 477 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
}
|
|
|
|
| 431 |
backdrop-filter: blur(10px);
|
| 432 |
}
|
| 433 |
|
| 434 |
+
/* Page Navigation */
|
| 435 |
+
.page-navigation {
|
| 436 |
+
display: flex;
|
| 437 |
+
gap: 1rem;
|
| 438 |
+
margin-top: 2rem;
|
| 439 |
+
justify-content: center;
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.nav-tab {
|
| 443 |
+
padding: 0.75rem 2rem;
|
| 444 |
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
| 445 |
+
border-radius: 25px;
|
| 446 |
+
background: rgba(255, 255, 255, 0.1);
|
| 447 |
+
color: white;
|
| 448 |
+
font-size: 1rem;
|
| 449 |
+
font-weight: 500;
|
| 450 |
+
cursor: pointer;
|
| 451 |
+
transition: all 0.3s ease;
|
| 452 |
+
backdrop-filter: blur(10px);
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
.nav-tab:hover {
|
| 456 |
+
background: rgba(255, 255, 255, 0.2);
|
| 457 |
+
border-color: rgba(255, 255, 255, 0.5);
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.nav-tab.active {
|
| 461 |
+
background: white;
|
| 462 |
+
color: #667eea;
|
| 463 |
+
border-color: white;
|
| 464 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
/* Custom Puzzle Page */
|
| 468 |
+
.custom-puzzle-editor h3 {
|
| 469 |
+
margin-bottom: 0.5rem;
|
| 470 |
+
color: #333;
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
.puzzle-input-container {
|
| 474 |
+
margin-bottom: 2rem;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.custom-puzzle-textarea {
|
| 478 |
+
width: 100%;
|
| 479 |
+
min-height: 400px;
|
| 480 |
+
padding: 1rem;
|
| 481 |
+
border: 2px solid #e0e0e0;
|
| 482 |
+
border-radius: 8px;
|
| 483 |
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
| 484 |
+
font-size: 0.9rem;
|
| 485 |
+
line-height: 1.5;
|
| 486 |
+
resize: vertical;
|
| 487 |
+
transition: border-color 0.3s ease;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
.custom-puzzle-textarea:focus {
|
| 491 |
+
outline: none;
|
| 492 |
+
border-color: #667eea;
|
| 493 |
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
.puzzle-examples {
|
| 497 |
+
background: #f8f9ff;
|
| 498 |
+
border-radius: 8px;
|
| 499 |
+
padding: 1.5rem;
|
| 500 |
+
border-left: 4px solid #667eea;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
.puzzle-examples h4 {
|
| 504 |
+
margin-bottom: 1rem;
|
| 505 |
+
color: #333;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
.puzzle-examples ul {
|
| 509 |
+
margin-left: 1.5rem;
|
| 510 |
+
color: #555;
|
| 511 |
+
line-height: 1.6;
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
.puzzle-examples li {
|
| 515 |
+
margin-bottom: 0.5rem;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
/* Solution Navigator */
|
| 519 |
+
.solution-navigator {
|
| 520 |
+
background: #f8f9fa;
|
| 521 |
+
border-radius: 10px;
|
| 522 |
+
padding: 1.5rem;
|
| 523 |
+
margin-bottom: 2rem;
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.solution-header {
|
| 527 |
+
display: flex;
|
| 528 |
+
justify-content: space-between;
|
| 529 |
+
align-items: center;
|
| 530 |
+
margin-bottom: 1.5rem;
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
.solution-header h3 {
|
| 534 |
+
margin: 0;
|
| 535 |
+
color: #333;
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
.solution-controls {
|
| 539 |
+
display: flex;
|
| 540 |
+
align-items: center;
|
| 541 |
+
gap: 1rem;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.btn-sm {
|
| 545 |
+
padding: 0.5rem 1rem;
|
| 546 |
+
font-size: 0.9rem;
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
.solution-pagination {
|
| 550 |
+
display: flex;
|
| 551 |
+
gap: 0.25rem;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
.pagination-btn {
|
| 555 |
+
width: 35px;
|
| 556 |
+
height: 35px;
|
| 557 |
+
border: 1px solid #ddd;
|
| 558 |
+
border-radius: 6px;
|
| 559 |
+
background: white;
|
| 560 |
+
color: #666;
|
| 561 |
+
font-size: 0.9rem;
|
| 562 |
+
cursor: pointer;
|
| 563 |
+
transition: all 0.2s ease;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.pagination-btn:hover {
|
| 567 |
+
background: #f0f0f0;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.pagination-btn.active {
|
| 571 |
+
background: #667eea;
|
| 572 |
+
color: white;
|
| 573 |
+
border-color: #667eea;
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
.solution-display {
|
| 577 |
+
background: white;
|
| 578 |
+
border-radius: 8px;
|
| 579 |
+
padding: 1rem;
|
| 580 |
+
border: 1px solid #e0e0e0;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
.solution-info {
|
| 584 |
+
margin-bottom: 1rem;
|
| 585 |
+
padding-bottom: 0.5rem;
|
| 586 |
+
border-bottom: 1px solid #eee;
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
.solution-counter {
|
| 590 |
+
color: #666;
|
| 591 |
+
font-weight: 500;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.solution-table-container {
|
| 595 |
+
overflow-x: auto;
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
.solution-table {
|
| 599 |
+
width: 100%;
|
| 600 |
+
border-collapse: collapse;
|
| 601 |
+
font-size: 0.9rem;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
.solution-table th,
|
| 605 |
+
.solution-table td {
|
| 606 |
+
padding: 0.75rem;
|
| 607 |
+
text-align: left;
|
| 608 |
+
border: 1px solid #ddd;
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
.solution-table th {
|
| 612 |
+
background: #f8f9fa;
|
| 613 |
+
font-weight: 600;
|
| 614 |
+
color: #333;
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
.solution-table tr:nth-child(even) {
|
| 618 |
+
background: #f9f9f9;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
.solution-table tr:hover {
|
| 622 |
+
background: #f0f4ff;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
.solution-json {
|
| 626 |
+
background: #f8f9fa;
|
| 627 |
+
border-radius: 6px;
|
| 628 |
+
padding: 1rem;
|
| 629 |
+
overflow-x: auto;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
.solution-json pre {
|
| 633 |
+
margin: 0;
|
| 634 |
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
| 635 |
+
font-size: 0.85rem;
|
| 636 |
+
line-height: 1.4;
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
/* Error Section */
|
| 640 |
+
.error-section {
|
| 641 |
+
margin-top: 2rem;
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
.error-section h3 {
|
| 645 |
+
margin-bottom: 1rem;
|
| 646 |
+
color: #dc3545;
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
.error-display {
|
| 650 |
+
background: #fff5f5;
|
| 651 |
+
border: 1px solid #fed7d7;
|
| 652 |
+
border-radius: 8px;
|
| 653 |
+
padding: 1rem;
|
| 654 |
+
max-height: 300px;
|
| 655 |
+
overflow-y: auto;
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
.error-display pre {
|
| 659 |
+
margin: 0;
|
| 660 |
+
color: #c53030;
|
| 661 |
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
| 662 |
+
font-size: 0.9rem;
|
| 663 |
+
line-height: 1.5;
|
| 664 |
+
white-space: pre-wrap;
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
.result-value.warning {
|
| 668 |
+
color: #f56500;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
/* Responsive Design */
|
| 672 |
@media (max-width: 768px) {
|
| 673 |
.app-header h1 {
|
|
|
|
| 697 |
flex-direction: column;
|
| 698 |
gap: 1rem;
|
| 699 |
}
|
| 700 |
+
|
| 701 |
+
.page-navigation {
|
| 702 |
+
flex-direction: column;
|
| 703 |
+
align-items: center;
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
.nav-tab {
|
| 707 |
+
width: 200px;
|
| 708 |
+
text-align: center;
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
.solution-header {
|
| 712 |
+
flex-direction: column;
|
| 713 |
+
gap: 1rem;
|
| 714 |
+
align-items: stretch;
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
.solution-controls {
|
| 718 |
+
justify-content: center;
|
| 719 |
+
flex-wrap: wrap;
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
.solution-pagination {
|
| 723 |
+
max-width: 100%;
|
| 724 |
+
overflow-x: auto;
|
| 725 |
+
padding: 0.5rem 0;
|
| 726 |
+
}
|
| 727 |
}
|
| 728 |
|
| 729 |
@media (max-width: 480px) {
|
|
|
|
| 739 |
padding: 0.5rem 1rem;
|
| 740 |
font-size: 0.9rem;
|
| 741 |
}
|
| 742 |
+
|
| 743 |
+
.custom-puzzle-textarea {
|
| 744 |
+
min-height: 300px;
|
| 745 |
+
font-size: 0.8rem;
|
| 746 |
+
}
|
| 747 |
+
|
| 748 |
+
.solution-table {
|
| 749 |
+
font-size: 0.8rem;
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
.solution-table th,
|
| 753 |
+
.solution-table td {
|
| 754 |
+
padding: 0.5rem;
|
| 755 |
+
}
|
| 756 |
}
|
frontend/src/App.js
CHANGED
|
@@ -1,125 +1,19 @@
|
|
| 1 |
-
import React, { useState
|
| 2 |
import './App.css';
|
|
|
|
|
|
|
| 3 |
|
| 4 |
function App() {
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
const
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
const [executionSuccess, setExecutionSuccess] = useState(null);
|
| 16 |
-
const [attempts, setAttempts] = useState(0);
|
| 17 |
-
const [isSolving, setIsSolving] = useState(false);
|
| 18 |
-
const [problematicConstraints, setProblematicConstraints] = useState("");
|
| 19 |
-
|
| 20 |
-
// UI state
|
| 21 |
-
const [currentStep, setCurrentStep] = useState(1);
|
| 22 |
-
const [expandedSections, setExpandedSections] = useState({
|
| 23 |
-
puzzle: true,
|
| 24 |
-
sysContent: false,
|
| 25 |
-
result: false
|
| 26 |
-
});
|
| 27 |
-
|
| 28 |
-
// Frontend fetch sysContent in default
|
| 29 |
-
useEffect(() => {
|
| 30 |
-
fetch(`/default_sys_content`)
|
| 31 |
-
.then(res => res.json())
|
| 32 |
-
.then(data => {
|
| 33 |
-
if(data.success) {
|
| 34 |
-
setSysContent(data.sysContent);
|
| 35 |
-
}
|
| 36 |
-
})
|
| 37 |
-
.catch(e => console.error(e));
|
| 38 |
-
}, []);
|
| 39 |
-
|
| 40 |
-
// When puzzleIndex changing,auto get puzzle
|
| 41 |
-
useEffect(() => {
|
| 42 |
-
fetch(`/get_puzzle?index=${puzzleIndex}`)
|
| 43 |
-
.then(res => res.json())
|
| 44 |
-
.then(data => {
|
| 45 |
-
if(data.success) {
|
| 46 |
-
setPuzzleText(data.puzzle);
|
| 47 |
-
setExpectedSolution(data.expected_solution);
|
| 48 |
-
} else {
|
| 49 |
-
console.error("Failed to fetch puzzle", data.error);
|
| 50 |
-
setPuzzleText("");
|
| 51 |
-
setExpectedSolution(null);
|
| 52 |
-
}
|
| 53 |
-
})
|
| 54 |
-
.catch(e => console.error(e));
|
| 55 |
-
}, [puzzleIndex]);
|
| 56 |
-
|
| 57 |
-
const handleSolve = () => {
|
| 58 |
-
if(!puzzleText || !expectedSolution) {
|
| 59 |
-
alert("puzzle or expectedSolution incomplete");
|
| 60 |
-
return;
|
| 61 |
-
}
|
| 62 |
-
const payload = {
|
| 63 |
-
index: puzzleIndex,
|
| 64 |
-
puzzle: puzzleText,
|
| 65 |
-
expected_solution: expectedSolution,
|
| 66 |
-
sys_content: sysContent,
|
| 67 |
-
problematic_constraints: problematicConstraints
|
| 68 |
-
};
|
| 69 |
-
|
| 70 |
-
setIsSolving(true);
|
| 71 |
-
setCurrentStep(3);
|
| 72 |
-
setExpandedSections({...expandedSections, result: true});
|
| 73 |
-
|
| 74 |
-
fetch(`/solve`, {
|
| 75 |
-
method: "POST",
|
| 76 |
-
headers: { "Content-Type": "application/json" },
|
| 77 |
-
body: JSON.stringify(payload)
|
| 78 |
-
})
|
| 79 |
-
.then(res => res.json())
|
| 80 |
-
.then(data => {
|
| 81 |
-
if(!data.success) {
|
| 82 |
-
alert("Backend error: " + data.error);
|
| 83 |
-
return;
|
| 84 |
-
}
|
| 85 |
-
const result = data.result;
|
| 86 |
-
setGeneratedCode(result.generatedCode || "");
|
| 87 |
-
setExecutionSuccess(result.success);
|
| 88 |
-
setAttempts(result.attempts || 0);
|
| 89 |
-
setProblematicConstraints(result.problematicConstraints || "");
|
| 90 |
-
})
|
| 91 |
-
.catch(e => console.error(e))
|
| 92 |
-
.finally(() => {
|
| 93 |
-
setIsSolving(false);
|
| 94 |
-
});
|
| 95 |
-
};
|
| 96 |
-
|
| 97 |
-
const toggleSection = (section) => {
|
| 98 |
-
setExpandedSections({
|
| 99 |
-
...expandedSections,
|
| 100 |
-
[section]: !expandedSections[section]
|
| 101 |
-
});
|
| 102 |
-
};
|
| 103 |
-
|
| 104 |
-
const nextStep = () => {
|
| 105 |
-
if (currentStep < 3) {
|
| 106 |
-
setCurrentStep(currentStep + 1);
|
| 107 |
-
if (currentStep === 1) {
|
| 108 |
-
setExpandedSections({puzzle: false, sysContent: true, result: false});
|
| 109 |
-
} else if (currentStep === 2) {
|
| 110 |
-
setExpandedSections({puzzle: false, sysContent: false, result: true});
|
| 111 |
-
}
|
| 112 |
-
}
|
| 113 |
-
};
|
| 114 |
-
|
| 115 |
-
const prevStep = () => {
|
| 116 |
-
if (currentStep > 1) {
|
| 117 |
-
setCurrentStep(currentStep - 1);
|
| 118 |
-
if (currentStep === 2) {
|
| 119 |
-
setExpandedSections({puzzle: true, sysContent: false, result: false});
|
| 120 |
-
} else if (currentStep === 3) {
|
| 121 |
-
setExpandedSections({puzzle: false, sysContent: true, result: false});
|
| 122 |
-
}
|
| 123 |
}
|
| 124 |
};
|
| 125 |
|
|
@@ -128,181 +22,25 @@ function App() {
|
|
| 128 |
<header className="app-header">
|
| 129 |
<h1>🦓 Zebra Puzzle Solver</h1>
|
| 130 |
<p>Solve complex logic puzzles using AI-powered constraint satisfaction</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
</header>
|
| 132 |
|
| 133 |
-
{
|
| 134 |
-
<div className="progress-container">
|
| 135 |
-
<div className="progress-steps">
|
| 136 |
-
<div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
|
| 137 |
-
<div className="step-number">1</div>
|
| 138 |
-
<div className="step-label">Select Puzzle</div>
|
| 139 |
-
</div>
|
| 140 |
-
<div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
|
| 141 |
-
<div className="step-number">2</div>
|
| 142 |
-
<div className="step-label">Configure System</div>
|
| 143 |
-
</div>
|
| 144 |
-
<div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
|
| 145 |
-
<div className="step-number">3</div>
|
| 146 |
-
<div className="step-label">Solve & Results</div>
|
| 147 |
-
</div>
|
| 148 |
-
</div>
|
| 149 |
-
</div>
|
| 150 |
-
|
| 151 |
-
<main className="main-content">
|
| 152 |
-
{/* Step 1: Puzzle Selection */}
|
| 153 |
-
<section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
|
| 154 |
-
<div className="section-header" onClick={() => toggleSection('puzzle')}>
|
| 155 |
-
<h2>📋 Puzzle Selection</h2>
|
| 156 |
-
<span className="toggle-icon">{expandedSections.puzzle ? '▼' : '▶'}</span>
|
| 157 |
-
</div>
|
| 158 |
-
|
| 159 |
-
{expandedSections.puzzle && (
|
| 160 |
-
<div className="section-content">
|
| 161 |
-
<div className="puzzle-selector">
|
| 162 |
-
<label htmlFor="puzzle-index">Choose puzzle index (0 - 999):</label>
|
| 163 |
-
<div className="input-group">
|
| 164 |
-
<input
|
| 165 |
-
id="puzzle-index"
|
| 166 |
-
type="number"
|
| 167 |
-
value={puzzleIndex}
|
| 168 |
-
onChange={(e) => setPuzzleIndex(Number(e.target.value))}
|
| 169 |
-
min={0}
|
| 170 |
-
max={999}
|
| 171 |
-
className="number-input"
|
| 172 |
-
/>
|
| 173 |
-
<button
|
| 174 |
-
onClick={() => setPuzzleIndex(puzzleIndex)}
|
| 175 |
-
className="btn btn-secondary"
|
| 176 |
-
>
|
| 177 |
-
Load Puzzle
|
| 178 |
-
</button>
|
| 179 |
-
</div>
|
| 180 |
-
</div>
|
| 181 |
-
|
| 182 |
-
<div className="puzzle-display">
|
| 183 |
-
<div className="puzzle-text">
|
| 184 |
-
<h3>📄 Puzzle Text</h3>
|
| 185 |
-
<div className="text-display">
|
| 186 |
-
{puzzleText || "Loading puzzle..."}
|
| 187 |
-
</div>
|
| 188 |
-
</div>
|
| 189 |
-
|
| 190 |
-
<div className="expected-solution">
|
| 191 |
-
<h3>🎯 Expected Solution</h3>
|
| 192 |
-
<div className="json-display">
|
| 193 |
-
{expectedSolution ? (
|
| 194 |
-
<pre>{JSON.stringify(expectedSolution, null, 2)}</pre>
|
| 195 |
-
) : (
|
| 196 |
-
"Loading solution..."
|
| 197 |
-
)}
|
| 198 |
-
</div>
|
| 199 |
-
</div>
|
| 200 |
-
</div>
|
| 201 |
-
</div>
|
| 202 |
-
)}
|
| 203 |
-
</section>
|
| 204 |
-
|
| 205 |
-
{/* Step 2: System Configuration */}
|
| 206 |
-
<section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
|
| 207 |
-
<div className="section-header" onClick={() => toggleSection('sysContent')}>
|
| 208 |
-
<h2>⚙️ System Configuration</h2>
|
| 209 |
-
<span className="toggle-icon">{expandedSections.sysContent ? '▼' : '▶'}</span>
|
| 210 |
-
</div>
|
| 211 |
-
|
| 212 |
-
{expandedSections.sysContent && (
|
| 213 |
-
<div className="section-content">
|
| 214 |
-
<div className="sys-content-editor">
|
| 215 |
-
<h3>📝 System Content</h3>
|
| 216 |
-
<p className="description">
|
| 217 |
-
Edit the system prompt that will guide the AI in solving the puzzle:
|
| 218 |
-
</p>
|
| 219 |
-
<textarea
|
| 220 |
-
value={sysContent}
|
| 221 |
-
onChange={(e) => setSysContent(e.target.value)}
|
| 222 |
-
className="sys-content-textarea"
|
| 223 |
-
placeholder="Enter system content..."
|
| 224 |
-
/>
|
| 225 |
-
</div>
|
| 226 |
-
</div>
|
| 227 |
-
)}
|
| 228 |
-
</section>
|
| 229 |
-
|
| 230 |
-
{/* Step 3: Solve & Results */}
|
| 231 |
-
<section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
|
| 232 |
-
<div className="section-header" onClick={() => toggleSection('result')}>
|
| 233 |
-
<h2>🚀 Solve & Results</h2>
|
| 234 |
-
<span className="toggle-icon">{expandedSections.result ? '▼' : '▶'}</span>
|
| 235 |
-
</div>
|
| 236 |
-
|
| 237 |
-
{expandedSections.result && (
|
| 238 |
-
<div className="section-content">
|
| 239 |
-
<div className="solve-section">
|
| 240 |
-
<button
|
| 241 |
-
onClick={handleSolve}
|
| 242 |
-
disabled={isSolving || !puzzleText || !expectedSolution}
|
| 243 |
-
className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
|
| 244 |
-
>
|
| 245 |
-
{isSolving ? '🔄 Solving...' : '🧠 Solve Puzzle with AI'}
|
| 246 |
-
</button>
|
| 247 |
-
</div>
|
| 248 |
-
|
| 249 |
-
<div className="results-section">
|
| 250 |
-
<div className="result-summary">
|
| 251 |
-
<div className="result-item">
|
| 252 |
-
<span className="result-label">Status:</span>
|
| 253 |
-
<span className={`result-value ${executionSuccess === true ? 'success' : executionSuccess === false ? 'error' : 'pending'}`}>
|
| 254 |
-
{executionSuccess === null ? "⏳ Pending" : executionSuccess ? "✅ Success" : "❌ Failed"}
|
| 255 |
-
</span>
|
| 256 |
-
</div>
|
| 257 |
-
<div className="result-item">
|
| 258 |
-
<span className="result-label">Attempts:</span>
|
| 259 |
-
<span className="result-value">{attempts}</span>
|
| 260 |
-
</div>
|
| 261 |
-
</div>
|
| 262 |
-
|
| 263 |
-
{problematicConstraints && (
|
| 264 |
-
<div className="issues-section">
|
| 265 |
-
<h3>⚠️ Issues & Analysis</h3>
|
| 266 |
-
<div className="issues-display">
|
| 267 |
-
<pre>{problematicConstraints}</pre>
|
| 268 |
-
</div>
|
| 269 |
-
</div>
|
| 270 |
-
)}
|
| 271 |
-
|
| 272 |
-
{generatedCode && (
|
| 273 |
-
<div className="code-section">
|
| 274 |
-
<h3>💻 Generated Code</h3>
|
| 275 |
-
<div className="code-display">
|
| 276 |
-
<pre><code>{generatedCode}</code></pre>
|
| 277 |
-
</div>
|
| 278 |
-
</div>
|
| 279 |
-
)}
|
| 280 |
-
</div>
|
| 281 |
-
</div>
|
| 282 |
-
)}
|
| 283 |
-
</section>
|
| 284 |
-
</main>
|
| 285 |
-
|
| 286 |
-
{/* Navigation */}
|
| 287 |
-
<nav className="navigation">
|
| 288 |
-
<button
|
| 289 |
-
onClick={prevStep}
|
| 290 |
-
disabled={currentStep <= 1}
|
| 291 |
-
className="btn btn-outline"
|
| 292 |
-
>
|
| 293 |
-
← Previous
|
| 294 |
-
</button>
|
| 295 |
-
<span className="step-indicator">
|
| 296 |
-
Step {currentStep} of 3
|
| 297 |
-
</span>
|
| 298 |
-
<button
|
| 299 |
-
onClick={nextStep}
|
| 300 |
-
disabled={currentStep >= 3}
|
| 301 |
-
className="btn btn-outline"
|
| 302 |
-
>
|
| 303 |
-
Next →
|
| 304 |
-
</button>
|
| 305 |
-
</nav>
|
| 306 |
</div>
|
| 307 |
);
|
| 308 |
}
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
import './App.css';
|
| 3 |
+
import PredefinedPuzzlePage from './pages/PredefinedPuzzlePage';
|
| 4 |
+
import CustomPuzzlePage from './pages/CustomPuzzlePage';
|
| 5 |
|
| 6 |
function App() {
|
| 7 |
+
const [currentPage, setCurrentPage] = useState('predefined');
|
| 8 |
+
|
| 9 |
+
const renderPage = () => {
|
| 10 |
+
switch (currentPage) {
|
| 11 |
+
case 'predefined':
|
| 12 |
+
return <PredefinedPuzzlePage />;
|
| 13 |
+
case 'custom':
|
| 14 |
+
return <CustomPuzzlePage />;
|
| 15 |
+
default:
|
| 16 |
+
return <PredefinedPuzzlePage />;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
}
|
| 18 |
};
|
| 19 |
|
|
|
|
| 22 |
<header className="app-header">
|
| 23 |
<h1>🦓 Zebra Puzzle Solver</h1>
|
| 24 |
<p>Solve complex logic puzzles using AI-powered constraint satisfaction</p>
|
| 25 |
+
|
| 26 |
+
{/* Navigation Tabs */}
|
| 27 |
+
<nav className="page-navigation">
|
| 28 |
+
<button
|
| 29 |
+
className={`nav-tab ${currentPage === 'predefined' ? 'active' : ''}`}
|
| 30 |
+
onClick={() => setCurrentPage('predefined')}
|
| 31 |
+
>
|
| 32 |
+
📚 Predefined Puzzles
|
| 33 |
+
</button>
|
| 34 |
+
<button
|
| 35 |
+
className={`nav-tab ${currentPage === 'custom' ? 'active' : ''}`}
|
| 36 |
+
onClick={() => setCurrentPage('custom')}
|
| 37 |
+
>
|
| 38 |
+
✏️ Custom Puzzle
|
| 39 |
+
</button>
|
| 40 |
+
</nav>
|
| 41 |
</header>
|
| 42 |
|
| 43 |
+
{renderPage()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
</div>
|
| 45 |
);
|
| 46 |
}
|
frontend/src/components/SolutionNavigator.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
|
| 3 |
+
function SolutionNavigator({ solutions, currentIndex, onIndexChange }) {
|
| 4 |
+
if (!solutions || solutions.length === 0) {
|
| 5 |
+
return null;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
const currentSolution = solutions[currentIndex];
|
| 9 |
+
|
| 10 |
+
const nextSolution = () => {
|
| 11 |
+
if (currentIndex < solutions.length - 1) {
|
| 12 |
+
onIndexChange(currentIndex + 1);
|
| 13 |
+
}
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
const prevSolution = () => {
|
| 17 |
+
if (currentIndex > 0) {
|
| 18 |
+
onIndexChange(currentIndex - 1);
|
| 19 |
+
}
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
const goToSolution = (index) => {
|
| 23 |
+
onIndexChange(index);
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
return (
|
| 27 |
+
<div className="solution-navigator">
|
| 28 |
+
<div className="solution-header">
|
| 29 |
+
<h3>🎯 Solutions ({solutions.length} found)</h3>
|
| 30 |
+
|
| 31 |
+
{solutions.length > 1 && (
|
| 32 |
+
<div className="solution-controls">
|
| 33 |
+
<button
|
| 34 |
+
onClick={prevSolution}
|
| 35 |
+
disabled={currentIndex <= 0}
|
| 36 |
+
className="btn btn-sm btn-outline"
|
| 37 |
+
>
|
| 38 |
+
← Previous
|
| 39 |
+
</button>
|
| 40 |
+
|
| 41 |
+
<div className="solution-pagination">
|
| 42 |
+
{solutions.map((_, index) => (
|
| 43 |
+
<button
|
| 44 |
+
key={index}
|
| 45 |
+
onClick={() => goToSolution(index)}
|
| 46 |
+
className={`pagination-btn ${index === currentIndex ? 'active' : ''}`}
|
| 47 |
+
>
|
| 48 |
+
{index + 1}
|
| 49 |
+
</button>
|
| 50 |
+
))}
|
| 51 |
+
</div>
|
| 52 |
+
|
| 53 |
+
<button
|
| 54 |
+
onClick={nextSolution}
|
| 55 |
+
disabled={currentIndex >= solutions.length - 1}
|
| 56 |
+
className="btn btn-sm btn-outline"
|
| 57 |
+
>
|
| 58 |
+
Next →
|
| 59 |
+
</button>
|
| 60 |
+
</div>
|
| 61 |
+
)}
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div className="solution-display">
|
| 65 |
+
<div className="solution-info">
|
| 66 |
+
<span className="solution-counter">
|
| 67 |
+
Solution {currentIndex + 1} of {solutions.length}
|
| 68 |
+
</span>
|
| 69 |
+
</div>
|
| 70 |
+
|
| 71 |
+
<div className="solution-table-container">
|
| 72 |
+
{currentSolution.header && currentSolution.rows ? (
|
| 73 |
+
<table className="solution-table">
|
| 74 |
+
<thead>
|
| 75 |
+
<tr>
|
| 76 |
+
{currentSolution.header.map((header, index) => (
|
| 77 |
+
<th key={index}>{header}</th>
|
| 78 |
+
))}
|
| 79 |
+
</tr>
|
| 80 |
+
</thead>
|
| 81 |
+
<tbody>
|
| 82 |
+
{currentSolution.rows.map((row, rowIndex) => (
|
| 83 |
+
<tr key={rowIndex}>
|
| 84 |
+
{row.map((cell, cellIndex) => (
|
| 85 |
+
<td key={cellIndex}>{cell}</td>
|
| 86 |
+
))}
|
| 87 |
+
</tr>
|
| 88 |
+
))}
|
| 89 |
+
</tbody>
|
| 90 |
+
</table>
|
| 91 |
+
) : (
|
| 92 |
+
<div className="solution-json">
|
| 93 |
+
<pre>{JSON.stringify(currentSolution, null, 2)}</pre>
|
| 94 |
+
</div>
|
| 95 |
+
)}
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
export default SolutionNavigator;
|
frontend/src/pages/CustomPuzzlePage.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
import SolutionNavigator from '../components/SolutionNavigator';
|
| 3 |
+
|
| 4 |
+
function CustomPuzzlePage() {
|
| 5 |
+
// Custom puzzle input
|
| 6 |
+
const [customPuzzleText, setCustomPuzzleText] = useState("");
|
| 7 |
+
const [sysContent, setSysContent] = useState("");
|
| 8 |
+
|
| 9 |
+
// Results
|
| 10 |
+
const [solutions, setSolutions] = useState([]);
|
| 11 |
+
const [currentSolutionIndex, setCurrentSolutionIndex] = useState(0);
|
| 12 |
+
const [generatedCode, setGeneratedCode] = useState("");
|
| 13 |
+
const [isSolving, setIsSolving] = useState(false);
|
| 14 |
+
const [solutionStatus, setSolutionStatus] = useState(""); // "success", "no_solution", "error"
|
| 15 |
+
const [errorMessage, setErrorMessage] = useState("");
|
| 16 |
+
|
| 17 |
+
// UI state
|
| 18 |
+
const [currentStep, setCurrentStep] = useState(1);
|
| 19 |
+
const [expandedSections, setExpandedSections] = useState({
|
| 20 |
+
puzzle: true,
|
| 21 |
+
sysContent: false,
|
| 22 |
+
result: false
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
// Load default system content
|
| 26 |
+
useEffect(() => {
|
| 27 |
+
fetch(`/default_sys_content_multi`)
|
| 28 |
+
.then(res => res.json())
|
| 29 |
+
.then(data => {
|
| 30 |
+
if(data.success) {
|
| 31 |
+
setSysContent(data.sysContent);
|
| 32 |
+
}
|
| 33 |
+
})
|
| 34 |
+
.catch(e => console.error(e));
|
| 35 |
+
}, []);
|
| 36 |
+
|
| 37 |
+
const handleSolveCustom = () => {
|
| 38 |
+
if(!customPuzzleText.trim()) {
|
| 39 |
+
alert("Please enter a puzzle text");
|
| 40 |
+
return;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const payload = {
|
| 44 |
+
puzzle: customPuzzleText,
|
| 45 |
+
sys_content: sysContent,
|
| 46 |
+
custom: true
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
setIsSolving(true);
|
| 50 |
+
setCurrentStep(3);
|
| 51 |
+
setExpandedSections({puzzle: false, sysContent: false, result: true});
|
| 52 |
+
setSolutions([]);
|
| 53 |
+
setCurrentSolutionIndex(0);
|
| 54 |
+
setSolutionStatus("");
|
| 55 |
+
setErrorMessage("");
|
| 56 |
+
|
| 57 |
+
fetch(`/solve_custom`, {
|
| 58 |
+
method: "POST",
|
| 59 |
+
headers: { "Content-Type": "application/json" },
|
| 60 |
+
body: JSON.stringify(payload)
|
| 61 |
+
})
|
| 62 |
+
.then(res => res.json())
|
| 63 |
+
.then(data => {
|
| 64 |
+
if(!data.success) {
|
| 65 |
+
setErrorMessage("Backend error: " + data.error);
|
| 66 |
+
setSolutionStatus("error");
|
| 67 |
+
return;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
const result = data.result;
|
| 71 |
+
setGeneratedCode(result.generatedCode || "");
|
| 72 |
+
|
| 73 |
+
if (result.solutions && result.solutions.length > 0) {
|
| 74 |
+
setSolutions(result.solutions);
|
| 75 |
+
setSolutionStatus("success");
|
| 76 |
+
setCurrentSolutionIndex(0);
|
| 77 |
+
} else if (result.no_solution) {
|
| 78 |
+
setSolutionStatus("no_solution");
|
| 79 |
+
setErrorMessage(result.message || "No solution found for this puzzle");
|
| 80 |
+
} else {
|
| 81 |
+
setSolutionStatus("error");
|
| 82 |
+
setErrorMessage(result.error || "Unknown error occurred");
|
| 83 |
+
}
|
| 84 |
+
})
|
| 85 |
+
.catch(e => {
|
| 86 |
+
console.error(e);
|
| 87 |
+
setErrorMessage("Network error: " + e.message);
|
| 88 |
+
setSolutionStatus("error");
|
| 89 |
+
})
|
| 90 |
+
.finally(() => {
|
| 91 |
+
setIsSolving(false);
|
| 92 |
+
});
|
| 93 |
+
};
|
| 94 |
+
|
| 95 |
+
const toggleSection = (section) => {
|
| 96 |
+
setExpandedSections({
|
| 97 |
+
...expandedSections,
|
| 98 |
+
[section]: !expandedSections[section]
|
| 99 |
+
});
|
| 100 |
+
};
|
| 101 |
+
|
| 102 |
+
const nextStep = () => {
|
| 103 |
+
if (currentStep < 3) {
|
| 104 |
+
setCurrentStep(currentStep + 1);
|
| 105 |
+
if (currentStep === 1) {
|
| 106 |
+
setExpandedSections({puzzle: false, sysContent: true, result: false});
|
| 107 |
+
} else if (currentStep === 2) {
|
| 108 |
+
setExpandedSections({puzzle: false, sysContent: false, result: true});
|
| 109 |
+
}
|
| 110 |
+
}
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
const prevStep = () => {
|
| 114 |
+
if (currentStep > 1) {
|
| 115 |
+
setCurrentStep(currentStep - 1);
|
| 116 |
+
if (currentStep === 2) {
|
| 117 |
+
setExpandedSections({puzzle: true, sysContent: false, result: false});
|
| 118 |
+
} else if (currentStep === 3) {
|
| 119 |
+
setExpandedSections({puzzle: false, sysContent: true, result: false});
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
};
|
| 123 |
+
|
| 124 |
+
return (
|
| 125 |
+
<>
|
| 126 |
+
{/* Progress Steps */}
|
| 127 |
+
<div className="progress-container">
|
| 128 |
+
<div className="progress-steps">
|
| 129 |
+
<div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
|
| 130 |
+
<div className="step-number">1</div>
|
| 131 |
+
<div className="step-label">Create Puzzle</div>
|
| 132 |
+
</div>
|
| 133 |
+
<div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
|
| 134 |
+
<div className="step-number">2</div>
|
| 135 |
+
<div className="step-label">Configure System</div>
|
| 136 |
+
</div>
|
| 137 |
+
<div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
|
| 138 |
+
<div className="step-number">3</div>
|
| 139 |
+
<div className="step-label">Solve & Results</div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<main className="main-content">
|
| 145 |
+
{/* Step 1: Custom Puzzle Input */}
|
| 146 |
+
<section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
|
| 147 |
+
<div className="section-header" onClick={() => toggleSection('puzzle')}>
|
| 148 |
+
<h2>✏️ Custom Puzzle Creation</h2>
|
| 149 |
+
<span className="toggle-icon">{expandedSections.puzzle ? '▼' : '▶'}</span>
|
| 150 |
+
</div>
|
| 151 |
+
|
| 152 |
+
{expandedSections.puzzle && (
|
| 153 |
+
<div className="section-content">
|
| 154 |
+
<div className="custom-puzzle-editor">
|
| 155 |
+
<h3>📝 Puzzle Description</h3>
|
| 156 |
+
<p className="description">
|
| 157 |
+
Enter your custom zebra puzzle. Include categories, items, and constraints:
|
| 158 |
+
</p>
|
| 159 |
+
<div className="puzzle-input-container">
|
| 160 |
+
<textarea
|
| 161 |
+
value={customPuzzleText}
|
| 162 |
+
onChange={(e) => setCustomPuzzleText(e.target.value)}
|
| 163 |
+
className="custom-puzzle-textarea"
|
| 164 |
+
placeholder="Example:
|
| 165 |
+
There are 5 houses in a row.
|
| 166 |
+
|
| 167 |
+
Categories:
|
| 168 |
+
- Color: red, blue, green, yellow, white
|
| 169 |
+
- Name: Alice, Bob, Carol, Dave, Eve
|
| 170 |
+
- Pet: cat, dog, fish, bird, rabbit
|
| 171 |
+
- Drink: tea, coffee, milk, juice, water
|
| 172 |
+
- Sport: tennis, soccer, golf, swimming, running
|
| 173 |
+
|
| 174 |
+
Constraints:
|
| 175 |
+
1. The person in the red house owns a cat.
|
| 176 |
+
2. Alice drinks tea.
|
| 177 |
+
3. The green house is to the left of the white house.
|
| 178 |
+
..."
|
| 179 |
+
/>
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
<div className="puzzle-examples">
|
| 183 |
+
<h4>💡 Tips for Creating Puzzles:</h4>
|
| 184 |
+
<ul>
|
| 185 |
+
<li>Clearly define all categories and their possible values</li>
|
| 186 |
+
<li>Number your constraints for clarity</li>
|
| 187 |
+
<li>Use spatial relationships like "to the left of", "next to", "between"</li>
|
| 188 |
+
<li>Include direct assignments like "Alice lives in the red house"</li>
|
| 189 |
+
<li>It will be better to have your expected results</li>
|
| 190 |
+
</ul>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
)}
|
| 195 |
+
</section>
|
| 196 |
+
|
| 197 |
+
{/* Step 2: System Configuration */}
|
| 198 |
+
<section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
|
| 199 |
+
<div className="section-header" onClick={() => toggleSection('sysContent')}>
|
| 200 |
+
<h2>⚙️ System Configuration</h2>
|
| 201 |
+
<span className="toggle-icon">{expandedSections.sysContent ? '▼' : '▶'}</span>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
{expandedSections.sysContent && (
|
| 205 |
+
<div className="section-content">
|
| 206 |
+
<div className="sys-content-editor">
|
| 207 |
+
<h3>📝 System Content</h3>
|
| 208 |
+
<p className="description">
|
| 209 |
+
Edit the system prompt that will guide the AI in solving your custom puzzle:
|
| 210 |
+
</p>
|
| 211 |
+
<textarea
|
| 212 |
+
value={sysContent}
|
| 213 |
+
onChange={(e) => setSysContent(e.target.value)}
|
| 214 |
+
className="sys-content-textarea"
|
| 215 |
+
placeholder="Enter system content..."
|
| 216 |
+
/>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
)}
|
| 220 |
+
</section>
|
| 221 |
+
|
| 222 |
+
{/* Step 3: Solve & Results */}
|
| 223 |
+
<section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
|
| 224 |
+
<div className="section-header" onClick={() => toggleSection('result')}>
|
| 225 |
+
<h2>🚀 Solve & Results</h2>
|
| 226 |
+
<span className="toggle-icon">{expandedSections.result ? '▼' : '▶'}</span>
|
| 227 |
+
</div>
|
| 228 |
+
|
| 229 |
+
{expandedSections.result && (
|
| 230 |
+
<div className="section-content">
|
| 231 |
+
<div className="solve-section">
|
| 232 |
+
<button
|
| 233 |
+
onClick={handleSolveCustom}
|
| 234 |
+
disabled={isSolving || !customPuzzleText.trim()}
|
| 235 |
+
className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
|
| 236 |
+
>
|
| 237 |
+
{isSolving ? '🔄 Solving Custom Puzzle...' : '🧠 Solve Custom Puzzle'}
|
| 238 |
+
</button>
|
| 239 |
+
</div>
|
| 240 |
+
|
| 241 |
+
<div className="results-section">
|
| 242 |
+
<div className="result-summary">
|
| 243 |
+
<div className="result-item">
|
| 244 |
+
<span className="result-label">Status:</span>
|
| 245 |
+
<span className={`result-value ${
|
| 246 |
+
solutionStatus === 'success' ? 'success' :
|
| 247 |
+
solutionStatus === 'no_solution' ? 'warning' :
|
| 248 |
+
solutionStatus === 'error' ? 'error' : 'pending'
|
| 249 |
+
}`}>
|
| 250 |
+
{solutionStatus === 'success' ? "✅ Solved" :
|
| 251 |
+
solutionStatus === 'no_solution' ? "⚠️ No Solution" :
|
| 252 |
+
solutionStatus === 'error' ? "❌ Error" : "⏳ Pending"}
|
| 253 |
+
</span>
|
| 254 |
+
</div>
|
| 255 |
+
{solutions.length > 0 && (
|
| 256 |
+
<div className="result-item">
|
| 257 |
+
<span className="result-label">Solutions Found:</span>
|
| 258 |
+
<span className="result-value">{solutions.length}</span>
|
| 259 |
+
</div>
|
| 260 |
+
)}
|
| 261 |
+
</div>
|
| 262 |
+
|
| 263 |
+
{/* Solution Navigator */}
|
| 264 |
+
{solutions.length > 0 && (
|
| 265 |
+
<SolutionNavigator
|
| 266 |
+
solutions={solutions}
|
| 267 |
+
currentIndex={currentSolutionIndex}
|
| 268 |
+
onIndexChange={setCurrentSolutionIndex}
|
| 269 |
+
/>
|
| 270 |
+
)}
|
| 271 |
+
|
| 272 |
+
{/* Error Message */}
|
| 273 |
+
{(solutionStatus === 'error' || solutionStatus === 'no_solution') && errorMessage && (
|
| 274 |
+
<div className="error-section">
|
| 275 |
+
<h3>⚠️ {solutionStatus === 'no_solution' ? 'No Solution Found' : 'Error'}</h3>
|
| 276 |
+
<div className="error-display">
|
| 277 |
+
<pre>{errorMessage}</pre>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
)}
|
| 281 |
+
|
| 282 |
+
{/* Generated Code */}
|
| 283 |
+
{generatedCode && (
|
| 284 |
+
<div className="code-section">
|
| 285 |
+
<h3>💻 Generated Code</h3>
|
| 286 |
+
<div className="code-display">
|
| 287 |
+
<pre><code>{generatedCode}</code></pre>
|
| 288 |
+
</div>
|
| 289 |
+
</div>
|
| 290 |
+
)}
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
)}
|
| 294 |
+
</section>
|
| 295 |
+
</main>
|
| 296 |
+
|
| 297 |
+
{/* Navigation */}
|
| 298 |
+
<nav className="navigation">
|
| 299 |
+
<button
|
| 300 |
+
onClick={prevStep}
|
| 301 |
+
disabled={currentStep <= 1}
|
| 302 |
+
className="btn btn-outline"
|
| 303 |
+
>
|
| 304 |
+
← Previous
|
| 305 |
+
</button>
|
| 306 |
+
<span className="step-indicator">
|
| 307 |
+
Step {currentStep} of 3
|
| 308 |
+
</span>
|
| 309 |
+
<button
|
| 310 |
+
onClick={nextStep}
|
| 311 |
+
disabled={currentStep >= 3}
|
| 312 |
+
className="btn btn-outline"
|
| 313 |
+
>
|
| 314 |
+
Next →
|
| 315 |
+
</button>
|
| 316 |
+
</nav>
|
| 317 |
+
</>
|
| 318 |
+
);
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
export default CustomPuzzlePage;
|
frontend/src/pages/PredefinedPuzzlePage.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from 'react';
|
| 2 |
+
|
| 3 |
+
function PredefinedPuzzlePage() {
|
| 4 |
+
// For puzzle index and puzzle data
|
| 5 |
+
const [puzzleIndex, setPuzzleIndex] = useState(0);
|
| 6 |
+
const [puzzleText, setPuzzleText] = useState("");
|
| 7 |
+
const [expectedSolution, setExpectedSolution] = useState(null);
|
| 8 |
+
|
| 9 |
+
// sysContent can be editted, default using Example.txt
|
| 10 |
+
const [sysContent, setSysContent] = useState("");
|
| 11 |
+
|
| 12 |
+
// Interaction results
|
| 13 |
+
const [generatedCode, setGeneratedCode] = useState("");
|
| 14 |
+
const [executionSuccess, setExecutionSuccess] = useState(null);
|
| 15 |
+
const [attempts, setAttempts] = useState(0);
|
| 16 |
+
const [isSolving, setIsSolving] = useState(false);
|
| 17 |
+
const [problematicConstraints, setProblematicConstraints] = useState("");
|
| 18 |
+
|
| 19 |
+
// UI state
|
| 20 |
+
const [currentStep, setCurrentStep] = useState(1);
|
| 21 |
+
const [expandedSections, setExpandedSections] = useState({
|
| 22 |
+
puzzle: true,
|
| 23 |
+
sysContent: false,
|
| 24 |
+
result: false
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
// Frontend fetch sysContent in default
|
| 28 |
+
useEffect(() => {
|
| 29 |
+
fetch(`/default_sys_content`)
|
| 30 |
+
.then(res => res.json())
|
| 31 |
+
.then(data => {
|
| 32 |
+
if(data.success) {
|
| 33 |
+
setSysContent(data.sysContent);
|
| 34 |
+
}
|
| 35 |
+
})
|
| 36 |
+
.catch(e => console.error(e));
|
| 37 |
+
}, []);
|
| 38 |
+
|
| 39 |
+
// When puzzleIndex changing,auto get puzzle
|
| 40 |
+
useEffect(() => {
|
| 41 |
+
fetch(`/get_puzzle?index=${puzzleIndex}`)
|
| 42 |
+
.then(res => res.json())
|
| 43 |
+
.then(data => {
|
| 44 |
+
if(data.success) {
|
| 45 |
+
setPuzzleText(data.puzzle);
|
| 46 |
+
setExpectedSolution(data.expected_solution);
|
| 47 |
+
} else {
|
| 48 |
+
console.error("Failed to fetch puzzle", data.error);
|
| 49 |
+
setPuzzleText("");
|
| 50 |
+
setExpectedSolution(null);
|
| 51 |
+
}
|
| 52 |
+
})
|
| 53 |
+
.catch(e => console.error(e));
|
| 54 |
+
}, [puzzleIndex]);
|
| 55 |
+
|
| 56 |
+
const handleSolve = () => {
|
| 57 |
+
if(!puzzleText || !expectedSolution) {
|
| 58 |
+
alert("puzzle or expectedSolution incomplete");
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
const payload = {
|
| 62 |
+
index: puzzleIndex,
|
| 63 |
+
puzzle: puzzleText,
|
| 64 |
+
expected_solution: expectedSolution,
|
| 65 |
+
sys_content: sysContent,
|
| 66 |
+
problematic_constraints: problematicConstraints
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
setIsSolving(true);
|
| 70 |
+
setCurrentStep(3);
|
| 71 |
+
setExpandedSections({...expandedSections, result: true});
|
| 72 |
+
|
| 73 |
+
fetch(`/solve`, {
|
| 74 |
+
method: "POST",
|
| 75 |
+
headers: { "Content-Type": "application/json" },
|
| 76 |
+
body: JSON.stringify(payload)
|
| 77 |
+
})
|
| 78 |
+
.then(res => res.json())
|
| 79 |
+
.then(data => {
|
| 80 |
+
if(!data.success) {
|
| 81 |
+
alert("Backend error: " + data.error);
|
| 82 |
+
return;
|
| 83 |
+
}
|
| 84 |
+
const result = data.result;
|
| 85 |
+
setGeneratedCode(result.generatedCode || "");
|
| 86 |
+
setExecutionSuccess(result.success);
|
| 87 |
+
setAttempts(result.attempts || 0);
|
| 88 |
+
setProblematicConstraints(result.problematicConstraints || "");
|
| 89 |
+
})
|
| 90 |
+
.catch(e => console.error(e))
|
| 91 |
+
.finally(() => {
|
| 92 |
+
setIsSolving(false);
|
| 93 |
+
});
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
const toggleSection = (section) => {
|
| 97 |
+
setExpandedSections({
|
| 98 |
+
...expandedSections,
|
| 99 |
+
[section]: !expandedSections[section]
|
| 100 |
+
});
|
| 101 |
+
};
|
| 102 |
+
|
| 103 |
+
const nextStep = () => {
|
| 104 |
+
if (currentStep < 3) {
|
| 105 |
+
setCurrentStep(currentStep + 1);
|
| 106 |
+
if (currentStep === 1) {
|
| 107 |
+
setExpandedSections({puzzle: false, sysContent: true, result: false});
|
| 108 |
+
} else if (currentStep === 2) {
|
| 109 |
+
setExpandedSections({puzzle: false, sysContent: false, result: true});
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
};
|
| 113 |
+
|
| 114 |
+
const prevStep = () => {
|
| 115 |
+
if (currentStep > 1) {
|
| 116 |
+
setCurrentStep(currentStep - 1);
|
| 117 |
+
if (currentStep === 2) {
|
| 118 |
+
setExpandedSections({puzzle: true, sysContent: false, result: false});
|
| 119 |
+
} else if (currentStep === 3) {
|
| 120 |
+
setExpandedSections({puzzle: false, sysContent: true, result: false});
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
};
|
| 124 |
+
|
| 125 |
+
return (
|
| 126 |
+
<>
|
| 127 |
+
{/* Progress Steps */}
|
| 128 |
+
<div className="progress-container">
|
| 129 |
+
<div className="progress-steps">
|
| 130 |
+
<div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
|
| 131 |
+
<div className="step-number">1</div>
|
| 132 |
+
<div className="step-label">Select Puzzle</div>
|
| 133 |
+
</div>
|
| 134 |
+
<div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
|
| 135 |
+
<div className="step-number">2</div>
|
| 136 |
+
<div className="step-label">Configure System</div>
|
| 137 |
+
</div>
|
| 138 |
+
<div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
|
| 139 |
+
<div className="step-number">3</div>
|
| 140 |
+
<div className="step-label">Solve & Results</div>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
|
| 145 |
+
<main className="main-content">
|
| 146 |
+
{/* Step 1: Puzzle Selection */}
|
| 147 |
+
<section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
|
| 148 |
+
<div className="section-header" onClick={() => toggleSection('puzzle')}>
|
| 149 |
+
<h2>📋 Puzzle Selection</h2>
|
| 150 |
+
<span className="toggle-icon">{expandedSections.puzzle ? '▼' : '▶'}</span>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
{expandedSections.puzzle && (
|
| 154 |
+
<div className="section-content">
|
| 155 |
+
<div className="puzzle-selector">
|
| 156 |
+
<label htmlFor="puzzle-index">Choose puzzle index (0 - 999):</label>
|
| 157 |
+
<div className="input-group">
|
| 158 |
+
<input
|
| 159 |
+
id="puzzle-index"
|
| 160 |
+
type="number"
|
| 161 |
+
value={puzzleIndex}
|
| 162 |
+
onChange={(e) => setPuzzleIndex(Number(e.target.value))}
|
| 163 |
+
min={0}
|
| 164 |
+
max={999}
|
| 165 |
+
className="number-input"
|
| 166 |
+
/>
|
| 167 |
+
<button
|
| 168 |
+
onClick={() => setPuzzleIndex(puzzleIndex)}
|
| 169 |
+
className="btn btn-secondary"
|
| 170 |
+
>
|
| 171 |
+
Load Puzzle
|
| 172 |
+
</button>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
|
| 176 |
+
<div className="puzzle-display">
|
| 177 |
+
<div className="puzzle-text">
|
| 178 |
+
<h3>📄 Puzzle Text</h3>
|
| 179 |
+
<div className="text-display">
|
| 180 |
+
{puzzleText || "Loading puzzle..."}
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<div className="expected-solution">
|
| 185 |
+
<h3>🎯 Expected Solution</h3>
|
| 186 |
+
<div className="json-display">
|
| 187 |
+
{expectedSolution ? (
|
| 188 |
+
<pre>{JSON.stringify(expectedSolution, null, 2)}</pre>
|
| 189 |
+
) : (
|
| 190 |
+
"Loading solution..."
|
| 191 |
+
)}
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
</div>
|
| 196 |
+
)}
|
| 197 |
+
</section>
|
| 198 |
+
|
| 199 |
+
{/* Step 2: System Configuration */}
|
| 200 |
+
<section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
|
| 201 |
+
<div className="section-header" onClick={() => toggleSection('sysContent')}>
|
| 202 |
+
<h2>⚙️ System Configuration</h2>
|
| 203 |
+
<span className="toggle-icon">{expandedSections.sysContent ? '▼' : '▶'}</span>
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
+
{expandedSections.sysContent && (
|
| 207 |
+
<div className="section-content">
|
| 208 |
+
<div className="sys-content-editor">
|
| 209 |
+
<h3>📝 System Content</h3>
|
| 210 |
+
<p className="description">
|
| 211 |
+
Edit the system prompt that will guide the AI in solving the puzzle:
|
| 212 |
+
</p>
|
| 213 |
+
<textarea
|
| 214 |
+
value={sysContent}
|
| 215 |
+
onChange={(e) => setSysContent(e.target.value)}
|
| 216 |
+
className="sys-content-textarea"
|
| 217 |
+
placeholder="Enter system content..."
|
| 218 |
+
/>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
)}
|
| 222 |
+
</section>
|
| 223 |
+
|
| 224 |
+
{/* Step 3: Solve & Results */}
|
| 225 |
+
<section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
|
| 226 |
+
<div className="section-header" onClick={() => toggleSection('result')}>
|
| 227 |
+
<h2>🚀 Solve & Results</h2>
|
| 228 |
+
<span className="toggle-icon">{expandedSections.result ? '▼' : '▶'}</span>
|
| 229 |
+
</div>
|
| 230 |
+
|
| 231 |
+
{expandedSections.result && (
|
| 232 |
+
<div className="section-content">
|
| 233 |
+
<div className="solve-section">
|
| 234 |
+
<button
|
| 235 |
+
onClick={handleSolve}
|
| 236 |
+
disabled={isSolving || !puzzleText || !expectedSolution}
|
| 237 |
+
className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
|
| 238 |
+
>
|
| 239 |
+
{isSolving ? '🔄 Solving...' : '🧠 Solve Puzzle with AI'}
|
| 240 |
+
</button>
|
| 241 |
+
</div>
|
| 242 |
+
|
| 243 |
+
<div className="results-section">
|
| 244 |
+
<div className="result-summary">
|
| 245 |
+
<div className="result-item">
|
| 246 |
+
<span className="result-label">Status:</span>
|
| 247 |
+
<span className={`result-value ${executionSuccess === true ? 'success' : executionSuccess === false ? 'error' : 'pending'}`}>
|
| 248 |
+
{executionSuccess === null ? "⏳ Pending" : executionSuccess ? "✅ Success" : "❌ Failed"}
|
| 249 |
+
</span>
|
| 250 |
+
</div>
|
| 251 |
+
<div className="result-item">
|
| 252 |
+
<span className="result-label">Attempts:</span>
|
| 253 |
+
<span className="result-value">{attempts}</span>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
|
| 257 |
+
{problematicConstraints && (
|
| 258 |
+
<div className="issues-section">
|
| 259 |
+
<h3>⚠️ Issues & Analysis</h3>
|
| 260 |
+
<div className="issues-display">
|
| 261 |
+
<pre>{problematicConstraints}</pre>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
)}
|
| 265 |
+
|
| 266 |
+
{generatedCode && (
|
| 267 |
+
<div className="code-section">
|
| 268 |
+
<h3>💻 Generated Code</h3>
|
| 269 |
+
<div className="code-display">
|
| 270 |
+
<pre><code>{generatedCode}</code></pre>
|
| 271 |
+
</div>
|
| 272 |
+
</div>
|
| 273 |
+
)}
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
)}
|
| 277 |
+
</section>
|
| 278 |
+
</main>
|
| 279 |
+
|
| 280 |
+
{/* Navigation */}
|
| 281 |
+
<nav className="navigation">
|
| 282 |
+
<button
|
| 283 |
+
onClick={prevStep}
|
| 284 |
+
disabled={currentStep <= 1}
|
| 285 |
+
className="btn btn-outline"
|
| 286 |
+
>
|
| 287 |
+
← Previous
|
| 288 |
+
</button>
|
| 289 |
+
<span className="step-indicator">
|
| 290 |
+
Step {currentStep} of 3
|
| 291 |
+
</span>
|
| 292 |
+
<button
|
| 293 |
+
onClick={nextStep}
|
| 294 |
+
disabled={currentStep >= 3}
|
| 295 |
+
className="btn btn-outline"
|
| 296 |
+
>
|
| 297 |
+
Next →
|
| 298 |
+
</button>
|
| 299 |
+
</nav>
|
| 300 |
+
</>
|
| 301 |
+
);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
export default PredefinedPuzzlePage;
|