النزاهة القابلة للإثبات

أدخل تفاصيل اللعبة للتحقق من نتائجك

الكود

اختر لعبة من الأعلى لفحص الكود المصدري الذي ينتج نتائجها.

import { createHmac } from 'crypto';

const UINT32_MAX = 0x100000000; // 2^32

/**
 * Generates an infinite deterministic stream of cryptographically secure
 * 32-bit unsigned integers derived from HMAC-SHA256.
 *
 * @param key - Secret cryptographic key (server seed). Must remain private until revealed.
 * @param message - Public input string (e.g., client seed, nonce, or domain label).
 *
 * @remarks
 * - Uses HMAC-SHA256 as a pseudorandom function (PRF) to produce a reproducible
 *   and verifiable sequence of numbers.
 * - `step` ensures each 32-bit block is derived from a unique input.
 * - Output is split into 32-bit chunks, suitable for unbiased integer or bit-level
 *   extraction.
 * - Bits are consumed starting from the Most Significant Bit (MSB) to preserve
 *   full numeric precision and maximum entropy.
 * - Fully deterministic: anyone with the same key and message can verify the results.
 */
function* getUint32Stream(key: string, message: string): Iterator<number> {
  let step = 0;

  while (true) {
    const hmac = createHmac('sha256', key);
    hmac.update(`${message}:${step}`);
    const hash = hmac.digest(); // 32 bytes (256 bits)

    for (let i = 0; i < hash.length; i += 4) {
      yield (
        (hash[i] << 24) |
        (hash[i + 1] << 16) |
        (hash[i + 2] << 8) |
        hash[i + 3]
      ) >>> 0; // force unsigned 32-bit
    }

    step++;
  }
}

/**
 * Converts a 32-bit random number into an unbiased integer within [0, range).
 *
 * @param uint32StreamFactory - Function that returns a stream of 32-bit integers.
 * @param range - The desired upper bound (exclusive) for the output.
 *
 * @remarks
 * - Uses rejection sampling to avoid modulo bias.
 * - Every integer within the range is equally likely.
 * - Critical for fair gameplay and provably fair verification.
 */
function getUnbiasedInt(uint32StreamFactory: () => Iterator<number>, range: number) {
  if (!Number.isInteger(range) || range <= 0) {
    throw new Error('Invalid range');
  }

  const maxAcceptable = UINT32_MAX - (UINT32_MAX % range);
  const stream = uint32StreamFactory();
  let next = stream.next();

  while (!next.done) {
    const value = next.value;
    if (value < maxAcceptable) {
      return value % range;
    }
    next = stream.next();
  }

  throw new Error('Failed to generate unbiased value: entropy stream exhausted');
}

function determineHouseBet(options: {
  difficulty: 'easy' | 'medium' | 'hard' | 'expert' | 'master';
  serverSeed: string;
  clientSeed: string;
  nonce: number;
  round: number;
}) {
  const towerConfig = {
    easy: {
      tileCount: 4,
      fairValueProduces: 'lose-index',
    },
    medium: {
      tileCount: 3,
      fairValueProduces: 'lose-index',
    },
    hard: {
      tileCount: 2,
      fairValueProduces: 'win-index',
    },
    expert: {
      tileCount: 3,
      fairValueProduces: 'win-index',
    },
    master: {
      tileCount: 4,
      fairValueProduces: 'win-index',
    },
  };

  const tileCount = towerConfig[options.difficulty].tileCount;
  const fairValueProduces = towerConfig[options.difficulty].fairValueProduces;

  const fairSelectedTileIndex = getUnbiasedInt(
    () =>
      getUint32Stream(
        options.serverSeed,
        `${options.clientSeed}:${options.nonce}:${options.round}:tower`
      ),
    tileCount
  );

  const result: string[] = [];

  for (let index = 0; index < tileCount; index++) {
    switch (fairValueProduces) {
      case 'win-index':
        result.push(index === fairSelectedTileIndex ? '✅' : '❌');
        break;
      default:
        result.push(index !== fairSelectedTileIndex ? '✅' : '❌');
    }
  }

  return result;
}

for (let i = 1 ; i <= 8; i++) {
  console.log(
    determineHouseBet({
      difficulty: 'master',
      clientSeed: 'tower-test',
      serverSeed: 'tower-test',
      nonce: 0,
      round: i,
    })
  )
}