Building a Tetris Clone in x86 Assembly, pt. Ⅲ: Time, pt. Ⅰ

It’s certainly been a while, hasn’t it? This is the third part in a series I started back in September of 2015. After months, this part still doesn’t cover what it was meant to, so this is just part Ⅰ of part Ⅲ. Enjoy.

The real instruction, the best instruction

First, I’d like to introduce you to my favourite x86 instruction: rdtsc1. This handy instruction loads the 64-bit timestamp counter register into edx:eax. The register starts at zero and is monotonically incremented on each clock cycle.

It may not seem immediately useful, but it can actually be put to a couple fun/scary uses in a constrained environment.

Quite possibly the worst random

To implement a Tetris game, we’ll need to spawn tetrominoes, and they need to spawn randomly. The actual mechanic uses a “shuffled bag” of the seven tetrominoes for balance.

To shuffle the bag, we can use Fisher–Yates shuffle, which will need an implementation of rand. Maybe you see where this is going.

I know, rdtsc is clearly not random. It is monotonically increasing! The thing is, processors are fast, and reading the cycle count at varying points should produce somewhat unpredictable lower bits.

Those lower bits could certainly be used to seed some sort of pseudo-random number generator, but that’s more work than it’s worth. Let’s just use modulo.

Rand

The rand function will take a double-word range parameter and return a “random” number n, where 0 ≤ n < range.

It won’t need to bother with ebp, so can load the range directly from [esp + 4].2

rand:
  rdtsc
  xor edx, edx
  div dword [esp + 4]
  mov eax, edx
  ret

Before dividing the cycle count, edx is zeroed to prevent overflow exceptions. The div instructions leaves the remainder in edx, so that is returned.

Shuffle

The shuffle function will take two parameters. The first will be a double-word address of the first word in an array of words, and the second will be the double-word length of the array. It will shuffle arrays of words since tetrominoes will later be represented as such.

The function begins by loading the address of the array into ebx, and the index of the last element into ecx.

shuffle:
  push ebp
  mov ebp, esp
  push ebx

  mov ebx, [ebp + 8]
  mov ecx, [ebp + 12]
  dec ecx

In a loop, rand is used to get a random index between 0 and ecx inclusive.

  .loop:
    lea eax, [ecx + 1]
    push eax
    call rand
    add esp, 4

In order to pass ecx + 1 to rand, the “load effective address” lea3 instruction is used. This is a common trick for doing certain arithmetic operations in a single instruction.

The values at the current index ecx and the random index eax are then swapped, using dx as a temporary.

    mov dx, [ebx + ecx * 2]
    xchg dx, [ebx + eax * 2]
    mov [ebx + ecx * 2], dx
    loop .loop

The loop continues, decrementing ecx each time. When the loop completes, the array has been shuffled in-place, and the function simply returns.

  pop ebx
  mov esp, ebp
  pop ebp
  ret

The full implementation can be found in random.asm.

Proof that it works

Using functions from previous parts of the series, we can build a small test that shuffles an array of 4 words on each key event.

section .data

array dw 0xAAAA, 0xBBBB, 0xCCCC, 0xDDDD

section .text

main:
  push word BG.BLACK | ' '
  call clear

  .loop:
    call scan
    test al, al
    jz .loop

    push dword 4
    push array
    call shuffle

    push word 0x1008
    push dword [array]
    call itoa
    push dword 0x0101 << 16 | FG.BRIGHT | FG.BLUE
    push eax
    call puts

    push word 0x1008
    push dword [array + 4]
    call itoa
    push dword 0x0109 << 16 | FG.BRIGHT | FG.BLUE
    push eax
    call puts

    add esp, 36
    jmp .loop

And hopefully the array will be shuffled differently each time.

Shuffled words

In the distant future…

At some point, there will be a post covering actual timing using rdtsc. It will be terrible and wonderful at the same time. Oh, and eventually we’ll make an actual Tetris game. Almost forgot.

  1. RDTSC, x86 Instruction Set Reference 

  2. GitHub recently “upgraded” Pages to Jekyll 3.0, and forced the use of the Rouge highlighter, which doesn’t support assembly. Thanks, GitHub. 

  3. LEA, x86 Instruction Set Reference