Random number problem

Tony P

Member
I have recently started using IC's (555, 4017 etc) but recently I have been convinced that PIC's are the way to go :D
I am very new to Picaxe and Basic commands and I am trying to do the following:

I have 9 buttons of which 4 are 'true' and the remaining 5 are 'false'.
If the 'true' buttons are pressed in the correct order then an output pulls a solenoid and opens a box. Any 'false' buttons pressed returns the user back to the beginning.

This I have set up and is fully operational.

What I am trying to do is to randomise the 4 'true' numbers for each time the box is activated. They will always be the same 4 buttons as that is the way I have wired the circuit.

By reading various posts on this forum I have tried the following but the do/while loop does not work. I sometimes get a repeat number which is of no use.
Code:
	table 0,(2,3,4,5) ; test numbers
main:
	pause 500 	
	random w0
	b2=w0//3			
	readtable b2,b1		
	high b1
		
	pause 500
	do 
	random w1
	b3=w1//3
	loop while w1=w0	
	readtable b3,b1	
	high b1

	pause 500
	do 
	random w2
	b4=w2//3
	loop while w2=w1 or w2=w0 
	readtable b4,b1
	high b1

	pause 500
	do 
	random w3
	b5=w3//3
	loop while w3=w2 or w3=w1 or w3=w0
	readtable b5,b1
	high b1

	pause 500
	low b.2
	low b.3
	low b.4
	low b.5
	goto main
Any ideas please or am I going about it completely the wrong way? Please be gentle with me as I am a newbie :D

18M2+
 
Last edited:

geoff07

Senior Member
The key thing with 'random' numbers is to recognise that they don't really exist except in special circumstances. Most number sequences have some degree of autocorrellation. What you really want are 'pseudo' random numbers. These are sequences that have the properties of randomness until they repeat (which is of course thoroughly non-random at that point).

The thing to remember is that each number is generated on the back of the preceding number. So you should use a single variable, and each number generated uses the previous number as a 'seed'. A quick look at your code shows that you are using separate variables. That won't get you pseudo-randomness.

Also remember that any sequence is as likely as any other. So 11111111 is as likely as 10011100. The former doesn't look random, the latter might. So if you don't like the sequence you get because it doesn't look like randomness, use a different starting value.

To get closer to randomness you can add some unpredictability, such as measuring the resistance of an ldr or some other physical value that changes, and using that as a seed.

You can find a lot about randomness by searching this forum.
 

hippy

Ex-Staff (retired)
Another way to use random sequences is to create an array of all numbers available ( in this an array that can hold nine values, 0-8, to represent each button ). You can take the first four to determine which buttons should be pressed. Juggle the array in some way to swap numbers around in a random manner and the code required is also changed.

I've been meaning to write an example of that and the following code shows one way to do this. It uses an array in RAM in the area shared by 'b0' to 'b8' and also accessed via 'bptr=0' to 'bptr=8' ...

Code:
#Picaxe 08M2
#No_data
#Terminal 4800

' b0-b8 used to store array

Symbol randomWord =  w5 ; b11:b10

Pause 1000  ; Pause while Terminal opens
Gosub InitialiseArray
Gosub ShowArray
Do
  Gosub ShuffleArray
  Gosub ShowArray
Loop

InitialiseArray:
  For bptr = 0 To 8
    @bptr = bptr
  Next
  Return

ShuffleArray:
  Random randomWord
  bptr = randomWord // 9 ; creates bptr = 0 to 8
  Swap @bptr, b0
  Return
  
