I found a picture of a roulette wheel. I wonder how hard it would be to make my little game use it.

base-image

I did a little research into how to import an image into Codea Lua and display it. The trick I used was to copy the image to the iPad clipboard, and then execute this line of code:

    Image = pasteboard.image
    saveImage("Documents:Roulette", Image)

That does more or less what you’d expect. Then I went on to do this:

-- S2ratch

-- Use this function to perform your initial setup
function setup()
--    Image = pasteboard.image
--    saveImage("Documents:Roulette", Image)
    Image = readImage("Documents:Roulette")
    angle = 0
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)
    translate(WIDTH/2, HEIGHT/2)
    rotate(angle)
    sprite(Image)
    angle = angle + 1
end

Which results in this:

The Wheel

So that’s pretty fine, we’re nearly done. I think I’ll start using this in the R3oulette program, the tiny one I did last time. How to proceed?

First, I’ll just add the wheel, and display the answer on top of it. I think I’ll just display the final result, with the text gone, right in the middle of the wheel. Maybe I can make the text bigger. Here goes …

-- R3oulette

function setup()
    Spinning = false
    Slot = 36
    Time = 0
    Image = readImage("Documents:Roulette")
end

function touched(ignored)
    Spinning = true
    Time = ElapsedTime
end

-- This function gets called once every frame
function draw()
    if Spinning and ElapsedTime - Time > 5 then
        Slot = math.random(0,36)
        Spinning = false
    end
    background(40, 40, 50)
    strokeWidth(2)
    translate(WIDTH/2, HEIGHT/2)
    sprite(Image)
    if not Spinning then 
        text("" .. Slot)
    else
        text("" .. math.random(0,36))
    end
end

This displays the wheel, shows a number changing rapidly while “spinning”, and shows the final slot when done. The wheel isn’t spinning, and I have a good reason for that: I want to display the ball in the proper slot.

Wow, looking at the wheel, I notice for the first time that it is one with two zeros, 0 and 00. I could look for a better graphic but let’s just deal with that issue later. Anyway there are 38 slots around the wheel. Looks to me as if the slot at angle zero (due right, if I am not mistaken) is slot 3. Let’s see if we can display a ball at that location. First, I’d like to just know how big the wheel is. I could perhaps measure it or find out in the original file. But wait, the sprite function can set height and width. I’ll just do this:

    sprite(Image, 0, 0, 500,500)

And now I can guess that the wheel is about 250 pixels in radius:

250

I’ll try to put a ball at the location of the 3. I expect I’ll need to adjust the radius a bit to get it where I want it. First cut:

function draw()
    if Spinning and ElapsedTime - Time > 5 then
        Slot = math.random(0,36)
        Spinning = false
    end
    background(40, 40, 50)
    strokeWidth(2)
    translate(WIDTH/2, HEIGHT/2)
    sprite(Image, 0, 0, 500,500)
    if not Spinning then 
        text("" .. Slot)
    else
        text("" .. math.random(0,36))
    end
    local ballpos = vec2(250,0)
    ellipse(ballpos.x, ballpos.y, 10)
end

edge

We can barely see it. It needs to be larger and it needs to be filled. And it should of course be at a somewhat smaller radius than 250:

    fill(255)
    local ballpos = vec2(240,0)
    ellipse(ballpos.x, ballpos.y, 20)

in

Yes, well. Now that I look at it, I see that the ball should rest, not on top of the number, but in that little square slot nearer to the center of the wheel. I’ll just fiddle till I like it: local ballpos = vec2(190,0) does the job:

got it

Now we need to position the ball in the right slot. If the slots were in order, this would amount to rotating our direction vector by 1/38 of the circle (two pi) for each uptick in the slot. But they are not in order, so we need to get the wheel slot given what the ball value is supposed to be. I think we’ll just want to look it up in a list, which I’ll type in by hand, going around the wheel.

    Wheel = {3, 15, 24, 22, 5, 17, 32, 20, 7, 11, 30, 26, 9, 28, 0, 2,14, 35, 23,
    4, 16, 33, 21, 6, 18, 31, 19, 8, 12, 29, 25, 10, 27, 00, 1, 13, 36, 24}

I just ignored the 00 question still. We’ll see what to do about that. Right now I’m thinking of hacking the roll of 37 to mean 00 but we’ll see what makes sense.

Now the angle we want is, I think, two pi over 38, times the zero-based index of the table Wheel. I mention zero-based because Lua tables are one-based. Anyway, how about this:

    local ballpos = vec2(190,0)
    local angle = angleOf(Slot)
    ballpos = ballpos:rotate(angle)
    ellipse(ballpos.x, ballpos.y, 20)

Now it works when it’s standing still at the beginning …

36

And after a spin. This one came up 13:

