Vai al contenuto principale

Provably Fair - Eventi di gioco

Cosa sono gli eventi di gioco

In precedenza, abbiamo generato numeri in virgola mobile casuali compresi tra 0 e 1 utilizzando le funzioni generateFloats e byteGenerator. Gli eventi di gioco traducono questi numeri in virgola mobile generati in effettivi risultati di gioco. Questo avviene in modi diversi per i vari giochi. In Plinko, i numeri in virgola mobile aiutano a determinare la direzione di caduta della pallina. In Dice, aiutano a determinare il risultato del lancio dei dadi.

Di seguito trovi una spiegazione dettagliata di come traduciamo i numeri in virgola mobile in eventi o risultati equi per ogni gioco originale disponibile sulla nostra piattaforma.


Tower

In Tower, l'obiettivo del gioco è raggiungere la cima della torre e salvare la principessa trovando le chiavi ed evitando le mele avvelenate. Ogni riga della torre contiene un numero predefinito di caselle sicure (chiavi) e mele avvelenate, in base alla difficoltà selezionata.

L'evento di gioco in Tower determina la posizione delle caselle sicure (o chiavi) in ogni riga.

La torre è composta da un totale di 9 righe. Il numero di colonne e la distribuzione di chiavi e mele avvelenate variano in base alla difficoltà scelta:

  • Facile: 4 colonne - 3 chiavi e 1 mela avvelenata per riga

  • Media: 3 colonne - 2 chiavi e 1 mela avvelenata per riga

  • Difficile: 2 colonne - 1 chiave e 1 mela avvelenata per riga

  • Esperto: 3 colonne: - 1 chiave e 2 mele avvelenate per riga

  • Master: 4 colonne - 1 chiave e 3 mele avvelenate per riga

Il risultato di ogni riga viene determinato generando posizioni casuali delle chiavi (caselle sicure) tra le colonne disponibili, in base alla configurazione della difficoltà.
​

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

In Chicken, l'obiettivo è far avanzare il pollo attraverso il maggior numero possibile di corsie senza atterrare sulla corsia pericolosa.

Ogni passo in avanti riuscito aumenta il moltiplicatore della vincita. Il giocatore può effettuare il cashout in qualsiasi momento prima che il pollo salti sulla corsia pericolosa. Se il pollo raggiunge la zona pericolosa prima che il giocatore effettui il cashout, la puntata viene persa.

L'evento di gioco principale in Chicken è la determinazione della corsia pericolosa, ovvero la corsia specifica in cui il pollo perderà.

Chicken prevede 4 livelli di difficoltà. Il numero di corsie che il pollo può attraversare varia in base alla difficoltà selezionata:

  • Facile: 19 corsie

  • Media: 17 corsie

  • Difficile: 15 corsie

  • Esperto: 10 corsie

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

In Plinko, i giocatori possono scegliere una piramide con altezza da 8 a 16. Quando la pallina viene rilasciata dall'alto, cadrà a sinistra o a destra da 8 a 16 volte, ogni volta che colpisce un perno sul tabellone Plinko. L'evento di gioco in Plinko determina se la pallina cade a sinistra o a destra a ogni livello, rappresentati da 0 e 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

In Dice, un lancio di dadi può avere un valore compreso tra 0.00 e 100.00. L'evento di gioco in Dice determina questo valore. A tal fine utilizziamo (floats * 10,001) / 100). Poiché includiamo valori fino a 2 cifre decimali e il numero 0, esistono in totale 10,000 + 1 = 10,001 risultati possibili (inclusi i decimali).

// 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

In Limbo, l'obiettivo del gioco è indovinare un moltiplicatore >1. Se il risultato del gioco è superiore al moltiplicatore, l'utente vince la partita. L'evento di gioco genera un valore in virgola mobile superiore a 1 e segue una distribuzione a curva iperbolica.

// 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

In Keno, l'evento di gioco richiede l'identificazione di 10 posizionamenti univoci delle gemme sul tabellone del keno. A tale scopo, generiamo 10 diversi numeri in virgola mobile compresi tra 1 e 40, che rappresentano ciascuna posizione sul tabellone del keno. Viene quindi utilizzato un sistema per gestire i numeri in virgola mobile duplicati e restituire le 10 posizioni finali delle gemme.

// 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

In Mines, l'evento di gioco richiede l'identificazione della posizione delle mine sul tabellone Mines a 25 caselle. In base alla selezione dell'utente del numero di mine, generiamo un elenco di numeri interi da 0 a 24 per rappresentare i 25 spazi sul tabellone.

// 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

La nostra Roulette si basa sulla ruota europea a singolo ‘0’, con 37 diverse caselle che vanno da 0 a 36. L'evento di gioco nella roulette calcola la casella, tra le 37 caselle, in cui atterrerà la pallina.

// 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

In Wheel, un utente seleziona il numero di segmenti della ruota. A seconda del segmento su cui si ferma l'indicatore, l'utente vincerà il moltiplicatore di quel segmento. L'evento di gioco determina il 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

