little enhancements
This commit is contained in:
parent
cd22ded40e
commit
a75b898ea6
31
style.css
31
style.css
@ -110,14 +110,13 @@ input[type="number"]::-webkit-calendar-picker-indicator {
|
|||||||
font-size: 0.9rem !important;
|
font-size: 0.9rem !important;
|
||||||
}
|
}
|
||||||
.grid input:disabled {
|
.grid input:disabled {
|
||||||
color: white;
|
color: white !important;
|
||||||
background: #6666ff;
|
background: #6666ff !important;
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
.grid input:disabled,
|
|
||||||
.grid input.forbidden:enabled {
|
.grid input.forbidden:enabled {
|
||||||
background: #ffffaa;
|
background: #ffffaa !important;
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
.grid input.same-value:enabled {
|
.grid input.same-value:enabled {
|
||||||
background: #ffff33;
|
background: #ffff33;
|
||||||
@ -129,7 +128,7 @@ input[type="number"]::-webkit-calendar-picker-indicator {
|
|||||||
}
|
}
|
||||||
.grid input.same-value:disabled,
|
.grid input.same-value:disabled,
|
||||||
.tools button.same-value:enabled,
|
.tools button.same-value:enabled,
|
||||||
.tools input:enabled:checked+label {
|
.tools input:enabled:checked + label {
|
||||||
color: #ffffaa !important;
|
color: #ffffaa !important;
|
||||||
background: #00b359 !important;
|
background: #00b359 !important;
|
||||||
}
|
}
|
||||||
@ -138,7 +137,7 @@ input[type="number"]::-webkit-calendar-picker-indicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tools button,
|
.tools button,
|
||||||
.tools input+label {
|
.tools input + label {
|
||||||
color: white;
|
color: white;
|
||||||
text-shadow: -1px -1px #5b6c9e;
|
text-shadow: -1px -1px #5b6c9e;
|
||||||
background: #8ca6f2;
|
background: #8ca6f2;
|
||||||
@ -156,22 +155,28 @@ input[type="number"]::-webkit-calendar-picker-indicator {
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
.tools input {
|
.tools input {
|
||||||
display:none;
|
position: fixed;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.tools button:enabled:hover,
|
.tools button:enabled:hover,
|
||||||
.tools input:enabled:hover+label {
|
.tools button:enabled:focus,
|
||||||
|
.tools input:enabled:hover + label,
|
||||||
|
.tools input:enabled:focus + label {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: outset;
|
border-style: outset;
|
||||||
padding: 5px 5px 5px 6px;
|
padding: 5px 5px 5px 6px;
|
||||||
margin: 1px 1px 1px 2px;
|
margin: 1px 1px 1px 2px;
|
||||||
}
|
}
|
||||||
.tools input:enabled:checked:hover+label {
|
.tools input:enabled:checked:hover + label,
|
||||||
|
.tools input:enabled:checked:focus + label {
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
border-style: inset;
|
border-style: inset;
|
||||||
padding: 4px 2px 2px 5px;
|
padding: 4px 2px 2px 5px;
|
||||||
margin: 1px 1px 1px 2px;
|
margin: 1px 1px 1px 2px;
|
||||||
}
|
}
|
||||||
.tools input:enabled:checked+label {
|
.tools input:enabled:checked + label {
|
||||||
text-shadow: -1px -1px #005f2f;
|
text-shadow: -1px -1px #005f2f;
|
||||||
border: 2px inset #00b359;
|
border: 2px inset #00b359;
|
||||||
background: #00b359;
|
background: #00b359;
|
||||||
@ -179,14 +184,14 @@ input[type="number"]::-webkit-calendar-picker-indicator {
|
|||||||
margin: 1px 1px 0px 2px;
|
margin: 1px 1px 0px 2px;
|
||||||
}
|
}
|
||||||
.tools button:enabled:active,
|
.tools button:enabled:active,
|
||||||
.tools input:enabled:active+label {
|
.tools input:enabled:active + label {
|
||||||
border-width: 4px !important;
|
border-width: 4px !important;
|
||||||
border-style: inset !important;
|
border-style: inset !important;
|
||||||
padding: 4px 0px 0px 5px !important;
|
padding: 4px 0px 0px 5px !important;
|
||||||
margin: 0px 1px 0px 2px !important;
|
margin: 0px 1px 0px 2px !important;
|
||||||
}
|
}
|
||||||
.tools button:disabled,
|
.tools button:disabled,
|
||||||
.tools input:disabled+label {
|
.tools input:disabled + label {
|
||||||
text-shadow: -1px -1px #555;
|
text-shadow: -1px -1px #555;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
background: darkgrey;
|
background: darkgrey;
|
||||||
|
191
sudoku.js
191
sudoku.js
@ -52,6 +52,35 @@ window.onload = function () {
|
|||||||
rowId++
|
rowId++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadSavedGame()
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (label of document.getElementsByTagName("label")) {
|
||||||
|
label.control.label = label
|
||||||
|
}
|
||||||
|
|
||||||
|
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]")) {
|
||||||
|
shortcut = ` [${node.accessKeyLabel||(accessKeyModifiers+node.accessKey)}]`
|
||||||
|
if (node.title) node.title += shortcut
|
||||||
|
else if (node.label) node.label.title += shortcut
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshUI()
|
||||||
|
|
||||||
|
if ("serviceWorker" in navigator) {
|
||||||
|
navigator.serviceWorker.register(`service-worker.php?location=${location.pathname}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSavedGame() {
|
||||||
const savedGame = localStorage[location.pathname]
|
const savedGame = localStorage[location.pathname]
|
||||||
if (savedGame) {
|
if (savedGame) {
|
||||||
boxes.forEach((box, i) => {
|
boxes.forEach((box, i) => {
|
||||||
@ -62,25 +91,6 @@ window.onload = function () {
|
|||||||
})
|
})
|
||||||
fixGridLink.href = savedGame
|
fixGridLink.href = savedGame
|
||||||
}
|
}
|
||||||
|
|
||||||
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]")) {
|
|
||||||
node.title += " [" + (node.accessKeyLabel || accessKeyModifiers + node.accessKey) + "]"
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshUI()
|
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
navigator.serviceWorker.register(`service-worker.php?location=${location.pathname}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchCandidatesOf(box) {
|
function searchCandidatesOf(box) {
|
||||||
@ -111,10 +121,6 @@ function onfocus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onclick() {
|
function onclick() {
|
||||||
if (this.value == "" && this.candidates.size == 1) {
|
|
||||||
valueToInsert = this.candidates.values().next().value
|
|
||||||
document.getElementById("insertRadio" + valueToInsert).checked = true
|
|
||||||
}
|
|
||||||
if (inkPenRadio.checked) {
|
if (inkPenRadio.checked) {
|
||||||
if (valueToInsert) {
|
if (valueToInsert) {
|
||||||
this.value = valueToInsert
|
this.value = valueToInsert
|
||||||
@ -145,20 +151,95 @@ function oninput() {
|
|||||||
this.previousPlaceholder = this.placeholder
|
this.previousPlaceholder = this.placeholder
|
||||||
refreshBox(this)
|
refreshBox(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (suggestionTimer) clearTimeout(suggestionTimer)
|
||||||
|
suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshBox(box) {
|
function refreshBox(box) {
|
||||||
|
saveGame()
|
||||||
|
checkBox(box)
|
||||||
|
refreshUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGame() {
|
||||||
let saveGame = boxes.map(box => box.value || UNKNOWN).join("")
|
let saveGame = boxes.map(box => box.value || UNKNOWN).join("")
|
||||||
localStorage[location.pathname] = saveGame
|
localStorage[location.pathname] = saveGame
|
||||||
fixGridLink.href = saveGame
|
fixGridLink.href = saveGame
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshUI() {
|
||||||
|
enableRadio()
|
||||||
|
highlight()
|
||||||
|
showEasyBoxes()
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableRadio() {
|
||||||
|
for (radio of insertRadioGroup.getElementsByTagName("input")) {
|
||||||
|
if (boxes.filter(box => box.value == "").some(box => box.candidates.has(radio.value))) {
|
||||||
|
radio.disabled = false
|
||||||
|
radio.label.title = `Insérer un ${radio.value} [${radio.accessKeyLabel||(accessKeyModifiers+radio.accessKey)}]`
|
||||||
|
} else {
|
||||||
|
radio.disabled = true
|
||||||
|
radio.label.title = `Tous les ${radio.value} sont posés.`
|
||||||
|
if (valueToInsert == radio.value) {
|
||||||
|
let nextRadio = document.querySelector(".insertRadioGroup :checked ~ input:enabled") || document.querySelector(".insertRadioGroup :enabled")
|
||||||
|
if (nextRadio) {
|
||||||
|
nextRadio.click()
|
||||||
|
nextRadio.onfocus()
|
||||||
|
} else {
|
||||||
|
valueToInsert = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlight() {
|
||||||
|
boxes.forEach(box => {
|
||||||
|
if (valueToInsert && box.value == valueToInsert) {
|
||||||
|
box.classList.add("same-value")
|
||||||
|
box.tabIndex = -1
|
||||||
|
} else {
|
||||||
|
box.classList.remove("same-value")
|
||||||
|
if (box.disabled) {
|
||||||
|
box.classList.add("forbidden")
|
||||||
|
} else {
|
||||||
|
if (valueToInsert && highlighterCheckbox.checked && !box.candidates.has(valueToInsert)) {
|
||||||
|
box.classList.add("forbidden")
|
||||||
|
box.tabIndex = -1
|
||||||
|
} else {
|
||||||
|
box.classList.remove("forbidden")
|
||||||
|
box.tabIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
highlighterCheckbox.label.title = "Surligner les lignes, colonnes et régions contenant déjà " + (valueToInsert? "un " + valueToInsert: "le chiffre sélectionné")
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEasyBoxes() {
|
||||||
|
boxes.filter(box => !box.disabled).forEach(box => {
|
||||||
|
if (!box.value && box.candidates.size == 1) {
|
||||||
|
box.classList.add("one-candidate")
|
||||||
|
box.onclick = function() {
|
||||||
|
valueToInsert = this.candidates.values().next().value
|
||||||
|
document.getElementById("insertRadio" + valueToInsert).checked = true
|
||||||
|
onclick.apply(box)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
box.classList.remove("one-candidate")
|
||||||
|
box.onclick = onclick
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBox(box) {
|
||||||
box.neighbourhood.concat([box]).forEach(neighbour => {
|
box.neighbourhood.concat([box]).forEach(neighbour => {
|
||||||
searchCandidatesOf(neighbour)
|
searchCandidatesOf(neighbour)
|
||||||
neighbour.setCustomValidity("")
|
neighbour.setCustomValidity("")
|
||||||
})
|
})
|
||||||
|
|
||||||
refreshUI()
|
|
||||||
|
|
||||||
for (neighbour1 of box.neighbourhood) {
|
for (neighbour1 of box.neighbourhood) {
|
||||||
if (neighbour1.value) {
|
if (neighbour1.value) {
|
||||||
for (area of [
|
for (area of [
|
||||||
@ -187,56 +268,6 @@ function refreshBox(box) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
|
||||||
label.title = radio.previousTitle
|
|
||||||
label.previousTitle = null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
radio.disabled = true
|
|
||||||
label.previousTitle = label.title
|
|
||||||
label.title = `Tous les ${radio.value} sont posés`
|
|
||||||
if (valueToInsert == radio.value) valueToInsert = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
boxes.forEach(box => {
|
|
||||||
if (valueToInsert && box.value == valueToInsert) {
|
|
||||||
box.classList.add("same-value")
|
|
||||||
box.tabIndex = -1
|
|
||||||
} else {
|
|
||||||
box.classList.remove("same-value")
|
|
||||||
if (box.disabled) {
|
|
||||||
box.classList.add("forbidden")
|
|
||||||
} else {
|
|
||||||
if (valueToInsert && highlighterCheckbox.checked && !box.candidates.has(valueToInsert)) {
|
|
||||||
box.classList.add("forbidden")
|
|
||||||
box.tabIndex = -1
|
|
||||||
} else {
|
|
||||||
box.classList.remove("forbidden")
|
|
||||||
box.tabIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
highlighterCheckbox.nextElementSibling.title = "Surligner les lignes, colonnes et régions contenant déjà " + (valueToInsert? "un " + valueToInsert: "le chiffre sélectionné")
|
|
||||||
}
|
|
||||||
|
|
||||||
function onblur() {
|
function onblur() {
|
||||||
if (this.classList.contains("pencil")) {
|
if (this.classList.contains("pencil")) {
|
||||||
this.placeholder = this.value
|
this.placeholder = this.value
|
||||||
@ -289,7 +320,9 @@ function restart() {
|
|||||||
function showSuggestion() {
|
function showSuggestion() {
|
||||||
const easyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1)
|
const easyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1)
|
||||||
if (easyBoxes.length) {
|
if (easyBoxes.length) {
|
||||||
shuffle(easyBoxes)[0].placeholder = "💡"
|
let randomEasyBox = shuffle(easyBoxes)[0]
|
||||||
|
randomEasyBox.placeholder = "💡"
|
||||||
|
randomEasyBox.focus()
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(suggestionTimer)
|
clearTimeout(suggestionTimer)
|
||||||
suggestionTimer = null
|
suggestionTimer = null
|
||||||
@ -321,16 +354,18 @@ function oncontextmenu(event) {
|
|||||||
}
|
}
|
||||||
contextMenu.style.left = `${event.pageX}px`
|
contextMenu.style.left = `${event.pageX}px`
|
||||||
contextMenu.style.top = `${event.pageY}px`
|
contextMenu.style.top = `${event.pageY}px`
|
||||||
|
console.log(event.target)
|
||||||
contextMenu.style.display = "block"
|
contextMenu.style.display = "block"
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
document.onclick = function (event) {
|
document.onclick = function (event) {
|
||||||
contextMenu.style.display = "none"
|
if (contextMenu.style.display == "block")
|
||||||
|
contextMenu.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
document.onkeydown = function(event) {
|
document.onkeydown = function(event) {
|
||||||
if (event.key == "Escape") {
|
if (event.key == "Escape" && contextMenu.style.display == "block") {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
contextMenu.style.display = "none"
|
contextMenu.style.display = "none"
|
||||||
}
|
}
|
||||||
|
23
sudoku.php
23
sudoku.php
@ -90,26 +90,17 @@
|
|||||||
<div id='insertRadioGroup' class='insertRadioGroup'>
|
<div id='insertRadioGroup' class='insertRadioGroup'>
|
||||||
<?php
|
<?php
|
||||||
for($value=1; $value<=9; $value++) {
|
for($value=1; $value<=9; $value++) {
|
||||||
echo " <input type='radio' id='insertRadio$value' value='$value' name='insertRadioGroup' onclick='insert(this)' accesskey='$value'/>\n";
|
echo " <input type='radio' id='insertRadio$value' value='$value' name='insertRadioGroup' onclick='insert(this)' accesskey='$value'/><label for='insertRadio$value' title='Insérer un $value'>$value</label>\n";
|
||||||
echo " <label for='insertRadio$value' title='Insérer un $value'>$value</label>\n";
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id='highlighterCheckbox' type="checkbox" onclick='highlight()'/>
|
<input id='highlighterCheckbox' type="checkbox" onclick='highlight()'/><label for='highlighterCheckbox' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'><img src='img/highlighter.svg' alt='Surligneur'></label>
|
||||||
<label for='highlighterCheckbox' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'><img src='img/highlighter.svg' alt='Surligneur'></label>
|
<input type='radio' id='inkPenRadio' name='tool' onclick='grid.style.cursor = "url(img/ink-pen.svg) 2 22, auto"' checked/><label for='inkPenRadio' title='Écrire un chiffre'><img src='img/ink-pen.svg' alt='Stylo indélébile'/></label>
|
||||||
<input type='radio' id='inkPenRadio' name='tool' onclick='grid.style.cursor = "url(img/ink-pen.svg) 2 22, auto"' checked/>
|
<input type='radio' id='pencilRadio' name='tool' onclick='grid.style.cursor = "url(img/pencil.svg) 2 22, auto"'/><label for='pencilRadio' title='Prendre des notes'><img src='img/pencil.svg' alt='Crayon'/></label>
|
||||||
<label for='inkPenRadio' title='Écrire un chiffre'><img src='img/ink-pen.svg' alt='Stylo indélébile'/></label>
|
<input type='radio' id='eraserRadio' name='tool' onclick='grid.style.cursor = "url(img/eraser.svg) 2 22, auto"'/><label for='eraserRadio' title='Effacer une case'><img src='img/eraser.svg' alt='Gomme'/></label>
|
||||||
<input type='radio' id='pencilRadio' name='tool' onclick='grid.style.cursor = "url(img/pencil.svg) 2 22, auto"'/>
|
<button type='button' class='warning' onclick='restart()' title='Recommencer'><img src='img/restart.svg' alt='Recommencer'/></button>
|
||||||
<label for='pencilRadio' title='Prendre des notes'><img src='img/pencil.svg' alt='Crayon'/></label>
|
<button id='undoButton' type='button' onclick='undo()' disabled title='Annuler' accesskey='z'><img src='img/undo.svg' alt='Annuler'/></button>
|
||||||
<input type='radio' id='eraserRadio' name='tool' onclick='grid.style.cursor = "url(img/eraser.svg) 2 22, auto"'/>
|
|
||||||
<label for='eraserRadio' title='Effacer une case'><img src='img/eraser.svg' alt='Gomme'/></label>
|
|
||||||
<button type='button' class='warning' onclick='restart()' title='Recommencer'>
|
|
||||||
<img src='img/restart.svg' alt='Recommencer'/>
|
|
||||||
</button>
|
|
||||||
<button id='undoButton' type='button' onclick='undo()' disabled title='Annuler' accesskey='z'>
|
|
||||||
<img src='img/undo.svg' alt='Annuler'/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user