Розробка блокчейн-гри Keno
Keno — лотерейна гра: гравець вибирає числа з діапазону (зазвичай 1–80), випадково витягуються 20 чисел, виграш залежить від збігів. Проста механіка, але реалізація на блокчейні вимагає вирішення кількох нетривіальних задач: верифіковану випадковість для витягування 20 чисел, gas-ефективну перевірку збігів, правильну таблицю виплат.
На відміну від Crash, Keno — гра з фіксованим результатом до кешау: числа витягнуті, результат одразу відомий. Це спрощує деякі аспекти, але вимагає особливої уваги до якості RNG.
RNG: вибір 20 унікальних чисел з 80
Основна технічна задача: з одного VRF random seed отримати 20 унікальних чисел у діапазоні 1–80. Наївний підхід (rand % 80 повторити 20 разів) створює колізії — одне число може випасти двічі.
Fisher-Yates Перетасування для On-Chain Keno
function drawNumbers(uint256 seed) public pure returns (uint8[20] memory drawn) {
// Ініціалізуємо масив 1..80
uint8[80] memory pool;
for (uint8 i = 0; i < 80; i++) {
pool[i] = i + 1;
}
// Fisher-Yates: тасуємо перші 20 позицій
for (uint8 i = 0; i < 20; i++) {
// Отримуємо pseudo-random індекс з seed
uint256 j = uint256(keccak256(abi.encodePacked(seed, i))) % (80 - i);
// Swap pool[i] та pool[i + j]
uint8 temp = pool[i];
pool[i] = pool[i + j];
pool[i + j] = temp;
drawn[i] = pool[i];
}
}
Fisher-Yates гарантує унікальні числа без reject-sampling. Для on-chain виконання: 20 ітерацій × keccak256 ≈ 80,000–100,000 газу. На Arbitrum ~$0.01. Прийнятно.
Альтернатива — bitmap підхід:
function drawNumbersBitmap(uint256 seed) public pure returns (uint8[20] memory drawn) {
uint256 bitmap = 0; // кожен біт = число 1-80
uint8 count = 0;
uint256 nonce = 0;
while (count < 20) {
uint8 num = uint8(uint256(keccak256(abi.encodePacked(seed, nonce))) % 80) + 1;
nonce++;
if (bitmap & (1 << (num - 1)) == 0) {
bitmap |= (1 << (num - 1));
drawn[count] = num;
count++;
}
}
}
Bitmap підхід простіший, але reject-sampling може потребувати більше keccak256 викликів у гіршому випадку (колізії частіші при виборі останніх чисел). Fisher-Yates preferable для передбачуваного газу.
Перевірка совпадений: Gas-Ефективна реалізація
Гравець вибрав M чисел (1–10), потрібно підрахувати збіги з 20 витягнутих чисел.
Наївний підхід: вкладені цикли O(M×20) — прийнятно для малого M.
Bitmap підхід для ефективності:
function countMatches(
uint8[] memory playerPicks, // числа гравця
uint8[20] memory drawnNumbers
) public pure returns (uint8 matches) {
// Будуємо bitmap витягнутих чисел
uint256 drawnBitmap = 0;
for (uint8 i = 0; i < 20; i++) {
drawnBitmap |= (1 << (drawnNumbers[i] - 1));
}
// Перевіряємо picks гравця проти bitmap
for (uint8 i = 0; i < playerPicks.length; i++) {
if (drawnBitmap & (1 << (playerPicks[i] - 1)) != 0) {
matches++;
}
}
}
Бітові операції швидше ніж вкладені цикли. Для типових 1–10 picks: ≈ 3,000–5,000 додатково газу.
Таблиця виплат
Keno виплати — найважливіша економічна частина. Потрібно балансувати house edge (зазвичай 20–35% у Keno) при різних кількостях picks.
// Виплати як множник ставки (basis points: 100 = 1x)
// [picks][matches] → множник
uint256[11][11] public payoutTable;
constructor() {
// 1 pick
payoutTable[1][0] = 0;
payoutTable[1][1] = 360; // 3.6x
// 3 picks
payoutTable[3][0] = 0;
payoutTable[3][1] = 0;
payoutTable[3][2] = 200; // 2x
payoutTable[3][3] = 4600; // 46x
// 5 picks
payoutTable[5][0] = 0;
payoutTable[5][1] = 0;
payoutTable[5][2] = 0;
payoutTable[5][3] = 300; // 3x
payoutTable[5][4] = 1200; // 12x
payoutTable[5][5] = 50000; // 500x
// 10 picks
payoutTable[10][0] = 0;
payoutTable[10][5] = 200; // 2x
payoutTable[10][6] = 1800; // 18x
payoutTable[10][7] = 17000; // 170x
payoutTable[10][8] = 100000; // 1000x
payoutTable[10][9] = 250000; // 2500x
payoutTable[10][10] = 1000000; // 10000x (jackpot)
}
House edge верифікується математично: для кожного варіанту picks розрахуйте Expected Value:
EV(5 picks) = Σ P(k matches) × payout(5, k) для k = 0..5
P(k matches) = C(20,k) × C(60, 5-k) / C(80, 5)
EV повинен бути ≈ 0.70–0.80 (70–80% RTP, 20–30% house edge)
Стек та терміни
Chain: Polygon PoS або Arbitrum для низької гази. Chainlink VRF V2 Plus. Solidity + Foundry. Frontend: React + wagmi. WebSocket для реалтайм draw анімації.
| Фаза | Термін |
|---|---|
| Контракти (single player, VRF, payout table) | 3–4 тижні |
| Multi-player shared draw | 2 тижні |
| Frontend + draw анімація | 2–3 тижні |
| Bankroll + admin panel | 1–2 тижні |
| Audit + testnet | 3–4 тижні |
MVP (single player Keno): 5–7 тижнів. Повна платформа з multi-player draw: 9–12 тижнів.







