Search code examples
assemblyrandomx86-16tasm

Generate 2 random integers simultaneously in Assembly


Backgroud

The function rollCubes rolls 2 cubes within each turn of a backgammon game.

Issue

When running: rollCubes always returns 2-2, 4-4 or 6-6.

[edit]

When debugging (executing each line inside turbo debugger): the function manages to produce two different random integers, but the cube 1 is never rolled.

It makes me wonder what prevents rollCubes from producing random integers when running the executable.

Sourcecode

proc rollCubes
    xor cx, cx
    mov [hicube], 0 
    mov [locube], 0
    mov cx, 2
    addAgainHigh:
        ; Random number [0, 3] --> two rolls [0, 6]
        mov ax, 40h
        mov es, ax
        mov ax, es:6Ch ;Random number
        and al, 00000011b
        cmp al, 0 ;Make sure al isn't 0
        je addAgainHigh ;If 0, roll again
        add [hicube], al ;add to cube value
        loop addAgainHigh
    mov cx, 2
    addAgainLow:
    ; Random number [0, 3] --> two rolls [0, 6]
        mov ax, 40h
        mov es, ax
        mov ax, es:6Ch
        and al, 00000011b
        cmp al, 0
        je addAgainLow
        add [locube], al
        loop addAgainLow
    mov al, [locube]
    mov ah, [hicube]
    ret 
endp rollCubes

Solution

  • Your code reads some number (BIOS.Timer) and keeps only a subset of the possible values {1,2,3}. After repeating you have {2,3,4,5,6}. You can never roll 1 this way

    A roll of a dice should produce a number from 1 to 6.

    call myRandom    ; -> AX = [0,65535]
    xor  dx, dx
    mov  cx, 6
    div  cx          ; -> Remainder DX = [0,5]
    inc  dx          ; -> Dice is [1,6]
    ...
    
    ; IN () OUT (ax)
    proc myRandom
        imul ax, [NextRandom], 25173
        add  ax, 13849
        xor  ax, 62832
        mov  [NextRandom], ax
        ret
    endp myRandom
    

    The code in this myRandom proc is a pseudo random number generator that will produce 65536 unique 16-bit numbers before it will repeat itself. No sooner will it output the same number.

    When I run the above code 80 times (NextRandom starts at 0), I get the following numbers for rolling the dice:

    4,5,6,1,2,3,2,3,4,3, 2,3,6,5,6,3,4,3,4,1
    6,5,6,5,4,3,6,1,2,5, 4,5,2,3,6,5,2,1,6,3
    6,3,6,3,2,3,2,1,2,5, 2,1,2,3,4,1,6,5,4,1
    4,5,4,5,4,1,2,1,2,3, 6,3,6,1,4,1,6,3,6,1
    

    The distribution of these numbers is fine:

    1 ~ 13
    2 ~ 13
    3 ~ 16
    4 ~ 12
    5 ~ 11
    6 ~ 15
    

    [edit]

    When running: rollCubes always returns 2-2, 4-4 or 6-6. It makes me wonder what prevents rollCubes from producing randoms when running the executable.

    The BIOS.TimerTick at linear address 0040h:006Ch increments every 55 ms. This is a very long time in computers, and because your rollCubes procedure reads this counter 4 times in rapid succession, you will nearly always receive the same value from it.

    This is what happens most of the time:

    • If TimerTick happens to end with 00b

        addAgainHigh:
         00b  reread until it changes to 01b
         01b=1  hicube = 0 + 1 + 1 = 2
        addAgainLow:
         01b=1  locube = 0 + 1 + 1 = 2
      
    • If TimerTick happens to end with 01b

        addAgainHigh:
         01b=1  hicube = 0 + 1 + 1 = 2
        addAgainLow:
         01b=1  locube = 0 + 1 + 1 = 2
      
    • If TimerTick happens to end with 10b

        addAgainHigh:
         10b=2  hicube = 0 + 2 + 2 = 4
        addAgainLow:
         10b=2  locube = 0 + 2 + 2 = 4
      
    • If TimerTick happens to end with 11b

        addAgainHigh:
         11b=3  hicube = 0 + 3 + 3 = 6
        addAgainLow:
         11b=3  locube = 0 + 3 + 3 = 6
      

    When debugging (executing each line inside turbo debugger): the function manages to produce random rolls as should be.

    Indeed, there's no longer the problem of rapid succession! Executing the program from within Turbo Debugger takes time. The BIOS.TimerTick at linear address 0040h:006Ch will (very probably) have incremented in between reads from your program.

    When debugging (executing each line inside turbo debugger): the function manages to produce random rolls as should be.

    Not really! As stated in my earliest answer, you can never roll 1 this way.
    Because your program throws out the zero, the following are all the sums your code can produce combining both sets {1,2,3} through addition:

    1 + 1 = 2
    1 + 2 = 3
    1 + 3 = 4
    2 + 1 = 3
    2 + 2 = 4
    2 + 3 = 5
    3 + 1 = 4
    3 + 2 = 5
    3 + 3 = 6
    

    Do notice that the numbers have very different probabilities this way.