Compare commits

...

57 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
20 changed files with 379 additions and 2441 deletions

29
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>
<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
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.

68
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" /> <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
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: 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");
} }
} }
?> ?>

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(

54
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;
@ -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;
}

View File

@ -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"
}

View File

@ -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
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