Compare commits
57 Commits
60b5f74e94
...
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 |
29
400.php
Normal file → Executable file
29
400.php
Normal file → Executable file
@ -6,17 +6,22 @@
|
|||||||
<title>Requête incorrecte</title>
|
<title>Requête incorrecte</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<nav class="navbar mb-4">
|
||||||
<h1>Requête incorrecte</h1>
|
<h1 class="display-4 text-center m-auto">Sudoku</h1>
|
||||||
</header>
|
</nav>
|
||||||
L'adresse URL doit être de la forme :<br/>
|
<main class="container my-4">
|
||||||
<?=$dirUrl?>/?<em>grille</em><br/>
|
<header>
|
||||||
<em>grille</em> étant une suite de 81 caractères représentant la grille de gauche à droite puis de haut en bas, soit :
|
<h1 class="mb-4">Requête incorrecte</h1>
|
||||||
<ul>
|
</header>
|
||||||
<li>un chiffre entre 1 et 9 pour les cases connues</li>
|
L'adresse URL doit être de la forme :<br/>
|
||||||
<li>un point pour les case vides</li>
|
<?=$dirUrl?>/?<em>grille</em><br/>
|
||||||
</ul>
|
<em>grille</em> étant une suite de 81 caractères représentant la grille de gauche à droite puis de haut en bas, soit :
|
||||||
Exemple :<br/>
|
<ul>
|
||||||
<a href='<?=$newGridUrl?>'><?=$newGridUrl?></a>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Sudoku
|
||||||
|
|
||||||
|
Web sudoku assistant
|
||||||
|
|
||||||
|

