Skip to content

Instantly share code, notes, and snippets.

@matthewmorrone
Last active July 20, 2020 16:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewmorrone/9b4082b0a4989f574f7ab90db84968aa to your computer and use it in GitHub Desktop.
Save matthewmorrone/9b4082b0a4989f574f7ab90db84968aa to your computer and use it in GitHub Desktop.
nyt updated their sudoku implementation
.su-candidate-button {
-webkit-transition: opacity 1;
-moz-transition: opacity 1;
-o-transition: opacity 1;
transition: opacity 1;
-webkit-transition-delay: 0;
-moz-transition-delay: 0;
-o-transition-delay: 0;
transition-delay: 0;
opacity: 0;
}
.unit, .single /*:not(.selected) */, [single], [unit], .checked {
/*background-color: #00FF0080;*/
}
.selected {
background-color: #FFDA00FF !important;
}
.attention {
background-color: #00FF0040;
}
#pzm-congrats .su-modal__message {
visibility: hidden;
}
console.clear();
Object.defineProperty(Object.prototype, "define", {
writable: false,
enumerable: false,
configurable: true,
value: function(key, value) {
if (Object[key]) {
delete Object[key];
}
Object.defineProperty(this, key, {
writable: false,
enumerable: false,
configurable: true,
value: value
});
return this.key;
}
});
function round(numToRound, numToRoundTo) {
return Math.round(numToRound / numToRoundTo) * numToRoundTo;
}
function floor(numToFloor, numToFloorTo) {
return Math.floor(numToFloor / numToFloorTo) * numToFloorTo;
}
Array.prototype.define("each", Array.prototype.forEach);
Object.prototype.define("getKeys", function() {
return Object.keys(this);
});
Object.prototype.define("getValues", function() {
return Object.values(this);
});
Object.prototype.define("getEntries", function() {
return Object.entries(this);
});
Object.prototype.define("isEmpty", function() {
return Object.entries(this).length === 0 && this.constructor === Object;
});
Array.prototype.define("unique", function() {
return [...new Set(this)];
});
String.prototype.define("toProperCase", function() {
return this.replace(/\w\S*/g, function(txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
});
String.prototype.define("int", function() {
return Number.parseInt(this, 10);
});
String.prototype.define("sort", function() {
return this.split("").sort().join("");
})
Object.prototype.define("map", function(f, ctx) {
let o = this;
ctx = ctx || this;
let result = {};
Object.keys(o).forEach(function(k) {
result[k] = f.call(ctx, o[k], k, o);
});
return result;
});
Array.prototype.define("toSet", function() {
return new Set(this);
});
Array.prototype.define("unique", function() {
return Array.from(new Set(this));
});
Object.define("size", function(...args) {
let len = 0;
for (let arg of args) {
len += arg.size();
}
return len;
});
Object.prototype.define("size", function(...args) {
let len = 0;
for (let arg of args) {
len += arg.size();
}
return len + Object.entries(this).length;
});
String.prototype.define("intersection", function(s2) {
let s1 = this, i, a = {};
for (i = 0,
l = s1.length; i < l; i++) {
a[s1[i]] = (a[s1[i]] || 0) + 1;
}
for (i = 0,
l = s2.length; i < l; i++) {
a[s2[i]] = (a[s2[i]] || 0) + 1;
}
for (var p in a) {
if (a[p] <= 1) {
delete a[p];
}
}
return a.getKeys().join("").trim();
});
function unwrap({candidates, frequencies}, index) {
frequencies = frequencies[index];
return {frequencies, candidates};
}
$.each({
bottom: "bottom",
left: "left",
right: "right",
top: "top"
}, function() {
let attr = arguments[0]
$.fn[attr] = function() {
if (arguments.length === 0) {
return parseFloat(this.css(attr))
}
if (arguments.length === 1) {
this.css(attr, arguments[0])
}
return this
}
})
function getActive() {
return $(".su-cell.selected");
}
function getCandidateContainer(cell) {
return $(`div[data-candidate=${$(cell).attr("data-cell")}]`).eq(0)
}
function getCandidates($cell) {
return getCandidateContainer($cell).find("svg")
.toArray().filter(el => $(el).attr("class") === "su-candidate")
.map(el => $(el).attr("number")).unique().sort().join("");
}
function getCoordinates($cell) {
$cell = $($cell);
let id = Number.parseInt($cell.attr("data-cell"), 10);
let x = id % 9 + 1;
let y = Math.floor(id / 9) + 1;
return {
x: x,
y: y,
id: id,
c: getCandidates($cell)
};
}
function getSec(k) {
return getSecs()[k - 1];
}
function getSecs() {
let rows = getRows(), secs = [];
for (let k of Array(9).keys()) {
[j, i] = [k % 3 * 3, Math.floor(k / 3) * 3];
let stuff = rows.slice(i, i + 3).map(r => r.slice(j, j + 3))
for (let l = 0; l < rows.length; l++) {
stuff.push(rows[l].slice(j, j + 3));
}
secs.push(stuff);
}
secs = secs.map(function($a) {
$a = $a.map(a => Array.from(a));
$a = $a[0].concat($a[1]).concat($a[2]);
return $a;
});
return secs;
}
function getRow(i) {
return $(".su-board").find("[data-cell]").slice(i * 9, (i + 1) * 9);
}
function getRows() {
let cells = $(".su-board").find("[data-cell]"), rows = [];
while (cells.length) {
rows.push(cells.splice(0, 9));
}
return rows;
}
function getCol(j) {
let cells = $(".su-board").find("[data-cell]"), col = [];
return cells.filter(function(index) {
return index % 9 == j;
});
}
function getCols($table) {
let rows = getRows(), cols = [];
for (let i of Array(9).keys()) {
let col = [];
for (let j of Array(9).keys()) {
col.push(rows[j][i]);
}
cols.push(col);
}
return cols;
}
function removeDuplicateCells(cells) {
let i, j, coords = [], cell, flag;
for (i = 0; i < cells.length; i++) {
cell = cells[i];
flag = false;
for (j = 0; j < coords.length; j++) {
if (cell.id === coords[j].id) {
flag = true;
}
}
if (flag === false) {
coords.push(cell);
}
}
return coords;
}
function count(arr) {
let a = [], b = [], prev;
arr.sort();
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== prev) {
a.push(arr[i]);
b.push(1);
}
else {
b[b.length - 1]++;
}
prev = arr[i];
}
return [a, b];
}
function getFrequencies(str, f=1, g=false) {
let fre = {}, i, chr, ret = {};
for (i = 0; i < str.length; i++) {
chr = str.charAt(i);
fre[chr] ? fre[chr]++ : fre[chr] = 1;
}
if (f == 0) {
return fre;
}
if (g) {
Object.entries(fre).forEach(function(entry) {
let[key, val] = entry;
ret[val] ? ret[val].push(entry) : ret[val] = [entry];
})
return ret;
}
else {
Object.keys(fre).forEach(function(key) {
if (fre[key] === f) {
ret[key] = fre[key];
}
})
return ret;
}
}
function groupData(thing) {
let disp = $(thing).map(function(a, sq) {
return getCandidates($(sq));
}).toArray();
let fre = getFrequencies(disp.join(""), 1, true);
let result = {
cells: thing,
candidates: disp,
frequencies: fre
}
if (fre[1] && fre[1].length === 1) {
result.answer = fre[1][0][0]
}
if (fre[1] && fre[1].length > 1) {
result.answer = fre[1].map(x => x[0]).join(", ")
}
return result;
}
function perform($things, n=2, info={parity: "", group: ""}) {
let results = {}, $single, data, freq;
if (n === 1) {
$things.forEach(function(thing, x) {
data = groupData(thing);
$single = ""
if (!data.frequencies.isEmpty()) {
if (data.frequencies[n] && data.frequencies[n][0]) {
data.cells.each(function(el) {
if ($(el).hasClass("prefilled")) return
if (getCandidates($(el)).includes(data.frequencies[n][0][0])) {
$single = $(el)
}
})
if (!$single) return
$single.attr("parity", $things.parity);
$single.attr("single", true)
data.coordinates = getCoordinates($single);
data.frequencies = data.frequencies.map(x => x.map(y => y[0]));
results[x] = data;
}
}
})
return results;
}
$things.forEach(function(thing, x) {
let data = groupData(thing);
let d1 = data.candidates.filter(Boolean);
let d2 = d1.join("").split("").unique().sort().join("");
if (!d1.length || !d2.length) {
return;
}
if ((n === d1.length) && (d1.length === d2.length)) {
data.parity = 'bare';
results[x] = data;
return;
}
data.squares = data.cells.map(cell => getCoordinates(cell));
if (data.frequencies[n]) {
let nset = data.frequencies[n].map(m => m[0]).join("");
data.squares = data.squares.filter(square => square.c.intersection(nset));
}
let d3 = data.candidates.filter(function(a) {
return a.length === n
})
let d4 = Array.from(new Set(d3))
if (d3.length > d4.length) {
data.set = data.candidates.join("").split("").unique().sort().join("")
// if () {
// isolate the relevant pair of squares, and check if their frequencies exceed their parity
// }
if (data.set.replace(d4.join("").sort(), "").length > 0) {
data.parity = 'plain';
}
else {
data.parity = 'bare';
}
delete data.set;
delete data.squares;
data.frequencies = data.frequencies.map(x => x.map(y => y[0]));
results[x] = data;
return;
}
let fre = getFrequencies(data.candidates.join(""), 0);
let th = [], tn = [], res = {};
Object.entries(fre).forEach(function(entry) {
let[key, val] = entry;
if (val === n) {
th.push(key);
}
})
if (th.length > 1) {
$(thing).each(function() {
let obj = getCoordinates($(this));
th.forEach(function(y) {
if (obj.c.includes(y)) {
tn.push(obj)
}
})
})
tn = removeDuplicateCells(tn);
if (tn.length === n && Object.keys(data.frequencies[n]).length === n && Object.keys(data.frequencies).length > 1) {
data.parity = 'mask';
data.frequencies = data.frequencies.map(x => x.map(y => y[0]));
delete data.n;
delete data.squares;
results[x] = data;
}
}
})
return results;
}
function sudoku() {
// next, implement checks for confinement of possibilities in a sec to a single row or col
let display = {
unit: []
}
$cells.each(function() {
let coordinates = getCoordinates($(this))
if (coordinates.c.length === 1) {
$(this).attr("unit", true)
display.unit.push(coordinates)
}
})
if (!$.isEmptyObject(display.unit)) {
console.group("Unit")
if (display.unit.length === 1) {
console.log("1 unit")
}
else {
console.log(display.unit.length + " units")
}
console.groupEnd("Unit")
}
function displayGroups(group, name, index) {
if (!$.isEmptyObject(group)) {
group.size() === 1 ? name = name.slice(0, -1) : name
console.group(name)
Object.entries(group).forEach(function (entry) {
console.log(Number.parseInt(entry[0])+1, entry[1])
})
console.groupEnd(name)
}
}
let groups = [
"singles", "doubles"// , "triples" // , "quadruples", "pentuples", "sixtuples", "heptuples", "octuples"
]
groups.each(function(group, n) {
let secs = perform($secs, n+1, {parity: "sec", group: groups[n-1]})
, rows = perform($rows, n+1, {parity: "row", group: groups[n-1]})
, cols = perform($cols, n+1, {parity: "col", group: groups[n-1]})
, empty = Object.size(rows, cols, secs) < 1
, prevEmpty = display[groups[n-1]]
? display[groups[n-1]].empty
: false
display[group] = {
secs: secs,
rows: rows,
cols: cols,
empty: empty,
prevEmpty: prevEmpty
}
})
function groupsToConsole(groups, name, collapsed) {
if (!collapsed) {
console.group(name)
}
else {
console.groupCollapsed(name)
}
displayGroups(groups.rows, "Rows", name.toLowerCase())
displayGroups(groups.cols, "Cols", name.toLowerCase())
displayGroups(groups.secs, "Secs", name.toLowerCase())
console.groupEnd(name)
}
let displayed = 0
groups.each(function(group, n) {
if (display[group].empty) {
return
}
displayed++
groupsToConsole(display[group], group.toProperCase(), displayed !== 1)
})
let count = $("[single]").length + display.unit.length
$("#count").text(`(${count})`)
$("[single]").removeAttr("single")
}
$(".su-keyboard__auto").after(`<div class="su-keyboard__auto">
<label style='display: contents;'>
<input class="su-keyboard__checkbox" type="checkbox" name="helper" id="helper">
<div>Helper Mode&nbsp;<span id='count'></span></div>
</label>
</div>`)
$(function() {
$cells = $(".su-cell")
$cands = $(".su-candidates")
$rows = getRows()
$cols = getCols()
$secs = getSecs()
let top = $(".su-board").offset().top
let left = $(".su-board").offset().left
let height = $(".su-board").height()
let width = $(".su-board").width()
let x, y, id
$(".su-candidates").each(function(a, b) {
x = $(this).find("svg").eq(0).css("left").replace("px", "").int()
y = $(this).find("svg").eq(0).css("top") .replace("px", "").int()
x = round(floor(x, 78) / width, .11) / .11
y = round(floor(y, 78) / height, .11) / .11
id = 0 + Number.parseInt(y, 10) * 9 + Number.parseInt(x, 10)
$(this).attr("x", x+1)
$(this).attr("y", y+1)
$(this).attr("cell", id)
})
$cands.each(function(index, cand) {
$(cand).removeAttr("style")
let $svg = $(cand).find("svg").eq(0)
let cell = $(document.elementsFromPoint($svg.position().left, $svg.position().top)).filter(".su-cell")[0]
$svg.parent().attr("data-candidate", $(cell).attr("data-cell"))
})
$("#helper").click(function() {
$("[single]").removeAttr("single")
$("[unit]").removeAttr("unit")
$("[parity]").removeAttr("parity")
$(".attention").removeClass("attention")
$("#count").text("")
console.clear()
if (!$("#helper").is(":checked")) {
return
}
sudoku()
})
$(".su-board").off("mouseup").on("mouseup", function(e) {
if (e.which === 116) return
$active1 = {x: 0, y: 0}
$active2 = {x: 0, y: 0}
$active1 = getCoordinates(getActive())
setTimeout(function() {
$active2 = getCoordinates(getActive())
if ($active1.x !== $active2.x || $active1.y !== $active2.y) {
return
}
if ($(".conflicted").length) {
return
}
$("[single]").removeAttr("single")
$("[unit]").removeAttr("unit")
$("[parity]").removeAttr("parity")
$(".attention").removeClass("attention")
$("#count").text("")
console.clear()
if (!$("#helper").is(":checked")) {
return
}
sudoku()
})
})
$(document).off("keyup").on("keyup", function(e) {
if (e.which === 116) return
if (e.which >= 37 && e.which <= 40) {
return
}
e.preventDefault()
$active1 = {x: 0, y: 0}
$active2 = {x: 0, y: 0}
$active1 = getCoordinates(getActive())
setTimeout(function() {
$active2 = getCoordinates(getActive())
if ($active1.x !== $active2.x || $active1.y !== $active2.y) {
return
}
if ($(".conflicted").length) {
return
}
$("[single]").removeAttr("single")
$("[unit]").removeAttr("unit")
$("[parity]").removeAttr("parity")
$(".attention").removeClass("attention")
$("#count").text("")
console.clear()
if (!$("#helper").is(":checked")) {
return
}
sudoku()
})
})
if ($("#helper").is(":checked")) {
sudoku()
}
$(".su-app__print").before(`<span role="button" class="pz-toolbar-button su-app__reset">Reset</span>`)
$(".su-app__reset").click(function() {
$(".su-toolbar__help-menu-toggle").click()
$(".su-toolbar__help-menu .su-button__dropdown-item").eq(5).click()
})
$("#portal-game-toolbar").prepend(`<span role="button" class="pz-toolbar-button"><a href="/crosswords">Home</a></span>`)
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment