ゲームイベントとは
ゲームイベントとは、0から1の間のランダムな浮動小数点数を使って、実際のゲーム結果を導き出す仕組みです。この浮動小数点数は、generateFloats や byteGenerator 関数を使って生成されます。ゲームによってこの変換の方法は異なります。例えば、プリンコでは、浮動小数点数がボールの落ちる方向を決めるのに対し、ダイスでは出目を決定します。
以下では、当社のプラットフォームにある各オリジナルゲームで、浮動小数点数をどのように公平な結果に変換しているかを詳しく説明します。
プリンコ
プリンコでは、プレイヤーが高さ8から16のピラミッドを選べます。ボールが上から落ちると、ピンに当たるたびに8回から16回、左か右に進みます。このゲームのイベントでは、ボールが各レベルで左に落ちるか右に落ちるかを決めます。左に落ちれば0、右に落ちれば1で表現されます。
// Create multiple floats between 0 and 1
// Example: floats = [0.8320531346835196, 0.8531235849950463, 0.18227359023876488, 0.774284637067467, 0.18627392360940576, 0.6012540922965854, 0.608081541955471, 0.46018488449044526] when clientSeed='abc', serverSeed='def', nonce=1, count=8
// count refers to the number of plinko rows
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor, count });
// Example: [1, 1, 0, 1, 0, 1, 1, 0] Each 1 represents a drop to the right whereas 0 represents a drop to the left
const dropDirections = floats.map(val => Math.floor(val * 2));
// This sums up all the values in the array to get the final position of the ball from the left.
// Example: finalPositionFromLeft = 5 (the ball will land on the 6th slot from the left)
const finalPositionFromLeft = dropDirections.reduce((cur, acc) => cur + acc, 0)
ダイス
ダイスでは、出目は0.00から100.00の範囲で決まります。このゲームのイベントでは、その値を算出します。具体的には、生成した浮動小数点数を使って、次のように計算します: (floats × 10,001) ÷ 100). 小数点以下2桁までの値を考慮するため、合計で10,000通りの結果に加えて、0も含めて10,001の可能性があります。
// Creates an array of random numbers
// Example: With clientSeed = 'abc', serverSeed = 'def', nonce = 1, floats = [0.43053962965495884]
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
// Converts the value in the array to a number between 0.00 to 100.00 and return the value
// Example: resultValue = [43.05]
const resultValue = floats.map(val => Math.floor(floats * 10001) / 100);
return resultValue[0];
// Example: 43.05 is returned as the result of the game
リンボ
リンボでは、プレイヤーが1より大きい倍率を予想することが目的です。ゲームの結果がその倍率を上回れば、勝利となります。このゲームのイベントでは、1より大きい浮動小数点数を生成し、それが双曲線的な分布に従います。
// Generate float between 0 and 1
// Example: floats = [0.43053962965495884], float = 0.43053962965495884,
// when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
const float = floats[0]
// generate limbo game event results with house edge (1%)
// using a hyperbolic 1/x curve
// multiplying by this and dividing by (MAX_VALUE + 1) prevents division by 0 error
const MAX_VALUE = 2 ** 24;
const rtp = 0.99;
// Example: rawValue = 2.298905
const rawValue = (rtp * MAX_VALUE) / (MAX_VALUE * float + 1)
// resultValue = 2.29
const resultValue = (0.99 * MAX_VALUE / (MAX_VALUE * float +1)).toFixed(2,ROUND_DOWN);
return resultValue;
ケノ
ケノでは、ゲームのイベントとして、ケノボード上に10個のユニークな宝石の配置を決める必要があります。このために、1から40の間で10個の異なる浮動小数点数を生成し、それぞれがケノボード上の位置を表します。その後、重複を避けるための処理を行い、最終的な10個の宝石の位置を決定します。
// Generate 10 different floats as there are 10 positions that are selected when the game is played
// Example: floats = [18, 26, 7, 22, 31, 10, 23, 8, 24, 13] when clientSeed='abc', serverSeed='def', nonce=1
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: 10 }) .map((val, index) => Math.ceil(val * (40 - index)))
// array used to store the position of the gems const gemLocations: number[] = [];
// this loop helps to prevent duplicate gem positions by skipping positions that already contain a gem
for (const val of floats) { let currentGem = val;
let i = 0;
while (i <= currentGem) {
if (gemLocations.includes(i)) {
currentGem++;
}
i++;
}
gemLocations.push(currentGem);
}
// returns the location of the gem on the board
// Example: [18, 27, 7, 24, 35, 11, 28, 9, 31, 16] return gemLocations;
マインズ
マインズでは、ゲームのイベントとして、25マスのボード上に地雷の配置を決める必要があります。ユーザーが選択した地雷の数に基づいて、0から24までの整数のリストを生成し、ボード上の25のスペースを表します。
// Generate 10 different floats
// Example: [20, 20, 4] when clientSeed='abc', serverSeed='def', nonce=1, numberOfBombs=3
const floats = generateFloats({ serverSeed, clientSeed, nonce, cursor: 0, count: numberOfBombs })
.map((val, index) => Math.floor(val * (25 - index)));
// array used to store the position of the bombs
const mineLocations: number[] = [];
// this loop helps to prevent duplicate bomb positions by skipping positions that already contain a mine
for (const val of floats) {
let currentMine = val;
let i = 0;
while (i <= currentMine) {
if (mineLocations.includes(i)) {
currentMine++;
} i++; } mineLocations.push(currentMine); }
// returns the location of the bombs on the board
// Example: [20, 21, 4]
return mineLocations;
ルーレット
当社のルーレットは、ヨーロピアンスタイルのシングル「0」ホイールに基づいており、0から36までの37のポケットがあります。ルーレットのゲームイベントでは、ボールがどのポケットに入るかを計算します。
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884]
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor: 0, count: 1 });
// generate an integer from 0 to 37 to represent the pocket to land on
// Example: resultValue = 15
const resultValue = floats.map(val => Math.floor(val * 37))[0];
return resultValue
ホイール
ホイールでは、ユーザーがセグメントの数を選びます。ポインターがどのセグメントに止まるかによって、そのセグメントの倍率が決まります。ゲームイベントでは、止まるセグメントを特定します。
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884]
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:1 });
// Get an integer representing the number of segments set in the game
// Example: resultSegment = 8
const resultSegment = floats.map(val => Math.floor(val * numberOfSegments))[0];
return resultSegment
ブラックジャック
ブラックジャックでは、無限に続くカードデッキを使ってゲームが行われます。そのため、各ターンの確率は常に同じです。ゲームイベントでは、プレイヤーまたはバンカーがデッキからカードを引く際に、どのカードが引かれるかを決定します。
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (52 elements are generated but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });
const CARDS = [ ♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4, ♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6, ♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9, ♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J, ♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A, ♥A, ♠A, ♣A
];
// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));
// Example: cards = [♠7, ♠10, ♥4, ♥9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;
ハイロー
ハイローでは、無限のカードデッキを使用します。そのため、各ターンの確率は常に同じです。ゲームの目的は、次に引かれるカードが現在のカードよりも高いか低いかを予想することです。ゲームイベントでは、次にプレイヤーが引くカードを決定します。
// Generate a float between 0 and 1
// Example: floats = [0.43053962965495884, 0.6645898742135614, 0.17454026662744582, 0.5729992990382016, 0.850033157505095] (an unlimited number of cards can be drawn but only 5 are shown here for brevity)
const floats = generateFloats({serverSeed, clientSeed, nonce, cursor, count:52 });
const CARDS = [
♦2, ♥2, ♠2, ♣2, ♦3, ♥3, ♠3, ♣3, ♦4, ♥4, ♠4, ♣4, ♦5, ♥5, ♠5, ♣5, ♦6, ♥6, ♠6, ♣6, ♦7, ♥7, ♠7, ♣7, ♦8, ♥8, ♠8, ♣8, ♦9, ♥9, ♠9, ♣9, ♦10, ♥10, ♠10, ♣10, ♦J, ♥J, ♠J, ♣J, ♦Q, ♥Q, ♠Q, ♣Q, ♦K, ♥K, ♠K, ♣K, ♦A, ♥A, ♠A, ♣A
];
// Get an integer 0 to 51, representing the index of the cards
// Example: cardFloats = [22, 34, 9, 29, 44]
const cardFloats = floats.map(val => Math.floor(val * 52));
// Example: cards = [♠7, ♠10, ♥4, ♥9, ♦K]
const cards = cardFloats.map(val => CARDS[val])
return cards;
クラッシュ
クラッシュゲームの結果を計算するには、以下の2つの要素があります。
ビットコインのブロックハッシュ
シード
ビットコインハッシュ
詳細はこちらのツイートをご覧ください:
ブロック779588のハッシュは、BTCブロックエクスプローラー (https://www.blockchain.com/explorer/blocks/btc/779588) で確認でき、ツイートに表示されているハッシュと一致します。
シード
10百万回のクラッシュゲームのためのシード生成プロセスは、公平性と透明性を確保するために設計されており、Shuffleによる操作の可能性を排除しています。以下にシードの生成と検証の方法を示します。
初期シード生成:1000万回目のゲームのシードは、Shuffleによってランダムに選ばれます。
シード生成のための逐次ハッシュ:他のすべてのゲームのシードを生成するために、逆ハッシュ法を使用します。
各ゲームのシードは、次のゲームのシードをハッシュした結果です。具体的には、999万9999回目のゲームのシードは、1000万回目のゲームのシードのSHA256ハッシュです。
この連鎖は続き、各ゲームのシードは次のゲームのシードをハッシュすることで導出され、最終的には最初のゲームのシードが2回目のゲームのシードから導出されます。
シードの整合性の検証:
最初のゲームで使用されたシードのハッシュ値を公開することで、プレイヤーは1000万回目のゲームまでのすべてのゲームのシードの整合性を独自に検証できます。
この方法を用いれば、特定のゲームのシードを知っていれば、すべての前のゲームのシードを検証できます。たとえば、ゲーム1323185のシードを知っている場合、このシードをハッシュしてゲーム1323184のシードと一致することを確認できます。
たとえば、ゲーム1323185 (https://shuffle.com/games/originals/crash?md-id=1323185&modal=crashGame) では、シードは「2f824ebb9548a4d24d042ecab390acd1dc931048f696dc03c738937852825ac0」です。
ゲーム1323184 (https://shuffle.com/games/originals/crash?md-id=1323184&modal=crashGame)では、シードは「20f42ccc46fed8e005767f85eb4bd5d42d3bd2a72b01ae77dcc7071bec280265」です。
ゲーム1323184のシード値は、1323185のシードのSHA256ハッシュです。これを確認するためには、オンラインのSHA256計算機 https://xorbin.com/tools/sha256-hash-calculator を使用できます。
透明性とセキュリティ:
Shuffleの管理外にある将来のBTCブロックハッシュを使用し、最初のゲームのシードのハッシュ版を公開することで、ユーザーはクラッシュゲームの結果が公平で改ざんされていないことを証明できます。