changeset 784:1f152ca95658

Replace pytouhou.utils.random.Random with a Rust version libtouhou.Prng now supports having its seed generated randomly using the getrandom crate, and is now exposed to Python.
author Link Mauve <linkmauve@linkmauve.fr>
date Sun, 23 Nov 2025 12:48:03 +0100
parents ec1e06402a97
children f73e8524c045
files python/Cargo.toml python/src/lib.rs pytouhou/game/game.pxd pytouhou/game/game.pyx pytouhou/utils/random.pxd pytouhou/utils/random.pyx scripts/pytouhou utils/Cargo.toml utils/src/prng.rs
diffstat 9 files changed, 39 insertions(+), 88 deletions(-) [+]
line wrap: on
line diff
--- a/python/Cargo.toml
+++ b/python/Cargo.toml
@@ -14,6 +14,7 @@
 
 [dependencies]
 touhou-formats = "*"
+touhou-utils = { version = "*", path = "../utils" }
 pyo3 = "0.27"
 image = { version = "0.25", default-features = false, features = ["png"], optional = true }
 glob = "0.3.3"
--- a/python/src/lib.rs
+++ b/python/src/lib.rs
@@ -216,12 +216,43 @@
     }
 }
 
+/// A loader for Touhou files.
+#[pyclass(module = "libtouhou")]
+struct Prng {
+    inner: touhou_utils::prng::Prng,
+}
+
+#[pymethods]
+impl Prng {
+    #[pyo3(signature = (seed=None))]
+    #[new]
+    fn new(seed: Option<u16>) -> Prng {
+        let inner = touhou_utils::prng::Prng::new(seed);
+        Prng { inner }
+    }
+
+    fn rand_uint16(&mut self) -> u16 {
+        self.inner.get_u16()
+    }
+
+    fn rand_uint32(&mut self) -> u32 {
+        self.inner.get_u32()
+    }
+
+    fn rand_double(&mut self) -> f64 {
+        self.inner.get_f64()
+    }
+}
+
 #[pymodule]
 mod libtouhou {
     #[pymodule_export]
     use super::Loader;
 
     #[pymodule_export]
+    use super::Prng;
+
+    #[pymodule_export]
     use crate::audio::Audio;
 
     #[cfg(feature = "glide")]
--- a/pytouhou/game/game.pxd
+++ b/pytouhou/game/game.pxd
@@ -1,7 +1,6 @@
 from pytouhou.game.effect cimport Effect
 from pytouhou.game.player cimport Player
 from pytouhou.game.text cimport Text, NativeText
-from pytouhou.utils.random cimport Random
 
 cdef class Game:
     cdef public long width, height, nb_bullets_max, stage, rank, difficulty, difficulty_min, difficulty_max, frame
@@ -9,7 +8,7 @@
     cdef public object interface, boss, msg_runner
     cdef public dict texts
     cdef public object sfx_player, music
-    cdef public Random prng
+    cdef public object prng
     cdef public double continues
     cdef public Effect spellcard_effect
     cdef public tuple spellcard
--- a/pytouhou/game/game.pyx
+++ b/pytouhou/game/game.pyx
@@ -26,7 +26,7 @@
 cdef class Game:
     def __init__(self, players, long stage, long rank, long difficulty, bullet_types,
                  laser_types, item_types, long nb_bullets_max=0, long width=384,
-                 long height=448, Random prng=None, interface=None, hints=None,
+                 long height=448, prng=None, interface=None, hints=None,
                  bint friendly_fire=True):
         self.width, self.height = width, height
 
deleted file mode 100644
--- a/pytouhou/utils/random.pxd
+++ /dev/null
@@ -1,10 +0,0 @@
-cdef class Random:
-    cdef unsigned short seed
-    cdef unsigned long counter
-
-    cdef void set_seed(self, unsigned short seed) nogil
-    cdef unsigned short rewind(self) nogil
-
-    cpdef unsigned short rand_uint16(self)
-    cpdef unsigned int rand_uint32(self)
-    cpdef double rand_double(self)
deleted file mode 100644
--- a/pytouhou/utils/random.pyx
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-
-
-"""
-This file provides a pseudo-random number generator identical to the one used in
-Touhou 6: The Embodiment of Scarlet Devil.
-It is the only truly reverse-engineered piece of code of this project,
-as it is needed in order to retain compatibility with replay files produced by
-the offical game code.
-
-It has been reverse engineered from 102h.exe."""
-
-
-#TODO: maybe some post-processing is missing
-
-cimport cython
-from time import time
-
-
-@cython.final
-cdef class Random:
-    def __init__(self, long seed=-1):
-        if seed < 0:
-            seed = time()
-        self.set_seed(<unsigned short>(seed & 65535))
-
-
-    cdef void set_seed(self, unsigned short seed) nogil:
-        self.seed = seed
-        self.counter = 0
-
-
-    cdef unsigned short rewind(self) nogil:
-        """Rewind the PRNG by 1 step. This is the reverse of rand_uint16.
-        Might be useful for debugging purposes.
-        """
-        x = self.seed
-        x = (x >> 2) | ((x & 3) << 14)
-        self.seed = ((x + 0x6553) & 0xffff) ^ 0x9630
-        self.counter -= 1
-        return self.seed
-
-
-    cpdef unsigned short rand_uint16(self):
-        # 102h.exe@0x41e780
-        x = ((self.seed ^ 0x9630) - 0x6553) & 0xffff
-        self.seed = (((x & 0xc000) >> 14) | (x << 2)) & 0xffff
-        self.counter += 1
-        return self.seed
-
-
-    cpdef unsigned int rand_uint32(self):
-        # 102h.exe@0x41e7f0
-        a = self.rand_uint16() << 16
-        a |= self.rand_uint16()
-        return a
-
-
-    cpdef double rand_double(self):
-        # 102h.exe@0x41e820
-        return self.rand_uint32() / <double>0x100000000
--- a/scripts/pytouhou
+++ b/scripts/pytouhou
@@ -72,7 +72,7 @@
 from pytouhou.ui.gamerunner import GameRunner
 from pytouhou.game import NextStage, GameOver
 from pytouhou.formats.t6rp import T6RP, Level
-from pytouhou.utils.random import Random
+from libtouhou import Prng as Random
 from pytouhou.formats.hint import Hint
 from pytouhou.network import Network
 
--- a/utils/Cargo.toml
+++ b/utils/Cargo.toml
@@ -10,3 +10,4 @@
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+getrandom = "0.3.4"
--- a/utils/src/prng.rs
+++ b/utils/src/prng.rs
@@ -8,7 +8,9 @@
 
 impl Prng {
     /// Create a new pseudo-random number generator from this seed.
-    pub fn new(seed: u16) -> Prng {
+    pub fn new(seed: Option<u16>) -> Prng {
+        // TODO: Maybe add a getrandom::u16() to getrandom instead?
+        let seed = seed.unwrap_or_else(|| getrandom::u32().unwrap() as u16);
         Prng {
             seed,
         }