const SEG = { a:0, b:1, c:2, d:3, e:4, f:5, g:6 }; const segNames = ["a","b","c","d","e","f","g"]; function maskFromSegments(list){ let m = 0; for(const s of list) m |= (1 << SEG[s]); return m; } const DIGIT_MASK = [ maskFromSegments(["a","b","c","d","e","f"]), // 0 maskFromSegments(["b","c"]), // 1 maskFromSegments(["a","b","d","e","g"]), // 2 maskFromSegments(["a","b","c","d","g"]), // 3 maskFromSegments(["b","c","f","g"]), // 4 maskFromSegments(["a","c","d","f","g"]), // 5 maskFromSegments(["a","c","d","e","f","g"]), // 6 maskFromSegments(["a","b","c"]), // 7 maskFromSegments(["a","b","c","d","e","f","g"]), // 8 maskFromSegments(["a","b","c","d","f","g"]) // 9 ]; const MASK_TO_DIGIT = new Map(DIGIT_MASK.map((m,d)=>[m,d])); function removableTargetsFromDigit(d){ const start = DIGIT_MASK[d]; const res = []; for(let t=0;t<=9;t++){ const target = DIGIT_MASK[t]; if((target & start) === target){ const removed = popcount(start ^ target); res.push({to:t, removed}); } } return res; } function popcount(x){ x = x >>> 0; let c = 0; while(x){ x &= (x-1); c++; } return c; } function randInt(min, max){ // inclusive return Math.floor(Math.random() * (max - min + 1)) + min; } function pick(arr){ return arr[randInt(0, arr.length-1)]; } let level = 1; let targetRemove = 2; let current = null; // {A,B,C} shown digits let solution = null; // {A,B,C} target digits after removals let removedSoFar = 0; // Each segment element knows which digit/segment it belongs to // We'll store: digitIndex (0..2 for A,B,C) and segName let removedSet = new Set(); // keys like "0-a" meaning digit0 seg a removed const elEq = document.getElementById("equation"); const elRemovedGrid = document.getElementById("removedGrid"); const elRemovedCount = document.getElementById("removedCount"); const elPileCount = document.getElementById("pileCount"); const elLvl = document.getElementById("lvl"); const elTarget = document.getElementById("target"); const elGoalN = document.getElementById("goalN"); const elTruthDot = document.getElementById("truthDot"); const elTruthText = document.getElementById("truthText"); const elHint = document.getElementById("hint"); document.getElementById("btnNew").addEventListener("click", () => { level++; generateLevel(); }); document.getElementById("btnReset").addEventListener("click", () => { // reset current level state but keep the same puzzle resetPlayState(); renderEquation(); updateTruthUI(); }); // Turn mask into a set of active segment names function segmentsFromMask(mask){ const set = new Set(); for(let i=0;i<7;i++){ if(mask & (1< false const a = displayedDigitValue(current.A, 0); const b = displayedDigitValue(current.B, 1); const c = displayedDigitValue(current.C, 2); if(a === null || b === null || c === null) return false; return (a + b) === c; } function updateTruthUI(){ const ok = equationIsTrue(); elTruthDot.classList.toggle("ok", ok); elTruthText.textContent = ok ? "Equation is TRUE" : "Equation is FALSE"; } // Reset removals and removed pile function resetPlayState(){ removedSet.clear(); removedSoFar = 0; elRemovedGrid.innerHTML = ""; syncRemovedCounts(); } function syncRemovedCounts(){ elRemovedCount.textContent = String(removedSoFar); elPileCount.textContent = String(removedSoFar); } // Render a digit as a 7-seg container with clickable match segments function renderDigit(digitValue, digitIndex, color){ const digit = document.createElement("div"); digit.className = "digit"; const baseMask = DIGIT_MASK[digitValue]; const baseSegs = segmentsFromMask(baseMask); for(const s of segNames){ if(!baseSegs.has(s)) continue; // no stick here in the original digit const seg = document.createElement("div"); seg.className = "seg " + s; seg.style.background = color; const key = digitIndex + "-" + s; if(removedSet.has(key)) seg.classList.add("removed"); seg.addEventListener("click", () => { if(removedSet.has(key)) return; // If player already removed target count, block further removes if(removedSoFar >= targetRemove) return; removedSet.add(key); removedSoFar++; // visually remove from equation (turn white/invisible) seg.classList.add("removed"); // add to removed pile as a small colored bar const clone = document.createElement("div"); clone.className = "removedSeg"; clone.style.background = color; elRemovedGrid.appendChild(clone); syncRemovedCounts(); updateTruthUI(); // win condition if(removedSoFar === targetRemove){ if(equationIsTrue()){ elHint.innerHTML = "✅ Solved! You removed exactly the target and made the equation true. Click New level."; } else { elHint.innerHTML = "❌ Not solved. You used all removals but the equation isn’t true. Click Reset level to try again."; } } }); digit.appendChild(seg); } return digit; } function renderEquation(){ elEq.innerHTML = ""; // color palette (A,B,C different) const colors = [ "linear-gradient(180deg,#ff6b6b,#d64545)", "linear-gradient(180deg,#6bcBff,#3a7bd5)", "linear-gradient(180deg,#6bffb3,#1fae63)" ]; const g1 = document.createElement("div"); g1.className = "group"; g1.appendChild(renderDigit(current.A, 0, colors[0])); const plus = document.createElement("div"); plus.className = "symbol"; plus.textContent = "+"; const g2 = document.createElement("div"); g2.className = "group"; g2.appendChild(renderDigit(current.B, 1, colors[1])); const eq = document.createElement("div"); eq.className = "symbol"; eq.textContent = "="; const g3 = document.createElement("div"); g3.className = "group"; g3.appendChild(renderDigit(current.C, 2, colors[2])); elEq.appendChild(g1); elEq.appendChild(plus); elEq.appendChild(g2); elEq.appendChild(eq); elEq.appendChild(g3); updateTruthUI(); } // -------- Infinite level generation algorithm -------- // // We generate a "solution equation" (A_s + B_s = C_s) valid in 0..9. // Then we create the "shown equation" by turning each solution digit into a SUPERSET digit // (i.e., add extra segments) so that the player can remove sticks to get back to the solution. // We also ensure the shown equation is NOT already true. // We finally choose a removal target N = total segments added across the 3 digits (or some bounded version). // // This guarantees solvable by removals only, for infinite levels. // ----------------------------------------------------- function generateLevel(){ resetPlayState(); // Level difficulty curve: increase target removals slowly, but never explode. // You can tweak this. Keeps it playable while still "infinite". targetRemove = Math.min(2 + Math.floor(Math.log2(level + 1)), 6); // Find a puzzle with exactly targetRemove removals needed to reach a true equation. // We'll attempt random constructions until we hit the required N. let tries = 0; while(true){ tries++; if(tries > 5000){ // fallback: relax target a bit if extremely unlucky targetRemove = Math.max(2, targetRemove - 1); tries = 0; } // 1) Pick a valid solution equation (A_s + B_s = C_s) const As = randInt(0, 9); const Bs = randInt(0, 9); const Cs = As + Bs; if(Cs < 0 || Cs > 9) continue; // 2) For each digit, choose a "shown digit" that can be reduced to solution digit by removing sticks. // i.e., shownMask is a superset of solutionMask. const Achoices = superDigits(As); const Bchoices = superDigits(Bs); const Cchoices = superDigits(Cs); // pick a random superset (could be itself) const Ashow = pick(Achoices); const Bshow = pick(Bchoices); const Cshow = pick(Cchoices); // Count how many removals required to go from shown -> solution (sum removed bits) const need = popcount(DIGIT_MASK[Ashow] ^ DIGIT_MASK[As]) + popcount(DIGIT_MASK[Bshow] ^ DIGIT_MASK[Bs]) + popcount(DIGIT_MASK[Cshow] ^ DIGIT_MASK[Cs]); // Require exactly targetRemove removals if(need !== targetRemove) continue; // 3) Ensure the shown equation is not already true. // (It might accidentally be true with different digit meanings.) const shownTrue = (Ashow + Bshow) === Cshow; if(shownTrue) continue; // Accept puzzle solution = { A: As, B: Bs, C: Cs }; current = { A: Ashow, B: Bshow, C: Cshow }; break; } // UI labels elLvl.textContent = String(level); elTarget.textContent = String(targetRemove); elGoalN.textContent = String(targetRemove); // Friendly hint (don’t reveal exact digits) elHint.innerHTML = `Level ${level}: Make the equation true by removing ${targetRemove} match(es). ` + `You can only remove sticks (click them).`; renderEquation(); syncRemovedCounts(); updateTruthUI(); } // Returns digits whose mask is a SUPERSET of base digit's mask (i.e., can remove to base) function superDigits(baseDigit){ const base = DIGIT_MASK[baseDigit]; const res = []; for(let d=0; d<=9; d++){ const m = DIGIT_MASK[d]; // m must contain all base segments if((m & base) === base){ res.push(d); } } return res; } // Start generateLevel();