In Blackjack, il gioco utilizza un mazzo di carte illimitato. Pertanto, ogni turno avrà sempre la stessa probabilità. L'evento di gioco in Blackjack determina la carta estratta se il giocatore o il banco pesca una carta dal mazzo.

// 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

In Hilo, il gioco utilizza un mazzo di carte illimitato. Pertanto, ogni turno avrà sempre la stessa probabilità. L'obiettivo del gioco è indovinare se la carta successiva ha un valore più alto o più basso rispetto alla carta attuale. L'evento di gioco in Hilo determina quale carta pesca successivamente un utente, se viene pescata.

// 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

Il calcolo del risultato di una partita Crash si compone di 2 parti

  • Hash del blocco Bitcoin

  • Seed

Hash Bitcoin

I dettagli sono disponibili in questo tweet:

L'hash del blocco 779588 è disponibile sul block explorer BTC (https://btcscan.org/block/000000000000000000033ab71b5fa7edcafe388e5f49719f1de8e1f8d0670460) e corrisponde all'hash mostrato nel tweet.

Seed

Il processo di generazione dei seed per 10 milioni di partite Crash è progettato per garantire equità e trasparenza, impedendo qualsiasi possibilità di manipolazione da parte di Shuffle. Ecco come vengono generati e verificati i seed:

  1. Generazione del seed iniziale: Il seed della 10 milionesima partita viene scelto casualmente da Shuffle.

  2. Hash sequenziale per la generazione dei seed: Per generare i seed per tutte le altre partite, utilizziamo un metodo di hashing a ritroso:

    • Il seed di ogni partita è il risultato dell'hashing del seed della partita successiva. Nello specifico, l'hash della 9,999,999a partita è l'hash SHA256 dell'hash della 10 milionesima partita.

    • Questa catena continua in modo che il seed di ogni partita derivi dall'hashing dell'hash della partita successiva, fino al seed della prima partita, che deriva dall'hash della seconda partita.

  3. Verifica dell'integrità del seed:

    • Pubblicando il valore sottoposto ad hashing dell'hash utilizzato nella prima partita, i giocatori possono verificare in modo indipendente l'integrità dell'hash per tutte le partite fino alla 10 milionesima partita.

    • Con questo metodo, se conosci l'hash di una partita specifica, puoi verificare i seed di tutte le partite precedenti. Ad esempio, se conosci il seed della partita 1323185, puoi eseguire l'hashing di questo seed per verificare che corrisponda al seed della partita 1323184, e così via, fino alla prima partita.

  4. Trasparenza e sicurezza:

    • Utilizzando un hash di un futuro blocco BTC, al di fuori del controllo di Shuffle, e pubblicando la versione sottoposta ad hashing del seed della prima partita, gli utenti possono dimostrare che i risultati delle partite Crash sono equi e non sono stati manomessi.

Slide

Slide utilizza lo stesso processo di provably fair e di generazione dei seed di Crash. La generazione dei seed è stata effettuata pubblicamente su x.com, e il post è disponibile qui.

L'hash del blocco 952294 è disponibile sul block explorer BTC (https://btcscan.org/block/00000000000000000000a382760ff19cdb7f9f22147b9123f3e471fb3c6d129a) e corrisponde all'hash mostrato nel tweet.

Baccarat

In Baccarat, il gioco utilizza un mazzo di carte illimitato. Pertanto, ogni turno avrà sempre la stessa probabilità. L'evento di gioco in Baccarat determina le carte distribuite sia alla mano del Giocatore sia a quella del Banco, durante la distribuzione iniziale e le eventuali pescate aggiuntive secondo le regole standard del Baccarat.

Le carte vengono distribuite nel seguente ordine dalla sequenza di numeri in virgola mobile generata:

  1. Prima carta del Giocatore - indice del numero in virgola mobile 0

  2. Prima carta del Banco - indice del numero in virgola mobile 1

  3. Seconda carta del Giocatore - indice del numero in virgola mobile 2

  4. Seconda carta del Banco - indice del numero in virgola mobile 3

  5. Terza carta del Giocatore (se pescata) - indice del numero in virgola mobile 4

  6. Terza carta del Banco (se pescata)

    1. indice del numero in virgola mobile 4 - se il giocatore non ha pescato la terza carta

    2. indice del numero in virgola mobile 5 - se il giocatore pesca la terza 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

In Blitz, il gioco utilizza un mazzo di carte illimitato. Pertanto, ogni turno avrà sempre la stessa probabilità. L'evento di gioco in Blitz determina le carte distribuite al giocatore in base al numero di pescate scelto e verifica se tutte le carte pescate sono univoche.

// 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

In Coinflip, l'obiettivo è indovinare correttamente il lato su cui atterra una moneta lanciata. L'evento di gioco in Coinflip determina il risultato (TESTA o CROCE) di ogni lancio della moneta. Coinflip dispone di tre modalità — Classica (un singolo lancio), Classica progressiva (lanci ripetuti e cashout in qualsiasi momento) e Obiettivo (scegliere un numero di lanci e il numero minimo di pronostici corretti necessario per vincere) — ma tutte e tre ricavano i risultati dei lanci nello stesso modo.