ShowArray:
  For bptr = 0 To 8
   SerTxd( #@bptr )
  Next
  SerTxd( CR, LF )
  Pause 1000
  Return
This uses a very simple juggling algorithm; chose a number 0 to 8 and swap the array position value with the value in position 0 ( which is also 'b0' ). When run you should see ...

012345678
210345678
510342678
015342678
215340678
512340678

The first four digits are the buttons you need to push to unlock your bank safe, and you will usually see some change most of the time, unless bptr also happens to be 0 (see end note).

The sequence only changes slowly ( note the stubbornly sticking '1' and '678' ) but you can put a FOR-NEXT inside 'ShuffleArray:'. For example, doing 20 shuffles rather than just one gives -

012345678
650324178
547602183
542307168
602743158
820413765

Much better.

The code is a little complicated using reasonable advanced tricks so step it through simulation to get a better understanding of how the code is doing things. You will need to understand what it is doing to free-up 'b0' to 'b8' for program use if you need that, otherwise just allocate program variables from b9 upwards.

Added : You can change the 'bptr = randomWord // 9" so it always shuffles some number, avoids bptr=0 ...

bptr = randomWord // 8 + 1 ; bptr = 1 to 8
 
Last edited:

Buzby

Senior Member
OK hippy, now you've got the array shuffled, how about an algorithm to sort it back to it's initial condition ?.

There are heaps to choose from, which will you bubble up ?

Cheers,

Tipsy
 

Tony P

Member
Great replies, Thanks
But they are a little wasted on me at this stage of my learning curve!!

Looking at my code, where am I going wrong?
 

DamonHD

Senior Member
Another tip or two:

1) Call random several times between the numbers you want to sample to make the relationship between successive values that you draw from it less obvious.

2) 'Seed' the random number generator a little, and add some more such 'entropy' periodically, to help get some real randomness into the sequence. As suggested earlier the output of an LDR (particularly the least significant bits, which will be more 'noisy') or temperature (eg readinternaltemp raw values where you have the command) XORed into your current random value is a good start.

Have a look at what I do in this little random lights desk ornament project for example:

https://sourceforge.net/p/picaxeplay/code/HEAD/tree/trunk/7LEDs/18M2.bas

Rgds

Damon

PS. Code pasted in below at current version for posterity!

Code:
; *************************************************************
;
; Damon Hart-Davis licenses this file to you
; under the Apache Licence, Version 2.0 (the "Licence");
; you may not use this file except in compliance
; with the Licence. You may obtain a copy of the Licence at
;
; http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing,
; software distributed under the Licence is distributed on an
; "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
; KIND, either express or implied. See the Licence for the
; specific language governing permissions and limitations
; under the Licence.
;
; *************************************************************
; Author(s) / Copyright (c): Damon Hart-Davis 2013

; Flashing 7 LEDs, a red/green bi-colour in the centre; 6 surrounding it in a circle.
; Bi-colour LED between B.3 and B.6 outputs (via 330R+).
; All other B.0 to B.7 outputs driving LEDs (connected to ground via 330R+) in clockwise order around the circle.


#picaxe 18m2 ; 18M2+ device.

#no_data ; Not using the EEPROM.

dirsB = %11111111 ; All outputs.

symbol LED_BI1_RED_C = B.3 ; Bi-colour LED red cathode (short leg), green anode.
symbol LED_BI1_RED_A = B.6 ; Bi-colour LED red anode (long leg), green cathode.

; C.0: LDR light sensor (ADC input); higher voltage indicates more ambient light.
; Should be pulled low externally whether LDR used or not, so can be left as input or set to low output.
; LDR is ~1M dark resistance to V+, being pulled to 0V by a 10k--100k resistor.
symbol INPUT_LDR = C.0

; Light level (0 is dark, 255 is light).
symbol ambientLighting = B24
readadc INPUT_LDR, ambientLighting ; Take initial reading.
#rem
testPWM:
readadc INPUT_LDR, ambientLighting ; Take initial reading.
pinsB = $ff ; Everything on.
gosub centreRedSet
gosub pauseAndPWMLeds1s
toggle LED_BI1_RED_C, LED_BI1_RED_A
gosub pauseAndPWMLeds1s
goto testPWM
#endrem


; Speed (and possibly brightness) settings.
symbol MAX_SPEED = 4
symbol MIN_SPEED = 0
symbol speed = B25 ; 0 is slowest (can be used with nap).


symbol randWord = W13 ; Random number store
symbol randB1 = B26
symbol randB2 = B27
; Initialise with bits from the internal temperature sensor.
readinternaltemp IT_RAW_L, 0, randWord

