Compare commits

...

134 Commits

Author SHA1 Message Date
adc0f040c4 test2 2025-05-23 20:51:23 +02:00
c76b8a2b30 test 2025-05-23 20:49:18 +02:00
909a1cf4c9 dz 2025-05-23 20:46:11 +02:00
667a481ffb bhk 2025-05-23 20:43:55 +02:00
acb6433b4e oihẑgrjopqpjirùjlkpjiùÉFSBHpBY^bBàyn_)NYBÀNYb,È=BBUÇ) 2025-05-23 20:36:11 +02:00
c639ec0759 Actualiser README.md 2025-05-21 10:37:02 +02:00
cb4078e4ce fix 2025-05-14 08:45:39 +02:00
40be9eeee1 serialize 2025-05-11 03:03:43 +02:00
ee76bbfc61 sauvegarder que les définitions 2025-05-11 01:57:43 +02:00
c4e98016b4 chargerle dico qu'une fois 2025-05-10 19:34:39 +02:00
97daba979f sauvegarder les mots intéressants du dico pour pas tout relire 2025-05-10 19:24:03 +02:00
b5930083bb aperçu en png et svg 2025-05-10 18:06:54 +02:00
15b7ecf4ae réorganisation 2025-05-10 14:29:04 +02:00
64b7c08f58 aperçu 2025-05-10 14:10:37 +02:00
cfb0d1fdc9 continuer après la redirection 2025-05-10 11:11:27 +02:00
eeb6889739 __str 2025-05-10 11:08:56 +02:00
db75bb0921 nouveau thumbnail 2025-05-09 18:08:08 +02:00
4ef2821596 fond noir icone 2025-05-09 16:47:14 +02:00
6d0477567f nouvel aperçu 2025-05-09 16:15:21 +02:00
21741e394e meta 2025-05-09 16:10:54 +02:00
203e5a7edc meta 2025-05-09 00:54:23 +02:00
8c685d9794 🄼🄾🅃🅂🄲🅁🄾🄸🅂🄴🅂 2025-05-08 19:19:12 +02:00
add4f47ad7 fix nb mots 2025-05-08 14:33:31 +02:00
895cf0adda fix fin de partir 2025-05-08 13:05:00 +02:00
fe955fe95c fix CASE_NOIRE 2025-05-08 12:59:38 +02:00
74beb1388a mise en forme des définitions dans index.php 2025-05-08 12:19:10 +02:00
a42f8a5f18 CASE_NOIRE 2025-05-08 05:24:21 +02:00
2e93e2c878 changement de format du dictionnaire 2025-05-08 05:21:07 +02:00
ffe308ba3e variable inutile 2025-05-08 04:50:30 +02:00
cb054036ad variable inutile 2025-05-08 04:49:49 +02:00
4bbc2d27fd ajout de poncifs 2025-05-08 04:38:57 +02:00
457d18436e petites modifs 2025-05-08 01:52:28 +02:00
ca39709fbd nombre de mots 2025-05-07 23:54:00 +02:00
ddebd453df interface trie avec des strings 2025-05-07 21:21:48 +02:00
d5a120cd9a omettre les mots plus grands que la grille 2025-05-07 19:38:04 +02:00
ab9e1f08ef Tries everywhere! 2025-05-07 18:34:08 +02:00
b1f3e8b85f omission des lettres isolées 2025-05-07 00:16:55 +02:00
c20d2324e9 FAST & FURIOUS 2025-05-06 17:44:16 +02:00
65bb8be2e3 Revert "Merge branch 'tmp3'"
This reverts commit ed1ad0566f, reversing
changes made to 6ee909914d.
2025-05-06 17:29:51 +02:00
ed1ad0566f Merge branch 'tmp3' 2025-05-06 17:27:47 +02:00
47be3d2e51 FAST & FURIOUS 2025-05-06 16:56:59 +02:00
6ee909914d WIP 2025-05-06 15:55:58 +02:00
4c014bc789 variable inutilisée 2025-05-06 08:47:10 +02:00
b063d39689 Merge branch 'tmp2' 2025-05-06 02:43:01 +02:00
d150e76a9e fix id vide 2025-05-06 02:38:18 +02:00
a6b15fbb87 fix 2025-05-06 02:15:13 +02:00
afd79eb3a2 utiliser session_id comme id 2025-05-06 01:40:53 +02:00
ea4555144c sans cookies 2025-05-05 22:41:55 +02:00
1a35d8e920 sauvegarde 2025-05-05 20:02:56 +02:00
7ae6506539 WIP 2025-05-05 16:57:29 +02:00
f8bd1d35cd TRIE 2025-05-05 02:49:57 +02:00
23cac370a1 conserver la numérotation 2025-05-04 13:57:08 +02:00
dc488aea4d renvoyer un numéro de grille seulement si elle est valide 2025-05-03 14:48:54 +02:00
91db11cc2d permutations 2025-05-03 14:14:56 +02:00
4a19252a94 petites modifs 2025-05-03 12:59:29 +02:00
ca95b76558 ne vérifier les doublons que sur le dernier mot de la ligne 2025-05-03 12:43:11 +02:00
97b9766db3 optimisation 2025-05-03 02:40:38 +02:00
168fc5f7a2 moins de comparaisons 2025-05-03 02:36:46 +02:00
0dd9881f6d correction OFFLINE_URL 2025-05-03 02:25:55 +02:00
32db8f5a6c 2025-05-03 02:21:25 +02:00
9f3a90a04e 2025-05-03 02:09:16 +02:00
73e8d6a857 ça marche 2025-05-03 01:18:33 +02:00
7a245d601a suppression melanger_valeurs 2025-05-02 16:23:39 +02:00
e1eb7685e4 suppression des points quand une seule phrase 2025-05-02 16:23:29 +02:00
10ea7a7f0b fix 2025-05-02 15:15:47 +02:00
63cd243e91 Merge branch 'tmp' 2025-05-02 11:55:23 +02:00
c097be27d8 style 2025-05-02 11:50:08 +02:00
399370624c 7 2025-05-02 04:32:32 +02:00
911da7aef9 ajouts 2025-05-02 04:30:46 +02:00
1987905eb9 annulation de l'autorisation des doublons 2025-05-02 04:08:38 +02:00
2214cf4a05 épurage 2025-05-02 03:55:38 +02:00
5997397908 retour de la limite de mots 2025-05-02 03:34:07 +02:00
b85bf7eb99 corrections 2025-05-02 03:27:01 +02:00
4a1106fcba autoriser autant de doublons que de définitions,
suppression de la limite de mots
2025-05-02 03:06:18 +02:00
d867e40499 mots_espace renvoie une liste de mots 2025-05-02 00:47:00 +02:00
d852596386 élimination des doublons 2025-05-02 00:00:59 +02:00
38344a315c Eviter les doublons lorsqu'il y a plusieurs mots en ligne 2025-05-01 23:25:21 +02:00
b042fcd77a ajout termes informatiques 2025-05-01 22:34:44 +02:00
912b4c410d changement de structure du dictionnaire 2025-05-01 20:58:08 +02:00
fcee18ee04 Nouvelles définitions 2025-05-01 20:13:36 +02:00
1073cd53fc ajout Wikipourri 2025-05-01 19:05:18 +02:00
fa134b8102 suppression du Dictionnaire des idées reçues 2025-05-01 18:52:54 +02:00
6b6edd369b petites modifs 2025-05-01 18:21:44 +02:00
db9b79fb6d correction limite 2025-05-01 17:47:46 +02:00
4874bc0d78 limite nombre de mots 2025-05-01 17:40:35 +02:00
6db7e7ab63 fusion de dico et mot_de_n_lettres, favicon en svg 2025-05-01 17:11:32 +02:00
9546ac4d97 changer le nombre de lignes et de colonnes 2025-05-01 15:16:45 +02:00
351e8387bb publier les mots entiers en ligne et colonne 2025-05-01 14:23:27 +02:00
c8ecb504ef n'afficher que les définitions de la case 2025-05-01 14:06:44 +02:00
92306e2a2a flèches en infobulle 2025-04-30 15:51:16 +02:00
fcf7977e1a définitions en infobulle 2025-04-30 15:47:09 +02:00
64e4113aa7 retour du récursif 2025-04-30 14:06:14 +02:00
590e9ab3ec et encore flex 2025-04-30 10:37:13 +02:00
0276726063 flex 2025-04-30 10:33:33 +02:00
7adeb187bd flex 2025-04-30 09:35:39 +02:00
20c1bc090d flex 2025-04-30 09:18:25 +02:00
851edebb4b corrections 2025-04-30 03:33:13 +02:00
18cce82633 petite modif 2025-04-30 03:28:20 +02:00
b703729e66 responsive design 2025-04-30 03:25:53 +02:00
cb8b42af67 petits ajustements 2025-04-30 02:23:06 +02:00
7aea2d9f99 recherche par arbre 2025-04-30 02:10:53 +02:00
88d95e42ad pile à la place de la récursion 2025-04-29 17:47:45 +02:00
1416b8bd54 nouvel algoritme plus rapide 2025-04-29 08:57:08 +02:00
2445bfcf54 gestion problème de génération 2025-04-28 18:33:14 +02:00
50872aef31 icône sur le bouton nouvelle grille 2025-04-28 17:54:55 +02:00
4fb2e70915 style 2025-04-28 17:48:34 +02:00
44b6fd7e8e theme sombre 2025-04-28 17:22:06 +02:00
f1ceae33f4 theme sombre 2025-04-28 17:18:03 +02:00
584f1a81a0 liste unique de définition 2025-04-28 17:17:58 +02:00
bcfe6da555 corrections mineures 2025-04-28 14:02:02 +02:00
dbe228c6c2 liste de définitions 2025-04-28 02:37:02 +02:00
856825a5e4 nom de variable 2025-04-28 00:38:44 +02:00
b3ad38815f nombre de mots 2025-04-28 00:38:15 +02:00
e04b6f0b91 accents 2025-04-28 00:38:07 +02:00
92dcf21297 nettoyage 2025-04-26 08:35:20 +02:00
ac778222e1 séparation de l'auteur 2025-04-25 20:55:01 +02:00
cfa03c1927 tab 2025-04-25 18:08:36 +02:00
7d346da288 autres rajouts 2025-04-25 18:05:23 +02:00
eae502702e rajout Ambrose Bierce 2025-04-25 17:54:45 +02:00
34db5a47a7 suppression des lettres accentuées 2025-04-25 14:00:22 +02:00
c2d9947131 ajout du Dictionnaire des idées reçues 2025-04-25 09:26:09 +02:00
958116a83c auteur en italique 2025-04-25 08:46:02 +02:00
33dd403de1 centrer la grille 2025-04-25 03:06:02 +02:00
967d5de46c centrer la grille 2025-04-25 03:05:08 +02:00
dd8dabd39c dimensions différentes 2025-04-25 02:50:31 +02:00
4b69fa4803 ajustement de mots_espaces 2025-04-25 02:11:21 +02:00
64bc91d4a0 smartphone style 2025-04-24 22:12:33 +02:00
65e31d71c6 petites améliorations 2025-04-24 21:41:50 +02:00
e234dd0ff8 styler le bouton comme un lien 2025-04-24 21:07:43 +02:00
2aba951d3b styler le bouton comme un lien 2025-04-24 21:00:33 +02:00
d1d189a0db lien nouvelle grille 2025-04-24 20:18:32 +02:00
6249729fcc style 2025-04-24 20:17:47 +02:00
3834e579a6 Merge commit 'ea141bfde63d43f180c1f50646f239142228822f' 2025-04-24 19:08:03 +02:00
f0b9052211 export du script js 2025-04-24 18:59:50 +02:00
22 changed files with 4095 additions and 2989 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__/
test*.*
*.py

