before new php version
This commit is contained in:
parent
99646d3175
commit
d752ed6b54
55
dev.html
55
dev.html
@ -1,55 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Sudoku</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
||||||
<script type="text/javascript" src="sudoku_dev.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>Sudoku</h1>
|
|
||||||
</header>
|
|
||||||
<form id="sudokuForm">
|
|
||||||
<div>
|
|
||||||
<table id="gridTable" class="grid"></table>
|
|
||||||
<script>createGrid()</script>
|
|
||||||
</div>
|
|
||||||
<div id="helpDiv">
|
|
||||||
<fieldset id="helpFieldset">
|
|
||||||
<legend>Aide</legend>
|
|
||||||
<div>
|
|
||||||
<input type="checkbox" id="showAllowedValues" onclick="grid.forEach(box => showAllowedValuesOn(box))"/>
|
|
||||||
<label for="showAllowedValues">Indiquer les valeurs possibles pour chaque case</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="highlightSelect">Surligner les cases pouvant contenir un :</label>
|
|
||||||
<select id="highlightSelect" onchange="highlightAndTabOrder()">
|
|
||||||
<option></option>
|
|
||||||
<option>1</option>
|
|
||||||
<option>2</option>
|
|
||||||
<option>3</option>
|
|
||||||
<option>4</option>
|
|
||||||
<option>5</option>
|
|
||||||
<option>6</option>
|
|
||||||
<option>7</option>
|
|
||||||
<option>8</option>
|
|
||||||
<option>9</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button id="solveButton" type="button" onclick="solve()">Montrer la solution</button>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div>
|
|
||||||
<button id="clearButton" type="button" onclick="erase()">Effacer tout</button>
|
|
||||||
<button type="button" onclick="generateGrid()">Nouvelle partie</button>
|
|
||||||
<button id="customGridButton" type="button" onclick="customGrid()">Grille personnalisée</button>
|
|
||||||
</div>
|
|
||||||
Pour partager cette grille, copiez le lien suivant :<br/>
|
|
||||||
<div id="shareDiv">
|
|
||||||
<a id="shareLink" href="">malingrey.fr<br/>/sudoku/#<br/></a>
|
|
||||||
</div>
|
|
||||||
<script>loadGrid()</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -42,7 +42,7 @@
|
|||||||
</div>
|
</div>
|
||||||
Pour partager cette grille, copiez le lien suivant :<br/>
|
Pour partager cette grille, copiez le lien suivant :<br/>
|
||||||
<div id="shareDiv">
|
<div id="shareDiv">
|
||||||
<a id="shareA" href="">malingrey.fr<br/>/sudoku/#<br/></a>
|
<a id="shareA" href="">jeux.malingrey.fr/sudoku/<br/></a>
|
||||||
</div>
|
</div>
|
||||||
<script>loadGrid()</script>
|
<script>loadGrid()</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Sudoku</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="style.css" />
|
|
||||||
<script type="text/javascript" src="sudoku.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<h1>Sudoku</h1>
|
|
||||||
</header>
|
|
||||||
<form id="sudokuForm">
|
|
||||||
<div>
|
|
||||||
<table id="gridTable" class="grid"></table>
|
|
||||||
<script>createGrid()</script>
|
|
||||||
</div>
|
|
||||||
<div id="helpDiv">
|
|
||||||
<fieldset id="helpFieldset">
|
|
||||||
<legend>Aide</legend>
|
|
||||||
<div>
|
|
||||||
<input type="checkbox" id="showAllowedValues" onclick="grid.forEach(box => showAllowedValuesOn(box))"/>
|
|
||||||
<label for="showAllowedValues">Indiquer les valeurs possibles</label>
|
|
||||||
</div>
|
|
||||||
<label for="highlightSelect">Surligner les cases pouvant contenir :</label>
|
|
||||||
<select id="highlightSelect" multiple size="1" onclick="highlightAndTabOrder()">
|
|
||||||
<option>1</option>
|
|
||||||
<option>2</option>
|
|
||||||
<option>3</option>
|
|
||||||
<option>4</option>
|
|
||||||
<option>5</option>
|
|
||||||
<option>6</option>
|
|
||||||
<option>7</option>
|
|
||||||
<option>8</option>
|
|
||||||
<option>9</option>
|
|
||||||
</select>
|
|
||||||
<button id="solveButton" type="button" onclick="solve()">Montrer la solution</button>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div>
|
|
||||||
<button id="clearButton" type="button" onclick="erase()">Effacer tout</button>
|
|
||||||
<button type="button" onclick="generateGrid()">Nouvelle partie</button>
|
|
||||||
<button id="customGridButton" type="button" onclick="customGrid()">Grille personnalisée</button>
|
|
||||||
</div>
|
|
||||||
<div id="shareDiv">
|
|
||||||
Lien vers cette grille :<br/>
|
|
||||||
<a id="shareLink" href="">malingrey.fr<br/>/sudoku/?<br/></a>
|
|
||||||
</div>
|
|
||||||
<script>loadGrid()</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -57,6 +57,7 @@ label.disabled {
|
|||||||
|
|
||||||
.grid input:disabled {
|
.grid input:disabled {
|
||||||
color: black;
|
color: black;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ label.disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.highlighted {
|
.highlighted {
|
||||||
background: yellow;
|
background: lightYellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shareDiv {
|
#shareDiv {
|
||||||
|
446
sudoku.js
Normal file
446
sudoku.js
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
const VALUES = "123456789"
|
||||||
|
suggestionTimer = null
|
||||||
|
highlightedValue = null
|
||||||
|
|
||||||
|
function createGrid() {
|
||||||
|
rows = Array.from(Array(9), x => [])
|
||||||
|
columns = Array.from(Array(9), x => [])
|
||||||
|
regions = Array.from(Array(9), x => [])
|
||||||
|
for (let regionRow = 0; regionRow < 3; regionRow++) {
|
||||||
|
const gridRow = document.createElement("tr")
|
||||||
|
for(let regionColumn = 0; regionColumn < 3; regionColumn++) {
|
||||||
|
const gridCell = document.createElement("td")
|
||||||
|
const regionTable = document.createElement("table")
|
||||||
|
regionTable.className = "region"
|
||||||
|
for (let row = 3*regionRow; row < 3*(regionRow+1); row++) {
|
||||||
|
const regionTr = document.createElement("tr")
|
||||||
|
for (let column = 3*regionColumn; column < 3*(regionColumn+1); column++) {
|
||||||
|
const regionCell = document.createElement("td")
|
||||||
|
const box = document.createElement("input")
|
||||||
|
box.type = "text"
|
||||||
|
box.oninput = oninput
|
||||||
|
box.oninvalid = oninvalid
|
||||||
|
box.onfocus = box.select
|
||||||
|
box.onkeydown = keyboardBrowse
|
||||||
|
box.setAttribute("inputmode", "numeric")
|
||||||
|
box.required = false
|
||||||
|
box.region = 3*regionRow + regionColumn
|
||||||
|
box.column = column
|
||||||
|
box.row = row
|
||||||
|
box.minLength = 0
|
||||||
|
box.maxLength = 1
|
||||||
|
box.tabIndex = 9
|
||||||
|
rows[row].push(box)
|
||||||
|
columns[column].push(box)
|
||||||
|
regions[box.region].push(box)
|
||||||
|
regionCell.appendChild(box)
|
||||||
|
regionTr.appendChild(regionCell)
|
||||||
|
}
|
||||||
|
regionTable.appendChild(regionTr)
|
||||||
|
}
|
||||||
|
gridCell.appendChild(regionTable)
|
||||||
|
gridRow.appendChild(gridCell)
|
||||||
|
}
|
||||||
|
gridTable.appendChild(gridRow)
|
||||||
|
}
|
||||||
|
grid = rows.reduce((grid, row) => grid.concat(row), [])
|
||||||
|
// box.neighbourhood: boxes in the same row, column and region as box
|
||||||
|
grid.forEach(box => {
|
||||||
|
box.neighbourhood = new Set(rows[box.row].concat(columns[box.column]).concat(regions[box.region]))
|
||||||
|
box.neighbourhood.delete(box)
|
||||||
|
box.neighbourhood = Array.from(box.neighbourhood)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearGrid() {
|
||||||
|
clearTimeout(suggestionTimer)
|
||||||
|
suggestionTimer = null
|
||||||
|
grid.forEach(box => {
|
||||||
|
box.value = ""
|
||||||
|
box.allowedValues = new Set(VALUES)
|
||||||
|
box.testValueWasAllowed = []
|
||||||
|
box.pattern = "[1-9]?"
|
||||||
|
box.placeholder = ""
|
||||||
|
box.disabled = false
|
||||||
|
box.required = false
|
||||||
|
})
|
||||||
|
enableHighlightOptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadGrid() {
|
||||||
|
window.onhashchange = function() {
|
||||||
|
if (location.hash) {
|
||||||
|
if (location.hash.match(/^#[1-9?]{81}$/)) {
|
||||||
|
gridAsString = location.hash.substring(1)
|
||||||
|
if (gridAsString != grid.map(box => box.disabled? box.value : "?").join("")) {
|
||||||
|
// Load grid from hash if it differs
|
||||||
|
clearGrid()
|
||||||
|
if (savedGame = localStorage.getItem(gridAsString + "SavedGame")) {
|
||||||
|
// We trust grid is valid if it was previously saved
|
||||||
|
grid.forEach((box, i) => {
|
||||||
|
box.value = savedGame[i] == "?"? "" : savedGame[i]
|
||||||
|
box.disabled = Boolean(gridAsString[i] != "?")
|
||||||
|
})
|
||||||
|
startTime = Date.now() - localStorage.getItem(gridAsString + "Time")
|
||||||
|
finishGridLoad()
|
||||||
|
} else {
|
||||||
|
// Else we need to check if grid is valid
|
||||||
|
grid.forEach((box, i) => box.value = gridAsString[i] == "?"? "" : gridAsString[i])
|
||||||
|
grid.forEach(box => searchAllowedValuesOf(box))
|
||||||
|
checkGrid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(location.hash.substring(1) + " n'est pas une grille valide.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.hash)
|
||||||
|
window.onhashchange()
|
||||||
|
else if ((lastPlayedGrid = localStorage.getItem("lastPlayedGrid")) && localStorage.getItem(lastPlayedGrid + "SavedGame").match("[?]"))
|
||||||
|
location.hash = lastPlayedGrid
|
||||||
|
else
|
||||||
|
generateGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGrid() {
|
||||||
|
if (sudokuForm.checkValidity()) {
|
||||||
|
switch(new Set(findSolutions(false, 2, 4)).size) {
|
||||||
|
case 0:
|
||||||
|
this.reportValidity()
|
||||||
|
alert("Cette grille n'a pas de solution. Veuillez la corriger.")
|
||||||
|
break
|
||||||
|
case 1:
|
||||||
|
freezeGrid()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
alert("Cette grille a plusieurs solutions. Rajoutez des indices.")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sudokuForm.reportValidity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function freezeGrid() {
|
||||||
|
grid.forEach(box => box.disabled = Boolean(box.value.length))
|
||||||
|
startTime = Date.now()
|
||||||
|
gridAsString = grid.map(box => box.disabled? box.value : "?").join("")
|
||||||
|
location.hash = gridAsString
|
||||||
|
finishGridLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishGridLoad() {
|
||||||
|
grid.forEach(box => {
|
||||||
|
searchAllowedValuesOf(box)
|
||||||
|
showAllowedValuesOn(box)
|
||||||
|
box.pattern = `[${Array.from(box.allowedValues).join("")}]?`
|
||||||
|
})
|
||||||
|
enableHighlightOptions()
|
||||||
|
highlightAndTabOrder()
|
||||||
|
shareA.href = location
|
||||||
|
shareA.innerHTML = location.hostname + location.pathname + "#<br/>" + Array(9).fill().map((_,i) => gridAsString.slice(9*i, 9*i+9)).join("<br/>")
|
||||||
|
solutions = findSolutions(false, 1, 4)
|
||||||
|
localStorage.setItem("lastPlayedGrid", gridAsString)
|
||||||
|
saveGame()
|
||||||
|
suggestionTimer = setTimeout(showSuggestion, 30000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGame() {
|
||||||
|
localStorage.setItem(gridAsString + "SavedGame", grid.map(box => box.value || "?").join(""))
|
||||||
|
localStorage.setItem(gridAsString + "Time", Date.now() - startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromEasyToDifficult = (box1, box2) => box1.allowedValues.size - box2.allowedValues.size
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
function* findSolutions(randomized=false, maxSolutions=1, maxTries=4) {
|
||||||
|
let emptyBoxes = grid.filter(box => box.value == "")
|
||||||
|
if (emptyBoxes.length) {
|
||||||
|
if (randomized) // Generate random grids
|
||||||
|
emptyBoxes = shuffle(emptyBoxes)
|
||||||
|
const testBox = emptyBoxes.sort(fromEasyToDifficult)[0] // Pick an easy box to solve
|
||||||
|
// Try every allowed value
|
||||||
|
let nbSolutionsFound = 0
|
||||||
|
let nbTries = 0
|
||||||
|
for (testBox.value of randomized? shuffle(testBox.allowedValues) : testBox.allowedValues) {
|
||||||
|
testBox.neighbourhood.forEach(neighbour => {
|
||||||
|
// Remember if testBox.value was in neighbour.allowedValues in case we rewrite it
|
||||||
|
neighbour.testValueWasAllowed.push(neighbour.allowedValues.has(testBox.value))
|
||||||
|
neighbour.allowedValues.delete(testBox.value)
|
||||||
|
})
|
||||||
|
// If grid still correct, yield all solutions with this hypothesis
|
||||||
|
if (testBox.neighbourhood.filter(box => box.value == "").every(emptyBox => emptyBox.allowedValues.size)) {
|
||||||
|
for (const solution of findSolutions(randomized, maxSolutions-nbSolutionsFound, maxTries)) {
|
||||||
|
yield solution
|
||||||
|
nbSolutionsFound++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testBox.neighbourhood.filter(
|
||||||
|
neighbour => neighbour.testValueWasAllowed.pop()
|
||||||
|
).forEach(neighbour => neighbour.allowedValues.add(testBox.value))
|
||||||
|
if (maxSolutions && nbSolutionsFound >= maxSolutions) break
|
||||||
|
if (++nbTries >= maxTries) break
|
||||||
|
}
|
||||||
|
testBox.value = ""
|
||||||
|
} else {
|
||||||
|
yield grid.map(box => box.value).join("")
|
||||||
|
}
|
||||||
|
return "No more solutions"
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateGrid() {
|
||||||
|
clearGrid()
|
||||||
|
shuffle(Array.from(VALUES)).forEach((value, c) => {
|
||||||
|
rows[0][c].value = value
|
||||||
|
rows[0][c].neighbourhood.forEach(neighbour => neighbour.allowedValues.delete(rows[0][c].value))
|
||||||
|
})
|
||||||
|
// Generate a random valid grid where all boxes are clues
|
||||||
|
while (findSolutions(true, 1, 4).next().done) {}
|
||||||
|
// Remove clues while there is still a unique solution
|
||||||
|
let untestedBoxes = shuffle(grid)
|
||||||
|
let nbClues = 81
|
||||||
|
while(untestedBoxes.length) {
|
||||||
|
const testBoxes = [untestedBoxes.pop()]
|
||||||
|
if (nbClues >= 30)
|
||||||
|
testBoxes.push(rows[8-testBoxes[0].row][8-testBoxes[0].column])
|
||||||
|
if (nbClues >= 61) {
|
||||||
|
testBoxes.push(rows[8-testBoxes[0].row][testBoxes[0].column])
|
||||||
|
testBoxes.push(rows[testBoxes[0].row][8-testBoxes[0].column])
|
||||||
|
}
|
||||||
|
const erasedValues = testBoxes.map(box => box.value)
|
||||||
|
testBoxes.forEach(box => {
|
||||||
|
box.value = ""
|
||||||
|
box.neighbourhood.forEach(neighbour => searchAllowedValuesOf(neighbour))
|
||||||
|
})
|
||||||
|
if (Array.from(findSolutions(false, 2, 4)).length == 1) {
|
||||||
|
nbClues -= testBoxes.length
|
||||||
|
testBoxes.slice(1).forEach(box => untestedBoxes.splice(untestedBoxes.indexOf(box), 1))
|
||||||
|
} else {
|
||||||
|
testBoxes.forEach((box, i) => {
|
||||||
|
box.value = erasedValues[i]
|
||||||
|
box.neighbourhood.forEach(neighbour => neighbour.allowedValues.delete(box.value))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freezeGrid()
|
||||||
|
}
|
||||||
|
|
||||||
|
function customGrid() {
|
||||||
|
clearGrid()
|
||||||
|
grid.forEach(box => showAllowedValuesOn(box))
|
||||||
|
enableHighlightOptions()
|
||||||
|
highlightAndTabOrder()
|
||||||
|
solutions = findSolutions(false, 1, 4)
|
||||||
|
customGridButton.innerText = "Figer la grille"
|
||||||
|
customGridButton.onclick = checkGrid
|
||||||
|
location.hash = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchAllowedValuesOf(box) {
|
||||||
|
box.allowedValues = new Set(VALUES)
|
||||||
|
box.neighbourhood.forEach(neighbour => box.allowedValues.delete(neighbour.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyboardBrowse(event) {
|
||||||
|
switch(event.key) {
|
||||||
|
case "ArrowLeft":
|
||||||
|
event.preventDefault()
|
||||||
|
moveOn(rows[this.row], this.column, 8)
|
||||||
|
break
|
||||||
|
case "ArrowRight":
|
||||||
|
event.preventDefault()
|
||||||
|
moveOn(rows[this.row], this.column, 1)
|
||||||
|
break
|
||||||
|
case "ArrowUp":
|
||||||
|
event.preventDefault()
|
||||||
|
moveOn(columns[this.column], this.row, 8)
|
||||||
|
break
|
||||||
|
case "ArrowDown":
|
||||||
|
event.preventDefault()
|
||||||
|
moveOn(columns[this.column], this.row, 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeFormat = new Intl.DateTimeFormat("fr-FR", {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
timeZone: "UTC"
|
||||||
|
}).format
|
||||||
|
|
||||||
|
function oninput() {
|
||||||
|
saveGame()
|
||||||
|
|
||||||
|
this.neighbourhood.concat([this]).forEach(box => {
|
||||||
|
box.setCustomValidity("")
|
||||||
|
searchAllowedValuesOf(box)
|
||||||
|
box.pattern = `[${Array.from(box.allowedValues).join("")}]?`
|
||||||
|
})
|
||||||
|
|
||||||
|
highlightAndTabOrder()
|
||||||
|
enableHighlightOptions()
|
||||||
|
this.neighbourhood.concat([this]).forEach(neighbour => showAllowedValuesOn(neighbour))
|
||||||
|
|
||||||
|
if (this.form.checkValidity()) { // Correct grid
|
||||||
|
if (grid.filter(box => box.value == "").length == 0) {
|
||||||
|
alert(`Bravo ! Vous avez résolu la grille en ${timeFormat(Date.now() - startTime)}.`)
|
||||||
|
} else {
|
||||||
|
solutions = findSolutions(false, 1, 4) // Reset solutions generator
|
||||||
|
if (suggestionTimer) clearTimeout(suggestionTimer)
|
||||||
|
suggestionTimer = setTimeout(showSuggestion, 30000)
|
||||||
|
}
|
||||||
|
} else { // Errors on grid
|
||||||
|
this.select()
|
||||||
|
this.reportValidity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help functions
|
||||||
|
|
||||||
|
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 oninvalid() {
|
||||||
|
if (this.value.length && !this.value.match(/[1-9]/))
|
||||||
|
this.setCustomValidity("Entrez un chiffre entre 1 et 9.")
|
||||||
|
else if (sameValueIn(regions[this.region]))
|
||||||
|
this.setCustomValidity(`Il y a un autre ${this.value} dans cette région.`)
|
||||||
|
else if (sameValueIn(rows[this.row]))
|
||||||
|
this.setCustomValidity(`Il y a un autre ${this.value} dans cette ligne.`)
|
||||||
|
else if (sameValueIn(columns[this.column]))
|
||||||
|
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 highlight(value) {
|
||||||
|
highlightedValue = value
|
||||||
|
highlightAndTabOrder()
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightAndTabOrder() {
|
||||||
|
if (highlightedValue) {
|
||||||
|
for (const box of grid) {
|
||||||
|
box.tabIndex = -1
|
||||||
|
box.className = "unhighlighted"
|
||||||
|
if (box.value == "") {
|
||||||
|
if (box.allowedValues.has(highlightedValue)) {
|
||||||
|
box.tabIndex = 0
|
||||||
|
box.className = "highlighted"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const box of grid) {
|
||||||
|
box.className = "unhighlighted"
|
||||||
|
if (box.value == "" && box.allowedValues.size == 1) {
|
||||||
|
box.tabIndex = 0
|
||||||
|
} else {
|
||||||
|
box.tabIndex = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enableHighlightOptions() {
|
||||||
|
for (value of VALUES) {
|
||||||
|
let highlightRadio = document.getElementById("highlightRadio" + value)
|
||||||
|
let highlightLabel = document.getElementById("highlightLabel" + value)
|
||||||
|
if (grid.filter(box => box.value == "").some(box => box.allowedValues.has(value))) {
|
||||||
|
highlightRadio.disabled = false
|
||||||
|
highlightLabel.className = ""
|
||||||
|
} else {
|
||||||
|
highlightRadio.disabled = true
|
||||||
|
highlightRadio.checked = false
|
||||||
|
highlightLabel.className = "disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuggestion() {
|
||||||
|
const emptyBoxes = grid.filter(box => box.value == "")
|
||||||
|
if (emptyBoxes.length) {
|
||||||
|
shuffle(emptyBoxes).sort(fromEasyToDifficult)[0].placeholder = "!"
|
||||||
|
} else {
|
||||||
|
clearTimeout(suggestionTimer)
|
||||||
|
suggestionTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function erase() {
|
||||||
|
grid.filter(box => !box.disabled).forEach(box => {
|
||||||
|
box.value = ""
|
||||||
|
box.placeholder = ""
|
||||||
|
})
|
||||||
|
grid.filter(box => !box.disabled).forEach(box => {
|
||||||
|
searchAllowedValuesOf(box)
|
||||||
|
showAllowedValuesOn(box)
|
||||||
|
})
|
||||||
|
sudokuForm.checkValidity()
|
||||||
|
sudokuForm.reportValidity()
|
||||||
|
enableHighlightOptions()
|
||||||
|
solutions = findSolutions(false, 1, 4)
|
||||||
|
solveButton.innerText = "Résoudre"
|
||||||
|
solveButton.type = "button"
|
||||||
|
solveButton.onclick = solve
|
||||||
|
solveButton.disabled = false
|
||||||
|
startTime = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
function solve() {
|
||||||
|
if (sudokuForm.checkValidity()) {
|
||||||
|
if (solutions.next().done) { // End of solutions generator
|
||||||
|
solveButton.innerText = "Montrer la solution"
|
||||||
|
solutions = findSolutions(false, 1, 4)
|
||||||
|
} else {
|
||||||
|
solveButton.innerText = "Effacer la solution"
|
||||||
|
}
|
||||||
|
grid.forEach(box => showAllowedValuesOn(box))
|
||||||
|
} else {
|
||||||
|
sudokuForm.reportValidity()
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user