summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile15
-rwxr-xr-xapply_params.sh40
-rw-r--r--index.css66
-rw-r--r--index.html20
-rw-r--r--index.js348
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();
+});