Разработка игры Dice на блокчейне
Dice — базовая игра крипто-казино: игрок выбирает число (порог) и направление (roll over / roll under), делает ставку. Если результат броска соответствует условию — выигрыш. Простейшая механика, но реализация требует внимания к математике house edge и обязательного verifiable randomness.
Математика Dice
Стандартный диапазон: 1-100 (или 0.00-99.99 в дробном варианте).
При ставке «roll over 50»: шанс выиграть = 50%, справедливый множитель = 2x. Реальный множитель с house edge 1% = 1.98x.
Формула: multiplier = (100 - houseEdge) / winProbability
При roll over 75: winProbability = 25%, multiplier = 99/25 = 3.96x. При roll under 10: winProbability = 9% (числа 1-9), multiplier = 99/9 = 11x.
Диапазон допустимых ставок: обычно roll over 2-97 и roll under 3-98 (чтобы house edge оставался разумным).
Smart contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract BlockchainDice is VRFConsumerBaseV2Plus {
uint256 public houseEdge = 100; // 1% в basis points (10000 = 100%)
struct DiceBet {
address player;
uint256 amount;
uint8 target; // 1-99
bool isOver; // roll over или roll under
uint256 potentialPayout;
bool settled;
}
mapping(uint256 => DiceBet) public bets;
event BetPlaced(uint256 indexed requestId, address player, uint256 amount, uint8 target, bool isOver, uint256 payout);
event BetResult(uint256 indexed requestId, uint8 roll, bool win, uint256 payout);
function roll(uint8 target, bool isOver) external payable returns (uint256 requestId) {
require(msg.value >= MIN_BET && msg.value <= getMaxBet(), "Invalid bet");
require(target >= 2 && target <= 98, "Invalid target");
uint256 payout = calculatePayout(msg.value, target, isOver);
require(address(this).balance >= payout, "Insufficient bankroll");
requestId = _requestVRF();
bets[requestId] = DiceBet({
player: msg.sender,
amount: msg.value,
target: target,
isOver: isOver,
potentialPayout: payout,
settled: false,
});
emit BetPlaced(requestId, msg.sender, msg.value, target, isOver, payout);
}
function calculatePayout(
uint256 betAmount,
uint8 target,
bool isOver
) public view returns (uint256) {
uint256 winProbability;
if (isOver) {
winProbability = 100 - uint256(target); // числа от target+1 до 100
} else {
winProbability = uint256(target) - 1; // числа от 1 до target-1
}
require(winProbability > 0 && winProbability < 100, "Invalid probability");
// multiplier = (10000 - houseEdge) / winProbability / 100
uint256 multiplier = ((10000 - houseEdge) * 100) / winProbability;
return (betAmount * multiplier) / 10000;
}
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords)
internal override
{
DiceBet storage bet = bets[requestId];
require(!bet.settled, "Already settled");
bet.settled = true;
// Генерируем число 1-100
uint8 roll = uint8((randomWords[0] % 100) + 1);
bool win = bet.isOver ? roll > bet.target : roll < bet.target;
if (win) {
payable(bet.player).transfer(bet.potentialPayout);
}
emit BetResult(requestId, roll, win, win ? bet.potentialPayout : 0);
}
// Функция верификации: воспроизвести результат из requestId
function verifyResult(uint256 requestId, uint256 vrfOutput) external view returns (uint8 roll, bool win) {
DiceBet storage bet = bets[requestId];
roll = uint8((vrfOutput % 100) + 1);
win = bet.isOver ? roll > bet.target : roll < bet.target;
}
function getMaxBet() public view returns (uint256) {
// Максимальная ставка = bankroll / 100 (не рискуем более 1% банкролла)
return address(this).balance / 100;
}
}
High-Low вариант (расширенная механика)
Популярный вариант: игрок выбирает диапазон (например, от 25 до 75), и выигрывает если roll попадает в диапазон. Более интуитивный UI.
function rollRange(uint8 lowerBound, uint8 upperBound) external payable {
require(upperBound > lowerBound, "Invalid range");
require(lowerBound >= 1 && upperBound <= 100);
uint8 winRange = upperBound - lowerBound + 1; // включительно
// Минимальный выигрышный диапазон = 3 (иначе house edge > 30%)
require(winRange >= 3 && winRange <= 97);
uint256 payout = ((10000 - houseEdge) * msg.value * 100) / (uint256(winRange) * 10000);
// ... запрос VRF
}
UX для быстрого gameplay
Dice — это быстрая игра. Игроки делают десятки ставок в минуту. UX должен это поддерживать:
- Быстрые пресеты (½x, 2x ставка, max bet)
- Автоматический режим с настраиваемыми стратегиями
- Мгновенная обратная связь (анимация кубиков, звуковые эффекты)
- История ставок с результатами
Для массовой игры на L2 (Arbitrum, Polygon) VRF delay 3-15 секунд — приемлемо. На Ethereum mainnet может быть некомфортно.
Разработка Dice смарт-контракта с VRF — 2-3 недели. С frontend (React + анимации) и auto-режимом — 4-5 недель.







