Error in method

Next is the Draw_Functions tab:

function drawBuilding()
    pushStyle()
    
    fill(200, 200, 200, 255)
    rect(20, 20, 710, 730)
    stroke(0, 0, 0, 255)
    strokeWidth(5)
    line(25, 25, 500, 25)
    line(25, 205, 500, 205)
    line(25, 385, 500, 385)
    line(25, 565, 500, 565)
    fill(150, 150, 150, 255)
    strokeWidth(4)
    rect(50, 205, 70, 140)
    rect(50, 385, 70, 140)
    rect(50, 565, 70, 140)
    rect(190, 205, 70, 140)
    rect(190, 385, 70, 140)
    rect(190, 565, 70, 140)
    rect(330, 205, 70, 140)
    rect(330, 385, 70, 140)
    rect(330, 565, 70, 140)
    fill(220, 220, 220, 255)
    strokeWidth(1)
    ellipse(108, 270, 15)
    ellipse(108, 450, 15)
    ellipse(108, 630, 15)
    ellipse(248, 270, 15)
    ellipse(248, 450, 15)
    ellipse(248, 630, 15)
    ellipse(388, 270, 15)
    ellipse(388, 450, 15)
    ellipse(388, 630, 15)
    
    popStyle()
end

function drawCallButtons()
    pushStyle()
    -- draw call button arrows
    strokeWidth(3)
    stroke(0, 0, 0, 255)
    
    -- level 1 up
    if lev1Up then
        lev1UpMesh.colors = {colorLit, colorLit, colorLit}
    else lev1UpMesh.colors = {colorDark, colorDark, colorDark}
    end
    lev1UpMesh:draw()
    line(480, 180, 500, 180)
    line(500, 180, 490, 195)
    line(480, 180, 490, 195)
    
    -- level 2 up
    if lev2Up then
        lev2UpMesh.colors = {colorLit, colorLit, colorLit}
    else lev2UpMesh.colors = {colorDark, colorDark, colorDark}
    end
    lev2UpMesh:draw()
    line(480, 360, 500, 360)
    line(500, 360, 490, 375)
    line(480, 360, 490, 375)
    
    -- level 2 down
    if lev2Down then
        lev2DownMesh.colors = {colorLit, colorLit, colorLit}
    else lev2DownMesh.colors = {colorDark, colorDark, colorDark}
    end
    lev2DownMesh:draw()
    line(480, 355, 500, 355)
    line(500, 355, 490, 340)
    line(480, 355, 490, 340)
    
    -- level 3 up
    if lev3Up then
        lev3UpMesh.colors = {colorLit, colorLit, colorLit}
    else lev3UpMesh.colors = {colorDark, colorDark, colorDark}
    end
    lev3UpMesh:draw()
    line(480, 540, 500, 540)
    line(500, 540, 490, 555)
    line(480, 540, 490, 555)
    
    -- level 3 down
    if lev3Down then
        lev3DownMesh.colors = {colorLit, colorLit, colorLit}
    else lev3DownMesh.colors = {colorDark, colorDark, colorDark}
    end
    lev3DownMesh:draw()
    line(480, 535, 500, 535)
    line(500, 535, 490, 520)
    line(480, 535, 490, 520)
    
    -- level 4 down
    if lev4Down then
        lev4DownMesh.colors = {colorLit, colorLit, colorLit}
    else lev4DownMesh.colors = {colorDark, colorDark, colorDark}
    end
    lev4DownMesh:draw()
    line(480, 715, 500, 715)
    line(500, 715, 490, 700)
    line(480, 715, 490, 700)
    popStyle()
end

function drawPeople()
    if persons[1] ~= nil then
        for i = 1, #persons do   
            persons[i]:draw()
        end 
    end
end

Next, the Other_Functions tab:

function setCallButtons()
    -- call button states
    lev1Up = false
    lev2Down = false
    lev2Up = false
    lev3Down = false
    lev3Up = false
    lev4Down = false
    
    -- call button colors
    colorLit = color(0, 0, 255, 255)
    colorDark = color(150, 150, 150, 255)
    
    -- call button meshes
    lev1UpMesh = mesh()
    lev1UpMesh.vertices = {vec2(480,180),vec2(500,180),vec2(490,195)}
    lev1UpMesh.colors = {colorDark, colorDark, colorDark}
    lev2UpMesh = mesh()
    lev2UpMesh.vertices = {vec2(480,360),vec2(500,360),vec2(490,375)}
    lev2UpMesh.colors = {colorDark, colorDark, colorDark}
    lev2DownMesh = mesh()
    lev2DownMesh.vertices = {vec2(480,355),vec2(500,355),vec2(490,340)}
    lev2DownMesh.colors = {colorDark, colorDark, colorDark}
    lev3UpMesh = mesh()
    lev3UpMesh.vertices = {vec2(480,540),vec2(500,540),vec2(490,555)}
    lev3UpMesh.colors = {colorDark, colorDark, colorDark}
    lev3DownMesh = mesh()
    lev3DownMesh.vertices = {vec2(480,535),vec2(500,535),vec2(490,520)}
    lev3DownMesh.colors = {colorDark, colorDark, colorDark}
    lev4DownMesh = mesh()
    lev4DownMesh.vertices = {vec2(480,715),vec2(500,715),vec2(490,700)}
    lev4DownMesh.colors = {colorDark, colorDark, colorDark}
end

