Blockchain tower game development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Blockchain tower game development
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Blockchain Tower Game Development

Tower is a high-risk climbing game: a player ascends through levels, selecting one cell per level while avoiding the "mine". Higher levels yield larger multipliers. At any moment, players can cashout and collect their winnings. Structurally similar to Mines, but with progressive betting growth.

Blockchain Tower is interesting because it requires fair randomness at each level independently, while preventing players from knowing the mine location on the next level in advance.

Fair Randomness Architecture

The key challenge: where is the mine on each level? On-chain data is public — if mine placement is stored in contract state, players can read it before acting.

Approach 1: Commit-Reveal per level

The operator/oracle generates a seed hash for each level before game start, publishes it on-chain, and reveals the seed only after the player makes their choice:

struct TowerGame {
    address player;
    uint256 bet;
    uint8 currentLevel;    // current level (0 = start)
    uint8 maxLevels;       // tower height
    uint256 currentMultiplier; // x1000 for precision
    bytes32 serverSeedHash;    // server seed hash
    bool active;
}

The challenge: requires a backend that plays honestly (cannot place mine retroactively). Solution — publish hash before game start. If the server reveals a seed mismatching the hash — the violation is verifiable.

Approach 2: Chainlink VRF per game

Request one large random number at game start, deterministically derive mine position for each level:

mapping(uint256 => TowerGame) public games; // requestId → game

function startTower(uint8 levels, uint8 cellsPerLevel) external payable {
    require(msg.value >= MIN_BET);
    require(levels >= 3 && levels <= 10);
    require(cellsPerLevel >= 2 && cellsPerLevel <= 5);
    
    uint256 requestId = s_vrfCoordinator.requestRandomWords(
        VRFV2PlusClient.RandomWordsRequest({
            keyHash: KEY_HASH,
            subId: subscriptionId,
            requestConfirmations: 3,
            callbackGasLimit: 200000,
            numWords: 1,
            extraArgs: VRFV2PlusClient._argsToBytes(
                VRFV2PlusClient.ExtraArgsV1({nativePayment: false})
            )
        })
    );
    
    games[requestId] = TowerGame({
        player: msg.sender,
        bet: msg.value,
        currentLevel: 0,
        maxLevels: levels,
        currentMultiplier: 1000, // x1.0
        gameSeed: 0, // filled during fulfillment
        active: false
    });
}

function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
    TowerGame storage game = games[requestId];
    game.gameSeed = randomWords[0];
    game.active = true;
    emit TowerReady(requestId, game.player);
}

// Get mine position for level (only when move is made!)
function _getMinePosition(uint256 requestId, uint8 level, uint8 cellsPerLevel) private view returns (uint8) {
    return uint8(uint256(keccak256(abi.encodePacked(
        games[requestId].gameSeed,
        level
    ))) % cellsPerLevel);
}

_getMinePosition is a private view. Technically readable if you know gameSeed. But gameSeed is stored in state... and public again.

Solution: seed concealment via hashing

Store only keccak256(gameSeed) in events, seed itself only as transaction parameter, not in storage. Imperfect, but raises the bar for cheating: requires monitoring pending transactions.

Practical production solution: hybrid — Chainlink VRF for unreadable seed + storing only seed hash publicly. Mine position revealed via event only after player move, not stored until action.

Multipliers and Mathematics

Each tower level with n cells and one mine: probability of safe selection = (n-1)/n. Mathematically fair multiplier after k levels:

multiplier(k) = product_{i=1}^{k} (n_i / (n_i - 1))

For 5-level tower, 3 cells: each level ×(3/2) = ×1.5. After 5 levels: 1.5^5 ≈ 7.59x. House edge added via coefficient:

// Multiplier table (x1000, 3 cells, 2% house edge)
uint256[10] public multipliers3Cells = [
    0,      // level 0
    1470,   // x1.47 (fair 1.5 * 0.98)
    2161,   // x2.16
    3177,   // x3.18
    4670,   // x4.67
    6865,   // x6.87
    10092,  // x10.09
    14835,  // x14.84
    21807,  // x21.81
    32056   // x32.06
];
function selectCell(uint256 gameId, uint8 cellIndex) external {
    TowerGame storage game = games[gameId];
    require(game.player == msg.sender && game.active);
    require(cellIndex < cellsPerLevel);
    
    uint8 minePosition = _getMinePosition(gameId, game.currentLevel, cellsPerLevel);
    
    if (cellIndex == minePosition) {
        // Hit mine — lose stake
        game.active = false;
        emit GameLost(gameId, msg.sender, game.currentLevel, minePosition);
        // ETH stays in contract (bankroll)
    } else {
        // Passed level — update multiplier
        game.currentMultiplier = multipliers[game.currentLevel + 1];
        game.currentLevel++;
        
        if (game.currentLevel == game.maxLevels) {
            // Beat tower — automatic cashout
            _payout(game);
        } else {
            emit LevelCleared(gameId, game.currentLevel, game.currentMultiplier);
        }
    }
}

function cashout(uint256 gameId) external {
    TowerGame storage game = games[gameId];
    require(game.player == msg.sender && game.active && game.currentLevel > 0);
    _payout(game);
}

function _payout(TowerGame storage game) private {
    uint256 payout = game.bet * game.currentMultiplier / 1000;
    game.active = false;
    (bool success, ) = game.player.call{value: payout}("");
    require(success, "Transfer failed");
    emit GameWon(msg.sender, payout, game.currentLevel);
}

Frontend: Animations and UX

Tower game is visually simple, but UX is critical: climbing animation, multiplier pulsing, cashout button always available.

Async flow: VRF fulfillment awaited via TowerReady event polling. After — each move is instant on-chain transaction (no additional VRF).

// wagmi hook for awaiting game start
const { data: gameReadyEvent } = useWatchContractEvent({
  address: TOWER_ADDRESS,
  abi: TOWER_ABI,
  eventName: 'TowerReady',
  args: { player: address },
  onLogs: (logs) => {
    const gameId = logs[0].args.requestId
    setActiveGameId(gameId)
    setGameState('playing')
  }
})

Progressive multiplier disclosure: animate multiplier growth on each successful level — key retention moment. Current potential payout should be prominently visible, in real-time.

Bankroll and Limits

Maximum payout capped by bankroll. Check before accepting bet:

function maxWinForBet(uint256 bet) public view returns (uint256) {
    return bet * multipliers[maxLevels] / 1000;
}

modifier bankrollSufficient(uint256 bet) {
    require(address(this).balance >= maxWinForBet(bet) + bet, "Insufficient bankroll");
    _;
}

Stack and Timeline

Component Technology
Contract Solidity + Chainlink VRF
Tests Foundry + VRF mock
Frontend React + wagmi
Network Arbitrum / Polygon

Basic Tower game (smart contract, tests, UI): 3-4 weeks. With advanced visuals, stats, leaderboard: 6-8 weeks. Smart contract audit mandatory — contract manages game bankroll.