; Warm up with some spins...
for speed = MIN_SPEED to MAX_SPEED
    gosub spinCW
    gosub spinCW
    gosub spinCCW
    gosub spinCCW
next
sleep 1

mainloop:
    readadc INPUT_LDR, ambientLighting
    W0 = ambientLighting * MAX_SPEED / 255
    speed = W0 ; Set basic activity speed...

    ; Maybe do some spinning...
    do while randB1 >= $40 ; 0 or more times, typically 2 or 3...
        random randWord
        if randB2 >= $80 then
            gosub spinCW
        else
            gosub spinCCW
        endif
    loop

    random randWord
    readadc INPUT_LDR, ambientLighting
    ; More likely to turn LEDs off and sleep here as ambient lighting falls.
    if ambientLighting < randB1 then
        pinsB = 0 ; All off...
        sleep 1
    else
        B12 = 255 - ambientLighting min 1
        gosub pauseAndPWMLeds
    endif

    ; Do some flashing...
    gosub briefRandomRound

    ; Sleep, longer when darker...
    pinsB = 0 ; All off...
    random randWord
    do while ambientLighting < randB2 ; Guaranteed to terminate eventually, eg when randB2 == 0.
        random randWord
        sleep 1
        ; Flash just the centre light, briefly...
        B2 = randB1 & 1
        if B2 = 0 then : gosub centreGreenSet : else gosub centreRedSet : endif
        nap 0
        readadc INPUT_LDR, ambientLighting ; Re-check light level.
        pinsB = 0 ; All off...
    loop

    gosub reseed
    goto mainloop


; Stir a little entropy into the pool!
reseed:
    readinternaltemp IT_RAW_L, 0, W0
    randWord = randWord ^ W0
    random randWord
    randWord = randWord ^ time
    random randWord
    return


; Pause for *nominally* 1s, matching LED output to ambient lighting.
; Destroys B11, B12, B13, B14, B15.
pauseAndPWMLeds1s:
    B12 = 100
; Pause in B12 units of *nominally* 10ms, matching LED output to ambient lighting.
; Destroys B11, B13, B14, B15.
pauseAndPWMLeds:
    B11 = pinsB ; Capture extant LED state.
    ; Software PWM on nominal 10ms cycle.
    B14 = ambientLighting / 25 min 1 max 10 ; On time: never 0.
    B15 = 10 - B14 min 1; Off time: nominally can be zero (for max brightness).
    for B13 = 1 to B12
       ; Aim for LED duty cycle to go down with the ambient light level.
       pause B14
       pinsB = 0 ; Turn all LEDs off.
       pause B15
       pinsB = B11 ; Restore state of LEDs.
    next
    return

; Pauses in inverse proportion to speed and may adjust brightness of LEDs to match (software PWM).
; Attempts to conserve energy too.
; Destroys B10, B11, B12, B13, B14, B15
pauseToMatchSpeed:
    B10 = MAX_SPEED - speed ; B10 is pause time on 'nap' (log) scale.
    B12 = 2
    if B10 > 0 then : for B13 = 1 to B10 : B12 = B12 + B12 : next : endif ; B12 is pause time on (linear) scale.
    gosub pauseAndPWMLeds
    return


; Spin clockwise at specified speed.
spinCW:
    pinsB = 0; All off...
    gosub centreRedSet
    high B.0
    gosub pauseToMatchSpeed
    high B.1
        low B.0
    gosub pauseToMatchSpeed
    high B.2
        low B.1
    gosub pauseToMatchSpeed
    high B.4
        low B.2
    gosub pauseToMatchSpeed
    high B.5
        low B.4
    gosub pauseToMatchSpeed
    high B.7
        low B.5
    gosub pauseToMatchSpeed
        low B.7
    return

; Spin counter-clockwise at specified speed.
spinCCW:
    pinsB = 0; All off...
    gosub centreGreenSet
    high B.7
    gosub pauseToMatchSpeed
    high B.5
        low B.7
    gosub pauseToMatchSpeed
    high B.4
        low B.5
    gosub pauseToMatchSpeed
    high B.2
        low B.4
    gosub pauseToMatchSpeed
    high B.1
        low B.2
    gosub pauseToMatchSpeed
    high B.0
        low B.1
    gosub pauseToMatchSpeed
        low B.0
    return

