Compare commits

..

70 Commits

Author SHA1 Message Date
c477355fe9 Ajouter README.md 2025-05-21 10:38:18 +02:00
f52c970bef meta 2025-05-09 11:50:32 +02:00
cf0a5a465e fix 2025-05-09 11:13:29 +02:00
310a1883d2 declare properties, use session without cookies 2025-05-06 18:20:08 +02:00
4b92464c94 format 2025-05-05 23:18:26 +02:00
c09ed80a52 serviceWorker in html 2024-05-12 11:17:03 +02:00
b145ae566a hover 2024-04-09 01:38:42 +02:00
21e8f4134f auto import 2023-12-23 13:25:04 +01:00
31a40a7e93 format 2023-12-23 13:24:44 +01:00
ad3992ac30 navbar 2023-12-23 13:24:22 +01:00
5feaa65955 global $sudokuGridSolutions 2023-12-23 13:24:12 +01:00
ed5795a6cc save directly nbsolutions in $_SESSION 2023-12-12 18:36:37 +01:00
bdb1a094c3 back to bootstrap dark 2023-12-11 09:13:35 +01:00
85efaca248 color: inherit 2023-12-08 01:17:20 +01:00
39c564fb89 $_SESSION["sudokuGridSolutions"] 2023-12-08 01:17:06 +01:00
ba07a531d0 save nbSolutions in session 2023-12-06 15:10:06 +01:00
f510310549 use bootstrap default color theme switcher 2023-12-06 12:08:45 +01:00
5091e6a888 update 400.php style 2023-12-06 11:02:46 +01:00
67cd3594a6 fix context-menu z-index 2023-12-06 10:38:57 +01:00
4027a8b36f md 2023-12-05 17:36:00 +01:00
e1fc974372 links on aside 2023-12-05 17:32:40 +01:00
853d93f8ff fixes 2023-11-25 22:38:03 +01:00
ddc1a51899 sight fixes 2023-11-25 22:37:44 +01:00
77886a6878 fix area on showDuplicates 2023-11-25 21:51:56 +01:00
80bc9f083d refactor with bind 2023-11-25 21:40:35 +01:00
1365ef65dd optimize with sort 2023-11-25 19:55:59 +01:00
ad414d0da8 refactor 2023-11-25 19:03:21 +01:00
57d06c3b53 fix check on onpopstate 2023-11-25 18:47:11 +01:00
84d9222a1c undo button is back 2023-11-25 11:58:18 +01:00
fa979cb973 use window.history 2023-11-25 04:40:40 +01:00
7245e0f073 ternary accessKeyModifiers 2023-11-02 10:37:07 +01:00
2429845dd6 navigator.platform is depreciated 2023-11-02 10:28:39 +01:00
956966fbe9 html format 2023-11-02 08:45:59 +01:00
ddcabbdd39 html format 2023-11-02 08:40:13 +01:00
743f7fa72d format 2023-10-31 03:29:02 +01:00
3a243c38f0 color picker icon 2023-10-23 19:28:03 +02:00
beebb14464 improve color picker 2023-10-23 17:25:21 +02:00
24806a289f improve color picker 2023-10-23 16:43:19 +02:00
4a0d03f445 add color picker 2023-10-23 09:16:46 +02:00
2e5125c298 uncheck radio if clicked twice 2023-10-17 23:33:39 +02:00
959deb5e14 icon margin 2023-06-21 08:38:19 +02:00
562cd7964b negative icon margin 2023-06-21 08:25:20 +02:00
e437a01e30 more button padding 2023-06-18 12:32:00 +02:00
f811000ea0 less button padding 2023-06-18 12:08:43 +02:00
35f1bee053 update thumbnail 2023-04-30 10:59:58 +02:00
79a20f323a update manifest 2023-04-30 10:27:41 +02:00
e80fef6c08 force favicon and manifest reload 2023-04-30 03:43:18 +02:00
7c65480bdd CDN 2023-04-22 16:14:46 +02:00
7043fb8bb6 CDN 2023-04-22 16:09:00 +02:00
15ed790caf smartphone friendly 2023-04-21 03:40:11 +02:00
c0c815e757 new thumbnail 2023-04-21 01:53:16 +02:00
5bd46082d5 move insertRadio0 2023-04-16 16:11:09 +02:00
de7b013716 text cursor 2023-04-16 15:14:08 +02:00
dab898c391 focus 2023-04-16 03:37:24 +02:00
f53e09b52e remix icons 2023-04-16 03:36:30 +02:00
176e72c465 small changes 2023-04-05 21:54:42 +02:00
cedd63dd90 source code 2023-03-31 02:33:57 +02:00
60b5f74e94 restart on no hash 2023-03-31 02:22:07 +02:00
c47cd498c0 fix onhashchange 2023-03-31 02:15:39 +02:00
6ee740cebd onhashchange 2023-03-31 01:55:12 +02:00
785f1460fb save to location.hash 2023-03-31 01:47:22 +02:00
80fa25c092 fix disable insertRadio 2023-03-31 01:35:47 +02:00
ac91453013 table-success 2023-03-30 21:26:14 +02:00
8c62d6bb0e text insert radio to the right 2023-03-30 19:38:36 +02:00
8f412bdd0f keyboard button 2023-03-30 03:42:32 +02:00
dee27c9ca7 ui 2023-03-30 02:55:15 +02:00
0f83b8de42 Merge branch 'master' of https://git.malingrey.fr/adrien/sudoku 2023-03-30 02:23:06 +02:00
ab11f85215 add sight function 2023-03-30 02:22:59 +02:00
9191b6836c fix insertRadio filter 2023-03-30 01:21:34 +02:00
b5345e1a4d optimize 2023-03-30 01:12:02 +02:00
20 changed files with 414 additions and 2423 deletions

