פיתוח משחק קנו על בלוקצ'יין
קנו הוא משחק לוטו: השחקן בוחר מספרים מטווח 1–80, המערכת בוחרת באקראי 20 מספרים, והזכיות תלויות במספר ההתאמות. המכניקה פשוטה, אך יישומה על בלוקצ'יין דורש פתרון של מספר בעיות לא טריוויאליות: אקראיות ניתנת לאימות לבחירת 20 מספרים, בדיקת התאמות חסכונית בגז, וטבלת תשלומים נכונה מתמטית. אנו מפתחים משחקים כאלה במפתח מלא כבר למעלה מ-5 שנים והשקנו בהצלחה 20+ פרויקטים על Ethereum, Polygon ו-Arbitrum.
בניגוד ל-Crash, קנו הוא משחק עם תוצאה קבועה לפני משיכת כספים: המספרים נשלפים, והתוצאה ידועה מיד. זה מפשט את הארכיטקטורה אך דורש תשומת לב מיוחדת לאיכות ה-RNG ולניהול הקופה. הצוות שלנו עוזר ללקוחות להימנע מטעויות אופייניות—כמו חישוב שגוי של יתרון הבית או חריגה בגז במהלך השליפה. צרו קשר לייעוץ חינם על ארכיטקטורת החוזה.
כיצד להבטיח אקראיות בקנו?
האתגר הטכני המרכזי: מזרע אקראי VRF יחיד, יש לקבל 20 מספרים ייחודיים בטווח 1–80. גישה נאיבית (rand % 80 חוזר 20 פעמים) יוצרת התנגשויות—מספר יכול להופיע פעמיים. אנו משתמשים ב-Fisher-Yates shuffle בחוזה חכם:
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++) {
// Получаем псевдо-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 מבטיח מספרים ייחודיים ללא דגימה עם דחייה. ביצוע על השרשרת: 20 איטרציות × 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++) { // Получаем псевдо-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]; } } ≈ 80,000–100,000 גז. על Arbitrum זה ~$0.01—מקובל לרוב התרחישים.
למה Fisher-Yates על פני Bitmap?
גישת bitmap (סימון מספרים משומשים במסכת סיביות) פשוטה יותר אך יכולה לדרוש יותר קריאות keccak256 בעת בחירת המספרים האחרונים עקב התנגשויות. Fisher-Yates מבטיח מספר קבוע של איטרציות—20. לגז צפוי, אנו ממליצים עליו, במיוחד לשליפות המוניות.
בדיקת התאמות: יישום חסכוני בגז
השחקן בוחר M מספרים (1–10), ועלינו לספור התאמות עם 20 המספרים שנשלפו. לולאות מקוננות O(M×20) מקובלות עבור M קטן, אך אנו משתמשים ב-bitmap כדי לחסוך בגז:
function countMatches( uint8[] memory playerPicks, uint8[20] memory drawnNumbers ) public pure returns (uint8 matches) { // Строим bitmap drawn numbers
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: ~3,000–5,000 גז נוסף.
טבלת תשלומים ויתרון בית
תשלומי קנו הם החלק הכלכלי החשוב ביותר. יש לאזן את יתרון הבית (בדרך כלל 20–35% בקנו) עבור מספרי בחירות שונים. הנה קטע מטבלת מכפילים לווריאנטים פופולריים:
| מספר בחירות | התאמות | מכפיל (X) |
|---|---|---|
| 1 | 1 | 3.6 |
| 3 | 2 | 2 |
| 3 | 3 | 46 |
| 5 | 3 | 3 |
| 5 | 4 | 12 |
| 5 | 5 | 500 |
| 10 | 5 | 2 |
| 10 | 6 | 18 |
| 10 | 7 | 170 |
| 10 | 8 | 1000 |
| 10 | 9 | 2500 |
| 10 | 10 | 10000 |
יתרון הבית מאומת מתמטית: עבור כל קבוצת בחירות, מחושב ערך צפוי (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)לדוגמה, לבחירה אחת: P(התאמה אחת) = 20/80 = 0.25, EV = 0.25 × 3.6 = 0.9, כלומר RTP 90%, יתרון בית 10%.
מחזור משחק מלא על השרשרת
contract KenoGame is VRFConsumerBaseV2Plus {
struct KenoRound {
address player;
uint256 betAmount;
uint8[] playerPicks;
uint8[20] drawnNumbers;
uint8 matchCount;
uint256 payout;
RoundStatus status;
uint256 vrfRequestId;
}
mapping(uint256 => KenoRound) public rounds;
mapping(uint256 => uint256) public vrfToRound;
uint256 public nextRoundId;
function playKeno(uint8[] calldata picks) external payable {
require(picks.length >= 1 && picks.length <= 10, "Invalid picks count");
require(msg.value >= MIN_BET && msg.value <= maxBet(), "Invalid bet");
// Валидируем picks (1-80, уникальные)
_validatePicks(picks);
uint256 roundId = nextRoundId++;
rounds[roundId] = KenoRound({
player: msg.sender,
betAmount: msg.value,
playerPicks: picks,
drawnNumbers: [uint8(0),...], // заполнится в callback
matchCount: 0,
payout: 0,
status: RoundStatus.PENDING,
vrfRequestId: 0
});
// Запрашиваем VRF
uint256 requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: s_keyHash,
subId: s_subscriptionId,
requestConfirmations: 1,
callbackGasLimit: 300_000, // с запасом для drawNumbers
numWords: 1,
extraArgs: ""
})
);
rounds[roundId].vrfRequestId = requestId;
vrfToRound[requestId] = roundId;
emit KenoRoundStarted(roundId, msg.sender, picks, msg.value);
}
function fulfillRandomWords(
uint256 requestId,
uint256[] calldata randomWords
) internal override {
uint256 roundId = vrfToRound[requestId];
KenoRound storage round = rounds[roundId];
// Вытягиваем 20 чисел
round.drawnNumbers = drawNumbers(randomWords[0]);
// Считаем совпадения
round.matchCount = countMatches(round.playerPicks, round.drawnNumbers);
// Рассчитываем выплату
uint256 multiplier = payoutTable[round.playerPicks.length][round.matchCount];
round.payout = round.betAmount * multiplier / 100;
round.status = RoundStatus.COMPLETED;
// Выплачиваем победителю
if (round.payout > 0) {
require(address(this).balance >= round.payout, "Insufficient bankroll");
payable(round.player).transfer(round.payout);
}
emit KenoResult(
roundId,
round.player,
round.drawnNumbers,
round.matchCount,
round.payout
);
}
} מה זה שליפה משותפת ואיך היא חוסכת בגז?
לסגנון קזינו שבו מספר שחקנים משתתפים בסבב שליפה אחד, אנו מיישמים שליפה משותפת. בקשת VRF אחת לכל הסבב מחולקת בין כל המשתתפים, ומפחיתה את הגז לשחקן מ-~100,000 ל-~15,000.
contract MultiPlayerKeno is VRFConsumerBaseV2Plus {
struct DrawRound {
uint8[20] drawnNumbers;
uint256 drawTime;
bool resolved;
address[] participants;
}
// Раунды каждые N минут
uint256 public roundInterval = 3 minutes;
// Ставки привязываются к будущему draw раунду
struct PlayerBet {
uint8[] picks;
uint256 amount;
uint256 drawRoundId;
}
function getBetsOnNextDraw(address player) external view returns (PlayerBet[] memory) {
uint256 nextDraw = (block.timestamp / roundInterval + 1) * roundInterval;
return pendingBets[nextDraw][player];
}
// Один VRF запрос на весь draw — делится между всеми участниками
// Gas cost per player: ~15,000 gas (vs ~100,000 для single player)
function triggerDraw(uint256 drawRoundId) external {
require(block.timestamp >= drawRoundId, "Too early");
require(!drawRounds[drawRoundId].resolved, "Already drawn");
uint256 requestId = s_vrfCoordinator.requestRandomWords(...);
vrfToDrawRound[requestId] = drawRoundId;
}
} כיצד לאמת את תוצאת המשחק?
- קבלו את בקשת ה-VRF והתשובה מאירועים על השרשרת.
- הפעילו drawNumbers(vrfResult) — קבלו את אותם 20 מספרים.
- ודאו שהקזינו לא תמרן.
Chainlink מפרסם בפומבי את ההוכחה הקריפטוגרפית של כל תשובת VRF — האימות אפשרי באופן עצמאי מהקזינו. מנגנון זה מבוסס על Chainlink VRF.
מה כלול בעבודה
- פיתוח החוזה החכם למשחק (שחקן יחיד או רב-שחקנים)
- אינטגרציה של Chainlink VRF V2 Plus
- הגדרת טבלת התשלומים עם אימות מתמטי של יתרון הבית
- יצירת ממשק חזיתי (React + wagmi + RainbowKit)
- פריסה על testnet ו-mainnet (Polygon, Arbitrum, או רשת אחרת)
- ביקורת חוזה חכם (פנימית או צד שלישי)
- תיעוד לאינטגרציה ותמיכה
לוחות זמנים משוערים
| שלב | משך |
|---|---|
| חוזים (שחקן יחיד, VRF, טבלת תשלומים) | 3–4 שבועות |
| שליפה משותפת רב-שחקנים | שבועיים |
| חזית + אנימציית שליפה | 2–3 שבועות |
| קופה + פאנל ניהול | 1–2 שבועות |
| ביקורת + testnet | 3–4 שבועות |
סה"כ MVP (קנו שחקן יחיד): 5–7 שבועות. פלטפורמה מלאה עם שליפה רב-שחקנים: 9–12 שבועות.
לצוות שלנו ניסיון של 5+ שנים בפיתוח בלוקצ'יין והוא סיפק בהצלחה 20+ חוזים חכמים למשחקים. אם אתם מתכננים להשיק פלטפורמת קנו משלכם עם שקיפות מובטחת וגז נמוך, קבלו ייעוץ—נעריך את הפרויקט שלכם ביום אחד.







