var cvs, ctx, cvsScaling = 1; var bd, bdnotes, isgiven; var selidx = -1; function contains(l, x) { for (var i = 0; i < l.length; i++) { if (l[i] == x) return true; } return false; } function setupBoard() { bd = []; for (var i = 0; i < 9; i++) { var row = []; for (var j = 0; j < 9; j++) row.push(-1); bd.push(row); } bd[1][0] = 4; bd[2][1] = 5; bd[0][3] = 2; bd[2][4] = 9; bd[0][6] = 8; bd[1][8] = 7; bd[4][0] = 7; bd[5][0] = 1; bd[3][3] = 6; bd[4][4] = 4; bd[3][6] = 3; bd[3][7] = 2; bd[6][2] = 8; bd[7][1] = 2; bd[7][3] = 3; bd[6][7] = 9; bd[6][8] = 4; bdnotes = []; for (var i = 0; i < 9; i++) { var row = []; for (var j = 0; j < 9; j++) row.push([]); bdnotes.push(row); } isgiven = []; for (var i = 0; i < 9; i++) { var row = []; for (var j = 0; j < 9; j++) row.push(bd[i][j] != -1); isgiven.push(row); } } function setupHTML() { var cont = document.getElementById("digits-container"); cont.children[1].setAttribute("style", "width: " + @@SS@@ * 0.15 + "px"); var tbls = [ document.getElementById("digit-table-small"), document.getElementById("digit-table-large") ]; var prefixes = ["digit-small-", "digit-large-"]; for (var i = 0; i < 2; i++) { var tbl = tbls[i]; var tbody = document.createElement("tbody"); for (var y = 0; y < 3; y++) { var tr = document.createElement("tr"); for (var x = 0; x < 3; x++) { var td = document.createElement("td"); var n = 3 * y + x + 1; td.id = prefixes[i] + n; td.innerHTML = n; td.addEventListener("mousedown", enterDigit.bind(this, n, i == 1)); td.addEventListener("touchstart", (function(n, isLarge, ev) { if (ev.changedTouches.length == 1) { ev.preventDefault(); enterDigit(n, isLarge); } }).bind(this, n, i == 1)); tr.appendChild(td); } tbody.appendChild(tr); } tbl.appendChild(tbody); } } function enterDigit(n, isLarge) { var selX = selidx % 9, selY = selidx / 9 | 0; if (selidx != -1 && !isgiven[selY][selX]) { if (isLarge) { if (bd[selY][selX] == n) bd[selY][selX] = -1; else bd[selY][selX] = n; } else { if (bd[selY][selX] == -1) { var found = false; for (var i = 0; i < bdnotes[selY][selX].length; i++) { if (bdnotes[selY][selX][i] == n) { bdnotes[selY][selX].splice(i, 1); found = true; break; } } if (!found) bdnotes[selY][selX].push(n); } } drawBoard(); writeStorage(); } } function listenEvents() { var cvsClick = function(x, y) { x /= cvsScaling; y /= cvsScaling; x = x / @@SS@@ * 9 | 0; y = y / @@SS@@ * 9 | 0; if (x < 0) x = 0; if (x > 8) x = 8; if (y < 0) y = 0; if (y > 8) y = 8; selidx = 9 * y + x; drawBoard(); }; cvs.addEventListener("mousedown", function(ev) { var bbox = cvs.getBoundingClientRect(); cvsClick(ev.clientX - bbox.left, ev.clientY - bbox.top); }); cvs.addEventListener("touchstart", function(ev) { if (ev.changedTouches.length == 1) { ev.preventDefault(); var touch = ev.changedTouches[0]; var bbox = cvs.getBoundingClientRect(); cvsClick(touch.clientX - bbox.left, touch.clientY - bbox.top); } }); } // Considers the passed cell empty function possibles(x, y) { var poss = new Array(10); for (var i = 1; i <= 9; i++) poss[i] = true; var ltx = ~~(x / 3) * 3; var lty = ~~(y / 3) * 3; for (var i = 0; i < 9; i++) { if (i != x && bd[y][i] != -1) poss[bd[y][i]] = false; if (i != y && bd[i][x] != -1) poss[bd[i][x]] = false; var bx = ltx + i % 3; var by = lty + ~~(i / 3); if ((bx != x || by != y) && bd[by][bx] != -1) poss[bd[by][bx]] = false; } var ret = []; for (var i = 1; i <= 9; i++) if (poss[i]) ret.push(i); return ret; } function cellLT(idx) { var border = idx * (@@SS@@ - 2) / 9 | 0; return border + 1 + (idx % 3 == 0); } function cellRB(idx) { var border = (idx + 1) * (@@SS@@ - 2) / 9 | 0; return border - 1; } function drawBoard() { var SS = @@SS@@; var selX = selidx == -1 ? -1 : selidx % 9; var selY = selidx == -1 ? -1 : selidx / 9 | 0; var selNum = selidx == -1 ? -1 : bd[selY][selX]; ctx.fillStyle = @@"cBG"@@; ctx.fillRect(0, 0, SS, SS); ctx.lineWidth = 1; ctx.strokeStyle = @@"cMinor"@@; ctx.beginPath(); for (var i = 1; i <= 8; i++) { if (i % 3 == 0) continue; var c = cellLT(i) - 0.5; ctx.moveTo(c, 0); ctx.lineTo(c, SS); ctx.moveTo(0, c); ctx.lineTo(SS, c); } ctx.stroke(); ctx.lineWidth = 2; ctx.strokeStyle = @@"cMajor"@@; ctx.beginPath(); for (var i = 0; i <= 9; i += 3) { var c = cellLT(i) - 1; ctx.moveTo(c, 0); ctx.lineTo(c, SS); ctx.moveTo(0, c); ctx.lineTo(SS, c); } ctx.stroke(); ctx.fillStyle = @@"cFG"@@; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = "40px arial"; for (var y = 0; y < 9; y++) { for (var x = 0; x < 9; x++) { var clr = ""; if (selidx != -1) { if (selNum != -1 && bd[y][x] == selNum) { clr = @@"cSelNum"@@; } else if (x == selX && y == selY) { clr = @@"cSelCell"@@; } else if (selY == y || selX == x || (~~(selX / 3) == ~~(x / 3) && ~~(selY / 3) == ~~(y / 3))) { clr = @@"cSelFill"@@; } } if (clr != "") { ctx.fillStyle = clr; ctx.fillRect(cellLT(x), cellLT(y), cellRB(x) - cellLT(x) + 1, cellRB(y) - cellLT(y) + 1); } if (bd[y][x] != -1) { if (isgiven[y][x]) ctx.fillStyle = @@"cGiven"@@; else if (!contains(possibles(x, y), bd[y][x])) ctx.fillStyle = @@"cFGinvalid"@@; else ctx.fillStyle = @@"cNotGiven"@@; ctx.font = "40px arial"; ctx.fillText(bd[y][x], (cellLT(x) + cellRB(x)) / 2, (cellLT(y) + cellRB(y)) / 2 + 2) } else { ctx.font = "18px arial"; for (var i = 0; i < bdnotes[y][x].length; i++) { var n = bdnotes[y][x][i]; var tx = (n - 1) % 3, ty = (n - 1) / 3 | 0; if (!contains(possibles(x, y), n)) ctx.fillStyle = @@"cFGinvalid"@@; else ctx.fillStyle = @@"cFG"@@; ctx.fillText(n, cellLT(x) + (cellRB(x) - cellLT(x)) / 6 * (2 * tx + 1), cellLT(y) + (cellRB(y) - cellLT(y)) / 6 * (2 * ty + 1) + 2); } } } } if (selidx != -1) { for (var i = 1; i <= 9; i++) { if (isgiven[selY][selX]) { document.getElementById("digit-large-" + i).classList.add("disabled"); } else { document.getElementById("digit-large-" + i).classList.remove("disabled"); if (bd[selY][selX] == i) { document.getElementById("digit-large-" + i).classList.add("selected"); } else { document.getElementById("digit-large-" + i).classList.remove("selected"); } } } for (var i = 1; i <= 9; i++) { if (bd[selY][selX] == -1 && contains(bdnotes[selY][selX], i)) { document.getElementById("digit-small-" + i).classList.add("selected"); } else { document.getElementById("digit-small-" + i).classList.remove("selected"); } } } } var storagePrefix = "sudokuhtml_"; function readStorage() { try { localStorage.getItem(storagePrefix); } catch (e) { console.log(e); alert("This browser doesn't support the Local Storage API. This means that progress cannot be saved."); return; } var v = localStorage.getItem(storagePrefix + "bd"); if (v != null) bd = JSON.parse(v); v = localStorage.getItem(storagePrefix + "bdnotes"); if (v != null) bdnotes = JSON.parse(v); } function writeStorage() { localStorage.setItem(storagePrefix + "bd", JSON.stringify(bd)); localStorage.setItem(storagePrefix + "bdnotes", JSON.stringify(bdnotes)); } var menuScalingState = null; function fixMenuScaling() { var cont = document.getElementById("digits-container"); var cvs_bbox = cvs.getBoundingClientRect(); var targetw = cvs_bbox.width; var cont_bbox = menuScalingState ? menuScalingState.cont_bbox : cont.getBoundingClientRect(); var wid = menuScalingState ? menuScalingState.wid : cont_bbox.width; var hei = menuScalingState ? menuScalingState.hei : cont_bbox.height; var sc = targetw / wid; var transx = targetw / 2 - wid / 2; var targeth = sc * hei; var transy = targeth / 2 - hei / 2; cont.setAttribute("style", "transform: translate(" + transx + "px, " + transy + "px) scale(" + targetw / wid + ")"); cvsScaling = targetw / @@SS@@; menuScalingState = { cont_bbox: cont_bbox, wid: wid, hei: hei, }; } function setupInterfaceFixes() { setTimeout(fixMenuScaling, 1); var tmout = null; window.addEventListener("resize", function() { if (tmout) { clearTimeout(tmout); } tmout = setTimeout(function() { tmout = null; fixMenuScaling(); }, 100); }); } window.addEventListener("load", function() { cvs = document.getElementById("cvs"); cvs.width = cvs.height = @@SS@@; ctx = cvs.getContext("2d"); setupHTML(); setupBoard(); readStorage(); writeStorage(); drawBoard(); listenEvents(); setupInterfaceFixes(); });