13

Sweet. I notice that while it’s running, the ball just sits where it was. We can condition the ball display on Spinning easily enough:

Ball vanishes when “spinning”

Wow that works just fine. Now what? Well, the wheel is supposed to spin. So let’s make it spin and make the ball spin with it when the ball is visible. I’ll need to move the wheel rotation code over from the previous version. And I think, since we rotated the whole screen view, it might just work. We’ll see.

function draw()
    if Spinning and ElapsedTime - Time > 5 then
        Slot = math.random(0,36)
        Spinning = false
    end
    background(40, 40, 50)
    strokeWidth(2)
    translate(WIDTH/2, HEIGHT/2)
    if not Spinning then 
        text("" .. Slot)
    else
        text("" .. math.random(0,36))
    end
    rotate(Angle)
    Angle = Angle + 1
    sprite(Image, 0, 0, 500,500)
    fill(255)
    if not Spinning then
        local ballpos = vec2(190,0)
        local angle = angleOf(Slot)
        ballpos = ballpos:rotate(angle)
        ellipse(ballpos.x, ballpos.y, 20)
    end
end

(Angle is initialized to 0 up in setup()). This works fine for the ball display, as you see here:

Rotating but no text

But the text display has disappeared. Why? I moved the sprite down to be inside the rotate logic and the text was then drawn first and overwritten. We’ll have to move it down. However then it will be rotated, so we’ll have to push and pop the matrix:

function draw()
    if Spinning and ElapsedTime - Time > 5 then
        Slot = math.random(0,36)
        Spinning = false
    end
    background(40, 40, 50)
    strokeWidth(2)
    translate(WIDTH/2, HEIGHT/2)
    pushMatrix()
    rotate(Angle)
    Angle = Angle + 1
    sprite(Image, 0, 0, 500,500)
    fill(255)
    if not Spinning then
        local ballpos = vec2(190,0)
        local angle = angleOf(Slot)
        ballpos = ballpos:rotate(angle)
        ellipse(ballpos.x, ballpos.y, 20)
    end
    popMatrix()
    if not Spinning then 
        text("" .. Slot)
    else
        text("" .. math.random(0,36))
    end
end

Oops 24

That video turned up a bug! The slot is 24 but the ball appears in 34. There must be a flaw in my table. Thing one is to fix it. Thing two is to check it for further errors.

    Wheel = {3, 15, 24, 22, 5, 17, 32, 20, 7, 11, 30, 26, 9, 28, 0, 2,14, 35, 23,
    4, 16, 33, 21, 6, 18, 31, 19, 8, 12, 29, 25, 10, 27, 00, 1, 13, 36, 24}

Sure enough there are two 24s in there. Looking at the wheel, I see that the first one should be 34. Fixed. And I got really lucky on my first spin: 24 came up and went to the right place. I better buy a lottery ticket today. Anyway, how can we check this table? As it stands right now, I expect no duplicates except for zero, and I expect all the values to be between 0 and 36. The latter is easy. How can I readily check the duplicates? Well, if the table size is 38 and every integer appears in it, then it is covered. But with the double zero it is possible, in principle, that 31 is duplicated rather than zero. I think I’ll ignore that and just test that I can find each number from zero to 36. For now: we’ve still not dealt with the double zero.

function setup()
    Spinning = false
    Slot = 36
    Time = 0
    Image = readImage("Documents:Roulette")
    Wheel = {3, 15, 34, 22, 5, 17, 32, 20, 7, 11, 30, 26, 9, 28, 0, 2,14, 35, 23,
    4, 16, 33, 21, 6, 18, 31, 19, 8, 12, 29, 25, 10, 27, 00, 1, 13, 36, 24}
    Angle = 0
    checkWheel()
end

function checkWheel()
    for slot = 0, 36 do
        if not angleOf(slot) then print("could not find " .. slot) end
    end
end

That works. And I tried it with 37 instead of 36 and it printed the error, so we’re good. Oh, I forgot to check the size.

function checkWheel()
    local len = 0
    for i, _ in pairs(Wheel) do
        len = len + 1
    end
    print(len)
    for slot = 0, 36 do
        len = len + 1
        if not angleOf(slot) then print("could not find " .. slot) end
    end
end

That prints 38 in the text window and doesn’t print anything about could not find, so we’re in good shape. I think we’re nearly done here. I see two issues I’d like to address: the double zero, and cleaning up the code. Here’s my cunning plan for double zero: roll -1 through 36 rather than 0 through 36, and change the 00 in our table to -1. That will make the -1 roll display as 00. We’ll have to fix the text display too, of course. A bit of a hack but maybe not too terrible. Let’s see. Here’s all the code:

-- R3oulette

