29 changed files with 134615 additions and 1 deletions
@ -0,0 +1,7 @@
|
||||
node_modules/ |
||||
js/ |
||||
public/mots.txt |
||||
public/motsATrouve.txt |
||||
public/motsATrouveNettoyes.txt |
||||
public/motsNettoyes.txt |
||||
ts/mots/motsATrouver.prod.ts |
@ -0,0 +1,30 @@
|
||||
{ |
||||
"name": "sutom", |
||||
"version": "1.0.0", |
||||
"description": "Jeu de lettres en ligne (et en français)", |
||||
"main": "js/main.js", |
||||
"dependencies": { |
||||
"@types/express": "^4.17.13", |
||||
"express": "^4.17.2", |
||||
"readline-sync": "^1.4.10", |
||||
"requirejs": "^2.3.6", |
||||
"typescript": "^4.5.4" |
||||
}, |
||||
"devDependencies": { |
||||
"ts-node-dev": "^1.1.8", |
||||
"tsc-watch": "^4.6.0" |
||||
}, |
||||
"scripts": { |
||||
"test": "echo \"Error: no test specified\" && exit 1", |
||||
"start": "tsc && node js/server.js & tsc-watch" |
||||
}, |
||||
"keywords": [ |
||||
"wordle", |
||||
"letters", |
||||
"word", |
||||
"word game" |
||||
], |
||||
"author": "JonathanMM <jonathanmm@free.fr>", |
||||
"repository": "https://framagit.org/JonathanMM/sutom", |
||||
"license": "MIT" |
||||
} |
Binary file not shown.
@ -0,0 +1,7 @@
|
||||
AddDefaultCharset UTF-8 |
||||
|
||||
#Force HTTPS |
||||
RewriteEngine On |
||||
|
||||
RewriteCond %{SERVER_PORT} 80 |
||||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] |
@ -0,0 +1,150 @@
|
||||
:root { |
||||
--taille-cellule: 48px; |
||||
--epaisseur-bordure-cellule: 1px; |
||||
--epaisseur-padding-cellule: 2px; |
||||
--couleur-bien-place: #e7002a; |
||||
--couleur-mal-place: #ffbd00; |
||||
--couleur-fond-grille: #0077c7; |
||||
--couleur-non-trouve: rgb(112, 112, 112); |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: "Roboto Medium"; |
||||
src: url("/fonts/Roboto-Medium.ttf"); |
||||
} |
||||
|
||||
body { |
||||
font-family: "Roboto Medium", Ubuntu, Arial, Helvetica, sans-serif; |
||||
font-size: 32px; |
||||
background-color: #2b2b2b; |
||||
height: 100vh; |
||||
text-align: center; |
||||
color: white; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
#contenu { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-left: 25%; |
||||
margin-right: 25%; |
||||
justify-content: space-between; |
||||
height: 100%; |
||||
} |
||||
|
||||
@media (max-width: 1024px) { |
||||
#contenu { |
||||
margin-left: 2px; |
||||
margin-right: 2px; |
||||
} |
||||
} |
||||
|
||||
#grille { |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
background-color: var(--couleur-fond-grille); |
||||
} |
||||
|
||||
#grille table { |
||||
border-spacing: 0; |
||||
} |
||||
|
||||
#grille td { |
||||
width: calc(var(--taille-cellule) - 2 * var(--epaisseur-padding-cellule)); |
||||
height: calc(var(--taille-cellule) - 2 * var(--epaisseur-padding-cellule)); |
||||
text-align: center; |
||||
position: relative; |
||||
padding: var(--epaisseur-padding-cellule); |
||||
color: white; |
||||
border: 1px solid white; |
||||
z-index: 0; |
||||
} |
||||
|
||||
#grille td:not(.resultat) { |
||||
background-color: #0077c7; |
||||
} |
||||
|
||||
#grille td.resultat::after { |
||||
width: calc(var(--taille-cellule)); |
||||
height: calc(var(--taille-cellule)); |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
z-index: -1; |
||||
content: " "; |
||||
} |
||||
|
||||
#grille td.mal-place::after { |
||||
background-color: var(--couleur-mal-place); |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
#grille td.bien-place::after { |
||||
background-color: var(--couleur-bien-place); |
||||
} |
||||
|
||||
#grille td.non-trouve::after { |
||||
background-color: var(--couleur-fond-grille); |
||||
} |
||||
|
||||
#fin-de-partie-panel, |
||||
#victoire-panel, |
||||
#defaite-panel { |
||||
display: none; |
||||
|
||||
font-size: 24px; |
||||
} |
||||
|
||||
#input-area { |
||||
margin-top: 0.5em; |
||||
margin-bottom: 2em; |
||||
} |
||||
|
||||
.input-ligne + .input-ligne { |
||||
margin-top: 0.5em; |
||||
} |
||||
|
||||
.input-lettre { |
||||
font-size: 18px; |
||||
display: inline-block; |
||||
border: 1px solid white; |
||||
padding: 0.5em; |
||||
user-select: none; |
||||
min-width: 0.5em; |
||||
} |
||||
|
||||
.input-lettre.lettre-bien-place { |
||||
background: var(--couleur-bien-place); |
||||
} |
||||
|
||||
.input-lettre.lettre-mal-place { |
||||
background: var(--couleur-mal-place); |
||||
} |
||||
|
||||
.input-lettre.lettre-non-trouve { |
||||
color: var(--couleur-non-trouve); |
||||
border: 1px solid var(--couleur-non-trouve); |
||||
} |
||||
|
||||
.input-lettre:hover, |
||||
.input-lettre:active { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
#regles-panel { |
||||
font-size: 14px; |
||||
text-align: left; |
||||
} |
||||
|
||||
#regles-panel a, |
||||
#regles-panel a:visited, |
||||
#fin-de-partie-panel a, |
||||
#fin-de-partie-panel a:visited { |
||||
color: white; |
||||
} |
||||
|
||||
#notification { |
||||
opacity: 0; |
||||
transition: opacity linear 1s; |
||||
} |
@ -0,0 +1,26 @@
|
||||
import ListeMotsProposables from "./mots/listeMotsProposables"; |
||||
import MotsATrouver from "./mots/motsATrouver"; |
||||
export default class Dictionnaire { |
||||
public constructor() {} |
||||
|
||||
public getMot(): string { |
||||
let aujourdhui = new Date().getTime(); |
||||
let origine = new Date(2022, 0, 8).getTime(); |
||||
|
||||
let numeroGrille = Math.floor((aujourdhui - origine) / (24 * 3600 * 1000)); |
||||
|
||||
return MotsATrouver.Liste[numeroGrille]; |
||||
} |
||||
|
||||
public estMotValide(mot: string): boolean { |
||||
mot = this.nettoyerMot(mot); |
||||
return mot.length >= 6 && mot.length <= 9 && ListeMotsProposables.Dictionnaire.includes(mot); |
||||
} |
||||
|
||||
public nettoyerMot(mot: string): string { |
||||
return mot |
||||
.normalize("NFD") |
||||
.replace(/[\u0300-\u036f]/g, "") |
||||
.toUpperCase(); |
||||
} |
||||
} |
@ -0,0 +1,71 @@
|
||||
import LettreResultat from "./lettreResultat"; |
||||
import { LettreStatut } from "./lettreStatut"; |
||||
import NotificationMessage from "./notificationMessage"; |
||||
|
||||
export default class FinDePartiePanel { |
||||
private readonly _reglesPanel: HTMLElement; |
||||
private readonly _finDePartiePanel: HTMLElement; |
||||
private readonly _victoirePanel: HTMLElement; |
||||
private readonly _defaitePanel: HTMLElement; |
||||
private readonly _defaitePanelMot: HTMLElement; |
||||
private readonly _resume: HTMLPreElement; |
||||
private readonly _resumeBouton: HTMLElement; |
||||
|
||||
private _resumeTexte: string = ""; |
||||
|
||||
public constructor() { |
||||
this._reglesPanel = document.getElementById("regles-panel") as HTMLElement; |
||||
this._finDePartiePanel = document.getElementById("fin-de-partie-panel") as HTMLElement; |
||||
this._victoirePanel = document.getElementById("victoire-panel") as HTMLElement; |
||||
this._defaitePanel = document.getElementById("defaite-panel") as HTMLElement; |
||||
this._defaitePanelMot = document.getElementById("defaite-panel-mot") as HTMLElement; |
||||
this._resume = document.getElementById("fin-de-partie-panel-resume") as HTMLPreElement; |
||||
this._resumeBouton = document.getElementById("fin-de-partie-panel-resume-bouton") as HTMLElement; |
||||
|
||||
this._resumeBouton.addEventListener("click", (event) => { |
||||
event.stopPropagation(); |
||||
if (!navigator.clipboard) { |
||||
NotificationMessage.ajouterNotification("Votre navigateur n'est pas compatible"); |
||||
} |
||||
|
||||
navigator.clipboard.writeText(this._resumeTexte + "\n\nhttps://sutom.nocle.fr"); |
||||
|
||||
NotificationMessage.ajouterNotification("Résumé copié dans le presse papier"); |
||||
}); |
||||
} |
||||
|
||||
public genererResume(aBonneReponse: boolean, resultats: Array<Array<LettreResultat>>): void { |
||||
let resultatsEmojis = resultats.map((mot) => |
||||
mot |
||||
.map((resultat) => resultat.statut) |
||||
.reduce((ligne, statut) => { |
||||
switch (statut) { |
||||
case LettreStatut.BienPlace: |
||||
return ligne + "🟥"; |
||||
case LettreStatut.MalPlace: |
||||
return ligne + "🟡"; |
||||
default: |
||||
return ligne + "🟦"; |
||||
} |
||||
}, "") |
||||
); |
||||
let aujourdhui = new Date().getTime(); |
||||
let origine = new Date(2022, 0, 8).getTime(); |
||||
|
||||
let numeroGrille = Math.floor((aujourdhui - origine) / (24 * 3600 * 1000)) + 1; |
||||
|
||||
this._resumeTexte = "SUTOM #" + numeroGrille + " " + (aBonneReponse ? resultats.length : "-") + "/6\n\n" + resultatsEmojis.join("\n"); |
||||
this._resume.innerText = this._resumeTexte; |
||||
} |
||||
|
||||
public afficher(estVictoire: boolean, motATrouver: string): void { |
||||
this._reglesPanel.style.display = "none"; |
||||
this._finDePartiePanel.style.display = "block"; |
||||
|
||||
if (estVictoire) this._victoirePanel.style.display = "block"; |
||||
else { |
||||
this._defaitePanelMot.innerText = motATrouver; |
||||
this._defaitePanel.style.display = "block"; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,148 @@
|
||||
import Dictionnaire from "./dictionnaire"; |
||||
import Grille from "./grille"; |
||||
import Input from "./input"; |
||||
import LettreResultat from "./lettreResultat"; |
||||
import { LettreStatut } from "./lettreStatut"; |
||||
import FinDePartiePanel from "./finDePartiePanel"; |
||||
import NotificationMessage from "./notificationMessage"; |
||||
import SauvegardeStats from "./sauvegardeStats"; |
||||
import Sauvegardeur from "./sauvegardeur"; |
||||
|
||||
export default class Gestionnaire { |
||||
private readonly _dictionnaire: Dictionnaire; |
||||
private readonly _grille: Grille; |
||||
private readonly _input: Input; |
||||
private readonly _sauvegardeur: Sauvegardeur; |
||||
private readonly _victoirePanel: FinDePartiePanel; |
||||
private readonly _propositions: Array<string>; |
||||
private readonly _resultats: Array<Array<LettreResultat>>; |
||||
|
||||
private _motATrouver: string; |
||||
private _compositionMotATrouver: { [lettre: string]: number }; |
||||
private _maxNbPropositions: number = 6; |
||||
private _datePartieEnCours: Date | undefined; |
||||
private _stats: SauvegardeStats = { partiesJouees: 0, partiesGagnees: 0 }; |
||||
|
||||
public constructor() { |
||||
this._dictionnaire = new Dictionnaire(); |
||||
this._motATrouver = this.choisirMot(); |
||||
this._grille = new Grille(this._motATrouver.length, this._maxNbPropositions, this._motATrouver[0]); |
||||
this._input = new Input(this, this._motATrouver.length); |
||||
this._sauvegardeur = new Sauvegardeur(); |
||||
this._victoirePanel = new FinDePartiePanel(); |
||||
this._propositions = new Array<string>(); |
||||
this._resultats = new Array<Array<LettreResultat>>(); |
||||
this._compositionMotATrouver = this.decompose(this._motATrouver); |
||||
|
||||
this.chargerSauvegarde(); |
||||
} |
||||
|
||||
private chargerSauvegarde(): void { |
||||
let sauvegardePartieEnCours = this._sauvegardeur.chargerSauvegardePartieEnCours(); |
||||
if (sauvegardePartieEnCours) { |
||||
this._datePartieEnCours = sauvegardePartieEnCours.datePartie; |
||||
for (let mot of sauvegardePartieEnCours.propositions) { |
||||
this.verifierMot(mot, true); |
||||
} |
||||
} |
||||
this._stats = this._sauvegardeur.chargerSauvegardeStats() ?? { partiesJouees: 0, partiesGagnees: 0 }; |
||||
} |
||||
|
||||
private enregistrerPartieDansStats(): void { |
||||
this._stats.partiesJouees++; |
||||
if (this._resultats.some((resultat) => resultat.every((item) => item.statut === LettreStatut.BienPlace))) this._stats.partiesGagnees++; |
||||
this._stats.dernierePartie = this._datePartieEnCours; |
||||
|
||||
this._sauvegardeur.sauvegarderStats(this._stats); |
||||
} |
||||
|
||||
private sauvegarderPartieEnCours(): void { |
||||
let datePartieEnCours = this._datePartieEnCours ?? new Date(); |
||||
|
||||
this._sauvegardeur.sauvegarderPartieEnCours(this._propositions, datePartieEnCours); |
||||
} |
||||
|
||||
private choisirMot(): string { |
||||
return this._dictionnaire.nettoyerMot(this._dictionnaire.getMot()); |
||||
} |
||||
|
||||
private decompose(mot: string): { [lettre: string]: number } { |
||||
let composition: { [lettre: string]: number } = {}; |
||||
for (let position = 0; position < mot.length; position++) { |
||||
let lettre = mot[position]; |
||||
if (composition[lettre]) composition[lettre]++; |
||||
else composition[lettre] = 1; |
||||
} |
||||
return composition; |
||||
} |
||||
|
||||
public verifierMot(mot: string, skipAnimation: boolean = false): void { |
||||
mot = this._dictionnaire.nettoyerMot(mot); |
||||
//console.debug(mot + " => " + (this._dictionnaire.estMotValide(mot) ? "Oui" : "non"));
|
||||
if (mot[0] !== this._motATrouver[0] || !this._dictionnaire.estMotValide(mot)) { |
||||
NotificationMessage.ajouterNotification("Ce mot n'est pas valide"); |
||||
return; |
||||
} |
||||
if (!this._datePartieEnCours) this._datePartieEnCours = new Date(); |
||||
let resultats = this.analyserMot(mot); |
||||
let isBonneReponse = resultats.every((item) => item.statut === LettreStatut.BienPlace); |
||||
this._propositions.push(mot); |
||||
this._resultats.push(resultats); |
||||
this._grille.validerMot(mot, resultats, isBonneReponse, skipAnimation); |
||||
this._input.updateClavier(resultats); |
||||
|
||||
if (isBonneReponse || this._propositions.length === this._maxNbPropositions) { |
||||
this._input.bloquer(); |
||||
this._victoirePanel.genererResume(isBonneReponse, this._resultats); |
||||
this._victoirePanel.afficher(isBonneReponse, this._motATrouver); |
||||
this.enregistrerPartieDansStats(); |
||||
} |
||||
|
||||
this.sauvegarderPartieEnCours(); |
||||
} |
||||
|
||||
public actualiserAffichage(mot: string): void { |
||||
this._grille.actualiserAffichage(this._dictionnaire.nettoyerMot(mot)); |
||||
} |
||||
|
||||
private analyserMot(mot: string): Array<LettreResultat> { |
||||
let resultats = new Array<LettreResultat>(); |
||||
mot = mot.toUpperCase(); |
||||
|
||||
let composition = { ...this._compositionMotATrouver }; |
||||
|
||||
for (let position = 0; position < this._motATrouver.length; position++) { |
||||
let lettreATrouve = this._motATrouver[position]; |
||||
let lettreProposee = mot[position]; |
||||
|
||||
if (lettreATrouve === lettreProposee) { |
||||
composition[lettreProposee]--; |
||||
} |
||||
} |
||||
|
||||
for (let position = 0; position < this._motATrouver.length; position++) { |
||||
let lettreATrouve = this._motATrouver[position]; |
||||
let lettreProposee = mot[position]; |
||||
|
||||
let resultat = new LettreResultat(); |
||||
resultat.lettre = lettreProposee; |
||||
|
||||
if (lettreATrouve === lettreProposee) { |
||||
resultat.statut = LettreStatut.BienPlace; |
||||
} else if (this._motATrouver.includes(lettreProposee)) { |
||||
if (composition[lettreProposee] > 0) { |
||||
resultat.statut = LettreStatut.MalPlace; |
||||
composition[lettreProposee]--; |
||||
} else { |
||||
resultat.statut = LettreStatut.NonTrouve; |
||||
} |
||||
} else { |
||||
resultat.statut = LettreStatut.NonTrouve; |
||||
} |
||||
|
||||
resultats.push(resultat); |
||||
} |
||||
|
||||
return resultats; |
||||
} |
||||
} |
@ -0,0 +1,153 @@
|
||||
import LettreResultat from "./lettreResultat"; |
||||
import { LettreStatut } from "./lettreStatut"; |
||||
|
||||
export default class Grille { |
||||
private readonly _grille: HTMLElement; |
||||
private readonly _propositions: Array<string>; |
||||
private readonly _resultats: Array<Array<LettreResultat>>; |
||||
private readonly _longueurMot: number; |
||||
private readonly _maxPropositions: number; |
||||
private _indice: Array<string | undefined>; |
||||
private _motActuel: number; |
||||
|
||||
public constructor(longueurMot: number, maxPropositions: number, indice: string) { |
||||
this._grille = document.getElementById("grille") as HTMLElement; |
||||
//console.log("Chargement de la grille");
|
||||
|
||||
this._longueurMot = longueurMot; |
||||
this._maxPropositions = maxPropositions; |
||||
this._indice = new Array<string | undefined>(longueurMot); |
||||
this._indice[0] = indice; |
||||
|
||||
this._propositions = new Array<string>(); |
||||
this._resultats = new Array<Array<LettreResultat>>(); |
||||
this._motActuel = 0; |
||||
this.afficherGrille(); |
||||
} |
||||
|
||||
private afficherGrille() { |
||||
let table = document.createElement("table"); |
||||
for (let nbMot = 0; nbMot < this._maxPropositions; nbMot++) { |
||||
let ligne = document.createElement("tr"); |
||||
let mot = this._propositions.length <= nbMot ? "" : this._propositions[nbMot]; |
||||
for (let nbLettre = 0; nbLettre < this._longueurMot; nbLettre++) { |
||||
let cellule = document.createElement("td"); |
||||
let contenuCellule: string = ""; |
||||
if (nbMot < this._motActuel || (nbMot === this._motActuel && mot.length !== 0)) { |
||||
if (mot.length <= nbLettre) { |
||||
contenuCellule = "."; |
||||
} else { |
||||
contenuCellule = mot[nbLettre].toUpperCase(); |
||||
} |
||||
} else if (nbMot === this._motActuel) { |
||||
let lettreIndice = this._indice[nbLettre]; |
||||
if (lettreIndice !== undefined) contenuCellule = lettreIndice; |
||||
else contenuCellule = "."; |
||||
} |
||||
if (this._resultats.length > nbMot && this._resultats[nbMot][nbLettre]) { |
||||
let resultat = this._resultats[nbMot][nbLettre]; |
||||
let emoji: string = "🟦"; |
||||
switch (resultat.statut) { |
||||
case LettreStatut.BienPlace: |
||||
emoji = "🟥"; |
||||
cellule.classList.add("bien-place", "resultat"); |
||||
break; |
||||
case LettreStatut.MalPlace: |
||||
emoji = "🟡"; |
||||
cellule.classList.add("mal-place", "resultat"); |
||||
break; |
||||
default: |
||||
emoji = "🟦"; |
||||
cellule.classList.add("non-trouve", "resultat"); |
||||
} |
||||
// console.log(resultat.lettre + " => " + emoji);
|
||||
} |
||||
cellule.innerText = contenuCellule; |
||||
ligne.appendChild(cellule); |
||||
} |
||||
|
||||
table.appendChild(ligne); |
||||
} |
||||
this._grille.innerHTML = ""; |
||||
this._grille.appendChild(table); |
||||
} |
||||
|
||||
public actualiserAffichage(mot: string) { |
||||
this.saisirMot(this._motActuel, mot); |
||||
|
||||
this.afficherGrille(); |
||||
} |
||||
|
||||
public validerMot(mot: string, resultats: Array<LettreResultat>, isBonneReponse: boolean, skipAnimation: boolean = false): void { |
||||
this.saisirMot(this._motActuel, mot); |
||||
this.mettreAJourIndice(resultats); |
||||
this._resultats.push(resultats); |
||||
|
||||
if (!skipAnimation) this.animerResultats(resultats); |
||||
|
||||
if (isBonneReponse) { |
||||
this.bloquerGrille(); |
||||
} else { |
||||
this._motActuel++; |
||||
} |
||||
|
||||
if (skipAnimation) this.afficherGrille(); |
||||
} |
||||
|
||||
private animerResultats(resultats: Array<LettreResultat>): void { |
||||
let table = this._grille.getElementsByTagName("table").item(0); |
||||
if (table === null) { |
||||
this.afficherGrille(); |
||||
return; |
||||
} |
||||
|
||||
let ligne = table.getElementsByTagName("tr").item(this._motActuel); |
||||
if (ligne === null) { |
||||
this.afficherGrille(); |
||||
return; |
||||
} |
||||
|
||||
let td = ligne.getElementsByTagName("td"); |
||||
this.animerLettre(td, resultats, 0); |
||||
} |
||||
|
||||
private animerLettre(td: HTMLCollectionOf<HTMLTableCellElement>, resultats: Array<LettreResultat>, numLettre: number): void { |
||||
if (numLettre >= td.length) { |
||||
this.afficherGrille(); |
||||
return; |
||||
} |
||||
let cellule = td[numLettre]; |
||||
let resultat = resultats[numLettre]; |
||||
cellule.innerHTML = resultat.lettre; |
||||
switch (resultat.statut) { |
||||
case LettreStatut.BienPlace: |
||||
cellule.classList.add("bien-place", "resultat"); |
||||
|
||||
break; |
||||
case LettreStatut.MalPlace: |
||||
cellule.classList.add("mal-place", "resultat"); |
||||
|
||||
break; |
||||
default: |
||||
cellule.classList.add("non-trouve", "resultat"); |
||||
} |
||||
setTimeout((() => this.animerLettre(td, resultats, numLettre + 1)).bind(this), 250); |
||||
} |
||||
|
||||
private mettreAJourIndice(resultats: Array<LettreResultat>): void { |
||||
for (let i = 0; i < this._indice.length; i++) { |
||||
if (!this._indice[i]) { |
||||
this._indice[i] = resultats[i].statut === LettreStatut.BienPlace ? resultats[i].lettre : undefined; |
||||
} |
||||
} |
||||
} |
||||
|
||||
private saisirMot(position: number, mot: string): void { |
||||
if (this._propositions.length <= position) { |
||||
this._propositions.push(""); |
||||
} |
||||
this._propositions[position] = mot; |
||||
} |
||||
|
||||
private bloquerGrille(): void {} |
||||
} |
@ -0,0 +1,154 @@
|
||||
import Gestionnaire from "./gestionnaire"; |
||||
import LettreResultat from "./lettreResultat"; |
||||
import { LettreStatut } from "./lettreStatut"; |
||||
|
||||
export default class Input { |
||||
private readonly _grille: HTMLElement; |
||||
private readonly _inputArea: HTMLElement; |
||||
private readonly _gestionnaire: Gestionnaire; |
||||
|
||||
private _longueurMot: number; |
||||
private _motSaisi: string; |
||||
private _estBloque: boolean; |
||||
|
||||
public constructor(gestionnaire: Gestionnaire, longueurMot: number) { |
||||
this._grille = document.getElementById("grille") as HTMLElement; |
||||
this._inputArea = document.getElementById("input-area") as HTMLElement; |
||||
this._longueurMot = longueurMot; |
||||
this._gestionnaire = gestionnaire; |
||||
this._motSaisi = ""; |
||||
this._estBloque = false; |
||||
|
||||
document.addEventListener( |
||||
"keypress", |
||||
((event: KeyboardEvent) => { |
||||
event.stopPropagation(); |
||||
let touche = event.key; |
||||
|
||||
if (touche === "Enter") { |
||||
this.validerMot(); |
||||
} else if (touche === "Backspace") { |
||||
this.effacerLettre(); |
||||
} else { |
||||
this.saisirLettre(touche); |
||||
} |
||||
}).bind(this) |
||||
); |
||||
|
||||
// Le retour arrière n'est détecté que par keyup
|
||||
document.addEventListener( |
||||
"keyup", |
||||
((event: KeyboardEvent) => { |
||||
event.stopPropagation(); |
||||
let touche = event.key; |
||||
|
||||
if (touche === "Backspace") { |
||||
this.effacerLettre(); |
||||
} |
||||
}).bind(this) |
||||
); |
||||
|
||||
this._inputArea.querySelectorAll(".input-lettre").forEach((lettreDiv) => |
||||
lettreDiv.addEventListener("click", (event) => { |
||||
event.stopPropagation(); |
||||
let div = event.currentTarget; |
||||
if (!div) return; |
||||
let lettre = (div as HTMLElement).dataset["lettre"]; |
||||
if (lettre === undefined) { |
||||
return; |
||||
} else if (lettre === "_effacer") { |
||||
this.effacerLettre(); |
||||
} else if (lettre === "_entree") { |
||||
this.validerMot(); |
||||
} else { |
||||
this.saisirLettre(lettre); |
||||
} |
||||
}) |
||||
); |
||||
} |
||||
|
||||
private effacerLettre(): void { |
||||
if (this._estBloque) return; |
||||
if (this._motSaisi.length === 0) return; |
||||
this._motSaisi = this._motSaisi.substring(0, this._motSaisi.length - 1); |
||||
this._gestionnaire.actualiserAffichage(this._motSaisi); |
||||
} |
||||
|
||||
private validerMot(): void { |
||||
if (this._estBloque) return; |
||||
let mot = this._motSaisi; |
||||
if (mot.length === this._longueurMot) { |
||||
this._gestionnaire.verifierMot(mot); |
||||
this._motSaisi = ""; |
||||
} |
||||
} |
||||
|
||||
private saisirLettre(lettre: string): void { |
||||
if (this._estBloque) return; |
||||
if (this._motSaisi.length >= this._longueurMot) return; |
||||
this._motSaisi += lettre; |
||||
this._gestionnaire.actualiserAffichage(this._motSaisi); |
||||
} |
||||
|
||||
public bloquer(): void { |
||||
this._estBloque = true; |
||||
} |
||||
|
||||
public updateClavier(resultats: Array<LettreResultat>): void { |
||||
if (this._estBloque) return; |
||||
let statutLettres: { [lettre: string]: LettreStatut } = {}; |
||||
// console.log(statutLettres);
|
||||
for (let resultat of resultats) { |
||||
if (!statutLettres[resultat.lettre]) statutLettres[resultat.lettre] = resultat.statut; |
||||
else { |
||||
switch (resultat.statut) { |
||||
case LettreStatut.BienPlace: |
||||
statutLettres[resultat.lettre] = LettreStatut.BienPlace; |
||||
break; |
||||
case LettreStatut.MalPlace: |
||||
if (statutLettres[resultat.lettre] !== LettreStatut.BienPlace) { |
||||
statutLettres[resultat.lettre] = LettreStatut.MalPlace; |
||||
} |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
// console.log(statutLettres);
|
||||
|
||||
let touches = this._inputArea.querySelectorAll(".input-lettre"); |
||||
|
||||
for (let lettre in statutLettres) { |
||||
let statut = statutLettres[lettre]; |
||||
for (let numTouche = 0; numTouche < touches.length; numTouche++) { |
||||
let touche = touches.item(numTouche) as HTMLElement; |
||||
if (touche === undefined || touche === null) continue; |
||||
if (touche.dataset["lettre"] === lettre) { |
||||
// console.log(lettre + " => " + statut);
|
||||
switch (statut) { |
||||
case LettreStatut.BienPlace: |
||||
touche.className = ""; |
||||
touche.classList.add("input-lettre"); |
||||
touche.classList.add("lettre-bien-place"); |
||||
break; |
||||
case LettreStatut.MalPlace: |
||||
if (touche.classList.contains("lettre-bien-place")) break; |
||||
touche.className = ""; |
||||
touche.classList.add("input-lettre"); |
||||
touche.classList.add("lettre-mal-place"); |
||||
break; |
||||
default: |
||||
if (touche.classList.contains("lettre-bien-place")) break; |
||||
if (touche.classList.contains("lettre-mal-place")) break; |
||||
touche.className = ""; |
||||
touche.classList.add("input-lettre"); |
||||
touche.classList.add("lettre-non-trouve"); |
||||
break; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,6 @@
|
||||
import { LettreStatut } from "./lettreStatut"; |
||||
|
||||
export default class LettreResultat { |
||||
lettre: string = ""; |
||||
statut: LettreStatut = LettreStatut.NonTrouve; |
||||
} |
@ -0,0 +1,5 @@
|
||||
export enum LettreStatut { |
||||
NonTrouve, |
||||
MalPlace, |
||||
BienPlace, |
||||
} |
@ -0,0 +1,8 @@
|
||||
import Gestionnaire from "./gestionnaire"; |
||||
export default class Main { |
||||
public constructor() { |
||||
console.log("🟥🟦🟦🟡🟡🟡🟦🟦"); |
||||
|
||||
let gestionnaire = new Gestionnaire(); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
export default class MotsATrouver { |
||||
public static readonly Liste: Array<string> = ["DIFFUSION", "NEGATIVE", "ABSENCE", "LISTES"]; |
||||
} |
@ -0,0 +1,20 @@
|
||||
export default class NotificationMessage { |
||||
private static _notificationArea: HTMLElement = document.getElementById("notification") as HTMLElement; |
||||
private static _currentTimeout: NodeJS.Timeout | undefined; |
||||
public static ajouterNotification(message: string): void { |
||||
if (this._currentTimeout) { |
||||
clearTimeout(this._currentTimeout); |
||||
this._currentTimeout = undefined; |
||||
} |
||||
this._notificationArea.innerHTML = message; |
||||
this._notificationArea.style.opacity = "1"; |
||||
this._currentTimeout = setTimeout( |
||||
(() => { |
||||
this._notificationArea.style.opacity = "0"; |
||||
this._notificationArea.innerHTML = ""; |
||||
this._currentTimeout = undefined; |
||||
}).bind(this), |
||||
5000 |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,4 @@
|
||||
export default class SauvegardePartie { |
||||
propositions: Array<string> = []; |
||||
datePartie: Date = new Date(); |
||||
} |
@ -0,0 +1,5 @@
|
||||
export default class SauvegardeStats { |
||||
dernierePartie?: Date = new Date(); |
||||
partiesJouees: number = 0; |
||||
partiesGagnees: number = 0; |
||||
} |
@ -0,0 +1,47 @@
|
||||
import SauvegardePartie from "./sauvegardePartie"; |
||||
import SauvegardeStats from "./sauvegardeStats"; |
||||
|
||||
export default class Sauvegardeur { |
||||
public constructor() {} |
||||
|
||||
public sauvegarderStats(stats: SauvegardeStats): void { |
||||
localStorage.setItem("stats", JSON.stringify(stats)); |
||||
} |
||||
|
||||
public chargerSauvegardeStats(): SauvegardeStats | undefined { |
||||
let dataStats = localStorage.getItem("stats"); |
||||
if (!dataStats) return; |
||||
|
||||
let stats = JSON.parse(dataStats) as SauvegardeStats; |
||||
return stats; |
||||
} |
||||
|
||||
public sauvegarderPartieEnCours(propositions: Array<string>, datePartie: Date): void { |
||||
let partieEnCours: SauvegardePartie = { |
||||
propositions: propositions, |
||||
datePartie, |
||||
}; |
||||
localStorage.setItem("partieEnCours", JSON.stringify(partieEnCours)); |
||||
} |
||||
|
||||
public chargerSauvegardePartieEnCours(): { propositions: Array<string>; datePartie: Date } | undefined { |
||||
let dataPartieEnCours = localStorage.getItem("partieEnCours"); |
||||
if (!dataPartieEnCours) return; |
||||
|
||||
let partieEnCours = JSON.parse(dataPartieEnCours) as SauvegardePartie; |
||||
let aujourdhui = new Date(); |
||||
let datePartieEnCours = new Date(partieEnCours.datePartie); |
||||
if ( |
||||
aujourdhui.getDate() !== datePartieEnCours.getDate() || |
||||
aujourdhui.getMonth() !== datePartieEnCours.getMonth() || |
||||
aujourdhui.getFullYear() !== datePartieEnCours.getFullYear() |
||||
) { |
||||
localStorage.removeItem("partieEnCours"); |
||||
return; |
||||
} |
||||
return { |
||||
datePartie: datePartieEnCours, |
||||
propositions: partieEnCours.propositions, |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,18 @@
|
||||
import express from "express"; |
||||
import http from "http"; |
||||
|
||||
const app = express(); |
||||
const port = 4000; |
||||
(async () => { |
||||
app.use("/", express.static("public/")); |
||||
app.use("/js", express.static("js/")); |
||||
app.use("/ts", express.static("ts/")); |
||||
app.use("/node_modules/requirejs/require.js", express.static("node_modules/requirejs/require.js")); |
||||
|
||||
app.use(express.json()); |
||||
const server = http.createServer(app); |
||||
|
||||
server.listen(port, () => { |
||||
console.log(`Jeu démarré : http://localhost:${port}`); |
||||
}); |
||||
})(); |
@ -0,0 +1,64 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
/* Basic Options */ |
||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, |
||||
"module": "umd" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, |
||||
// "lib": [], /* Specify library files to be included in the compilation. */ |
||||
// "allowJs": true, /* Allow javascript files to be compiled. */ |
||||
// "checkJs": true, /* Report errors in .js files. */ |
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ |
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ |
||||
"sourceMap": true /* Generates corresponding '.map' file. */, |
||||
//"outFile": "./js/index.js" /* Concatenate and emit output to single file. */, |
||||
"outDir": "./js/" /* Redirect output structure to the directory. */, |
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ |
||||
// "composite": true, /* Enable project compilation */ |
||||
// "removeComments": true, /* Do not emit comments to output. */ |
||||
// "noEmit": true, /* Do not emit outputs. */ |
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ |
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ |
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ |
||||
|
||||
/* Strict Type-Checking Options */ |
||||
"strict": true /* Enable all strict type-checking options. */, |
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||
// "strictNullChecks": true, /* Enable strict null checks. */ |
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ |
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ |
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ |
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ |
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ |
||||
|
||||
/* Additional Checks */ |
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */ |
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */ |
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ |
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ |
||||
|
||||
/* Module Resolution Options */ |
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ |
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ |
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ |
||||
// "typeRoots": [], /* List of folders to include type definitions from. */ |
||||
// "types": [], /* Type declaration files to be included in compilation. */ |
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ |
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, |
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ |
||||
|
||||
/* Source Map Options */ |
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ |
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ |
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ |
||||
|
||||
/* Experimental Options */ |
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||
"moduleResolution": "node" |
||||
}, |
||||
|
||||
"include": ["ts/*.ts", "type/*"] |
||||
} |
||||
|
@ -0,0 +1,52 @@
|
||||
"use strict"; |
||||
|
||||
/** |
||||
* Petit script qui permet de remplir rapidement (mais manuellement) la liste des fichiers à trouver. |
||||
*/ |
||||
var fs = require("fs"); |
||||
var readlineSync = require("readline-sync"); |
||||
|
||||
function start() { |
||||
let motsGardes = []; |
||||
fs.readFile("public/mots.txt", "UTF8", function (erreur, contenu) { |
||||
//console.log(erreur);
|
||||
var dictionnaire = contenu.split("\n"); |
||||
while (true) { |
||||
var motTrouve = false; |
||||
var mot = ""; |
||||
do { |
||||
var position = Math.floor(Math.random() * dictionnaire.length); |
||||
mot = dictionnaire[position]; |
||||
let motAnalyse = mot.normalize("NFD").replace(/\p{Diacritic}/gu, ""); |
||||
motTrouve = |
||||
!(motAnalyse[0] === motAnalyse[0].toUpperCase()) && |
||||
motAnalyse.length >= 6 && |
||||
motAnalyse.length <= 9 && |
||||
!motAnalyse.includes("!") && |
||||
!motAnalyse.includes(" ") && |
||||
!motAnalyse.includes("-") && |
||||
!mot.toUpperCase().startsWith("K") && |
||||
!mot.toUpperCase().startsWith("Q") && |
||||
!mot.toUpperCase().startsWith("W") && |
||||
!mot.toUpperCase().startsWith("X") && |
||||
!mot.toUpperCase().startsWith("Y") && |
||||
!mot.toUpperCase().startsWith("Z"); |
||||
} while (!motTrouve); |
||||
console.log(mot); |
||||
|
||||
let reponse = readlineSync.question("On garde ? [O]ui ou [N]on (ou [STOP])\n"); |
||||
if (reponse.toLowerCase() === "stop") break; |
||||
let isGarde = reponse.toLowerCase() === "o"; |
||||
if (isGarde) motsGardes.push(mot); |
||||
} |
||||
fs.appendFile("public/motsATrouve.txt", motsGardes.join("\n") + "\n", (err) => { |
||||
if (err) { |
||||
console.error(err); |
||||
return; |
||||
} |
||||
//file written successfully
|
||||
}); |
||||
}); |
||||
} |
||||
|
||||
start(); |
@ -0,0 +1,41 @@
|
||||
"use strict"; |
||||
|
||||
/** |
||||
* Petit script qui nettoie le fichier dictionnaire pour le mettre dans le format attendu par le système |
||||
*/ |
||||
var fs = require("fs"); |
||||
|
||||
fs.readFile("public/mots.txt", "UTF8", function (erreur, contenu) { |
||||
//console.log(erreur);
|
||||
var dictionnaire = contenu.split("\n"); |
||||
contenu = "private readonly _dictionnaire: Array<string> = [\n"; |
||||
contenu += dictionnaire |
||||
.map((mot) => mot.normalize("NFD").replace(/\p{Diacritic}/gu, "")) |
||||
.filter( |
||||
(mot) => |
||||
!(mot[0] === mot[0].toUpperCase()) && |
||||
mot.length >= 6 && |
||||
mot.length <= 9 && |
||||
!mot.includes("!") && |
||||
!mot.includes(" ") && |
||||
!mot.includes("-") && |
||||
!mot.toUpperCase().startsWith("K") && |
||||
!mot.toUpperCase().startsWith("Q") && |
||||
!mot.toUpperCase().startsWith("W") && |
||||
!mot.toUpperCase().startsWith("X") && |
||||
!mot.toUpperCase().startsWith("Y") && |
||||
!mot.toUpperCase().startsWith("Z") |
||||
) |
||||
.map(function (mot) { |
||||
return '"' + mot.toUpperCase() + '",'; |
||||
}) |
||||
.join("\n"); |
||||
contenu += "\n]"; |
||||
fs.writeFile("public/motsNettoyes.txt", contenu, function (err) { |
||||
if (err) { |
||||
console.error(err); |
||||
return; |
||||
} |
||||
//file written successfully
|
||||
}); |
||||
}); |
@ -0,0 +1,77 @@
|
||||
"use strict"; |
||||
/** |
||||
* Petit script qui nettoie le fichier des mots à trouver pour le mettre dans le format attendu par le système |
||||
*/ |
||||
var fs = require("fs"); |
||||
function shuffle(array) { |
||||
let currentIndex = array.length, |
||||
randomIndex; |
||||
|
||||
// While there remain elements to shuffle...
|
||||
while (currentIndex != 0) { |
||||
// Pick a remaining element...
|
||||
randomIndex = Math.floor(Math.random() * currentIndex); |
||||
currentIndex--; |
||||
|
||||
// And swap it with the current element.
|
||||
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; |
||||
} |
||||
|
||||
return array; |
||||
} |
||||
|
||||
let aujourdhui = new Date().getTime(); |
||||
let origine = new Date(2022, 0, 8).getTime(); |
||||
|
||||
let numeroGrille = Math.floor((aujourdhui - origine) / (24 * 3600 * 1000)); |
||||
|
||||
const maxFige = numeroGrille; // inclus
|
||||
fs.readFile("public/motsATrouve.txt", "UTF8", function (erreur, contenu) { |
||||
//console.log(erreur);
|
||||
var dictionnaire = contenu.split("\n"); |
||||
let motsFiges = dictionnaire.slice(0, maxFige + 1); |
||||
let motsMelanges = shuffle(dictionnaire.slice(maxFige + 1)); |
||||
|
||||
contenu = "private readonly _motATrouve: Array<string> = [\n"; |
||||
contenu += |
||||
motsFiges |
||||
.map( |
||||
(mot) => |
||||
'"' + |
||||
mot |
||||
.normalize("NFD") |
||||
.replace(/\p{Diacritic}/gu, "") |
||||
.toUpperCase() + |
||||
'",' |
||||
) |
||||
.join("\n") + "\n"; |
||||
contenu += motsMelanges |
||||
.map((mot) => mot.normalize("NFD").replace(/\p{Diacritic}/gu, "")) |
||||
.filter( |
||||
(mot) => |
||||
mot && |
||||
mot.length >= 6 && |
||||
mot.length <= 9 && |
||||
!mot.includes("!") && |
||||
!mot.includes(" ") && |
||||
!mot.includes("-") && |
||||
!mot.toUpperCase().startsWith("K") && |
||||
!mot.toUpperCase().startsWith("Q") && |
||||
!mot.toUpperCase().startsWith("W") && |
||||
!mot.toUpperCase().startsWith("X") && |
||||
!mot.toUpperCase().startsWith("Y") && |
||||
!mot.toUpperCase().startsWith("Z") |
||||
) |
||||
.map(function (mot) { |
||||
return '"' + mot.toUpperCase() + '",'; |
||||
}) |
||||