Sudoku/sudoku.js
2020-10-11 00:02:48 +02:00

203 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 = ""
let history = []
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 = onfocus
box.oninput = oninput
}
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(searchCandidatesOf)
enableButtons()
boxes.forEach(showCandidatesOn)
for(box of boxes) {
if (!box.readOnly) {
box.focus()
break
}
}
suggestionTimer = setTimeout(showSuggestion, 30000)
}
function searchCandidatesOf(box) {
box.candidates = new Set(VALUES)
box.neighbourhood.forEach(neighbour => box.candidates.delete(neighbour.value))
}
function showCandidatesOn(box) {
box.required = box.candidates.size == 0
if (box.value.length) {
box.title = ""
} else if (box.candidates.size) {
const candidatesArray = Array.from(box.candidates).sort()
box.title = candidatesArray.length ==1 ? candidatesArray[0] : candidatesArray.slice(0, candidatesArray.length-1).join(", ") + " ou " + candidatesArray[candidatesArray.length-1]
} else {
box.title = "Aucune valeur possible !"
}
}
function onfocus() {
this.previousValue = this.value
this.select()
}
function oninput() {
history.push({input: this, value: this.previousValue})
undoButton.disabled = false
refresh(this)
}
function refresh(box) {
box.style.color = colorPicker.value
box.neighbourhood.concat([box]).forEach(neighbour => {
searchCandidatesOf(neighbour)
showCandidatesOn(neighbour)
neighbour.setCustomValidity("")
})
for (neighbour1 of box.neighbourhood) {
neighbour1.setCustomValidity("")
if (neighbour1.value.length) {
for (area of [
{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) {
neighbour1.setCustomValidity(`Il y a un autre ${neighbour1.value} dans cette ${area.name}.`)
neighbour2.setCustomValidity(`Il y a un autre ${neighbour1.value} dans cette ${area.name}.`)
}
}
}
enableButtons()
highlightAndTab()
if (box.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
box.select()
box.reportValidity()
}
}
function undo() {
if (history.length) {
previousState = history.pop()
previousState.input.value = previousState.value
refresh(previousState.input)
if (history.length < 1) undoButton.disabled = true
}
}
function enableButtons() {
for (button of buttons.getElementsByTagName("button")) {
if (boxes.filter(box => box.value == "").some(box => box.candidates.has(button.textContent))) {
button.disabled = false
} else {
button.disabled = true
if (highlightedValue == button.textContent) highlightedValue = ""
}
}
}
function moveOn(area, position, direction) {
if (area.filter(box => box.disabled).length < 9) {
do {
position = (position + direction) % 9
} while (area[position].disabled)
area[position].focus()
}
}
function highlight(value) {
if (value == highlightedValue) {
highlightedValue = ""
} else {
highlightedValue = value
}
highlightAndTab()
boxes.filter(box => box.value == "" && box.tabIndex == 0)[0].focus()
}
function highlightAndTab() {
if (highlightedValue) {
boxes.forEach(box => {
if (box.value == highlightedValue) {
box.className = "same-value"
box.tabIndex = -1
}
else if (box.candidates.has(highlightedValue)) {
box.className = ""
box.tabIndex = 0
} else {
box.className = "forbidden-value"
box.tabIndex = -1
}
})
} else {
boxes.forEach(box => box.className = "")
}
}
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.candidates.size - box2.candidates.size
function showSuggestion() {
const emptyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1)
if (emptyBoxes.length) {
shuffle(emptyBoxes).placeholder = "!"
} else {
clearTimeout(suggestionTimer)
suggestionTimer = null
}
}