Przejdź do głównej zawartości

Provably Fair - Zdarzenia w grze

Czym są zdarzenia w grze

Wcześniej generowaliśmy losowe liczby zmiennoprzecinkowe od 0 do 1 za pomocą funkcji generateFloats i byteGenerator. Zdarzenia w grze przekształcają te wygenerowane liczby w rzeczywiste wyniki gry. W zależności od gry odbywa się to na różne sposoby. W Plinko liczby zmiennoprzecinkowe pomagają określić kierunek spadania kulki. W Dice pomagają określić wynik rzutu kośćmi.

Poniżej znajdziesz szczegółowe wyjaśnienie, jak przekształcamy liczby zmiennoprzecinkowe w uczciwe zdarzenia lub wyniki dla każdej autorskiej gry dostępnej na naszej platformie.


Tower

W Tower celem gry jest dotarcie na szczyt wieży i uratowanie księżniczki poprzez znajdowanie kluczy oraz unikanie zatrutych jabłek. Każdy rząd wieży zawiera określoną liczbę bezpiecznych pól (kluczy) i zatrutych jabłek, zależnie od wybranego poziomu trudności.

Zdarzenie w grze Tower polega na określeniu pozycji bezpiecznych pól (lub kluczy) w każdym rzędzie.

Wieża ma łącznie 9 rzędów. Liczba kolumn oraz rozmieszczenie kluczy i zatrutych jabłek różnią się zależnie od wybranego poziomu trudności:

  • Łatwy: 4 kolumn - 3 klucze i 1 zatrute jabłko w każdym rzędzie

  • Średni: 3 kolumn - 2 klucze i 1 zatrute jabłko w każdym rzędzie

  • Trudny: 2 kolumn - 1 klucz i 1 zatrute jabłko w każdym rzędzie

  • Ekspert: 3 kolumn: - 1 klucz i 2 zatrute jabłka w każdym rzędzie

  • Mistrz: 4 kolumn - 1 klucz i 3 zatrute jabłka w każdym rzędzie

Wynik dla każdego rzędu jest ustalany poprzez wygenerowanie losowych pozycji kluczy (bezpiecznych pól) w dostępnych kolumnach na podstawie konfiguracji poziomu trudności.
​

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

W Chicken celem gry jest przeprowadzenie kurczaka przez jak największą liczbę pasów bez wylądowania na niebezpiecznym pasie.

Każdy udany krok naprzód zwiększa mnożnik wypłaty. Gracz może dokonać wypłaty w dowolnym momencie, zanim kurczak wskoczy na niebezpieczny pas. Jeśli kurczak dotrze do strefy zagrożenia przed dokonaniem wypłaty przez gracza, zakład zostaje przegrany.

Główne zdarzenie w grze Chicken polega na określeniu niebezpiecznego pasa — konkretnego pasa, na którym kurczak przegra.

W Chicken są 4 poziomy trudności. Liczba pasów, które kurczak może przekroczyć, zależy od wybranego poziomu trudności:

  • Łatwy: 19 pasów

  • Średni: 17 pasów

  • Trudny: 15 pasów

  • Ekspert: 10 pasów

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

W Plinko gracze mogą wybrać piramidę o wysokości od 8 do 16. Po wypuszczeniu kulki z góry, przy każdym uderzeniu w kołek na planszy Plinko spada ona w lewo lub w prawo od 8 do 16 razy. Zdarzenie w grze Plinko polega na określeniu, czy kulka spada w lewo, czy w prawo na każdym poziomie, co jest reprezentowane przez 0 i 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

W Dice rzut kośćmi może przyjąć wartość od 0.00 do 100.00. Zdarzenie w grze Dice polega na określeniu tej wartości. Robimy to za pomocą (floats * 10,001) / 100). Ponieważ uwzględniamy wartości do 2 miejsc po przecinku oraz liczbę 0, istnieje łącznie 10,000 + 1 = 10,001 możliwych wyników (w tym miejsca po przecinku).

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

W Limbo celem gry jest wybranie mnożnika >1. Jeśli wynik gry jest większy niż mnożnik, użytkownik wygrywa grę. Zdarzenie w grze polega na wygenerowaniu wartości zmiennoprzecinkowej większej niż 1, która podlega rozkładowi krzywej hiperbolicznej.

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

