Wishful Coding

Didn't you ever wish your
computer understood you?

Branch-free FizzBuzz in Assembly

I came across this post that discusses ways to write FizzBuzz in Clojure without using conditionals. However, most if not all of the solutions still do a lot of branching behind the scenes. Think of hash lookups for example.

So I asked to myself, how can I write a FizzBuzz solution with no branches at all? Probably not in Clojure; you can’t easily tell where it is branching or not.

The only way to be absolutely sure is to write it in assembly. So I did. I never did assembly before, so it might be terrible code.

I used an array of 15 pointers to either “fizz”, “buzz”, “fizzbuzz”, or a number buffer. I then filled the number buffer with the current number in ascii and printed whatever I get from the array.

One thing I struggeled with is how to stop. At first I had one condition to see if I reached 100. Now I use a lookup table that calls sys_time 99 times and then sys_exit.

section .data
  f db "fizz    "
  b db "buzz    "
  fb db "fizzbuzz"
  n db 10 ; newline string
  cycle dq num, num, f, num, b, f, num, num, f, b, num, f, num, num, fb
  callid dq 13, 1 ; sys_time, sys_exit

section .bss
  num resb 8 ; number buffer

section .text
global _start

print:      ;write rcx
  mov rax,4 ;sys_write
  mov rbx,1 ;stdout
  mov rdx,8
  int 0x80
  ret

newline:    ;write newline
  mov rax,4 ;sys_write
  mov rbx,1 ;stdout
  mov rcx,n
  mov rdx,1
  int 0x80
  ret

itoa:      ;convert rax to str
  mov byte[num+1],0x30
  mov rdx,0
  mov rcx,10
  div rcx
  add [num+1],rdx
  mov byte[num],0x30
  mov rdx,0
  mov rcx,10
  div rcx
  add [num],rdx
  ret

_start:
  mov r12,0

hundredtimes:
  ; initialise number buffer
  mov rax,r12
  inc rax
  call itoa

  ; mod 15 the number
  mov rax,r12
  mov rdx,0
  mov rcx,15
  div rcx

  ; look up the number in cycle
  ; prints the num buffer or any of the strings
  mov rcx,[cycle+rdx*8]
  call print
  call newline
  
  ; next...
  inc r12

  ; devide the number by 100
  mov rax,r12
  mov rdx,0
  mov rcx,100
  div rcx

  ; get the time or exit
  mov rax,[callid+rax*8]
  int 0x80

  ; jump to the top of the loop
  jmp hundredtimes

To compile on a 64 bit machine:

nasm -f elf64 fizzbuzz.asm
ld -o fb fizzbuzz.o
./fb

CD spinner

I know this is a rather silly robot. It does nothing at all, and it’s not very beautiful either. I guess I just want to remind you that it’s important to just play without expectations or goals.

According to John Cleese, it is very useful to attempt something impossible sometimes. This often puts your mind in a new perspective, leading to new ideas.

Reading a CD with a light sensor is obviously not going to work, but maybe there are other ways to read data with the EV3?

EV3 Sumo Robot

After a long period of silence, I finally made some things with my EV3. Last time the tools where not really ready, but now everything works great.

I used ev3dev and python-ev3 to build this bulldozer. Watching sumo matches at LEGO World, I saw a few things that do and don’t work.

A lot of people seem to go for blades and hammers, but unless you actually want to destroy the robot, they are useless. Treads sound like a good idea, but wheels have more grip in practice. Make sure the wheels don’t fall off. Encased wheels are the best.

The goal is to push the other robot off the table, so the more powered rubber the better. Four wheel drive is better than rear wheel drive, but rear wheel drive is still much better than front wheel drive.

It’s all about pushing. The only useful thing besides pushing forward is pushing up or sideways. Pushing up is the easiest, because you have the floor to back you up. It also pushes the opponent’s wheels off the ground. This lead me to the following design.

[gallery columns=”2” ids=”534,532,533,535”]

I used two pairs of driven rear wheels on a pivot, so even if I’m lifted, all four remain in contact with the ground. They are geared down, which means I’m slow, but powerful. All the weight of the motors and EV3 is directly above the wheel, for maximum grip.

On the front there is a lift arm, using a worm wheel and a very solid construction. I went through several iterations, improving the strength each time. On the first iteration, it was to weak, but its wedge shape still lifted my opponent a little.

The code is quite simple. Push forward. When you reach the edge, turn around. When something hits the bumper, raise the arm.

from ev3 import lego
import time
import select
import os

# Utility for waiting on the motor.
p = select.poll()
callbacks = {}

def poll():
    for fd, mask in p.poll(0):
        os.lseek(fd, 0, os.SEEK_SET)
        state = os.read(fd, 16)
        if state == "idle\n":
            p.unregister(fd)
            os.close(fd)
            callbacks.pop(fd)()

def on_completion(motor, callback):
    fd = os.open(motor.sys_path + '/state', os.O_RDONLY)
    callbacks[fd] = callback
    os.read(fd, 16)
    p.register(fd, select.POLLPRI)

# Initiate all sensors and motors
l = lego.LargeMotor("A")
r = lego.LargeMotor("D")
lift = lego.MediumMotor()

dist = lego.InfraredSensor()
line = lego.ColorSensor()
touch = lego.TouchSensor()

# reset the motors
l.reset()
r.reset()
lift.reset()

try:
    # run forwards
    l.run_forever(100, regulation_mode=False)
    r.run_forever(100, regulation_mode=False)
    # main loop
    while True:
        poll()
        if touch.is_pushed and lift.state == "idle":
            # we hit something, raise the arm!
            lift.run_position_limited(-3000, 1000)
            on_completion(lift, lambda: lift.run_position_limited(0, 1000))
        elif line.reflect < 20 and l.duty_cycle > 0 and r.duty_cycle > 0:
            # we reached the edge, back up and scan for opponent
            l.run_time_limited(1000, -100)
            on_completion(l, lambda: l.run_forever(100))
            r.run_forever(-100)
        elif dist.prox < 50:
            # found opponent, charge!!!
            r.run_forever(100)
            l.run_forever(100)
finally:
    # stop motors and lower arm
    l.stop()
    r.stop()
    lift.run_position_limited(0, 1000)