View File

@ -1,141 +1,259 @@
<?php
include_once "dico.php";
const MIN_LETTRES = 1;
const ECART_TYPE_ALEA = 5;
class Grille {
$randmax = mt_getrandmax() + 1;
function gaussienne($moyenne = 0, $ecartType = 1.0): float {
global $randmax;
$u = 0;
$v = 0;
$u = (mt_rand() + 1) / $randmax;
$v = (mt_rand() + 1) / $randmax;
$z = sqrt(-2.0 * log($u)) * cos(2.0 * M_PI * $v);
return $z * $ecartType + $moyenne;
}
class Grille implements ArrayAccess
{
public $grille;
public $hauteur;
public $largeur;
private $grilles;
private $mots_commencant_par;
private $mots_utilises = [];
private $dico;
private $positions;
private $nb_positions;
private $lignes = [];
private $colonnes = [];
public $valide = false;
public $definitions = [];
public function __construct($hauteur, $largeur, $id="") {
public function __construct($hauteur, $largeur)
{
$this->hauteur = $hauteur;
$this->largeur = $largeur;
$this->grille = array_fill(0, $hauteur, array_fill(0, $largeur, '.'));
if ($hauteur == $largeur) {
$dimensions = [$hauteur];
} else {
$dimensions = [$hauteur, $largeur];
}
$this->mots_commencant_par = [];
foreach ($dimensions as $longueur) {
$this->mots_commencant_par[$longueur] = [];
foreach(mots_espaces($longueur, MIN_LETTRES, crc32($id)) as $mot) {
for ($i = 0; $i <= $longueur; $i++) {
$debut = substr($mot, 0, $i);
if (!isset($this->mots_commencant_par[$longueur][$debut])) {
$this->mots_commencant_par[$longueur][$debut] = [];
}
$this->mots_commencant_par[$longueur][$debut][] = $mot;
}
}
}
$this->grilles = $this->generateur();
$this->grilles->current();
$this->grille = array_fill(0, $hauteur, array_fill(0, $largeur, ''));
}
public function get_ligne($l, $longueur = 100) {
$longueur = min($longueur, $this->largeur);
public function get_ligne($y, $largeur)
{
$ligne = "";
for ($i = 0; $i < $longueur; $i++) {
$ligne .= $this->grille[$l][$i];
}
for ($x = 0; $x < $largeur; $x++)
$ligne .= $this->grille[$y][$x];
return $ligne;
}
public function set_ligne($l, $mot) {
for ($i = 0; $i < strlen($mot); $i++) {
$this->grille[$l][$i] = $mot[$i];
}
}
public function get_colonne($c, $longueur = 100) {
$longueur = min($longueur, $this->hauteur);
public function get_colonne($x, $hauteur)
{
$colonne = "";
for ($i = 0; $i < $longueur; $i++) {
$colonne .= $this->grille[$i][$c];
}
for ($y = 0; $y < $hauteur; $y++)
$colonne .= $this->grille[$y][$x];
return $colonne;
}
public function set_colonne($c, $mot) {
for ($i = 0; $i < strlen($mot); $i++) {
$this->grille[$i][$c] = $mot[$i];
public function genere($id)
{
mt_srand(crc32($id));
if (!isset($this->dico)) {
$this->dico = mots_espaces(max($this->hauteur, $this->largeur));
}
if (!isset($this->positions)) {
$this->positions = [];
for ($y = 0; $y < $this->hauteur; $y++) {
for ($x = 0; $x < $this->largeur; $x++)
$this->positions[] = [$x, $y];
}
$this->nb_positions = count($this->positions);
}
$grilles = $this->gen_grilles();
$grilles->current();
if ($grilles->valid()) {
$this->definitions = [
"horizontales" => [],
"verticales" => []
];
foreach($this->lignes as $y => $mots) {
$this->definitions["horizontales"][$y] = [];
foreach($mots as $mot) {
$definitions = $this->dico[strlen($mot)][$mot];
if (count($definitions)) {
$this->definitions["horizontales"][$y][] = $definitions[mt_rand(0, count($definitions) - 1)];
}
}
}
foreach($this->colonnes as $x => $mots) {
$this->definitions["verticales"][$x] = [];
foreach($mots as $mot) {
$definitions = $this->dico[strlen($mot)][$mot];
if (count($definitions)) {
$this->definitions["verticales"][$x][] = $definitions[mt_rand(0, count($definitions) - 1)];
}
}
}
$this->save($id);
return true;
} else {
return false;
}
}
public function generateur() {
yield from $this->trouve_une_ligne(0);
$this->grille = array_fill(0, $this->hauteur, array_fill(0, $this->largeur, ' '));
public function gen_grilles($i = 0, $lettres_ligne = NULL)
{
[$x, $y] = $this->positions[$i];
// Recherche de la prochaine lettre possible sur la case courante
// en ligne
if ($x == 0) {
$lettres_ligne = $this->dico[$this->largeur];
}
private function trouve_une_ligne($l) {
global $mots_de_n_lettres;
foreach ($this->mots_commencant_par[$this->largeur][$this->get_ligne($l, $l)] as $mot_lig) {
$this->set_ligne($l, $mot_lig);
$ok = true;
for ($c = $l; $c < $this->largeur; $c++) {
if (!isset($this->mots_commencant_par[$this->hauteur][$this->get_colonne($c, $l+1)])) {
$ok = false;
break;
// en colonne
$lettres_colonne = $this->dico[$this->hauteur];
for ($y2 = 0; $y2 < $y; $y2++) {
$lettres_colonne = $lettres_colonne->branches[$this->grille[$y2][$x]];
}
$lettres_communes = array_intersect_key(
$lettres_ligne->branches,
$lettres_colonne->branches
);
foreach ($lettres_communes as $lettre => $_) {
$lettres_communes[$lettre] = count($lettres_ligne->branches[$lettre]) * count($lettres_colonne->branches[$lettre]) * gaussienne(1, ECART_TYPE_ALEA);
}
if (!$ok) {
uksort($lettres_communes, function($a, $b) use ($lettres_communes) {
return $lettres_communes[$b] <=> $lettres_communes[$a];
});
$lettres_communes = array_slice($lettres_communes, 0, 3);
foreach ($lettres_communes as $lettre => $_) {
$this->grille[$y][$x] = $lettre;
// Omission des lettres isolées
if ($lettre == CASE_NOIRE
&& ($y - 2 < 0 || $this->grille[$y - 2][$x] == CASE_NOIRE)
&& ($y - 1 < 0 || $x - 1 < 0 || $this->grille[$y - 1][$x - 1] == CASE_NOIRE)
&& ($y - 1 < 0 || $x + 1 >= $this->largeur || $this->grille[$y - 1][$x + 1] == CASE_NOIRE)
) {
continue;
}
$this->mots_utilises[$mot_lig] = true;
if ($l < $this->largeur) {
yield from $this->trouve_une_colonne($l);
} else if ($l + 1 < $this->hauteur) {
yield from $this->trouve_une_ligne($l + 1);
// Omission des doublons
$mots = [];
if ($x == $this->largeur - 1) $mots = explode(CASE_NOIRE, $this->get_ligne($y, $this->largeur));
else if ($lettre == CASE_NOIRE) $mots = explode(CASE_NOIRE, $this->get_ligne($y, $x));
else $mots = [];
$this->lignes[$y] = array_filter($mots, function ($mot) {
return strlen($mot) >= 2;
});
if (count($this->lignes[$y])) {
$mot = array_pop($this->lignes[$y]);
if (strlen($mot > 2) && in_array($mot, array_merge(...$this->lignes, ...$this->colonnes))) continue;
else $this->lignes[$y][] = $mot;
}
if ($y == $this->hauteur - 1) {
$mots = explode(CASE_NOIRE, $this->get_colonne($x, $this->hauteur));
foreach ($mots as $rang => $mot) {
if (strlen($mot) < 2) continue;
if (strlen($mot > 2) && in_array($mot, array_merge(...$this->lignes, ...$this->colonnes))) continue 2;
else $this->colonnes[$x][$rang] = $mot;
}
} else {
$this->colonnes[$x] = [];
}
if ($i < $this->nb_positions - 1) {
yield from $this->gen_grilles($i + 1, $lettres_ligne->branches[$lettre]);
} else {
yield $this;
}
unset($this->mots_utilises[$mot_lig]);
}
}
private function trouve_une_colonne($c) {
global $mots_de_n_lettres;
foreach ($this->mots_commencant_par[$this->hauteur][$this->get_colonne($c, $c + 1)] as $mot_col) {
if (isset($this->mots_utilises[$mot_col])) {
continue;
}
$this->set_colonne($c, $mot_col);
$ok = true;
for ($l = $c; $l < $this->hauteur; $l++) {
if (!isset($this->mots_commencant_par[$this->largeur][$this->get_ligne($l, $c+1)])) {
$ok = false;
break;
}
}
if (!$ok) {
continue;
}
$this->mots_utilises[$mot_col] = true;
if ($c +1 < $this->hauteur) {
yield from $this->trouve_une_ligne($c + 1);
} else if ($c + 1 < $this->largeur) {
yield from $this->trouve_une_colonne($c + 1);
} else {
yield $this;
}
unset($this->mots_utilises[$mot_col]);
}
}
public function hash() {
public function hash()
{
$string = "";
foreach ($this->grille as $ligne) {
foreach ($this->grille as $ligne)
$string .= implode("", $ligne);
}
return hash('sha256', $string);
}
public function __toString() {
return implode(
PHP_EOL,
array_map(
function ($ligne) {
return implode("", $ligne);
},
$this->grille
)
);
}
public function __serialize(): array {
return [
"grille" => $this->grille,
"definitions" => $this->definitions
];
}
public function __unserialize(array $data): void {
$this->grille = $data["grille"];
$this->definitions = $data["definitions"];
}
public function save($id)
{
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
session_id("$this->largeur,$this->hauteur,$id");
session_start(["use_cookies" => false]);
$_SESSION = serialize($this);
}
public function load($id)
{
session_id("$this->largeur,$this->hauteur,$id");
session_start(["use_cookies" => false]);
if (!isset($_SESSION["grille"])) {
return false;
}
unserialize($_SESSION);
return true;
}
public function offsetExists(mixed $offset): bool
{
return isset($this->grille[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->grille[$offset];
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->grille[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->grille[$offset]);
}
}

View File

@ -1,3 +1,5 @@
# mots-croises
# Mots croisés
Générateur de grille de mots croisés en PHP
![screenshot](https://git.malingrey.fr/adrien/mots-croises/raw/branch/main/thumbnail.png)

95
Trie.php Normal file
View File

@ -0,0 +1,95 @@
<?php
class Trie implements ArrayAccess, IteratorAggregate, Countable {
public array $branches = [];
private $nb_branches = 0;
public function arraySet($cles, $valeur) {
$cle = $cles[0];
$this->nb_branches++;
$cles = array_slice($cles, 1);
if ($cles == []) {
$this->branches[$cle] = $valeur;
} else {
if (!isset($this->branches[$cle])) $this->branches[$cle] = new Trie();
$this->branches[$cle]->arraySet($cles, $valeur);
}
}
public function arrayExists($cles) {
$cle = $cles[0];
$cles = array_slice($cles, 1);
if ($cles == []) {
return isset($this->branches[$cle]);
} else {
return isset($this->branches[$cle]) && $this->branches[$cle]->arrayExists($cles);
}
}
public function &arrayGet($cles) {
$cle = $cles[0];
$cles = array_slice($cles, 1);
if ($cles == []) {
return $this->branches[$cle];
} else {
return $this->branches[$cle]->arrayGet($cles);
}
}
public function arrayUnset($cles) {
$cle = $cles[0];
$cles = array_slice($cles, 1);
if ($cles == []) {
unset($this->branches[$cle]);
$this->nb_branches--;
} else {
$this->branches[$cle]->arrayUnset($cles);
$this->nb_branches--;
if (count($this->branches[$cle]) == 0) {
unset($this->branches[$cle]);
}
}
}
public function arrayIterator() {
foreach ($this->branches as $cle => $branche) {
if ($branche instanceof Trie) {
foreach($branche->arrayIterator() as $sous_cles => $feuille) {
yield array_merge([$cle], $sous_cles) => $feuille;
}
} else {
yield [$cle] => $branche;
}
}
}
// ArrayAccess
public function offsetSet($string, $valeur): void {
$this->arraySet(str_split($string), $valeur);
}
public function offsetExists($string): bool {
return $this->arrayExists(str_split($string));
}
public function &offsetGet($string): mixed {
return $this->arrayGet(str_split($string));
}
public function offsetUnset($string): void {
$this->arrayUnset(str_split($string));
}
// IteratorAggregate
public function getIterator(): Traversable {
foreach($this->arrayIterator() as $array => $valeur) {
yield implode("", $array) => $valeur;
}
}
// Countable
public function count(): int {
return $this->nb_branches;
}
}

84
apercu.png.php Normal file
View File

@ -0,0 +1,84 @@
<?php
$largeur = isset($_GET['largeur']) ? (int)$_GET['largeur'] : 200;
$hauteur = isset($_GET['hauteur']) ? (int)$_GET['hauteur'] : 200;
$lignes = isset($_GET['lignes']) ? (int)$_GET['lignes'] : 8;
$colonnes = isset($_GET['colonnes']) ? (int)$_GET['colonnes'] : 8;
$image = imagecreatetruecolor($largeur, $hauteur);
imagesavealpha($image, true);
$blanc = imagecolorallocate($image, 255, 255, 255);
$noir = imagecolorallocate($image, 0, 0, 0);
$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
// Calculer la taille et la position des cases
$min_dimension = min($largeur, $hauteur);
if ($min_dimension <= 16) {
$bordure_exterieure = 0;
} else if ($min_dimension < 32) {
$bordure_exterieure = 1;
} else if ($min_dimension <= 96) {
$bordure_exterieure = 2;
} else if ($min_dimension <= 600) {
$bordure_exterieure = 3;
} else {
$bordure_exterieure = 6;
}
$cote = (int)min(($largeur - 2 * $bordure_exterieure) / $colonnes, ($hauteur - 2 * $bordure_exterieure) / $lignes);
if ($cote < 3) {
$bordure_interieure = 0;
} else if ($min_dimension < 600) {
$bordure_interieure = 1;
} else {
$bordure_interieure = 2;
}
$haut = (int)(($hauteur - $lignes * $cote - 2 * $bordure_exterieure) / 2) + (int)$bordure_exterieure;
$gauche = (int)(($largeur - $colonnes * $cote - 2 * $bordure_exterieure) / 2) + $bordure_exterieure;
$bas = $haut + $lignes * $cote - $bordure_interieure;
$droite = $gauche + $colonnes * $cote - $bordure_interieure;
// Remplir l'image avec un fond transparent
imagefill($image, 0, 0, $transparent);
// Dessiner les bordures extérieures (3 pixels d'épaisseur)
$marge1 = ceil($bordure_exterieure / 2);
$marge2 = floor($bordure_exterieure / 2);
imagesetthickness($image, $bordure_exterieure);
imagerectangle($image, $gauche - $marge1, $haut - $marge1, $droite + $marge2 - 1, $bas + $marge2 - 1, $noir);
imagefilledrectangle($image, $gauche, $haut, $droite - 1, $bas - 1, $blanc);
// Dessiner les lignes et colonnes internes (1 pixel d'épaisseur)
if ($bordure_interieure >= 1) {
imagesetthickness($image, $bordure_interieure);
for ($x = $gauche + $cote - ceil($bordure_interieure / 2); $x < $droite; $x += $cote) {
imageline($image, $x, $haut, $x, $bas, $noir); // Lignes verticales
}
for ($y = $haut + $cote - ceil($bordure_interieure / 2); $y < $bas; $y += $cote) {
imageline($image, $gauche, $y, $droite, $y, $noir); // Lignes horizontales
}
}
// Noicir les cases
if (isset($_GET["grille"])) {
include_once "Grille.php";
$grille = new Grille($lignes, $colonnes);
$id = htmlspecialchars($_GET["grille"]);
$grille->load($id) || $grille->genere($id);
for ($y = 0; $y < $lignes; $y++) {
for ($x = 0; $x < $colonnes; $x++) {
if ($grille[$y][$x] == CASE_NOIRE) {
imagefilledrectangle($image, $gauche + $x * $cote, $haut + $y * $cote, $gauche + ($x + 1) * $cote - 1, $haut + ($y + 1) * $cote - 1, $noir);
}
}
}
}
// Envoyer l'image au navigateur
header('Content-Type: image/png');
imagepng($image);
// Libérer la mémoire
imagedestroy($image);
?>

90
apercu.svg.php Normal file
View File

@ -0,0 +1,90 @@
<?php
$lignes = isset($_GET['lignes']) ? (int)$_GET['lignes'] : 8;
$colonnes = isset($_GET['colonnes']) ? (int)$_GET['colonnes'] : 8;
$bordure = 2;
$marge = $bordure / 2;
$cote = 20;
// Dimensions du SVG
$width = $colonnes * $cote; // Largeur proportionnelle au nombre de colonnes
$height = $lignes * $cote; // Hauteur proportionnelle au nombre de lignes
$rectRadius = 20; // Rayon des coins arrondis du rectangle
// Création du document XML
$doc = new DOMDocument('1.0', 'UTF-8');
$doc->formatOutput = true;
// Élément SVG principal
$svg = $doc->createElement('svg');
$svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
$svg->setAttribute('viewBox', -$marge . " " . -$marge . " " . ($width + $bordure) . " " . ($height + $bordure));
$svg->setAttribute('width', $width + $bordure);
$svg->setAttribute('height', $height + $bordure);
$doc->appendChild($svg);
// Rectangle arrondi
$rect = $doc->createElement('rect');
$rect->setAttribute('x', 0);
$rect->setAttribute('y', 0);
$rect->setAttribute('width', $width);
$rect->setAttribute('height', $height);
$rect->setAttribute('rx', $rectRadius);
$rect->setAttribute('ry', $rectRadius);
$rect->setAttribute('fill', 'white');
$rect->setAttribute('stroke', 'black');
$rect->setAttribute('stroke-width', $bordure);
$svg->appendChild($rect);
// Lignes verticales
for ($i = 1; $i < $colonnes; $i++) {
$x = $i * $cote;
$line = $doc->createElement('line');
$line->setAttribute('x1', $x);
$line->setAttribute('y1', $marge);
$line->setAttribute('x2', $x);
$line->setAttribute('y2', $height - $marge);
$line->setAttribute('stroke', '#000');
$line->setAttribute('stroke-width', 1);
$svg->appendChild($line);
}
// Lignes horizontales
for ($i = 1; $i < $lignes; $i++) {
$y = $i * $cote;
$line = $doc->createElement('line');
$line->setAttribute('x1', $marge);
$line->setAttribute('y1', $y);
$line->setAttribute('x2', $width - $marge);
$line->setAttribute('y2', $y);
$line->setAttribute('stroke', '#000');
$line->setAttribute('stroke-width', 1);
$svg->appendChild($line);
}
// Noicir les cases
if (isset($_GET["grille"])) {
include_once "Grille.php";
$grille = new Grille($lignes, $colonnes);
$id = htmlspecialchars($_GET["grille"]);
$grille->load($id) || $grille->genere($id);
for ($y = 0; $y < $lignes; $y++) {
for ($x = 0; $x < $colonnes; $x++) {
if ($grille[$y][$x] == CASE_NOIRE) {
$rect = $doc->createElement('rect');
$rect->setAttribute('x', $x * $cote);
$rect->setAttribute('y', $y * $cote);
$rect->setAttribute('width', $cote);
$rect->setAttribute('height', $cote);
$rect->setAttribute('fill', 'black');
$svg->appendChild($rect);
}
}
}
}
header('Content-Type: image/svg+xml');
echo $doc->saveXML();

5129
dico.csv

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +1,58 @@
<?php
include_once "Trie.php";
const CASE_NOIRE = " ";
function dico($longueur_max) {
$transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: Upper(); :: NFC;', Transliterator::FORWARD);
$dico = [[new Trie()]];
for ($longueur = 0; $longueur <= $longueur_max; $longueur++) {
$dico[] = new Trie();
}
if (($lecteur = fopen("dico.csv", "r")) !== FALSE) {
$entete = fgetcsv($lecteur, 0, "\t");
while (($ligne = fgetcsv($lecteur, 0, "\t")) !== FALSE) {
if (
$ligne[0] == NULL
|| substr($ligne[0], 0, 1) == "#"
|| strlen($ligne[0]) > $longueur_max
) continue;
$dico = [];
if (($handle = fopen("dico.csv", "r")) !== FALSE) {
$header = fgetcsv($handle, 0, "\t");
while (($ligne = fgetcsv($handle, 0, "\t")) !== FALSE) {
if (count($ligne) >= 2) {
$mot = $ligne[0];
$definition = $ligne[1];
$dico[$mot] = $definition;
}
}
fclose($handle);
}
$definitions = array_slice($ligne, 1);
$mots_de_n_lettres = [[]];
foreach ($dico as $mot => $definition) {
$n = strlen($mot);
if (!isset($mots_de_n_lettres[$n])) {
$mots_de_n_lettres[$n] = [];
}
$mots_de_n_lettres[$n][] = $mot;
}
function fisherYatesShuffle(&$items, $seed)
{
@mt_srand($seed);
for ($i = count($items) - 1; $i > 0; $i--)
{
$j = @mt_rand(0, $i);
$tmp = $items[$i];
$items[$i] = $items[$j];
$items[$j] = $tmp;
$mot = str_replace("-", CASE_NOIRE, $mot);
$mot = $transliterator->transliterate($mot);
if (strpos($mot, CASE_NOIRE) !== false) {
$mots = explode(CASE_NOIRE, $mot);
$nb_mots = count($mots);
$mot = implode("", $mots);
foreach($definitions as $i => $definition) {
$definitions[$i] = "$definition#$nb_mots";
}
}
function mots_espaces($max, $min=0, $seed=0) {
global $mots_de_n_lettres;
global $dico;
$dico[strlen($mot)][$mot] = $definitions;
}
fclose($lecteur);
}
if ($seed) {
fisherYatesShuffle($mots_de_n_lettres[$max], $seed);
} else {
shuffle($mots_de_n_lettres[$max]);
}
foreach($mots_de_n_lettres[$max] as $mot) {
yield $mot;
}
for ($i = ceil($max / 2); $max - $i -1 >= $min; $i++) {
foreach ($mots_de_n_lettres[$i] as $mot1) {
foreach (mots_espaces($max - $i -1, $min) as $mot2) {
if ($mot1 != $mot2) {
$dico["$mot1 $mot2"] = $dico[$mot1] && $dico[$mot2] ? "{$dico[$mot1]}<br/>{$dico[$mot2]}." : $dico[$mot1] . $dico[$mot2];
yield "$mot1 $mot2";
$dico["$mot2 $mot1"] = $dico[$mot2] && $dico[$mot1] ? "{$dico[$mot2]}<br/>{$dico[$mot1]}." : $dico[$mot2] . $dico[$mot1];
yield "$mot2 $mot1";
return $dico;
}
function mots_espaces($longueur_max) {
$dico = dico($longueur_max);
for ($longueur = 1; $longueur <= $longueur_max; $longueur++) {
for ($position_espace = 1; $position_espace + 1 < $longueur; $position_espace++) {
$mots_suivants = $dico[$longueur - $position_espace - 1];
foreach ($dico[$position_espace]->arrayIterator() as $premier_mot => $definition) {
$premier_mot[] = CASE_NOIRE;
$dico[$longueur]->arraySet($premier_mot, $mots_suivants);
}
}
}
return $dico;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
favicons/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

50
favicons/favicon.svg Normal file
View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 64 64"
version="1.1"
id="svg3"
sodipodi:docname="favicon.svg"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3" />
<sodipodi:namedview
id="namedview3"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="8.3124997"
inkscape:cx="32"
inkscape:cy="32"
inkscape:window-width="1536"
inkscape:window-height="793"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg3" />
<path
fill="#ffffff"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
d="m 63,8 v 48 a 7,7 0 0 1 -7,7 H 8 a 7,7 0 0 1 -7,-7 V 9 A 7,7 0 0 1 8,1 H 56 A 7,7 0 0 1 63,8 Z M 47,17 H 32 v 15 h 15 z"
id="path1" />
<path
d="M 47,17 H 32 v 15 h 15 z"
id="path2"
style="stroke-width:1" />
<path
fill="none"
stroke="#000000"
stroke-width="2"
stroke-miterlimit="10"
d="M 47,63 V 32 m 0,-15 V 1 M 32,63 V 32 m 0,-15 V 1 m -16,0 V 63 M 47,32 h 16 m -31,0 H 1 M 63,47 H 1 M 63,16 H 1"
id="path3" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

189
index.php
View File

@ -1,58 +1,90 @@
<?php
ini_set('display_errors', 1);
ini_set('html_errors', 1);
ini_set('error_reporting', E_ALL);
const HAUTEUR_PAR_DEFAUT = 6;
const LARGEUR_PAR_DEFAUT = 6;
$id = filter_input(INPUT_GET, 'grille', FILTER_VALIDATE_REGEXP, [
"options" => [
"regexp" => "/^[a-f0-9]{13}$/"
]
]);
if (!$id) {
$_GET["grille"] = uniqid();
header("Location: " . $_SERVER['PHP_SELF'] . "?" . http_build_query($_GET));
exit;
}
include_once "dico.php";
include_once "Grille.php";
const HAUTEUR_DEFAUT = 8;
const HAUTEUR_MIN = 2;
const HAUTEUR_MAX = 10;
const LARGEUR_DEFAUT = 8;
const LARGEUR_MIN = 2;
const LARGEUR_MAX = 10;
$hauteur = filter_input(INPUT_GET, 'lignes', FILTER_VALIDATE_INT, [
"options" => [
"default" => HAUTEUR_PAR_DEFAUT,
"min_range" => 2,
"max_range" => 30
"default" => HAUTEUR_DEFAUT,
"min_range" => HAUTEUR_MIN,
"max_range" => HAUTEUR_MAX
]
]);
$largeur = filter_input(INPUT_GET, 'colonnes', FILTER_VALIDATE_INT, [
"options" => [
"default" => LARGEUR_PAR_DEFAUT,
"min_range" => 2,
"max_range" => 30
"default" => LARGEUR_DEFAUT,
"min_range" => LARGEUR_MIN,
"max_range" => LARGEUR_MAX
]
]);
$grille = new Grille($hauteur, $largeur, $id);
$grille = new Grille($hauteur, $largeur);
$basedir = $_SERVER["REQUEST_SCHEME"]."://".$_SERVER["HTTP_HOST"].dirname($_SERVER["DOCUMENT_URI"]);
if (!isset($_GET["grille"]) || $_GET["grille"] == "") {
do {
$id = uniqid();
$grille_valide = $grille->genere($id);
} while (!$grille_valide);
$_GET["grille"] = $id;
header("Location: $basedir/?" . http_build_query($_GET));
} else {
$id = htmlspecialchars($_GET["grille"]);
$grille_valide = $grille->load($id) || $grille->genere($id);
}
function formatter_definition($definition) {
if (strpos($definition, "#") !== false) {
[$definition, $nb_mots] = explode("#", $definition);
$nb_mots = " <small>($nb_mots mots)</small>";
} else {
$nb_mots = "";
}
if (strpos($definition, "@") !== false) {
[$definition, $auteur] = explode("@", $definition);
$auteur = " <small><em>$auteur</em></small>";
} else {
$auteur = "";
}
return $definition;
}
?>
<!DOCTYPE HTML>
<html>
<html lang="fr-FR" dir="ltr" prefix="og: https://ogp.me/ns#">
<head>
<meta charset="utf-8">
<title>Mots croisés</title>
<title>🄼🄾🅃🅂▣🄲🅁🄾🄸🅂🄴🅂</title>
<link rel="stylesheet" href="style.css">
<link rel="icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="favicon.ico" />
<link rel="icon" type="image/svg+xml" href="apercu.svg.php?grille=<?=$id?>&lignes=<?=$hauteur?>&colonnes=<?=$largeur?>">
<link rel="icon" type="image/png" sizes="96x96" href="apercu.png.php?grille=<?=$id?>&lignes=<?=$hauteur?>&colonnes=<?=$largeur?>&largeur=96&hauteur=96" />
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="🄼🄾🅃🅂 🄲🅁🄾🄸🅂🄴🅂" />
<link rel="manifest" href="site.webmanifest" />
<meta property="og:title" content="🄼🄾🅃🅂▣🄲🅁🄾🄸🅂🄴🅂"/>
<meta property="og:type" content="game"/>
<meta property="og:url" content="<?=$basedir?>"/>
<meta property="og:image" content="<?=$basedir?>/apercu.png.php?grille=<?=$id?>&lignes=<?=$hauteur?>&colonnes=<?=$largeur?>&largeur=1200&hauteur=630"/>
<meta property="og:image:width" content="1200"/>
<meta property="og:image:height" content="630"/>
<meta property="og:locale" content="fr_FR"/>
<meta property="og:site_name" content="<?=$_SERVER["HTTP_HOST"]?>"/>
</head>
<body>
<h1>
<form id="grilleForm" method="get" location=".">
<h1 class="large width">
<table>
<tbody>
<tr>
@ -79,53 +111,96 @@ $grille = new Grille($hauteur, $largeur, $id);
</tbody>
</table>
</h1>
<form id="grilleForm" class="grille" method="get" location=".">
<input type="hidden" id="hauteur" name="lignes" value="<?= $hauteur ?>" />
<input type="hidden" id="largeur" name="colonnes" value="<?= $largeur ?>" />
<input type="hidden" id="solution_hashee" value="<?= $grille->hash() ?>" />
<h1 class="small width">Mots■croisés</h1>
<div class="grille-et-definitions">
<?php if ($grille_valide): ?>
<div class="grille">
<table>
<tr>
<th></th>
<?php for ($c = 0; $c < $largeur; $c++): ?>
<th><?= chr($c + 65) ?></th>
<?php for ($x = 0; $x < $largeur; $x++): ?>
<th><?= chr($x + 65) ?></th>
<?php endfor; ?>
<th></th>
</tr>
<?php for ($l = 0; $l < $hauteur; $l++): ?>
<?php for ($y = 0; $y < $hauteur; $y++): ?>
<tr>
<th><?= $l + 1 ?></th>
<?php for ($c = 0; $c < $largeur; $c++): ?>
<td class="case <?= $grille->grille[$l][$c] == " " ? "noire" : "blanche" ?>">
<?php if ($grille->grille[$l][$c] == " "): ?>
<input type="text" maxlength="1" size="1" value=" " disabled />
<?php else: ?>
<input type="text" maxlength="1" size="1" required pattern="[A-Z]" />
<?php endif; ?>
<th><?= $y + 1 ?></th>
<?php for ($x = 0; $x < $largeur; $x++): ?>
<?php if ($grille[$y][$x] == CASE_NOIRE): ?>
<td class="case noire">
<input id="<?= chr($x + 65) . ($y + 1) ?>" type="text" maxlength="1" size="1" value="<?= CASE_NOIRE ?>" disabled />
</td>
<?php else: ?>
<td class="case blanche">
<input id="<?= chr($x + 65) . ($y + 1) ?>" type="text" maxlength="1" size="1" pattern="[A-Z]" placeholder="<?= $grille[$y][$x] ?>"
title="<?= strip_tags("→ " . implode("\n→ ", array_map("formatter_definition", $grille->definitions["horizontales"][$y] ?? [])) . "\n↓ " . implode("\n↓ ", array_map("formatter_definition", $grille->definitions["verticales"][$x] ?? []))) ?>" />
</td>
<?php endif; ?>
<?php endfor; ?>
</tr>
<?php endfor; ?>
</table>
</form>
<div class="definitions">
<div class="horizontales">
</div>
<div class="definitions horizontales">
<h2>Horizontalement</h2>
<ol type="1">
<?php foreach ($grille->definitions["horizontales"] as $y => $definitions): ?>
<li>
<?php if (count($definitions)): ?>
<?php if (count($definitions) == 1): ?>
<?= formatter_definition($definitions[0]) ?>
<?php else: ?>
<ol>
<?php for ($l = 0; $l < $hauteur; $l++): ?>
<li><?= $dico[$grille->get_ligne($l, $largeur)] ?></li>
<?php endfor; ?>
<?php foreach ($definitions as $definition) : ?>
<li><?= formatter_definition($definition) ?></li>
<?php endforeach ?>
</ol>
<?php endif ?>
<?php endif ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<div class="verticales">
<div class="definitions verticales">
<h2>Verticalement</h2>
<ol type="A">
<?php for ($c = 0; $c < $largeur; $c++): ?>
<li><?= $dico[$grille->get_colonne($c, $hauteur)] ?></li>
<?php endfor; ?>
<?php foreach ($grille->definitions["verticales"] as $x => $definitions): ?>
<li>
<?php if (count($definitions)): ?>
<?php if (count($definitions) == 1): ?>
<?= formatter_definition($definitions[0]) ?>
<?php else: ?>
<ol>
<?php foreach ($definitions as $definition) : ?>
<li><?= formatter_definition($definition) ?></li>
<?php endforeach ?>
</ol>
<?php endif ?>
<?php endif ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<input type="hidden" id="solution_hashee" value="<?= $grille->hash() ?>" />
<?php else: http_response_code(500); ?>
<h3 class="erreur">Erreur de génération de la grille</h3>
<?php endif ?>
</div>
<div class="nouvelle-grille">
<img src="favicons/favicon.svg" width="16" height="16">
<button type="submit">Nouvelle grille</button>
de
<input type="number" id="lignes"<?= isset($_GET["lignes"])? ' name="lignes"': "" ?> value="<?= $hauteur ?>" min="<?=HAUTEUR_MIN?>" max="<?=HAUTEUR_MAX?>"/>
lignes et
<input type="number" id="colonnes"<?= isset($_GET["colonnes"])? ' name="colonnes"': "" ?> value="<?= $largeur ?>" min="<?=LARGEUR_MIN?>" max="<?=LARGEUR_MAX?>"/>
colonnes
</div>
</form>
<script src="script.js"></script>
<script>navigator?.serviceWorker.register('service-worker.js')</script>
</body>
</html>

117
script.js
View File

@ -1,81 +1,102 @@
const inputs = Array.from(grilleForm.querySelectorAll('input[type="text"]'));
let nb_cases = inputs.length;
async function sha256(text) {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}
let inputs = Array.from(grilleForm.querySelectorAll(".grille input"));
let largeur = Number(colonnes.value);
let nb_cases = inputs.length;
let index = 0;
inputs.forEach(input => {
for (let input of inputs) {
input.index = index++;
input.x = input.index % largeur;
input.y = Math.floor(input.index / largeur);
input.onfocus = function (event) {
for (li of document.querySelectorAll(
`.definitions.horizontales > ol > li:nth-child(${
input.y + 1
}), .definitions.verticales > ol > li:nth-child(${input.x + 1})`
)) {
li.classList.add("selectionee");
}
for (li of document.querySelectorAll(
`.definitions.horizontales > ol > li:not(:nth-child(${
input.y + 1
})), .definitions.verticales > ol > li:not(:nth-child(${input.x + 1}))`
)) {
li.classList.add("non-selectionee");
}
input.select();
};
input.onkeydown = function (event) {
console.log(largeur);
largeur = Number(largeur.value);
console.log(largeur);
next_input = null;
switch (event.key) {
case 'ArrowUp':
inputs[(input.index - largeur + nb_cases) % nb_cases].focus();
case "ArrowUp":
next_input = inputs[(input.index - largeur + nb_cases) % nb_cases];
break;
case 'ArrowDown':
inputs[(input.index + largeur) % nb_cases].focus();
case "ArrowDown":
next_input = inputs[(input.index + largeur) % nb_cases];
break;
case 'ArrowLeft':
inputs[(input.index - 1 + nb_cases) % nb_cases].focus();
case "ArrowLeft":
next_input = inputs[(input.index - 1 + nb_cases) % nb_cases];
break;
case 'ArrowRight':
inputs[(input.index + 1) % nb_cases].focus();
case "ArrowRight":
next_input = inputs[(input.index + 1) % nb_cases];
break;
}
if (next_input) {
next_input.focus();
next_input.select();
event.preventDefault();
}
};
input.oninput = function (event) {
this.value = this.value.toUpperCase();
if (!input.checkValidity()) {
input.value = '';
input.value = "";
}
if (grilleForm.checkValidity()) {
sha256(inputs.map(input => input.value).join('')).then(hash => {
if (hash == '<?= $grille->hash() ?>') {
if (confirm('Bravo ! \nUne nouvelle partie ?')) {
location.href = location.href.replace(/grille=[a-f0-9]{13}&/, '');
}
}
});
}
};
});
document.querySelectorAll('input').forEach(input => {
input.onkeydown = function (event) {
switch (event.key) {
case 'ArrowRight':
inputs[(input.index + 1) % nb_cases].focus();
break;
}
};
input.oninput = function (event) {
this.value = this.value.toUpperCase();
if (!input.checkValidity()) {
input.value = '';
}
if (grilleForm.checkValidity()) {
sha256(inputs.map(input => input.value).join('')).then(hash => {
if (
inputs.every((input) => input.value.length == 1) &&
grilleForm.checkValidity()
) {
sha256(inputs.map((input) => input.value).join("")).then((hash) => {
if (hash == solution_hashee.value) {
if (confirm('Bravo ! \nUne nouvelle partie ?')) {
if (confirm("Bravo !\nUne nouvelle partie ?")) {
grilleForm.submit();
}
}
});
}
};
});
input.onblur = function (event) {
for (li of document.querySelectorAll(
`.definitions.horizontales > ol > li:nth-child(${
input.y + 1
}), .definitions.verticales > ol > li:nth-child(${input.x + 1})`
)) {
li.classList.remove("selectionee");
}
for (li of document.querySelectorAll(
`.definitions.horizontales > ol > li:not(:nth-child(${
input.y + 1
})), .definitions.verticales > ol > li:not(:nth-child(${input.x + 1}))`
)) {
li.classList.remove("non-selectionee");
}
};
}
for (let input of grilleForm.querySelectorAll(".nouvelle-grille input")) {
input.onfocus = function (event) {
input.name = input.id;
input.select();
};
}

86
service-worker.js Normal file
View File

@ -0,0 +1,86 @@
/*
Copyright 2015, 2019, 2020 Google LLC. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
// Customize this with a different URL if needed.
const OFFLINE_URL = "index.php";
self.addEventListener("install", (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the
// response isn't fulfilled from the HTTP cache; i.e., it will be from
// the network.
await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
})()
);
// Force the waiting service worker to become the active service worker.
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
(async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ("navigationPreload" in self.registration) {
await self.registration.navigationPreload.enable();
}
})()
);
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
// We only want to call event.respondWith() if this is a navigation request
// for an HTML page.
if (event.request.mode === "navigate") {
event.respondWith(
(async () => {
try {
// First, try to use the navigation preload response if it's supported.
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
// Always try the network first.
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
// catch is only triggered if an exception is thrown, which is likely
// due to a network error.
// If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called.
console.log("Fetch failed; returning offline page instead.", error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})()
);
}
// If our if() condition is false, then this fetch handler won't intercept the
// request. If there are any other fetch handlers registered, they will get a
// chance to call event.respondWith(). If no fetch handlers call
// event.respondWith(), the request will be handled by the browser as if there
// were no service worker involvement.
});

21
site.webmanifest Normal file
View File

@ -0,0 +1,21 @@
{
"name": "🄼🄾🅃🅂 🄲🅁🄾🄸🅂🄴🅂",
"short_name": "🄼🄾🅃🅂 🄲🅁🄾🄸🅂🄴🅂",
"icons": [
{
"src": "favicons/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "favicons/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

299
style.css
View File

@ -1,19 +1,40 @@
body {
margin: 0;
padding: 1rem;
height: calc(100vh - 2rem);
padding: 0;
background-color: white;
font-family: Times, "Times New Roman", Georgia, serif;
}
form {
display: flex;
padding: 1rem;
min-height: calc(100vh - 2rem);
flex-flow: column;
justify-content: space-evenly;
justify-content: space-between;
}
h1 {
margin: 0;
letter-spacing: 0.2em;
}
h1.large.width {
display: inherit;
padding-top: 1rem;
}
h1.small.width {
display: none;
}
h1 table {
margin: auto;
line-height: 0.7;
line-height: 0.8;
}
h1 td {
width: 0.7em;
text-align: center;
}
h1,
@ -22,34 +43,52 @@ h2 {
text-align: center;
}
.grille-et-definitions {
display: flex;
flex-flow: row;
justify-content: space-evenly;
flex-wrap: wrap;
height: max-content;
gap: 1em;
}
.grille {
display: flex;
}
.grille table {
border-collapse: collapse;
margin: 0 auto;
}
.grille tr:nth-of-type(2) td {
border-top: 3px solid black;
}
.grille tr:last-of-type td {
border-bottom: 3px solid black;
}
.grille td:first-of-type {
border-left: 3px solid black;
}
.grille td:last-child {
border-right: 3px solid black;
}
.grille td {
border: 1px solid black;
padding: 2px;
margin: auto;
height: fit-content;
}
.grille th,
.grille td {
width: 30px;
height: 30px;
width: 2rem;
height: 2rem;
text-align: center;
vertical-align: middle;
}
.grille td {
width: 2rem;
height: 2rem;
border: 1px solid black;
padding: 2px;
background-color: white;
}
.grille tr:nth-of-type(2) td {
border-top-width: 3px;
}
.grille tr:last-of-type td {
border-bottom-width: 3px;
}
.grille td:first-of-type {
border-left-width: 3px;
}
.grille td:last-child {
border-right-width: 3px;
}
.grille .case.noire {
@ -63,22 +102,224 @@ h2 {
padding: 0;
text-align: center;
font-size: 1.2em;
font-family: "Comic Sans MS", "Comic Sans", sans;
color: darkblue;
background-color: white;
}
.grille input[disabled] {
color: black;
background-color: black;
}
.grille input::placeholder {
color: transparent;
}
.definitions {
width: 30%;
}
.definitions.horizontales {
order: -1;
}
.definitions ol {
padding-left: 2em;
}
.definitions > div > ol > li::marker {
font-weight: bold;
}
.definitions li ol {
padding-left: 0em;
list-style: parenthese;
}
@counter-style parenthese {
system: extends decimal;
suffix: ") ";
}
.definitions li li {
margin-left: 0.8em;
counter-increment: count;
}
.definitions li li::marker {
font-size: small;
}
.definitions em {
text-wrap: nowrap;
}
.definitions.case ol.horizontales {
list-style-type: "→ ";
}
.definitions.case ol.verticales {
list-style-type: "↓ ";
}
.definitions li {
transition: opacity 0.3s;
}
.definitions li.non-selectionee {
opacity: 30%;
}
.erreur {
text-align: center;
}
.nouvelle-grille img {
margin: 0 0.5em;
}
.nouvelle-grille {
margin: 1em auto 0 auto;
display: flex;
justify-content: space-evenly;
align-items: baseline;
}
.nouvelle-grille button,
.nouvelle-grille input {
border: none;
background: none;
color: inherit;
font-family: inherit;
font-size: 1em;
cursor: pointer;
}
.nouvelle-grille button {
text-decoration: underline;
}
.nouvelle-grille button:hover {
color: #2a6496;
}
.nouvelle-grille button:active {
color: darkorchid;
}
.nouvelle-grille input {
-moz-appearance: textfield;
appearance: textfield;
margin: 0;
padding: 0;
text-align: center;
text-decoration: underline dotted;
width: 1em;
}
.nouvelle-grille input::-webkit-inner-spin-button,
.nouvelle-grille input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
@media (max-width: 1024px) {
h1.large.width {
display: none;
}
h1.small.width {
display: initial;
}
h1 table {
line-height: 0.7;
}
.grille {
width: 100%;
margin: auto;
}
.definitions {
width: 45%;
}
.definitions.horizontales {
order: inherit;
}
}
@media (max-width: 640px) {
* {
box-sizing: border-box;
}
body {
width: auto;
margin: 0;
padding: 1rem 0;
}
h1.large.width {
display: none;
}
h1.small.width {
display: none;
}
.grille td {
width: 2.5rem;
height: 2.5rem;
}
h2 {
font-size: 1.2em;
margin: 1em 0 0.5em 0;
}
.definitions.horizontales,
.definitions.verticales {
width: 45%;
width: 100%;
}
.definitions li::marker {
font-weight: bold;
.definitions > ol {
margin: 0;
}
.definitions li.non-selectionee {
visibility: hidden;
height: 0;
margin: 0;
padding: 0;
}
}
@media (max-device-width: 768px) and (orientation: landscape) {
html {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
}
@media (prefers-color-scheme: dark) {
body,
button {
background-color: #02081a;
color: #c6c6c6;
}
.grille td,
.grille input {
background-color: #edeeee;
}
.nouvelle-grille button:hover {
color: #479fec;
}
.nouvelle-grille button:active {
color: orchid;
}
}

144
test.py
View File

@ -1,144 +0,0 @@
import csv
from re import compile, match
from random import choice, sample, randrange
from collections import defaultdict
from math import ceil
from itertools import product, chain
dico = defaultdict(list)
with open("dico.csv", "r", encoding="utf-8") as fichier:
for mot, definition in csv.reader(fichier, delimiter="\t"):
if not mot.startswith("#"):
dico[mot].append(definition)
mots_de_n_lettres = defaultdict(set)
for mot in dico:
mots_de_n_lettres[len(mot)].add(mot)
def mots_espaces(n):
for mot in mots_de_n_lettres[n]:
yield mot
# for mot in mots_de_n_lettres[n-1]:
# yield f"{mot} "
# yield f" {mot}"
for i in range(1, ceil(n / 2)):
for mot1, mot2 in product(mots_de_n_lettres[i], mots_espaces(n - i - 1)):
yield f"{mot1} {mot2}"
yield f"{mot2} {mot1}"
# for mot1, mot2 in product(mots_de_n_lettres[i], mots_espaces(n - i - 2)):
# yield f" {mot1} {mot2}"
# yield f"{mot2} {mot1} "
# for mot1, mot2 in product(mots_de_n_lettres[i-1], mots_espaces(n - i - 1)):
# yield f" {mot1} {mot2}"
# yield f"{mot2} {mot1} "
class Ligne:
def __init__(self, grille):
self.grille = grille
def __getitem__(self, n):
return "".join(self.grille[n])
def __setitem__(self, n, mot):
self.grille[n] = list(mot)
class Colonne:
def __init__(self, grille):
self.grille = grille
def __getitem__(self, n):
return "".join(ligne[n] for ligne in self.grille)
def __setitem__(self, n, mot):
for i, char in enumerate(mot):
self.grille[i][n] = char
class Grille:
def __init__(self, hauteur, largeur):
self.hauteur = hauteur
self.largeur = largeur
self.grille = [["." for _ in range(largeur)] for _ in range(hauteur)]
self.ligne = Ligne(self.grille)
self.colonne = Colonne(self.grille)
self.mots_commencant_par = defaultdict(lambda: defaultdict(list))
for dimension in (hauteur,) if hauteur == largeur else (hauteur, largeur):
for mot in mots_espaces(dimension):
for i in range(dimension+1):
self.mots_commencant_par[dimension][mot[:i]].append(mot)
self.grilles = self.genere_grilles()
next(self.grilles)
def __iter__(self):
return self
def __next__(self):
return next(self.grilles)
def genere_grilles(self):
print(f"Grille({self.hauteur}, {self.largeur})")
yield from self.trouve_une_ligne(0)
def trouve_une_ligne(self, l):
for mot in self.mots_commencant_par[self.largeur][self.ligne[l][:l]]:
self.ligne[l] = mot
if all(
self.colonne[c][:l+1] in self.mots_commencant_par[self.hauteur]
for c in range(l, self.largeur)
):
if l < self.largeur:
yield from self.trouve_une_colonne(l)
elif l + 1 < self.hauteur:
yield from self.trouve_une_ligne(l + 1)
else:
yield self
def trouve_une_colonne(self, c):
for mot in self.mots_commencant_par[self.hauteur][self.colonne[c][:c+1]]:
self.colonne[c] = mot
if all(
self.ligne[l][:c+1] in self.mots_commencant_par[self.largeur]
for l in range(c, self.largeur)
):
if c + 1 < self.hauteur:
yield from self.trouve_une_ligne(c + 1)
elif c + 1 < self.largeur:
yield from self.trouve_une_colonne(c + 1)
else:
yield self
def __str__(self):
return (
" "
+ " ".join(chr(65 + i) for i in range(self.largeur))
+ "\n"
+ "\n".join(
f"{i + 1:2} " + " ".join(ligne) for i, ligne in enumerate(self.grille)
)
)
def __repr__(self):
return self.__str__()
if __name__ == "__main__":
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *exc_info):
end = time.time()
print(f"Execution time: {end - self.start:.2f} seconds")
for n in range(2, 14):
with Timer():
print(Grille(n, n))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

12
vercel.json Normal file
View File

@ -0,0 +1,12 @@
{
"functions": {
"api/*.php": {
"runtime": "vercel-php@0.7.3"
}
},
"routes": [
{ "src": "/(.*)", "dest": "/index.php" }
]
}