Разработка игры Slots на блокчейне
Slots (игровые автоматы) — самая популярная категория казино-игр. На блокчейне ключевая задача: генерировать символы барабанов через verifiable randomness, обеспечивать заявленный RTP (Return to Player) и предотвращать манипуляции результатом ни со стороны казино, ни со стороны игрока.
Механика и математика Slots
Классический slot: 5 барабанов × 3 строки = 15 позиций. Каждый барабан имеет виртуальный стрип (например, 64 позиции с разными символами). Random number → позиция на стрипе → видимые символы. Платёж определяется комбинацией символов на paylines.
RTP 96% означает: из каждых $100 ставок игрокам возвращается $96 в долгосрочной перспективе. Это достигается через математически выверенные паблтейблы (pay tables).
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: symbol combination -> multiplier (в 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 random words
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];
// Определяем позиции барабанов из random числа
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;
}
}
}
}
Bonus features
Slots без бонусных механик не конкурентоспособны. Обязательные фичи:
Free Spins: scatter символы (обычно 3+) запускают серию бесплатных спинов с повышенным multiplier.
Wild символы: заменяют любой другой символ для формирования winning combination.
Multiplier Wilds: wild с ×2, ×3 множителем.
Expanding Wilds: при выпадении расширяются на весь барабан.
Bonus game: специальная mini-game с pick-me механикой (выбери из 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+ bonus символах на payline 1
hasBonusGame = _checkBonusLine(grid);
}
Off-chain анимации, on-chain результат
Блокчейн-Slots обычно работают так: результат (позиции барабанов) приходит из VRF, анимация прокрутки барабанов — off-chain в браузере/приложении. Пользователь видит spinning, затем итоговые символы соответствуют on-chain результату. Интеграция через event от смарт-контракта.
Задержка VRF (3-15 секунд) — проблема для UX. Решения: оптимистичная анимация (показываем "вращение" пока ждём VRF), L2 деплой (Chainlink VRF дешевле и быстрее на Arbitrum/Polygon), commit-reveal (быстрее, но менее верифицируемо).
Разработка полноценного Slots (5 барабанов, 20 линий, Free Spins, Wild) — 4-6 недель смарт-контракт + 4-8 недель frontend с анимациями (Pixi.js/Three.js). Chainlink VRF интеграция включена в смарт-контракт часть.







