Розробка блокчейн-гри Slots
Слоти (ігрові автомати) — найпопулярніша категорія казино-ігор. На блокчейні ключева задача: генерувати символи барабанів через верифіковану випадковість, забезпечити заявлений RTP (Return to Player) та запобігти маніпуляціям результатом ні зі сторони казино, ні зі сторони гравця.
Механіка та математика Слотів
Класичний слот: 5 барабанів × 3 рядки = 15 позицій. Кожен барабан має віртуальний стрип (наприклад, 64 позиції з різними символами). Випадкове число → позиція на стрипі → видимі символи. Виплата визначається комбінацією символів на paylines.
RTP 96% означає: з кожних $100 ставок гравці отримують $96 у довгостроковій перспективі. Це досягається через математично верифіковані таблиці виплат.
Реалізація Smart Contract
contract BlockchainSlots is VRFConsumerBaseV2Plus {
// Віртуальні стрипи барабанів
// Індекс = позиція на стрипі, значення = символ (0-8)
uint8[64] public reel1Strip;
uint8[64] public reel2Strip;
uint8[64] public reel3Strip;
uint8[64] public reel4Strip;
uint8[64] public reel5Strip;
// Pay table: комбінація символів -> множник (в basis points)
mapping(bytes32 => uint256) public payTable;
struct SpinRequest {
address player;
uint256 betAmount;
uint256 lines; // кількість активних paylines
}
mapping(uint256 => SpinRequest) public pendingSpins;
function spin(uint256 lines) external payable returns (uint256 requestId) {
require(lines >= 1 && lines <= 20, "Invalid lines");
require(msg.value >= MIN_BET * lines, "Insufficient bet");
requestId = _requestRandomWords(3); // 3 випадкові числа
pendingSpins[requestId] = SpinRequest({
player: msg.sender,
betAmount: msg.value,
lines: lines,
});
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
SpinRequest memory spinReq = pendingSpins[requestId];
delete pendingSpins[requestId];
// Визначаємо позиції барабанів з випадкового числа
uint8[5] memory reelPositions;
reelPositions[0] = uint8(randomWords[0] % 64);
reelPositions[1] = uint8((randomWords[0] >> 8) % 64);
reelPositions[2] = uint8((randomWords[0] >> 16) % 64);
reelPositions[3] = uint8(randomWords[1] % 64);
reelPositions[4] = uint8((randomWords[1] >> 8) % 64);
// Отримуємо символи для 3 рядків кожного барабана
uint8[5][3] memory grid = _buildGrid(reelPositions);
// Обчислюємо виплату за всіма активними paylines
uint256 totalPayout = _calculatePayout(grid, spinReq.betAmount, spinReq.lines);
if (totalPayout > 0) {
payable(spinReq.player).transfer(totalPayout);
}
emit SpinResult(spinReq.player, reelPositions, grid, totalPayout, requestId);
}
function _buildGrid(uint8[5] memory positions) internal view returns (uint8[5][3] memory grid) {
// Для кожного барабана беремо 3 послідовних символи (wrap-around)
for (uint i = 0; i < 5; i++) {
uint8 pos = positions[i];
grid[i][0] = _getSymbol(i, (pos + 63) % 64); // рядок вище
grid[i][1] = _getSymbol(i, pos); // середній рядок
grid[i][2] = _getSymbol(i, (pos + 1) % 64); // рядок нижче
}
}
function _calculatePayout(
uint8[5][3] memory grid,
uint256 betAmount,
uint256 activeLines
) internal view returns (uint256 payout) {
uint256 betPerLine = betAmount / activeLines;
// Перевіряємо кожну payline
for (uint l = 0; l < activeLines; l++) {
uint8[5] memory line = _getPayline(l, grid);
uint256 lineMultiplier = _getLineMultiplier(line);
if (lineMultiplier > 0) {
payout += (betPerLine * lineMultiplier) / 100;
}
}
}
}
Бонусні функції
Слоти без бонусних механік неконкурентоспроможні. Обов'язкові функції:
Free Spins: scatter символи (зазвичай 3+) запускають серію безкоштовних спінів з підвищеним множником.
Wild символи: замінюють будь-який інший символ для формування виграшної комбінації.
Multiplier Wilds: wild з ×2, ×3 множником.
Expanding Wilds: розширюються на весь барабан при приземленні.
Bonus game: спеціальна мініігра з механікою вибору (виберіть з N скриньок).
function _checkBonusFeatures(uint8[5][3] memory grid) internal pure
returns (bool hasFreeSpin, uint256 freeSpinCount, bool hasBonusGame)
{
uint256 scatterCount = 0;
for (uint col = 0; col < 5; col++) {
for (uint row = 0; row < 3; row++) {
if (grid[col][row] == SCATTER_SYMBOL) scatterCount++;
}
}
if (scatterCount >= 3) {
hasFreeSpin = true;
freeSpinCount = scatterCount == 3 ? 10 : scatterCount == 4 ? 15 : 20;
}
// Bonus game на 3+ бонусних символах на payline 1
hasBonusGame = _checkBonusLine(grid);
}
Офлайн-анімації, On-chain результат
Блокчейн-слоти звичайно працюють так: результат (позиції барабанів) надходить від VRF, анімація обертання барабанів — офлайн у браузері/додатку. Користувач бачить обертання, потім остаточні символи відповідають on-chain результату. Інтеграція через подію смарт-контракту.
Затримка VRF (3-15 секунд) — проблема для UX. Розв'язання: оптимістична анімація (показуємо «обертання» поки чекаємо VRF), L2 деплой (Chainlink VRF дешевше та швидше на Arbitrum/Polygon), commit-reveal (швидше, але менш верифіковано).
Розробка повних Слотів (5 барабанів, 20 ліній, Free Spins, Wild) — 4-6 тижнів смарт-контракт + 4-8 тижнів фронтенд з анімаціями (Pixi.js/Three.js). Інтеграція Chainlink VRF включена в частину смарт-контракту.







