diff --git a/app.js b/app.js index b834ef3..167a61c 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,7 @@ let boxes = [] let rows = Array.from(Array(9), x => []) let columns = Array.from(Array(9), x => []) let regions = Array.from(Array(9), x => []) -let suggestionTimer= null +let suggestionTimer = null let valueToInsert = "" let history = [] let accessKeyModifiers = "AccessKey+" @@ -16,26 +16,27 @@ function shuffle(iterable) { if (array.length > 1) { let i, j, tmp for (i = array.length - 1; i > 0; i--) { - j = Math.floor(Math.random() * (i+1)) + j = Math.floor(Math.random() * (i + 1)) tmp = array[i] array[i] = array[j] array[j] = tmp } - } + } return array } -window.onload = function() { +window.onload = function () { let rowId = 0 for (let row of grid.getElementsByTagName('tr')) { let columnId = 0 for (let box of row.getElementsByTagName('input')) { - let regionId = rowId - rowId%3 + Math.floor(columnId/3) + let regionId = rowId - rowId % 3 + Math.floor(columnId / 3) if (!box.disabled) { box.onfocus = onfocus box.oninput = oninput box.onblur = onblur box.onclick = onclick + box.previousValue = "" box.previousPlaceholder = "" } box.oncontextmenu = oncontextmenu @@ -50,8 +51,8 @@ window.onload = function() { } rowId++ } - - let savedGame = localStorage[location.href] + + const savedGame = localStorage[location.href] if (savedGame) { boxes.forEach((box, i) => { if (!box.disabled && savedGame[i] != UNKNOWN) { @@ -60,28 +61,22 @@ window.onload = function() { } }) } - + boxes.forEach(box => { box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId])) box.neighbourhood.delete(box) box.neighbourhood = Array.from(box.neighbourhood) searchCandidatesOf(box) }) - + if (/Win/.test(navigator.platform) || /Linux/.test(navigator.platform)) accessKeyModifiers = "Alt+Maj+" else if (/Mac/.test(navigator.platform)) accessKeyModifiers = "⌃⌥" - for(node of document.querySelectorAll("*[accesskey]")) { + for (node of document.querySelectorAll("*[accesskey]")) { node.title += " [" + (node.accessKeyLabel || accessKeyModifiers + node.accessKey) + "]" } - - enableRadios() - highlight() - - document.onclick = function (event) { - contextMenu.style.display = "none" - } - suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY) - + + refreshUI() + if ("serviceWorker" in navigator) { navigator.serviceWorker.register("service-worker.js") } @@ -94,81 +89,69 @@ function searchCandidatesOf(box) { switch (box.candidates.size) { case 0: box.title = "Aucune possibilité !" - break + break case 1: box.title = "1 possibilité [Clic-droit]" - break + break default: box.title = box.candidates.size + " possibilités [Clic-droit]" } } } -function onclick() { - if (valueToInsert) { - if (inkPenRadio.checked) { - this.value = valueToInsert - } else { - if (!this.value.includes(valueToInsert)) - this.value += valueToInsert - } - oninput.apply(this) - } -} - function onfocus() { - if (pencilRadio.checked && this.value == "") { + if (pencilRadio.checked) { this.value = this.placeholder - this.placeholder = "" this.classList.add("pencil") } else { this.select() } - if (valueToInsert) { - this.style.caretColor = "transparent" + this.style.caretColor = valueToInsert? "transparent": "auto" +} + +function onclick() { + if (inkPenRadio.checked) { + this.value = valueToInsert + } else if (pencilRadio.checked) { + this.value += valueToInsert } else { - this.style.caretColor = "auto" + this.value = "" + this.placeholder = "" } + this.oninput() } function oninput() { - history.push({box: this, value: this.previousValue, placeholder: this.previousPlaceholder}) - this.previousValue = this.value - this.previousPlaceholder = this.placeholder + history.push({ box: this, value: this.previousValue, placeholder: this.previousPlaceholder }) undoButton.disabled = false - if (inkPenRadio.checked) { - refresh(this) + if (pencilRadio.checked) { + this.value = Array.from(new Set(this.value)).sort().join("") + this.previousValue = "" + this.previousPlaceholder = this.value + } else { + this.previousValue = this.value + this.previousPlaceholder = this.placeholder + refreshBox(this) } } -function undo() { - if (history.length) { - previousState = history.pop() - previousState.box.value = previousState.value - previousState.box.placeholder = previousState.placeholder - refresh(previousState.box) - if (history.length < 1) undoButton.disabled = true - } -} - -function refresh(box) { +function refreshBox(box) { localStorage[location.href] = boxes.map(box => box.value || ".").join("") - + box.neighbourhood.concat([box]).forEach(neighbour => { searchCandidatesOf(neighbour) neighbour.setCustomValidity("") neighbour.required = false }) - - enableRadios() - highlight() - + + refreshUI() + for (neighbour1 of box.neighbourhood) { if (neighbour1.value.length == 1) { for (area of [ - {name: "région", neighbours: regions[neighbour1.regionId]}, - {name: "ligne", neighbours: rows[neighbour1.rowId]}, - {name: "colonne", neighbours: columns[neighbour1.columnId]}, + { name: "région", neighbours: regions[neighbour1.regionId] }, + { name: "ligne", neighbours: rows[neighbour1.rowId] }, + { name: "colonne", neighbours: columns[neighbour1.columnId] }, ]) for (neighbour2 of area.neighbours) if (neighbour2 != neighbour1 && neighbour2.value == neighbour1.value) { @@ -183,76 +166,76 @@ function refresh(box) { } } } - + if (box.form.checkValidity()) { // Correct grid - if (boxes.filter(box => box.value == "").length == 0) { - setTimeout(() => alert(`Bravo ! Vous avez résolu la grille.`), 0) - } else { - boxes.filter(box => box.value == "" && box.tabIndex == 0)[0].focus() - if (suggestionTimer) clearTimeout(suggestionTimer) - suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY) - } + if (boxes.filter(box => box.value == "").length == 0) + setTimeout(() => alert(`Bravo ! Vous avez résolu la grille.`), 500) } else { // Errors on grid box.form.reportValidity() box.select() } } -function enableRadios() { +function refreshUI() { for (radio of insertRadioGroup.getElementsByTagName("input")) { + const label = radio.nextElementSibling if (boxes.filter(box => box.value == "").some(box => box.candidates.has(radio.value))) { radio.disabled = false if (radio.previousTitle) { - radio.title = radio.previousTitle - radio.previousTitle = null + label.title = radio.previousTitle + label.previousTitle = null } } else { radio.disabled = true - radio.previousTitle = radio.title - radio.title = "Tous les " + radio.value + " sont posés" + label.previousTitle = label.title + label.title = `Tous les ${radio.value} sont posés` if (valueToInsert == radio.value) valueToInsert = "" } } -} -function insert(radio) { - if (radio.value == valueToInsert) { - valueToInsert = "" - radio.checked = false - } else { - valueToInsert = radio.value - } highlight() + + boxes.filter(box => !box.disabled).forEach(box => { + if (!box.value && box.candidates.size == 1) box.classList.add("one-candidate") + else box.classList.remove("one-candidate") + }) + if (suggestionTimer) clearTimeout(suggestionTimer) + suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY) } function highlight() { - if (highlighterCheckbox.checked && valueToInsert) { - boxes.forEach(box => { - if (box.value == valueToInsert) { - box.classList.add("same-value") - box.tabIndex = -1 - } - else { - box.classList.remove("same-value") - if (box.candidates.has(valueToInsert) && !box.disabled) { - box.classList.add("allowed") - box.classList.remove("forbidden") - box.tabIndex = 0 - } else { - box.classList.add("forbidden") - box.classList.remove("allowed") + if (valueToInsert) { + if (highlighterCheckbox.checked) { + boxes.forEach(box => { + if (box.value == valueToInsert) { + box.classList.add("same-value") box.tabIndex = -1 } - } - }) + else { + box.classList.remove("same-value") + if (box.candidates.has(valueToInsert) && !box.disabled) { + box.classList.remove("forbidden") + box.tabIndex = 0 + } else { + box.classList.add("forbidden") + box.tabIndex = -1 + } + } + }) + } else { + boxes.forEach(box => { + box.classList.remove("same-value") + if (box.disabled) { + box.classList.add("forbidden") + } else { + box.classList.remove("forbidden") + } + box.tabIndex = 0 + }) + } } else { boxes.forEach(box => { - box.classList.remove("same-value", "allowed", "forbidden") - if (box.disabled) { - box.classList.add("forbidden") - } else if (valueToInsert) { - box.classList.add("allowed") - } + box.classList.remove("same-value", "forbidden") box.tabIndex = 0 }) } @@ -266,10 +249,47 @@ function onblur() { } } +function insert(radio) { + if (radio.value == valueToInsert) { + valueToInsert = "" + radio.checked = false + } else { + valueToInsert = radio.value + } + highlight() +} + +function undo() { + if (history.length) { + const previousState = history.pop() + previousState.box.value = previousState.value + previousState.box.placeholder = previousState.placeholder + refreshBox(previousState.box) + if (history.length < 1) undoButton.disabled = true + } +} + +function restart() { + if (confirm("Effacer toutes les cases ?")) { + boxes.filter(box => !box.disabled).forEach(box => { + box.value = "" + box.previousValue = "" + box.placeholder = "" + box.previousPlaceholder = "" + box.required = false + box.setCustomValidity("") + }) + let history = [] + undoButton.disabled = true + boxes.forEach(searchCandidatesOf) + refreshUI() + } +} + function showSuggestion() { - const emptyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1) - if (emptyBoxes.length) { - shuffle(emptyBoxes)[0].placeholder = "💡" + const easyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1) + if (easyBoxes.length) { + shuffle(easyBoxes)[0].placeholder = "💡" } else { clearTimeout(suggestionTimer) suggestionTimer = null @@ -281,15 +301,15 @@ function oncontextmenu(event) { while (contextMenu.firstChild) contextMenu.firstChild.remove() const box = event.target if (box.candidates.size) { - candidatesArray = Array.from(box.candidates).sort().forEach(candidate => { + Array.from(box.candidates).sort().forEach(candidate => { li = document.createElement("li") li.innerText = candidate li.onclick = function (event) { contextMenu.style.display = "none" - onfocus.apply(box) + box.onfocus() box.value = event.target.innerText - oninput.apply(box) - onblur.apply(box) + box.oninput() + box.onblur() } contextMenu.appendChild(li) }) @@ -305,24 +325,13 @@ function oncontextmenu(event) { return false } -function erasePencil() { - if (confirm("Effacer les chiffres écrits au crayon ?")) { - boxes.filter(box => !box.disabled).forEach(box => { - box.placeholder = "" - }) - } +document.onclick = function (event) { + contextMenu.style.display = "none" } -function eraseAll() { - if (confirm("Effacer tous les chiffres écrits au crayon et au stylo ?")) { - boxes.filter(box => !box.disabled).forEach(box => { - box.value = "" - box.placeholder = "" - box.setCustomValidity("") - box.required = false - }) - boxes.forEach(searchCandidatesOf) - enableRadios() - highlight() +document.onkeydown = function(event) { + if (event.key == "Escape") { + event.preventDefault() + contextMenu.style.display = "none" } } \ No newline at end of file diff --git a/classes.php b/classes.php index b34d5f1..a7cc40e 100644 --- a/classes.php +++ b/classes.php @@ -179,7 +179,7 @@ $str = ""; foreach($this->rows as $row) { forEach($row as $box) { - $str .= ($box->value? $box->value : UNKNOWN); + $str .= $box->value; } } return $str; diff --git a/img/eraser.svg b/img/eraser.svg new file mode 100644 index 0000000..dae3da0 --- /dev/null +++ b/img/eraser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/ink-eraser.svg b/img/ink-eraser.svg deleted file mode 100644 index b233c46..0000000 --- a/img/ink-eraser.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/img/ink-pen.svg b/img/ink-pen.svg index 64ca976..fa78420 100644 --- a/img/ink-pen.svg +++ b/img/ink-pen.svg @@ -1,7 +1,7 @@ + viewBox="0 0 511.989 511.989" style="enable-background:new 0 0 511.989 511.989;" xml:space="preserve" width="24" height="24"> diff --git a/img/pencil-eraser.svg b/img/pencil-eraser.svg deleted file mode 100644 index 91cf5f4..0000000 --- a/img/pencil-eraser.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/img/pencil.svg b/img/pencil.svg index 72e7703..ce5c6c5 100644 --- a/img/pencil.svg +++ b/img/pencil.svg @@ -1,7 +1,7 @@ + viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="24" height="24"> diff --git a/img/restart.svg b/img/restart.svg new file mode 100644 index 0000000..2bd2308 --- /dev/null +++ b/img/restart.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/undo.svg b/img/undo.svg index 1c7c953..8710e84 100644 --- a/img/undo.svg +++ b/img/undo.svg @@ -1 +1,46 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/style.css b/style.css index b17c0d1..a086ffe 100644 --- a/style.css +++ b/style.css @@ -10,27 +10,27 @@ h1 { } section, div, footer { - display: flex; - flex-wrap: wrap; align-items: center; - row-gap: 0.5rem; + align-items: center; justify-content: center; text-align: center; - column-gap: 0.3rem; -} - -section, footer { - margin: 0.8rem auto; + margin: 0.8rem 0; } div { - margin: 0 auto; + display: flex; + flex-wrap: wrap; + row-gap: 0.5rem; + column-gap: 0.3rem; + margin: 0.5rem auto; } .grid { border-spacing: 0; border: 1px solid black; border-radius: 6px; + margin: auto; + cursor: url(img/ink-pen.svg) 2 22, auto; } .grid td, tr { padding: 0; @@ -99,21 +99,26 @@ input::-webkit-inner-spin-button { input::-webkit-calendar-picker-indicator { display: none; } -.grid input:enabled { +.grid input:enabled{ background: white; color: darkblue; + cursor: inherit; } -.grid input:disabled, -button:enabled, -.tools input+label { +.grid input.pencil, +.grid input::placeholder { + color: #666 !important; + font-size: 0.9rem !important; + text-align: bottom; + height: 2.2rem; + padding: 0.3rem 0 0 0; +} +.grid input:disabled { color: white; background: #6666ff; } -.grid input.allowed:enabled { - cursor: copy; -} +.grid input:disabled, .grid input.forbidden { - cursor: not-allowed; + cursor: not-allowed !important; } .grid input.forbidden:enabled { background: #ffffaa; @@ -126,76 +131,78 @@ button:enabled, background: #6666ff; } .grid input.same-value:disabled, -button.same-value:enabled, +.tools button.same-value:enabled, .tools input:enabled:checked+label { color: #ffffaa !important; background: #00b359 !important; } -.grid input.pencil, input::placeholder { - color: #888 !important; - font-size: 1rem !important; - width: 2.5rem !important; - height: 2.5rem !important; +.grid input.one-candidate { + cursor: help !important; } +.tools button, +.tools input+label { + color: white; + text-shadow: -1px -1px #5b6c9e; + background: #8ca6f2; + border: 2px outset #8ca6f2; + border-radius: 4px; + font-size: 1.3rem; + min-width:20px; + padding: 4px 5px 5px 5px; + margin: 0px 1px 1px 1px; + cursor: pointer; +} +.tools img { + display: block; + width: 24px; + height: 24px; +} .tools input { display:none; } -button, -.tools input+label { - border: 2px outset #6666ff; - border-radius: 4px; - font-size: 1.3rem; - padding: 4px 9px 5px 9px; - margin: 0px 1px 1px 1px; -} -img { - width: 16px; - height: 16px; -} -button:enabled:hover, +.tools button:enabled:hover, .tools input:enabled:hover+label { border-width: 1px; border-style: outset; - padding: 5px 9px 5px 10px; + padding: 5px 5px 5px 6px; margin: 1px 1px 1px 2px; } .tools input:enabled:checked:hover+label { border-width: 3px; border-style: inset; - padding: 4px 6px 2px 9px; + padding: 4px 2px 2px 5px; margin: 1px 1px 1px 2px; } .tools input:enabled:checked+label { + text-shadow: -1px -1px #005f2f; border: 2px inset #00b359; background: #00b359; - padding: 4px 8px 4px 9px; + padding: 4px 4px 4px 5px; margin: 1px 1px 0px 2px; } -button:enabled:active, +.tools button:enabled:active, .tools input:enabled:active+label { border-width: 4px !important; border-style: inset !important; - padding: 4px 4px 0px 9px !important; + padding: 4px 0px 0px 5px !important; margin: 0px 1px 0px 2px !important; } -button:disabled, +.tools button:disabled, .tools input:disabled+label { - color: #666; + text-shadow: -1px -1px #555; + color: #ccc; background: darkgrey; border: 1px outset darkgrey; - padding: 5px 10px 6px 10px; + padding: 5px 6px 6px 6px; margin: 0px 1px 1px 1px; + cursor: not-allowed; } -button.warning { +.tools button.warning { background: #ff5050; border-color: #ff5050; } -a { - text-decoration: none; -} - .context-menu { display: none; z-index: 1000; @@ -212,28 +219,27 @@ a { padding: 0; margin: 0; } - .context-menu li { padding: 6px 10px; cursor: default; list-style-type: none; transition: all .3s ease; - user-select: none; font-size: 1rem; } - .context-menu li:hover { background-color: #DEF; } - .context-menu li.error { color: #888 } - .context-menu li.error:hover { background-color: #EEE; } +a { + text-decoration: none; +} + .credits { font-size: 0.8rem; margin: 0; diff --git a/sudoku.php b/sudoku.php index 18d63c7..1c023bc 100644 --- a/sudoku.php +++ b/sudoku.php @@ -80,16 +80,15 @@
- - - - + + + + - -