Compare commits
83 Commits
e1cd9ca1a2
...
master
Author | SHA1 | Date | |
---|---|---|---|
c477355fe9 | |||
f52c970bef | |||
cf0a5a465e | |||
310a1883d2 | |||
4b92464c94 | |||
c09ed80a52 | |||
b145ae566a | |||
21e8f4134f | |||
31a40a7e93 | |||
ad3992ac30 | |||
5feaa65955 | |||
ed5795a6cc | |||
bdb1a094c3 | |||
85efaca248 | |||
39c564fb89 | |||
ba07a531d0 | |||
f510310549 | |||
5091e6a888 | |||
67cd3594a6 | |||
4027a8b36f | |||
e1fc974372 | |||
853d93f8ff | |||
ddc1a51899 | |||
77886a6878 | |||
80bc9f083d | |||
1365ef65dd | |||
ad414d0da8 | |||
57d06c3b53 | |||
84d9222a1c | |||
fa979cb973 | |||
7245e0f073 | |||
2429845dd6 | |||
956966fbe9 | |||
ddcabbdd39 | |||
743f7fa72d | |||
3a243c38f0 | |||
beebb14464 | |||
24806a289f | |||
4a0d03f445 | |||
2e5125c298 | |||
959deb5e14 | |||
562cd7964b | |||
e437a01e30 | |||
f811000ea0 | |||
35f1bee053 | |||
79a20f323a | |||
e80fef6c08 | |||
7c65480bdd | |||
7043fb8bb6 | |||
15ed790caf | |||
c0c815e757 | |||
5bd46082d5 | |||
de7b013716 | |||
dab898c391 | |||
f53e09b52e | |||
176e72c465 | |||
cedd63dd90 | |||
60b5f74e94 | |||
c47cd498c0 | |||
6ee740cebd | |||
785f1460fb | |||
80fa25c092 | |||
ac91453013 | |||
8c62d6bb0e | |||
8f412bdd0f | |||
dee27c9ca7 | |||
0f83b8de42 | |||
ab11f85215 | |||
9191b6836c | |||
b5345e1a4d | |||
fa970170d4 | |||
573608b63c | |||
ca22cb129d | |||
80d368446a | |||
f7d6dbf3f3 | |||
cfc535b772 | |||
019ce058cd | |||
a75b898ea6 | |||
cd22ded40e | |||
5836f53a24 | |||
9b2f1f9d78 | |||
3b8e9b85ea | |||
8121768fe4 |
27
400.php
Executable file
@ -0,0 +1,27 @@
|
||||
<?php http_response_code(400); ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang='fr'>
|
||||
<head>
|
||||
<?php require("head.php"); ?>
|
||||
<title>Requête incorrecte</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar mb-4">
|
||||
<h1 class="display-4 text-center m-auto">Sudoku</h1>
|
||||
</nav>
|
||||
<main class="container my-4">
|
||||
<header>
|
||||
<h1 class="mb-4">Requête incorrecte</h1>
|
||||
</header>
|
||||
L'adresse URL doit être de la forme :<br/>
|
||||
<?=$dirUrl?>/?<em>grille</em><br/>
|
||||
<em>grille</em> étant une suite de 81 caractères représentant la grille de gauche à droite puis de haut en bas, soit :
|
||||
<ul>
|
||||
<li>un chiffre entre 1 et 9 pour les cases connues</li>
|
||||
<li>un tiret (-) pour les case vides</li>
|
||||
</ul>
|
||||
Exemple :<br/>
|
||||
<a href='<?=$newGridUrl?>'><?=$newGridUrl?></a>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
32
404.php
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
require("classes.php");
|
||||
$grid = new Grid();
|
||||
$grid->generate();
|
||||
|
||||
header("HTTP/1.0 404 Not Found", true, 404);
|
||||
|
||||
$urlDir = $_SERVER["REQUEST_SCHEME"] . "://" . $_SERVER["HTTP_HOST"] . dirname($_SERVER["DOCUMENT_URI"]);
|
||||
$urlExample = $urlDir . "/" . $grid->toString();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang='fr'>
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width' />
|
||||
<title>Sudoku non trouvé</title>
|
||||
<link rel='stylesheet' type='text/css' href='style.css' />
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>#404</h1>
|
||||
</header>
|
||||
L'adresse URL doit être de la forme : <?=$urlDir?>/<em>grille</em>,<br/>
|
||||
<em>grille</em> étant une suite de 81 caractères représentant la grille de gauche à droite puis de haut en bas, soit :
|
||||
<ul>
|
||||
<li>un chiffre entre 1 et 9 pour les cases connues</li>
|
||||
<li>un point pour les case vides</li>
|
||||
</ul>
|
||||
Exemple : <a href='<?=$urlExample?>'><?=$urlExample?></a><br/>
|
||||
</body>
|
||||
</html>
|
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Sudoku
|
||||
|
||||
Web sudoku assistant
|
||||
|
||||

