Réponses:
J'ai trouvé une solution qui n'est probablement pas la plus efficace, mais qui fonctionne assez bien. Fondamentalement:
Cela fait des mots croisés fonctionnels, mais souvent assez médiocres. J'ai apporté un certain nombre de modifications à la recette de base ci-dessus pour obtenir un meilleur résultat.
J'ai récemment écrit le mien en Python. Vous pouvez le trouver ici: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Cela ne crée pas les mots croisés de style NYT dense, mais le style de mots croisés que vous pourriez trouver dans le livre de puzzle d'un enfant.
Contrairement à quelques algorithmes que j'ai découverts et qui implémentaient une méthode par force brute aléatoire pour placer des mots comme certains l'ont suggéré, j'ai essayé de mettre en œuvre une approche de force brute légèrement plus intelligente lors du placement des mots. Voici mon processus:
À la fin, vous avez un puzzle de mots croisés ou de recherche de mots décent, car ils sont à peu près les mêmes. Il a tendance à fonctionner plutôt bien, mais faites-moi savoir si vous avez des suggestions d'amélioration. Les réseaux plus grands fonctionnent exponentiellement plus lentement; de plus grandes listes de mots linéairement. Les listes de mots plus volumineuses ont également beaucoup plus de chances d'obtenir de meilleurs numéros de placement de mots.
array.sort(key=f)
est stable, ce qui signifie (par exemple) que le simple tri d'une liste de mots alphabétique par longueur conserverait tous les mots de 8 lettres classés par ordre alphabétique.
En fait, j'ai écrit un programme de génération de mots croisés il y a environ dix ans (c'était cryptique mais les mêmes règles s'appliqueraient pour les mots croisés normaux).
Il contenait une liste de mots (et des indices associés) stockés dans un fichier trié par utilisation décroissante à ce jour (de sorte que les mots les moins utilisés étaient en haut du fichier). Un modèle, essentiellement un masque de bits représentant les carrés noirs et libres, a été choisi au hasard dans un pool fourni par le client.
Ensuite, pour chaque mot non complet du puzzle (recherchez essentiellement le premier carré vide et voyez si celui à droite (à travers le mot) ou celui en dessous (mot en bas) est également vide), une recherche a été effectuée sur le fichier recherchant le premier mot qui correspondait, en tenant compte des lettres déjà présentes dans ce mot. S'il n'y avait pas de mot qui puisse convenir, vous venez de marquer le mot entier comme incomplet et de passer à autre chose.
À la fin, il y aurait des mots incomplets que le compilateur devrait remplir (et ajouter le mot et un indice au fichier si désiré). S'ils ne pouvaient pas venir avec des idées, ils pourraient modifier les mots croisés pour modifier les contraintes ou tout simplement demander manuellement une nouvelle génération totale.
Une fois que le fichier de mots / indices a atteint une certaine taille (et qu'il ajoutait 50 à 100 indices par jour pour ce client), il y avait rarement un cas de plus de deux ou trois corrections manuelles qui devaient être faites pour chaque mot croisé .
Cet algorithme crée 50 mots croisés de flèches 6x9 denses en 60 secondes. Il utilise une base de données de mots (avec mot + astuces) et une base de données de tableaux (avec des tableaux préconfigurés).
1) Search for all starting cells (the ones with an arrow), store their size and directions
2) Loop through all starting cells
2.1) Search a word
2.1.1) Check if it was not already used
2.1.2) Check if it fits
2.2) Add the word to the board
3) Check if all cells were filled
Une plus grande base de données de mots réduit considérablement le temps de génération et certains types de tableaux sont plus difficiles à remplir! Les planches plus grandes nécessitent plus de temps pour être remplies correctement!
Exemple:
Carte 6x9 pré-configurée:
(# signifie un conseil dans une cellule,% signifie deux conseils dans une cellule, flèches non affichées)
# - # # - % # - #
- - - - - - - - -
# - - - - - # - -
% - - # - # - - -
% - - - - - % - -
- - - - - - - - -
Carte 6x9 générée:
# C # # P % # O #
S A T E L L I T E
# N I N E S # T A
% A B # A # G A S
% D E N S E % W E
C A T H E D R A L
Conseils [ligne, colonne]:
[1,0] SATELLITE: Used for weather forecast
[5,0] CATHEDRAL: The principal church of a city
[0,1] CANADA: Country on USA's northern border
[0,4] PLEASE: A polite way to ask things
[0,7] OTTAWA: Canada's capital
[1,2] TIBET: Dalai Lama's region
[1,8] EASEL: A tripod used to put a painting
[2,1] NINES: Dressed up to (?)
[4,1] DENSE: Thick; impenetrable
[3,6] GAS: Type of fuel
[1,5] LS: Lori Singer, american actress
[2,7] TA: Teaching assistant (abbr.)
[3,1] AB: A blood type
[4,3] NH: New Hampshire (abbr.)
[4,5] ED: (?) Harris, american actor
[4,7] WE: The first person of plural (Grammar)
Bien que ce soit une question plus ancienne, j'essaierai une réponse basée sur un travail similaire que j'ai effectué.
Il existe de nombreuses approches pour résoudre les problèmes de contraintes (qui appartiennent généralement à la classe de complexité NPC).
Ceci est lié à l'optimisation combinatoire et à la programmation par contraintes. Dans ce cas, les contraintes sont la géométrie du maillage et l'exigence que les mots soient uniques etc.
Les approches de randomisation / recuit peuvent également fonctionner (bien que dans le cadre approprié).
Une simplicité efficace pourrait bien être la sagesse ultime!
Les exigences étaient pour un compilateur de mots croisés plus ou moins complet et un constructeur (visuel WYSIWYG).
Laissant de côté la partie du constructeur WYSIWYG, le plan du compilateur était le suivant:
Charger les listes de mots disponibles (triées par longueur de mot, c'est-à-dire 2,3, .., 20)
Trouvez les espaces de mots (c'est-à-dire les mots de la grille) sur la grille construite par l'utilisateur (par exemple, mot à x, y de longueur L, horizontal ou vertical) (complexité O (N))
Calculer les points d'intersection des mots de la grille (qui doivent être remplis) (complexité O (N ^ 2))
Calculer les intersections des mots dans les listes de mots avec les différentes lettres de l'alphabet utilisé (cela permet de rechercher des mots correspondants en utilisant un modèle par exemple. Thèse Sik Cambon telle qu'utilisée par cwc ) (complexité O (WL * AL))
Les étapes .3 et .4 permettent d'effectuer cette tâche:
une. Les intersections des mots de la grille avec eux-mêmes permettent de créer un "modèle" pour essayer de trouver des correspondances dans la liste de mots associée des mots disponibles pour ce mot de la grille (en utilisant les lettres d'autres mots croisés avec ce mot qui sont déjà remplis à un certain étape de l'algorithme)
b. Les intersections des mots d'une liste de mots avec l'alphabet permettent de trouver des mots (candidats) correspondants qui correspondent à un "modèle" donné (par exemple 'A' en 1ère place et 'B' en 3ème place etc.)
Donc, avec ces structures de données implémentées, l'algorithme utilisé était comme ceci:
REMARQUE: si la grille et la base de données de mots sont constantes, les étapes précédentes ne peuvent être effectuées qu'une seule fois.
La première étape de l'algorithme est de sélectionner un emplacement de mots vide (mot de la grille) au hasard et de le remplir avec un mot candidat de sa liste de mots associée (la randomisation permet de produire différents solutons lors d'exécutions consécutives de l'algorithme) (complexité O (1) ou O ( N))
Pour chaque créneau de mots encore vide (qui ont des intersections avec des lots de mots déjà remplis), calculez un rapport de contrainte (cela peut varier, sth simple est le nombre de solutions disponibles à cette étape) et triez les lots de mots vides par ce rapport (complexité O (NlogN ) ou O (N))
Faites une boucle dans les espaces de mots vides calculés à l'étape précédente et essayez pour chacun d'entre eux un certain nombre de solutions annulées (en vous assurant que la "cohérence d'arc est conservée", c'est-à-dire que la grille a une solution après cette étape si ce mot est utilisé) et triez-les selon disponibilité maximale pour l'étape suivante (c'est-à-dire que l'étape suivante a un maximum de solutions possibles si ce mot est utilisé à ce moment-là à cet endroit, etc.) (complexité O (N * MaxCandidatesUsed))
Remplissez ce mot (marquez-le comme rempli et passez à l'étape 2)
Si aucun mot ne satisfait aux critères de l'étape .3, essayez de revenir en arrière vers une autre solution candidate d'une étape précédente (les critères peuvent varier ici) (complexité O (N))
Si un retour en arrière est trouvé, utilisez l'alternative et réinitialisez éventuellement tous les mots déjà remplis qui pourraient nécessiter une réinitialisation (marquez-les à nouveau comme non remplis) (complexité O (N))
Si aucun retour en arrière n'est trouvé, aucune solution ne peut être trouvée (au moins avec cette configuration, graine initiale etc.)
Sinon, lorsque tous les lots de mots sont remplis, vous avez une solution
Cet algorithme effectue une marche cohérente aléatoire de l'arbre de solution du problème. Si à un moment donné il y a une impasse, il fait un retour en arrière vers un nœud précédent et suit une autre route. Jusqu'à ce qu'une solution trouvée ou le nombre de candidats pour les différents nœuds soient épuisés.
La partie cohérence s'assure qu'une solution trouvée est bien une solution et la partie aléatoire permet de produire différentes solutions dans différentes exécutions et aussi en moyenne d'avoir de meilleures performances.
PS. tout cela (et d'autres) ont été implémentés en JavaScript pur (avec traitement parallèle et WYSIWYG)
PS2. L'algorithme peut être facilement parallélisé afin de produire plus d'une solution (différente) en même temps
J'espère que cela t'aides
Pourquoi ne pas simplement utiliser une approche probabiliste aléatoire pour commencer. Commencez par un mot, puis choisissez à plusieurs reprises un mot au hasard et essayez de l'adapter à l'état actuel du puzzle sans casser les contraintes de taille etc. Si vous échouez, recommencez simplement.
Vous serez surpris de la fréquence à laquelle une approche de Monte Carlo comme celle-ci fonctionne.
Voici un code JavaScript basé sur la réponse de nickf et le code Python de Bryan. Il suffit de le publier au cas où quelqu'un d'autre en aurait besoin dans js.
function board(cols, rows) { //instantiator object for making gameboards
this.cols = cols;
this.rows = rows;
var activeWordList = []; //keeps array of words actually placed in board
var acrossCount = 0;
var downCount = 0;
var grid = new Array(cols); //create 2 dimensional array for letter grid
for (var i = 0; i < rows; i++) {
grid[i] = new Array(rows);
}
for (var x = 0; x < cols; x++) {
for (var y = 0; y < rows; y++) {
grid[x][y] = {};
grid[x][y].targetChar = EMPTYCHAR; //target character, hidden
grid[x][y].indexDisplay = ''; //used to display index number of word start
grid[x][y].value = '-'; //actual current letter shown on board
}
}
function suggestCoords(word) { //search for potential cross placement locations
var c = '';
coordCount = [];
coordCount = 0;
for (i = 0; i < word.length; i++) { //cycle through each character of the word
for (x = 0; x < GRID_HEIGHT; x++) {
for (y = 0; y < GRID_WIDTH; y++) {
c = word[i];
if (grid[x][y].targetChar == c) { //check for letter match in cell
if (x - i + 1> 0 && x - i + word.length-1 < GRID_HEIGHT) { //would fit vertically?
coordList[coordCount] = {};
coordList[coordCount].x = x - i;
coordList[coordCount].y = y;
coordList[coordCount].score = 0;
coordList[coordCount].vertical = true;
coordCount++;
}
if (y - i + 1 > 0 && y - i + word.length-1 < GRID_WIDTH) { //would fit horizontally?
coordList[coordCount] = {};
coordList[coordCount].x = x;
coordList[coordCount].y = y - i;
coordList[coordCount].score = 0;
coordList[coordCount].vertical = false;
coordCount++;
}
}
}
}
}
}
function checkFitScore(word, x, y, vertical) {
var fitScore = 1; //default is 1, 2+ has crosses, 0 is invalid due to collision
if (vertical) { //vertical checking
for (i = 0; i < word.length; i++) {
if (i == 0 && x > 0) { //check for empty space preceeding first character of word if not on edge
if (grid[x - 1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
} else if (i == word.length && x < GRID_HEIGHT) { //check for empty space after last character of word if not on edge
if (grid[x+i+1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
}
if (x + i < GRID_HEIGHT) {
if (grid[x + i][y].targetChar == word[i]) { //letter match - aka cross point
fitScore += 1;
} else if (grid[x + i][y].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
fitScore = 0;
break;
} else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
if (y < GRID_WIDTH - 1) { //check right side if it isn't on the edge
if (grid[x + i][y + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
}
if (y > 0) { //check left side if it isn't on the edge
if (grid[x + i][y - 1].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
}
}
}
}
} else { //horizontal checking
for (i = 0; i < word.length; i++) {
if (i == 0 && y > 0) { //check for empty space preceeding first character of word if not on edge
if (grid[x][y-1].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
} else if (i == word.length - 1 && y + i < GRID_WIDTH -1) { //check for empty space after last character of word if not on edge
if (grid[x][y + i + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
}
if (y + i < GRID_WIDTH) {
if (grid[x][y + i].targetChar == word[i]) { //letter match - aka cross point
fitScore += 1;
} else if (grid[x][y + i].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
fitScore = 0;
break;
} else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
if (x < GRID_HEIGHT) { //check top side if it isn't on the edge
if (grid[x + 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
}
if (x > 0) { //check bottom side if it isn't on the edge
if (grid[x - 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
fitScore = 0;
break;
}
}
}
}
}
}
return fitScore;
}
function placeWord(word, clue, x, y, vertical) { //places a new active word on the board
var wordPlaced = false;
if (vertical) {
if (word.length + x < GRID_HEIGHT) {
for (i = 0; i < word.length; i++) {
grid[x + i][y].targetChar = word[i];
}
wordPlaced = true;
}
} else {
if (word.length + y < GRID_WIDTH) {
for (i = 0; i < word.length; i++) {
grid[x][y + i].targetChar = word[i];
}
wordPlaced = true;
}
}
if (wordPlaced) {
var currentIndex = activeWordList.length;
activeWordList[currentIndex] = {};
activeWordList[currentIndex].word = word;
activeWordList[currentIndex].clue = clue;
activeWordList[currentIndex].x = x;
activeWordList[currentIndex].y = y;
activeWordList[currentIndex].vertical = vertical;
if (activeWordList[currentIndex].vertical) {
downCount++;
activeWordList[currentIndex].number = downCount;
} else {
acrossCount++;
activeWordList[currentIndex].number = acrossCount;
}
}
}
function isActiveWord(word) {
if (activeWordList.length > 0) {
for (var w = 0; w < activeWordList.length; w++) {
if (word == activeWordList[w].word) {
//console.log(word + ' in activeWordList');
return true;
}
}
}
return false;
}
this.displayGrid = function displayGrid() {
var rowStr = "";
for (var x = 0; x < cols; x++) {
for (var y = 0; y < rows; y++) {
rowStr += "<td>" + grid[x][y].targetChar + "</td>";
}
$('#tempTable').append("<tr>" + rowStr + "</tr>");
rowStr = "";
}
console.log('across ' + acrossCount);
console.log('down ' + downCount);
}
//for each word in the source array we test where it can fit on the board and then test those locations for validity against other already placed words
this.generateBoard = function generateBoard(seed = 0) {
var bestScoreIndex = 0;
var top = 0;
var fitScore = 0;
var startTime;
//manually place the longest word horizontally at 0,0, try others if the generated board is too weak
placeWord(wordArray[seed].word, wordArray[seed].displayWord, wordArray[seed].clue, 0, 0, false);
//attempt to fill the rest of the board
for (var iy = 0; iy < FIT_ATTEMPTS; iy++) { //usually 2 times is enough for max fill potential
for (var ix = 1; ix < wordArray.length; ix++) {
if (!isActiveWord(wordArray[ix].word)) { //only add if not already in the active word list
topScore = 0;
bestScoreIndex = 0;
suggestCoords(wordArray[ix].word); //fills coordList and coordCount
coordList = shuffleArray(coordList); //adds some randomization
if (coordList[0]) {
for (c = 0; c < coordList.length; c++) { //get the best fit score from the list of possible valid coordinates
fitScore = checkFitScore(wordArray[ix].word, coordList[c].x, coordList[c].y, coordList[c].vertical);
if (fitScore > topScore) {
topScore = fitScore;
bestScoreIndex = c;
}
}
}
if (topScore > 1) { //only place a word if it has a fitscore of 2 or higher
placeWord(wordArray[ix].word, wordArray[ix].clue, coordList[bestScoreIndex].x, coordList[bestScoreIndex].y, coordList[bestScoreIndex].vertical);
}
}
}
}
if(activeWordList.length < wordArray.length/2) { //regenerate board if if less than half the words were placed
seed++;
generateBoard(seed);
}
}
}
function seedBoard() {
gameboard = new board(GRID_WIDTH, GRID_HEIGHT);
gameboard.generateBoard();
gameboard.displayGrid();
}
Je générerais deux nombres: la longueur et le score au Scrabble. Supposons qu'un faible score au Scrabble signifie qu'il est plus facile de rejoindre (scores faibles = beaucoup de lettres courantes). Triez la liste par longueur décroissante et par score Scrabble croissant.
Ensuite, descendez simplement la liste. Si le mot ne croise pas avec un mot existant (vérifiez chaque mot par sa longueur et son score au Scrabble, respectivement), mettez-le dans la file d'attente et vérifiez le mot suivant.
Rincez et répétez, et cela devrait générer un mot croisé.
Bien sûr, je suis à peu près sûr que c'est O (n!) Et il n'est pas garanti de compléter les mots croisés pour vous, mais peut-être que quelqu'un peut l'améliorer.
J'ai réfléchi à ce problème. Mon sentiment est que pour créer un mot croisé vraiment dense, vous ne pouvez pas espérer que votre liste de mots limitée suffira. Par conséquent, vous voudrez peut-être prendre un dictionnaire et le placer dans une structure de données "trie". Cela vous permettra de trouver facilement des mots qui remplissent les espaces à gauche. Dans un essai, il est assez efficace d'implémenter un parcours qui, par exemple, vous donne tous les mots de la forme "c? T".
Donc, ma pensée générale est la suivante: créer une sorte d'approche de force relativement brute comme certains l'ont décrit ici pour créer une croix de faible densité, et remplir les espaces avec des mots du dictionnaire.
Si quelqu'un d'autre a adopté cette approche, veuillez me le faire savoir.
Je jouais avec le moteur de générateur de mots croisés, et j'ai trouvé que c'était le plus important:
0.!/usr/bin/python
une. allwords.sort(key=len, reverse=True)
b. Créez un élément / objet comme un curseur qui se déplacera autour de la matrice pour une orientation facile à moins que vous ne vouliez itérer par choix aléatoire plus tard.
la première, prenez la première paire et placez-les en travers et en bas de 0,0; stockez le premier comme notre «leader» actuel de mots croisés.
déplacer le curseur par ordre diagonal ou aléatoire avec une plus grande probabilité diagonale vers la prochaine cellule vide
itérer sur les mots comme et utiliser la longueur de l'espace libre pour définir la longueur maximale des mots:
temp=[]
for w_size in range( len( w_space ), 2, -1 ) :
# t
for w in [ word for word in allwords if len(word) == w_size ] :
#
if w not in temp and putTheWord( w, w_space ) :
#
temp.append( w )
pour comparer le mot à l'espace libre, j'ai utilisé, c'est-à-dire:
w_space=['c','.','a','.','.','.'] # whereas dots are blank cells
# CONVERT MULTIPLE '.' INTO '.*' FOR REGEX
pattern = r''.join( [ x.letter for x in w_space ] )
pattern = pattern.strip('.') +'.*' if pattern[-1] == '.' else pattern
prog = re.compile( pattern, re.U | re.I )
if prog.match( w ) :
#
if prog.match( w ).group() == w :
#
return True
après chaque mot utilisé avec succès, changez de direction. Bouclez alors que toutes les cellules sont remplies OU vous manquez de mots OU par limite d'itérations alors:
# CHANGE ALL WORDS LIST
inexOf1stWord = allwords.index( leading_w )
allwords = allwords[:inexOf1stWord+1][:] + allwords[inexOf1stWord+1:][:]
... et répétez de nouveaux mots croisés.
Faites le système de notation par facilité de remplissage et quelques calculs d'estimation. Donnez le score pour le mot croisé actuel et restreignez le choix ultérieur en l'ajoutant à la liste des mots croisés créés si le score est satisfait par votre système de notation.
Après la première session d'itération, parcourez à nouveau la liste des mots croisés créés pour terminer le travail.
En utilisant plus de paramètres, la vitesse peut être améliorée par un facteur énorme.
J'obtiendrais un index de chaque lettre utilisée par chaque mot pour connaître les croix possibles. Ensuite, je choisirais le mot le plus grand et l'utiliserais comme base. Sélectionnez le prochain grand et traversez-le. Rincez et répétez. C'est probablement un problème NP.
Une autre idée consiste à créer un algorithme génétique où la métrique de force est le nombre de mots que vous pouvez mettre dans la grille.
La partie difficile que je trouve est de savoir quand connaître une certaine liste ne peut pas être franchie.
Celui-ci apparaît comme un projet dans le cours AI CS50 de Harvard. L'idée est de formuler le problème de génération de mots croisés comme un problème de satisfaction de contraintes et de le résoudre par retour arrière avec différentes heuristiques pour réduire l'espace de recherche.
Pour commencer, nous avons besoin de quelques fichiers d'entrée:
»
###_####_#
____####_#
_##_#_____
_##_#_##_#
______####
#_###_####
#_##______
#_###_##_#
_____###_#
#_######_#
##_______#
»
Un vocabulaire d'entrée (liste de mots / dictionnaire) à partir duquel les mots candidats seront choisis (comme celui illustré ci-dessous).
a
abandon
ability
able
abortion
about
above
abroad
absence
absolute
absolutely
...
Maintenant, le CSP est défini et doit être résolu comme suit:
Ce qui suit montre la sortie obtenue à l'aide d'une implémentation de l'algorithme de résolution CSP:
`
███S████D█
MUCH████E█
E██A█AGENT
S██R█N██Y█
SUPPLY████
█N███O████
█I██INSIDE
█Q███E██A█
SUGAR███N█
█E██████C█
██OFFENSE█
»
L'animation suivante montre les étapes de retour en arrière:
En voici un autre avec une liste de mots en langue Bangla (bengali):
J'ai codé une solution JavaScript / jQuery à ce problème:
Exemple de démonstration: http://www.earthfluent.com/crossword-puzzle-demo.html
Code source: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator
L'intention de l'algorithme que j'ai utilisé:
Je décrirai l'algorithme que j'ai utilisé:
Regroupez les mots selon ceux qui partagent une lettre commune.
À partir de ces groupes, créez des ensembles d'une nouvelle structure de données ("blocs de mots"), qui est un mot principal (qui traverse tous les autres mots), puis les autres mots (qui traversent le mot principal).
Commencez le puzzle de mots croisés avec le tout premier de ces blocs de mots dans la position tout en haut à gauche du puzzle de mots croisés.
Pour le reste des blocs de mots, en partant de la position la plus en bas à droite du mot croisé, déplacez-vous vers le haut et vers la gauche, jusqu'à ce qu'il n'y ait plus d'emplacements disponibles à remplir. S'il y a plus de colonnes vides vers le haut que vers la gauche, déplacez-vous vers le haut et vice versa.
var crosswords = generateCrosswordBlockSources(puzzlewords);
. Consignez simplement cette valeur sur la console. N'oubliez pas, il y a un "cheat-mode" dans le jeu, où vous pouvez simplement cliquer sur "Révéler la réponse", pour obtenir la valeur immédiatement.