Sudoku/sudoku.js
2020-10-06 08:50:02 +02:00

197 lines
6.0 KiB
JavaScript

const VALUES = "123456789"
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 highlightedValue = ""
window.onload = function() {
let rowId = 0
for (row of grid.getElementsByTagName('tr')) {
let columnId = 0
for (box of row.getElementsByTagName('input')) {
let regionId = rowId - rowId%3 + Math.floor(columnId/3)
if (!box.readOnly) box.onfocus = box.select
box.oninput = oninput
box.oninvalid = oninvalid
box.onkeydown = keyboardBrowse
box.rowId = rowId
box.columnId = columnId
box.regionId = regionId
boxes.push(box)
rows[rowId].push(box)
columns[columnId].push(box)
regions[regionId].push(box)
columnId++
}
rowId++
}
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)
})
boxes.forEach(searchAllowedValuesOf)
enableButtons()
boxes.forEach(showAllowedValuesOn)
for(box of boxes) {
if (!box.readOnly) {
box.focus()
break
}
}
suggestionTimer = setTimeout(showSuggestion, 30000)
}
function searchAllowedValuesOf(box) {
box.allowedValues = new Set(VALUES)
box.neighbourhood.forEach(neighbour => box.allowedValues.delete(neighbour.value))
}
function showAllowedValuesOn(box) {
box.required = box.allowedValues.size == 0
if (box.value.length) {
box.title = ""
} else if (box.allowedValues.size) {
const allowedValuesArray = Array.from(box.allowedValues).sort()
box.title = allowedValuesArray.length ==1 ? allowedValuesArray[0] : allowedValuesArray.slice(0, allowedValuesArray.length-1).join(", ") + " ou " + allowedValuesArray[allowedValuesArray.length-1]
} else {
box.title = "Aucune valeur possible !"
}
}
function oninput() {
this.style.color = colorPicker.value
this.neighbourhood.concat([this]).forEach(box => {
box.setCustomValidity("")
searchAllowedValuesOf(box)
box.pattern = `[${Array.from(box.allowedValues).join("")}]?`
})
enableButtons()
refreshShowValue()
this.neighbourhood.concat([this]).forEach(neighbour => showAllowedValuesOn(neighbour))
if (this.form.checkValidity()) { // Correct grid
if (boxes.filter(box => box.value == "").length == 0) {
alert(`Bravo ! Vous avez résolu la grille.`)
} else {
if (suggestionTimer) clearTimeout(suggestionTimer)
suggestionTimer = setTimeout(showSuggestion, 30000)
}
} else { // Errors on grid
this.select()
this.reportValidity()
}
}
function enableButtons() {
for (button of buttons.getElementsByTagName("button")) {
if (boxes.filter(box => box.value == "").some(box => box.allowedValues.has(button.textContent))) {
button.disabled = false
} else {
button.disabled = true
if (highlightedValue == button.textContent) highlightedValue = ""
}
}
}
function oninvalid() {
if (this.value.length && !this.value.match(/[1-9]/))
this.setCustomValidity("Entrez un chiffre entre 1 et 9.")
else if (sameValueIn(regions[this.regionId]))
this.setCustomValidity(`Il y a un autre ${this.value} dans cette région.`)
else if (sameValueIn(rows[this.rowId]))
this.setCustomValidity(`Il y a un autre ${this.value} dans cette ligne.`)
else if (sameValueIn(columns[this.columnId]))
this.setCustomValidity(`Il y a un autre ${this.value} dans cette colonne.`)
else if (this.allowedValues.size == 0)
this.setCustomValidity("La grille est incorrecte.")
}
function sameValueIn(area) {
for (const box1 of area) {
for (const box2 of area) {
if (box1 != box2 && box1.value.length && box1.value == box2.value) {
return true
}
}
}
return false
}
function keyboardBrowse(event) {
switch(event.key) {
case "ArrowLeft":
event.preventDefault()
moveOn(rows[this.rowId], this.columnId, 8)
break
case "ArrowRight":
event.preventDefault()
moveOn(rows[this.rowId], this.columnId, 1)
break
case "ArrowUp":
event.preventDefault()
moveOn(columns[this.columnId], this.rowId, 8)
break
case "ArrowDown":
event.preventDefault()
moveOn(columns[this.columnId], this.rowId, 1)
break
}
}
function moveOn(area, position, direction) {
position = (position + direction) % 9
area[position].focus()
}
function showValue(value) {
if (value == highlightedValue) {
highlightedValue = ""
} else {
highlightedValue = value
}
refreshShowValue()
}
function refreshShowValue() {
boxes.forEach(box => box.className = "")
if (highlightedValue) {
boxes.forEach(box => {
if (box.value == highlightedValue) box.className = "same-value"
if (!box.allowedValues.has(highlightedValue)) box.className = "forbidden-value"
})
}
}
function shuffle(iterable) {
array = Array.from(iterable)
if (array.length > 1) {
let i, j, tmp
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i+1))
tmp = array[i]
array[i] = array[j]
array[j] = tmp
}
}
return array
}
easyFirst = (box1, box2) => box1.allowedValues.size - box2.allowedValues.size
function showSuggestion() {
const emptyBoxes = boxes.filter(box => box.value == "")
if (emptyBoxes.length) {
shuffle(emptyBoxes).sort(easyFirst)[0].placeholder = "!"
} else {
clearTimeout(suggestionTimer)
suggestionTimer = null
}
}