diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 15 | ||||
-rwxr-xr-x | apply_params.sh | 40 | ||||
-rw-r--r-- | index.css | 66 | ||||
-rw-r--r-- | index.html | 20 | ||||
-rw-r--r-- | index.js | 348 |
6 files changed, 490 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..354c326 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +SOURCES = index.html index.js index.css + +.PHONY: all clean + +all: out $(patsubst %,out/%,$(SOURCES)) + +clean: + rm -rf out + + +out: + mkdir out + +out/%: % apply_params.sh + ./apply_params.sh <$< >$@ diff --git a/apply_params.sh b/apply_params.sh new file mode 100755 index 0000000..0a12d30 --- /dev/null +++ b/apply_params.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +function colourAdjust() { + local r g b + r="0x${1:1:2}" + g="0x${1:3:2}" + b="0x${1:5:2}" + # r=$((255-r)); g=$((255-g)); b=$((255-b)) + printf '#%02x%02x%02x' $r $g $b +} + +params=( + SS 600 + cBG "$(colourAdjust "#ffffff")" + cFG "$(colourAdjust "#000000")" + cInvFG "$(colourAdjust "#ffffff")" + cFGdisabled "$(colourAdjust "#eeeeee")" + cFGinvalid "$(colourAdjust "#ff0000")" + cBGdigitLargeSelected "$(colourAdjust "#bbbbbb")" + cBGdigitSmallSelected "$(colourAdjust "#aaaaaa")" + cGiven "$(colourAdjust "#000000")" + cNotGiven "$(colourAdjust "#000099")" + cMajor "$(colourAdjust "#111111")" + cMinor "$(colourAdjust "#333333")" + cMenuLarge "$(colourAdjust "#88ff88")" + cMenuSmall "$(colourAdjust "#9999ff")" + cSelFill "$(colourAdjust "#eeeeff")" + cSelCell "$(colourAdjust "#cccccc")" + cSelNum "$(colourAdjust "#f8f8dd")" +) + +nparams=${#params[@]} + +text="$(cat)" +for i in $(seq 0 2 $((nparams-1))); do + from="${params[$i]}" + to="${params[$((i+1))]}" + text="$(sed "s/@@$from@@/$to/g" <<<"$text" | sed "s/@@\"$from\"@@/\"$to\"/")" +done + +cat <<<"$text" diff --git a/index.css b/index.css new file mode 100644 index 0000000..d72be4e --- /dev/null +++ b/index.css @@ -0,0 +1,66 @@ +body { + background-color: @@cBG@@; + color: @@cFG@@; + font: 24px arial; + margin: 2px; + padding: 0; + + overflow-x: hidden; + overflow-y: hidden; +} + +#cvs { + width: 99vw; +} + +#digits-container { + width: @@SS@@px; + text-align: center; +} + +.spacer { + display: inline-block; +} + +#digits-container table { + display: inline-block; + border-spacing: 6px; +} + +#digits-container td { + border-radius: 8px; + margin: 0; + padding: 0; + width: 70px; + height: 70px; + + color: @@cInvFG@@; + + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + + cursor: pointer; +} + +#digit-table-large td.selected { + background-color: @@cBGdigitLargeSelected@@; +} + +#digit-table-small td.selected { + background-color: @@cBGdigitSmallSelected@@; +} + +#digits-container td.disabled { + color: @@cFGdisabled@@; +} + +#digit-table-small td { + background-color: @@cMenuSmall@@; + font-size: 24px; +} + +#digit-table-large td { + background-color: @@cMenuLarge@@; + font-size: 42px; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..474f74f --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<title>Sudoku HTML</title> +<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> +<link rel="stylesheet" href="index.css"> +<script src="index.js"></script> +</head> +<body> +<canvas id="cvs" width="@@SS@@" height="@@SS@@"></canvas><br> +<div id="digits-container"> + <table id="digit-table-small"></table> + <div class="spacer"></div> + <table id="digit-table-large"></table> +</div> + +<div id="status"></div> +</body> +</html> diff --git a/index.js b/index.js new file mode 100644 index 0000000..143215b --- /dev/null +++ b/index.js @@ -0,0 +1,348 @@ +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(); +}); |