Розробка гри Hilo на блокчейні

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка гри Hilo на блокчейні
Простий
від 1 дня до 3 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Розробка блокчейн-гри HiLo

HiLo — проста карткова гра: угадати, буде наступна карта вище або нижче за поточну. Правильне угадування збільшує множник, у будь-який момент можна «кешаутити» та забрати виграш. Проста механіка робить HiLo ідеальним прикладом для provably fair реалізації на блокчейні — вся логіка прозора та верифіковна.

Контракт: Commit-Reveal Схема

Chainlink VRF для HiLo занадто повільний — гра передбачає швидкі рішення. Використовуємо server seed + client seed commit-reveal: казино публікує хеш seed заздалегідь, гравець додає свій seed, результат детерміністичний з комбінації. Ні казино не знає seed гравця заздалегідь, ні гравець не знає server seed.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract HiLoGame {
    uint8 constant DECK_SIZE = 52;

    struct Game {
        address player;
        bytes32 serverSeedHash;  // хеш seed, опублікованого до гри
        bytes32 clientSeed;       // seed гравця, розкритий в кінці
        string serverSeed;        // розкритий після гри
        uint256 betAmount;
        uint8 currentCard;        // поточна карта (0-51)
        uint8 position;           // позиція в колоді
        uint256 multiplier;       // x100 для точності (200 = 2.0x)
        bool active;
        bool cashed;
    }

    mapping(bytes32 => Game) public games;  // gameId → Game
    uint256 public constant HOUSE_EDGE = 100; // 1% (100/10000)

    event GameStarted(bytes32 indexed gameId, address player, uint8 firstCard);
    event CardRevealed(bytes32 indexed gameId, uint8 card, uint256 multiplier);
    event GameCashed(bytes32 indexed gameId, uint256 payout);
    event GameLost(bytes32 indexed gameId, uint8 card);

    // Казино публікує хеш server seed до гри
    function startGame(
        bytes32 serverSeedHash,
        bytes32 clientSeed
    ) external payable returns (bytes32 gameId) {
        require(msg.value > 0, "Bet required");

        gameId = keccak256(abi.encodePacked(
            msg.sender, serverSeedHash, clientSeed, block.timestamp
        ));

        // Генеруємо першу карту з clientSeed + serverSeedHash
        // (server seed ще невідомий, але хеш зафіксований)
        uint8 firstCard = _deriveCard(serverSeedHash, clientSeed, 0);

        games[gameId] = Game({
            player: msg.sender,
            serverSeedHash: serverSeedHash,
            clientSeed: clientSeed,
            serverSeed: "",
            betAmount: msg.value,
            currentCard: firstCard,
            position: 0,
            multiplier: 100, // 1.0x
            active: true,
            cashed: false
        });

        emit GameStarted(gameId, msg.sender, firstCard);
    }

    // Гравець робить вибір: Higher (true) або Lower (false)
    // Казино викликає reveal наступної карти з частковим server seed
    function revealNextCard(
        bytes32 gameId,
        string calldata serverSeedPartial, // частинка для верифікації
        bool guessHigher
    ) external {
        Game storage game = games[gameId];
        require(game.active, "Game not active");
        require(msg.sender == owner() || msg.sender == gameServer, "Unauthorized");

        uint8 nextCard = _deriveCard(
            game.serverSeedHash,
            game.clientSeed,
            game.position + 1
        );

        bool correct;
        if (guessHigher) {
            correct = _cardValue(nextCard) > _cardValue(game.currentCard);
        } else {
            correct = _cardValue(nextCard) < _cardValue(game.currentCard);
        }

        // Нічия (рівні карти) — програш
        if (_cardValue(nextCard) == _cardValue(game.currentCard)) {
            correct = false;
        }

        game.position++;
        game.currentCard = nextCard;

        if (!correct) {
            game.active = false;
            emit GameLost(gameId, nextCard);
            return;
        }

        // Оновлюємо множник: вірогідність угадування * house edge
        uint256 probability = _calculateProbability(game.currentCard, guessHigher);
        game.multiplier = (game.multiplier * 9900) / probability; // 9900 = 99% (1% house edge)

        emit CardRevealed(gameId, nextCard, game.multiplier);
    }

    // Гравець забирає виграш
    function cashout(bytes32 gameId) external {
        Game storage game = games[gameId];
        require(game.active, "Game not active");
        require(msg.sender == game.player, "Not your game");

        game.active = false;
        game.cashed = true;

        uint256 payout = (game.betAmount * game.multiplier) / 100;
        payable(game.player).transfer(payout);

        emit GameCashed(gameId, payout);
    }

    // Після гри казино розкриває повний server seed
    // Гравець може верифікувати: hash(serverSeed) == serverSeedHash
    function revealServerSeed(bytes32 gameId, string calldata serverSeed) external {
        Game storage game = games[gameId];
        require(!game.active, "Game still active");
        require(
            keccak256(bytes(serverSeed)) == game.serverSeedHash,
            "Invalid server seed"
        );
        game.serverSeed = serverSeed;
    }

    function _deriveCard(
        bytes32 serverSeedHash,
        bytes32 clientSeed,
        uint8 position
    ) internal pure returns (uint8) {
        bytes32 combined = keccak256(abi.encodePacked(serverSeedHash, clientSeed, position));
        return uint8(uint256(combined) % DECK_SIZE);
    }

    function _cardValue(uint8 card) internal pure returns (uint8) {
        return (card % 13) + 1; // 1=Ace, 13=King
    }

    function _calculateProbability(uint8 currentCard, bool higher) internal pure returns (uint256) {
        uint8 value = _cardValue(currentCard);
        uint256 cardsHigher = 13 - value;
        uint256 cardsLower = value - 1;
        // Повертаємо вірогідність * 100 (для точності)
        if (higher) return (cardsHigher * 100 * 100) / 13; // *100 для масштабу множника
        return (cardsLower * 100 * 100) / 13;
    }

    receive() external payable {}
    address public gameServer;
    address public owner;
    constructor() { owner = msg.sender; gameServer = msg.sender; }
    modifier onlyOwner() { require(msg.sender == owner); _; }
}

Верифіковність для Гравця

Provably fair працює так: після гри користувач бере розкритий serverSeed та обчислює keccak256(serverSeed) — якщо відповідає serverSeedHash, опублікованому до гри, казино не підставило seed. Потім відтворює послідовність карт через _deriveCard — результати мають совпадать з грою.

Верифікація на стороні клієнта можлива через JavaScript.

Фронтенд

Анімації карт — CSS flip transitions або Pixi.js. Ключові елементи UI: поточна карта, історія останніх 5 карт, поточний множник, кнопки Higher/Lower, кнопка Cash Out. Множник повинен оновлюватися анімовано при кожному правильному угадуванні — core feedback loop гри.

Для швидкого зворотного зв'язку без очікування on-chain підтвердження — використовуємо optimistic UI: показуємо результат одразу на основі даних game server, підтверджуємо on-chain асинхронно. Якщо on-chain транзакція не вдасться — откатуємо стан.