Having trouble with a PRNG

classic Classic list List threaded Threaded
4 messages Options
JM
Reply | Threaded
Open this post in threaded view
|

Having trouble with a PRNG

JM
Hi all,

I'm using Sorbus's PRNG in the tetris game I'm writing (see his post titled Pseudo Random Number Generator in this project's Q&A folder) to pseudorandomly generate my tetris game pieces. However, when I run my code on the VM emulator, the PRNG always spits out the same number, and thus the generated game piece is always the same. Changing the seed value for the PRNG appears to make no difference; indeed, when I look at the relevant data registers in the VM emulator I can see that the PRNG always generates the same number. I assume the issue is with my code and not with Sorbus's but I am having trouble figuring out where I'm going wrong.

Here are the relevant details.

1. I have a class titled gamePiece that contains a constructor and a bunch of methods for the various tetris pieces. Here is the relevant code for that class:

class gamePiece {

        field int x, y, chosenType, current_orientation;

        /** constructs the active game piece */

        constructor gamePiece new(int xcoord, int ycoord, int initial_orientation, int input_type) {
                let x = xcoord;
                let y = ycoord;
                let chosenType = input_type;
                let current_orientation = initial_orientation;
                if (chosenType = 1) {
                        do drawBlock();
                        }
                if (chosenType = 2) {
                        do drawLinepiece();
                        }
                if (chosenType = 3) {
                        do drawLpiece();
                        }
                if (chosenType = 4) {
                        do drawPyramidpiece();
                        }
                return this;
                }

[various methods]

}

2. Another class, currently titled Testgame, constructs a game piece and, ideally, uses the PRNG to pseudorandomly select the piece type. Here's the relevant code from that class:

class Testgame {

        static int seed_value; //a class-level seed value. Will change throughout game so that with each new piece the seed value changes

        field gamePiece game_piece; //the piece to be rotated
        field int piece_type; //the type of the active game piece
        field int future_orientation; //the orientation the piece changes to with previous user input
        field int leftMovement, rightMovement; // indicator variables for leftward or rightward movement of the current game piece

        /**sets the initial seed*/
        function void initialSeed() {
                let seed_value = 425;
                return;
        }

        /** constructs a game piece for testing */
        constructor Testgame new() {
                do LCGRandom.setSeed(seed_value); //sets a seed value for LCGRandom to use
                let piece_type = LCGRandom.randRange(1,4); //pseudo-randomly generates a number between 1 and 4, inclusive
                let game_piece = gamePiece.new(242, 8, 1, piece_type);
                let future_orientation = 0; //initial state is to leave orientation unchanged
                let leftMovement = 0; //initial state is no lateral movement
                let rightMovement = 0; //initial state is no lateral movement
                return this;
                }

[various methods]

}

Right now I am manually changing the variable seed_value in the Testgame class and then recompiling my game files to see whether the PRNG will actually generate different numbers. As I mentioned above, so far I haven't managed to get different numbers with different seed values.

Any insight and/or hints are much appreciated. Also, I've copied the contents of Sorbus's PRNG below for easier reference:

/* LCGRandom.jack, released under the BSD 2-Clause License, also known as Simplified BSD or FreeBSD License"
 * Copyright (c) 2013, Rowan Limb
 * All rights reserved.
 * This software implements a PRNG based on Linear Congruential Generator (Schrage Method).
 * Based on method documented here: http://www.cems.uwe.ac.uk/~irjohnso/coursenotes/ufeen8-15-m/p1192-parkmiller.pdf
 * and using constants for A and M from  "Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure" by Pierre L'Ecuyer, 1999 (citeseer: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.1024)
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
*/

class LCGRandom {
    static int seed;
    static int A;
    static int M;
    static int Q;
    static int R;

    function void setSeed(int newSeed) {
        let seed = newSeed;
        if(seed=0) {
           let seed=1;
        }
        let A=219;
        let M=32749;
        let Q=M/A;
        let R=Utils.mod(M,A);
        return;
    }

    /* returns a random int in range 0..(M-1) inclusive */
    function int rand() {
        var int test;
        let test=(A*(Utils.mod(seed,Q)))-(R*(seed/Q));
        if(test<0) {
           let seed=test+M;
        }
        else {
           let seed=test;
        }
        return seed;
    }

    /* returns a random int in range low..high inclusive */
    function int randRange(int low, int high) {
       var int scale;
       let scale = (M / (high - low + 1));
       return (LCGRandom.rand() / scale) + low;
    }
}

/* Utils.jack, released under the BSD 2-Clause License, also known as Simplified BSD or FreeBSD License"
 * Copyright (c) 2013, Rowan Limb
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
*/

class Utils {

    /* returns a % b */
    function int mod(int a, int b) {
        var int d;
        var int r;
        let d = Math.divide(a,b);
        let r = a - (b * d);
        return r;
    }
}


Reply | Threaded
Open this post in threaded view
|

Re: Having trouble with a PRNG

ashort
Maybe I missed it, but you don't seem to be calling "initialSeed" anywhere.  Assuming that's true, then seed_value is never being initialized, which would explain a lot.

Once you do this, then the first random piece and all subsequent pieces will be random, but will follow the same pattern every time you run your game.  This would be bad, as Tetris will get boring and you will be able to memorize say, the first 20 pieces that fall at the start of every game.  

To avoid this, you should set your initial seed based on a truly random event, such as how long it takes for the user to press any key to continue (normally you would use the system clock, but we don't have that).  Something like this:


let continueStr = "Press any key to continue";
do Output.printString(continueStr);
do continueStr.dispose();
       
let randomSeed = 0;

// wait until a key is pressed down
while (Keyboard.keyPressed() = 0) {
     // Use the wait time to increment the randomSeed.
     // may overflow to neg number, but that's ok.
     let randomSeed = randomSeed + 1;  
}

// wait until key is released
while (~(Keyboard.keyPressed() = 0)) {
     // do nothing but wait
}

// seed the pseudo random number generator, using randomSeed
do LCGRandom.setSeed(randomSeed)

JM
Reply | Threaded
Open this post in threaded view
|

Re: Having trouble with a PRNG

JM
Thanks for pointing that out, ashort. I should have seen that!


For now, I just want to check to see that the PRNG is behaving as it should; that when the seed values change the chosen piece type should (most likely) change. Now that I am actually initializing the seed value, this is happening.

I may add in the code you suggested to provide the seed, but I may also just increment the initial seed value according to in-game user input (for example, whenever the user moves a piece left or right). I think doing this might comprise the "randomness" though (I saw a post in another thread claiming that resetting the seed values--and therefore constantly changing the given pseudorandom sequence that the PRNG generates--can mess with the underlying distribution of numbers).

Reply | Threaded
Open this post in threaded view
|

Re: Having trouble with a PRNG

ashort
I suggest you initialize the seed literally only once, using the randomSeed as in my example (or a fixed starting seed, for debugging). From then on in your app, the seed is adjusted automatically by the LCGRandom class handling the seed.  Every time you call rand() or randRange(), the seed is changed for you automatically.