Ogni lancio viene determinato generando un numero in virgola mobile e associandolo a 0 o 1, esattamente come Plinko associa i suoi numeri in virgola mobile alle cadute a sinistra/destra:

// 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;


Classica — viene generato un lancio (conteggio = 1). Il giocatore vince se il lato selezionato corrisponde al risultato del lancio.

Classica progressiva — i lanci vengono generati uno alla volta man mano che il giocatore continua, ma il risultato di ogni lancio è stabilito dall'inizio del round: l'ennesimo lancio deriva sempre dalla generazione dei lanci da 1 fino a N e dalla selezione dell'ultimo; pertanto, rivelare i lanci uno alla volta mentre il giocatore avanza non modifica il risultato che avrebbe avuto ciascun lancio. Il giocatore può effettuare il cashout dopo ogni lancio riuscito; il round termina al primo errore.

Obiettivo — il giocatore sceglie un numero totale di lanci (fino a 20) e un numero minimo di pronostici corretti necessario per vincere. Tutti i lanci vengono generati in un unico gruppo (conteggio = totalFlips) e il round viene vinto se il numero di lanci che corrispondono alla previsione del giocatore è almeno pari al minimo scelto.

Floor is Lava

Obiettivo: Attraversa una griglia di 7×7 (49 caselle) in tre livelli consecutivi senza calpestare una casella che si trasforma in lava.

Ogni livello si svolge in "round" di dimensione fissa: un gruppo di caselle cade nella lava contemporaneamente in ogni round e puoi effettuare il cashout dopo essere sopravvissuto a qualsiasi round.

Generazione del tabellone

Prima dell'inizio del round, la disposizione delle caselle per tutti e tre i livelli viene estratta in un'unica shuffle Provably Fair: una permutazione casuale degli interi 0..146 (49 caselle × 3 livelli) viene estratta senza reinserimento dal generatore basato sul seed, quindi suddivisa per livello:

// 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

Estrarre un'unica permutazione di 147 valori invece di tre 49 separati di shuffle valori è solo un accorgimento di efficienza — poiché esattamente 49 dei 147 valori rientrano nell'intervallo di ciascun livello, ogni livello mantiene comunque un ordinamento indipendente e uniformemente casuale delle proprie 49 tessere.

Round: come cade la lava

L'ordinamento delle 49 caselle di ciascun livello è suddiviso in gruppi consecutivi e non sovrapposti: un numero fisso di caselle per round (dropsPerRound), per un numero fisso di round (roundsPerLevel), mentre le caselle rimanenti alla fine sono quelle che sopravvivono all'intero livello:

tilesDroppedInRound(level, dropsPerRound, roundIndex) = levelBoard.slice(roundIndex * dropsPerRound, (roundIndex + 1) * dropsPerRound) 

tilesThatSurviveLevel(level, survivingCount) = levelBoard.slice(-survivingCount)

Poiché la shuffle ha già stabilito l'ordine delle caselle in anticipo, il gruppo di lava del round non viene scelto in base alla tua selezione: è semplicemente la porzione dell'elenco pre-shuffle prevista per quel round.

A ogni selezione, indichi una casella (selectedTile). Tale casella non deve già appartenere a un gruppo di lava di un round precedente in questo livello. Il round viene quindi risolto confrontando la tua singola casella con l' intero gruppo di lava del round (selectedTile):

isLoss = droppedTiles.includes(selectedTile) 

Se la tua casella è una di quelle che cadono in questo round, la partita termina. Altrimenti, sopravvivi al round, indipendentemente da quante altre caselle sono cadute insieme ad essa.

Difficoltà configurazione

Quattro livelli di difficoltà bilanciano la dimensione del round e il numero di round. Ognuno copre comunque l'intero tabellone di 49 caselle (dropsPerRound × roundsPerLevel + survivingTiles = 49):

Difficoltà

Cadute per round

Round per livello

Caselle sopravvissute

Facile

7

6

7

Media

11

4

5

Difficile

23

2

3

Tossica

48

1

1

Progressione di livelli e partita

Una partita ha 3 livelli, ciascuno con il proprio tabellone di 49 caselle generato indipendentemente con seed shuffle. Sopravvivere a tutti i round di un livello lo completa e ti porta al tabellone del livello successivo, al round uno; completare il terzo livello conclude la partita.

Cashout

Puoi effettuare il cashout dopo essere sopravvissuto a qualsiasi round: è disponibile già dopo la tua prima selezione riuscita.

Moltiplicatore

multiplier = edgeMultiplier * 49^(levelsCleared+1) / (tilesRemainingInLevel * survivingTiles^levelsCleared) 

levelsCleared è il numero di livelli completati finora, tilesRemainingInLevel si riduce del numero di caselle cadute nel round ogni volta che sopravvivi a un round nel livello corrente, e survivingTiles è il numero di sopravvissuti per livello della difficoltà, indicato nella tabella precedente.

Si tratta di una quota equa: l'inverso della probabilità cumulativa di aver raggiunto quel punto esatto (livelli completati × round sopravvissuti) con il posizionamento casuale uniforme delle caselle, adeguato al margine della casa.

Hai ricevuto la risposta alla tua domanda?