Qué son los eventos del juego
Anteriormente, generamos números de punto flotante aleatorios entre 0 y 1 mediante las funciones generateFloats y byteGenerator. Los eventos del juego convierten estos números de punto flotante generados en resultados reales del juego. Esto ocurre de distintas maneras según el juego. En Plinko, los números de punto flotante ayudan a determinar la dirección de la caída de la bola. En Dice, ayudan a determinar el resultado de la tirada de dados.
A continuación, se ofrece una explicación detallada de cómo convertimos los números de punto flotante en eventos o resultados justos para cada juego original disponible en nuestra plataforma.
Tower
En Tower, el objetivo del juego es llegar a la cima de la torre y salvar a la princesa encontrando las llaves y evitando las manzanas envenenadas. Cada fila de la torre contiene una cantidad predefinida de casillas seguras (llaves) y manzanas envenenadas, según la dificultad seleccionada.
El evento del juego en Tower consiste en determinar la posición de las casillas seguras (o llaves) en cada fila.
Hay un total de 9 filas en la torre. La cantidad de columnas y la distribución de llaves y manzanas envenenadas varían según la dificultad elegida:
Fácil: 4 columnas - 3 llaves y 1 manzana envenenada por fila
Media: 3 columnas - 2 llaves y 1 manzana envenenada por fila
Difícil: 2 columnas - 1 llave y 1 manzana envenenada por fila
Experto: 3 columnas: - 1 llave y 2 manzanas envenenadas por fila
Maestro: 4 columnas - 1 llave y 3 manzanas envenenadas por fila
El resultado de cada fila se determina generando posiciones aleatorias para las llaves (casillas seguras) dentro de las columnas disponibles, según la configuración de dificultad.
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
En Chicken, el objetivo es mover al pollo por tantos carriles como sea posible sin caer en el carril peligroso.
Cada paso exitoso hacia adelante aumenta el multiplicador de pago. El jugador puede retirar sus ganancias en cualquier momento antes de que el pollo salte al carril peligroso. Si el pollo llega a la zona peligrosa antes de que el jugador retire sus ganancias, se pierde la apuesta.
El evento principal del juego en Chicken es determinar el carril peligroso: el carril específico en el que el pollo perderá.
Hay 4 dificultades en Chicken. La cantidad de carriles que el pollo puede cruzar varía según la dificultad seleccionada:
Fácil: 19 carriles
Media: 17 carriles
Difícil: 15 carriles
Experto: 10 carriles
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
En Plinko, los jugadores pueden elegir una pirámide con una altura de 8 a 16. A medida que la bola se suelta desde la parte superior, caerá a la izquierda o a la derecha de 8 a 16 veces, cada vez que golpee una clavija en el tablero de Plinko. El evento del juego en Plinko consiste en determinar si la bola cae a la izquierda o a la derecha en cada nivel, representado por 0 y 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
En Dice, una tirada de dados puede tener un valor entre 0.00 y 100.00. El evento del juego en Dice consiste en determinar este valor. Lo hacemos utilizando (floats * 10,001) / 100). Como incluimos valores de hasta 2 decimales y el número 0, hay un total de 10,000 + 1 = 10,001 resultados posibles (incluidos los decimales).
// 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
En Limbo, el objetivo del juego es adivinar un multiplicador >1. Si el resultado del juego es mayor que el multiplicador, el usuario gana el juego. El evento del juego consiste en generar un valor de punto flotante superior a 1 y que siga una distribución de curva hiperbólica.
// 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
En Keno, el evento del juego requiere identificar 10 ubicaciones únicas de gemas en el tablero de keno. Para lograrlo, generamos 10 números de punto flotante diferentes entre 1 y 40, que representan cada ubicación en el tablero de keno. Luego se utiliza un método para gestionar los números de punto flotante duplicados y devolver las 10 ubicaciones finales de las gemas.
// 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
En Mines, el evento del juego requiere identificar la ubicación de las minas en el tablero de 25 casillas. Según la selección del usuario de una cantidad de minas, generamos una lista de números enteros de 0 a 24 para representar los 25 espacios del tablero.
// 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
Nuestra Roulette se basa en la rueda europea de un solo ‘0’, que tiene 37 casillas diferentes, desde 0 hasta 36. El evento del juego en Roulette consiste en calcular, entre las 37 casillas, en cuál caerá la bola.
// 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
En Wheel, el usuario selecciona la cantidad de segmentos que tendrá la rueda. Según el segmento en el que se detenga el indicador, el usuario ganará el multiplicador de ese segmento. El evento del juego consiste en determinar el segmento.
// 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
En Blackjack, el juego se juega con una baraja ilimitada. Por lo tanto, cada turno siempre tendrá la misma probabilidad. El evento del juego en Blackjack consiste en determinar la carta que se roba si el jugador o la banca roba una carta de la baraja.
// 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
En Hilo, el juego se juega con una baraja ilimitada. Por lo tanto, cada turno siempre tendrá la misma probabilidad. El objetivo del juego es adivinar si la siguiente carta tiene un valor mayor o menor que la carta actual. El evento del juego en Hilo consiste en determinar qué carta roba el usuario a continuación (si roba una).
// 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
Hay 2 partes en el cálculo del resultado de un juego de Crash
Hash de bloque de Bitcoin
Semilla
Hash de Bitcoin
Los detalles están en este tuit:
El hash del bloque 779588 se puede encontrar en el explorador de bloques de BTC (https://btcscan.org/block/000000000000000000033ab71b5fa7edcafe388e5f49719f1de8e1f8d0670460) y coincide con el hash que se muestra en el tuit.
Semilla
El proceso para generar semillas para 10 millones de juegos de Crash está diseñado para garantizar la imparcialidad y la transparencia, evitando cualquier posibilidad de manipulación por parte de Shuffle. Así se generan y verifican las semillas:
Generación inicial de semillas: La semilla del juego número 10 millones se elige aleatoriamente por Shuffle .
Hash secuencial para la generación de semillas: Para generar las semillas de todos los demás juegos, utilizamos un método de hash inverso:
La semilla de cada juego es el resultado de aplicar hash a la semilla del juego siguiente. Específicamente, el hash del juego número 9,999,999 es el hash SHA256 del hash del juego número 10 millones.
Este encadenamiento continúa de modo que la semilla de cada juego se deriva de aplicar hash al hash del juego siguiente, culminando con la semilla del primer juego, que se deriva del hash del segundo juego.
Verificación de la integridad de las semillas:
Al publicar el valor con hash del hash utilizado en el primer juego, los jugadores pueden verificar de forma independiente la integridad del hash de todos los juegos hasta el juego número 10 millones.
Con este método, si conocen el hash de cualquier juego específico, pueden verificar las semillas de todos los juegos anteriores. Por ejemplo, si conocen la semilla del juego 1323185, pueden aplicar hash a esta semilla para verificar que coincide con la semilla del juego 1323184, y así sucesivamente hasta llegar al primer juego.
Por ejemplo, en el juego 1323185 (https://shuffle.com/games/originals/crash?md-id=1323185&modal=crashGame), el hash es 2f824ebb9548a4d24d042ecab390acd1dc931048f696dc03c738937852825ac0
En el juego 1323184 (https://shuffle.com/games/originals/crash?md-id=1323184&modal=crashGame), el hash es 20f42ccc46fed8e005767f85eb4bd5d42d3bd2a72b01ae77dcc7071bec280265
El hash del juego 1323184 es el hash SHA256 del hash de 1323185. Pueden utilizar una calculadora de SHA256 en línea como https://xorbin.com/tools/sha256-hash-calculator para verificarlo
Transparencia y seguridad:
Al utilizar un hash de bloque de BTC futuro que está fuera del control de Shuffle y publicar la versión con hash de la semilla del primer juego, los usuarios pueden comprobar que los resultados de los juegos de Crash son justos y no fueron manipulados.
Slide
Slide utiliza el mismo proceso de provably fair y semillas que Crash. La generación de semillas se realizó públicamente en x.com, y la publicación se puede encontrar aquí.
El hash del bloque 952294 se puede encontrar en el explorador de bloques de BTC (https://btcscan.org/block/00000000000000000000a382760ff19cdb7f9f22147b9123f3e471fb3c6d129a) y coincide con el hash que se muestra en el tuit.
Baccarat
En Baccarat, el juego se juega con una baraja ilimitada. Por lo tanto, cada turno siempre tendrá la misma probabilidad. El evento del juego en Baccarat consiste en determinar las cartas repartidas tanto a la mano del Jugador como a la de la Banca durante el reparto inicial y cualquier carta adicional, siguiendo las reglas estándar de robo de Baccarat.
Las cartas se reparten en el siguiente orden a partir de la secuencia de números de punto flotante generada:
Primera carta del Jugador - índice de punto flotante 0
Primera carta de la Banca - índice de punto flotante 1
Segunda carta del Jugador - índice de punto flotante 2
Segunda carta de la Banca - índice de punto flotante 3
Tercera carta del Jugador (si roba) - índice de punto flotante 4
Tercera carta de la Banca (si roba)
índice de punto flotante 4 - si el jugador no robó la tercera carta
índice de punto flotante 5 - si el jugador roba la tercera carta
// 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
En Blitz, el juego se juega con una baraja ilimitada. Por lo tanto, cada turno siempre tendrá la misma probabilidad. El evento del juego en Blitz consiste en determinar las cartas repartidas al jugador según la cantidad de cartas que haya elegido robar y evaluar si todas las cartas robadas son únicas.
// 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
En Coinflip, el objetivo es acertar el lado en el que cae una moneda lanzada. El evento del juego en Coinflip consiste en determinar el resultado (CARA o CRUZ) de cada lanzamiento. Coinflip tiene tres modos: Clásico (un solo lanzamiento), Clásico Progresivo (lanzar repetidamente y retirar las ganancias en cualquier momento) y Objetivo (elegir una cantidad de lanzamientos y el número mínimo de aciertos necesarios para ganar). Sin embargo, los tres determinan los resultados de los lanzamientos de la misma forma.
Cada lanzamiento se determina generando un número de punto flotante y asignándolo a 0 o 1, exactamente como Plinko asigna sus números de punto flotante a caídas a la izquierda o a la derecha:
// 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;
Clásico: se genera un lanzamiento (cantidad = 1). El jugador gana si el lado que eligió coincide con el lanzamiento.
Clásico Progresivo: los lanzamientos se generan de uno en uno a medida que el jugador continúa, pero el resultado de cada lanzamiento queda establecido desde el inicio de la ronda: el lanzamiento número N siempre se obtiene generando los lanzamientos de 1 a N y tomando el último. Por lo tanto, revelar los lanzamientos uno por uno a medida que el jugador avanza no cambia cuál habría sido el resultado de un lanzamiento determinado. El jugador puede retirar sus ganancias después de cualquier lanzamiento exitoso; la ronda termina con el primer fallo.
Objetivo: el jugador elige una cantidad total de lanzamientos (hasta 20) y un número mínimo de aciertos necesarios para ganar. Todos los lanzamientos se generan como un solo lote (cantidad = totalFlips), y se gana la ronda si la cantidad de lanzamientos que coincide con la elección del jugador es al menos el mínimo seleccionado.
Floor is Lava
Objetivo: Cruzar una cuadrícula de 7×7 (49 casillas) a lo largo de tres niveles consecutivos sin pisar una casilla que se convierta en lava.
Cada nivel se desarrolla en «rondas» de tamaño fijo: en cada ronda, un lote de casillas cae en la lava a la vez, y pueden retirar sus ganancias después de sobrevivir a cualquier ronda.
Generación del tablero
Antes de que comience la ronda, la distribución de casillas de los tres niveles se genera en un solo shuffle Provably Fair: se genera, sin reemplazo, una permutación aleatoria de los números enteros 0..146 (49 casillas × 3 niveles) con el generador basado en semillas y luego se divide por nivel:
// 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
Generar una permutación de 147 valores en lugar de tres 49 de shuffle valores por separado es solo un truco de eficiencia; dado que exactamente 49 de los 147 valores se encuentran dentro del rango de cada nivel, cada nivel termina teniendo un ordenamiento independiente y uniformemente aleatorio de sus propias 49 fichas.
Rondas: cómo cae la lava
El ordenamiento de 49 casillas de cada nivel se divide en grupos consecutivos y sin superposición: una cantidad fija de casillas por ronda (dropsPerRound), durante una cantidad fija de rondas (roundsPerLevel), y las casillas restantes al final son las que sobreviven todo el nivel:
tilesDroppedInRound(level, dropsPerRound, roundIndex) = levelBoard.slice(roundIndex * dropsPerRound, (roundIndex + 1) * dropsPerRound)
tilesThatSurviveLevel(level, survivingCount) = levelBoard.slice(-survivingCount)
Dado que el shuffle ya fijó el orden de las casillas desde el inicio, el conjunto de lava de la ronda no se elige como reacción a su selección: es el segmento de la lista pre-shuffleda que corresponde a esa ronda.
En cada selección, eligen una casilla (selectedTile). Esa casilla no debe pertenecer ya a un lote de lava de una ronda anterior en este nivel. Luego, la ronda se resuelve comparando su única casilla con el lote de lava completo de la ronda (selectedTile):
isLoss = droppedTiles.includes(selectedTile)
Si su casilla es una de las que caen en esta ronda, la partida termina. De lo contrario, sobreviven la ronda, sin importar cuántas otras casillas hayan caído junto con ella.
Dificultad configuración
Cuatro dificultades equilibran el tamaño de la ronda con la cantidad de rondas. Todas suman el tablero completo de 49 casillas (dropsPerRound × roundsPerLevel + survivingTiles = 49):
Dificultad | Caídas por ronda | Rondas por nivel | Casillas supervivientes |
Fácil | 7 | 6 | 7 |
Media | 11 | 4 | 5 |
Difícil | 23 | 2 | 3 |
Tóxica | 48 | 1 | 1 |
Progresión de niveles y del juego
Un juego tiene 3 niveles, cada uno con su propio tablero de 49 casillas generado de forma independiente con shuffle. Sobrevivir todas las rondas de un nivel lo completa y los lleva al tablero del siguiente nivel en la primera ronda; completar el tercer nivel finaliza el juego.
Retiro de ganancias
Pueden retirar sus ganancias después de sobrevivir a cualquier ronda; su primera selección exitosa ya lo habilita.
Multiplicador
multiplier = edgeMultiplier * 49^(levelsCleared+1) / (tilesRemainingInLevel * survivingTiles^levelsCleared)
levelsCleared es la cantidad de niveles completados por completo hasta el momento, tilesRemainingInLevel se reduce según la cantidad de caídas de la ronda cada vez que sobreviven una ronda dentro del nivel actual, y survivingTiles es la cantidad de casillas supervivientes por nivel de la dificultad, según la tabla anterior.
Este es un precio con probabilidades justas: el inverso de la probabilidad acumulada de haber llegado a ese punto exacto (niveles completados × rondas sobrevividas) con una distribución uniforme y aleatoria de las casillas, ajustado por la ventaja de la casa.


