input type='number'
This commit is contained in:
parent
b2e96a060a
commit
e0fe365b57
58
classes.php
58
classes.php
@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function easyFirst($box1, $box2) {
|
function easyFirst($box1, $box2) {
|
||||||
return count($box1->allowedValues) - count($box2->allowedValues);
|
return count($box1->candidates) - count($box2->candidates);
|
||||||
}
|
}
|
||||||
|
|
||||||
function array_unset_value($value, &$array) {
|
function array_unset_value($value, &$array) {
|
||||||
@ -32,16 +32,16 @@
|
|||||||
$this->rowId = $rowId;
|
$this->rowId = $rowId;
|
||||||
$this->columnId = $columnId;
|
$this->columnId = $columnId;
|
||||||
$this->regionId = $regionId;
|
$this->regionId = $regionId;
|
||||||
$this->allowedValues = $this->values;
|
$this->candidates = $this->values;
|
||||||
$this->testValueWasAllowed = array();
|
$this->testValueWasAllowed = array();
|
||||||
$this->neighbourhood = array();
|
$this->neighbourhood = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchAllowedValues() {
|
function searchCandidates() {
|
||||||
$this->allowedValues = $this->values;
|
$this->candidates = $this->values;
|
||||||
forEach($this->neighbourhood as $neighbour) {
|
forEach($this->neighbourhood as $neighbour) {
|
||||||
if ($neighbour->value != UNKOWN)
|
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 = $this->rows[0][$columnId];
|
||||||
$box->value = $value;
|
$box->value = $value;
|
||||||
forEach($box->neighbourhood as $neighbour)
|
forEach($box->neighbourhood as $neighbour)
|
||||||
array_unset_value($box->value, $neighbour->allowedValues);
|
array_unset_value($box->value, $neighbour->candidates);
|
||||||
}
|
}
|
||||||
// Fill grid
|
// Fill grid
|
||||||
$this->findSolutions(true)->current();
|
$this->solutionsGenerator(true)->current();
|
||||||
|
|
||||||
// Remove clues while there is still a unique solution
|
// Remove clues while there is still a unique solution
|
||||||
shuffle($this->boxes);
|
shuffle($this->boxes);
|
||||||
@ -105,53 +105,59 @@
|
|||||||
$erasedValues[] = $testBox->value;
|
$erasedValues[] = $testBox->value;
|
||||||
$testBox->value = UNKOWN;
|
$testBox->value = UNKOWN;
|
||||||
forEach($testBox->neighbourhood as $neighbour)
|
forEach($testBox->neighbourhood as $neighbour)
|
||||||
$neighbour->searchAllowedValues();
|
$neighbour->searchCandidates();
|
||||||
}
|
}
|
||||||
if ($this->isValid()) {
|
if ($this->isValid()) {
|
||||||
$nbClues -= count($testBoxes);
|
$nbClues -= count($testBoxes);
|
||||||
} else {
|
} else {
|
||||||
forEach($testBoxes as $i => $testBox) {
|
forEach($testBoxes as $i => $testBox) {
|
||||||
$testBox->value = $erasedValues[$i];
|
$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() {
|
function countSolutions($max=2) {
|
||||||
$solutionsFinder = $this->findSolutions(false);
|
$solutions = $this->solutionsGenerator(false);
|
||||||
$solutionsFound = array();
|
$solutionsWithoutDuplicates = array();
|
||||||
foreach($solutionsFinder as $solution) {
|
$nbSolutions = 0;
|
||||||
$solutionsFound[$solution] = true;
|
foreach($solutions as $solution) {
|
||||||
if (count($solutionsFound) > 1) {
|
$solutionsWithoutDuplicates[$solution] = true;
|
||||||
$solutionsFinder->send(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");
|
$emptyBoxes = array_filter($this->boxes, "isUnknown");
|
||||||
if (count($emptyBoxes)) {
|
if (count($emptyBoxes)) {
|
||||||
if ($randomized) shuffle($emptyBoxes);
|
if ($randomized) shuffle($emptyBoxes);
|
||||||
usort($emptyBoxes, "easyFirst");
|
usort($emptyBoxes, "easyFirst");
|
||||||
$testBox = $emptyBoxes[0];
|
$testBox = $emptyBoxes[0];
|
||||||
$nbTries = 0;
|
$nbTries = 0;
|
||||||
if ($randomized) shuffle($testBox->allowedValues);
|
if ($randomized) shuffle($testBox->candidates);
|
||||||
$stop = null;
|
$stop = null;
|
||||||
foreach($testBox->allowedValues as $testBox->value) {
|
foreach($testBox->candidates as $testBox->value) {
|
||||||
foreach($testBox->neighbourhood as $neighbour)
|
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;
|
$correctGrid = true;
|
||||||
foreach(array_filter($testBox->neighbourhood, "isUnknown") as $neighbour) {
|
foreach(array_filter($testBox->neighbourhood, "isUnknown") as $neighbour) {
|
||||||
if (count($neighbour->allowedValues) == 0) $correctGrid = false;
|
if (count($neighbour->candidates) == 0) $correctGrid = false;
|
||||||
}
|
}
|
||||||
if ($correctGrid) {
|
if ($correctGrid) {
|
||||||
$solutionsFinder = $this->findSolutions($randomized);
|
$solutions = $this->solutionsGenerator($randomized);
|
||||||
foreach($solutionsFinder as $solution) {
|
foreach($solutions as $solution) {
|
||||||
$stop = (yield $solution);
|
$stop = (yield $solution);
|
||||||
if ($stop) {
|
if ($stop) {
|
||||||
$solutionsFinder->send($stop);
|
$solutions->send($stop);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +165,7 @@
|
|||||||
}
|
}
|
||||||
forEach($testBox->neighbourhood as $neighbour)
|
forEach($testBox->neighbourhood as $neighbour)
|
||||||
if (array_pop($neighbour->testValueWasAllowed))
|
if (array_pop($neighbour->testValueWasAllowed))
|
||||||
$neighbour->allowedValues[] = $testBox->value;
|
$neighbour->candidates[] = $testBox->value;
|
||||||
if ($stop) break;
|
if ($stop) break;
|
||||||
}
|
}
|
||||||
$testBox->value = UNKOWN;
|
$testBox->value = UNKOWN;
|
||||||
|
6
game.php
6
game.php
@ -37,7 +37,7 @@
|
|||||||
} else {
|
} else {
|
||||||
$disabled = " disabled";
|
$disabled = " disabled";
|
||||||
}
|
}
|
||||||
echo " <td><input type='text' inputmode='numeric' minlength=0 maxlength=1 value='$value'$disabled/></td>\n";
|
echo " <td><input type='number' min='1' max='9' step='1' value='$value'$disabled/></td>\n";
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</tr>
|
</tr>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
<button type='reset'>Tout effacer</button>
|
<button type='reset'>Tout effacer</button>
|
||||||
<button id='undoButton' type='button' onclick='undo()' disabled accesskey='z'>Annuler</button>
|
<button id='undoButton' type='button' onclick='undo()' disabled accesskey='z'>Annuler</button>
|
||||||
<label for='colorPicker'>🎨</label>
|
<label for='colorPicker'>🎨</label>
|
||||||
<input id='colorPicker' type='color' value='#00008b'/>
|
<input id='colorPicker' type='color' title='Changer de stylo' value='#00008b'/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<section>
|
<section>
|
||||||
@ -78,7 +78,7 @@
|
|||||||
<caption>Raccourcis clavier</caption>
|
<caption>Raccourcis clavier</caption>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>Tab</kbd> <kbd>⬅</kbd> <kbd>⬆</kbd> <kbd>⬇</kbd> <kbd>➡</kbd></td>
|
<td><kbd>Tab</kbd></td>
|
||||||
<td>Déplacement</td>
|
<td>Déplacement</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -84,7 +84,7 @@ section, div {
|
|||||||
border-right: 1px solid black;
|
border-right: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input[type=number] {
|
||||||
width: 1.6em;
|
width: 1.6em;
|
||||||
height: 1.6em;
|
height: 1.6em;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@ -92,6 +92,13 @@ input {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: background 0.5s;
|
transition: background 0.5s;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:enabled {
|
input:enabled {
|
||||||
|
122
sudoku.js
122
sudoku.js
@ -15,11 +15,9 @@ window.onload = function() {
|
|||||||
for (box of row.getElementsByTagName('input')) {
|
for (box of row.getElementsByTagName('input')) {
|
||||||
let regionId = rowId - rowId%3 + Math.floor(columnId/3)
|
let regionId = rowId - rowId%3 + Math.floor(columnId/3)
|
||||||
if (!box.readOnly) {
|
if (!box.readOnly) {
|
||||||
box.onfocus = onfocus
|
box.onfocus = onfocus
|
||||||
box.oninput = oninput
|
box.oninput = oninput
|
||||||
box.oninvalid = oninvalid
|
|
||||||
}
|
}
|
||||||
box.onkeydown = keyboardBrowse
|
|
||||||
box.rowId = rowId
|
box.rowId = rowId
|
||||||
box.columnId = columnId
|
box.columnId = columnId
|
||||||
box.regionId = regionId
|
box.regionId = regionId
|
||||||
@ -36,9 +34,9 @@ window.onload = function() {
|
|||||||
box.neighbourhood.delete(box)
|
box.neighbourhood.delete(box)
|
||||||
box.neighbourhood = Array.from(box.neighbourhood)
|
box.neighbourhood = Array.from(box.neighbourhood)
|
||||||
})
|
})
|
||||||
boxes.forEach(searchAllowedValuesOf)
|
boxes.forEach(searchCandidatesOf)
|
||||||
enableButtons()
|
enableButtons()
|
||||||
boxes.forEach(showAllowedValuesOn)
|
boxes.forEach(showCandidatesOn)
|
||||||
for(box of boxes) {
|
for(box of boxes) {
|
||||||
if (!box.readOnly) {
|
if (!box.readOnly) {
|
||||||
box.focus()
|
box.focus()
|
||||||
@ -48,48 +46,64 @@ window.onload = function() {
|
|||||||
suggestionTimer = setTimeout(showSuggestion, 30000)
|
suggestionTimer = setTimeout(showSuggestion, 30000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchAllowedValuesOf(box) {
|
function searchCandidatesOf(box) {
|
||||||
box.allowedValues = new Set(VALUES)
|
box.candidates = new Set(VALUES)
|
||||||
box.neighbourhood.forEach(neighbour => box.allowedValues.delete(neighbour.value))
|
box.neighbourhood.forEach(neighbour => box.candidates.delete(neighbour.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAllowedValuesOn(box) {
|
function showCandidatesOn(box) {
|
||||||
box.required = box.allowedValues.size == 0
|
box.required = box.candidates.size == 0
|
||||||
if (box.value.length) {
|
if (box.value.length) {
|
||||||
box.title = ""
|
box.title = ""
|
||||||
} else if (box.allowedValues.size) {
|
} else if (box.candidates.size) {
|
||||||
const allowedValuesArray = Array.from(box.allowedValues).sort()
|
const candidatesArray = Array.from(box.candidates).sort()
|
||||||
box.title = allowedValuesArray.length ==1 ? allowedValuesArray[0] : allowedValuesArray.slice(0, allowedValuesArray.length-1).join(", ") + " ou " + allowedValuesArray[allowedValuesArray.length-1]
|
box.title = candidatesArray.length ==1 ? candidatesArray[0] : candidatesArray.slice(0, candidatesArray.length-1).join(", ") + " ou " + candidatesArray[candidatesArray.length-1]
|
||||||
} else {
|
} else {
|
||||||
box.title = "Aucune valeur possible !"
|
box.title = "Aucune valeur possible !"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onfocus() {
|
function onfocus() {
|
||||||
this.oldValue = this.value
|
this.previousValue = this.value
|
||||||
this.select()
|
this.select()
|
||||||
}
|
}
|
||||||
|
|
||||||
function oninput() {
|
function oninput() {
|
||||||
history.push({input: this, value: this.oldValue})
|
history.push({input: this, value: this.previousValue})
|
||||||
undoButton.disabled = false
|
undoButton.disabled = false
|
||||||
refresh(this)
|
refresh(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
function refresh(input) {
|
function refresh(box) {
|
||||||
input.style.color = colorPicker.value
|
box.style.color = colorPicker.value
|
||||||
|
|
||||||
input.neighbourhood.concat([input]).forEach(box => {
|
box.neighbourhood.concat([box]).forEach(neighbour => {
|
||||||
box.setCustomValidity("")
|
searchCandidatesOf(neighbour)
|
||||||
searchAllowedValuesOf(box)
|
showCandidatesOn(neighbour)
|
||||||
box.pattern = `[${Array.from(box.allowedValues).join("")}]?`
|
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()
|
enableButtons()
|
||||||
highlightAndTab()
|
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) {
|
if (boxes.filter(box => box.value == "").length == 0) {
|
||||||
alert(`Bravo ! Vous avez résolu la grille.`)
|
alert(`Bravo ! Vous avez résolu la grille.`)
|
||||||
} else {
|
} else {
|
||||||
@ -97,8 +111,8 @@ function refresh(input) {
|
|||||||
suggestionTimer = setTimeout(showSuggestion, 30000)
|
suggestionTimer = setTimeout(showSuggestion, 30000)
|
||||||
}
|
}
|
||||||
} else { // Errors on grid
|
} else { // Errors on grid
|
||||||
input.select()
|
box.select()
|
||||||
input.reportValidity()
|
box.reportValidity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +127,7 @@ function undo() {
|
|||||||
|
|
||||||
function enableButtons() {
|
function enableButtons() {
|
||||||
for (button of buttons.getElementsByTagName("button")) {
|
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
|
button.disabled = false
|
||||||
} else {
|
} else {
|
||||||
button.disabled = true
|
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) {
|
function moveOn(area, position, direction) {
|
||||||
if (area.filter(box => box.disabled).length < 9) {
|
if (area.filter(box => box.disabled).length < 9) {
|
||||||
do {
|
do {
|
||||||
@ -194,7 +162,7 @@ function highlightAndTab() {
|
|||||||
box.className = "same-value"
|
box.className = "same-value"
|
||||||
box.tabIndex = -1
|
box.tabIndex = -1
|
||||||
}
|
}
|
||||||
else if (box.allowedValues.has(highlightedValue)) {
|
else if (box.candidates.has(highlightedValue)) {
|
||||||
box.className = ""
|
box.className = ""
|
||||||
box.tabIndex = 0
|
box.tabIndex = 0
|
||||||
} else {
|
} else {
|
||||||
@ -221,10 +189,10 @@ function shuffle(iterable) {
|
|||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
easyFirst = (box1, box2) => box1.allowedValues.size - box2.allowedValues.size
|
easyFirst = (box1, box2) => box1.candidates.size - box2.candidates.size
|
||||||
|
|
||||||
function showSuggestion() {
|
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) {
|
if (emptyBoxes.length) {
|
||||||
shuffle(emptyBoxes).placeholder = "!"
|
shuffle(emptyBoxes).placeholder = "!"
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user