<![CDATA[Strategy Math Sun Game]]> false true true false ]]> ./sunGame/Screenshot 2021-04-13 at 9.31.37 PM (2).png ;; DESCRIPTION_EDITOR Intro Page true false _default_ Intro Page false Sun Game Objective: Activate the correct edges such that the number of active edges connected to each node / vertice corresponds to the number on that particular node Controls: Click an edge once to active it, a second time to flag it, and a third time to reset it. (Functionally, a flagged edge and an inactive edge are the same. However, they are visually different, and can help players distinguish between an edge that is confirmed to be inactive, and an edge that is still undecided) ]]> 20 1 true VARIABLE_EDITOR Var Table true false VARIABLE_EDITOR lines true false VARIABLE_EDITOR circles true false VARIABLE_EDITOR text true false VARIABLE_EDITOR lookang true false CODE_EDITOR Init Page true false 0 && i < numOfRows - 1){ linesX.push(curX); linesXSize.push(-linesLength); linesY.push(curY); linesYSize.push(linesLength); edgeList.push([i * numOfRows + j, (i + 1) * numOfRows + j - 1]); // hotdots position linesXoffset1.push(curX - linesLength * offset); linesYoffset1.push(curY + linesLength * offset); linesXoffset2.push(curX - linesLength * (1 - offset)); linesYoffset2.push(curY + linesLength * (1 - offset)); } // diagonally right if(j < numOfCols - 1 && i < numOfRows - 1) { linesX.push(curX); linesXSize.push(linesLength); linesY.push(curY); linesYSize.push(linesLength); edgeList.push([i * numOfRows + j, (i + 1) * numOfRows + j + 1]); // hotdots position linesXoffset1.push(curX + linesLength * offset); linesYoffset1.push(curY + linesLength * offset); linesXoffset2.push(curX + linesLength * (1 - offset)); linesYoffset2.push(curY + linesLength * (1 - offset)); } nodeVal.push(0); // circle position (the vertices) circleX.push(curX); circleY.push(curY); circleColor.push("white"); // number text textX.push(curX); textY.push(curY); } } // initialising linesAttribute, linesActive and linesColor for(var i = 0; i < linesX.length; i++){ linesAttribute.push( { "stroke-dasharray": "10 5" } ); linesColor.push(linesColorInactive); linesActive.push(0); } console.log(edgeList); ]]> CODE_EDITOR debug true false LIBRARY_EDITOR toggleEdge true false grid[curNode]) { circleColor[curNode] = colorInvalid; } else { circleColor[curNode] = colorNormal; } } // checking for game over var completed = true; for(var i = 0; i < numOfNodes; i++) { if(nodeVal[i] != grid[i]) { completed = false; break; } } if(completed) { _tools.showOkDialog("Completed! Well done!"); gameOver = true; } } // Recompute node values and colors from absolute line states (safe full recompute) function recomputeNodeValsAndColors() { // reset node values for (var i = 0; i < numOfNodes; i++) { nodeVal[i] = 0; } // accumulate active edges for (var e = 0; e < edgeList.length; e++) { if (linesActive[e] == 1) { var a = edgeList[e][0], b = edgeList[e][1]; nodeVal[a]++; nodeVal[b]++; } } // update circle colors for (var i = 0; i < numOfNodes; i++) { if (nodeVal[i] == grid[i]) circleColor[i] = colorValid; else if (nodeVal[i] > grid[i]) circleColor[i] = colorInvalid; else circleColor[i] = colorNormal; } } // Apply solver decisions (solverData: 1 = active, 0 = inactive) function applySolverData(solverData) { for (var e = 0; e < solverData.length; e++) { var state = solverData[e]; if (state == 1) { // active linesActive[e] = 1; linesAttribute[e]["stroke-dasharray"] = 0; linesColor[e] = linesColorActive; } else { // decided inactive: show as inactive (not flagged) so lines remain visible linesActive[e] = 0; linesAttribute[e]["stroke-dasharray"] = "10 5"; linesColor[e] = linesColorInactive; } } // recompute counts & update node colors based on final line states recomputeNodeValsAndColors(); var completed = true; for (var i = 0; i < numOfNodes; i++) { if (nodeVal[i] != grid[i]) { completed = false; break; } } if (completed) { _tools.showOkDialog("Solved by Auto-solver!"); gameOver = true; } } // Auto-solver: constraint propagation + MRV backtracking with early pruning function autoSolve() { // build adjacency: for each node list edges touching it var adjEdges = []; for (var i = 0; i < numOfNodes; i++) adjEdges.push([]); for (var e = 0; e < edgeList.length; e++) { var a = edgeList[e][0], b = edgeList[e][1]; adjEdges[a].push(e); adjEdges[b].push(e); } // solverData: -1 unknown, 0 inactive, 1 active var solverData = []; for (var e = 0; e < edgeList.length; e++) { // Respect only explicit active lines (1). Treat everything else as unknown so the solver can decide. if (typeof linesActive[e] !== 'undefined' && linesActive[e] == 1) solverData[e] = 1; else solverData[e] = -1; } // initial propagation loop var changed = true; var iterations = 0; while (changed && iterations < 2000) { changed = false; iterations++; for (var n = 0; n < numOfNodes; n++) { var req = grid[n]; var curActive = 0, unknown = 0, unknownEdges = []; var edges = adjEdges[n]; for (var k = 0; k < edges.length; k++) { var ei = edges[k]; if (solverData[ei] == 1) curActive++; else if (solverData[ei] == -1) { unknown++; unknownEdges.push(ei); } } // contradiction: more active than required -> skip (will be pruned in backtracking) if (curActive > req) continue; if (curActive == req) { // mark unknowns inactive for (var k = 0; k < unknownEdges.length; k++) { var ei = unknownEdges[k]; if (solverData[ei] != 0) { solverData[ei] = 0; changed = true; } } } else if (curActive + unknown == req) { // all unknowns must be active for (var k = 0; k < unknownEdges.length; k++) { var ei = unknownEdges[k]; if (solverData[ei] != 1) { solverData[ei] = 1; changed = true; } } } } } // collect undecided edges var undecidedIdx = []; for (var e = 0; e < solverData.length; e++) if (solverData[e] == -1) undecidedIdx.push(e); // Backtracking configuration: allow exhaustive search per user request. // Set BACKTRACK_LIMIT to Infinity so we do not fallback to a greedy assignment. var BACKTRACK_LIMIT = Infinity; // TIME_LIMIT_MS controls a wall-clock cutoff for safety (0 = no timeout). var TIME_LIMIT_MS = 0; var startTime = Date.now(); // If no undecided edges after propagation, apply result immediately. if (undecidedIdx.length == 0) { applySolverData(solverData); return; } else { // Perform a few forward-checking propagation passes to reduce search size. var madeMoreProgress = true; var safetyI = 0; while (madeMoreProgress && safetyI++ < 10) { madeMoreProgress = false; for (var n = 0; n < numOfNodes; n++) { var req = grid[n]; var curActive = 0, unknown = 0, unknownEdgesLocal = []; var edges = adjEdges[n]; for (var k = 0; k < edges.length; k++) { var ei = edges[k]; if (solverData[ei] == 1) curActive++; else if (solverData[ei] == -1) { unknown++; unknownEdgesLocal.push(ei); } } if (curActive == req && unknown > 0) { for (var kk = 0; kk < unknownEdgesLocal.length; kk++) { solverData[unknownEdgesLocal[kk]] = 0; madeMoreProgress = true; } } else if (curActive + unknown == req && unknown > 0) { for (var kk = 0; kk < unknownEdgesLocal.length; kk++) { solverData[unknownEdgesLocal[kk]] = 1; madeMoreProgress = true; } } } } // recompute undecided list undecidedIdx = []; for (var e2 = 0; e2 < solverData.length; e2++) if (solverData[e2] == -1) undecidedIdx.push(e2); // continue to full backtracking even if undecidedIdx is large (exhaustive) } // Precompute node constraints for faster checks function validatePartial(assignments) { // assignments is solverData (modified in place) for (var n = 0; n < numOfNodes; n++) { var req = grid[n]; var curActive = 0, unknown = 0; var edges = adjEdges[n]; for (var k = 0; k < edges.length; k++) { var ei = edges[k]; if (assignments[ei] == 1) curActive++; else if (assignments[ei] == -1) unknown++; } if (curActive > req) return false; if (curActive + unknown < req) return false; } return true; } // MRV heuristic: choose undecided edge that touches the most constrained node function chooseEdgeMRV(assignments, undecidedList) { var best = null; var bestScore = Infinity; for (var i = 0; i < undecidedList.length; i++) { var e = undecidedList[i]; var a = edgeList[e][0], b = edgeList[e][1]; // compute node tightness: slack = req - curActive var reqA = grid[a], reqB = grid[b]; var curActiveA = 0, curActiveB = 0, unknownA = 0, unknownB = 0; for (var k = 0; k < adjEdges[a].length; k++) { var ei = adjEdges[a][k]; if (assignments[ei] == 1) curActiveA++; else if (assignments[ei] == -1) unknownA++; } for (var k = 0; k < adjEdges[b].length; k++) { var ei = adjEdges[b][k]; if (assignments[ei] == 1) curActiveB++; else if (assignments[ei] == -1) unknownB++; } var slackA = reqA - curActiveA; var slackB = reqB - curActiveB; // score: smaller slack is more constrained; tie-breaker by fewer unknowns var score = Math.min(slackA, slackB) + 0.01 * Math.min(unknownA, unknownB); if (score < bestScore) { bestScore = score; best = e; } } return best; } // Backtracking with early pruning and MRV var undecided = undecidedIdx.slice(); function backtrackMRV() { // find first undecided edge (none left => validate final) var any = false; for (var i = 0; i < solverData.length; i++) if (solverData[i] == -1) { any = true; break; } if (!any) { return validatePartial(solverData); } // build current undecided list var curUndecided = []; for (var i = 0; i < solverData.length; i++) if (solverData[i] == -1) curUndecided.push(i); var e = chooseEdgeMRV(solverData, curUndecided); if (e === null) return false; // Choose order dynamically: if adjacent nodes are tight, try ACTIVE first, otherwise try INACTIVE first // compute slack for both nodes var aNode = edgeList[e][0], bNode = edgeList[e][1]; var curActiveA = 0, curActiveB = 0, unknownA = 0, unknownB = 0; for (var k = 0; k < adjEdges[aNode].length; k++) { var ei = adjEdges[aNode][k]; if (solverData[ei] == 1) curActiveA++; else if (solverData[ei] == -1) unknownA++; } for (var k = 0; k < adjEdges[bNode].length; k++) { var ei = adjEdges[bNode][k]; if (solverData[ei] == 1) curActiveB++; else if (solverData[ei] == -1) unknownB++; } var slackA = grid[aNode] - curActiveA; var slackB = grid[bNode] - curActiveB; // If either node has very small slack (<=1), prefer trying ACTIVE first var tries = (Math.min(slackA, slackB) <= 1) ? [1,0] : [0,1]; for (var t = 0; t < tries.length; t++) { solverData[e] = tries[t]; // Forward-checking: validate entire board quickly to prune contradictions early if (!validatePartial(solverData)) { solverData[e] = -1; continue; } if (backtrackMRV()) return true; solverData[e] = -1; } return false; } backtrackMRV(); // any remaining -1 fallback to inactive (should be none) for (var i = 0; i < solverData.length; i++) if (solverData[i] == -1) solverData[i] = 0; applySolverData(solverData); } ]]> HTML_VIEW_EDITOR HtmlView Page true false true Elements.Panel Elements.ComboBox 0)? opts[0]:""; // selected option if ( option=="puzzle 1"){ // initialising the grid (bottom row up) grid = [ 2, 4, 1, 3, 2, 2, 3, 6, 1, 3, 2, 4, 6, 8, 1, 2, 2, 6, 4, 4, 1, 2, 2, 4, 1 ]; } else if ( option=="puzzle 2"){ // initialising the grid (bottom row up) grid = [ 2, 3, 3, 3, 1, 3, 6, 7, 5, 1, 4, 4, 6, 6, 5, 5, 5, 7, 4, 2, 2, 5, 2, 4, 1 ]; } else if ( option=="puzzle 3"){ // initialising the grid (bottom row up) grid = [ 1, 2, 2, 4, 2, 1, 5, 6, 3, 3, 4, 3, 5, 2, 3, 3, 7, 7, 6, 2, 2, 1, 3, 3, 2 ]; } else if ( option=="puzzle 4"){ // initialising the grid (bottom row up) grid = [ 2, 2, 3, 4, 2, 1, 7, 6, 4, 4, 2, 5, 1, 6, 3, 4, 5, 7, 5, 1, 2, 3, 4, 1, 2 ]; } // reset the board so the selected puzzle is applied and run auto-solver _reset(); autoSolve(); ]]> Elements.Button Elements.Button true Elements.Panel true Elements.PlottingPanel Elements.SegmentSet2D Elements.ShapeSet2D Elements.TextSet2D true Elements.Group2D Elements.ShapeSet2D Elements.ShapeSet2D true Elements.Panel Elements.Label Sun Game Objective: Activate the correct edges such that the number of active edges connected to each node / vertice corresponds to the number on that particular node

Controls: Click an edge once to active it, a second time to flag it, and a third time to reset it.
(Functionally, a flagged edge and an inactive edge are the same. However, they are visually different, and can help players distinguish between an edge that is confirmed to be inactive, and an edge that is still undecided)"]]>