9
400.php Normal file → Executable file
View File

@ -6,17 +6,22 @@
<title>Requête incorrecte</title> <title>Requête incorrecte</title>
</head> </head>
<body> <body>
<nav class="navbar mb-4">
<h1 class="display-4 text-center m-auto">Sudoku</h1>
</nav>
<main class="container my-4">
<header> <header>
<h1>Requête incorrecte</h1> <h1 class="mb-4">Requête incorrecte</h1>
</header> </header>
L'adresse URL doit être de la forme :<br/> L'adresse URL doit être de la forme :<br/>
<?=$dirUrl?>/?<em>grille</em><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 : <em>grille</em> étant une suite de 81 caractères représentant la grille de gauche à droite puis de haut en bas, soit :
<ul> <ul>
<li>un chiffre entre 1 et 9 pour les cases connues</li> <li>un chiffre entre 1 et 9 pour les cases connues</li>
<li>un point pour les case vides</li> <li>un tiret (-) pour les case vides</li>
</ul> </ul>
Exemple :<br/> Exemple :<br/>
<a href='<?=$newGridUrl?>'><?=$newGridUrl?></a> <a href='<?=$newGridUrl?>'><?=$newGridUrl?></a>
</main>
</body> </body>
</html> </html>

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Sudoku
Web sudoku assistant
![screenshot](https://git.malingrey.fr/adrien/sudoku/raw/branch/master/thumbnail.png)

27
classes.php Normal file → Executable file
View 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) {

File diff suppressed because one or more lines are too long

2018
css/bootstrap-icons.css vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

0
favicon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

Binary file not shown.

60
head.php Normal file → Executable file
View 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" />
<link rel="apple-touch-icon" href="thumbnail.php?size=57" sizes="57x57">
<link rel="apple-touch-icon" href="thumbnail.php?size=114" sizes="114x114">
<link rel="apple-touch-icon" href="thumbnail.php?size=72" sizes="72x72">
<link rel="apple-touch-icon" href="thumbnail.php?size=144" sizes="144x144">
<link rel="apple-touch-icon" href="thumbnail.php?size=60" sizes="60x60">
<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 rel="apple-touch-icon" href="thumbnail.php?size=152" sizes="152x152">
<link rel="icon" type="image/png" href="thumbnail.php?size=196" sizes="196x196">
<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 rel="icon" type="image/png" href="thumbnail.php?size=16" sizes="16x16">
<link rel="icon" type="image/png" href="thumbnail.php?size=32" sizes="32x32">
<link rel="manifest" href="manifest.php">
<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.php?size=200" />
<meta property="og:image:width" content="200" /> <meta property="og:image:width" content="200" />
<meta property="og:image:height" content="200" /> <meta property="og:image:height" content="200" />
<meta property="og:description" <meta name="Language" CONTENT="fr" /><meta property="og:locale" content="fr_FR" />
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"]?>" /> <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> <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">

37
index.php Normal file → Executable file
View 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:
$validGrids[] = $currentGrid;
break; break;
default: default:
$warning = "Cette grille a plusieurs solutions."; $warning = "Cette grille a plusieurs solutions.";
} }
}
}
require("sudoku.php"); require("sudoku.php");
} else {
if ($currentGrid) {
require("400.php");
} else { } else {
$grid = new Grid(); $grid = new Grid();
$grid->generate();
$gridAsString = $grid->toString(); $gridAsString = $grid->toString();
$newGridUrl = "$dirUrl/?$gridAsString"; $newGridUrl = "$dirUrl/?$gridAsString";
$_SESSION[$gridAsString] = "checked";
if (!$currentGrid) { session_id($gridAsString);
session_start(["use_cookies" => false]);
$_SESSION["nbSolutions"] = 1;
header("Location: $newGridUrl"); header("Location: $newGridUrl");
} else {
require("400.php");
} }
} }
?> ?>

