Blockchain Dice Game Development
Dice—basic casino game: player chooses number (threshold) and direction (roll over/roll under), places bet. If result matches condition—win. Simplest mechanic but requires attention to math of house edge and mandatory verifiable randomness.
Dice Mathematics
Standard range: 1-100 (or 0.00-99.99 fractional).
Roll over 50: win chance = 50%, fair multiplier = 2x. Actual with 1% house edge = 1.98x.
Formula: multiplier = (100 - houseEdge) / winProbability
Roll over 75: winProbability = 25%, multiplier = 99/25 = 3.96x. Roll under 10: winProbability = 9%, multiplier = 99/9 = 11x.
Valid range: roll over 2-97 and roll under 3-98 (to keep house edge reasonable).
Smart Contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract BlockchainDice is VRFConsumerBaseV2Plus {
uint256 public houseEdge = 100; // 1% in basis points
struct DiceBet {
address player;
uint256 amount;
uint8 target; // 1-99
bool isOver; // roll over or 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);
} else {
winProbability = uint256(target) - 1;
}
require(winProbability > 0 && winProbability < 100, "Invalid probability");
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;
// Generate number 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);
}
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) {
return address(this).balance / 100;
}
}
High-Low Variant
Player chooses range (e.g., 25 to 75), wins if roll in range. More intuitive UI.
function rollRange(uint8 lowerBound, uint8 upperBound) external payable {
require(upperBound > lowerBound, "Invalid range");
require(lowerBound >= 1 && upperBound <= 100);
uint8 winRange = upperBound - lowerBound + 1;
require(winRange >= 3 && winRange <= 97);
uint256 payout = ((10000 - houseEdge) * msg.value * 100) / (uint256(winRange) * 10000);
}
UX for Fast Gameplay
Dice is fast game. Players place dozens bets per minute. UX must support:
- Quick presets (½x, 2x bet, max bet)
- Auto mode with custom strategies
- Instant feedback (dice animation, sounds)
- Bet history with results
On L2 (Arbitrum, Polygon): VRF delay 3-15 sec—acceptable. Mainnet may be uncomfortable.
Developing Dice contract with VRF—2-3 weeks. With frontend (React + animations) and auto mode—4-5 weeks.