|
136
classes.php
Normal file → Executable file
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
const UNKNOWN = ".";
|
||||
const UNKNOWN = "-";
|
||||
|
||||
$validGrids = array();
|
||||
|
||||
function isKnown($box) {
|
||||
return $box->value != UNKNOWN;
|
||||
@ -24,16 +26,20 @@
|
||||
}
|
||||
|
||||
class Box {
|
||||
public $values = array("1", "2", "3", "4", "5", "6", "7", "8", "9");
|
||||
public $values = array('1', '2', '3', '4', '5', '6', '7', '8', '9');
|
||||
public $value = UNKNOWN;
|
||||
public $rowId;
|
||||
public $columnId;
|
||||
public $regionId;
|
||||
public $candidates;
|
||||
public $candidateRemoved = array();
|
||||
public $neighbourhood = array();
|
||||
|
||||
function __construct($rowId, $columnId, $regionId) {
|
||||
$this->value = UNKNOWN;
|
||||
$this->rowId = $rowId;
|
||||
$this->columnId = $columnId;
|
||||
$this->regionId = $regionId;
|
||||
$this->candidates = $this->values;
|
||||
$this->candidateRemoved = array();
|
||||
$this->neighbourhood = array();
|
||||
}
|
||||
|
||||
function searchCandidates() {
|
||||
@ -46,8 +52,13 @@
|
||||
}
|
||||
|
||||
class Grid {
|
||||
function __construct() {
|
||||
$this->boxes = array();
|
||||
|
||||
private $boxes = array();
|
||||
private $rows;
|
||||
private $columns;
|
||||
private $regions;
|
||||
|
||||
function __construct($gridStr="") {
|
||||
$this->rows = array_fill(0, 9, array());
|
||||
$this->columns = array_fill(0, 9, array());
|
||||
$this->regions = array_fill(0, 9, array());
|
||||
@ -72,6 +83,12 @@
|
||||
if ($box != $neighbour && !in_array($neighbour, $box->neighbourhood))
|
||||
$box->neighbourhood[] = $neighbour;
|
||||
}
|
||||
|
||||
if ($gridStr) {
|
||||
$this->import($gridStr);
|
||||
} else {
|
||||
$this->generate();
|
||||
}
|
||||
}
|
||||
|
||||
function import($gridStr) {
|
||||
@ -84,20 +101,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function containsDuplicates() {
|
||||
foreach(array_merge($this->rows, $this->columns, $this->regions) as $area) {
|
||||
$unknownBoxes = array_filter($area, "isUnknown");
|
||||
foreach($unknownBoxes as $box1) {
|
||||
foreach($unknownBoxes as $box2) {
|
||||
if (($box1 !== $box2) && ($box1->value == $box2->value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function generate() {
|
||||
// Init with a shuffle row
|
||||
$values = array("1", "2", "3", "4", "5", "6", "7", "8", "9");
|
||||
@ -110,34 +113,59 @@
|
||||
// Fill grid
|
||||
$this->solutionsGenerator(true)->current();
|
||||
|
||||
// Remove clues while there is still a unique solution
|
||||
shuffle($this->boxes);
|
||||
$nbClues = count($this->boxes);
|
||||
foreach($this->boxes as $testBox) {
|
||||
$testBoxes = array($testBox);
|
||||
if ($nbClues >=30)
|
||||
$testBoxes[] = $this->rows[8-$testBox->rowId][8-$testBox->columnId];
|
||||
if ($nbClues >=61) {
|
||||
$testBoxes[] = $this->rows[8-$testBox->rowId][$testBox->columnId];
|
||||
$testBoxes[] = $this->rows[$testBox->rowId][8-$testBox->columnId];
|
||||
// Group boxes with their groupedSymetricals
|
||||
$groupedSymetricals = array(array($this->rows[4][4]));
|
||||
for ($rowId = 0; $rowId <= 3; $rowId++) {
|
||||
for ($columnId = 0; $columnId <= 3; $columnId++) {
|
||||
$groupedSymetricals[] = array(
|
||||
$this->rows[$rowId][$columnId],
|
||||
$this->rows[8-$rowId][8-$columnId],
|
||||
$this->rows[8-$rowId][$columnId],
|
||||
$this->rows[$rowId][8-$columnId]
|
||||
);
|
||||
}
|
||||
$testBoxes = array_filter($testBoxes, "isKnown");
|
||||
$erasedValues = array();
|
||||
forEach($testBoxes as $testBox) {
|
||||
$erasedValues[] = $testBox->value;
|
||||
$groupedSymetricals[] = array(
|
||||
$this->rows[$rowId][4],
|
||||
$this->rows[8-$rowId][4]
|
||||
);
|
||||
}
|
||||
for ($columnId = 0; $columnId <= 3; $columnId++) {
|
||||
$groupedSymetricals[] = array(
|
||||
$this->rows[4][$columnId],
|
||||
$this->rows[4][8-$columnId]
|
||||
);
|
||||
}
|
||||
|
||||
// Remove clues randomly and their groupedSymetricals while there is still a unique solution
|
||||
shuffle($groupedSymetricals);
|
||||
foreach($groupedSymetricals as $symetricals) {
|
||||
shuffle($symetricals);
|
||||
foreach ($symetricals as $testBox) {
|
||||
$erasedValue = $testBox->value;
|
||||
$testBox->value = UNKNOWN;
|
||||
forEach($testBox->neighbourhood as $neighbour)
|
||||
$neighbour->searchCandidates();
|
||||
}
|
||||
if ($this->isValid()) {
|
||||
$nbClues -= count($testBoxes);
|
||||
} else {
|
||||
forEach($testBoxes as $i => $testBox) {
|
||||
$testBox->value = $erasedValues[$i];
|
||||
if (!$this->isValid()) {
|
||||
$testBox->value = $erasedValue;
|
||||
forEach($testBox->neighbourhood as $neighbour) array_unset_value($testBox->value, $neighbour->candidates);
|
||||
}
|
||||
}
|
||||
}
|
||||
$validGrids[] = $this->toString();
|
||||
}
|
||||
|
||||
function containsDuplicates() {
|
||||
foreach(array_merge($this->rows, $this->columns, $this->regions) as $area) {
|
||||
$knownBoxes = array_filter($area, "isKnown");
|
||||
foreach($knownBoxes as $box1) {
|
||||
foreach($knownBoxes as $box2) {
|
||||
if (($box1 != $box2) && ($box1->value == $box2->value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function countSolutions($max=2) {
|
||||
@ -145,10 +173,13 @@
|
||||
$solutionsWithoutDuplicates = array();
|
||||
$nbSolutions = 0;
|
||||
foreach($solutions as $solution) {
|
||||
$solutionsWithoutDuplicates[$solution] = true;
|
||||
$nbSolutions = count($solutionsWithoutDuplicates);
|
||||
if ($nbSolutions >= $max) {
|
||||
$solutions->send(true);
|
||||
if (!in_array($solution, $solutionsWithoutDuplicates)) {
|
||||
$solutionsWithoutDuplicates[] = $solution;
|
||||
$nbSolutions ++;
|
||||
if ($nbSolutions >= $max) {
|
||||
$solutions->send(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $nbSolutions;
|
||||
@ -164,7 +195,6 @@
|
||||
if ($randomized) shuffle($emptyBoxes);
|
||||
usort($emptyBoxes, "easyFirst");
|
||||
$testBox = $emptyBoxes[0];
|
||||
$nbTries = 0;
|
||||
if ($randomized) shuffle($testBox->candidates);
|
||||
$stop = null;
|
||||
foreach($testBox->candidates as $testBox->value) {
|
||||
@ -187,20 +217,6 @@
|
||||
}
|
||||
$testBox->value = UNKNOWN;
|
||||
} else {
|
||||
foreach(array($this->rows, $this->columns, $this->regions) as $areas) {
|
||||
foreach ($areas as $area) {
|
||||
foreach($area as $box1) {
|
||||
if (($box1->value == UNKNOWN) && (count($box1->candidates) == 0)) {
|
||||
return;
|
||||
}
|
||||
foreach($area as $box2) {
|
||||
if (($box1 !== $box2) && ($box1->value != UNKNOWN) && ($box1->value == $box2->value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield $this->toString();
|
||||
}
|
||||
}
|
||||
|
0
favicon.png
Normal file → Executable file
Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 542 B |
32
head.php
Executable file
@ -0,0 +1,32 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>Sudoku</title><meta property="og:title" content="Sudoku" />
|
||||
<meta property="og:type" content="game" />
|
||||
<meta name="description" property="og:description" content="Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9." />
|
||||
<link rel="canonical" href="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])?>" />
|
||||
<meta property="og:url" content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"]?>" />
|
||||
<meta property="og:image" content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])?>/thumbnail.php?size=200&grid=<?=$currentGrid?>" />
|
||||
<meta property="og:image:width" content="200" />
|
||||
<meta property="og:image:height" content="200" />
|
||||
<meta name="Language" CONTENT="fr" /><meta property="og:locale" content="fr_FR" />
|
||||
<meta property="og:site_name" content="<?=$_SERVER["HTTP_HOST"]?>" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-dark.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.2.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<link href="style.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=196" sizes="196x196" rel="icon" type="image/png">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=160" sizes="160x160" rel="icon" type="image/png">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=96" sizes="96x96" rel="icon" type="image/png">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=16" sizes="16x16" rel="icon" type="image/png">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=32" sizes="32x32" rel="icon" type="image/png">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=152" sizes="152x152" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=144" sizes="144x144" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=120" sizes="120x120" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=114" sizes="114x114" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=57" sizes="57x57" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=72" sizes="72x72" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=60" sizes="60x60" rel="apple-touch-icon">
|
||||
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=76" sizes="76x76" rel="apple-touch-icon">
|
||||
<link href="manifest.php?grid=<?=$currentGrid?>" rel="manifest">
|
@ -1 +0,0 @@
|
||||
<svg id="Capa_1" enable-background="new 0 0 512 512" height="24" viewBox="0 0 512 512" width="24" xmlns="http://www.w3.org/2000/svg"><g><path d="m214.28 498.762c4.098 0 8.029-1.628 10.927-4.526l278.144-278.144c11.398-11.398 11.398-29.879 0-41.277l-152.928-152.928c-11.398-11.398-29.878-11.398-41.277 0l-300.597 300.598c-11.398 11.398-11.398 29.878 0 41.277l130.474 130.474c2.898 2.898 6.828 4.526 10.927 4.526z" fill="#d3e1f5"/><path d="m275.208 386.418-78.91 78.91c-2.898 2.898-6.828 4.526-10.927 4.526h-64.331c-4.098 0-8.029-1.628-10.927-4.526l28.909 28.909c2.898 2.898 6.829 4.526 10.927 4.526h64.331c4.098 0 8.029-1.628 10.927-4.526l78.91-78.91z" fill="#c0d6f2"/><path d="m505.059 176.523-156.344-156.344c-10.455-10.455-28.935-8.925-41.277 3.417l-228.75 228.75 194.205 194.204 228.75-228.75c12.341-12.342 13.871-30.822 3.416-41.277z" fill="#0473ce"/><path d="m187.955 245.061 134.545-134.545c4.023-4.023 10.546-4.023 14.569 0l77.653 77.653c4.023 4.023 4.023 10.546 0 14.569l-134.545 134.545c-4.023 4.023-10.546 4.023-14.569 0l-77.653-77.653c-4.023-4.023-4.023-10.546 0-14.569z" fill="#0055a3"/><path d="m505.059 176.523-28.908-28.909c10.455 10.455 8.925 28.935-3.417 41.277l-228.75 228.75 28.909 28.909 228.75-228.75c12.341-12.342 13.871-30.822 3.416-41.277z" fill="#0067c5"/></g></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<polygon style="fill:#FFCC75;" points="47.202,417.53 94.404,464.732 47.202,511.945 0,464.743 "/>
|
||||
<polygon style="fill:#082947;" points="236.149,414.811 212.545,438.4 111.564,368.892 166.634,313.823 "/>
|
||||
<polygon style="fill:#274B6D;" points="182.37,329.559 127.301,384.628 73.529,299.384 97.126,275.788 "/>
|
||||
<polygon style="fill:#FF9D49;" points="512,118.078 452.926,59.003 294.044,186.413 166.634,345.296 251.886,430.548 "/>
|
||||
<path style="fill:#274B6D;" d="M143.037,368.892l69.508,69.508c0,0-61.136,0.802-102.397,42.063l-39.35-39.333l20.383-51.855
|
||||
L143.037,368.892z"/>
|
||||
<polygon style="fill:#FFCC75;" points="393.977,0.055 81.39,260.051 166.634,345.296 452.926,59.003 "/>
|
||||
<path style="fill:#185F8D;" d="M73.529,299.384l69.508,69.508l-72.239,72.239L31.458,401.79
|
||||
C72.719,360.529,73.529,299.384,73.529,299.384z"/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 511.989 511.989" style="enable-background:new 0 0 511.989 511.989;" xml:space="preserve" width="24" height="24">
|
||||
<rect x="408.399" y="26.854" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 819.7593 -231.0359)" style="fill:#9EC8E8;" width="98.659" height="54.811"/>
|
||||
<polygon style="fill:#082947;" points="430.604,174.402 186.029,418.976 124.017,372.468 376.343,120.141 "/>
|
||||
<polygon style="fill:#274B6D;" points="384.094,127.892 131.769,380.219 93.012,325.958 337.586,81.384 "/>
|
||||
<polygon style="fill:#4284B2;" points="477.112,127.892 430.604,174.402 376.343,120.141 430.604,81.384 "/>
|
||||
<rect x="20.377" y="439.543" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -273.1405 837.2019)" style="fill:#082947;" width="32.886" height="71.254"/>
|
||||
<polygon style="fill:#4284B2;" points="186.029,418.976 69.757,488.74 46.502,465.484 77.509,403.473 139.52,372.468 "/>
|
||||
<g>
|
||||
<polygon style="fill:#6DA8D6;" points="139.52,372.468 46.502,465.484 23.248,442.23 93.012,325.958 "/>
|
||||
|
||||
<rect x="351.209" y="48.511" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 713.2485 -132.6418)" style="fill:#6DA8D6;" width="65.773" height="65.773"/>
|
||||
</g>
|
||||
<polygon style="fill:#9EC8E8;" points="178.681,193.781 155.426,170.527 302.704,23.249 442.229,162.774 418.975,186.028
|
||||
302.704,69.757 "/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,45 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="24" height="24">
|
||||
<polygon style="fill:#FF9D49;" points="393.29,213.665 142.441,464.524 87.044,424.954 345.817,150.364 "/>
|
||||
<polygon style="fill:#FFCC75;" points="353.73,158.277 87.044,424.954 47.473,369.557 298.333,118.708 "/>
|
||||
<polygon style="fill:#BD1515;" points="512,94.965 440.78,166.185 377.475,118.708 464.522,47.486 "/>
|
||||
<polygon style="fill:#F2484B;" points="464.516,47.482 385.382,126.615 345.817,71.224 417.038,0.002 "/>
|
||||
<g>
|
||||
|
||||
<rect x="335.974" y="75.292" transform="matrix(-0.7071 0.7071 -0.7071 -0.7071 731.5841 -18.1559)" style="fill:#082947;" width="67.157" height="134.292"/>
|
||||
<polygon style="fill:#082947;" points="71.22,488.261 0,511.998 23.736,440.776 63.305,448.692 "/>
|
||||
</g>
|
||||
<polygon style="fill:#F2EBD9;" points="47.473,369.557 23.736,440.776 71.22,488.261 142.441,464.524 "/>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="574.859px" height="574.86px" viewBox="0 0 574.859 574.86" style="enable-background:new 0 0 574.859 574.86;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<path d="M181.688,521.185V353.841H19.125v167.344c0,10.566,13.34,23.906,23.906,23.906h124.312
|
||||
C177.91,545.091,181.688,531.751,181.688,521.185z M66.938,502.06c0,2.64-2.142,4.781-4.781,4.781s-4.781-2.142-4.781-4.781
|
||||
V377.748c0-2.64,2.142-4.781,4.781-4.781s4.781,2.142,4.781,4.781V502.06z M105.188,502.06c0,2.64-2.142,4.781-4.781,4.781
|
||||
s-4.781-2.142-4.781-4.781V377.748c0-2.64,2.142-4.781,4.781-4.781s4.781,2.142,4.781,4.781V502.06z M143.438,502.06
|
||||
c0,2.64-2.142,4.781-4.781,4.781s-4.781-2.142-4.781-4.781V377.748c0-2.64,2.142-4.781,4.781-4.781s4.781,2.142,4.781,4.781V502.06
|
||||
z"/>
|
||||
<path d="M19.125,334.716h162.562v-19.125h19.125v-19.125h-57.375c0-10.566-6.828-19.125-15.243-19.125H77.399
|
||||
c-8.415,0-15.243,8.559-15.243,19.125H0v19.125h19.125V334.716z"/>
|
||||
<path d="M357.007,191.556C370.968,329.811,243.892,542.08,243.892,542.08c145.235-78.212,169.189-207.363,169.189-207.363
|
||||
c42.333,66.479,44.475,228.305,44.475,228.305c80.995-194.109,0-377.049,0-377.049l117.304,48.874
|
||||
c-19.546-74.014-141.047-125.68-141.047-125.68c-110.322,50.27-249.974,44.686-249.974,44.686
|
||||
C259.249,226.469,357.007,191.556,357.007,191.556z"/>
|
||||
<circle cx="369.782" cy="55.128" r="43.29"/>
|
||||
<path d="M94.43,229.529c5.977-2.391,27.492-13.148,28.764,0c1.271,13.148,11.876,9.562,19.048,0s3.586-25.102,11.953-23.906
|
||||
s15.539-10.758,17.93-21.735c2.391-10.978-22.711-18.905-33.469-21.458s-20.32,13.321-27.492,13.321s-17.93-20.33-25.102-10.768
|
||||
s-11.953,40.641-11.953,40.641c-10.758-5.977-21.516,7.172-25.102,16.734S88.453,231.919,94.43,229.529z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.2 KiB |
46
img/undo.svg
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="299.021px" height="299.021px" viewBox="0 0 299.021 299.021" style="enable-background:new 0 0 299.021 299.021;"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M292.866,254.432c-2.288,0-4.443-1.285-5.5-3.399c-0.354-0.684-28.541-52.949-146.169-54.727v51.977
|
||||
c0,2.342-1.333,4.48-3.432,5.513c-2.096,1.033-4.594,0.793-6.461-0.63L2.417,154.392C0.898,153.227,0,151.425,0,149.516
|
||||
c0-1.919,0.898-3.72,2.417-4.888l128.893-98.77c1.87-1.426,4.365-1.667,6.461-0.639c2.099,1.026,3.432,3.173,3.432,5.509v54.776
|
||||
c3.111-0.198,7.164-0.37,11.947-0.37c43.861,0,145.871,13.952,145.871,143.136c0,2.858-1.964,5.344-4.75,5.993
|
||||
C293.802,254.384,293.34,254.432,292.866,254.432z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
49
index.php
Normal file → Executable file
@ -1,10 +1,45 @@
|
||||
<?php
|
||||
require("classes.php");
|
||||
session_start();
|
||||
$grid = new Grid();
|
||||
$grid->generate();
|
||||
$currentGrid = $grid->toString();
|
||||
$_SESSION[$currentGrid] = "checked";
|
||||
header("Location: ".$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])."/".$currentGrid);
|
||||
exit();
|
||||
|
||||
$fullUrl = $_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"];
|
||||
$dirUrl = dirname($fullUrl);
|
||||
$currentGrid = strip_tags($_SERVER['QUERY_STRING']);
|
||||
|
||||
if (preg_match("/^[1-9-]{81}$/", $currentGrid)) {
|
||||
session_id($currentGrid);
|
||||
session_start(["use_cookies" => false]);
|
||||
|
||||
if (!array_key_exists("nbSolutions", $_SESSION)) {
|
||||
$grid = new Grid($currentGrid);
|
||||
$_SESSION["nbSolutions"] = $grid->containsDuplicates() ? -1 : $grid->countSolutions(2);
|
||||
}
|
||||
switch($_SESSION["nbSolutions"]) {
|
||||
case -1:
|
||||
$warning = "Cette grille contient des doublons.";
|
||||
break;
|
||||
case 0:
|
||||
$warning = "Cette grille n'a pas de solution.";
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
$warning = "Cette grille a plusieurs solutions.";
|
||||
}
|
||||
require("sudoku.php");
|
||||
} else {
|
||||
if ($currentGrid) {
|
||||
require("400.php");
|
||||
} else {
|
||||
$grid = new Grid();
|
||||
$gridAsString = $grid->toString();
|
||||
$newGridUrl = "$dirUrl/?$gridAsString";
|
||||
|
||||
session_id($gridAsString);
|
||||
session_start(["use_cookies" => false]);
|
||||
|
||||
$_SESSION["nbSolutions"] = 1;
|
||||
|
||||
header("Location: $newGridUrl");
|
||||
}
|
||||
}
|
||||
?>
|
53
manifest.json.php → manifest.php
Normal file → Executable file
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
session_start();
|
||||
if ($_SESSION["currentGrid"])
|
||||
$currentGrid = $_SESSION["currentGrid"];
|
||||
if (isset($_GET["grid"]))
|
||||
$currentGrid = $_GET["grid"];
|
||||
else
|
||||
$currentGrid = ".";
|
||||
?>
|
||||
@ -10,27 +9,27 @@
|
||||
"name": "Sudoku",
|
||||
"description": "Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9.",
|
||||
"icons": [{
|
||||
"src": "thumbnail.png?size=48",
|
||||
"src": "thumbnail.php?size=48&grid=<?=$currentGrid?>",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=72",
|
||||
"src": "thumbnail.php?size=72&grid=<?=$currentGrid?>",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=96",
|
||||
"src": "thumbnail.php?size=96&grid=<?=$currentGrid?>",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=144",
|
||||
"src": "thumbnail.php?size=144&grid=<?=$currentGrid?>",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=168",
|
||||
"src": "thumbnail.php?size=168&grid=<?=$currentGrid?>",
|
||||
"sizes": "168x168",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=192",
|
||||
"src": "thumbnail.php?size=192&grid=<?=$currentGrid?>",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}],
|
||||
@ -47,27 +46,27 @@
|
||||
"description": "Continuer cette grille de sudoku",
|
||||
"url": "<?=$currentGrid?>",
|
||||
"icons": [{
|
||||
"src": "thumbnail.png?size=48",
|
||||
"src": "thumbnail.php?size=48&grid=<?=$currentGrid?>",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=72",
|
||||
"src": "thumbnail.php?size=72&grid=<?=$currentGrid?>",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=96",
|
||||
"src": "thumbnail.php?size=96&grid=<?=$currentGrid?>",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=144",
|
||||
"src": "thumbnail.php?size=144&grid=<?=$currentGrid?>",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=168",
|
||||
"src": "thumbnail.php?size=168&grid=<?=$currentGrid?>",
|
||||
"sizes": "168x168",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=192",
|
||||
"src": "thumbnail.php?size=192&grid=<?=$currentGrid?>",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}]
|
||||
@ -78,27 +77,27 @@
|
||||
"description": "Grille de sudoku vierge",
|
||||
"url": ".................................................................................",
|
||||
"icons": [{
|
||||
"src": "thumbnail.png?size=48",
|
||||
"src": "thumbnail.php?size=48&grid=.................................................................................",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=72",
|
||||
"src": "thumbnail.php?size=72&grid=.................................................................................",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=96",
|
||||
"src": "thumbnail.php?size=96&grid=.................................................................................",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=144",
|
||||
"src": "thumbnail.php?size=144&grid=.................................................................................",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=168",
|
||||
"src": "thumbnail.php?size=168&grid=.................................................................................",
|
||||
"sizes": "168x168",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=192",
|
||||
"src": "thumbnail.php?size=192&grid=.................................................................................",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}]
|
||||
@ -109,27 +108,27 @@
|
||||
"description": "Nouvelle grille de sudoku",
|
||||
"url": ".",
|
||||
"icons": [{
|
||||
"src": "thumbnail.png?size=48",
|
||||
"src": "thumbnail.php?size=48&grid=.528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=72",
|
||||
"src": "thumbnail.php?size=72&grid=.528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=96",
|
||||
"src": "thumbnail.php?size=96&grid=.528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=144",
|
||||
"src": "thumbnail.php?size=144&grid=.528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=168",
|
||||
"src": "thumbnail.php?size=168&grid=.528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...",
|
||||
"sizes": "168x168",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "thumbnail.png?size=192",
|
||||
"src": "thumbnail.php?size=192&grid=.528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}]
|
@ -1,35 +0,0 @@
|
||||
location /sudoku/ {
|
||||
alias /var/www/sudoku/;
|
||||
|
||||
if ($scheme = http) {
|
||||
rewrite ^ https://$server_name$request_uri? permanent;
|
||||
}
|
||||
|
||||
index index.php;
|
||||
|
||||
try_files $uri $uri/ @add-php-extention;
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
fastcgi_pass unix:/var/run/php/php7.3-fpm-sudoku.sock;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
fastcgi_param REMOTE_USER $remote_user;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
}
|
||||
|
||||
error_page 404 /sudoku/404.php;
|
||||
location /sudoku/404.php {
|
||||
internal;
|
||||
}
|
||||
}
|
||||
|
||||
location ~ "^/sudoku/([1-9.]{81})$" {
|
||||
rewrite "^/sudoku/([1-9.]{81})$" /sudoku/sudoku.php?grid=$1 last;
|
||||
}
|
||||
|
||||
location @add-php-extention {
|
||||
rewrite ^([^?#]*)(\?.*)?(#.*)?$ $1.php$2$3 last;
|
||||
}
|
10
service-worker.js.php → service-worker.js
Normal file → Executable file
@ -1,11 +1,3 @@
|
||||
<?php
|
||||
session_start();
|
||||
if ($_SESSION["currentGrid"])
|
||||
$currentGrid = $_SESSION["currentGrid"];
|
||||
else
|
||||
$currentGrid = ".";
|
||||
header ("Content-type: application/javascript");
|
||||
?>
|
||||
/*
|
||||
Copyright 2015, 2019, 2020 Google LLC. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -24,7 +16,7 @@ Copyright 2015, 2019, 2020 Google LLC. All Rights Reserved.
|
||||
const OFFLINE_VERSION = 1;
|
||||
const CACHE_NAME = "offline";
|
||||
// Customize this with a different URL if needed.
|
||||
const OFFLINE_URL = "<?=$currentGrid?>";
|
||||
const OFFLINE_URL = ".";
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
372
style.css
Normal file → Executable file
@ -1,243 +1,161 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
width: min-content;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
section, div, footer {
|
||||
align-items: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
margin: 0.8rem 0;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 0.3rem;
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
|
||||
.grid {
|
||||
border-spacing: 0;
|
||||
border: 1px solid black;
|
||||
border-radius: 6px;
|
||||
margin: auto;
|
||||
cursor: url(img/ink-pen.svg) 2 22, text;
|
||||
}
|
||||
.grid td, tr {
|
||||
padding: 0;
|
||||
}
|
||||
.grid tr:first-child td:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
.grid tr:first-child td:first-child input {
|
||||
border-top-left-radius: 4px;
|
||||
}
|
||||
.grid tr:first-child td:last-child {
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
.grid tr:first-child td:last-child input {
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.grid tr:last-child td:first-child {
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
.grid tr:last-child td:first-child > input {
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.grid tr:last-child td:last-child {
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
.grid tr:last-child td:last-child input {
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.grid tr:nth-child(3n+1) td {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
.grid tr:nth-child(3n+2) td {
|
||||
border-top: 1px solid grey;
|
||||
border-bottom: 1px solid grey;
|
||||
}
|
||||
.grid tr:nth-child(3n) td {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
.grid td:nth-child(3n+1) {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
.grid td:nth-child(3n+2) {
|
||||
border-left: 1px solid grey;
|
||||
border-right: 1px solid grey;
|
||||
}
|
||||
.grid td:nth-child(3n+3) {
|
||||
border-right: 1px solid black;
|
||||
}
|
||||
|
||||
.grid input {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
font-size: 1.5rem;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
transition: background 0.5s;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
input[type="number"]::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input[type="number"]::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
.grid input:enabled {
|
||||
background: white;
|
||||
color: darkblue;
|
||||
cursor: inherit;
|
||||
}
|
||||
.grid input.pencil,
|
||||
.grid input::placeholder {
|
||||
color: #666 !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
.grid input:disabled {
|
||||
color: white;
|
||||
background: #6666ff;
|
||||
}
|
||||
.grid input:disabled,
|
||||
.grid input.forbidden {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
.grid input.forbidden:enabled {
|
||||
background: #ffffaa;
|
||||
}
|
||||
.grid input.same-value:enabled {
|
||||
background: #ffff33;
|
||||
}
|
||||
.grid input.forbidden:disabled {
|
||||
color: #ffffaa;
|
||||
background: #6666ff;
|
||||
}
|
||||
.grid input.same-value:disabled,
|
||||
.tools button.same-value:enabled,
|
||||
.tools input:enabled:checked+label {
|
||||
color: #ffffaa !important;
|
||||
background: #00b359 !important;
|
||||
}
|
||||
.grid input.one-candidate {
|
||||
cursor: help !important;
|
||||
-webkit-appearance: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.tools button,
|
||||
.tools input+label {
|
||||
color: white;
|
||||
text-shadow: -1px -1px #5b6c9e;
|
||||
background: #8ca6f2;
|
||||
border: 2px outset #8ca6f2;
|
||||
border-radius: 4px;
|
||||
font-size: 1.3rem;
|
||||
min-width:20px;
|
||||
padding: 4px 5px 5px 5px;
|
||||
margin: 0px 1px 1px 1px;
|
||||
input[type="number"]::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
table input {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
font-size: 1.3rem !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
text-align: center;
|
||||
appearance: textfield !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
table td.table-primary input,
|
||||
table td.table-active input,
|
||||
table.table-success input,
|
||||
td.table-danger input:disabled,
|
||||
table input:not([disabled]) {
|
||||
background: transparent !important;
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
table input:not([disabled]):hover {
|
||||
background: #9fb9b945 !important;
|
||||
}
|
||||
|
||||
table input:disabled {
|
||||
background-position: center !important;
|
||||
}
|
||||
|
||||
tr:nth-child(3n+1) td input {
|
||||
border-top-width: 3px !important;
|
||||
}
|
||||
|
||||
tr:last-child td input {
|
||||
border-bottom-width: 3px !important;
|
||||
}
|
||||
|
||||
td:nth-child(3n+1) input {
|
||||
border-left-width: 3px !important;
|
||||
}
|
||||
|
||||
td:last-child input {
|
||||
border-right-width: 3px !important;
|
||||
}
|
||||
|
||||
tr:first-child td:first-child {
|
||||
border-top-left-radius: .7rem !important;
|
||||
}
|
||||
|
||||
tr:first-child td:first-child input {
|
||||
border-top-left-radius: .5rem !important;
|
||||
}
|
||||
|
||||
tr:first-child td:last-child {
|
||||
border-top-right-radius: .7rem !important;
|
||||
}
|
||||
|
||||
tr:first-child td:last-child input {
|
||||
border-top-right-radius: .5rem !important;
|
||||
}
|
||||
|
||||
tr:last-child td:first-child {
|
||||
border-bottom-left-radius: .7rem !important;
|
||||
}
|
||||
|
||||
tr:last-child td:first-child input {
|
||||
border-bottom-left-radius: .5rem !important;
|
||||
}
|
||||
|
||||
tr:last-child td:last-child {
|
||||
border-bottom-right-radius: .7rem !important;
|
||||
}
|
||||
|
||||
tr:last-child td:last-child input {
|
||||
border-bottom-right-radius: .5rem !important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
td,
|
||||
table input {
|
||||
transition: background-color .4s, box-shadow .4s !important;
|
||||
}
|
||||
|
||||
.context-menu li {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.table-active {
|
||||
cursor: inherit !important;
|
||||
}
|
||||
|
||||
.not-allowed {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
table {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
button,
|
||||
label {
|
||||
/*! padding: .375rem .65rem !important; */
|
||||
cursor: pointer;
|
||||
}
|
||||
.tools img {
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
button i,
|
||||
label i {
|
||||
margin: 0 -.125rem;
|
||||
}
|
||||
.tools input {
|
||||
display:none;
|
||||
|
||||
button:disabled,
|
||||
:disabled+label {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
.tools button:enabled:hover,
|
||||
.tools input:enabled:hover+label {
|
||||
border-width: 1px;
|
||||
border-style: outset;
|
||||
padding: 5px 5px 5px 6px;
|
||||
margin: 1px 1px 1px 2px;
|
||||
|
||||
table input:enabled {
|
||||
cursor: inherit;
|
||||
}
|
||||
.tools input:enabled:checked:hover+label {
|
||||
border-width: 3px;
|
||||
border-style: inset;
|
||||
padding: 4px 2px 2px 5px;
|
||||
margin: 1px 1px 1px 2px;
|
||||
|
||||
.pencil {
|
||||
color: var(--bs-secondary-color) !important;
|
||||
}
|
||||
.tools input:enabled:checked+label {
|
||||
text-shadow: -1px -1px #005f2f;
|
||||
border: 2px inset #00b359;
|
||||
background: #00b359;
|
||||
padding: 4px 4px 4px 5px;
|
||||
margin: 1px 1px 0px 2px;
|
||||
|
||||
#colorPickerInput{
|
||||
width: 2.3rem;
|
||||
height: auto;
|
||||
padding: .375rem;
|
||||
}
|
||||
.tools button:enabled:active,
|
||||
.tools input:enabled:active+label {
|
||||
border-width: 4px !important;
|
||||
border-style: inset !important;
|
||||
padding: 4px 0px 0px 5px !important;
|
||||
margin: 0px 1px 0px 2px !important;
|
||||
|
||||
#colorPickerLabel {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
.tools button:disabled,
|
||||
.tools input:disabled+label {
|
||||
text-shadow: -1px -1px #555;
|
||||
color: #ccc;
|
||||
background: darkgrey;
|
||||
border: 1px outset darkgrey;
|
||||
padding: 5px 6px 6px 6px;
|
||||
margin: 0px 1px 1px 1px;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.tools button.warning {
|
||||
background: #ff5050;
|
||||
border-color: #ff5050;
|
||||
|
||||
@media (prefers-color-scheme:dark) {
|
||||
.pencil {
|
||||
color: #5a5a5a !important;
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
border: 1px solid #CCC;
|
||||
white-space: nowrap;
|
||||
font-family: sans-serif;
|
||||
background: #EEE;
|
||||
color: #333;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.context-menu li {
|
||||
padding: 6px 10px;
|
||||
cursor: default;
|
||||
list-style-type: none;
|
||||
transition: all .3s ease;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.context-menu li:hover {
|
||||
background-color: #DEF;
|
||||
}
|
||||
.context-menu li.error {
|
||||
color: #888
|
||||
}
|
||||
.context-menu li.error:hover {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.credits {
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
z-index: 100;
|
||||
}
|
420
sudoku.js
Normal file → Executable file
@ -1,15 +1,18 @@
|
||||
const VALUES = "123456789"
|
||||
const VALUES = "123456789"
|
||||
const UNKNOWN = '.'
|
||||
const SUGESTION_DELAY = 60000 //ms
|
||||
|
||||
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 boxes = []
|
||||
let rows = Array.from(Array(9), x => [])
|
||||
let columns = Array.from(Array(9), x => [])
|
||||
let regions = Array.from(Array(9), x => [])
|
||||
let areaNames = {
|
||||
ligne: rows,
|
||||
colonne: columns,
|
||||
région: regions,
|
||||
}
|
||||
let valueToInsert = ""
|
||||
let history = []
|
||||
let accessKeyModifiers = "AccessKey+"
|
||||
let easyBoxes = []
|
||||
let insertRadios = []
|
||||
|
||||
function shuffle(iterable) {
|
||||
array = Array.from(iterable)
|
||||
@ -25,24 +28,24 @@ function shuffle(iterable) {
|
||||
return array
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
window.onload = function() {
|
||||
let rowId = 0
|
||||
for (let row of grid.getElementsByTagName('tr')) {
|
||||
let columnId = 0
|
||||
for (let box of row.getElementsByTagName('input')) {
|
||||
let regionId = rowId - rowId % 3 + Math.floor(columnId / 3)
|
||||
if (!box.disabled) {
|
||||
box.onfocus = onfocus
|
||||
box.oninput = oninput
|
||||
box.onblur = onblur
|
||||
box.onclick = onclick
|
||||
box.previousValue = ""
|
||||
box.previousPlaceholder = ""
|
||||
box.onfocus = onfocus
|
||||
box.oninput = oninput
|
||||
box.onblur = onblur
|
||||
box.onclick = onclick
|
||||
box.onmouseenter = onmouseenter
|
||||
box.onmouseleave = onmouseleave
|
||||
}
|
||||
box.oncontextmenu = oncontextmenu
|
||||
box.rowId = rowId
|
||||
box.columnId = columnId
|
||||
box.regionId = regionId
|
||||
box.rowId = rowId
|
||||
box.columnId = columnId
|
||||
box.regionId = regionId
|
||||
boxes.push(box)
|
||||
rows[rowId].push(box)
|
||||
columns[columnId].push(box)
|
||||
@ -52,34 +55,61 @@ window.onload = function () {
|
||||
rowId++
|
||||
}
|
||||
|
||||
const savedGame = localStorage[location.href]
|
||||
if (savedGame) {
|
||||
boxes.forEach((box, i) => {
|
||||
if (!box.disabled && savedGame[i] != UNKNOWN) {
|
||||
box.value = savedGame[i]
|
||||
box.previousValue = savedGame[i]
|
||||
}
|
||||
})
|
||||
}
|
||||
if (localStorage["tool"] == "sight") sightCheckbox.checked = true
|
||||
else if (localStorage["tool"] == "highlighter") highlighterCheckbox.checked = true
|
||||
|
||||
colorPickerInput.value = window.getComputedStyle(grid).getPropertyValue("--bs-body-color")
|
||||
|
||||
boxes.forEach(box => {
|
||||
box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId]))
|
||||
box.andNeighbourhood = Array.from(box.neighbourhood)
|
||||
box.neighbourhood.delete(box)
|
||||
box.neighbourhood = Array.from(box.neighbourhood)
|
||||
searchCandidatesOf(box)
|
||||
})
|
||||
|
||||
if (/Win/.test(navigator.platform) || /Linux/.test(navigator.platform)) accessKeyModifiers = "Alt+Maj+"
|
||||
else if (/Mac/.test(navigator.platform)) accessKeyModifiers = "⌃⌥"
|
||||
insertRadios = Array.from(insertRadioGroup.getElementsByTagName("input")).slice(1)
|
||||
|
||||
for (label of document.getElementsByTagName("label")) {
|
||||
label.control.label = label
|
||||
}
|
||||
let accessKeyModifiers = (/Win/.test(navigator.userAgent) || /Linux/.test(navigator.userAgent)) ? "Alt+Maj+"
|
||||
: (/Mac/.test(navigator.userAgent)) ? "⌃⌥"
|
||||
: "AccessKey+"
|
||||
for (node of document.querySelectorAll("*[accesskey]")) {
|
||||
node.title += " [" + (node.accessKeyLabel || accessKeyModifiers + node.accessKey) + "]"
|
||||
shortcut = ` [${node.accessKeyLabel||(accessKeyModifiers+node.accessKey)}]`
|
||||
if (node.title) node.title += shortcut
|
||||
else if (node.label) node.label.title += shortcut
|
||||
}
|
||||
|
||||
refreshUI()
|
||||
loadGame(history.state)
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register(`service-worker.js?location=${location.href}`)
|
||||
window.onpopstate = (event) => loadGame(event.state)
|
||||
|
||||
function loadGame(state) {
|
||||
if (state) {
|
||||
boxes.forEach((box, i) => {
|
||||
if (!box.disabled) {
|
||||
box.value = state.boxesValues[i]
|
||||
box.placeholder = state.boxesPlaceholders[i]
|
||||
}
|
||||
})
|
||||
restartLink.classList.remove("disabled")
|
||||
undoButton.disabled = false
|
||||
fixGridLink.href = "?" + state.boxesValues.map(value => value || UNKNOWN).join("")
|
||||
} else {
|
||||
boxes.filter(box => !box.disabled).forEach(box => {
|
||||
box.value = ""
|
||||
box.placeholder = ""
|
||||
})
|
||||
restartLink.classList.add("disabled")
|
||||
undoButton.disabled = true
|
||||
fixGridLink.href = ""
|
||||
}
|
||||
|
||||
checkBoxes()
|
||||
enableRadio()
|
||||
highlight()
|
||||
}
|
||||
|
||||
function searchCandidatesOf(box) {
|
||||
@ -89,10 +119,10 @@ function searchCandidatesOf(box) {
|
||||
switch (box.candidates.size) {
|
||||
case 0:
|
||||
box.title = "Aucune possibilité !"
|
||||
break
|
||||
break
|
||||
case 1:
|
||||
box.title = "1 possibilité [Clic-droit]"
|
||||
break
|
||||
box.title = "Une seule possibilité [Clic-droit]"
|
||||
break
|
||||
default:
|
||||
box.title = box.candidates.size + " possibilités [Clic-droit]"
|
||||
}
|
||||
@ -101,21 +131,21 @@ function searchCandidatesOf(box) {
|
||||
|
||||
function onfocus() {
|
||||
if (pencilRadio.checked) {
|
||||
this.value = this.placeholder
|
||||
this.type = "text"
|
||||
this.value = this.placeholder
|
||||
this.placeholder = ""
|
||||
this.classList.add("pencil")
|
||||
} else {
|
||||
this.select()
|
||||
}
|
||||
this.style.caretColor = valueToInsert? "transparent": "auto"
|
||||
if (penColor && inkPenRadio.checked) {
|
||||
this.style.setProperty("color", penColor)
|
||||
}
|
||||
this.style.caretColor = valueToInsert ? "transparent" : "auto"
|
||||
}
|
||||
|
||||
function onclick() {
|
||||
if (this.value == "" && this.candidates.size == 1) {
|
||||
valueToInsert = this.candidates.values().next().value
|
||||
document.getElementById("insertRadio" + valueToInsert).checked = true
|
||||
this.value = valueToInsert
|
||||
this.oninput()
|
||||
} else if (inkPenRadio.checked) {
|
||||
if (inkPenRadio.checked) {
|
||||
if (valueToInsert) {
|
||||
this.value = valueToInsert
|
||||
this.oninput()
|
||||
@ -123,9 +153,10 @@ function onclick() {
|
||||
this.select()
|
||||
}
|
||||
} else if (pencilRadio.checked) {
|
||||
if (valueToInsert)
|
||||
this.value += valueToInsert
|
||||
if (valueToInsert) {
|
||||
this.value = Array.from(new Set(this.value + valueToInsert)).join("")
|
||||
this.oninput()
|
||||
}
|
||||
} else if (eraserRadio.checked) {
|
||||
this.value = ""
|
||||
this.placeholder = ""
|
||||
@ -134,179 +165,180 @@ function onclick() {
|
||||
}
|
||||
|
||||
function oninput() {
|
||||
history.push({ box: this, value: this.previousValue, placeholder: this.previousPlaceholder })
|
||||
undoButton.disabled = false
|
||||
if (pencilRadio.checked) {
|
||||
this.value = Array.from(new Set(this.value)).sort().join("")
|
||||
this.previousValue = ""
|
||||
this.previousPlaceholder = this.value
|
||||
} else {
|
||||
this.previousValue = this.value
|
||||
this.previousPlaceholder = this.placeholder
|
||||
refreshBox(this)
|
||||
if (inkPenRadio.checked) {
|
||||
checkBoxes()
|
||||
enableRadio()
|
||||
highlight()
|
||||
fixGridLink.href = "?" + boxes.map(box => box.value || UNKNOWN).join("")
|
||||
}
|
||||
saveGame()
|
||||
}
|
||||
|
||||
function refreshBox(box) {
|
||||
localStorage[location.href] = boxes.map(box => box.value || ".").join("")
|
||||
|
||||
box.neighbourhood.concat([box]).forEach(neighbour => {
|
||||
searchCandidatesOf(neighbour)
|
||||
neighbour.setCustomValidity("")
|
||||
function checkBoxes() {
|
||||
boxes.forEach(box => {
|
||||
box.setCustomValidity("")
|
||||
box.classList.remove("is-invalid")
|
||||
box.parentElement.classList.remove("table-danger")
|
||||
searchCandidatesOf(box)
|
||||
if (box.candidates.size == 0) {
|
||||
box.setCustomValidity("Aucun chiffre possible !")
|
||||
box.classList.add("is-invalid")
|
||||
}
|
||||
})
|
||||
|
||||
refreshUI()
|
||||
|
||||
for (neighbour1 of box.neighbourhood) {
|
||||
if (neighbour1.value) {
|
||||
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) {
|
||||
for (neighbour of [neighbour1, neighbour2]) {
|
||||
neighbour.setCustomValidity(`Il y a un autre ${neighbour.value} dans cette ${area.name}.`)
|
||||
}
|
||||
for (let [areaName, areas] of Object.entries(areaNames))
|
||||
for (area of areas)
|
||||
area.filter(box => box.value).sort((box, neighbour) => {
|
||||
if(box.value == neighbour.value) {
|
||||
area.forEach(neighbour => neighbour.parentElement.classList.add("table-danger"))
|
||||
for (neighbour of [box, neighbour]) {
|
||||
neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${areaName}.`)
|
||||
neighbour.classList.add("is-invalid")
|
||||
}
|
||||
} else {
|
||||
if (neighbour1.candidates.size == 0) {
|
||||
neighbour1.setCustomValidity("Aucun chiffre possible !")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return box.value - neighbour.value
|
||||
})
|
||||
|
||||
if (box.form.checkValidity()) { // Correct grid
|
||||
if (boxes.filter(box => box.value == "").length == 0)
|
||||
setTimeout(() => alert(`Bravo ! Vous avez résolu la grille.`), 500)
|
||||
if (sudokuForm.checkValidity()) { // Correct grid
|
||||
if (boxes.filter(box => box.value == "").length == 0) {
|
||||
grid.classList.add("table-success")
|
||||
setTimeout(() => {
|
||||
if (confirm(`Bravo ! Vous avez résolu la grille. En voulez-vous une autre ?`))
|
||||
location = "."
|
||||
}, 400)
|
||||
} else {
|
||||
grid.classList.remove("table-success")
|
||||
}
|
||||
} else { // Errors on grid
|
||||
box.form.reportValidity()
|
||||
grid.classList.remove("table-success")
|
||||
sudokuForm.reportValidity()
|
||||
}
|
||||
}
|
||||
|
||||
function refreshUI() {
|
||||
for (radio of insertRadioGroup.getElementsByTagName("input")) {
|
||||
const label = radio.nextElementSibling
|
||||
function enableRadio() {
|
||||
for (radio of insertRadios) {
|
||||
if (boxes.filter(box => box.value == "").some(box => box.candidates.has(radio.value))) {
|
||||
radio.disabled = false
|
||||
if (radio.previousTitle) {
|
||||
label.title = radio.previousTitle
|
||||
label.previousTitle = null
|
||||
}
|
||||
radio.label.title = `Insérer un ${radio.value} [${radio.accessKeyLabel||(accessKeyModifiers+radio.accessKey)}]`
|
||||
} else {
|
||||
radio.disabled = true
|
||||
label.previousTitle = label.title
|
||||
label.title = `Tous les ${radio.value} sont posés`
|
||||
if (valueToInsert == radio.value) valueToInsert = ""
|
||||
radio.label.title = `Tous les ${radio.value} sont posés.`
|
||||
if (valueToInsert == radio.value) {
|
||||
insertRadio0.checked = true
|
||||
valueToInsert = ""
|
||||
grid.style.cursor = "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlight()
|
||||
|
||||
boxes.filter(box => !box.disabled).forEach(box => {
|
||||
if (!box.value && box.candidates.size == 1) box.classList.add("one-candidate")
|
||||
else box.classList.remove("one-candidate")
|
||||
})
|
||||
if (suggestionTimer) clearTimeout(suggestionTimer)
|
||||
suggestionTimer = setTimeout(showSuggestion, SUGESTION_DELAY)
|
||||
}
|
||||
|
||||
function highlight() {
|
||||
if (valueToInsert) {
|
||||
if (highlighterCheckbox.checked) {
|
||||
boxes.forEach(box => {
|
||||
if (box.value == valueToInsert) {
|
||||
box.classList.add("same-value")
|
||||
box.tabIndex = -1
|
||||
}
|
||||
else {
|
||||
box.classList.remove("same-value")
|
||||
if (box.candidates.has(valueToInsert) && !box.disabled) {
|
||||
box.classList.remove("forbidden")
|
||||
box.tabIndex = 0
|
||||
} else {
|
||||
box.classList.add("forbidden")
|
||||
box.tabIndex = -1
|
||||
}
|
||||
}
|
||||
})
|
||||
hintButton.disabled = true
|
||||
easyBoxes = []
|
||||
boxes.forEach(box => {
|
||||
if (valueToInsert && box.value == valueToInsert) {
|
||||
box.parentElement.classList.add("table-primary")
|
||||
box.tabIndex = -1
|
||||
} else {
|
||||
boxes.forEach(box => {
|
||||
box.classList.remove("same-value")
|
||||
if (box.disabled) {
|
||||
box.classList.add("forbidden")
|
||||
} else {
|
||||
box.classList.remove("forbidden")
|
||||
}
|
||||
box.tabIndex = 0
|
||||
})
|
||||
}
|
||||
} else {
|
||||
boxes.forEach(box => {
|
||||
box.classList.remove("same-value", "forbidden")
|
||||
box.parentElement.classList.remove("table-primary")
|
||||
box.tabIndex = 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (valueToInsert && highlighterCheckbox.checked && !box.candidates.has(valueToInsert)) {
|
||||
box.parentElement.classList.add("table-active")
|
||||
box.tabIndex = -1
|
||||
} else {
|
||||
box.parentElement.classList.remove("table-active")
|
||||
box.tabIndex = 0
|
||||
}
|
||||
|
||||
if (!box.value && box.candidates.size == 1) {
|
||||
hintButton.disabled = false
|
||||
easyBoxes.push(box)
|
||||
}
|
||||
})
|
||||
highlighterCheckbox.label.title = "Surligner les lignes, colonnes et régions contenant déjà " + (valueToInsert ? "un " + valueToInsert : "le chiffre sélectionné")
|
||||
}
|
||||
|
||||
function onblur() {
|
||||
if (this.classList.contains("pencil")) {
|
||||
this.placeholder = this.value
|
||||
this.value = ""
|
||||
this.type = "number"
|
||||
this.classList.remove("pencil")
|
||||
}
|
||||
}
|
||||
|
||||
function insert(radio) {
|
||||
if (radio.value == valueToInsert) {
|
||||
valueToInsert = ""
|
||||
radio.checked = false
|
||||
} else {
|
||||
valueToInsert = radio.value
|
||||
}
|
||||
if (inkPenRadio.checked) customCursor = "url(img/ink-pen.svg) 2 22"
|
||||
if (pencilRadio.checked) customCursor = "url(img/pencil.svg) 2 22"
|
||||
if (eraserRadio.checked) customCursor = "url(img/eraser.svg) 2 22"
|
||||
fallbackCursor = valueToInsert? "copy": "text"
|
||||
grid.style.cursor = `${customCursor}, ${fallbackCursor}`
|
||||
highlight()
|
||||
function saveGame() {
|
||||
history.pushState({
|
||||
boxesValues: boxes.map(box => box.value),
|
||||
boxesPlaceholders: boxes.map(box => box.placeholder)
|
||||
}, "")
|
||||
restartLink.classList.remove("disabled")
|
||||
undoButton.disabled = false
|
||||
}
|
||||
|
||||
function undo() {
|
||||
if (history.length) {
|
||||
const previousState = history.pop()
|
||||
previousState.box.value = previousState.value
|
||||
previousState.box.placeholder = previousState.placeholder
|
||||
refreshBox(previousState.box)
|
||||
if (history.length < 1) undoButton.disabled = true
|
||||
function onmouseenter(event) {
|
||||
if (sightCheckbox.checked){
|
||||
box = event.target
|
||||
box.andNeighbourhood.forEach(neighbour => {
|
||||
neighbour.parentElement.classList.add("table-active")
|
||||
})
|
||||
|
||||
box.neighbourhood.forEach(neighbour => {
|
||||
if (valueToInsert && neighbour.value == valueToInsert) {
|
||||
for (neighbour of [box, neighbour]) {
|
||||
neighbour.parentElement.classList.add("table-danger", "not-allowed")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function onmouseleave(event) {
|
||||
if (sightCheckbox.checked){
|
||||
box = event.target
|
||||
box.andNeighbourhood.forEach(neighbour => {
|
||||
neighbour.parentElement.classList.remove("table-active", "table-danger", "not-allowed")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function insert(radio) {
|
||||
if (radio.value && valueToInsert == radio.value) {
|
||||
radio.blur()
|
||||
insertRadio0.checked = true
|
||||
insert(0)
|
||||
} else {
|
||||
valueToInsert = radio.value
|
||||
grid.style.cursor = valueToInsert ? "pointer" : "text"
|
||||
highlight()
|
||||
}
|
||||
}
|
||||
|
||||
let penColor
|
||||
|
||||
function changeColor() {
|
||||
penColor = colorPickerInput.value
|
||||
colorPickerLabel.style.color = colorPickerInput.value
|
||||
}
|
||||
|
||||
function restart() {
|
||||
if (confirm("Effacer toutes les cases ?")) {
|
||||
boxes.filter(box => !box.disabled).forEach(box => {
|
||||
box.value = ""
|
||||
box.previousValue = ""
|
||||
box.placeholder = ""
|
||||
box.previousPlaceholder = ""
|
||||
box.setCustomValidity("")
|
||||
})
|
||||
let history = []
|
||||
undoButton.disabled = true
|
||||
boxes.forEach(searchCandidatesOf)
|
||||
refreshUI()
|
||||
restartButton.disabled = true
|
||||
location.hash = ""
|
||||
}
|
||||
}
|
||||
|
||||
function showSuggestion() {
|
||||
const easyBoxes = boxes.filter(box => box.value == "" && box.candidates.size == 1)
|
||||
function showHint() {
|
||||
if (easyBoxes.length) {
|
||||
shuffle(easyBoxes)[0].placeholder = "💡"
|
||||
} else {
|
||||
clearTimeout(suggestionTimer)
|
||||
suggestionTimer = null
|
||||
shuffle(easyBoxes)
|
||||
let box = easyBoxes.pop()
|
||||
box.placeholder = "💡"
|
||||
box.focus()
|
||||
return box
|
||||
}
|
||||
hintButton.disabled = true
|
||||
}
|
||||
|
||||
function oncontextmenu(event) {
|
||||
@ -317,34 +349,46 @@ function oncontextmenu(event) {
|
||||
Array.from(box.candidates).sort().forEach(candidate => {
|
||||
li = document.createElement("li")
|
||||
li.innerText = candidate
|
||||
li.onclick = function (event) {
|
||||
li.classList = "list-group-item list-group-item-action"
|
||||
li.onclick = function(e) {
|
||||
contextMenu.style.display = "none"
|
||||
box.onfocus()
|
||||
box.value = event.target.innerText
|
||||
box.oninput()
|
||||
box.onblur()
|
||||
valueToInsert = e.target.innerText
|
||||
grid.style.cursor = "pointer"
|
||||
document.getElementById("insertRadio" + valueToInsert).checked = true
|
||||
box.onclick()
|
||||
}
|
||||
li.oncontextmenu = function(e) {
|
||||
e.preventDefault()
|
||||
li.onclick(e)
|
||||
}
|
||||
contextMenu.appendChild(li)
|
||||
})
|
||||
} else {
|
||||
li = document.createElement("li")
|
||||
li.innerText = "Aucune possibilité !"
|
||||
li.classList.add("error")
|
||||
li.classList = "list-group-item list-group-item-action disabled"
|
||||
contextMenu.appendChild(li)
|
||||
}
|
||||
contextMenu.style.left = `${event.pageX}px`
|
||||
contextMenu.style.top = `${event.pageY}px`
|
||||
contextMenu.style.display = "block"
|
||||
|
||||
document.onclick = function(event) {
|
||||
contextMenu.style.display = "none"
|
||||
document.onclick = null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
document.onclick = function (event) {
|
||||
contextMenu.style.display = "none"
|
||||
}
|
||||
|
||||
document.onkeydown = function(event) {
|
||||
if (event.key == "Escape") {
|
||||
if (event.key == "Escape" && contextMenu.style.display == "block") {
|
||||
event.preventDefault()
|
||||
contextMenu.style.display = "none"
|
||||
}
|
||||
}
|
||||
|
||||
window.onbeforeunload = function(event) {
|
||||
saveGame()
|
||||
if (sightCheckbox.checked) localStorage["tool"] = "sight"
|
||||
else if (highlighterCheckbox.checked) localStorage["tool"] = "highlighter"
|
||||
}
|
211
sudoku.php
Normal file → Executable file
@ -1,135 +1,90 @@
|
||||
<?php
|
||||
require("classes.php");
|
||||
session_start();
|
||||
$currentGrid = strip_tags($_GET['grid']);
|
||||
$_SESSION["currentGrid"] = $currentGrid;
|
||||
|
||||
if (!isset($_SESSION[$currentGrid])) {
|
||||
$grid = new Grid();
|
||||
$grid->import($currentGrid);
|
||||
if ($grid->containsDuplicates()) {
|
||||
$warning = "Cette grille contient des doublons.";
|
||||
} else {
|
||||
switch($grid->countSolutions(2)) {
|
||||
case 0:
|
||||
$warning = "Cette grille n'a pas de solution.";
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
$warning = "Cette grille a plusieurs solutions.";
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang='fr' prefix="og: https://ogp.me/ns#">
|
||||
|
||||
<head>
|
||||
<meta charset='utf-8' />
|
||||
<meta name='viewport' content='width=device-width' />
|
||||
<title>Sudoku</title>
|
||||
<link rel='stylesheet' type='text/css' href='style.css' />
|
||||
<script src='sudoku.js'></script>
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=57" sizes="57x57">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=114" sizes="114x114">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=72" sizes="72x72">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=144" sizes="144x144">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=60" sizes="60x60">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=120" sizes="120x120">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=76" sizes="76x76">
|
||||
<link rel="apple-touch-icon" href="thumbnail.png?size=152" sizes="152x152">
|
||||
<link rel="icon" type="image/png" href="thumbnail.png?size=196" sizes="196x196">
|
||||
<link rel="icon" type="image/png" href="thumbnail.png?size=160" sizes="160x160">
|
||||
<link rel="icon" type="image/png" href="thumbnail.png?size=96" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="thumbnail.png?size=16" sizes="16x16">
|
||||
<link rel="icon" type="image/png" href="thumbnail.png?size=32" sizes="32x32">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta property="og:title" content="Sudoku"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:url" content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"]?>"/>
|
||||
<meta property="og:image" content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])?>/thumbnail.png?size=200"/>
|
||||
<meta property="og:image:width" content="200"/>
|
||||
<meta property="og:image:height" content="200"/>
|
||||
<meta property="og:description" content="Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9."/>
|
||||
<meta property="og:locale" content="fr_FR"/>
|
||||
<meta property="og:site_name" content="<?=$_SERVER["HTTP_HOST"]?>"/>
|
||||
<?php require_once("head.php") ?>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Sudoku</h1>
|
||||
</header>
|
||||
<form id='sudokuForm'>
|
||||
<table id='grid' class='grid'>
|
||||
<tbody>
|
||||
<?php
|
||||
for ($row = 0; $row < 9; $row++) {
|
||||
?>
|
||||
<tr>
|
||||
<?php
|
||||
for ($column = 0; $column < 9; $column++) {
|
||||
$value = $currentGrid[9*$row+$column];
|
||||
if ($value == UNKNOWN) {
|
||||
?>
|
||||
<td><input type='number' min='1' max='9' step='1' value='' title='Valeurs possibles [Clic-droit]'/></td>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<td><input type='number' min='1' max='9' step='1' value='<?=$value?>' disabled/></td>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<section class='tools'>
|
||||
<div id='insertRadioGroup' class='insertRadioGroup'>
|
||||
<?php
|
||||
for($value=1; $value<=9; $value++) {
|
||||
echo " <input type='radio' id='insertRadio$value' value='$value' name='insertRadioGroup' onclick='insert(this)' accesskey='$value'/>\n";
|
||||
echo " <label for='insertRadio$value' title='Insérer un $value'>$value</label>\n";
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<div>
|
||||
<input id='highlighterCheckbox' type="checkbox" onclick='highlight()'/>
|
||||
<label for='highlighterCheckbox' title='Surligner les cases interdites'><img src='img/highlighter.svg' alt='Surligneur'></label>
|
||||
<input type='radio' id='inkPenRadio' name='tool' onclick='grid.style.cursor = "url(img/ink-pen.svg) 2 22, auto"' checked/>
|
||||
<label for='inkPenRadio' title='Écrire au stylo'><img src='img/ink-pen.svg' alt='Stylo indélébile'/></label>
|
||||
<input type='radio' id='pencilRadio' name='tool' onclick='grid.style.cursor = "url(img/pencil.svg) 2 22, auto"'/>
|
||||
<label for='pencilRadio' title='Écrire au crayon'><img src='img/pencil.svg' alt='Crayon'/></label>
|
||||
<input type='radio' id='eraserRadio' name='tool' onclick='grid.style.cursor = "url(img/eraser.svg) 2 22, auto"'/>
|
||||
<label for='eraserRadio' title='Effacer une case'><img src='img/eraser.svg' alt='Gomme'/></label>
|
||||
<button type='button' class='warning' onclick='restart()' title='Recommencer'>
|
||||
<img src='img/restart.svg' alt='Recommencer'/>
|
||||
</button>
|
||||
<button id='undoButton' type='button' onclick='undo()' disabled title='Annuler' accesskey='z'>
|
||||
<img src='img/undo.svg' alt='Annuler'/>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<?php
|
||||
if (isset($warning))
|
||||
echo(" <strong>⚠️ $warning</strong><br/>\n");
|
||||
else
|
||||
echo(" Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9.\n")
|
||||
?>
|
||||
</section>
|
||||
<ul id="contextMenu" class="context-menu"></ul>
|
||||
<footer>
|
||||
<div id='links'>
|
||||
<a href=''>Lien vers cette grille</a><br/>
|
||||
<a href='.................................................................................'>Grille vierge</a><br/>
|
||||
<a href='.'>Nouvelle grille</a>
|
||||
</div>
|
||||
<div class='credits'>
|
||||
Icônes par <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> chez <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
|
||||
</div>
|
||||
</footer>
|
||||
<nav class="navbar mb-4">
|
||||
<h1 class="display-4 text-center m-auto">Sudoku</h1>
|
||||
</nav>
|
||||
<div class="row g-0">
|
||||
<main class="col-md-6 order-md-1">
|
||||
<div class="text-center m-auto" style="width: min-content;">
|
||||
<div class='d-flex justify-content-between mb-2'>
|
||||
<div class='btn-group'>
|
||||
<input type='radio' id='inkPenRadio' class='btn-check' name='penRadioGroup' checked />
|
||||
<label for='inkPenRadio' class='btn btn-primary' title='Écrire un chiffre'><i class="ri-ball-pen-fill"></i></label>
|
||||
<input type='radio' id='pencilRadio' class='btn-check' name='penRadioGroup' />
|
||||
<label for='pencilRadio' class='btn btn-primary' title='Prendre des notes'><i class="ri-pencil-fill"></i></label>
|
||||
<input type='radio' id='eraserRadio' class='btn-check' name='penRadioGroup' />
|
||||
<label for='eraserRadio' class='btn btn-primary' title='Effacer une case'><i class="ri-eraser-fill"></i></label>
|
||||
</div>
|
||||
<input type="color" class="btn-check" id="colorPickerInput" title="Changer la couleur" oninput="changeColor()"/>
|
||||
<label id="colorPickerLabel" for="colorPickerInput" class="btn btn-primary" title="Changer de couleur"><i class="ri-palette-fill"></i></label>
|
||||
<div class='btn-group'>
|
||||
<input type='checkbox' id='sightCheckbox' class='btn-check' onclick='highlighterCheckbox.checked = false; highlight()' />
|
||||
<label for='sightCheckbox' class='btn btn-info' title='Surligner la ligne, la colonne et la région de la case survolée'><i class="ri-focus-3-line"></i></label>
|
||||
<input type='checkbox' id='highlighterCheckbox' class='btn-check' onclick='sightCheckbox.checked = false; highlight()' />
|
||||
<label for='highlighterCheckbox' class='btn btn-info' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'><i class="ri-mark-pen-fill"></i></label>
|
||||
</div>
|
||||
<button id="hintButton" type="button" class='btn btn-info' onclick="showHint()" title="Montrer une case avec une seule possibilité" accesskey="H" disabled=""><i class="ri-lightbulb-line"></i></button>
|
||||
<a id='restartLink' class='btn btn-primary disabled' href="" title='Recommencer'><i class="ri-restart-line"></i></a>
|
||||
<button id='undoButton' type='button' class='btn btn-primary' onclick='window.history.back()' disabled title='Annuler' accesskey='Z'><i class="ri-arrow-go-back-fill"></i></button>
|
||||
</div>
|
||||
<form id='sudokuForm' class='needs-validation' novalidate>
|
||||
<table id='grid' class='table mb-2'>
|
||||
<tbody>
|
||||
<?php for ($row = 0; $row < 81; $row += 9): ?>
|
||||
<tr class="input-group d-inline-block w-auto">
|
||||
<?php for ($column = 0; $column < 9; $column++): $value = $currentGrid[$row+$column]; ?>
|
||||
<?php if ($value == UNKNOWN): ?>
|
||||
<td><input type='number' min='1' max='9' step='1' value='' class='form-control' /></td>
|
||||
<?php else: ?>
|
||||
<td><input type='number' min='1' max='9' step='1' value='<?=$value?>' class='form-control' disabled /></td>
|
||||
<?php endif ?>
|
||||
<?php endfor?>
|
||||
</tr>
|
||||
<?php endfor?>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
<div class='d-flex mb-4'>
|
||||
<div id='insertRadioGroup' class='radioGroup btn-group flex-fill'>
|
||||
<input type='radio' class='btn-check' id='insertRadio0' value='' name='insertRadioGroup' onclick='insert(this)' accesskey='0' checked /><label for='insertRadio0' class='btn btn-primary' title='Clavier'><i class="ri-input-cursor-move"></i></label>
|
||||
<?php for($value=1; $value<=9; $value++): ?>
|
||||
<input type='radio' class='btn-check' id='insertRadio<?=$value?>' value='<?=$value?>' name='insertRadioGroup' onclick='insert(this)' accesskey='<?=$value?>' disabled />
|
||||
<label for='insertRadio<?=$value?>' class='btn btn-primary' title='Insérer un <?=$value?>'><?=$value?></label>
|
||||
<?php endfor ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class='mb-3'>
|
||||
<?php if (isset($warning)): ?>
|
||||
<strong>⚠️ <?=$warning?> ⚠️</strong><br/>
|
||||
<?php else: ?>
|
||||
Remplissez la grille de sorte que chaque ligne, colonne et région (carré de 3×3 cases) contienne tous les chiffres de 1 à 9.
|
||||
<?php endif?>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<aside class="col-md-3 text-center text-md-start">
|
||||
<div class="d-flex flex-column flex-shrink-0 p-3">
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li><a href="." class="nav-link link-body-emphasis">Nouvelle grille</a></li>
|
||||
<li><a href="" class="nav-link link-body-emphasis">Lien vers cette grille</a></li>
|
||||
<li><a href="?---------------------------------------------------------------------------------" class="nav-link link-body-emphasis">Grille vierge</a></li>
|
||||
<li><a id="fixGridLink" href="" class="nav-link link-body-emphasis">Figer la grille</a></li>
|
||||
<li><a href="https://git.malingrey.fr/adrien/Sudoku" class="nav-link link-body-emphasis">Code source</a></li>
|
||||
<li><a href=".." class="nav-link link-body-emphasis">Autres jeux</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
<ul id='contextMenu' class='context-menu modal-content shadow list-group w-auto position-absolute'></ul>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
<script src='sudoku.js' defer></script>
|
||||
<script>navigator?.serviceWorker.register('service-worker.js')</script>
|
||||
</body>
|
||||
|
||||
</html>
|
63
thumbnail.png.php → thumbnail.php
Normal file → Executable file
@ -1,20 +1,23 @@
|
||||
<?php
|
||||
require("classes.php");
|
||||
session_start();
|
||||
if ($_SESSION["currentGrid"])
|
||||
$currentGrid = $_SESSION["currentGrid"];
|
||||
if (isset($_GET["grid"]) && preg_match("/^[1-9-]{81}$/", $_GET["grid"]))
|
||||
$currentGrid = $_GET["grid"];
|
||||
else
|
||||
$currentGrid = ".528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...w";
|
||||
$currentGrid = "-528-3----4-9-1---39-562------73-129---1-64-7---42-3656-13-5---28-6-4---4-5287---";
|
||||
header ("Content-type: image/png");
|
||||
$size = (int) $_GET['size'];
|
||||
if (isset($_GET['size']))
|
||||
$size = (int) $_GET['size'];
|
||||
else
|
||||
$size = 196;
|
||||
|
||||
$thumbnail = imagecreate($size, $size);
|
||||
$transparent = imagecolorallocate($thumbnail, 1, 1, 1);
|
||||
imagecolortransparent($thumbnail, $transparent);
|
||||
$black = imagecolorallocate($thumbnail, 0, 0, 0);
|
||||
$grey = imagecolorallocate($thumbnail, 128, 128, 128);
|
||||
$blue = imagecolorallocate($thumbnail, 102, 102, 255);
|
||||
$white = imagecolorallocate($thumbnail, 255, 255, 255);
|
||||
$darkerBorder = imagecolorallocate($thumbnail, 150, 155, 160);
|
||||
$lighterBorder = imagecolorallocate($thumbnail, 210, 225, 230);
|
||||
$emptyBoxBC = imagecolorallocate($thumbnail, 255, 255, 255);
|
||||
$clueBC = imagecolorallocate($thumbnail, 255, 255, 255);
|
||||
$clueFC = imagecolorallocate($thumbnail, 150, 155, 160);
|
||||
|
||||
if ($size <= 36) {
|
||||
$boxSize = floor(($size-4) / 9);
|
||||
@ -24,19 +27,19 @@
|
||||
$lineStart = $start + 1;
|
||||
$lineEnd = $end - 2;
|
||||
for ($i = $start; $i < $end; $i += 3*$boxSize + 1) {
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $black);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $black);
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $darkerBorder);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $darkerBorder);
|
||||
}
|
||||
$x = $start;
|
||||
$y = $start;
|
||||
$boxSizeMinusOne = $boxSize - 1;
|
||||
foreach(str_split($_SESSION["currentGrid"]) as $i => $value) {
|
||||
foreach(str_split($currentGrid) as $i => $value) {
|
||||
if ($i % 3 == 0) $x++;
|
||||
if ($i % 27 == 0) $y++;
|
||||
if ($value == UNKNOWN) {
|
||||
$bgColor = $white;
|
||||
$bgColor = $emptyBoxBC;
|
||||
} else {
|
||||
$bgColor = $blue;
|
||||
$bgColor = $clueFC;
|
||||
}
|
||||
imagefilledrectangle($thumbnail, $x, $y, $x+$boxSizeMinusOne, $y+$boxSizeMinusOne, $bgColor);
|
||||
$x += $boxSize;
|
||||
@ -53,21 +56,21 @@
|
||||
$lineStart = $start + 1;
|
||||
$lineEnd = $end - 2;
|
||||
for ($i = $start + $boxSize; $i < $end - $boxSize; $i += $boxSize) {
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $grey);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $grey);
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $lighterBorder);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $lighterBorder);
|
||||
}
|
||||
for ($i = $start; $i < $end; $i += 3*$boxSize) {
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $black);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $black);
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $darkerBorder);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $darkerBorder);
|
||||
}
|
||||
$x = $start + 1;
|
||||
$y = $start + 1;
|
||||
$boxSizeMinusTwo = $boxSize - 2;
|
||||
foreach(str_split($_SESSION["currentGrid"]) as $i => $value) {
|
||||
foreach(str_split($currentGrid) as $i => $value) {
|
||||
if ($value == UNKNOWN) {
|
||||
$bgColor = $white;
|
||||
$bgColor = $emptyBoxBC;
|
||||
} else {
|
||||
$bgColor = $blue;
|
||||
$bgColor = $clueFC;
|
||||
}
|
||||
imagefilledrectangle($thumbnail, $x, $y, $x+$boxSizeMinusTwo, $y+$boxSizeMinusTwo, $bgColor);
|
||||
$x += $boxSize;
|
||||
@ -86,26 +89,26 @@
|
||||
$fontSize = floor($boxSize/2) - 4;
|
||||
$fdx = floor(($boxSize - imagefontwidth($fontSize)) / 2);
|
||||
$fdy = ceil(($boxSize - imagefontheight($fontSize)) / 2) - 1;
|
||||
$fontColor = $white;
|
||||
$fontColor = $emptyBoxBC;
|
||||
for ($i = $start + $boxSize; $i < $end - $boxSize; $i += $boxSize) {
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $grey);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $grey);
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $lighterBorder);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $lighterBorder);
|
||||
}
|
||||
for ($i = $start; $i < $end; $i += 3*$boxSize) {
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $black);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $black);
|
||||
ImageLine($thumbnail, $lineStart, $i, $lineEnd, $i, $darkerBorder);
|
||||
ImageLine($thumbnail, $i, $lineStart, $i, $lineEnd, $darkerBorder);
|
||||
}
|
||||
$x = $start + 1;
|
||||
$y = $start + 1;
|
||||
$boxSizeMinusTwo = $boxSize - 2;
|
||||
foreach(str_split($_SESSION["currentGrid"]) as $i => $value) {
|
||||
foreach(str_split($currentGrid) as $i => $value) {
|
||||
if ($value == UNKNOWN) {
|
||||
$bgColor = $white;
|
||||
$bgColor = $emptyBoxBC;
|
||||
} else {
|
||||
$bgColor = $blue;
|
||||
$bgColor = $clueBC;
|
||||
}
|
||||
imagefilledrectangle($thumbnail, $x, $y, $x+$boxSizeMinusTwo, $y+$boxSizeMinusTwo, $bgColor);
|
||||
if ($value != UNKNOWN) imagestring($thumbnail, $fontSize, $x + $fdx, $y + $fdy, $value, $fontColor);
|
||||
if ($value != UNKNOWN) imagestring($thumbnail, $fontSize, $x + $fdx, $y + $fdy, $value, $clueFC);
|
||||
$x += $boxSize;
|
||||
if ($i % 9 == 8) {
|
||||
$y += $boxSize;
|
BIN
thumbnail.png
Executable file
After Width: | Height: | Size: 15 KiB |