|
27
classes.php
Normal file → Executable file
27
classes.php
Normal file → Executable file
@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
const UNKNOWN = ".";
|
const UNKNOWN = "-";
|
||||||
|
|
||||||
$validGrids = array();
|
$validGrids = array();
|
||||||
|
|
||||||
@ -27,15 +27,19 @@
|
|||||||
|
|
||||||
class Box {
|
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) {
|
function __construct($rowId, $columnId, $regionId) {
|
||||||
$this->value = UNKNOWN;
|
|
||||||
$this->rowId = $rowId;
|
$this->rowId = $rowId;
|
||||||
$this->columnId = $columnId;
|
$this->columnId = $columnId;
|
||||||
$this->regionId = $regionId;
|
$this->regionId = $regionId;
|
||||||
$this->candidates = $this->values;
|
$this->candidates = $this->values;
|
||||||
$this->candidateRemoved = array();
|
|
||||||
$this->neighbourhood = array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchCandidates() {
|
function searchCandidates() {
|
||||||
@ -48,8 +52,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Grid {
|
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->rows = array_fill(0, 9, array());
|
||||||
$this->columns = array_fill(0, 9, array());
|
$this->columns = array_fill(0, 9, array());
|
||||||
$this->regions = array_fill(0, 9, array());
|
$this->regions = array_fill(0, 9, array());
|
||||||
@ -74,6 +83,12 @@
|
|||||||
if ($box != $neighbour && !in_array($neighbour, $box->neighbourhood))
|
if ($box != $neighbour && !in_array($neighbour, $box->neighbourhood))
|
||||||
$box->neighbourhood[] = $neighbour;
|
$box->neighbourhood[] = $neighbour;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($gridStr) {
|
||||||
|
$this->import($gridStr);
|
||||||
|
} else {
|
||||||
|
$this->generate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function import($gridStr) {
|
function import($gridStr) {
|
||||||
|
11
css/bootstrap-dark.min.css
vendored
11
css/bootstrap-dark.min.css
vendored
File diff suppressed because one or more lines are too long
2018
css/bootstrap-icons.css
vendored
2018
css/bootstrap-icons.css
vendored
File diff suppressed because it is too large
Load Diff
10
css/bootstrap-night.min.css
vendored
10
css/bootstrap-night.min.css
vendored
File diff suppressed because one or more lines are too long
6
css/bootstrap.min.css
vendored
6
css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
0
favicon.png
Normal file → Executable file
0
favicon.png
Normal file → Executable file
Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 542 B |
Binary file not shown.
Binary file not shown.
68
head.php
Normal file → Executable file
68
head.php
Normal file → Executable file
@ -1,36 +1,32 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Sudoku</title>
|
<title>Sudoku</title><meta property="og:title" content="Sudoku" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta property="og:type" content="game" />
|
||||||
<link href="css/bootstrap-dark.min.css" rel="stylesheet" type="text/css" title="Automatique" />
|
<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 href="css/bootstrap.min.css" rel="alternate stylesheet" type="text/css" title="Clair">
|
<link rel="canonical" href="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])?>" />
|
||||||
<link href="css/bootstrap-night.min.css" rel="alternate stylesheet" type="text/css" title="Sombre">
|
<meta property="og:url" content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"]?>" />
|
||||||
<link rel="stylesheet" type="text/css" href="css/bootstrap-icons.css" />
|
<meta property="og:image" content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])?>/thumbnail.php?size=200&grid=<?=$currentGrid?>" />
|
||||||
<link rel="stylesheet" type="text/css" href="css/style.css" />
|
<meta property="og:image:width" content="200" />
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=57" sizes="57x57">
|
<meta property="og:image:height" content="200" />
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=114" sizes="114x114">
|
<meta name="Language" CONTENT="fr" /><meta property="og:locale" content="fr_FR" />
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=72" sizes="72x72">
|
<meta property="og:site_name" content="<?=$_SERVER["HTTP_HOST"]?>" />
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=144" sizes="144x144">
|
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=60" sizes="60x60">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=120" sizes="120x120">
|
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=76" sizes="76x76">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-dark.min.css" rel="stylesheet">
|
||||||
<link rel="apple-touch-icon" href="thumbnail.php?size=152" sizes="152x152">
|
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.2.0/fonts/remixicon.css" rel="stylesheet">
|
||||||
<link rel="icon" type="image/png" href="thumbnail.php?size=196" sizes="196x196">
|
<link href="style.css" rel="stylesheet" type="text/css" />
|
||||||
<link rel="icon" type="image/png" href="thumbnail.php?size=160" sizes="160x160">
|
|
||||||
<link rel="icon" type="image/png" href="thumbnail.php?size=96" sizes="96x96">
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=196" sizes="196x196" rel="icon" type="image/png">
|
||||||
<link rel="icon" type="image/png" href="thumbnail.php?size=16" sizes="16x16">
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=160" sizes="160x160" rel="icon" type="image/png">
|
||||||
<link rel="icon" type="image/png" href="thumbnail.php?size=32" sizes="32x32">
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=96" sizes="96x96" rel="icon" type="image/png">
|
||||||
<link rel="manifest" href="manifest.php">
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=16" sizes="16x16" rel="icon" type="image/png">
|
||||||
<meta property="og:title" content="Sudoku" />
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=32" sizes="32x32" rel="icon" type="image/png">
|
||||||
<meta property="og:type" content="website" />
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=152" sizes="152x152" rel="apple-touch-icon">
|
||||||
<meta property="og:url"
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=144" sizes="144x144" rel="apple-touch-icon">
|
||||||
content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"]?>" />
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=120" sizes="120x120" rel="apple-touch-icon">
|
||||||
<meta property="og:image"
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=114" sizes="114x114" rel="apple-touch-icon">
|
||||||
content="<?=$_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"])?>/thumbnail.php?size=200" />
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=57" sizes="57x57" rel="apple-touch-icon">
|
||||||
<meta property="og:image:width" content="200" />
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=72" sizes="72x72" rel="apple-touch-icon">
|
||||||
<meta property="og:image:height" content="200" />
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=60" sizes="60x60" rel="apple-touch-icon">
|
||||||
<meta property="og:description"
|
<link href="thumbnail.php?grid=<?=$currentGrid?>&size=76" sizes="76x76" rel="apple-touch-icon">
|
||||||
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 href="manifest.php?grid=<?=$currentGrid?>" rel="manifest">
|
||||||
<meta property="og:locale" content="fr_FR" />
|
|
||||||
<meta property="og:site_name" content="<?=$_SERVER["HTTP_HOST"]?>" />
|
|
||||||
<script src='js/sudoku.js'></script>
|
|
||||||
<script src="js/bootstrap.bundle.min.js" integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" crossorigin="anonymous"></script>
|
|
||||||
|
57
index.php
Normal file → Executable file
57
index.php
Normal file → Executable file
@ -1,40 +1,45 @@
|
|||||||
<?php
|
<?php
|
||||||
require("classes.php");
|
require("classes.php");
|
||||||
session_start();
|
|
||||||
$fullUrl = $_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"];
|
$fullUrl = $_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].$_SERVER["DOCUMENT_URI"];
|
||||||
$dirUrl = dirname($fullUrl);
|
$dirUrl = dirname($fullUrl);
|
||||||
$currentGrid = strip_tags($_SERVER['QUERY_STRING']);
|
$currentGrid = strip_tags($_SERVER['QUERY_STRING']);
|
||||||
|
|
||||||
if (preg_match("/^[1-9.]{81}$/", $currentGrid)) {
|
if (preg_match("/^[1-9-]{81}$/", $currentGrid)) {
|
||||||
if (!isset($_SESSION[$currentGrid]) || $_SESSION[$currentGrid] != "checked") {
|
session_id($currentGrid);
|
||||||
$grid = new Grid();
|
session_start(["use_cookies" => false]);
|
||||||
$grid->import($currentGrid);
|
|
||||||
if ($grid->containsDuplicates()) {
|
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.";
|
$warning = "Cette grille contient des doublons.";
|
||||||
} else {
|
break;
|
||||||
switch($grid->countSolutions(2)) {
|
case 0:
|
||||||
case 0:
|
$warning = "Cette grille n'a pas de solution.";
|
||||||
$warning = "Cette grille n'a pas de solution.";
|
break;
|
||||||
break;
|
case 1:
|
||||||
case 1:
|
break;
|
||||||
$validGrids[] = $currentGrid;
|
default:
|
||||||
break;
|
$warning = "Cette grille a plusieurs solutions.";
|
||||||
default:
|
|
||||||
$warning = "Cette grille a plusieurs solutions.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
require("sudoku.php");
|
require("sudoku.php");
|
||||||
} else {
|
} else {
|
||||||
$grid = new Grid();
|
if ($currentGrid) {
|
||||||
$grid->generate();
|
|
||||||
$gridAsString = $grid->toString();
|
|
||||||
$newGridUrl = "$dirUrl/?$gridAsString";
|
|
||||||
$_SESSION[$gridAsString] = "checked";
|
|
||||||
if (!$currentGrid) {
|
|
||||||
header("Location: $newGridUrl");
|
|
||||||
} else {
|
|
||||||
require("400.php");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
7
js/bootstrap.bundle.min.js
vendored
7
js/bootstrap.bundle.min.js
vendored
File diff suppressed because one or more lines are too long
53
manifest.php
Normal file → Executable file
53
manifest.php
Normal file → Executable file
@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
if (isset($_GET["grid"]))
|
||||||
if (isset($_SESSION["currentGrid"]))
|
$currentGrid = $_GET["grid"];
|
||||||
$currentGrid = $_SESSION["currentGrid"];
|
|
||||||
else
|
else
|
||||||
$currentGrid = ".";
|
$currentGrid = ".";
|
||||||
?>
|
?>
|
||||||
@ -10,27 +9,27 @@
|
|||||||
"name": "Sudoku",
|
"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.",
|
"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": [{
|
"icons": [{
|
||||||
"src": "thumbnail.php?size=48",
|
"src": "thumbnail.php?size=48&grid=<?=$currentGrid?>",
|
||||||
"sizes": "48x48",
|
"sizes": "48x48",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=72",
|
"src": "thumbnail.php?size=72&grid=<?=$currentGrid?>",
|
||||||
"sizes": "72x72",
|
"sizes": "72x72",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=96",
|
"src": "thumbnail.php?size=96&grid=<?=$currentGrid?>",
|
||||||
"sizes": "96x96",
|
"sizes": "96x96",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=144",
|
"src": "thumbnail.php?size=144&grid=<?=$currentGrid?>",
|
||||||
"sizes": "144x144",
|
"sizes": "144x144",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=168",
|
"src": "thumbnail.php?size=168&grid=<?=$currentGrid?>",
|
||||||
"sizes": "168x168",
|
"sizes": "168x168",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=192",
|
"src": "thumbnail.php?size=192&grid=<?=$currentGrid?>",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}],
|
}],
|
||||||
@ -47,27 +46,27 @@
|
|||||||
"description": "Continuer cette grille de sudoku",
|
"description": "Continuer cette grille de sudoku",
|
||||||
"url": "<?=$currentGrid?>",
|
"url": "<?=$currentGrid?>",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
"src": "thumbnail.php?size=48",
|
"src": "thumbnail.php?size=48&grid=<?=$currentGrid?>",
|
||||||
"sizes": "48x48",
|
"sizes": "48x48",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=72",
|
"src": "thumbnail.php?size=72&grid=<?=$currentGrid?>",
|
||||||
"sizes": "72x72",
|
"sizes": "72x72",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=96",
|
"src": "thumbnail.php?size=96&grid=<?=$currentGrid?>",
|
||||||
"sizes": "96x96",
|
"sizes": "96x96",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=144",
|
"src": "thumbnail.php?size=144&grid=<?=$currentGrid?>",
|
||||||
"sizes": "144x144",
|
"sizes": "144x144",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=168",
|
"src": "thumbnail.php?size=168&grid=<?=$currentGrid?>",
|
||||||
"sizes": "168x168",
|
"sizes": "168x168",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=192",
|
"src": "thumbnail.php?size=192&grid=<?=$currentGrid?>",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}]
|
}]
|
||||||
@ -78,27 +77,27 @@
|
|||||||
"description": "Grille de sudoku vierge",
|
"description": "Grille de sudoku vierge",
|
||||||
"url": ".................................................................................",
|
"url": ".................................................................................",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
"src": "thumbnail.php?size=48",
|
"src": "thumbnail.php?size=48&grid=.................................................................................",
|
||||||
"sizes": "48x48",
|
"sizes": "48x48",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=72",
|
"src": "thumbnail.php?size=72&grid=.................................................................................",
|
||||||
"sizes": "72x72",
|
"sizes": "72x72",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=96",
|
"src": "thumbnail.php?size=96&grid=.................................................................................",
|
||||||
"sizes": "96x96",
|
"sizes": "96x96",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=144",
|
"src": "thumbnail.php?size=144&grid=.................................................................................",
|
||||||
"sizes": "144x144",
|
"sizes": "144x144",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=168",
|
"src": "thumbnail.php?size=168&grid=.................................................................................",
|
||||||
"sizes": "168x168",
|
"sizes": "168x168",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?size=192",
|
"src": "thumbnail.php?size=192&grid=.................................................................................",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}]
|
}]
|
||||||
@ -109,27 +108,27 @@
|
|||||||
"description": "Nouvelle grille de sudoku",
|
"description": "Nouvelle grille de sudoku",
|
||||||
"url": ".",
|
"url": ".",
|
||||||
"icons": [{
|
"icons": [{
|
||||||
"src": "thumbnail.php?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",
|
"sizes": "48x48",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?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",
|
"sizes": "72x72",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?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",
|
"sizes": "96x96",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?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",
|
"sizes": "144x144",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?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",
|
"sizes": "168x168",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}, {
|
}, {
|
||||||
"src": "thumbnail.php?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",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}]
|
}]
|
||||||
|
10
service-worker.php → service-worker.js
Normal file → Executable file
10
service-worker.php → service-worker.js
Normal file → Executable file
@ -1,11 +1,3 @@
|
|||||||
<?php
|
|
||||||
session_start();
|
|
||||||
if (isset($_SESSION["currentGrid"]))
|
|
||||||
$currentGrid = $_SESSION["currentGrid"];
|
|
||||||
else
|
|
||||||
$currentGrid = ".";
|
|
||||||
header ("Content-type: application/javascript");
|
|
||||||
?>
|
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2019, 2020 Google LLC. All Rights Reserved.
|
Copyright 2015, 2019, 2020 Google LLC. All Rights Reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
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 OFFLINE_VERSION = 1;
|
||||||
const CACHE_NAME = "offline";
|
const CACHE_NAME = "offline";
|
||||||
// Customize this with a different URL if needed.
|
// Customize this with a different URL if needed.
|
||||||
const OFFLINE_URL = "<?=$currentGrid?>";
|
const OFFLINE_URL = ".";
|
||||||
|
|
||||||
self.addEventListener("install", (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
54
css/style.css → style.css
Normal file → Executable file
54
css/style.css → style.css
Normal file → Executable file
@ -1,8 +1,3 @@
|
|||||||
body {
|
|
||||||
width: min-content;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-outer-spin-button,
|
input[type="number"]::-webkit-outer-spin-button,
|
||||||
input::-webkit-inner-spin-button {
|
input::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none !important;
|
-webkit-appearance: none !important;
|
||||||
@ -19,21 +14,31 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table input {
|
table input {
|
||||||
width: 2.7rem !important;
|
width: 2.5rem !important;
|
||||||
height: 2.7rem !important;
|
height: 2.5rem !important;
|
||||||
font-size: 1.3rem !important;
|
font-size: 1.3rem !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
-moz-appearance: textfield !important;
|
appearance: textfield !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table td.table-primary input,
|
table td.table-primary input,
|
||||||
table td.table-active input,
|
table td.table-active input,
|
||||||
table.table-success input,
|
table.table-success input,
|
||||||
|
td.table-danger input:disabled,
|
||||||
table input:not([disabled]) {
|
table input:not([disabled]) {
|
||||||
background: transparent !important;
|
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 {
|
tr:nth-child(3n+1) td input {
|
||||||
@ -87,10 +92,14 @@ tr:last-child td:last-child input {
|
|||||||
td {
|
td {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
transition: background-color .4s, box-shadow .4s !important;
|
|
||||||
border: 0 !important;
|
border: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
table input {
|
||||||
|
transition: background-color .4s, box-shadow .4s !important;
|
||||||
|
}
|
||||||
|
|
||||||
.context-menu li {
|
.context-menu li {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
@ -99,7 +108,7 @@ td {
|
|||||||
cursor: inherit !important;
|
cursor: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-danger {
|
.not-allowed {
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,10 +118,15 @@ table {
|
|||||||
|
|
||||||
button,
|
button,
|
||||||
label {
|
label {
|
||||||
padding: .375rem .7rem !important;
|
/*! padding: .375rem .65rem !important; */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button i,
|
||||||
|
label i {
|
||||||
|
margin: 0 -.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
button:disabled,
|
button:disabled,
|
||||||
:disabled+label {
|
:disabled+label {
|
||||||
cursor: not-allowed !important;
|
cursor: not-allowed !important;
|
||||||
@ -122,16 +136,26 @@ table input:enabled {
|
|||||||
cursor: inherit;
|
cursor: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pencil {
|
.pencil {
|
||||||
color: var(--bs-secondary-color) !important;
|
color: var(--bs-secondary-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#colorPickerInput{
|
||||||
|
width: 2.3rem;
|
||||||
|
height: auto;
|
||||||
|
padding: .375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#colorPickerLabel {
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme:dark) {
|
@media (prefers-color-scheme:dark) {
|
||||||
.pencil {
|
.pencil {
|
||||||
color: #5a5a5a !important;
|
color: #5a5a5a !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
@ -1,16 +1,18 @@
|
|||||||
const VALUES = "123456789"
|
const VALUES = "123456789"
|
||||||
const UNKNOWN = '.'
|
const UNKNOWN = '.'
|
||||||
const SUGESTION_DELAY = 60000 //ms
|
|
||||||
|
|
||||||
let boxes = []
|
let boxes = []
|
||||||
let rows = Array.from(Array(9), x => [])
|
let rows = Array.from(Array(9), x => [])
|
||||||
let columns = Array.from(Array(9), x => [])
|
let columns = Array.from(Array(9), x => [])
|
||||||
let regions = Array.from(Array(9), x => [])
|
let regions = Array.from(Array(9), x => [])
|
||||||
|
let areaNames = {
|
||||||
|
ligne: rows,
|
||||||
|
colonne: columns,
|
||||||
|
région: regions,
|
||||||
|
}
|
||||||
let valueToInsert = ""
|
let valueToInsert = ""
|
||||||
let history = []
|
let easyBoxes = []
|
||||||
let accessKeyModifiers = "AccessKey+"
|
let insertRadios = []
|
||||||
let easyBoxes = []
|
|
||||||
let insertRadios = []
|
|
||||||
|
|
||||||
function shuffle(iterable) {
|
function shuffle(iterable) {
|
||||||
array = Array.from(iterable)
|
array = Array.from(iterable)
|
||||||
@ -33,19 +35,17 @@ window.onload = function() {
|
|||||||
for (let box of row.getElementsByTagName('input')) {
|
for (let 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.disabled) {
|
if (!box.disabled) {
|
||||||
box.onfocus = onfocus
|
box.onfocus = onfocus
|
||||||
box.oninput = oninput
|
box.oninput = oninput
|
||||||
box.onblur = onblur
|
box.onblur = onblur
|
||||||
box.onclick = onclick
|
box.onclick = onclick
|
||||||
box.onmouseenter = onmouseenter
|
box.onmouseenter = onmouseenter
|
||||||
box.onmouseleave = onmouseleave
|
box.onmouseleave = onmouseleave
|
||||||
box.previousValue = ""
|
|
||||||
box.previousPlaceholder = ""
|
|
||||||
}
|
}
|
||||||
box.oncontextmenu = oncontextmenu
|
box.oncontextmenu = oncontextmenu
|
||||||
box.rowId = rowId
|
box.rowId = rowId
|
||||||
box.columnId = columnId
|
box.columnId = columnId
|
||||||
box.regionId = regionId
|
box.regionId = regionId
|
||||||
boxes.push(box)
|
boxes.push(box)
|
||||||
rows[rowId].push(box)
|
rows[rowId].push(box)
|
||||||
columns[columnId].push(box)
|
columns[columnId].push(box)
|
||||||
@ -55,8 +55,10 @@ window.onload = function() {
|
|||||||
rowId++
|
rowId++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localStorage["sightCheckbox.checked"] == "true") sightCheckbox.checked = true
|
if (localStorage["tool"] == "sight") sightCheckbox.checked = true
|
||||||
if (localStorage["highlighterCheckbox.checked"] == "true") highlighterCheckbox.checked = true
|
else if (localStorage["tool"] == "highlighter") highlighterCheckbox.checked = true
|
||||||
|
|
||||||
|
colorPickerInput.value = window.getComputedStyle(grid).getPropertyValue("--bs-body-color")
|
||||||
|
|
||||||
boxes.forEach(box => {
|
boxes.forEach(box => {
|
||||||
box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId]))
|
box.neighbourhood = new Set(rows[box.rowId].concat(columns[box.columnId]).concat(regions[box.regionId]))
|
||||||
@ -65,47 +67,49 @@ window.onload = function() {
|
|||||||
box.neighbourhood = Array.from(box.neighbourhood)
|
box.neighbourhood = Array.from(box.neighbourhood)
|
||||||
})
|
})
|
||||||
|
|
||||||
insertRadios = Array.from(insertRadioGroup.getElementsByTagName("input"))
|
insertRadios = Array.from(insertRadioGroup.getElementsByTagName("input")).slice(1)
|
||||||
insertRadios.pop()
|
|
||||||
|
|
||||||
for (label of document.getElementsByTagName("label")) {
|
for (label of document.getElementsByTagName("label")) {
|
||||||
label.control.label = label
|
label.control.label = label
|
||||||
}
|
}
|
||||||
|
let accessKeyModifiers = (/Win/.test(navigator.userAgent) || /Linux/.test(navigator.userAgent)) ? "Alt+Maj+"
|
||||||
if (/Win/.test(navigator.platform) || /Linux/.test(navigator.platform)) accessKeyModifiers = "Alt+Maj+"
|
: (/Mac/.test(navigator.userAgent)) ? "⌃⌥"
|
||||||
else if (/Mac/.test(navigator.platform)) accessKeyModifiers = "⌃⌥"
|
: "AccessKey+"
|
||||||
for (node of document.querySelectorAll("*[accesskey]")) {
|
for (node of document.querySelectorAll("*[accesskey]")) {
|
||||||
shortcut = ` [${node.accessKeyLabel||(accessKeyModifiers+node.accessKey)}]`
|
shortcut = ` [${node.accessKeyLabel||(accessKeyModifiers+node.accessKey)}]`
|
||||||
if (node.title) node.title += shortcut
|
if (node.title) node.title += shortcut
|
||||||
else if (node.label) node.label.title += shortcut
|
else if (node.label) node.label.title += shortcut
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSavedGame()
|
loadGame(history.state)
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
navigator.serviceWorker.register(`service-worker.php?location=${location.pathname}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSavedGame() {
|
window.onpopstate = (event) => loadGame(event.state)
|
||||||
const savedGame = location.hash.slice(1)
|
|
||||||
if (savedGame.match(/[1-9.]{81}/)) {
|
function loadGame(state) {
|
||||||
|
if (state) {
|
||||||
boxes.forEach((box, i) => {
|
boxes.forEach((box, i) => {
|
||||||
if (!box.disabled && savedGame[i] != UNKNOWN) {
|
if (!box.disabled) {
|
||||||
box.value = savedGame[i]
|
box.value = state.boxesValues[i]
|
||||||
box.previousValue = savedGame[i]
|
box.placeholder = state.boxesPlaceholders[i]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
restartButton.disabled = false
|
restartLink.classList.remove("disabled")
|
||||||
fixGridLink.href = "?" + savedGame
|
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 = ""
|
||||||
}
|
}
|
||||||
boxes.forEach(searchCandidatesOf)
|
|
||||||
refreshUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
onhashchange = function(event) {
|
checkBoxes()
|
||||||
if (location.hash.slice(1)) loadSavedGame()
|
enableRadio()
|
||||||
else restart()
|
highlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchCandidatesOf(box) {
|
function searchCandidatesOf(box) {
|
||||||
@ -115,10 +119,10 @@ function searchCandidatesOf(box) {
|
|||||||
switch (box.candidates.size) {
|
switch (box.candidates.size) {
|
||||||
case 0:
|
case 0:
|
||||||
box.title = "Aucune possibilité !"
|
box.title = "Aucune possibilité !"
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
box.title = "Une seule possibilité [Clic-droit]"
|
box.title = "Une seule possibilité [Clic-droit]"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
box.title = box.candidates.size + " possibilités [Clic-droit]"
|
box.title = box.candidates.size + " possibilités [Clic-droit]"
|
||||||
}
|
}
|
||||||
@ -127,12 +131,16 @@ function searchCandidatesOf(box) {
|
|||||||
|
|
||||||
function onfocus() {
|
function onfocus() {
|
||||||
if (pencilRadio.checked) {
|
if (pencilRadio.checked) {
|
||||||
//this.type = "text"
|
this.type = "text"
|
||||||
this.value = this.placeholder
|
this.value = this.placeholder
|
||||||
|
this.placeholder = ""
|
||||||
this.classList.add("pencil")
|
this.classList.add("pencil")
|
||||||
} else {
|
} else {
|
||||||
this.select()
|
this.select()
|
||||||
}
|
}
|
||||||
|
if (penColor && inkPenRadio.checked) {
|
||||||
|
this.style.setProperty("color", penColor)
|
||||||
|
}
|
||||||
this.style.caretColor = valueToInsert ? "transparent" : "auto"
|
this.style.caretColor = valueToInsert ? "transparent" : "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,78 +165,56 @@ function onclick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function oninput() {
|
function oninput() {
|
||||||
history.push({
|
if (inkPenRadio.checked) {
|
||||||
box: this,
|
checkBoxes()
|
||||||
value: this.previousValue,
|
enableRadio()
|
||||||
placeholder: this.previousPlaceholder
|
highlight()
|
||||||
})
|
fixGridLink.href = "?" + boxes.map(box => box.value || UNKNOWN).join("")
|
||||||
undoButton.disabled = false
|
|
||||||
saveButton.disabled = false
|
|
||||||
restartButton.disabled = false
|
|
||||||
if (pencilRadio.checked) {
|
|
||||||
this.previousValue = ""
|
|
||||||
this.previousPlaceholder = this.value
|
|
||||||
} else {
|
|
||||||
this.previousValue = this.value
|
|
||||||
this.previousPlaceholder = this.placeholder
|
|
||||||
refreshBox(this)
|
|
||||||
}
|
}
|
||||||
|
saveGame()
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshBox(box) {
|
function checkBoxes() {
|
||||||
checkBox(box)
|
boxes.forEach(box => {
|
||||||
refreshUI()
|
box.setCustomValidity("")
|
||||||
}
|
box.classList.remove("is-invalid")
|
||||||
|
box.parentElement.classList.remove("table-danger")
|
||||||
function checkBox(box) {
|
searchCandidatesOf(box)
|
||||||
box.andNeighbourhood.forEach(neighbour => {
|
if (box.candidates.size == 0) {
|
||||||
neighbour.setCustomValidity("")
|
box.setCustomValidity("Aucun chiffre possible !")
|
||||||
neighbour.classList.remove("is-invalid")
|
box.classList.add("is-invalid")
|
||||||
searchCandidatesOf(neighbour)
|
|
||||||
if (neighbour.candidates.size == 0) {
|
|
||||||
neighbour.setCustomValidity("Aucun chiffre possible !")
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (box.value) {
|
for (let [areaName, areas] of Object.entries(areaNames))
|
||||||
for (area of[{
|
for (area of areas)
|
||||||
name: "région",
|
area.filter(box => box.value).sort((box, neighbour) => {
|
||||||
neighbours: regions[box.regionId]
|
if(box.value == neighbour.value) {
|
||||||
}, {
|
area.forEach(neighbour => neighbour.parentElement.classList.add("table-danger"))
|
||||||
name: "ligne",
|
for (neighbour of [box, neighbour]) {
|
||||||
neighbours: rows[box.rowId]
|
neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${areaName}.`)
|
||||||
}, {
|
|
||||||
name: "colonne",
|
|
||||||
neighbours: columns[box.columnId]
|
|
||||||
}, ])
|
|
||||||
for (neighbour of area.neighbours)
|
|
||||||
if (box != neighbour && box.value == neighbour.value) {
|
|
||||||
for (neighbour of[box, neighbour]) {
|
|
||||||
neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${area.name}.`)
|
|
||||||
neighbour.classList.add("is-invalid")
|
neighbour.classList.add("is-invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return box.value - neighbour.value
|
||||||
|
})
|
||||||
|
|
||||||
if (box.form.checkValidity()) { // Correct grid
|
if (sudokuForm.checkValidity()) { // Correct grid
|
||||||
if (boxes.filter(box => box.value == "").length == 0) {
|
if (boxes.filter(box => box.value == "").length == 0) {
|
||||||
grid.classList.add("table-success")
|
grid.classList.add("table-success")
|
||||||
saveButton.disabled = true
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (confirm(`Bravo ! Vous avez résolu la grille. En voulez-vous une autre ?`))
|
if (confirm(`Bravo ! Vous avez résolu la grille. En voulez-vous une autre ?`))
|
||||||
location = "."
|
location = "."
|
||||||
}, 400)
|
}, 400)
|
||||||
|
} else {
|
||||||
|
grid.classList.remove("table-success")
|
||||||
}
|
}
|
||||||
} else { // Errors on grid
|
} else { // Errors on grid
|
||||||
box.form.reportValidity()
|
grid.classList.remove("table-success")
|
||||||
|
sudokuForm.reportValidity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshUI() {
|
|
||||||
enableRadio()
|
|
||||||
highlight()
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableRadio() {
|
function enableRadio() {
|
||||||
for (radio of insertRadios) {
|
for (radio of insertRadios) {
|
||||||
if (boxes.filter(box => box.value == "").some(box => box.candidates.has(radio.value))) {
|
if (boxes.filter(box => box.value == "").some(box => box.candidates.has(radio.value))) {
|
||||||
@ -278,89 +264,69 @@ function onblur() {
|
|||||||
if (this.classList.contains("pencil")) {
|
if (this.classList.contains("pencil")) {
|
||||||
this.placeholder = this.value
|
this.placeholder = this.value
|
||||||
this.value = ""
|
this.value = ""
|
||||||
//this.type = "number"
|
this.type = "number"
|
||||||
this.classList.remove("pencil")
|
this.classList.remove("pencil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveGame() {
|
||||||
|
history.pushState({
|
||||||
|
boxesValues: boxes.map(box => box.value),
|
||||||
|
boxesPlaceholders: boxes.map(box => box.placeholder)
|
||||||
|
}, "")
|
||||||
|
restartLink.classList.remove("disabled")
|
||||||
|
undoButton.disabled = false
|
||||||
|
}
|
||||||
|
|
||||||
function onmouseenter(event) {
|
function onmouseenter(event) {
|
||||||
if (sightCheckbox.checked){
|
if (sightCheckbox.checked){
|
||||||
box = event.target
|
box = event.target
|
||||||
box.neighbourhood.concat([box]).forEach(neighbour => {
|
box.andNeighbourhood.forEach(neighbour => {
|
||||||
neighbour.parentElement.classList.add("table-active")
|
neighbour.parentElement.classList.add("table-active")
|
||||||
})
|
})
|
||||||
|
|
||||||
box.neighbourhood.forEach(neighbour => {
|
box.neighbourhood.forEach(neighbour => {
|
||||||
if (valueToInsert && neighbour.value == valueToInsert) {
|
if (valueToInsert && neighbour.value == valueToInsert) {
|
||||||
for (neighbour of[box, neighbour]) {
|
for (neighbour of [box, neighbour]) {
|
||||||
neighbour.parentElement.classList.add("table-danger")
|
neighbour.parentElement.classList.add("table-danger", "not-allowed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onmouseleave(event) {
|
function onmouseleave(event) {
|
||||||
if (sightCheckbox.checked){
|
if (sightCheckbox.checked){
|
||||||
box = event.target
|
box = event.target
|
||||||
box.neighbourhood.concat([box]).forEach(neighbour => {
|
box.andNeighbourhood.forEach(neighbour => {
|
||||||
neighbour.parentElement.classList.remove("table-active")
|
neighbour.parentElement.classList.remove("table-active", "table-danger", "not-allowed")
|
||||||
neighbour.parentElement.classList.remove("table-danger")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function insert(radio) {
|
function insert(radio) {
|
||||||
valueToInsert = radio.value
|
if (radio.value && valueToInsert == radio.value) {
|
||||||
grid.style.cursor = valueToInsert ? "copy" : "text"
|
radio.blur()
|
||||||
highlight()
|
insertRadio0.checked = true
|
||||||
|
insert(0)
|
||||||
|
} else {
|
||||||
|
valueToInsert = radio.value
|
||||||
|
grid.style.cursor = valueToInsert ? "pointer" : "text"
|
||||||
|
highlight()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function undo() {
|
let penColor
|
||||||
if (history.length) {
|
|
||||||
const previousState = history.pop()
|
function changeColor() {
|
||||||
previousState.box.value = previousState.value
|
penColor = colorPickerInput.value
|
||||||
previousState.box.placeholder = previousState.placeholder
|
colorPickerLabel.style.color = colorPickerInput.value
|
||||||
refreshBox(previousState.box)
|
|
||||||
if (history.length < 1) {
|
|
||||||
undoButton.disabled = true
|
|
||||||
saveButton.disabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function restart() {
|
function restart() {
|
||||||
if (confirm("Effacer toutes les cases ?")) {
|
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
|
|
||||||
restartButton.disabled = true
|
restartButton.disabled = true
|
||||||
location.hash = ""
|
location.hash = ""
|
||||||
boxes.forEach(searchCandidatesOf)
|
|
||||||
refreshUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function save() {
|
|
||||||
let saveGame = boxes.map(box => box.value || UNKNOWN).join("")
|
|
||||||
location.hash = saveGame
|
|
||||||
fixGridLink.href = "?" + saveGame
|
|
||||||
saveButton.disabled = true
|
|
||||||
alert("Partie sauvegardée")
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onbeforeunload = function(event) {
|
|
||||||
localStorage["sightCheckbox.checked"] = sightCheckbox.checked
|
|
||||||
localStorage["highlighterCheckbox.checked"] = highlighterCheckbox.checked
|
|
||||||
if (!saveButton.disabled) {
|
|
||||||
event.preventDefault()
|
|
||||||
event.returnValue = "La partie n'est pas sauvegardée. Quitter quand même ?"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +353,7 @@ function oncontextmenu(event) {
|
|||||||
li.onclick = function(e) {
|
li.onclick = function(e) {
|
||||||
contextMenu.style.display = "none"
|
contextMenu.style.display = "none"
|
||||||
valueToInsert = e.target.innerText
|
valueToInsert = e.target.innerText
|
||||||
grid.style.cursor = "copy"
|
grid.style.cursor = "pointer"
|
||||||
document.getElementById("insertRadio" + valueToInsert).checked = true
|
document.getElementById("insertRadio" + valueToInsert).checked = true
|
||||||
box.onclick()
|
box.onclick()
|
||||||
}
|
}
|
||||||
@ -400,7 +366,7 @@ function oncontextmenu(event) {
|
|||||||
} else {
|
} else {
|
||||||
li = document.createElement("li")
|
li = document.createElement("li")
|
||||||
li.innerText = "Aucune possibilité !"
|
li.innerText = "Aucune possibilité !"
|
||||||
li.classList.add("error")
|
li.classList = "list-group-item list-group-item-action disabled"
|
||||||
contextMenu.appendChild(li)
|
contextMenu.appendChild(li)
|
||||||
}
|
}
|
||||||
contextMenu.style.left = `${event.pageX}px`
|
contextMenu.style.left = `${event.pageX}px`
|
||||||
@ -420,3 +386,9 @@ document.onkeydown = function(event) {
|
|||||||
contextMenu.style.display = "none"
|
contextMenu.style.display = "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.onbeforeunload = function(event) {
|
||||||
|
saveGame()
|
||||||
|
if (sightCheckbox.checked) localStorage["tool"] = "sight"
|
||||||
|
else if (highlighterCheckbox.checked) localStorage["tool"] = "highlighter"
|
||||||
|
}
|
180
sudoku.php
180
sudoku.php
@ -2,111 +2,89 @@
|
|||||||
<html lang='fr' prefix="og: https://ogp.me/ns#">
|
<html lang='fr' prefix="og: https://ogp.me/ns#">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<?php require_once("head.php") ?>
|
<?php require_once("head.php") ?>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="text-center">
|
<body>
|
||||||
<header>
|
<nav class="navbar mb-4">
|
||||||
<h1 class="display-4 mb-3">Sudoku</h1>
|
<h1 class="display-4 text-center m-auto">Sudoku</h1>
|
||||||
</header>
|
</nav>
|
||||||
<div class='d-flex justify-content-between mb-2'>
|
<div class="row g-0">
|
||||||
<div class='btn-group'>
|
<main class="col-md-6 order-md-1">
|
||||||
<input type='radio' id='inkPenRadio' class='btn-check' name='penRadioGroup' checked />
|
<div class="text-center m-auto" style="width: min-content;">
|
||||||
<label for='inkPenRadio' class='btn btn-primary' title='Écrire un chiffre'>
|
<div class='d-flex justify-content-between mb-2'>
|
||||||
<i class="bi bi-pen-fill"></i>
|
<div class='btn-group'>
|
||||||
</label>
|
<input type='radio' id='inkPenRadio' class='btn-check' name='penRadioGroup' checked />
|
||||||
<input type='radio' id='pencilRadio' class='btn-check' name='penRadioGroup' />
|
<label for='inkPenRadio' class='btn btn-primary' title='Écrire un chiffre'><i class="ri-ball-pen-fill"></i></label>
|
||||||
<label for='pencilRadio' class='btn btn-primary' title='Prendre des notes'>
|
<input type='radio' id='pencilRadio' class='btn-check' name='penRadioGroup' />
|
||||||
<i class="bi bi-pencil-fill"></i>
|
<label for='pencilRadio' class='btn btn-primary' title='Prendre des notes'><i class="ri-pencil-fill"></i></label>
|
||||||
</label>
|
<input type='radio' id='eraserRadio' class='btn-check' name='penRadioGroup' />
|
||||||
<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>
|
||||||
<label for='eraserRadio' class='btn btn-primary' title='Effacer une case'>
|
</div>
|
||||||
<i class="bi bi-eraser-fill"></i>
|
<input type="color" class="btn-check" id="colorPickerInput" title="Changer la couleur" oninput="changeColor()"/>
|
||||||
</label>
|
<label id="colorPickerLabel" for="colorPickerInput" class="btn btn-primary" title="Changer de couleur"><i class="ri-palette-fill"></i></label>
|
||||||
</div>
|
<div class='btn-group'>
|
||||||
<div class='btn-group'>
|
<input type='checkbox' id='sightCheckbox' class='btn-check' onclick='highlighterCheckbox.checked = false; highlight()' />
|
||||||
<input type='checkbox' id='sightCheckbox' class='btn-check' onclick='highlighterCheckbox.checked = false; refreshUI()' />
|
<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>
|
||||||
<label for='sightCheckbox' class='btn btn-info' title='Surligner la ligne, la colonne et la région de la case survolée'>
|
<input type='checkbox' id='highlighterCheckbox' class='btn-check' onclick='sightCheckbox.checked = false; highlight()' />
|
||||||
<i class="bi bi-plus-square-fill"></i>
|
<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>
|
||||||
</label>
|
</div>
|
||||||
<input type='checkbox' id='highlighterCheckbox' class='btn-check' onclick='sightCheckbox.checked = false; refreshUI()' />
|
<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>
|
||||||
<label for='highlighterCheckbox' class='btn btn-info' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'>
|
<a id='restartLink' class='btn btn-primary disabled' href="" title='Recommencer'><i class="ri-restart-line"></i></a>
|
||||||
<i class="bi bi-ui-checks-grid"></i>
|
<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>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<form id='sudokuForm' class='needs-validation' novalidate>
|
||||||
<button id="hintButton" type="button" class='btn btn-info' onclick="showHint()" title="Montrer une case avec une seule possibilité" accesskey="H" disabled="">
|
<table id='grid' class='table mb-2'>
|
||||||
<i class="bi bi-lightbulb"></i>
|
<tbody>
|
||||||
</button>
|
<?php for ($row = 0; $row < 81; $row += 9): ?>
|
||||||
<button id='restartButton' type='button' class='btn btn-primary' onclick='restart()' disabled title='Recommencer'>
|
<tr class="input-group d-inline-block w-auto">
|
||||||
<i class="bi bi-skip-start-fill"></i>
|
<?php for ($column = 0; $column < 9; $column++): $value = $currentGrid[$row+$column]; ?>
|
||||||
</button>
|
<?php if ($value == UNKNOWN): ?>
|
||||||
<button id='undoButton' type='button' class='btn btn-primary' onclick='undo()' disabled title='Annuler' accesskey='Z'>
|
<td><input type='number' min='1' max='9' step='1' value='' class='form-control' /></td>
|
||||||
<i class="bi bi-arrow-left"></i>
|
<?php else: ?>
|
||||||
</button>
|
<td><input type='number' min='1' max='9' step='1' value='<?=$value?>' class='form-control' disabled /></td>
|
||||||
<button id='saveButton' type='button' class='btn btn-primary' onclick='save()' disabled title='Sauvegarder' accesskey='S'>
|
<?php endif ?>
|
||||||
<i class="bi bi-save-fill"></i>
|
<?php endfor?>
|
||||||
</button>
|
</tr>
|
||||||
</div>
|
<?php endfor?>
|
||||||
<form id='sudokuForm' class='needs-validation' novalidate>
|
</tbody>
|
||||||
<table id='grid' class='table mb-2'>
|
</table>
|
||||||
<tbody>
|
</form>
|
||||||
<?php
|
<div class='d-flex mb-4'>
|
||||||
for ($row = 0; $row < 81; $row += 9) {
|
<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>
|
||||||
<tr class="input-group d-inline-block">
|
<?php for($value=1; $value<=9; $value++): ?>
|
||||||
<?php
|
<input type='radio' class='btn-check' id='insertRadio<?=$value?>' value='<?=$value?>' name='insertRadioGroup' onclick='insert(this)' accesskey='<?=$value?>' disabled />
|
||||||
for ($column = 0; $column < 9; $column++) {
|
<label for='insertRadio<?=$value?>' class='btn btn-primary' title='Insérer un <?=$value?>'><?=$value?></label>
|
||||||
$value = $currentGrid[$row+$column];
|
<?php endfor ?>
|
||||||
if ($value == UNKNOWN) {
|
</div>
|
||||||
?>
|
</div>
|
||||||
<td><input type='number' min='1' max='9' step='1' value='' class='form-control'
|
<div class='mb-3'>
|
||||||
title='Valeurs possibles [Clic-droit]' /></td>
|
<?php if (isset($warning)): ?>
|
||||||
<?php
|
<strong>⚠️ <?=$warning?> ⚠️</strong><br/>
|
||||||
} else {
|
<?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.
|
||||||
<td><input type='number' min='1' max='9' step='1' value='<?=$value?>' class='form-control' disabled /></td>
|
<?php endif?>
|
||||||
<?php
|
</div>
|
||||||
}
|
</div>
|
||||||
}
|
</main>
|
||||||
?>
|
<aside class="col-md-3 text-center text-md-start">
|
||||||
</tr>
|
<div class="d-flex flex-column flex-shrink-0 p-3">
|
||||||
<?php
|
<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>
|
||||||
</tbody>
|
<li><a href="?---------------------------------------------------------------------------------" class="nav-link link-body-emphasis">Grille vierge</a></li>
|
||||||
</table>
|
<li><a id="fixGridLink" href="" class="nav-link link-body-emphasis">Figer la grille</a></li>
|
||||||
</form>
|
<li><a href="https://git.malingrey.fr/adrien/Sudoku" class="nav-link link-body-emphasis">Code source</a></li>
|
||||||
<div class='d-flex mb-2'>
|
<li><a href=".." class="nav-link link-body-emphasis">Autres jeux</a></li>
|
||||||
<div id='insertRadioGroup' class='radioGroup btn-group flex-fill'>
|
</ul>
|
||||||
<?php
|
</div>
|
||||||
for($value=1; $value<=9; $value++) {
|
</aside>
|
||||||
echo " <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>\n";
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<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="bi bi-cursor-text"></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='mb-3'>
|
|
||||||
<?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")
|
|
||||||
?>
|
|
||||||
</div>
|
</div>
|
||||||
<ul id='contextMenu' class='context-menu modal-content shadow list-group w-auto position-absolute'></ul>
|
<ul id='contextMenu' class='context-menu modal-content shadow list-group w-auto position-absolute'></ul>
|
||||||
<footer>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||||
<div id='links' class='list-group mb-2'>
|
<script src='sudoku.js' defer></script>
|
||||||
<a href='.' class='list-group-item list-group-item-action'>Nouvelle grille</a>
|
<script>navigator?.serviceWorker.register('service-worker.js')</script>
|
||||||
<a href='' class='list-group-item list-group-item-action'>Lien vers cette grille</a>
|
|
||||||
<a href='?.................................................................................' class='list-group-item list-group-item-action'>Grille
|
|
||||||
vierge</a>
|
|
||||||
<a href='' id='fixGridLink' class='list-group-item list-group-item-action'>Figer la grille enregistrée</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
7
thumbnail.php
Normal file → Executable file
7
thumbnail.php
Normal file → Executable file
@ -1,10 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
require("classes.php");
|
require("classes.php");
|
||||||
session_start();
|
if (isset($_GET["grid"]) && preg_match("/^[1-9-]{81}$/", $_GET["grid"]))
|
||||||
if (isset($_SESSION["currentGrid"]))
|
$currentGrid = $_GET["grid"];
|
||||||
$currentGrid = $_SESSION["currentGrid"];
|
|
||||||
else
|
else
|
||||||
$currentGrid = ".528.3....4.9.1...39.562......73.129...1.64.7...42.3656.13.5...28.6.4...4.5287...";
|
$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");
|
header ("Content-type: image/png");
|
||||||
if (isset($_GET['size']))
|
if (isset($_GET['size']))
|
||||||
$size = (int) $_GET['size'];
|
$size = (int) $_GET['size'];
|
||||||
|
BIN
thumbnail.png
Normal file → Executable file
BIN
thumbnail.png
Normal file → Executable file
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user