; Do brief round of random flashing at current speed, with fewer and dimmer LEDs on with lower ambient lighting.
; Destroys B0 and others.
briefRandomRound:
    random randWord
    B1 = randB2 & $1f
    B0 = ambientLighting / 4 + B1 min 15 ; 15 to 94 rounds depending in part on light levels...
    do while B0 > 0
        dec B0
        gosub allRandomSet
        if ambientLighting < $80 then
            random randWord
            pinsB = pinsB & randB1 ; For low light knock out about half the LEDs.
            if ambientLighting < $20 then
                pinsB = pinsB & time ; For very low light knock out about half of the remaining LEDs.
            endif
        endif
        gosub pauseToMatchSpeed
    loop
    return

; Randomise all the LED states, as normal (B) outputs, using randB2.
allRandomSet:
    random randWord
    dirsB = %11111111 ; All outputs.
    pinsB = randB2
    return

; Turn the centre LED red.
centreRedSet:
    low LED_BI1_RED_C
    high LED_BI1_RED_A
    return

; Turn the centre LED green.
centreGreenSet:
    high LED_BI1_RED_C
    low LED_BI1_RED_A
    return
 
Last edited:

hippy

Ex-Staff (retired)
OK hippy, now you've got the array shuffled, how about an algorithm to sort it back to it's initial condition ?.

There are heaps to choose from, which will you bubble up ?
I would go for a simple "bubble sort"; scan through the list and shuffling so the lowest number appears before the first, repeat until no shuffles were done. That is fairly easy to implement and suits 'bptr' use.

Two additional routines added; "SortArrayAscending" and "SortArrayDescending" ...

Code:
#Picaxe 08M2
#No_data
#Terminal 4800

' b0-b8 used to store array

Symbol randomWord = w5  ; b11:b10
Symbol counter    = b12
Symbol first      = b13
Symbol second     = b14

Pause 1000  ; Pause while Terminal opens

Gosub InitialiseArray
SerTxd( "Initialised", 9 ) : Gosub ShowArray

For counter = 1 To 100
  Gosub ShuffleArray
Next
SerTxd( "Shuffled", 9 ) : Gosub ShowArray

Gosub SortArrayAscending
SerTxd( "Ascending", 9 ) : Gosub ShowArray

For counter = 1 To 200
  Gosub ShuffleArray
Next
SerTxd( "Shuffled", 9 ) : Gosub ShowArray

Gosub SortArrayDescending
SerTxd( "Descending", 9 ) : Gosub ShowArray

End

InitialiseArray:
  For bptr = 0 To 8
    @bptr = bptr
  Next
  Return

ShuffleArray:
  Random randomWord
  bptr = randomWord // 9 ; creates bptr = 0 to 8
  Swap @bptr, b0
  Return

SortArrayAscending:
  Do
    counter = 0
    For bptr = 0 To 7
      first  = @bptrInc
      second = @bptrDec
      If first > second Then
        @bptrInc = second
        @bptrDec = first
;---    counter = bptr : SerTxd(9,9) : Gosub ShowArray : bptr = counter
        counter = 1
      End If
    Next
  Loop Until counter = 0
  Return  

SortArrayDescending:
  Do
    counter = 0
    For bptr = 0 To 7
      first  = @bptrInc
      second = @bptrDec
      If first < second Then
        @bptrInc = second
        @bptrDec = first
;---    counter = bptr : SerTxd(9,9) : Gosub ShowArray : bptr = counter
        counter = 1
      End If
    Next
  Loop Until counter = 0
  Return  

ShowArray:
  For bptr = 0 To 8
   SerTxd( #@bptr )
  Next
  SerTxd( CR, LF )
  Pause 1000
  Return
Uncomment the two ";---" lines to see the sorting in operation. 'bptr' is used in the 'ShowArray' routine so needs to be protected within the sorting 'FOR bptr - NEXT' loop; the counter variable is re-used for that.
 
Top