Shop OBEX P1 Docs P2 Docs Learn Events
Randomize Methods? — Parallax Forums

Randomize Methods?

JBWolfJBWolf Posts: 405
edited 2013-12-24 12:24 in Propeller 1
Hello,
I am working on a lighting project with long strips of rgb led's.
I've programmed lots of different display methods, but right now they are all named and so I have to manually write out the method order.
I would like to randomize this, and the only way I can think to randomize calling the different methods is to number them instead of using names so I can make calls using a variable created from a random integer.
I.E... coginit(8, randomvariable(A,B,C), @stack[0])

Does someone know a more organized way?

Thanks and Happy Holidays :)

Comments

  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 01:01
    JBWolf,

    Sounds like a job for a "case" statement. Basically: Have a repeat loop that: Gets a random number from somewhere. Uses it in a case statement to select your display method. The display method returns after a while and you go round and select another one.

    One general outline is like so:
    pub method1
      ' Flash some LEDS
    
    pub method2
      ' Flash some LEDS
    
    
    pub method3
      ' Flash some LEDS
    
    
    pub random | r
      'calculate random r
      return r
    
    
    pub main | x 
      repeat
        x := random
        case x
          1:
            method1
          2:
            method2
          3:
            method3
    
  • JBWolfJBWolf Posts: 405
    edited 2013-12-23 01:21
    oh that is a good idea too.
    I will probably go with that unless another solution is presented.... would be a pain renaming and documenting the 70+ methods I am using.
    The only reason I am wary to use it, is because I would need to make more than 70 case statements.

    What about putting the names in a DAT section?
    Would storing them as byte work properly for a method call?
    i.e....

    coginit(8, methods[randvar](A, B, C), @stack[0])

    DAT
    Methods BYTE "Method1", "Method2", "Method3", .....
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 01:46
    JBWolf,

    I'm not really sure what you are suggesting. If you have an outline of some code, that compiles, to show us that would be good.

    As far as I know there is no way to call methods via a variable or via an array of variables. (Well, I suspect some gurus here have hacks to do that but it's not "normal" Spin usage.

    The only way I can see to do it is via "case" or a bunch of "if"s one after the other.
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 01:53
    Except of course if all your display "methods" were actually written in PASM.

    In that case you could probably arrange to start different bits of PASM in a COG just by looking up their address in an array and calling COGINIT with it.

    There is possibly another approach.

    Sounds to me like you have all your light sequences hard coded as Spin statements. It might be possible to define the sequences in some form of data structure and only have a single method that "plays" the sequence. Basically your display method becomes an interpreter of the display sequence format you have defined. Then you only have to point that "play" method at different sequence data.
  • JBWolfJBWolf Posts: 405
    edited 2013-12-23 02:03
    yes they are all in spin.
    They are individual rgb LED's with integrated WS2811 chip.

    here is one method as an example, it displays 255 colors gradually fading across all led's in the string:
    pub rainbow2(secs,ms) | color, position, brightness
    
    Brightness := 255     ' Max brightness = 255
    
    repeat (secs / 4)
      repeat color from 0 to 255                                   ' Increment through 255 colors
        repeat position from 0 to (LEDcount -1)                    ' Increment through all positions
           strip.set(position, wheelx(((position * 256 / strip.num_pixels) + color) & $FF, brightness))
        pause(ms / 10)
    

    I have tried using DAT which didnt work.
    Going to go with case for now
  • dMajodMajo Posts: 855
    edited 2013-12-23 02:39
    Heater. wrote: »
    JBWolf,

    I'm not really sure what you are suggesting. If you have an outline of some code, that compiles, to show us that would be good.

    As far as I know there is no way to call methods via a variable or via an array of variables. (Well, I suspect some gurus here have hacks to do that but it's not "normal" Spin usage.

    The only way I can see to do it is via "case" or a bunch of "if"s one after the other.

    I am not sure but if IIRC that was a way to use spin objects in array published somewhere in the forums and/or obex. perhaps this can work also for methods. At the end an object can be a single method.
  • JBWolfJBWolf Posts: 405
    edited 2013-12-23 02:47
    ok I guess the only other question I have is... Since I am randomizing the display order, I may get repeated results.
    I would like to display all of the methods once without repeating any one twice, until all in the list have been used once.

    I can think of one way to do this, but seems absurdly archaic to me.
    each method including the main loop has its own counter variable just for this purpose.
    The main loop on the first cycle of running all methods has a counter variable equaling 1... on the second, 2 and so on... this is only incremented when all methods have been used once (we'll call this variable "LoopCount").
    When a specific display method is run (determined by random variable in main loop), +1 is added to that specific methods dedicated counter variable (we'll call these variables "MethodCount1", "MethodCount2" and so on).
    The main loop then generates a new random number, but before running the correlated method, the main loop compares the "LoopCount" against that methods "MethodCountX", if they are equal, then it has already been run and a new random number should be generated.
    I know this will work, but seems a terrible way to accomplish the task.
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 03:14
    Sounds like you need the Fisher-Yates Shuffle Algorithm.

    Basically let's say you have 10 different things to do. Number them. Put those numbers in an array. Apply the F-Y algorthm.

    You can find a description of Fisher-Yates here : http://en.wikipedia.org/wiki/Fisher–Yates_shuffle

    There you will find a few different versions to consider and play with. They are all very simple.

    One issue, if you are very fussy, is that having played through everything at random and once only, as you require, you may end up starting all over again and occasionally picking as the first sequence to play the one the was played last on the previous cycle.

    Be careful picking your random numbers. It's easy to introduce I bias when generating a random number within some desired range. You may or may not care about that.
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 03:52
    How perfect do you want this to be?

    If you are shuffling 70 odd different things you ideally want a really good random number generator otherwise there will be a bias in the results.

    Spin's built in random operator is terrible. It's a pseudo random number generator which:
    a) Fails the most basic statistical tests of randomness.
    b) Has a very short cycle before it repeats itself again.

    What this means is that your shuffling will not be evenly distributed because of a) and your shuffling won't generate all the possible permutations because of b).

    I would suggest you use Chip's "Real Random" object that generates randomness from electrical activity in the Propellers phased lock loops. I think Real Random is in the OBEX somewhere.

    That only leaves the problems of generating unbiased random numbers in the ranges required and perhaps ensuring that one shuffle does not end with the same thing that the next shuffle begins with.


    For fun you might want to check out JKISS32. A very long sequence and statistically sound random number generator that can be seeded with Real Random.
  • JBWolfJBWolf Posts: 405
    edited 2013-12-23 06:52
    wow heater you are an encyclopedia :)
    Indispensable for amateur programmers like myself, I sincerely appreciate the help!
    I will definitely check those out

    I created a random number generator to use with an effect I called 'sparkle' which quickly flashes random led's white, no idea just how many repeats I get, but the visual result turned out very nice.
    After writing it, I watched closely on the led chain to make sure that there weren't certain sections hat stuck out as being always dark or excessively used. Looked like they were all being used including the first and last in the chain, and I didnt notice any patterns.

    Here is the code:
    rval := 3 
    
      repeat
       rval := (++rval * cnt)
       randm := ?rval // (LEDcount -1)                    ' creates random number - max value of 71
    
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 09:58
    JBWolf,

    "Encyclopedia", if only. You just happen to have hit on a seam of useful information in the sentiments of my mind that is not too deep to have been forgotten yet:)

    Now, we have to fix that modulus operator you are using to generate random numbers within a range. Problem is it produces a bias in the resulting sequence, some numbers will occur more than others.

    Consider: If your random number generator produces numbers between 0 and 15 with equal probability. And then you do a modulus 10 so as to get numbers 0 to 9. Then numbers 0 to 9 from the generator will become 0 to 9 in your output. BUT numbers 10 to 15 from the generator will become 0 to 5 in your output. You then have more possibilities of getting 0 to 5 than you have 6 to 15. Not good.

    One can get obsessive about random number generation, as you see:)
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-12-23 10:42
    I do lots of randomized show programming, and that usually entails reading show data from a uSD. I pick a random number, make sure it's available, then create a file name from it to open and play. This strategy is now being used with the EFX-TEK HC-8+ by Legoland for their animated displays. Granted, they usually only have a few animations, but the process is still important.

    I would suggest you keep things easy at first. With 32 possiblities you can use a single long to keep track of everything. Generate a random # between 0 and 31, check that bit in your playlist variable, and if it was clear, play that routine. When played, mark it. When all bits in the playlist are set, clear it and start again. If the bit was already set, then run the randomizer again until you land on a clear bit.

    Of course you can use multiple playlist variables, but I think it's best to get the process working with something a little more manage.

    On a client project I had to randomize the playback of 600 audio files and maintain them between power outages. In this case I used an array of 600 bytes and when I file was played I marked the byte in RAM and wrote it to the EEPROM as well -- that way the next reboot would auto-populate the array. I also kept a counter variable (save to EE the same way) so I would have to scan 600 bytes to see if all were non-zero.

    BTW... for my random projects I use ReadRandom (by Chip) to seed Heater's random object. After the Heater's object is seeded I can stop RealRandom to recover a cog. It's only two additional lines of code in the start-up sequence and ensures the customer doesn't get the same sequence every start-up.
    var
    
      long  playlist
    
    
    pub main | seq, last
    
      rr.start
      prng.seed(rr.random, rr.random, rr.random, rr.random, rr.random)
      rr.stop
    
      last := -1
    
      repeat
        repeat
          seq := (prng.random >> 1) // 32                           ' randomize sequence #, 0..31
          ifnot (seq == last)                                       ' if not the last sequence played
            ifnot (playlist & (1 << seq))                           ' if not run in this cycle
              quit                                                  ' escape from randomizer loop
    
        last := seq                                                 ' mark for next loop
        playlist |= (1 << seq)                                     
        if (playlist == $FFFF_FFFF)                                 ' is playlist done?
          playlist := 0                                             ' reset
         
        play_the_sequence(seq)
    


    The method called play_the_sequence would hold the mechanism to jump to the appropriate sequence routine -- I think case is best.

    Here's a recent project using code like this: In the foreground of this photo is Limp Bizkit guitarist Wes Borland in a costume made by Steve Wang's shop. It uses 300 WS2801 LEDs that are controlled by a Propeller QuickStart with an output board I designed. There is quite a lot of "random" code in this dude -- thought it's not as exciting as we would like because we have to manage power to ensure that it stays lit for the entirety of a 2+ hour show (using a single 3300mAH LiPo).

    lb-20.jpg
    1024 x 683 - 73K
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 11:07
    JonnyMac,

    That was some cool gig.

    Are you telling me that some derived version of my JKISS32 object + Real Random is playing live on stage there ?

    I'm overwhelmed!

    Do you have a link to the vid? I'm in need of some counter Christmas music!
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-12-23 11:44
    Yes, indeed; your code and mine is on the Limb Bizkit tour.

    The only video I'm aware of is from the Riot Games championship. I had dinner with Steve and his crew on Friday and Steve told me Wes has posted video from the tour, but I have yet to find it. Here's a link to the first public appearance of that costume (Wes appears at the 5-minute mark).

    -- http://www.youtube.com/watch?feature=player_detailpage&v=pO5P7-LxzTE

    In this case, we ran a specific (monochrome) sequence so there is no randomizing. There are multiple shows in the tour code and Wes can change to another show by pressing a button on the back the helmet (left side, as that arm is free of costume).

    BTW... the prng object I use is your code; I'm just a nut about formatting, and as I knew I would be sharing project code that contained this, I re-formatted your code to my liking -- with full attribution as you can see in the attachment.
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 12:14
    JonnyMac,

    I have no idea what LoL is (as opposed to "LOL", LOL) and I have no idea what a Limb Bizkit might be. But that looks like an awesome event.

    No attribution required, after all I only ported JKISS32 from David Jones to Spin, who adapted it from the work of the late great Professor George Marsaglia.
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-12-23 12:39
    LoL == League of Legends == the most popular online game in the world today. That event was the world championship where four teenagers from Korea split a $2 millon prize. That's right, $2 million for playing and winning a video game. During the pre-show festivities we learned that these kids have contracts and earn $250K/year to play for their sponsor.

    Limp Bizkit is an American band; very popular world-wide. They play a mix of heavy metal with rap. It's an interesting combo but not really what I would listen to all the time (prefer classics like Led Zeppelin, Stones, Hendrix, Aerosmith, et al).

    Steve's shop is building another LoL game character display. I was over in his shop one night last week using a Propeller (EFX-TEK HC-8+) and some high-power amber and red LEDs to create the look of fire using steam and light. Your random object is used is in that, too!
  • Heater.Heater. Posts: 21,230
    edited 2013-12-23 12:54
    It's hard to keep up. Last time I played a video game it was Starglider from Argonaut Software in 1986 on an Atari ST 520. When I got up to 100,000 points the game ranked me as "cheat". That was the end of computer gaming for me!

    Limb Bizkit seem to have a good idea, rap needs something to make it interesting...anything.
  • JBWolfJBWolf Posts: 405
    edited 2013-12-24 05:19
    *** EDIT *** - nevermind this specifc question, I just checked out the realrandom object and found it has an example of exactly what im asking. its using a long address pointer from the random number generator method which was launched into a new cog, then a local method to get that value and return it.


    quick off topic Q...
    I know about using the 'result' command for returning a value from a method.... but what about if I want to get a value back from a non TSR cog?
    I have used long addresses many times to do this, but the new cog is just a simple one time run to get a random number when needed. Do I have to use a long address, or can I do something like:
    ReturnValue := coginit(8, methodname(variable), @stack)

    The book says if i were to use that code with 'cognew' in place of 'coginit', that 'returnvalue' would simply equal the cog number that was launched... but it doesnt say if the same happens with coginit.
    Seems to me that since I used coginit, i already know what cog number was launched, so 'returnvalue' might work with a 'result' in the method.

    would this code return like I want?:
    PUB Main | ReturnValue
    ReturnValue := coginit(8, methodname(variable), @stack)
    
    PUB Methodname(variable)
    result := 2
    
  • JBWolfJBWolf Posts: 405
    edited 2013-12-24 05:30
    The RealRandom object looks great, pretty complex with the ASM for my level of understanding.

    What I do notice is that this looks like it can generate a very large number.
    How can I modify this to have a maximum value? I'll probably never need a number value more than 100 for this particular project.
    But at the moment i need a max value of 30.. then later i'll be increasing this to between 75 and 100.

    Should I just do this?
    RandNum := (rr.random //30)
  • Heater.Heater. Posts: 21,230
    edited 2013-12-24 05:42
    The quick and dirty way everyone does this is to use the modulus operator.

    myRandom := Random MOD (myRandomRange)

    Where:
    Random is the big number coming out of a random number generator, in the range zero to something huge.
    myRandomRange is the range over which I want random numbers. So for example for an output range zero to nine, which is ten numbers, myRandomRange would be 10.
    myRandom is the result we are going to use.

    This works at providing numbers in range but does suffer from bias as explained above. Unless the range you want is a power of two.
  • Heater.Heater. Posts: 21,230
    edited 2013-12-24 05:51
    If you use the JKISS32 object it starts a COG to get some real randomness, "entropy". It then stops that COG. The entropy is then used seed the JKISS32 pseudo random number generator. After that you only need to call KISS32.random to get high quality random numbers. This saves wasting a COG on generating random numbers. The sequence coming out of JKISS32 only repeats after 2 to the power 121 numbers. It has very good statistical qualities of randomness.
  • JonnyMacJonnyMac Posts: 9,107
    edited 2013-12-24 07:56
    The book says if i were to use that code with 'cognew' in place of 'coginit', that 'returnvalue' would simply equal the cog number that was launched... but it doesnt say if the same happens with coginit.

    With cognew you are in fact requesting a cog if one is free. If the call was successful, you'll get the cog number 0..7 back; if there were no available cogs you get -1 back. This is why you see +1 in a lot of programs at the end of the cognew line: it promotes the result such that it can be evaluated as true or false (0..7 becomes 1..8 [true], -1 becomes 0 [false]).

    With coginit you are not asking for a cog, you're taking it -- no matter what else is happening in that cog. This is why you need to be cautious with coginit; if you've not kept track of cogs used you could overwrite a cog that is in use.
  • lonesocklonesock Posts: 917
    edited 2013-12-24 11:34
    Consider: If your random number generator produces numbers between 0 and 15 with equal probability. And then you do a modulus 10 so as to get numbers 0 to 9. Then numbers 0 to 9 from the generator will become 0 to 9 in your output. BUT numbers 10 to 15 from the generator will become 0 to 5 in your output. You then have more possibilities of getting 0 to 5 than you have 6 to 15. Not good.
    Heater, there's a quick hack to alleviate (but not remove) the bias of using the mod operator. I doubt this is very rigorous, but it seems to work pretty well in my trials. For your random number in a range, instead of saying:
    rn := prng_0_to_15
    rn //= 10
    
    Try this instead:
    rn += prng_0_to_15
    rn //= 10
    
    Just make sure you don't modify rn between runs. Each individual sample has an uneven probability, but the aggregate results are better.

    Here's some super ugly C++ code to demo (and yes, I know rand() is horrible [8^):
    #include <cmath>
    #include <ctime>
    #include <cstdio>
    #include <cstdlib>
    
    #include <vector>
    
    int main( int argc, char**argv )
    {
    	srand( time( 0 ) );
    
    	const int Mask = 15;
    	const int Count = 10;
    	std::vector<int> histo( Count, 0 );
    
    	const int N = 100000;
    	int rn = 0;
    	for( int i = 0; i < N; ++i )
    	{
    		rn += rand() & Mask;
    		rn %= Count;
    		histo[ rn ] += 1;
    	}
    
    	for( int i = 0; i < Count; ++i )
    	{
    		printf( "Histo %3i = %2.3f\n", i, 100.0 * histo[i] / N );
    	}
    
    	return 0;
    }
    

    Jonathan
  • Heater.Heater. Posts: 21,230
    edited 2013-12-24 11:51
    Lonesock,

    I have to think about that.

    My naive idea is that if you want numbers in the range 0 to 9, say, then mask off all but the low four bits of the generator, then when you get a number greater than 9 throw it away and take the next one, check it for greater than 9 again, and so on.

    That reduces the rate at which you can get random numbers but I think it removes the bias totally. (Emphasis on the word "think" there).
  • lonesocklonesock Posts: 917
    edited 2013-12-24 12:17
    You are exactly correct. That is the right way to do it...it just can take a random period of time. [8^)

    Jonathan
  • Heater.Heater. Posts: 21,230
    edited 2013-12-24 12:24
    I might suspect there is a way to scale a range of numbers 0 to N down to a range smaller range of 0 to n by converting to floating point and using the appropriate scaling factor and rounding methods. How that works in practice I have no idea.
Sign In or Register to comment.