Que sont les événements de jeu ?
Nous avons précédemment généré des nombres flottants aléatoires entre 0 et 1 à l’aide des fonctions generateFloats et byteGenerator. Les événements de jeu transforment ces nombres flottants générés en résultats de jeu réels. Cela fonctionne différemment selon les jeux. Dans Plinko, les nombres flottants permettent de déterminer la direction de chute de la balle. Dans Dice, ils permettent de déterminer le résultat du lancer de dés.
Vous trouverez ci-dessous une explication détaillée de la manière dont nous transformons les nombres flottants en événements ou résultats équitables pour chaque jeu original disponible sur notre plateforme.
Tower
Dans Tower, l’objectif est d’atteindre le sommet de la tour et de sauver la princesse en trouvant les clés et en évitant les pommes empoisonnées. Chaque rangée de la tour contient un nombre prédéfini de cases sûres (clés) et de pommes empoisonnées, selon la difficulté sélectionnée.
Dans Tower, l’événement de jeu consiste à déterminer la position des cases sûres (ou clés) sur chaque rangée.
La tour compte au total 9 rangées. Le nombre de colonnes ainsi que la répartition des clés et des pommes empoisonnées varient selon la difficulté choisie :
Facile: 4 colonnes - 3 clés et 1 pomme empoisonnée par rangée
Moyen: 3 colonnes - 2 clés et 1 pomme empoisonnée par rangée
Difficile: 2 colonnes - 1 clé et 1 pomme empoisonnée par rangée
Expert: 3 colonnes : 1 clé et 2 pommes empoisonnées par rangée
Maître: 4 colonnes - 1 clé et 3 pommes empoisonnées par rangée
Le résultat de chaque rangée est déterminé en générant des positions aléatoires pour les clés (cases sûres) parmi les colonnes disponibles, selon la configuration de difficulté.
const TOWER_DIFFICULTY_TO_CONFIG: Record<TowerDifficulty, DifficultyConfig> = {
[TowerDifficulty.EASY]: {
count: 3,
columns: 4,
},
[TowerDifficulty.MEDIUM]: {
count: 2,
columns: 3,
},
[TowerDifficulty.HARD]: {
count: 1,
columns: 2,
},
[TowerDifficulty.EXPERT]: {
count: 1,
columns: 3,
},
[TowerDifficulty.MASTER]: {
count: 1,
columns: 4,
},
};
// Generate floats to determine the positions of safe tiles (keys) for each row
const floats = generateFloats({
serverSeed,
clientSeed,
nonce,
cursor,
count,
});
// Get the tower configuration based on the selected difficulty
const towerConfig = TOWER_DIFFICULTY_TO_CONFIG[difficulty];
const results: number[][] = [];
const resultPerRow = towerConfig.count;
// Iterate through each row to determine the positions of safe tiles for that row
for (let i = 0; i < TOWER_TOTAL_ROWS.toNumber(); i++) {
const result: number[] = [];
const values = Array.from(Array(towerConfig.columns).keys());
// Get positions for safe tiles in the current row
for (let j = 0; j < resultPerRow; j++) {
const float = floats[i * resultPerRow + j];
if (float === undefined) {
break;
}
const position = Math.floor(float * (towerConfig.columns - j));
result.push(values.splice(position, 1)[0] ?? 0);
}
results.push(result);
}
return results;
// Example: when clientSeed='abc', serverSeed='def', nonce=1, difficulty='easy'
// results: [
// [0, 1, 2],
// [0, 2, 3],
// [0, 2, 3],
// [0, 1, 2],
// [0, 1, 3],
// [0, 1, 2],
// [1, 2, 3],
// [0, 1, 3],
// [0, 1, 3]
// ]
// Each sub-array represents the column indexes of safe tiles for that row
// (e.g., for the first row, safe tiles are in columns 0, 1, and 2)
Chicken
Dans Chicken, l’objectif est de faire avancer le poulet sur autant de voies que possible sans atterrir sur la voie dangereuse.
Chaque pas réussi augmente le multiplicateur de paiement. Le joueur peut effectuer un retrait à tout moment avant que le poulet ne saute sur la voie dangereuse. Si le poulet atteint la zone dangereuse avant que le joueur n’effectue un retrait, la mise est perdue.
Dans Chicken, l’événement de jeu principal consiste à déterminer la voie dangereuse, c’est-à-dire la voie sur laquelle le poulet perdra.
Chicken propose 4 niveaux de difficulté. Le nombre de voies que le poulet peut traverser varie selon la difficulté sélectionnée :
Facile: 19 voies
Moyen: 17 voies
Difficile: 15 voies
Expert: 10 voies
const TOTAL_LANES = 20
// Number of lanes that the chicken can cross for each difficulty
const DIFFICULTY_TO_VALID_LANE_COUNT: Record<ChickenDifficulty, number> = {
[ChickenDifficulty.EASY]: 19,
[ChickenDifficulty.MEDIUM]: 17,
[ChickenDifficulty.HARD]: 15,
[ChickenDifficulty.EXPERT]: 10,
};
// Number of gameResults generated depends on the difficulty
// Example with Hard difficulty, we generate 20 - 15 = 5 numbers
// and the smallest number in the array is the danger lane
const gameResults = generateFloats({
serverSeed,
clientSeed,
nonce,
cursor: 0,
count: TOTAL_LANES - DIFFICULTY_TO_VALID_LANE_COUNT[difficulty] // difficulty selected for the game
}).map((val, index) => Math.floor(val * (TOTAL_LANES - index)));
// Danger lane is determined by the smallest number in the game results array
const dangerLane = Math.min(...gameResults)
// Example: Hard difficulty - gameResults = [10, 4, 6, 15, 8]
// Danger lane will be on 4 (this is zero based index)
// Lane 0 - 3 are safe and lane 4 will be the danger lane
// The chicken will lose upon reaching lane 4 if not cashed out earlier
Plinko
Dans Plinko, les joueurs peuvent choisir une pyramide d’une hauteur comprise entre 8 et 16. Lorsque la balle est lâchée du sommet, elle tombe à gauche ou à droite 8 à 16 fois, chaque fois qu’elle touche une cheville sur le plateau de Plinko. Dans Plinko, l’événement de jeu consiste à déterminer si la balle tombe à gauche ou à droite à chaque niveau, représenté par 0 et 1.
// Create multiple floats between 0 and 1
// Example: floats = [0.8320531346835196, 0.8531235849950463, 0.18227359023876488, 0.774284637067467, 0.18627392360940576, 0.6012540922965854, 0.608081541955471, 0.46018488449044526] when clientSeed='abc', serverSeed='def', nonce=1, count=8
// count refers to the number of plinko rows
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor, count });
// Example: [1, 1, 0, 1, 0, 1, 1, 0] Each 1 represents a drop to the right whereas 0 represents a drop to the left
const dropDirections = floats.map(val => Math.floor(val * 2));
// This sums up all the values in the array to get the final position of the ball from the left.
// Example: finalPositionFromLeft = 5 (the ball will land on the 6th slot from the left)
const finalPositionFromLeft = dropDirections.reduce((cur, acc) => cur + acc, 0)
Dice
Dans Dice, un lancer de dés peut donner une valeur comprise entre 0.00 et 100.00. L’événement de jeu dans Dice consiste à déterminer cette valeur. Nous le faisons à l’aide de (floats * 10,001) / 100). Étant donné que nous incluons des valeurs avec jusqu’à 2 décimales, ainsi que le nombre 0, il existe au total 10,000 + 1 = 10,001 résultats possibles (décimales comprises).
// Creates an array of random numbers
// Example: With clientSeed = 'abc', serverSeed = 'def', nonce = 1, floats = [0.43053962965495884]
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
// Converts the value in the array to a number between 0.00 to 100.00 and return the value
// Example: resultValue = [43.05]
const resultValue = floats.map(val => Math.floor(floats * 10001) / 100);
return resultValue[0];
// Example: 43.05 is returned as the result of the game
Limbo
Dans Limbo, l’objectif est de deviner un multiplicateur >1. Si le résultat du jeu est supérieur au multiplicateur, l’utilisateur gagne la partie. L’événement de jeu consiste à générer une valeur flottante supérieure à 1 et suivant une distribution en courbe hyperbolique.
// Generate float between 0 and 1
// Example: floats = [0.43053962965495884], float = 0.43053962965495884,
// when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
const float = floats[0]
// generate limbo game event results with house edge (1%)
// using a hyperbolic 1/x curve
// multiplying by this and dividing by (MAX_VALUE + 1) prevents division by 0 error
const MAX_VALUE = 2 ** 24;
const rtp = 0.99;
// Example: rawValue = 2.298905
const rawValue = (rtp * MAX_VALUE) / (MAX_VALUE * float + 1)
// resultValue = 2.29
const resultValue = (0.99 * MAX_VALUE / (MAX_VALUE * float +1)).toFixed(2,ROUND_DOWN);
return resultValue;
Keno
Dans Keno, l’événement de jeu nécessite d’identifier 10 emplacements uniques de gemmes sur le plateau de keno. Pour cela, nous générons 10 nombres flottants différents entre 1 et 40, représentant chaque emplacement sur le plateau de keno. Un mécanisme est ensuite utilisé pour gérer les nombres flottants en double et renvoyer les 10 emplacements finaux des gemmes.
// Generate 10 different floats as there are 10 positions that are selected when the game is played
// Example: floats = [18, 26, 7, 22, 31, 10, 23, 8, 24, 13] when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 10 }) .map((val, index) => Math.ceil(val * (40 - index)))
// array used to store the position of the gems const gemLocations: number[] = [];
// this loop helps to prevent duplicate gem positions by skipping positions that already contain a gem
for (const val of floats) { let currentGem = val;
let i = 0;
while (i <= currentGem) {
if (gemLocations.includes(i)) {
currentGem++;
}
i++;
}
gemLocations.push(currentGem);
}
// returns the location of the gem on the board
// Example: [18, 27, 7, 24, 35, 11, 28, 9, 31, 16] return gemLocations;
Mines
Dans Mines, l’événement de jeu nécessite d’identifier le placement des mines sur le plateau de 25 cases. En fonction du nombre de mines sélectionné par l’utilisateur, nous générons une liste d’entiers de 0 à 24 afin de représenter les 25 emplacements du plateau.
// Generate 10 different floats
// Example: [20, 20, 4] when clientSeed='abc', serverSeed='def', nonce=1, numberOfBombs=3
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: numberOfBombs })
.map((val, index) => Math.floor(val * (25 - index)));
// array used to store the position of the bombs
const mineLocations: number[] = [];
// this loop helps to prevent duplicate bomb positions by skipping positions that already contain a mine
for (const val of floats) {
let currentMine = val;
let i = 0;
while (i <= currentMine) {
if (mineLocations.includes(i)) {
currentMine++;
} i++; } mineLocations.push(currentMine); }
// returns the location of the bombs on the board
// Example: [20, 21, 4]
return mineLocations;
Roulette
Notre roulette est basée sur une roue européenne à un seul « 0 », comportant 37 cases différentes allant de 0 à 36. Dans la roulette, l’événement de jeu consiste à déterminer, parmi les 37 cases, celle dans laquelle la bille s’arrêtera.
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884]
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
// generate an integer from 0 to 37 to represent the pocket to land on
// Example: resultValue = 15
const resultValue = floats.map(val => Math.floor(val * 37))[0];
return resultValue
Wheel
Dans Wheel, l’utilisateur choisit le nombre de segments de la roue. Selon le segment sur lequel le pointeur s’arrête, l’utilisateur gagne le multiplicateur associé à ce segment. L’événement de jeu consiste à déterminer le segment.
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884]
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:1 });
// Get an integer representing the number of segments set in the game
// Example: resultSegment = 8
const resultSegment = floats.map(val => Math.floor(val * numberOfSegments))[0];
return resultSegment
Blackjack
Dans Blackjack, le jeu se joue avec un paquet de cartes illimité. Chaque tour a donc toujours la même probabilité. L’événement de jeu dans Blackjack consiste à déterminer la carte tirée lorsque le joueur ou la banque tire une carte du paquet.
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (52 elements are generated but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });
const CARDS = [ ♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4, ♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6, ♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9, ♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J, ♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A, ♥A, ♠A, ♣A
];
// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));
// Example: cards = [♠7, ♠10, ♥4, ♥9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;
Hilo
Dans Hilo, le jeu se joue avec un paquet de cartes illimité. Chaque tour a donc toujours la même probabilité. L’objectif est de deviner si la carte suivante aura une valeur supérieure ou inférieure à celle de la carte actuelle. Dans Hilo, l’événement de jeu consiste à déterminer la prochaine carte tirée par l’utilisateur, le cas échéant.
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });
const CARDS = [
♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4, ♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6, ♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9, ♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J, ♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A, ♥A, ♠A, ♣A
];
// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));
// Example: cards = [♠7, ♠10, ♥4, ♥9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;
Crash
Le calcul du résultat d’une partie de Crash comporte 2 éléments.
Hash de bloc Bitcoin
Seed
Hash Bitcoin
Les détails se trouvent dans ce tweet :
Le hash du bloc 779588 est disponible dans l’explorateur de blocs BTC (https://btcscan.org/block/000000000000000000033ab71b5fa7edcafe388e5f49719f1de8e1f8d0670460) et correspond au hash indiqué dans le tweet.
Seed
Le processus de génération des seeds pour 10 millions de parties de Crash est conçu pour garantir l’équité et la transparence, en empêchant toute possibilité de manipulation par Shuffle. Voici comment les seeds sont générées et vérifiées :
Génération de la seed initiale: La seed de la 10 millionième partie est choisie aléatoirement par Shuffle.
Hachage séquentiel pour la génération des seeds: Pour générer les seeds de toutes les autres parties, nous utilisons une méthode de hachage rétrograde :
La seed de chaque partie résulte du hachage de la seed de la partie suivante. Plus précisément, le hash de la 9,999,999e partie est le hash SHA256 du hash de la 10 millionième partie.
Cet enchaînement se poursuit de sorte que la seed de chaque partie est obtenue en hachant le hash de la partie suivante, jusqu’à ce que la seed de la première partie soit obtenue à partir du hash de la deuxième partie.
Vérification de l’intégrité des seeds:
En publiant la valeur hachée du hash utilisé dans la première partie, les joueurs peuvent vérifier indépendamment l’intégrité du hash de toutes les parties jusqu’à la 10 millionième partie.
Avec cette méthode, si vous connaissez le hash d’une partie donnée, vous pouvez vérifier les seeds de toutes les parties précédentes. Par exemple, si vous connaissez la seed de la partie 1323185, vous pouvez hacher cette seed pour vérifier qu’elle correspond à la seed de la partie 1323184, et ainsi de suite jusqu’à la première partie.
Par exemple, dans la partie 1323185 (https://shuffle.com/games/originals/crash?md-id=1323185&modal=crashGame), le hash est 2f824ebb9548a4d24d042ecab390acd1dc931048f696dc03c738937852825ac0
Dans la partie 1323184 (https://shuffle.com/games/originals/crash?md-id=1323184&modal=crashGame), le hash est 20f42ccc46fed8e005767f85eb4bd5d42d3bd2a72b01ae77dcc7071bec280265
Le hash de la partie 1323184 est le hash SHA256 du hash de la partie 1323185. Vous pouvez utiliser un calculateur SHA256 en ligne tel que https://xorbin.com/tools/sha256-hash-calculator pour le vérifier.
Transparence et sécurité:
En utilisant un hash de bloc BTC futur, hors du contrôle de Shuffle, et en publiant la version hachée de la seed de la première partie, les utilisateurs peuvent prouver que les résultats des parties de Crash sont équitables et n’ont pas été manipulés.
Slide
Slide utilise le même processus de provably fair et de génération de seeds que Crash. La génération des seeds a été effectuée publiquement sur x.com, et la publication est disponible ici.
Le hash du bloc 952294 est disponible dans l’explorateur de blocs BTC (https://btcscan.org/block/00000000000000000000a382760ff19cdb7f9f22147b9123f3e471fb3c6d129a) et correspond au hash indiqué dans le tweet.
Baccarat
Dans Baccarat, le jeu se joue avec un paquet de cartes illimité. Chaque tour a donc toujours la même probabilité. Dans Baccarat, l’événement de jeu consiste à déterminer les cartes distribuées aux mains du Joueur et de la Banque lors de la distribution initiale et de tout tirage supplémentaire, conformément aux règles standard de tirage du baccarat.
Les cartes sont distribuées dans l’ordre suivant à partir de la séquence de nombres flottants générée :
Première carte du Joueur - indice du nombre flottant 0
Première carte de la Banque - indice du nombre flottant 1
Deuxième carte du Joueur - indice du nombre flottant 2
Deuxième carte de la Banque - indice du nombre flottant 3
Troisième carte du Joueur (si tirée) - indice du nombre flottant 4
Troisième carte de la Banque (si tirée)
indice du nombre flottant 4 - si le Joueur n’a pas tiré de troisième carte
indice du nombre flottant 5 - si le Joueur tire une troisième carte
// Generate a float between 0 and 1
// Example: floats = [0.40353962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });
const CARDS = [
♠2, ♥2, ♦2, ♣2, ♠3, ♥3, ♦3, ♣3, ♠4, ♥4,
♦4, ♣4, ♠5, ♥5, ♦5, ♣5, ♠6, ♥6, ♦6, ♣6,
♠7, ♥7, ♦7, ♣7, ♠8, ♥8, ♦8, ♣8, ♠9, ♥9,
♦9, ♣9, ♠10, ♥10, ♦10, ♣10, ♠J, ♥J, ♦J,
♣J, ♠Q, ♥Q, ♦Q, ♣Q, ♠K, ♥K, ♦K, ♣K, ♠A,
♥A, ♦A, ♣A
];
// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));
// Example: cards = [♠7, ♠10, ♥4, ♠9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;
Blitz
Dans Blitz, le jeu se joue avec un paquet de cartes illimité. Chaque tour a donc toujours la même probabilité. Dans Blitz, l’événement de jeu consiste à déterminer les cartes distribuées au joueur selon le nombre de cartes qu’il a choisi de tirer, et à vérifier si toutes les cartes tirées sont uniques.
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });
const CARDS = [
♠2, ♥2, ♣2, ♦2, ♠3, ♥3, ♣3, ♦3, ♠4, ♥4,
♣4, ♦4, ♠5, ♥5, ♣5, ♦5, ♠6, ♥6, ♣6, ♦6,
♠7, ♥7, ♣7, ♦7, ♠8, ♥8, ♣8, ♦8, ♠9, ♥9,
♣9, ♦9, ♠10, ♥10, ♣10, ♦10, ♠J, ♥J, ♣J, ♦J,
♠Q, ♥Q, ♣Q, ♦Q, ♠K, ♥K, ♣K, ♦K, ♠A,
♥A, ♣A, ♦A
];
// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));
// Example: cards = [♠7, ♠10, ♥4, ♠9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;
Coinflip
Dans Coinflip, l’objectif est de deviner correctement le côté sur lequel une pièce lancée retombe. Dans Coinflip, l’événement de jeu consiste à déterminer le résultat (PILE ou FACE) de chaque lancer. Coinflip propose trois modes — Classique (un seul lancer), Classique progressif (lancers répétés avec possibilité d’effectuer un retrait à tout moment) et Cible (choisissez un nombre de lancers et le nombre minimum de prédictions correctes nécessaires pour gagner) — mais les résultats des lancers sont déterminés de la même manière dans les trois modes.
Chaque lancer est déterminé en générant un nombre flottant et en l’associant à 0 ou 1, exactement comme Plinko associe ses nombres flottants aux chutes à gauche ou à droite :
// Generate floats to determine each flip outcome
// Example: floats = [0.43053962965495884, 0.6645898742135614, ...] when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor, count });
// Each float maps to 0 or 1, which maps to HEADS or TAILS
// Example: [0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]
const flipValues = floats.map(val => Math.floor(val * 2));
const flipResults = flipValues.map(val => (val === 0 ? 'HEADS' : 'TAILS'));
// Example: [HEADS, TAILS, HEADS, TAILS, TAILS, HEADS, TAILS, HEADS, TAILS, HEADS,
// TAILS, HEADS, TAILS, HEADS, HEADS, TAILS, HEADS, HEADS, HEADS, TAILS]
return flipResults;
Classique — un lancer est généré (count = 1). Le joueur gagne si le côté qu’il a sélectionné correspond au résultat du lancer.
Classique progressif — les lancers sont générés un par un à mesure que le joueur continue, mais le résultat de chaque lancer est fixé dès le début de la manche : le Nᵉ lancer est toujours obtenu en générant les lancers de 1 à N et en prenant le dernier. Ainsi, révéler les lancers un par un à mesure que le joueur progresse ne modifie pas le résultat qu’aurait eu un lancer donné. Le joueur peut effectuer un retrait après chaque lancer réussi ; la manche se termine au premier échec.
Cible — le joueur choisit un nombre total de lancers (jusqu’à 20) et un nombre minimum de prédictions correctes nécessaires pour gagner. Tous les lancers sont générés en un seul lot (count = totalFlips), et la manche est gagnée si le nombre de lancers correspondant à la prédiction du joueur atteint au moins le minimum choisi.
Floor is Lava
Objectif: Traversez une grille de 7×7 (49 cases) sur trois niveaux consécutifs sans marcher sur une case qui se transforme en lave.
Chaque niveau se déroule en « manches » de taille fixe — un lot de cases tombe simultanément dans la lave à chaque manche — et vous pouvez effectuer un retrait après avoir survécu à n’importe quelle manche.
Génération du plateau
Avant le début de la partie, la disposition des cases des trois niveaux est tirée en une seule shuffle vérifiable de manière équitable : une permutation aléatoire des entiers 0..146 (49 cases × 3 niveaux) est tirée sans remise à l’aide du générateur initialisé par seed, puis divisée par niveau :
// Draw a random permutation of 0..146 without replacement
// Example: raw = [103, 12, 140, 76, 3, ...]
const boards = [[], [], []];
for (const value of raw) {
boards[Math.floor(value / 49)].push(value % 49); // bucket = level, residue = tile index }
// boards[0], boards[1], boards[2] are each a uniformly random ordering of tiles 0..48
Tirer une permutation de 147 valeurs plutôt que trois 49s distincts de shuffle valeurs n’est qu’une astuce d’efficacité : comme exactement 49 des 147 valeurs se trouvent dans la plage de chaque niveau, chaque niveau obtient tout de même un ordre indépendant et uniformément aléatoire de ses propres 49 tuiles.
Manches : comment la lave progresse
L’ordre des 49 cases de chaque niveau est découpé en segments consécutifs non chevauchants : un nombre fixe de cases par manche (dropsPerRound), pour un nombre fixe de manches (roundsPerLevel), les cases restantes à la fin étant celles qui survivent à l’ensemble du niveau :
tilesDroppedInRound(level, dropsPerRound, roundIndex) = levelBoard.slice(roundIndex * dropsPerRound, (roundIndex + 1) * dropsPerRound)
tilesThatSurviveLevel(level, survivingCount) = levelBoard.slice(-survivingCount)
Comme la shuffle a déjà fixé l’ordre des cases à l’avance, l’ensemble des cases recouvertes de lave pendant la manche n’est pas choisi en réaction à votre sélection : il s’agit simplement du segment de la liste pré-shufflee prévu pour cette manche.
À chaque sélection, vous désignez une case (selectedTile). Cette case ne doit pas déjà appartenir à un lot de lave d’une manche précédente de ce niveau. La manche est ensuite résolue en comparant votre unique case à l’ensemble du lot de lave de la manche (selectedTile):
isLoss = droppedTiles.includes(selectedTile)
Si votre case fait partie des cases qui tombent pendant cette manche, la partie se termine. Sinon, vous avez survécu à la manche, quel que soit le nombre d’autres cases tombées en même temps.
Difficulté configuration
Quatre niveaux de difficulté établissent un compromis entre la taille des manches et leur nombre. Chacun couvre toujours l’intégralité du plateau de 49 cases (dropsPerRound × roundsPerLevel + survivingTiles = 49):
Difficulté | Cases tombant par manche | Manches par niveau | Cases survivantes |
Facile | 7 | 6 | 7 |
Moyen | 11 | 4 | 5 |
Difficile | 23 | 2 | 3 |
Toxique | 48 | 1 | 1 |
Progression des niveaux et de la partie
Une partie comporte 3 niveaux, chacun avec son propre plateau de 49 cases, généré indépendamment à l’aide d’une seed shufflee. Survivre à toutes les manches d’un niveau le termine et vous fait passer au plateau du niveau suivant, à la première manche ; terminer le troisième niveau achève la partie.
Retrait
Vous pouvez effectuer un retrait après avoir survécu à n’importe quelle manche — votre toute première sélection réussie le permet déjà.
Multiplicateur
multiplier = edgeMultiplier * 49^(levelsCleared+1) / (tilesRemainingInLevel * survivingTiles^levelsCleared)
levelsCleared est le nombre de niveaux entièrement terminés jusqu’à présent, tilesRemainingInLevel diminue du nombre de cases tombant pendant la manche chaque fois que vous survivez à une manche du niveau en cours, et survivingTiles est le nombre de survivants par niveau pour la difficulté concernée, indiqué dans le tableau ci-dessus.
Il s’agit d’un prix à cotes équitables : l’inverse de la probabilité cumulée d’avoir atteint ce point précis (niveaux terminés × manches survécues) avec un placement uniforme et aléatoire des cases, ajusté selon l’avantage de la maison.


