К основному содержимому

Provably Fair — игровые события

Что такое игровые события

Ранее мы генерировали случайные числа с плавающей точкой от 0 до 1 с помощью функций generateFloats и byteGenerator. Игровые события преобразуют эти сгенерированные числа в фактические результаты игры. В разных играх это происходит по-разному. В Plinko числа с плавающей точкой помогают определить направление падения шара. В Dice они помогают определить результат броска кубика.

Ниже приведено подробное объяснение того, как мы преобразуем числа с плавающей точкой в честные игровые события или результаты для каждой оригинальной игры на нашей платформе.


Tower

В Tower цель игры — добраться до вершины башни и спасти принцессу, находя ключи и избегая отравленных яблок. В каждом ряду башни содержится заранее определённое количество безопасных плиток (ключей) и отравленных яблок в зависимости от выбранного уровня сложности.

Игровое событие в Tower определяет расположение безопасных плиток (или ключей) в каждом ряду.

Всего в башне 9 рядов. Количество столбцов и распределение ключей и отравленных яблок зависят от выбранного уровня сложности:

  • Лёгкий: 4 столбца — 3 ключа и 1 отравленное яблоко в каждом ряду

  • Средний: 3 столбца — 2 ключа и 1 отравленное яблоко в каждом ряду

  • Сложный: 2 столбца — 1 ключ и 1 отравленное яблоко в каждом ряду

  • Эксперт: 3 столбца — 1 ключ и 2 отравленных яблока в каждом ряду

  • Мастер: 4 столбца — 1 ключ и 3 отравленных яблока в каждом ряду

Результат для каждого ряда определяется случайной генерацией позиций ключей (безопасных плиток) среди доступных столбцов в соответствии с настройками сложности.

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

В Chicken цель игры — провести курицу через как можно больше полос, не приземлившись на опасную полосу.

Каждый успешный шаг вперёд увеличивает множитель выплаты. Игрок может забрать выигрыш в любой момент до того, как курица прыгнет на опасную полосу. Если курица достигнет опасной зоны до того, как игрок заберёт выигрыш, ставка проигрывается.

Основное игровое событие в Chicken — определение опасной полосы, то есть конкретной полосы, на которой курица проиграет.

В Chicken доступно 4 уровня сложности. Количество полос, которые может пересечь курица, зависит от выбранного уровня сложности:

  • Лёгкий: 19 полос

  • Средний: 17 полос

  • Сложный: 15 полос

  • Эксперт: 10 полос

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

В Plinko игроки могут выбрать пирамиду высотой от 8 до 16 уровней. При падении сверху шар после каждого столкновения со штифтом на доске Plinko отклоняется влево или вправо — от 8 до 16 раз. Игровое событие в Plinko определяет, отклонится ли шар влево или вправо на каждом уровне; это обозначается значениями 0 и 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

В Dice результат броска может находиться в диапазоне от 0,00 до 100,00. Игровое событие в Dice определяет это значение. Для этого мы используем формулу (floats * 10,001) / 100). Поскольку учитываются значения с точностью до 2 знаков после запятой, а также число 0, всего существует 10 000 + 1 = 10 001 возможный результат.

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

В Limbo цель игры — угадать множитель >1. Если результат игры больше множителя, пользователь выигрывает. Игровое событие заключается в генерации значения с плавающей точкой больше 1, которое следует гиперболическому распределению.

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

В Keno игровое событие требует определения расположения 10 уникальных драгоценных камней на игровом поле. Для этого мы генерируем 10 разных чисел с плавающей точкой от 1 до 40, каждое из которых представляет позицию на поле Keno. Затем для обработки повторяющихся чисел с плавающей точкой и определения окончательных 10 позиций камней используется a.

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

В Mines игровое событие требует определения расположения мин на игровом поле из 25 плиток. На основе выбранного пользователем количества мин мы генерируем список целых чисел от 0 до 24, представляющих 25 ячеек игрового поля.

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

Наша Roulette основана на европейской рулетке с одним сектором «0», в которой 37 различных ячеек — от 0 до 36. Игровое событие в Roulette определяет, в какую из 37 ячеек попадёт шарик.

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

В Wheel пользователь выбирает количество секторов колеса. В зависимости от сектора, на котором остановится указатель, пользователь выиграет множитель этого сектора. Игровое событие определяет сектор.

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

В Blackjack используется неограниченная колода карт. Поэтому вероятность каждого хода всегда одинакова. Игровое событие в Blackjack определяет карту, которую получает игрок или банкир при взятии карты из колоды.

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