function addPerson()
    table.insert(persons, Person(30, 30))
    floor1Q:addTo(persons[#persons])
end

function movePeople()
    if persons[1] ~= nil then
        for i = 1, #persons do
            persons[i]:move()
        end
    end
end

function test()
    elevatorQ:addTo(floor1Q:releaseFrom(1))
    print("test")
end

function test2()
    elevator1:moveTo(205)
    print("test2")
end

Next, the Person class:

Person = class()

function Person:init(x, y)
    -- location of draw origin
    self.x = x
    self.y = y
    self.targetx = 150
    
    -- current motion state
    -- 1 - moving toward elevator wait queue
    -- 2 - waiting for elevator to arrive
    -- 3 - moving into elevator queue
    -- 4 - waiting for elevator to reach floor
    -- 5 - moving toward destination room
    -- 6 - waiting in room
    self.state = 1
    
    -- flag to change state
    self.bumpState = false
    
    -- floor currently on
    self.currentFloor = 1
    
    -- destination floor
    self.destFloor = math.random(2, 4)
    self.destRoom = math.random(1, 3)
end

function Person:draw()
    -- draw shape of person
    pushStyle()
    smooth()
    fill(255, 255, 255, 131)
    stroke(0, 0, 0, 255)
    strokeWidth(3)
    rect(self.x, self.y, 50, 80)
    strokeWidth(2)
    ellipse(self.x + 25, self.y + 103, 50, 50)
    popStyle()
    
    -- check for need to change state
    if self.bumpState == true then
        self.state = self.state + 1
        self.bumpState = false
    end
    
    
end

function Person:move()
    if self.x < self.targetx then
        self.x = self.x + 1
    end
    if self.x > self.targetx then
        self.x = self.x - 1
    end
end

--function Person:moveRight()
    --self.x = self.x + 1
--end

--function Person:moveLeft()
    --self.x = self.x - 1
--end

function Person:moveUp()
    self.y = self.y + 1
end

function Person:moveDown()
    self.y = self.y - 1
end

function Person:touched(touch)
    -- Codea does not automatically call this method
end

Next, the Queue class (I hope the problem isn’t here, but it probably is):

Queue = class()

function Queue:init(a, b)
    -- a sets queue length
    -- b sets place moving person stops
    self.length = a
    self.firstX = b
    
    self.qtable = {}
    for i = 1, self.length do
        self.qtable[i] = {}
        for j = 1, 2 do
            self.qtable[i] [j] = 0
        end
    end
    j = self.firstX
    for i = 1, self.length do
        self.qtable[i] [2] = j
        j = j - 10
    end
end

function Queue:addTo(p)
    -- this function will add a person to the queue in the first open spot
    local person = p
    if person ~= false then
        for i = 1, self.length do
            if self.qtable[i] [1] == 0 then
                self.qtable[i] [1] = person
                person.targetx = self.qtable[i] [2]
                break
            end
        end
    end
end

function Queue:releaseFrom(x)
    local position = x
    local counter = 0
    for i = 1, self.length do
        if self.qtable[i] [1] ~= 0 then
            counter = counter + 1
        end
    end
    local diff = counter - position + 2
    local person = self.qtable[position] [1]
    self.qtable[position] [1] = 0
    for i = position, diff do
        local j = i + 1
        local nextPerson = self.qtable[j] [1]
        if nextPerson ~= 0 then
            nextPerson.targetx = self.qtable[i] [2]
        end
        self.qtable[i] [1] = nextPerson
        self.qtable[j] [1] = 0
    end
    if person ~= 0 then
    return person
    else return false
    end
end

function Queue:numberIn()
    local number = 0
    for i = 1, self.length do
        if self.qtable[i] [1] ~= 0 then
            number = number + 1
        end
    end
    return number
end

function Queue:draw()
    -- Codea does not automatically call this method
end

function Queue:touched(touch)
    -- Codea does not automatically call this method
end

Next, the Elevator class:

Elevator = class()

function Elevator:init(x, y)
    -- you can accept and set parameters here
    self.x = x
    self.y = y
    self.currentFloor = 1
    self.destFloor = 1
    self.targety = 25
    
    -- floor button lights off
    self.light1 = false
    self.light2 = false
    self.light3 = false
    self.light4 = false
    
    -- current motion state
    -- 1 - idle
    -- 2 - door opening
    -- 3 - door closing
    -- 4 - moving up
    -- 5 - moving down
    
    self.state = 1
    
    self.doorHeight = 180
end

function Elevator:draw()
    pushStyle()
    stroke(0, 0, 0, 255)
    strokeWidth(5)
    
    -- door opening
    if self.state == 2 then
        self.doorHeight = self.doorHeight - 2
        if self.doorHeight == 0 then
            self.state = 1
        end
    end
    
    -- door closing
    if self.state == 3 then
        self.doorHeight = self.doorHeight + 2
        if self.doorHeight == 180 then
            self.state = 1
        end
    end
    
    -- draw elevator outline
    line(self.x, self.y, self.x + 215, self.y)
    line(self.x + 215, self.y, self.x + 215, self.y + 180)
    line(self.x + 215, self.y + 180, self.x, self.y + 180)
    -- line for door
    line(self.x, self.y, self.x, self.y + self.doorHeight)
    
    -- draw floor button lights
    strokeWidth(2)
    fill(150, 150, 150, 255)
    ellipse(self.x + 15, self.y + 85, 20, 20)
    ellipse(self.x + 15, self.y + 110, 20, 20)
    ellipse(self.x + 15, self.y + 135, 20, 20)
    ellipse(self.x + 15, self.y + 160, 20, 20)
    fill(0, 255, 0, 255)
    if self.light1 then
        ellipse(self.x + 15, self.y + 85, 20, 20)
    end
    if self.light2 then
        ellipse(self.x + 15, self.y + 110, 20, 20)
    end
    if self.light3 then
        ellipse(self.x + 15, self.y + 135, 20, 20)
    end
    if self.light4 then
        ellipse(self.x + 15, self.y + 160, 20, 20)
    end
    popStyle()
end

function Elevator:move()
    if self.y > self.targety then
        self.y = self.y - 1
        if elevatorQ:numberIn() >= 1 then
            for i = 1, elevatorQ:numberIn() do
                elevatorQ[i] [1]:moveDown()
            end
        end
    end
    if self.y < self.targety then
        self.y = self.y + 1
        if elevatorQ:numberIn() >= 1 then
            for i = 1, elevatorQ:numberIn() do
                elevatorQ[i] [1]:moveUp()
            end
        end
    end
end

function Elevator:moveTo(y)
    self.targety = y
end


Next are the roundRect tab and Button classes I got from tutorials and example programs. Here’s roundRect:

function roundRect(x,y,w,h,r)
    pushStyle()
    
    insetPos = vec2(x+r,y+r)
    insetSize = vec2(w-2*r,h-2*r)
    
    --Copy fill into stroke
    local red,green,blue,a = fill()
    stroke(red,green,blue,a)
    
    noSmooth()
    rectMode(CORNER)
    rect(insetPos.x,insetPos.y,insetSize.x,insetSize.y)
    
    if r > 0 then
        smooth()
        lineCapMode(ROUND)
        strokeWidth(r*2)

        line(insetPos.x, insetPos.y, 
             insetPos.x + insetSize.x, insetPos.y)
        line(insetPos.x, insetPos.y,
             insetPos.x, insetPos.y + insetSize.y)
        line(insetPos.x, insetPos.y + insetSize.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)
        line(insetPos.x + insetSize.x, insetPos.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)            
    end
    popStyle()
end

And here’s the Button class:

Button = class()

function Button:init(displayName)
    -- you can accept and set parameters here
    self.displayName = displayName
    
    self.pos = vec2(0,0)
    self.size = vec2(0,0)
    self.action = nil
    self.color = color(124, 123, 126, 255)
end

function Button:draw()
    -- Codea does not automatically call this method
    pushStyle()
    fill(self.color)
    
    font("ArialRoundedMTBold")
    fontSize(22)
    
    -- use name for size
    local w,h = textSize(self.displayName)
    w = w + 20
    h = h + 30
    
    roundRect(self.pos.x - w/2,
              self.pos.y - h/2,
              w,h,30)
            
    self.size = vec2(w,h)
            
    textMode(CENTER)
    fill(54, 65, 96, 255)
    text(self.displayName,self.pos.x+2,self.pos.y-2)
    fill(255, 255, 255, 255)
    text(self.displayName,self.pos.x,self.pos.y)
    
    popStyle()
end

function Button:hit(p)
    local l = self.pos.x - self.size.x/2
    local r = self.pos.x + self.size.x/2
    local t = self.pos.y + self.size.y/2
    local b = self.pos.y - self.size.y/2
    if p.x > l and p.x < r and
       p.y > b and p.y < t then
        return true
    end
    
    return false
end

function Button:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == ENDED and
       self:hit(vec2(touch.x,touch.y)) then
        if self.action then
            self.action()
        end
    end
end

That’s everything. This is my second pass at writing this program; the first try used simple tables to contain Person objects - it got further along, the elevator kinda worked, people rode it up and got off at their floor, but the code just got buggier and buggier as I added to it. Time to refactor, and to make a better way to keep track of people - the Queue!

@Eustace I loaded your code. When I press add person, the person moves to the right. When I press test, the person gets on the elevator. When I press test 2 the program stops with the nil error. When I print the value of I before the line that gives the error, it has a value of nil. Is that the correct sequence of pressing the buttons.

@Eustace

You’re not accessing elevatorQ correctly.

elevatorQ is an instance of your Queue class.

So you cannot directly index it with elevatorQ[i][1] or whatever.

I think what you mean to index is the qtable of elevatorQ, no?

So it should be elevatorQ.qtable[i][1].

I’m not sure what you mean by value of i.
If I replace the inner for/do loop with:

for i = 1, elevatorQ:numberIn() do
            --elevatorQ[i] [1]:moveUp()
            print(i)
            end

…so that the problem line is commented out, then when the code runs the value of i is printed out over and over as the elevator rises without its passengers.

Do you mean the value stored at elevatorQ[i] [1]? I haven’t tried that. I’m not sure how print() would handle a reference to an object.

@Eustace My mistake. I left the print statement in the wrong spot when I was printing something else. See what @yojimbo2000 has to say.

@Eustace elevatorQ is not a table. So you cannot subscript it like a table. It is an instance of a class, which contains a table called qtable. That is what you should be trying to subscript with elevatorQ.qtable[i][1]

Somehow I knew that when the answer appeared, two things would happen:

  1. I would feel a bit dim for not realizing what the problem was, and
  2. I would learn something valuable about Lua.
    Thanks, yojimbo2000.

Thanks to all of you, really. What a great forum!

you’re welcome, another pair of eyes on some code is often all that’s required.

Lua’s flexibility with types does make it easy to work with, but does also lead to errors like this one, which would be caught by the compiler in languages that have type-safety.

With nested arrays you can do a series of tests before you try to subscript eg

if elevatorQ.qtable[i] and elevatorQ.qtable[i][1] then elevatorQ.qtable[i][1]:moveUp() end

this is more concise then checking whether the two indices are in range and will prevent the “Attempt to index nil value” error, as Lua will stop evaluating the expression as soon as it hits the first failing clause (although it will just fail silently if either index is invalid, which might also lead to unintended side effects).

That looks like a useful check, thanks.

At least with your code, you knew exactly what line the error was. So “unsafe” code (as in, easy to make crash) can be more communicative than code that fails silently (it doesn’t crash, but doesn’t tell you there’s an issue either).