Parcourir la source

shuffling improvements

Llandy Riveron Del Risco il y a 4 mois
Parent
commit
c9b0f30026

+ 30 - 28
Assets/Scripts/Script/ContinuousController.cs

@@ -4,6 +4,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Security.Cryptography;
 using System.Threading.Tasks;
 using UnityEngine;
 using UnityEngine.Events;
@@ -494,9 +495,9 @@ public class ContinuousController : MonoBehaviour
     public async void Init()
     {
         Application.targetFrameRate = 60;
-        int random = RandomUtility.getRamdom();
-        UnityEngine.Random.InitState(random);
-        Debug.Log($"Game Initialize - random number sequence initialization,InitState:{random}");
+        long random = RandomUtility.GetSecureRandom();
+        GameRandom.Seed(random);
+        Debug.Log($"Game Initialize - random number sequence initialization, GameRandom.Seed:{random}");
 
         Sprite reverseCardSprite = await StreamingAssetsUtility.GetSprite("card_back_main");
 
@@ -1135,9 +1136,9 @@ public class ContinuousController : MonoBehaviour
 
         isAI = false;
 
-        int random = RandomUtility.getRamdom();
-        UnityEngine.Random.InitState(random);
-        Debug.Log($"random number sequence initialization, InitState:{random}");
+        long random = RandomUtility.GetSecureRandom();
+        GameRandom.Seed(random);
+        Debug.Log($"random number sequence initialization, GameRandom.Seed:{random}");
 
         var unload = SceneManager.UnloadSceneAsync("BattleScene");
         yield return unload;
@@ -1283,19 +1284,19 @@ public class ContinuousController : MonoBehaviour
     public bool DoneSetRandom { get; set; } = false;
     public bool CanSetRandom { get; set; } = false;
     [PunRPC]
-    public void SetRandom(int random)
+    public void SetRandom(long random)
     {
         StartCoroutine(SetRandomCoroutine(random));
     }
 
-    IEnumerator SetRandomCoroutine(int random)
+    IEnumerator SetRandomCoroutine(long random)
     {
         yield return new WaitWhile(() => !CanSetRandom);
 
-        UnityEngine.Random.InitState(random);
+        GameRandom.Seed(random);
         DoneSetRandom = true;
 
-        Debug.Log($"random number sequence initialization,InitState:{random}");
+        Debug.Log($"random number sequence initialization, GameRandom.Seed:{random}");
     }
 
 
@@ -1304,17 +1305,18 @@ public class ContinuousController : MonoBehaviour
 #region Manage random numbers
 public static class RandomUtility
 {
-    private static System.Random random;
-    public static int getRamdom()
+    /// <summary>
+    /// Generates a cryptographically secure 64-bit random seed.
+    /// Uses OS entropy pool via System.Security.Cryptography.
+    /// </summary>
+    public static long GetSecureRandom()
     {
-        int _max = 1500000000;
-
-        if (random == null)
+        byte[] bytes = new byte[8];
+        using (var rng = RandomNumberGenerator.Create())
         {
-            random = new System.Random((int)DateTime.Now.Ticks);
+            rng.GetBytes(bytes);
         }
-
-        return random.Next(0, _max);
+        return BitConverter.ToInt64(bytes, 0);
     }
 
     #region IsSucceedProbability(float Probability)
@@ -1330,7 +1332,7 @@ public static class RandomUtility
             return false;
         }
 
-        float random = UnityEngine.Random.Range(0f, 1f);
+        float random = GameRandom.Range(0f, 1f);
 
         if (random <= Probability)
         {
@@ -1347,17 +1349,17 @@ public static class RandomUtility
         List<CEntity_Base> CardDatas = new List<CEntity_Base>();
         CardDatas.AddRange(DeckCards);
 
-        // The initial value of the integer n is the number of cards in the deck
+        // Fisher-Yates shuffle using GameRandom (Xoshiro256**)
         int n = CardDatas.Count;
 
         while (n > 0)
         {
             n--;
 
-            // Random index from 0 to i (inclusive)
-            int k = UnityEngine.Random.Range(0, n + 1);
+            // Random index from 0 to n (inclusive) — Range takes exclusive max
+            int k = GameRandom.Range(0, n + 1);
 
-            // Swap elements at indices i and k
+            // Swap elements at indices n and k
             CEntity_Base temp = CardDatas[n];
             CardDatas[n] = CardDatas[k];
             CardDatas[k] = temp;
@@ -1372,17 +1374,17 @@ public static class RandomUtility
         List<CardSource> CardDatas = new List<CardSource>();
         CardDatas.AddRange(DeckCards);
 
-        // The initial value of the integer n is the number of cards in the deck
+        // Fisher-Yates shuffle using GameRandom (Xoshiro256**)
         int n = CardDatas.Count;
 
         while (n > 0)
         {
             n--;
 
-            // Random index from 0 to i (inclusive)
-            int k = UnityEngine.Random.Range(0, n + 1);
+            // Random index from 0 to n (inclusive) — Range takes exclusive max
+            int k = GameRandom.Range(0, n + 1);
 
-            // Swap elements at indices i and k
+            // Swap elements at indices n and k
             CardSource temp = CardDatas[n];
 
             if (!temp.IsFlipped)
@@ -1392,7 +1394,7 @@ public static class RandomUtility
                 if(temp.Owner.SecurityCards.Contains(temp))
                     GManager.OnSecurityStackChanged?.Invoke(temp.Owner);
             }
-                
+
 
             CardDatas[n] = CardDatas[k];
             CardDatas[k] = temp;

+ 124 - 0
Assets/Scripts/Script/GameRandom.cs

@@ -0,0 +1,124 @@
+using System;
+
+/// <summary>
+/// Deterministic PRNG for game-critical randomness (deck shuffling, sync keys, coin flips).
+/// Uses Xoshiro256** with SplitMix64 seeding for a 256-bit internal state.
+/// Both clients seed with the same value to produce identical sequences.
+/// </summary>
+public static class GameRandom
+{
+    private static ulong s0, s1, s2, s3;
+    private static bool initialized = false;
+
+    /// <summary>
+    /// Seed the PRNG with a 64-bit value. Uses SplitMix64 to expand
+    /// the seed into 256 bits of Xoshiro256** state.
+    /// </summary>
+    public static void Seed(long seed)
+    {
+        ulong s = (ulong)seed;
+        s0 = SplitMix64(ref s);
+        s1 = SplitMix64(ref s);
+        s2 = SplitMix64(ref s);
+        s3 = SplitMix64(ref s);
+
+        // Ensure state is not all-zero (degenerate case)
+        if (s0 == 0 && s1 == 0 && s2 == 0 && s3 == 0)
+            s0 = 1;
+
+        initialized = true;
+    }
+
+    /// <summary>
+    /// Returns a random int in [min, exclusiveMax) with no modulo bias.
+    /// Uses rejection sampling.
+    /// </summary>
+    public static int Range(int min, int exclusiveMax)
+    {
+        if (!initialized)
+            throw new InvalidOperationException("GameRandom has not been seeded. Call GameRandom.Seed() first.");
+
+        if (exclusiveMax <= min)
+            return min;
+
+        uint range = (uint)(exclusiveMax - min);
+
+        if (range == 1)
+            return min;
+
+        // Rejection sampling to eliminate modulo bias
+        // Threshold: values below this are in the biased zone
+        uint threshold = (uint)((0x100000000UL - range) % range);
+
+        uint raw;
+        do
+        {
+            raw = NextUInt32();
+        } while (raw < threshold);
+
+        return min + (int)(raw % range);
+    }
+
+    /// <summary>
+    /// Returns a random float in [min, max).
+    /// </summary>
+    public static float Range(float min, float max)
+    {
+        if (!initialized)
+            throw new InvalidOperationException("GameRandom has not been seeded. Call GameRandom.Seed() first.");
+
+        // Use 24 bits of randomness for float precision (IEEE 754 single has 23-bit mantissa)
+        float t = (NextUInt32() >> 8) * (1.0f / (1 << 24));
+        return min + (max - min) * t;
+    }
+
+    /// <summary>
+    /// Returns true with the given probability [0..1].
+    /// </summary>
+    public static bool Probability(float p)
+    {
+        if (p >= 1f) return true;
+        if (p <= 0f) return false;
+        return Range(0f, 1f) <= p;
+    }
+
+    // --- Internal: Xoshiro256** ---
+
+    private static ulong NextState()
+    {
+        // xoshiro256** result calculation
+        ulong result = RotateLeft(s1 * 5, 7) * 9;
+
+        ulong t = s1 << 17;
+
+        s2 ^= s0;
+        s3 ^= s1;
+        s1 ^= s2;
+        s0 ^= s3;
+
+        s2 ^= t;
+        s3 = RotateLeft(s3, 45);
+
+        return result;
+    }
+
+    private static uint NextUInt32()
+    {
+        return (uint)(NextState() >> 32);
+    }
+
+    private static ulong RotateLeft(ulong x, int k)
+    {
+        return (x << k) | (x >> (64 - k));
+    }
+
+    // --- Internal: SplitMix64 (for seed expansion) ---
+
+    private static ulong SplitMix64(ref ulong state)
+    {
+        ulong z = (state += 0x9E3779B97F4A7C15UL);
+        z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
+        z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
+        return z ^ (z >> 31);
+    }
+}

+ 1 - 2
Assets/Scripts/Script/PhotonWaitController.cs

@@ -225,8 +225,7 @@ public class PhotonWaitController : MonoBehaviour
         {
             key += "_" + PhotonNetwork.CurrentRoom.Name;
         }
-        //TODO Currently Disabled For Alpha Testing To Fix Xross Issues
-        key += "_" + UnityEngine.Random.Range(0, 9999999).ToString();
+        key += "_" + GameRandom.Range(0, 9999999).ToString();
         //int synchronizedSeed = PhotonNetwork.CurrentRoom.Name.GetHashCode() + waitCount;
         //System.Random random = new System.Random(synchronizedSeed);
         //key += "_" + random.Next(0, 9999999).ToString();

+ 2 - 2
Assets/Scripts/Script/TurnStateMachine.cs

@@ -224,7 +224,7 @@ public class TurnStateMachine : MonoBehaviourPunCallbacks
         #region 乱数列初期化
         if (PhotonNetwork.IsMasterClient)
         {
-            ContinuousController.instance.GetComponent<PhotonView>().RPC("SetRandom", RpcTarget.All, RandomUtility.getRamdom());
+            ContinuousController.instance.GetComponent<PhotonView>().RPC("SetRandom", RpcTarget.All, RandomUtility.GetSecureRandom());
         }
 
         yield return new WaitWhile(() => !ContinuousController.instance.DoneSetRandom);
@@ -256,7 +256,7 @@ public class TurnStateMachine : MonoBehaviourPunCallbacks
         }
 
         #region Deciding whether to attack first or last
-        gameContext.TurnPlayer = gameContext.PlayerFromID(UnityEngine.Random.Range(0, 2));
+        gameContext.TurnPlayer = gameContext.PlayerFromID(GameRandom.Range(0, 2));
 
         #region get first player from room custom property
         int firstPlayerId = -1;