В Hilo используется неограниченная колода карт. Поэтому вероятность каждого хода всегда одинакова. Цель игры — угадать, будет ли следующая карта старше или младше текущей. Игровое событие в Hilo определяет, какую карту пользователь получит следующей, если будет брать карту.

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

Расчёт результата игры Crash состоит из 2 частей

  • Хеш блока Bitcoin

  • Сид

Хеш Bitcoin

Подробности приведены в этом твите:

Хеш блока 779588 можно найти в обозревателе блоков BTC (https://btcscan.org/block/000000000000000000033ab71b5fa7edcafe388e5f49719f1de8e1f8d0670460) и он совпадает с хешем, указанным в твите.

Сид

Процесс генерации сидов для 10 миллионов игр Crash разработан для обеспечения честности и прозрачности, исключая любую возможность манипуляций со стороны Shuffle. Вот как генерируются и проверяются сиды:

  1. Генерация начального сида: Сид для десятимиллионной игры случайным образом выбирается Shuffle.

  2. Последовательное хеширование для генерации сидов: Для генерации сидов для всех остальных игр мы используем метод обратного хеширования:

    • Сид каждой игры является результатом хеширования сида следующей игры. В частности, хеш для игры 9 999 999 является SHA256-хешем хеша десятимиллионной игры.

    • Эта цепочка продолжается таким образом, что сид каждой игры формируется путём хеширования хеша следующей игры, а сид первой игры формируется путём хеширования хеша второй игры.

  3. Проверка целостности сидов:

    • Публикуя хешированное значение хеша, использованного в первой игре, игроки могут самостоятельно проверить целостность хеша для всех игр вплоть до десятимиллионной.

    • С помощью этого метода, зная хеш любой конкретной игры, вы можете проверить сиды всех предыдущих игр. Например, если вы знаете сид игры 1323185, вы можете хешировать его и проверить, совпадает ли он с сидом игры 1323184, и так далее до первой игры.

  4. Прозрачность и безопасность:

    • Используя будущий хеш блока BTC, который не контролируется Shuffle, и публикуя хешированную версию сида первой игры, пользователи могут доказать, что результаты игр Crash честны и не были изменены.

Slide

В Slide используется тот же provably fair и процесс генерации сидов, что и в Crash. Генерация сидов была публично проведена на x.com, а публикацию можно найти здесь.

Хеш блока 952294 можно найти в обозревателе блоков BTC (https://btcscan.org/block/00000000000000000000a382760ff19cdb7f9f22147b9123f3e471fb3c6d129a) и он совпадает с хешем, указанным в твите.

Baccarat

В Baccarat используется неограниченная колода карт. Поэтому вероятность каждого хода всегда одинакова. Игровое событие в Baccarat определяет карты, раздаваемые игроку и банкиру при начальной раздаче, а также при любых дополнительных доборах согласно стандартным правилам добора в Baccarat.

Карты раздаются в следующем порядке из сгенерированной последовательности чисел с плавающей точкой:

  1. Первая карта игрока — индекс числа с плавающей точкой 0

  2. Первая карта банкира — индекс числа с плавающей точкой 1

  3. Вторая карта игрока — индекс числа с плавающей точкой 2

  4. Вторая карта банкира — индекс числа с плавающей точкой 3

  5. Третья карта игрока (если берётся) — индекс числа с плавающей точкой 4

  6. Третья карта банкира (если берётся)

    1. индекс числа с плавающей точкой 4 — если игрок не берёт третью карту

    2. индекс числа с плавающей точкой 5 — если игрок берёт третью карту

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

В Blitz используется неограниченная колода карт. Поэтому вероятность каждого хода всегда одинакова. Игровое событие в Blitz определяет карты, раздаваемые игроку в соответствии с выбранным количеством доборов, а также проверяет, уникальны ли все полученные карты.

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

В Coinflip цель игры — правильно угадать сторону, на которую выпадет подброшенная монета. Игровое событие в Coinflip определяет результат (ОРЁЛ или РЕШКА) каждого подбрасывания монеты. В Coinflip доступно три режима: Classic (одно подбрасывание), Classic Progressive (последовательные подбрасывания с возможностью забрать выигрыш в любой момент) и Target (выбор количества подбрасываний и минимального числа правильных угадываний для победы). Во всех трёх режимах результаты подбрасываний определяются одинаково.

Результат каждого подбрасывания определяется генерацией числа с плавающей точкой и его сопоставлением с 0 или 1 — так же, как в Plinko числа с плавающей точкой сопоставляются с падением влево или вправо:

// 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 — генерируется одно подбрасывание (count = 1). Игрок выигрывает, если выбранная им сторона совпадает с результатом подбрасывания.

Classic Progressive — подбрасывания генерируются по одному по мере продолжения игры, однако результат каждого из них фиксируется в начале раунда: результат N-го подбрасывания всегда определяется генерацией результатов подбрасываний с 1-го по N-е и выбором последнего. Поэтому последовательное раскрытие результатов по мере продвижения игрока не меняет результат ни одного из подбрасываний. Игрок может забрать выигрыш после любого успешного подбрасывания; раунд заканчивается при первой ошибке.

Target — игрок выбирает общее количество подбрасываний (до 20) и минимальное количество правильных угадываний, необходимое для победы. Все подбрасывания генерируются одним пакетом (count = totalFlips), и раунд считается выигранным, если количество результатов, совпавших с выбором игрока, не меньше выбранного минимума.

Floor is Lava

Цель: Пройти сетку 7×7 (49 плиток) на протяжении трёх последовательных уровней, не наступив на плитку, которая превращается в лаву.

Каждый уровень состоит из раундов фиксированного размера: в каждом раунде группа плиток одновременно превращается в лаву. Вы можете забрать выигрыш после успешного прохождения любого раунда.

Генерация поля

До начала раунда расположение плиток для всех трёх уровней определяется в рамках одной проверяемой shuffle: случайная перестановка целых чисел от 0 до 146 (49 плиток × 3 уровня) выбирается без повторений с помощью генератора на основе сида, а затем разделяется по уровням:

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

Использование одной перестановки из 147 значений вместо трёх отдельных shuffle по 49 значений — это лишь способ повысить эффективность. Поскольку ровно 49 из 147 значений попадают в диапазон каждого уровня, на каждом уровне по-прежнему формируется независимый равномерно случайный порядок из 49 плиток.

Раунды: как плитки превращаются в лаву

Порядок 49 плиток каждого уровня делится на последовательные непересекающиеся группы: фиксированное количество плиток в раунде (dropsPerRound) при фиксированном количестве раундов (roundsPerLevel), а оставшиеся в конце плитки переживают весь уровень:

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

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

Поскольку shuffle уже заранее определил порядок плиток, набор плиток, превращающихся в лаву в текущем раунде, не выбирается в ответ на ваш ход — это просто соответствующая часть заранее shuffleного списка.

При каждом выборе вы указываете одну плитку (selectedTile). Эта плитка не должна уже входить в группу лавы из предыдущего раунда на этом уровне. Затем раунд завершается сравнением выбранной вами плитки со всей группой плиток, превращающихся в лаву (selectedTile):

isLoss = droppedTiles.includes(selectedTile) 

Если ваша плитка входит в число плиток, превратившихся в лаву в этом раунде, игра заканчивается. В противном случае вы проходите раунд — независимо от того, сколько других плиток превратилось в лаву вместе с ней.

Сложность настройка

На четырёх уровнях сложности размер раунда соотносится с количеством раундов по-разному. В каждом случае используется всё поле из 49 плиток (dropsPerRound × roundsPerLevel + survivingTiles = 49):

Сложность

Плиток, превращающихся в лаву за раунд

Раундов на уровень

Выживших плиток

Лёгкий

7

6

7

Средний

11

4

5

Сложный

23

2

3

Токсичный

48

1

1

Прохождение уровней и игры

Игра состоит из 3 уровней, каждый из которых имеет собственное независимо shuffleное поле из 49 плиток. Пройдя все раунды уровня, вы переходите к первому раунду поля следующего уровня; прохождение третьего уровня завершает игру.

Забрать выигрыш

Вы можете забрать выигрыш после успешного прохождения любого раунда — эта возможность становится доступна уже после первого успешного выбора.

Множитель

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

levelsCleared — количество полностью пройденных уровней на данный момент, tilesRemainingInLevel уменьшается на количество плиток, превращающихся в лаву в раунде, после каждого успешно пройденного раунда на текущем уровне, и survivingTiles — количество выживших плиток на уровне для выбранной сложности из таблицы выше.

Это цена с честными коэффициентами: обратная величина совокупной вероятности достижения этой конкретной точки (пройденные уровни × пройденные раунды) при равномерно случайном размещении плиток, скорректированная с учётом преимущества казино.

Нашли ответ на свой вопрос?