File diff suppressed because one or more lines are too long

53
manifest.php Normal file → Executable file
View 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
View 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(

56
css/style.css → style.css Normal file → Executable file
View 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;
@ -25,14 +20,25 @@ table input {
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,
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 {
@ -86,15 +92,23 @@ 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;
} }
.table-active { .table-active {
cursor: inherit !important;
}
.not-allowed {
cursor: not-allowed !important; cursor: not-allowed !important;
} }
@ -104,9 +118,15 @@ table {
button, button,
label { label {
/*! 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;
@ -116,20 +136,26 @@ table input:enabled {
cursor: inherit; cursor: inherit;
} }
.modal-content {
z-index: 1000;
}
.bi::before {
vertical-align: 0;
}
.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;
}

View File

@ -1,15 +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 accessKeyModifiers = "AccessKey+"
let easyBoxes = [] let easyBoxes = []
let insertRadios = []
function shuffle(iterable) { function shuffle(iterable) {
array = Array.from(iterable) array = Array.from(iterable)
@ -36,8 +39,8 @@ window.onload = function() {
box.oninput = oninput box.oninput = oninput
box.onblur = onblur box.onblur = onblur
box.onclick = onclick box.onclick = onclick
box.previousValue = "" box.onmouseenter = onmouseenter
box.previousPlaceholder = "" box.onmouseleave = onmouseleave
} }
box.oncontextmenu = oncontextmenu box.oncontextmenu = oncontextmenu
box.rowId = rowId box.rowId = rowId
@ -52,48 +55,61 @@ window.onload = function() {
rowId++ rowId++
} }
if (localStorage["highlighterCheckbox.checked"]) { if (localStorage["tool"] == "sight") sightCheckbox.checked = true
highlighterCheckbox.checked = true else if (localStorage["tool"] == "highlighter") highlighterCheckbox.checked = true
}
loadSavedGame() 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]))
box.andNeighbourhood = Array.from(box.neighbourhood)
box.neighbourhood.delete(box) box.neighbourhood.delete(box)
box.neighbourhood = Array.from(box.neighbourhood) box.neighbourhood = Array.from(box.neighbourhood)
searchCandidatesOf(box)
}) })
insertRadios = Array.from(insertRadioGroup.getElementsByTagName("input")).slice(1)
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
} }
refreshUI() 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 = localStorage[location.search]
if (savedGame) { 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]
} }
}) })
fixGridLink.href = "?" + savedGame 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) { function searchCandidatesOf(box) {
@ -115,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"
} }
@ -145,87 +165,69 @@ 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.neighbourhood.concat([box]).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",
neighbours: rows[box.rowId]
}, {
name: "colonne",
neighbours: columns[box.columnId]
}, ])
for (neighbour of area.neighbours)
if (box != neighbour && box.value == neighbour.value) {
for (neighbour of [box, neighbour]) { for (neighbour of [box, neighbour]) {
neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${area.name}.`) neighbour.setCustomValidity(`Il y a un autre ${box.value} dans cette ${areaName}.`)
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) {
saveButton.disabled = true grid.classList.add("table-success")
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 insertRadioGroup.getElementsByTagName("input")) { 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))) {
radio.disabled = false radio.disabled = false
radio.label.title = `Insérer un ${radio.value} [${radio.accessKeyLabel||(accessKeyModifiers+radio.accessKey)}]` radio.label.title = `Insérer un ${radio.value} [${radio.accessKeyLabel||(accessKeyModifiers+radio.accessKey)}]`
} else { } else {
radio.disabled = true radio.disabled = true
radio.label.title = `Tous les ${radio.value} sont posés.` radio.label.title = `Tous les ${radio.value} sont posés.`
if (valueToInsert == radio.value) if (valueToInsert == radio.value) {
insertRadio0.checked = true
valueToInsert = "" valueToInsert = ""
grid.style.cursor = "text"
}
} }
} }
} }
@ -241,6 +243,7 @@ function highlight() {
box.parentElement.classList.remove("table-primary") box.parentElement.classList.remove("table-primary")
box.tabIndex = 0 box.tabIndex = 0
} }
if (valueToInsert && highlighterCheckbox.checked && !box.candidates.has(valueToInsert)) { if (valueToInsert && highlighterCheckbox.checked && !box.candidates.has(valueToInsert)) {
box.parentElement.classList.add("table-active") box.parentElement.classList.add("table-active")
box.tabIndex = -1 box.tabIndex = -1
@ -261,65 +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 insert(radio) { function saveGame() {
if (radio.value == valueToInsert) { history.pushState({
valueToInsert = "" boxesValues: boxes.map(box => box.value),
radio.checked = false boxesPlaceholders: boxes.map(box => box.placeholder)
} else { }, "")
valueToInsert = radio.value restartLink.classList.remove("disabled")
} undoButton.disabled = false
grid.style.cursor = valueToInsert ? "copy" : "text"
highlight()
} }
function undo() { function onmouseenter(event) {
if (history.length) { if (sightCheckbox.checked){
const previousState = history.pop() box = event.target
previousState.box.value = previousState.value box.andNeighbourhood.forEach(neighbour => {
previousState.box.placeholder = previousState.placeholder neighbour.parentElement.classList.add("table-active")
refreshBox(previousState.box) })
if (history.length < 1) {
undoButton.disabled = true box.neighbourhood.forEach(neighbour => {
saveButton.disabled = true 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() { 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
boxes.forEach(searchCandidatesOf) location.hash = ""
refreshUI()
}
}
function save() {
let saveGame = boxes.map(box => box.value || UNKNOWN).join("")
localStorage[location.search] = saveGame
fixGridLink.href = "?" + saveGame
saveButton.disabled = true
alert("Partie sauvegardée")
}
window.onbeforeunload = function(event) {
localStorage["highlighterCheckbox.checked"] = highlighterCheckbox.checked
if (!saveButton.disabled) {
event.preventDefault()
event.returnValue = "La partie n'est pas sauvegardée. Quitter quand même ?"
} }
} }
@ -346,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()
} }
@ -359,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`
@ -379,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"
}

View File

@ -5,98 +5,86 @@
<?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="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='d-flex justify-content-between mb-2'>
<div class='radioGroup btn-group'> <div class='btn-group'>
<input type='radio' id='inkPenRadio' class='btn-check' name='tool' checked /> <input type='radio' id='inkPenRadio' class='btn-check' name='penRadioGroup' checked />
<label for='inkPenRadio' class='btn btn-primary' title='Écrire un chiffre'> <label for='inkPenRadio' class='btn btn-primary' title='Écrire un chiffre'><i class="ri-ball-pen-fill"></i></label>
<i class="bi bi-pen-fill"></i> <input type='radio' id='pencilRadio' class='btn-check' name='penRadioGroup' />
</label> <label for='pencilRadio' class='btn btn-primary' title='Prendre des notes'><i class="ri-pencil-fill"></i></label>
<input type='radio' id='pencilRadio' class='btn-check' name='tool' /> <input type='radio' id='eraserRadio' class='btn-check' name='penRadioGroup' />
<label for='pencilRadio' class='btn btn-primary' title='Prendre des notes'> <label for='eraserRadio' class='btn btn-primary' title='Effacer une case'><i class="ri-eraser-fill"></i></label>
<i class="bi bi-pencil-fill"></i>
</label>
<input type='radio' id='eraserRadio' class='btn-check' name='tool' '/>
<label for='eraserRadio' class='btn btn-primary' title='Effacer une case'>
<i class="bi bi-eraser-fill"></i>
</label>
</div> </div>
<input id='highlighterCheckbox' type="checkbox" class='btn-check' onclick='highlight()' /> <input type="color" class="btn-check" id="colorPickerInput" title="Changer la couleur" oninput="changeColor()"/>
<label for='highlighterCheckbox' class='btn btn-primary' title='Surligner les lignes, colonnes et régions contenant déjà le chiffre sélectionné'> <label id="colorPickerLabel" for="colorPickerInput" class="btn btn-primary" title="Changer de couleur"><i class="ri-palette-fill"></i></label>
<i class="bi bi-magic"></i> <div class='btn-group'>
</label> <input type='checkbox' id='sightCheckbox' class='btn-check' onclick='highlighterCheckbox.checked = false; highlight()' />
<button id="hintButton" type="button" class='btn btn-primary' onclick="showHint()" title="Montrer une case avec une seule possibilité" accesskey="H" disabled=""> <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>
<i class="bi bi-lightbulb-fill"></i> <input type='checkbox' id='highlighterCheckbox' class='btn-check' onclick='sightCheckbox.checked = false; highlight()' />
</button> <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>
<button id='restartButton' type='button' class='btn btn-primary' onclick='restart()' disabled title='Recommencer'> </div>
<i class="bi bi-x-circle-fill"></i></i> <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>
</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='undo()' disabled title='Annuler' accesskey='Z'> <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>
<i class="bi bi-arrow-counterclockwise"></i>
</button>
<button id='saveButton' type='button' class='btn btn-primary' onclick='save()' disabled title='Sauvegarder' accesskey='S'>
<i class="bi bi-save-fill"></i>
</button>
</div> </div>
<form id='sudokuForm' class='needs-validation' novalidate> <form id='sudokuForm' class='needs-validation' novalidate>
<table id='grid' class='table mb-2'> <table id='grid' class='table mb-2'>
<tbody> <tbody>
<?php <?php for ($row = 0; $row < 81; $row += 9): ?>
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]; ?>
<tr class="input-group d-inline-block"> <?php if ($value == UNKNOWN): ?>
<?php <td><input type='number' min='1' max='9' step='1' value='' class='form-control' /></td>
for ($column = 0; $column < 9; $column++) { <?php else: ?>
$value = $currentGrid[$row+$column];
if ($value == UNKNOWN) {
?>
<td><input type='number' min='1' max='9' step='1' value='' class='form-control'
title='Valeurs possibles [Clic-droit]' /></td>
<?php
} else {
?>
<td><input type='number' min='1' max='9' step='1' value='<?=$value?>' class='form-control' disabled /></td> <td><input type='number' min='1' max='9' step='1' value='<?=$value?>' class='form-control' disabled /></td>
<?php <?php endif ?>
} <?php endfor?>
}
?>
</tr> </tr>
<?php <?php endfor?>
}
?>
</tbody> </tbody>
</table> </table>
</form> </form>
<div class='d-flex mb-2'> <div class='d-flex mb-4'>
<div id='insertRadioGroup' class='radioGroup btn-group flex-fill'> <div id='insertRadioGroup' class='radioGroup btn-group flex-fill'>
<?php <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>
for($value=1; $value<=9; $value++) { <?php for($value=1; $value<=9; $value++): ?>
echo " <input type='radio'class='btn-check' id='insertRadio$value' value='$value' name='insertRadioGroup' onclick='insert(this)' accesskey='$value'/><label for='insertRadio$value' class='btn btn-primary' title='Insérer un $value'>$value</label>\n"; <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> </div>
<div class='mb-3'> <div class='mb-3'>
<?php <?php if (isset($warning)): ?>
if (isset($warning)) <strong>⚠️ <?=$warning?> ⚠️</strong><br/>
echo(" <strong>⚠️ $warning ⚠️</strong><br/>\n"); <?php else: ?>
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.
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") <?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> </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
View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB