<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="favicon.png">
<title></title>
<meta name="description" content="" />
<link href="style.css" rel="stylesheet" type="text/css" />
<style>
#win {
--height: calc({{x * y}} * var(--block) + 2px * {{x * y}} + 10px + 2px * {{ y - 1 }});
--width: calc({{x * y}} * var(--block) + 2px * {{x * y}} + 10px + 2px * {{ x - 1 }});
}
#win > div {
width: calc({{x * y + 1}} * var(--width));
}
{% for i in range(1,x * y + 1) %}
#n{{i}}:checked ~ #top > span:nth-child({{i}}) {
background: #AFA;
}
{% endfor %}
.cell {
display: inline-block;
position: absolute;
width: var(--block);
height: var(--block);
border: 1px solid black;
text-align: center;
line-height: var(--block);
}
{% for i in range(1,x * y + 1) %}
#n{{i}}:checked ~ #game .n-{{i}} {
z-index: 10;
}
#n{{i}}:checked ~ #game .n-{{i}}:hover {
background: #CCF;
}
{% endfor %}
{% for i in range(1,x * y + 1) %}
{% for r in range(1,x * y + 1) %}
{% for c in range(1,x * y + 1) %}
{% if ignores[i * x * x * y * y + r * x * y + c] %}{% else %}
#radio-{{i}}-{{r}}-{{c}}:checked ~ #game #cell-{{i}}-{{r}}-{{c}}::before {
content: "{{ label[i - 1] }}";
}
#radio-{{i}}-{{r}}-{{c}}:checked ~ #game #cell-{{x * y + 1}}-{{r}}-{{c}} {
z-index: 20;
}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% set border = 0 %}
{% set i = 1 %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-left: 6px solid black;
}
{% set border = border + 7 %}
{% set i = i + 1 %}
{% for ii in range(2,y) %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
}
{% set border = border + 2 %}
{% set i = i + 1 %}
{% endfor %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-right: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% for iii in range(2,x) %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-left: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% for ii in range(2,y) %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
}
{% set border = border + 2 %}
{% set i = i + 1 %}
{% endfor %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-right: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% endfor %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-left: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% for ii in range(2,y) %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
}
{% set border = border + 2 %}
{% set i = i + 1 %}
{% endfor %}
.col-{{i}} {
left: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-right: 6px solid black;
}
{% set border = 0 %}
{% set i = 1 %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-top: 6px solid black;
}
{% set border = border + 7 %}
{% set i = i + 1 %}
{% for ii in range(2,x) %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
}
{% set border = border + 2 %}
{% set i = i + 1 %}
{% endfor %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-bottom: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% for iii in range(2,y) %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-top: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% for ii in range(2,x) %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
}
{% set border = border + 2 %}
{% set i = i + 1 %}
{% endfor %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-bottom: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% endfor %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-top: 2px solid black;
}
{% set border = border + 3 %}
{% set i = i + 1 %}
{% for ii in range(2,x) %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
}
{% set border = border + 2 %}
{% set i = i + 1 %}
{% endfor %}
.row-{{i}} {
top: calc({{ i - 1 }} * var(--block) + {{border}}px);
border-bottom: 6px solid black;
}
{% for i in range(1,x * y + 1) %}
{% for r in range(1,x * y + 1) %}
.bn-{{i}}.br-{{r}}:checked ~ #n{{i}} ~ #game .n-{{i}}.row-{{r}} {
pointer-events: none;
}
{% endfor %}
{% for c in range(1,x * y + 1) %}
.bn-{{i}}.bc-{{c}}:checked ~ #n{{i}} ~ #game .n-{{i}}.col-{{c}} {
pointer-events: none;
}
{% endfor %}
{% for b in range(1,x * y + 1) %}
.bn-{{i}}.bb-{{b}}:checked ~ #n{{i}} ~ #game label.n-{{i}}.blk-{{b}} {
pointer-events: none;
}
{% endfor %}
{% endfor %}
{% for i in range(1,x * y + 1) %}
{% for ii in range(1,x * y + 1) %}.bn-{{i}}:checked ~ {% endfor %}#n{{i}} ~ #top > span:nth-child({{i}}) {
pointer-events: none;
background: #DDD;
}
{% endfor %}
{% for i in range(1,x * y + 1) %}
{% for ii in range(1,x * y + 1) %}.bn-{{i}}:checked ~ {% endfor %}#win > div > span:nth-child({{i}}) {
display: none;
}
{% endfor %}
.puz {
font-weight: bold;
z-index: 5;
}
</style>
</head>
<body>
<div id="puzzle">
{% for i in range(1,x * y + 2) %}
{% for r in range(1,x * y + 1) %}
{% set b = (r - 1 - ((r - 1) % x)) + 1 %}
{% for c in range(1,x * y + 1) %}
{% if c % y == 1 and c > 1 %}{% set b = b + 1 %}{% endif %}
{% if ignores[ i * x * x * y * y + r * x * y + c] %}{% else %}
<input type="radio" class="bn-{{i}} br-{{r}} bc-{{c}} bb-{{b}}" name="radio-{{r}}-{{c}}" id="radio-{{ i }}-{{ r }}-{{ c }}" {% if cells[ i * x * x * y * y + r * x * y + c] %}checked{% endif %} />
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% for i in range(1,x * y + 1) %}
<input type="radio" name="number" id="n{{i}}" {% if i == 1 %}checked{% endif %}/>
{% endfor %}
<div id="top">
{% for i in range(1,x * y + 1) %}
<span><label for="n{{i}}">{{ label[i - 1] }}</label></span>
{% endfor %}
</div>
<div id="game">
{% for i in range(1,x * y + 2) %}
{% for r in range(1,x * y + 1) %}
{% set b = (r - 1 - ((r - 1) % x)) + 1 %}
{% for c in range(1,x * y + 1) %}
{% if c % y == 1 and c > 1 %}{% set b = b + 1 %}{% endif %}
{% if ignores[ i * x * x * y * y + r * x * y + c] %}{% else %}
<label id="cell-{{ i }}-{{ r }}-{{ c }}" class="cell row-{{ r }} col-{{c}} blk-{{b}} n-{{i}} {% if cells[ i * x * x * y * y + r * x * y + c] %}puz{% endif %}" for="radio-{{ i }}-{{ r }}-{{ c }}"></label>
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
</div>
<div id="win">
<div>
{% for i in range(1,x * y + 1) %}<span></span>{% endfor %}<span>
You solved the puzzle!
</span>
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="favicon.png">
<title></title>
<meta name="description" content="" />
<style>
#container {
display: block;
--mw: calc((100vw - 2rem - 4px * var(--x) * var(--y)) / (var(--x) * var(--y)));
--mh: calc((95vh - 3rem - 4px * var(--x) * var(--y)) / (var(--x) * var(--y) + 2));
--block: min(var(--mw),var(--mh));
--y: 3;
--x: 3;
font-size: max(calc(var(--block) * 2 / 3), 1rem);
}
#menu {
border: 1px solid black;
display: flex;
flex-wrap: nowrap;
width: calc((var(--block) + 2px) * var(--y) * var(--x) + (4px * var(--x)) + 2px);
margin-bottom: var(--block);
line-height: var(--block);
text-align: center;
position: relative;
left: calc(50% - ((var(--block) + 2px) * var(--y) * var(--x) + (4px * var(--x)) + 2px) / 2);
}
#menu > div {
border: 1px solid black;
display: inline-block;
width: var(--block);
height: var(--block);
flex-grow: 1;
flex-shrink: 1;
cursor: pointer;
}
#menu > div.active {
background: #BFB;
}
#menu > div.disabled {
background: #AAA;
}
#menu > div.active.disabled {
background: #9C9;
}
#puzzle {
border: 2px solid black;
display: flex;
flex-wrap: wrap;
width: calc((var(--block) + 2px) * var(--y) * var(--x) + (4px * var(--x)));
position: relative;
left: calc(50% - ((var(--block) + 2px) * var(--y) * var(--x) + (4px * var(--x)) + 2px) / 2);
}
#puzzle > div {
border: 2px solid black;
display: flex;
flex-wrap: wrap;
width: calc((var(--block) + 2px) * var(--y));
height: calc((var(--block) + 2px) * var(--x));
}
#puzzle > div > div {
border: 1px solid black;
width: var(--block);
height: var(--block);
flex-grow: 1;
flex-shrink: 1;
display: inline-block;
line-height: var(--block);
text-align: center;
}
#puzzle > div > div.initial {
font-weight: bold;
}
#puzzle > div > div:Not(.initial) {
cursor: pointer;
}
</style>
</head>
<body onload="setActive(1);">
<div id="container">
<div id="menu"></div>
<div id="puzzle"></div>
</div>
<script>
var puzzleEl = document.getElementById('puzzle');
var menuEl = document.getElementById('menu');
var puzzle = "..3.5.1...7.168.2.8...4...7.8.4.6.9.639...418.2.8.1.3.9...7...3.4.685.7...8.1.2..";
//var puzzle = ".1.6..5.235..164.51.628...2....15....7...765.34.384..625.2..4.7.";
var x = 3;
var y = 3;
var digits = ["","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G"];
var qs = window.location.search;
var urlParams = new URLSearchParams(qs);
puzzle = urlParams.get('p');
if (!puzzle){
puzzle = urlParams.get('puzzle');
}
if (puzzle){puzzle = puzzle.trim()}
if (puzzle.indexOf(",") > -1){
var split = puzzle.split(",");
puzzle =split[0].trim();
}
x = urlParams.get('x');
if (x){
x = parseInt(x);
}
else {
x = Math.floor(Math.sqrt(Math.sqrt(puzzle.length)));
}
y = urlParams.get('y');
if (y){
y = parseInt(y);
}
else {
y = Math.floor(Math.sqrt(puzzle.length/x/x));
}
var customDigits = urlParams.get('d');
if (!customDigits){
customDigits = urlParams.get('digits');
}
if (!customDigits){
customDigits = urlParams.get('n');
}
if (customDigits){
if (customDigits.indexOf(",") > -1){
customDigits = customDigits.split(",");
}
for (var i=0;i<customDigits.length;i++){
digits[i+1]=customDigits[i];
}
}
var container = document.getElementById('container');
//var maxWidth = container.getBoundingClientRect().width;
//var maxHeight = container.getBoundingClientRect().height;
//var blockSize = Math.floor(Math.min((maxWidth - x*y*4)/(x*y),(maxHeight - x*y*4)/(x*y + 2)));
container.style.setProperty("--x",x);
container.style.setProperty("--y",y);
//container.style.setProperty("--block",blockSize+"px");
//container.style.setProperty("height","unset");
var fills = {};
for (var r=0;r<y;r++){
for (var c=0;c<x;c++){
var div = document.createElement('div');
div.id = "blk-"+r+"-"+c;
puzzleEl.appendChild(div);
var div = document.createElement('div');
div.id = "menu-"+(r*x+c+1);
div.textContent = digits[r*x+c+1];
div.addEventListener('click',(evt) => {setActive(evt.currentTarget.id.substring(5));})
menuEl.appendChild(div);
fills[r*x+c+1]={total:0};
}
}
for (var i=0;i<puzzle.length;i++){
var n = 0;
if (puzzle[i] != 0 && puzzle[i] != "."){
n = parseInt(puzzle[i],16);
}
var row = Math.floor(i/(x*y));
var col = i%(x*y);
var rowBlock = Math.floor(row/x);
var colBlock = Math.floor(col/y);
var blockID = "blk-"+rowBlock+"-"+colBlock;
var div = document.createElement('div');
div.textContent = digits[n];
div.id = "cell-"+i;
if (n > 0){
div.classList.add('initial');
fills[n].total++;
fills[n]['r-'+row]=true;
fills[n]['c-'+col]=true;
fills[n]['b-'+rowBlock+"-"+colBlock]=true;
}
else {
div.addEventListener('click',clickCell);
}
document.getElementById(blockID).appendChild(div);
}
function setActive(val){
activeNumber = parseInt(val);
menuEl.querySelectorAll('div').forEach((el) => {
if (el.id == "menu-"+activeNumber){
el.classList.add('active');
}
else {
el.classList.remove('active');
}
})
}
function clickCell(evt){
if (activeNumber < 1){return;}
var el = evt.currentTarget;
var id = parseInt(el.id.substring(5));
var row = Math.floor(id/(x*y));
var col = id%(x*y);
var rowBlock = Math.floor(row/x);
var colBlock = Math.floor(col/y);
if (el.textContent == digits[activeNumber]){
el.textContent = "";
fills[activeNumber]['r-'+row]=false;
fills[activeNumber]['c-'+col]=false;
fills[activeNumber]['b-'+rowBlock+"-"+colBlock]=false;
fills[activeNumber].total--;
}
else if (fills[activeNumber]['r-'+row]) {
alert("There is already a "+digits[activeNumber]+" in that row.");
}
else if (fills[activeNumber]['c-'+col]) {
alert("There is already a "+digits[activeNumber]+" in that column.");
}
else if (fills[activeNumber]['b-'+rowBlock+"-"+colBlock]) {
alert("There is already a "+digits[activeNumber]+" in that block.");
}
else {
el.textContent = digits[activeNumber];
fills[activeNumber]['r-'+row]=true;
fills[activeNumber]['c-'+col]=true;
fills[activeNumber]['b-'+rowBlock+"-"+colBlock]=true;
fills[activeNumber].total++;
}
var complete = true;
menuEl.querySelectorAll('div').forEach((el) => {
var n = parseInt(el.id.substring(5));
if (fills[n].total == x*y){
el.classList.add('disabled');
}
else {
complete = false;
el.classList.remove('disabled');
}
})
if (complete){
win();
}
}
function win(){
alert('You completed the puzzle!');
}
</script>
</body>
</html>
const fs = require('fs');
const express = require('express');
const nunjucks = require('nunjucks');
const app = express();
app.use('/jspuzzle', express.static('./index.html'))
app.use('/jspuzzle.html', express.static('./index.html'))
app.use('/index.html', express.static('./index.html'))
app.use('/style.css', express.static('./style.css'))
app.get(['/csspuzzle', '/csspuzzle.html'], function(req, res) {
//var puzzle = ".1.6..5.235..164.51.628...2....15....7...765.34.384..625.2..4.7.";
var x = 3;
var y = 3;
var digits = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G"];
var puzzle = "";
puzzle = req.query.p;
if (!puzzle) {
puzzle = req.query.puzzle;
}
if (puzzle) { puzzle = puzzle.trim() }
if (puzzle.indexOf(",") > -1) {
var split = puzzle.split(",");
puzzle = split[0].trim();
}
x = req.query.x;
if (x) {
x = parseInt(x);
}
else {
x = Math.floor(Math.sqrt(Math.sqrt(puzzle.length)));
}
y = req.query.y;
if (y) {
y = parseInt(y);
}
else {
y = Math.floor(Math.sqrt(puzzle.length / x / x));
}
var customDigits = req.query.d;
if (!customDigits) {
customDigits = req.query.digits;
}
if (!customDigits) {
customDigits = req.query.n;
}
if (customDigits) {
if (customDigits.indexOf(",") > -1) {
customDigits = customDigits.split(",");
}
for (var i = 0; i < customDigits.length; i++) {
digits[i] = customDigits[i];
}
}
var html = createPuzzle(puzzle, x, y, digits);
res.send(html);
})
app.listen(3000, () => {
console.log('server started');
});
var halloween = ["🎃", "👻", "🦇", "⚰️", "💀", "🧙", "🍭", "🍫", "😱"];
var emojis = ["❤️", "🙂", "🐈", "🐕", "🍕", "🔥", "🏈", "⛄", "🚀"];
var digits = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G"];
var numberMap = {};
for (var i = 0; i < 16; i++) {
numberMap[digits[i]] = i + 1;
}
function createPuzzle(rawPuzzle, x, y, digits) {
console.log(x, y);
var puzzle = { cells: {}, ignores: {}, x: x, y: y, label: digits };
for (var i = 0; i < rawPuzzle.length; i++) {
if (rawPuzzle.at(i) != ".") {
var inti = numberMap[rawPuzzle.at(i)];
var ii = inti * x * x * y * y;
ii += (Math.floor(i / (x * y)) + 1) * x * y;
ii += ((i % (x * y)) + 1);
puzzle.cells[ii] = true;
for (var iii = 1; iii < x * y + 2; iii++) {
if (iii == inti) { continue; }
var ii = iii * x * x * y * y;
ii += (Math.floor(i / (x * y)) + 1) * x * y;
ii += ((i % (x * y)) + 1);
puzzle.ignores[ii] = true;
}
}
}
var html = nunjucks.render('csstemplate.html', puzzle);
return html;
}
#puzzle {
position: relative;
display: block;
--block: 3rem;
height: calc(9 * var(--block));
}
#puzzle input {
display: none;
}
#top {
position: relative;
display: block;
top: 1rem;
left: 1rem;
height: var(--block);
}
#top > span {
display: inline-block;
height: var(--block);
width: var(--block);
border: 1px solid black;
}
#puzzle label {
cursor: pointer;
font-size: calc(3 * var(--block) / 5);
}
#top > span > label {
width: 100%;
height: 100%;
display: inline-block;
text-align: center;
line-height: var(--block);
}
#game {
position: relative;
left: 1rem;
top: calc(var(--block) + 0.5rem);
}
#win {
position: absolute;
left: 1rem;
top: calc(var(--block) * 2 + 0.5rem);
height: var(--height);
width: var(--width);
padding: 0;
overflow: hidden;
z-index: 30;
pointer-events: none;
}
#win > div {
height: var(--height);
padding: 0;
}
#win > div > span {
display: inline-block;
background: transparent;
height: var(--height);
width: var(--width);
}
#win > div > span:last-child {
background: rgba(255,50,50,0.4);
text-align: center;
font-size: 2rem;
line-height: 3rem;
box-sizing: border-box;
padding: 1rem;
text-shadow: 2px 2px 3px rgba(255, 255, 255, 1),2px -2px 3px rgba(255, 255, 255, 1),-2px 2px 3px rgba(255, 255, 255, 1),-2px -2px 3px rgba(255, 255, 255, 1),1px 1px 1px rgba(255, 255, 255, 1),1px -1px 1px rgba(255, 255, 255, 1),-1px 1px 1px rgba(255, 255, 255, 1),-1px -1px 1px rgba(255, 255, 255, 1),4px 4px 4px rgba(255, 255, 255, 1),4px -4px 4px rgba(255, 255, 255, 1),-4px 4px 4px rgba(255, 255, 255, 1),-4px -4px 4px rgba(255, 255, 255, 1);
}
This repl serves sudoku puzzles of a variety of sizes in both JavaScript and CSS only formats.
To view this on TripleLog, click here.
Visit {{url}}/jspuzzle?p={{Puzzle String}}&x=3&y=3 to generate a JavaScript solvable puzzle where {{Puzzle String}} should be replaced by the string for the puzzle. x and y are the width and height of the blocks so 3 and 3 for a normal puzzle. You can also add &d=1,2,3,4,5,6,7,8,9 to set the characters for each digit where you can just replace the digit with a letter, emoji, or another digit.
Visit {{url}}/csspuzzle?p={{Puzzle String}}&x=3&y=3 for the same but with CSS-only sudoku puzzles.
The index.html file grabs the parameters in the url to create the JS puzzles. The csstemplate.html is the template for the CSS only puzzles.
The style.css file applies to the CSS only puzzles while the CSS for the JS puzzles is included in the index.html file.
The server.js will create the HTML files and serve them. The JS puzzles can be served as a static file. Run the server with node server.js. You will need to npm install express and nunjucks.