Provably Fair
पारदर्शिता और प्रमाणित निष्पक्षता सुनिश्चित करने के लिए, हमने प्रत्येक मल्टीप्लेयर गेम के लिए 10 मिलियन SHA256 हैशेस की एक श्रृंखला बनाई है, एक सर्वर गुप्त से शुरू करते हुए जिसे SHA256 के आउटपुट को 10 मिलियन बार खुद में वापस डाला गया है
Platform के लिए अंतिम हैश का SHA256 है: eeb4d340bfaed702601b9a08faba2f8088c4810c3e3a883293accaf25053b883
Crash के लिए अंतिम हैश का SHA256 है: c46f0705f6ba4df891ce44accda8f89f5a6fa8b987eb7c7b445280cf6cabfbc6
Battle के लिए अंतिम हैश का SHA256 है: 63cb1eb64ec4eb4de02f6bad85e42365d08f37497a1b7e907bd0b9183f27e1b3
इसे यहां प्रकाशित करके, हम एक वैकल्पिक SHA256 चेन चुनने की क्षमता को रोक रहे हैं। अब गेम सर्वर इस हैशेस की श्रृंखला को रिवर्स ऑर्डर में चला रहा है, गेम परिणामों को सिद्ध रूप से निष्पक्ष तरीके से गणना करने के लिए इन मानों का उपयोग कर रहा है।
1. गुप्त प्रारंभिक हैश: प्रत्येक मल्टीप्लेयर गेम प्रकार के अपने निजी प्रारंभिक हैश हैं (केवल सर्वर द्वारा ज्ञात), एक अद्वितीय हैश चेन की जड़ बनाते हैं।
2. हैश चेन जनरेशन: प्रारंभिक गुप्त हैश से, हम SHA256 को बार-बार लागू करके 10,000,000 हैशेस की एक श्रृंखला बनाते हैं।
func HashSeed(seed string) string {
hash := sha256.Sum256([]byte(seed))
return hex.EncodeToString(hash[:])
}
func CalculateHashChain(firstHash string) []string {
hashes := make([]string, 10_000_000)
for i := int64(0); i <= 10_000_000; i++ {
firstHash = HashSeed(firstHash)
hashes[i] = firstHash
}
return hashes
}
3. रिवर्स खपत: हम चेन में अंतिम हैश को जनता के सामने प्रकट करते हैं। प्रत्येक गेम राउंड हैशेस को रिवर्स ऑर्डर में खपत करता है: राउंड 1 हैश[10_000_000] का उपयोग करता है, राउंड 2 हैश[9_999_999] का उपयोग करता है, और इसी तरह...
4. यह डिज़ाइन भविष्य के हैशेस को जाली करना असंभव बनाता है, जबकि किसी को भी सभी पिछले हैशेस को सत्यापित करने की अनुमति देता है।
अप्रत्याशितता: भविष्य के गेम परिणाम ज्ञात या प्रभावित नहीं हो सकते, यहां तक कि सर्वर द्वारा भी।
सत्यापन योग्यता: खिलाड़ी केवल सार्वजनिक रूप से प्रकट अंतिम हैश का उपयोग करके किसी भी पिछले राउंड की अखंडता को सत्यापित कर सकते हैं।
गेम अलगाव: प्रत्येक गेम प्रकार की अपनी चेन है, जो गेम-पार जोड़तोड़ को रोकता है।
सिंगल-प्लेयर गेम्स में सिद्ध निष्पक्षता सुनिश्चित करने के लिए, हमारा प्लेटफ़ॉर्म परिणाम बीज उत्पन्न करने के लिए तीन मुख्य घटकों पर आधारित एक प्रणाली का उपयोग करता है:
1. क्लाइंट सीड: यह एक उपयोगकर्ता-नियंत्रित बीज है। यह उपयोगकर्ता के पंजीकरण के समय स्वचालित रूप से उत्पन्न होता है, लेकिन उपयोगकर्ता इसे किसी भी समय बदल सकता है। क्लाइंट सीड को बदलने से एक नया सर्वर सीड उत्पन्न होता है।
2. सर्वर सीड: यह सर्वर द्वारा आंतरिक पैरामीटर का उपयोग करके उत्पन्न होता है जो उपयोगकर्ता को प्रकट नहीं किए जाते हैं। यह वर्तमान क्लाइंट सीड के लिए अद्वितीय रूप से बाध्य है। प्रत्येक क्लाइंट सीड के लिए, एक संबंधित सर्वर सीड है। जब उपयोगकर्ता क्लाइंट सीड बदलता है, तो पिछला सर्वर सीड सत्यापन उद्देश्यों के लिए प्रकट किया जाता है।
3. नॉन्स: एक अद्वितीय संख्या जो दांव के समय टिकट (मिलीसेकंड में) पर आधारित है। यह सुनिश्चित करता है कि दोहराए गए क्रियाएं भी विभिन्न परिणाम देते हैं।
ये तीनों मान HMAC-SHA256 का उपयोग करके संयुक्त और हैश किए जाते हैं अंतिम गेम सीड उत्पन्न करने के लिए, जिसका उपयोग गेम के परिणाम निर्धारित करने के लिए किया जाता है।
func GenerateUserGameSeed(userSeed string, userServerSeed string, nonce int64) (string, error) {
data := fmt.Sprintf("%s:%s:%d", userServerSeed, userSeed, nonce)
mac := hmac.New(sha256.New, []byte(data))
gameSeed := hex.EncodeToString(mac.Sum(nil))
return gameSeed, nil
}
सर्वर किसी भी दांव से पहले वर्तमान सर्वर सीड का हैश किया गया संस्करण प्रदान करता है।
जब उपयोगकर्ता अपने क्लाइंट सीड को बदलता है, तो पहले उपयोग किया जाने वाला सर्वर सीड प्रकट किया जाता है।
यह उपयोगकर्ताओं को सत्यापित करने की अनुमति देता है कि पिछले सर्वर सीड के साथ उत्पन्न सभी परिणाम सुसंगत और निष्पक्ष थे।
पासा गेम में, एक उपयोगकर्ता 1 से 5 संख्याएं (6 संभावित पक्षों में से) चुनता है। उत्पन्न गेम सीड का उपयोग करके एक एकल पासा रोल का अनुकरण किया जाता है। यदि रोल की गई संख्या उपयोगकर्ता के चयन में से एक से मेल खाती है, तो उपयोगकर्ता जीतता है।
func rollDice(seed string, selectedNumbers []int64) (int64, bool, error) {
if len(selectedNumbers) < 1 || len(selectedNumbers) > 5 {
return 0, false, fmt.Errorf("incorrect number of selected numbers: %d", len(selectedNumbers))
}
bigSeed, ok := new(big.Int).SetString(seed, 16)
if !ok {
return 0, false, fmt.Errorf("failed to convert seed to big.Int")
}
// Simulate dice roll: number from 1 to 6
dice := new(big.Int).Mod(bigSeed, big.NewInt(6))
diceFace := dice.Int64() + 1
for _, num := range selectedNumbers {
if num == diceFace {
return diceFace, true, nil
}
}
return diceFace, false, nil
}
खान गेम में, उद्देश्य खान को हिट किए बिना जितने संभव हो सके टाइलों को उजागर करना है। खान की प्लेसमेंट एक क्रिप्टोग्राफिकली सुरक्षित गेम सीड का उपयोग करके निर्धारक रूप से प्राप्त की जाती है। यह निष्पक्षता और पारदर्शिता सुनिश्चित करता है, उपयोगकर्ता को यह सत्यापित करने की अनुमति देता है कि गेम बोर्ड में हेरफेर नहीं किया गया था।
इनपुट:
seed: HMAC-SHA256 का उपयोग करके ClientSeed, ServerSeed और Nonce के संयोजन से प्राप्त 64-वर्ण हेक्स स्ट्रिंग।
numberOfMines: बोर्ड पर रखने के लिए खानों की संख्या।
maxCells: बोर्ड पर कुल कोशिकाओं की संख्या (उदाहरण के लिए, 5x5 ग्रिड के लिए 25)।
func generateMines(seedHex string, numberOfMines int) ([]int64, error) {
if numberOfMines > 25 {
return nil, fmt.Errorf("invalid number of mines: %d", numberOfMines)
}
var mines []int64
used := make(map[int]bool)
i := 0
for len(mines) < numberOfMines {
mac := hmac.New(sha256.New, []byte(seedHex))
mac.Write([]byte(fmt.Sprintf("mine-%d", i)))
sum := mac.Sum(nil)
val := binary.BigEndian.Uint32(sum)
pos := int(val%25) + 1
if !used[pos] {
used[pos] = true
mines = append(mines, int64(pos))
}
i++
}
return mines, nil
}यदि numberOfMines = 3 और maxCells = 25, तो फ़ंक्शन निर्धारक रूप से 3 अद्वितीय सेल इंडेक्स (1 से 25 तक) लौटाएगा जहां खानें रखी गई हैं।
प्लेटफॉर्म गेम में, प्रत्येक राउंड का परिणाम एक सार्वजनिक, सत्यापन योग्य हैश द्वारा निर्धारित किया जाता है जो पहले से प्रकाशित हैश चेन का हिस्सा है।
प्रत्येक राउंड चेन से एक हैश का उपयोग करके "अंतिम प्लेटफॉर्म" निर्धारित करता है — प्लेटफॉर्म जो गेम के अंत में गायब हो जाएगा।
func generateLastPlatform(hash string) (int64, error) {
h, err := hex.DecodeString(hash)
if err != nil {
return 0, err
}
number := binary.BigEndian.Uint64(h[:8])
rng := rand.New(rand.NewSource(int64(number)))
randomNumber := rng.Intn(25) + 1 // Platforms are numbered 1 through 25
return int64(randomNumber), nil
}
गेमप्ले के दौरान, खिलाड़ी विभिन्न प्लेटफॉर्मों पर खड़े होते हैं। जो प्लेटफॉर्म ड्रॉप होता है वह उपलब्ध प्लेटफॉर्मों से यादृच्छिक रूप से चुना जाता है, यह सुनिश्चित करता है कि यह पहले से हटाए गए जैसा नहीं है।
रॉकेट में, प्रत्येक राउंड परिणाम (क्रैश गुणक) पूर्व-जनरेट हैश चेन (प्रति गेम अद्वितीय) से एक सार्वजनिक हैश का उपयोग करके निर्धारित किया जाता है। परिणाम एक निर्धारक कार्य का उपयोग करके गणना की जाती है जो हैश को गुणक में परिवर्तित करता है।
func generateMultiplier(hash string) (float64, error) {
const N = 40
bigH, ok := new(big.Int).SetString(hash, 16)
if !ok {
return 0, fmt.Errorf("failed to convert seed to big.Int")
}
mod := new(big.Int).Mod(bigH, big.NewInt(N))
if mod.Cmp(big.NewInt(0)) == 0 {
return 1, nil
}
if len(hash) < 13 {
return 0, fmt.Errorf("hash too short")
}
h13Str := hash[:13]
bigH13, ok := new(big.Int).SetString(h13Str, 16)
if !ok {
return 0, fmt.Errorf("failed to convert first 13 hex digits to big.Int")
}
// Compute 100 * (2^52 - h) / (2^52 - h)
e := new(big.Int).Lsh(big.NewInt(1), 52) // 2^52
hundred := big.NewInt(100)
hundredE := new(big.Int).Mul(hundred, e)
numerator := new(big.Int).Sub(hundredE, bigH13)
denom := new(big.Int).Sub(e, bigH13)
numFloat := new(big.Float).SetInt(numerator)
denomFloat := new(big.Float).SetInt(denom)
ratio, _ := new(big.Float).Quo(numFloat, denomFloat).Float64()
floored := math.Floor(ratio)
result := floored / 100.0
return result, nil
}
प्रत्येक खिलाड़ी राउंड से पहले हैश प्राप्त करता है, और स्वतंत्र रूप से यह सत्यापित कर सकता है कि परिणाम प्रकाशित सूत्र से मेल खाते हैं, पूर्ण पारदर्शिता और सिद्ध निष्पक्षता सुनिश्चित करते हैं।
बैटल में, एक खिलाड़ी को विजेता के रूप में चुना जाता है कुल पूल के लिए उनके दांव के आनुपातिक आकार के आधार पर। इसका मतलब है कि बड़े दांव जीतने की उच्च संभावनाओं के परिणामस्वरूप होते हैं।
हम एक निर्धारक और सिद्ध रूप से निष्पक्ष चयन तंत्र का उपयोग करते हैं:
1. एक सार्वजनिक गेम हैश का उपयोग रेंज [0.0, 1.0) में एक छद्म-यादृच्छिक संख्या उत्पन्न करने के लिए किया जाता है।
2. प्रत्येक खिलाड़ी को रेंज के एक सेगमेंट को असाइन किया जाता है जो उनके दांव के अनुपात में होता है (उदाहरण के लिए 20% दांव रेंज के 20% को कवर करता है)।
3. यादृच्छिक संख्या विजयी सेगमेंट निर्धारित करती है, और इसलिए विजेता निर्धारित करती है।
func pickWinner(hash string, chances []float64) (int, error) {
if len(chances) == 0 {
return -1, fmt.Errorf("zero participants")
}
total := 0.0
for _, chance := range chances {
if chance < 0 {
return -1, fmt.Errorf("chance cannot be negative")
}
total += chance
}
if total < 99.99 || total > 100.01 {
return -1, fmt.Errorf("sum of chances must be 100%%, got: %.2f%%", total)
}
randomValue, err := deterministicFloatFromHash(hash)
if err != nil {
return -1, fmt.Errorf("random generation failed: %w", err)
}
cumulative := 0.0
for i, chance := range chances {
cumulative += chance / 100.0
if randomValue < cumulative {
return i, nil
}
}
return -1, fmt.Errorf("failed to pick winner")
}
func deterministicFloatFromHash(hash string) (float64, error) {
bytes, err := hex.DecodeString(hash)
if err != nil {
return 0, err
}
if len(bytes) < 8 {
return 0, fmt.Errorf("hash too short")
}
num := binary.BigEndian.Uint64(bytes[:8])
return float64(num) / float64(math.MaxUint64), nil
}