স্বচ্ছতা এবং প্রমাণযোগ্য ন্যায্যতা নিশ্চিত করতে আমরা প্রতিটি মাল্টিপ্লেয়ার গেমের জন্য ১ কোটি SHA256 হ্যাশের একটি চেইন তৈরি করেছি, একটি সার্ভার সিক্রেট দিয়ে শুরু করে যা বারবার SHA256-এর আউটপুট নিজের মধ্যে ১ কোটি বার ফিড করা হয়েছে
Platform-এর জন্য চূড়ান্ত হ্যাশের SHA256 হল: eeb4d340bfaed702601b9a08faba2f8088c4810c3e3a883293accaf25053b883
Crash-এর জন্য চূড়ান্ত হ্যাশের SHA256 হল: c46f0705f6ba4df891ce44accda8f89f5a6fa8b987eb7c7b445280cf6cabfbc6
Battle-এর জন্য চূড়ান্ত হ্যাশের SHA256 হল: 63cb1eb64ec4eb4de02f6bad85e42365d08f37497a1b7e907bd0b9183f27e1b3
এটি এখানে প্রকাশ করে আমরা যেকোনো বিকল্প SHA256 চেইন বেছে নেওয়ার ক্ষমতা প্রতিরোধ করছি। এখন গেম সার্ভার বিপরীত ক্রমে এই হ্যাশ চেইনের মধ্য দিয়ে খেলছে, এই মানগুলি ব্যবহার করে গেমের ফলাফল প্রমাণযোগ্যভাবে ন্যায্যভাবে গণনা করছে।
১. সিক্রেট প্রাথমিক হ্যাশ: প্রতিটি মাল্টিপ্লেয়ার গেমের ধরনের নিজস্ব ব্যক্তিগত প্রাথমিক হ্যাশ রয়েছে (শুধুমাত্র সার্ভারের কাছে পরিচিত), যা একটি অনন্য হ্যাশ চেইনের মূল গঠন করে।
২. হ্যাশ চেইন জেনারেশন: প্রাথমিক সিক্রেট হ্যাশ থেকে, আমরা বারবার SHA256 প্রয়োগ করে ১,০০,০০,০০০ হ্যাশের একটি চেইন তৈরি করি।
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
}
৩. বিপরীত ব্যবহার: আমরা চেইনের চূড়ান্ত হ্যাশ জনসাধারণের কাছে প্রকাশ করি। প্রতিটি গেম রাউন্ড বিপরীত ক্রমে হ্যাশ ব্যবহার করে: রাউন্ড ১ hash[10_000_000] ব্যবহার করে রাউন্ড ২ hash[9_999_999] ব্যবহার করে এবং এভাবে চলতে থাকে...
৪. এই ডিজাইন ভবিষ্যতের হ্যাশ জাল করা অসম্ভব করে তোলে, একইসাথে যে কাউকে সমস্ত পূর্ববর্তী হ্যাশ যাচাই করার অনুমতি দেয়।
অপ্রত্যাশিততা: ভবিষ্যতের গেমের ফলাফল জানা বা প্রভাবিত করা যায় না, এমনকি সার্ভার দ্বারাও নয়।
যাচাইযোগ্যতা: খেলোয়াড়রা শুধুমাত্র প্রকাশ্যে প্রকাশিত চূড়ান্ত হ্যাশ ব্যবহার করে যেকোনো পূর্ববর্তী রাউন্ডের অখণ্ডতা যাচাই করতে পারেন।
গেম বিচ্ছিন্নতা: প্রতিটি গেমের ধরনের নিজস্ব চেইন রয়েছে, ক্রস-গেম ম্যানিপুলেশন প্রতিরোধ করে।
একক-খেলোয়াড় গেমে প্রমাণযোগ্য ন্যায্যতা নিশ্চিত করতে, আমাদের প্ল্যাটফর্ম ফলাফল সিড তৈরি করতে তিনটি প্রধান উপাদানের উপর ভিত্তি করে একটি সিস্টেম ব্যবহার করে:
১. ক্লায়েন্ট সিড: এটি একটি ব্যবহারকারী-নিয়ন্ত্রিত সিড। একজন ব্যবহারকারী নিবন্ধন করলে এটি স্বয়ংক্রিয়ভাবে তৈরি হয়, তবে ব্যবহারকারী যেকোনো সময় এটি পরিবর্তন করতে পারেন। ক্লায়েন্ট সিড পরিবর্তন করলে একটি নতুন সার্ভার সিড তৈরি শুরু হয়।
২. সার্ভার সিড: এটি সার্ভার দ্বারা অভ্যন্তরীণ পরামিতি ব্যবহার করে তৈরি করা হয় যা ব্যবহারকারীর কাছে প্রকাশ করা হয় না। এটি বর্তমান ক্লায়েন্ট সিডের সাথে অনন্যভাবে আবদ্ধ। প্রতিটি ক্লায়েন্ট সিডের জন্য, একটি সংশ্লিষ্ট সার্ভার সিড রয়েছে। যখন ব্যবহারকারী ক্লায়েন্ট সিড পরিবর্তন করেন, পূর্ববর্তী সার্ভার সিড যাচাইকরণের উদ্দেশ্যে প্রকাশ করা হয়।
৩. নন্স: বাজির টাইমস্ট্যাম্পের (মিলিসেকেন্ডে) উপর ভিত্তি করে একটি অনন্য সংখ্যা। এটি নিশ্চিত করে যে এমনকি পুনরাবৃত্তিমূলক কর্মও ভিন্ন ফলাফল উৎপন্ন করে।
এই তিনটি মান একত্রিত করা হয় এবং 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
}
কোনো বাজি রাখার আগে সার্ভার বর্তমান সার্ভার সিডের হ্যাশ করা সংস্করণ প্রদান করে।
ব্যবহারকারী তাদের ক্লায়েন্ট সিড পরিবর্তন করার পরে, পূর্বে ব্যবহৃত সার্ভার সিড প্রকাশ করা হয়।
এটি ব্যবহারকারীদের যাচাই করতে দেয় যে পূর্ববর্তী সার্ভার সিড দিয়ে তৈরি সমস্ত ফলাফল সামঞ্জস্যপূর্ণ এবং ন্যায্য ছিল।
Dice গেমে, একজন ব্যবহারকারী ১ থেকে ৫টি সংখ্যা নির্বাচন করেন (৬টি সম্ভাব্য দিকের মধ্যে)। তৈরি করা গেম সিড ব্যবহার করে একটি একক ডাইস রোল অনুকরণ করা হয়। যদি রোল করা সংখ্যাটি ব্যবহারকারীর নির্বাচনের একটির সাথে মেলে, তাহলে ব্যবহারকারী জেতেন।
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
}
Mines গেমে, লক্ষ্য হল একটি মাইনে আঘাত না করে যতটা সম্ভব বেশি টাইল উন্মোচন করা। মাইনের স্থাপন একটি ক্রিপ্টোগ্রাফিকভাবে নিরাপদ গেম সিড ব্যবহার করে নির্ধারকভাবে উদ্ভূত হয়। এটি ন্যায্যতা এবং স্বচ্ছতা নিশ্চিত করে, ব্যবহারকারীকে যাচাই করতে দেয় যে গেম বোর্ড ম্যানিপুলেট করা হয়নি।
ইনপুট:
seed: ClientSeed, ServerSeed, এবং Nonce-এর সংমিশ্রণ থেকে HMAC-SHA256 ব্যবহার করে উদ্ভূত একটি ৬৪-অক্ষরের হেক্স স্ট্রিং।
numberOfMines: বোর্ডে স্থাপন করার মাইনের সংখ্যা।
maxCells: বোর্ডে মোট কোষের সংখ্যা (যেমন, ৫x৫ গ্রিডের জন্য ২৫)।
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 = ৩ এবং maxCells = ২৫ হয়, ফাংশনটি নির্ধারকভাবে ৩টি অনন্য কোষ সূচক (১ থেকে ২৫ পর্যন্ত) ফেরত দেবে যেখানে মাইন স্থাপন করা হয়েছে।
Platforms গেমে, প্রতিটি রাউন্ডের ফলাফল একটি পাবলিক, যাচাইযোগ্য হ্যাশ দ্বারা নির্ধারিত হয় যা পূর্বে প্রকাশিত একটি হ্যাশ চেইনের অংশ।
প্রতিটি রাউন্ড চেইন থেকে একটি হ্যাশ ব্যবহার করে "শেষ প্ল্যাটফর্ম" নির্ধারণ করতে - সেই প্ল্যাটফর্ম যা গেমের শেষে অদৃশ্য হয়ে যাবে।
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
}
গেমপ্লের সময়, খেলোয়াড়রা বিভিন্ন প্ল্যাটফর্মে দাঁড়ান। যে প্ল্যাটফর্মটি পড়ে যায় তা উপলব্ধ প্ল্যাটফর্মগুলির মধ্য থেকে এলোমেলোভাবে নির্বাচিত হয়, নিশ্চিত করে যে এটি কখনও পূর্বে পড়ে যাওয়া প্ল্যাটফর্মের মতো নয়।
Rocket-এ, প্রতিটি রাউন্ডের ফলাফল (ক্র্যাশ মাল্টিপ্লায়ার) পূর্ব-উৎপন্ন হ্যাশ চেইন (প্রতি গেমে অনন্য) থেকে একটি পাবলিক হ্যাশ ব্যবহার করে নির্ধারিত হয়। ফলাফলটি একটি নির্ধারক ফাংশন ব্যবহার করে গণনা করা হয় যা হ্যাশকে একটি মাল্টিপ্লায়ারে রূপান্তরিত করে।
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
}
প্রতিটি খেলোয়াড় রাউন্ডের আগে হ্যাশ পান, এবং স্বাধীনভাবে যাচাই করতে পারেন যে ফলাফলটি প্রকাশিত সূত্রের সাথে মেলে, সম্পূর্ণ স্বচ্ছতা এবং প্রমাণযোগ্য ন্যায্যতা নিশ্চিত করে।
Battle-এ, একজন খেলোয়াড়কে মোট পুলে তাদের বাজির আনুপাতিক আকারের ভিত্তিতে বিজয়ী হিসেবে নির্বাচিত করা হয়। এর অর্থ বড় বাজি জেতার সম্ভাবনা বেশি দেয়।
আমরা একটি নির্ধারক এবং প্রমাণযোগ্যভাবে ন্যায্য নির্বাচন প্রক্রিয়া ব্যবহার করি:
১. [0.0, 1.0) পরিসরে একটি সিউডো-র্যান্ডম সংখ্যা তৈরি করতে একটি পাবলিক গেম হ্যাশ ব্যবহার করা হয়।
২. প্রতিটি খেলোয়াড়কে তাদের বাজির আনুপাতিক পরিসরের একটি অংশ বরাদ্দ করা হয় (যেমন একটি ২০% বাজি পরিসরের ২০% কভার করে)।
৩. র্যান্ডম সংখ্যাটি বিজয়ী অংশ নির্ধারণ করে, এবং এভাবে বিজয়ীকে।
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
}