diff --git a/classes.php b/classes.php
index be9ae35..a414893 100644
--- a/classes.php
+++ b/classes.php
@@ -11,7 +11,7 @@
}
function easyFirst($box1, $box2) {
- return count($box1->allowedValues) - count($box2->allowedValues);
+ return count($box1->candidates) - count($box2->candidates);
}
function array_unset_value($value, &$array) {
@@ -32,16 +32,16 @@
$this->rowId = $rowId;
$this->columnId = $columnId;
$this->regionId = $regionId;
- $this->allowedValues = $this->values;
+ $this->candidates = $this->values;
$this->testValueWasAllowed = array();
$this->neighbourhood = array();
}
- function searchAllowedValues() {
- $this->allowedValues = $this->values;
+ function searchCandidates() {
+ $this->candidates = $this->values;
forEach($this->neighbourhood as $neighbour) {
if ($neighbour->value != UNKOWN)
- array_unset_value($neighbour->value, $this->allowedValues);
+ array_unset_value($neighbour->value, $this->candidates);
}
}
}
@@ -83,10 +83,10 @@
$box = $this->rows[0][$columnId];
$box->value = $value;
forEach($box->neighbourhood as $neighbour)
- array_unset_value($box->value, $neighbour->allowedValues);
+ array_unset_value($box->value, $neighbour->candidates);
}
// Fill grid
- $this->findSolutions(true)->current();
+ $this->solutionsGenerator(true)->current();
// Remove clues while there is still a unique solution
shuffle($this->boxes);
@@ -105,53 +105,59 @@
$erasedValues[] = $testBox->value;
$testBox->value = UNKOWN;
forEach($testBox->neighbourhood as $neighbour)
- $neighbour->searchAllowedValues();
+ $neighbour->searchCandidates();
}
if ($this->isValid()) {
$nbClues -= count($testBoxes);
} else {
forEach($testBoxes as $i => $testBox) {
$testBox->value = $erasedValues[$i];
- forEach($testBox->neighbourhood as $neighbour) array_unset_value($testBox->value, $neighbour->allowedValues);
+ forEach($testBox->neighbourhood as $neighbour) array_unset_value($testBox->value, $neighbour->candidates);
}
}
}
}
- function isValid() {
- $solutionsFinder = $this->findSolutions(false);
- $solutionsFound = array();
- foreach($solutionsFinder as $solution) {
- $solutionsFound[$solution] = true;
- if (count($solutionsFound) > 1) {
- $solutionsFinder->send(true);
+ function countSolutions($max=2) {
+ $solutions = $this->solutionsGenerator(false);
+ $solutionsWithoutDuplicates = array();
+ $nbSolutions = 0;
+ foreach($solutions as $solution) {
+ $solutionsWithoutDuplicates[$solution] = true;
+ $nbSolutions = count($solutionsWithoutDuplicates);
+ if ($nbSolutions >= $max) {
+ $solutions->send(true);
}
}
- return count($solutionsFound) == 1;
+ return $nbSolutions;
}
- function findSolutions($randomized=false) {
+ function isValid() {
+ return $this->countSolutions(2) == 1;
+ }
+
+ function solutionsGenerator($randomized=false) {
$emptyBoxes = array_filter($this->boxes, "isUnknown");
if (count($emptyBoxes)) {
if ($randomized) shuffle($emptyBoxes);
usort($emptyBoxes, "easyFirst");
$testBox = $emptyBoxes[0];
$nbTries = 0;
- if ($randomized) shuffle($testBox->allowedValues);
+ if ($randomized) shuffle($testBox->candidates);
$stop = null;
- foreach($testBox->allowedValues as $testBox->value) {
+ foreach($testBox->candidates as $testBox->value) {
foreach($testBox->neighbourhood as $neighbour)
- $neighbour->testValueWasAllowed[] = array_unset_value($testBox->value, $neighbour->allowedValues);
+ $neighbour->testValueWasAllowed[] = array_unset_value($testBox->value, $neighbour->candidates);
$correctGrid = true;
foreach(array_filter($testBox->neighbourhood, "isUnknown") as $neighbour) {
- if (count($neighbour->allowedValues) == 0) $correctGrid = false;
+ if (count($neighbour->candidates) == 0) $correctGrid = false;
}
if ($correctGrid) {
- $solutionsFinder = $this->findSolutions($randomized);
- foreach($solutionsFinder as $solution) {
+ $solutions = $this->solutionsGenerator($randomized);
+ foreach($solutions as $solution) {
$stop = (yield $solution);
if ($stop) {
- $solutionsFinder->send($stop);
+ $solutions->send($stop);
break;
}
}
@@ -159,7 +165,7 @@
}
forEach($testBox->neighbourhood as $neighbour)
if (array_pop($neighbour->testValueWasAllowed))
- $neighbour->allowedValues[] = $testBox->value;
+ $neighbour->candidates[] = $testBox->value;
if ($stop) break;
}
$testBox->value = UNKOWN;
diff --git a/game.php b/game.php
index 93f8c83..18802cc 100644
--- a/game.php
+++ b/game.php
@@ -37,7 +37,7 @@
} else {
$disabled = " disabled";
}
- echo "
| \n";
+ echo " | \n";
}
?>
@@ -58,7 +58,7 @@
-
+
@@ -78,7 +78,7 @@
Raccourcis clavier
- Tab ⬅ ⬆ ⬇ ➡ |
+ Tab |
Déplacement |
diff --git a/style.css b/style.css
index 96f416a..e3ac053 100644
--- a/style.css
+++ b/style.css
@@ -84,7 +84,7 @@ section, div {
border-right: 1px solid black;
}
-input {
+input[type=number] {
width: 1.6em;
height: 1.6em;
font-size: 1.5em;
@@ -92,6 +92,13 @@ input {
padding: 0;
text-align: center;
transition: background 0.5s;
+ -moz-appearance: textfield;
+}
+
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
}
input:enabled {
diff --git a/sudoku.js b/sudoku.js
index d81e1f0..c759651 100644
--- a/sudoku.js
+++ b/sudoku.js
@@ -15,11 +15,9 @@ window.onload = function() {
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.oninvalid = oninvalid
+ box.onfocus = onfocus
+ box.oninput = oninput
}
- box.onkeydown = keyboardBrowse
box.rowId = rowId
box.columnId = columnId
box.regionId = regionId
@@ -36,9 +34,9 @@ window.onload = function() {
box.neighbourhood.delete(box)
box.neighbourhood = Array.from(box.neighbourhood)
})
- boxes.forEach(searchAllowedValuesOf)
+ boxes.forEach(searchCandidatesOf)
enableButtons()
- boxes.forEach(showAllowedValuesOn)
+ boxes.forEach(showCandidatesOn)
for(box of boxes) {
if (!box.readOnly) {
box.focus()
@@ -48,48 +46,64 @@ window.onload = function() {
suggestionTimer = setTimeout(showSuggestion, 30000)
}
-function searchAllowedValuesOf(box) {
- box.allowedValues = new Set(VALUES)
- box.neighbourhood.forEach(neighbour => box.allowedValues.delete(neighbour.value))
+function searchCandidatesOf(box) {
+ box.candidates = new Set(VALUES)
+ box.neighbourhood.forEach(neighbour => box.candidates.delete(neighbour.value))
}
-function showAllowedValuesOn(box) {
- box.required = box.allowedValues.size == 0
+function showCandidatesOn(box) {
+ box.required = box.candidates.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 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.oldValue = this.value
+ this.previousValue = this.value
this.select()
}
function oninput() {
- history.push({input: this, value: this.oldValue})
+ history.push({input: this, value: this.previousValue})
undoButton.disabled = false
refresh(this)
}
-function refresh(input) {
- input.style.color = colorPicker.value
+function refresh(box) {
+ box.style.color = colorPicker.value
- input.neighbourhood.concat([input]).forEach(box => {
- box.setCustomValidity("")
- searchAllowedValuesOf(box)
- box.pattern = `[${Array.from(box.allowedValues).join("")}]?`
+ 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()
- input.neighbourhood.concat([input]).forEach(neighbour => showAllowedValuesOn(neighbour))
- if (input.form.checkValidity()) { // Correct grid
+
+ if (box.form.checkValidity()) { // Correct grid
if (boxes.filter(box => box.value == "").length == 0) {
alert(`Bravo ! Vous avez résolu la grille.`)
} else {
@@ -97,8 +111,8 @@ function refresh(input) {
suggestionTimer = setTimeout(showSuggestion, 30000)
}
} else { // Errors on grid
- input.select()
- input.reportValidity()
+ box.select()
+ box.reportValidity()
}
}
@@ -113,7 +127,7 @@ function undo() {
function enableButtons() {
for (button of buttons.getElementsByTagName("button")) {
- if (boxes.filter(box => box.value == "").some(box => box.allowedValues.has(button.textContent))) {
+ if (boxes.filter(box => box.value == "").some(box => box.candidates.has(button.textContent))) {
button.disabled = false
} else {
button.disabled = true
@@ -122,52 +136,6 @@ function enableButtons() {
}
}
-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) {
if (area.filter(box => box.disabled).length < 9) {
do {
@@ -194,7 +162,7 @@ function highlightAndTab() {
box.className = "same-value"
box.tabIndex = -1
}
- else if (box.allowedValues.has(highlightedValue)) {
+ else if (box.candidates.has(highlightedValue)) {
box.className = ""
box.tabIndex = 0
} else {
@@ -221,10 +189,10 @@ function shuffle(iterable) {
return array
}
-easyFirst = (box1, box2) => box1.allowedValues.size - box2.allowedValues.size
+easyFirst = (box1, box2) => box1.candidates.size - box2.candidates.size
function showSuggestion() {
- const emptyBoxes = boxes.filter(box => box.value == "" && box.allowedValues.size == 1)
+ const emptyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1)
if (emptyBoxes.length) {
shuffle(emptyBoxes).placeholder = "!"
} else {