function setup()
    Spinning = false
    Slot = 36
    Time = 0
    Image = readImage("Documents:Roulette")
    Wheel = {3, 15, 34, 22, 5, 17, 32, 20, 7, 11, 30, 26, 9, 28, 0, 2,14, 35, 23,
    4, 16, 33, 21, 6, 18, 31, 19, 8, 12, 29, 25, 10, 27, -1, 1, 13, 36, 24}
    Angle = 0
    checkWheel()
end

function touched(ignored)
    Spinning = true
    Time = ElapsedTime
end

-- This function gets called once every frame
function draw()
    if Spinning and ElapsedTime - Time > 5 then
        Slot = math.random(-1,36)
        Spinning = false
    end
    background(40, 40, 50)
    strokeWidth(2)
    translate(WIDTH/2, HEIGHT/2)
    pushMatrix()
    rotate(Angle)
    Angle = Angle + 1
    sprite(Image, 0, 0, 500,500)
    fill(255)
    if not Spinning then
        local ballpos = vec2(190,0)
        local angle = angleOf(Slot)
        ballpos = ballpos:rotate(angle)
        ellipse(ballpos.x, ballpos.y, 20)
    end
    popMatrix()
    if not Spinning then 
        text("" .. display(Slot))
    else
        text("" .. display(math.random(-1,36)))
    end
end

function angleOf(aSlot)
    for i,v in pairs(Wheel) do
        if aSlot == v then return math.pi*2/38.0*(i-1) end
    end
    return nil
end

function checkWheel()
    local len = 0
    for i, _ in pairs(Wheel) do
        len = len + 1
    end
    print(len)
    for slot = 0, 36 do
        len = len + 1
        if not angleOf(slot) then print("could not find " .. slot) end
    end
end

function display(aSlot)
    if aSlot == -1 then return "00" else return "" .. aSlot end
end

I added the display function, put the -1 in the Wheel table, and to test it, I jammed Slot = -1 into the calculation of slot, so that every roll came up double-zero:

Forcing Double Zero

While doing that I noticed the duplication of the call to the random number generator, which needed to be changed twice, once for the answer and once while spinning. As I think about that, we could of course set Slot on every cycle, since we’re displaying a number on every cycle but maybe we’ll save that.

The draw() function is pretty messy now. We should do Composed Function on that and pull out some bits. I see three of note: the calculation of wheel state, drawing the wheel, and drawing the text. Then there’s the actual fussing about with background, strokes and fills.

I did that one Extract Function at a time. I decided to pull out the graphics setup, just to see if I liked it, and I moved the fill() call in there. The result is this:

-- R3oulette

function setup()
    Spinning = false
    Slot = 36
    Time = 0
    Image = readImage("Documents:Roulette")
    Wheel = {3, 15, 34, 22, 5, 17, 32, 20, 7, 11, 30, 26, 9, 28, 0, 2,14, 35, 23,
    4, 16, 33, 21, 6, 18, 31, 19, 8, 12, 29, 25, 10, 27, -1, 1, 13, 36, 24}
    Angle = 0
    checkWheel()
end

function touched(ignored)
    Spinning = true
    Time = ElapsedTime
end

-- This function gets called once every frame
function draw()
    setGraphics()
    calculate()
    drawWheel()
    drawText()
end

function angleOf(aSlot)
    for i,v in pairs(Wheel) do
        if aSlot == v then return math.pi*2/38.0*(i-1) end
    end
    return nil
end

function calculate()
    if Spinning and ElapsedTime - Time > 5 then
        Slot = math.random(-1,36)
        Spinning = false
    end    
end

function checkWheel()
    local len = 0
    for i, _ in pairs(Wheel) do
        len = len + 1
    end
    print(len)
    for slot = 0, 36 do
        len = len + 1
        if not angleOf(slot) then print("could not find " .. slot) end
    end
end

function display(aSlot)
    if aSlot == -1 then return "00" else return "" .. aSlot end
end

function drawText()
    if not Spinning then 
        text("" .. display(Slot))
    else
        text("" .. display(math.random(-1,36)))
    end
end

function drawWheel()
    pushMatrix()
    rotate(Angle)
    Angle = Angle + 1
    sprite(Image, 0, 0, 500,500)
    if not Spinning then
        local ballpos = vec2(190,0)
        local angle = angleOf(Slot)
        ballpos = ballpos:rotate(angle)
        ellipse(ballpos.x, ballpos.y, 20)
    end
    popMatrix()
end

function setGraphics()
    background(40, 40, 50)
    strokeWidth(2)
    translate(WIDTH/2, HEIGHT/2)
    fill(255)
end

Final Run

Frankly, that doesn’t look bad to me. How does it look to you?

Next time, I’ll sum up this series, at least so far. I think I’m done with roulette but you never know. Thanks for reading!