0

I am using swift and want to have a number of duplicatable patterns throughout my game.

Ideally I would have some sort of shared class that worked sort of like this (this is sort of pseudo-Swift code):

class RandomNumberUtility {
    static var sharedInstance = RandomNumberUtility()
    var random1 : Random()
    var random2 : Random()

    func seedRandom1(seed : Int){
        random1 = Random(seed)
    }
    func seedRandom2(seed : Int){
        random2 = Random(seed)
    }
    func getRandom1() -> Int {
        return random1.next(1,10)
    }
    func getRandom2() -> Int {
        return random2.next(1,100)
    }
}

Then, to begin the series, anywhere in my program I could go like this:

RandomNumberUtility.sharedInstance.seedNumber1(7)
RandomNumberUtility.sharedInstance.seedNumber2(12)

And then I would know that (for example) the first 4 times I called

RandomNumberUtility.sharedInstance.getRandom1()

I would always get the same values (for example: 6, 1, 2, 6) This would continue until at some point I seeded the number again, and then I would either get the exact same series back (if I used the same seed), or a different series (if I used a different seed).

And I want to have multiple series of numbers (random1 & random2) at the same time.

I am not sure how to begin to turn this into an actual Swift class.

user49hc
  • 121
  • 7

2 Answers2

0

Here is a possible implementation. It uses the jrand48 pseudo random number generator, which produces 32-bit numbers. This PRNG is not as good as arc4random(), but has the advantage that all its state is stored in a user-supplied array, so that multiple instances can run independently.

struct RandomNumberGenerator {
    // 48 bit internal state for jrand48()
    private var state : [UInt16] = [0, 0, 0]

    // Return pseudo-random number in the range 0 ... upper_bound-1:
    mutating func next(upper_bound: UInt32) -> UInt32 {

        // Implementation avoiding the "module bias" problem,
        // taken from: http://stackoverflow.com/a/10989061/1187415,
        // Swift translation here: http://stackoverflow.com/a/26550169/1187415

        let range = UInt32.max - UInt32.max % upper_bound
        var rnd : UInt32
        do {
            rnd = UInt32(truncatingBitPattern: jrand48(&state))
        } while rnd >= range

        return rnd % upper_bound
    }

    mutating func seed(newSeed : Int) {
        state[0] = UInt16(truncatingBitPattern: newSeed)
        state[1] = UInt16(truncatingBitPattern: (newSeed >> 16))
        state[2] = UInt16(truncatingBitPattern: (newSeed >> 32))
    }
}

Example:

var rnd1 = RandomNumberGenerator()
rnd1.seed(7)
var rnd2 = RandomNumberGenerator()
rnd2.seed(12)

println(rnd1.next(10)) // 2
println(rnd1.next(10)) // 8
println(rnd1.next(10)) // 1

println(rnd2.next(10)) // 6
println(rnd2.next(10)) // 0
println(rnd2.next(10)) // 5

If rnd1 is seeded with the same value as above then it produces the same numbers again:

rnd1.seed(7)
println(rnd1.next(10)) // 2
println(rnd1.next(10)) // 8
println(rnd1.next(10)) // 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
0

What you need is a singleton that generates pseudo-random numbers and make sure all your code that need a random number call via this class. The trick is to reset the seed for each run of your code. Here is a simple RandomGenerator class that will do the trick for you (it's optimized for speed which is a good thing when writing games):

import Foundation

// This random number generator comes from: Klimov, A. and Shamir, A.,
// "A New Class of Invertible Mappings", Cryptographic Hardware and Embedded
// Systems 2002, http://dl.acm.org/citation.cfm?id=752741
//
// Very fast, very simple, and passes Diehard and other good statistical
// tests as strongly as cryptographically-secure random number generators (but
// is not itself cryptographically-secure).

class RandomNumberGenerator {

    static let sharedInstance = RandomNumberGenerator()

    private init(seed: UInt64 = 12347) {
        self.seed = seed
    }

    func nextInt() -> Int {
        return next(32)
    }

    private func isPowerOfTwo(x: Int) -> Bool { return x != 0 && ((x & (x - 1)) == 0) }

    func nextInt(max: Int) -> Int {
        assert(!(max < 0))

        // Fast path if max is a power of 2.
        if isPowerOfTwo(max) {
            return Int((Int64(max) * Int64(next(31))) >> 31)
        }

        while (true) {
            var rnd = next(31)
            var val = rnd % max
            if rnd - val + (max - 1) >= 0 {
                return val
            }
        }
    }

    func nextBool() -> Bool {
        return next(1) != 0
    }

    func nextDouble() -> Double {
        return Double((Int64(next(26)) << 27) + Int64(next(27))) /
            Double(Int64(1) << 53)
    }

    func nextInt64() -> Int64 {
        let lo = UInt(next(32))
        let hi = UInt(next(32))
        return Int64(UInt64(lo) | UInt64(hi << 32))
    }

    func nextBytes(inout buffer: [UInt8]) {
        for n in 0..<buffer.count {
            buffer[n] = UInt8(next(8))
        }
    }

    var seed: UInt64 {
        get {
            return _seed
        }
        set(seed) {
            _initialSeed = seed
            _seed = seed
        }
    }

    var initialSeed: UInt64 {
        return _initialSeed!
    }

    private func randomNumber() -> UInt32 {
        _seed = _seed &+ ((_seed &* _seed) | 5)
        return UInt32(_seed >> 32)
    }

    private func next(bits: Int) -> Int {
        assert(bits > 0)
        assert(!(bits > 32))
        return Int(randomNumber() >> UInt32(32 - bits))
    }

    private var _initialSeed: UInt64?
    private var _seed: UInt64 = 0
}
John Difool
  • 5,572
  • 5
  • 45
  • 80
  • I made some performance tests, and it turned out that this is slower than using `jrand48()`. I cannot explain why, and I cannot judge the quality of both methods, but (unless I made some error) generating 1,000,000 random numbers in the range [0,1000) took 6 ms with my suggested method and 21 ms with your method (compiled in Release mode on a Mac Book Pro). – Martin R Jun 01 '15 at 05:37
  • If the upper bound is a power of two (1024 instead of 1000) then your's is faster: 3 ms (your method) vs 6 ms (my method). – Martin R Jun 01 '15 at 05:46
  • The while(true) to catch out of range values may be causing this. That's a hack. I couldn't think of something better than retrying till it felt inside the boundaries. In that case, it's probably best to request a double 0..<1 and multiply by the upper bound. – John Difool Jun 01 '15 at 05:50