W Keno zdarzenie w grze wymaga wskazania 10 unikalnych położeń klejnotów na planszy keno. Aby to osiągnąć, generujemy 10 różnych liczb zmiennoprzecinkowych od 1 do 40., reprezentujących każdą lokalizację na planszy keno. Następnie używane jest do obsługi zduplikowanych liczb zmiennoprzecinkowych i zwrócenia końcowych 10 lokalizacji klejnotów

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

W Mines zdarzenie w grze wymaga określenia rozmieszczenia min na planszy Mines z 25 polami. Na podstawie liczby min wybranej przez użytkownika generujemy listę liczb całkowitych od 0 do 24, aby reprezentować 25 pól na planszy.

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

Nasza Roulette opiera się na europejskim kole z pojedynczym ‘0’, na którym znajduje się 37 różnych pól, od 0 do 36. Zdarzenie w grze Roulette polega na obliczeniu pola spośród 37 pól, na którym zatrzyma się kulka.

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

W Wheel użytkownik wybiera liczbę segmentów koła. W zależności od segmentu, na którym zatrzyma się wskaźnik, użytkownik wygrywa mnożnik przypisany do tego segmentu. Zdarzenie w grze polega na określeniu segmentu.

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

W Blackjack gra odbywa się przy użyciu nieograniczonej talii kart. Dlatego każda tura zawsze ma takie samo prawdopodobieństwo. Zdarzenie w grze Blackjack polega na określeniu dobieranej karty, gdy gracz lub bankier dobiera kartę z talii.

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

W Hilo gra odbywa się przy użyciu nieograniczonej talii kart. Dlatego każda tura zawsze ma takie samo prawdopodobieństwo. Celem gry jest odgadnięcie, czy następna karta będzie miała wyższą, czy niższą wartość od aktualnej karty. Zdarzenie w grze Hilo polega na określeniu, którą kartę użytkownik dobierze jako następną (jeśli dobierze).

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

Obliczenie wyniku gry Crash składa się z 2 części

  • Hash bloku Bitcoin

  • Seed

Hash Bitcoin

Szczegóły znajdują się w tym tweecie:

Hash bloku 779588 można znaleźć w eksploratorze bloków BTC (https://btcscan.org/block/000000000000000000033ab71b5fa7edcafe388e5f49719f1de8e1f8d0670460), a jego wartość jest zgodna z hashem pokazanym w tweecie.

Seed

Proces generowania seedów dla 10 milionów gier Crash został zaprojektowany w celu zapewnienia uczciwości i przejrzystości oraz uniemożliwienia jakiejkolwiek manipulacji przez Shuffle. Oto sposób generowania i weryfikacji seedów:

  1. Początkowe generowanie seedu: Seed dla 10 milionowej gry jest losowo wybierany przez Shuffle

  2. Sekwencyjne hashowanie na potrzeby generowania seedów: Aby wygenerować seedy dla wszystkich pozostałych gier, stosujemy metodę hashowania wstecznego:

    • Seed dla każdej gry jest wynikiem zahashowania seedu kolejnej gry. W szczególności hash 9,999,999. gry jest hashem SHA256 hasha 10 milionowej gry.

    • To łańcuchowanie jest kontynuowane w taki sposób, że seed każdej gry jest uzyskiwany przez hashowanie hasha następnej gry, aż seed pierwszej gry zostanie uzyskany z hasha drugiej gry.

  3. Weryfikacja integralności seedu:

    • Publikując zahashowaną wartość hasha użytego w pierwszej grze, gracze mogą niezależnie zweryfikować integralność hasha dla wszystkich gier aż do 10 milionowej gry.

    • Dzięki tej metodzie, jeśli znasz hash dowolnej konkretnej gry, możesz zweryfikować seedy wszystkich wcześniejszych gier. Na przykład, jeśli znasz seed gry 1323185, możesz zahashować ten seed, aby sprawdzić, czy odpowiada seedowi gry 1323184, i tak dalej, aż do pierwszej gry.

  4. Przejrzystość i bezpieczeństwo:

    • Dzięki wykorzystaniu hasha przyszłego bloku BTC, który jest poza kontrolą Shuffle, oraz opublikowaniu zahashowanej wersji seedu pierwszej gry użytkownicy mogą udowodnić, że wyniki gier Crash są uczciwe i nie zostały zmanipulowane.

Slide

Slide wykorzystuje ten sam proces provably fair i seedowania co Crash. Seedowanie zostało przeprowadzone publicznie na x.com, a post można znaleźć tutaj.

Hash bloku 952294 można znaleźć w eksploratorze bloków BTC (https://btcscan.org/block/00000000000000000000a382760ff19cdb7f9f22147b9123f3e471fb3c6d129a) i jest zgodny z hashem pokazanym w tweecie.

Baccarat

W Baccarat gra odbywa się przy użyciu nieograniczonej talii kart. Dlatego każda tura zawsze ma takie samo prawdopodobieństwo. Zdarzenie w grze Baccarat polega na określeniu kart rozdawanych zarówno Graczowi, jak i Bankierowi podczas początkowego rozdania oraz wszelkich dodatkowych dobrań zgodnie ze standardowymi zasadami dobierania w Baccarat.

Karty są rozdawane w następującej kolejności z wygenerowanej sekwencji liczb zmiennoprzecinkowych:

  1. Pierwsza karta Gracza - indeks liczby zmiennoprzecinkowej 0

  2. Pierwsza karta Bankiera - indeks liczby zmiennoprzecinkowej 1

  3. Druga karta Gracza - indeks liczby zmiennoprzecinkowej 2

  4. Druga karta Bankiera - indeks liczby zmiennoprzecinkowej 3

  5. Trzecia karta Gracza (jeśli zostanie dobrana) - indeks liczby zmiennoprzecinkowej 4

  6. Trzecia karta Bankiera (jeśli zostanie dobrana)

    1. indeks liczby zmiennoprzecinkowej 4 - jeśli gracz nie dobierze trzeciej karty

    2. indeks liczby zmiennoprzecinkowej 5 - jeśli gracz dobierze trzecią kartę

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

W Blitz gra odbywa się przy użyciu nieograniczonej talii kart. Dlatego każda tura zawsze ma takie samo prawdopodobieństwo. Zdarzenie w grze Blitz polega na określeniu kart rozdawanych graczowi zgodnie z wybraną liczbą dobrań oraz na sprawdzeniu, czy wszystkie dobrane karty są unikalne.

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

W Coinflip celem jest prawidłowe wskazanie strony, na której wyląduje rzucona moneta. Zdarzenie w grze Coinflip polega na określeniu wyniku (ORZEŁ lub RESZKA) każdego rzutu monetą. Coinflip ma trzy tryby — Classic (pojedynczy rzut), Classic Progressive (kolejne rzuty z możliwością wypłaty w dowolnym momencie) oraz Target (wybór liczby rzutów i minimalnej liczby poprawnych wskazań wymaganych do wygranej) — ale we wszystkich trzech wyniki rzutów są ustalane w ten sam sposób.

Każdy rzut jest określany przez wygenerowanie liczby zmiennoprzecinkowej i przypisanie jej do 0 lub 1, dokładnie tak jak w Plinko liczby zmiennoprzecinkowe są przypisywane do spadków w lewo/prawo:

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


Classic — generowany jest jeden rzut (liczba = 1). Gracz wygrywa, jeśli wybrana przez niego strona odpowiada wynikowi rzutu.

Classic Progressive — rzuty są generowane pojedynczo w miarę kontynuowania gry przez gracza, ale wynik każdego rzutu jest ustalony od początku rundy: N-ty rzut jest zawsze wyprowadzany przez wygenerowanie rzutów od 1 do N i wybranie ostatniego, więc ujawnianie rzutów pojedynczo w miarę postępów gracza nie zmienia wyniku żadnego z nich. Gracz może dokonać wypłaty po każdym udanym rzucie; runda kończy się przy pierwszym niepowodzeniu.

Target — gracz wybiera łączną liczbę rzutów (do 20) oraz minimalną liczbę poprawnych wskazań wymaganą do wygranej. Wszystkie rzuty są generowane jako jedna partia (liczba = totalFlips), a runda jest wygrana, jeśli liczba rzutów zgodnych ze wskazaniem gracza jest co najmniej równa wybranemu minimum.

Floor is Lava

Cel: Przejdź przez siatkę 7×7 (49 pól) na trzech kolejnych poziomach, nie stając na polu, które zamienia się w lawę.

Każdy poziom rozgrywany jest w „rundach” o stałym rozmiarze — w każdej rundzie cała partia pól jednocześnie wpada do lawy — a po przetrwaniu dowolnej rundy możesz dokonać wypłaty.

Generowanie planszy

Przed rozpoczęciem rundy układ pól dla wszystkich trzech poziomów jest losowany w ramach jednego, uczciwego shuffle: losowa permutacja liczb całkowitych 0..146 (49 pól × 3 poziomy) jest losowana bez powtórzeń z generatora opartego na seedzie, a następnie dzielona według poziomów:

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

Losowanie jednej permutacji o 147 wartościach zamiast trzech oddzielnych 49-wartościowych shuffle to wyłącznie optymalizacja wydajności — ponieważ dokładnie 49 z 147 wartości przypada na zakres każdego poziomu, każdy poziom nadal otrzymuje niezależną, równomiernie losową kolejność własnych 49 pól.

Rundy: jak pola wpadają do lawy

Kolejność 49 pól na każdym poziomie jest dzielona na następujące po sobie, niepokrywające się części: stałą liczbę pól na rundę (dropsPerRound), dla stałej liczby rund (roundsPerLevel), przy czym pola pozostałe na końcu to pola, które przetrwają cały poziom:

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

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

Ponieważ shuffle ustalił już kolejność pól z góry, zestaw pól wpadających do lawy w rundzie nie jest wybierany w reakcji na Twój wybór — jest to część wcześniej shufflenej listy, której kolej przypada w tej rundzie.

Przy każdym wyborze wskazujesz jedno pole (selectedTile). To pole nie może już należeć do partii pól wpadających do lawy we wcześniejszej rundzie na tym poziomie. Runda jest następnie rozstrzygana przez porównanie wybranego pola z całą partią pól wpadających do lawy w tej rundzie (selectedTile):

isLoss = droppedTiles.includes(selectedTile) 

Jeśli Twoje pole należy do pól, które spadają w tej rundzie, gra kończy się. W przeciwnym razie przetrwasz rundę — niezależnie od tego, ile innych pól spadło razem z nim.

Poziom trudności konfiguracja

Cztery poziomy trudności równoważą liczbę pól spadających w rundzie i liczbę rund. Każdy z nich nadal obejmuje pełną planszę z 49 polami (dropsPerRound × roundsPerLevel + survivingTiles = 49):

Poziom trudności

Spadki na rundę

Rundy na poziom

Pola, które przetrwają

Łatwy

7

6

7

Średni

11

4

5

Trudny

23

2

3

Toksyczny

48

1

1

Postęp poziomów i gry

Gra ma 3 poziomy, a każdy z nich ma własną, niezależnie shuffleną planszę z 49 polami. Przetrwanie wszystkich rund na poziomie powoduje jego ukończenie i przenosi Cię na planszę kolejnego poziomu, do pierwszej rundy; ukończenie trzeciego poziomu kończy grę.

Wypłata

Możesz dokonać wypłaty po przetrwaniu dowolnej rundy — możliwość ta jest dostępna już po pierwszym udanym wyborze.

Mnożnik

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

levelsCleared to liczba w pełni ukończonych dotychczas poziomów, tilesRemainingInLevel zmniejsza się o liczbę pól spadających w rundzie za każdym razem, gdy przetrwasz rundę na bieżącym poziomie, oraz survivingTiles to liczba pól, które przetrwają na poziomie dla danego poziomu trudności, zgodnie z tabelą powyżej.

Jest to stawka o uczciwych kursach: odwrotność skumulowanego prawdopodobieństwa dotarcia dokładnie do tego momentu (ukończone poziomy × przetrwane rundy) przy równomiernym losowym rozmieszczeniu pól, skorygowana o przewagę kasyna.

Czy to odpowiedziało na twoje pytanie?