Join us on Discord!
You can help CodeWalrus stay online by donating here.

Need some help speeding up a game I am working on

Started by Strontium, May 04, 2015, 03:50:33 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Strontium

I am making a clone of Greed, a command line game for Linux, on my TI Nspire. It works fine, but it is horrendously slow (on calc, on the emulator it runs fine).

So far, I have only implemented moving the player around to clear the board. There is no code to make sure you make a valid move as of now.

My code so far is as follows:
-- make a global distance value, for some reason
distance = 0

-- font width & heights
-- hardcoded because my other method only worked on the emulator for some reason
strWidth = 7
strHeight = 13

-- window width & height
winWidth = platform.window:width()
winHeight = platform.window:height()

-- window width & height, character wise
wincharWidth = math.floor(winWidth / strWidth)
wincharHeight = math.floor(winHeight / strHeight)

-- colors {{color, style}, {color, style}}
colors = {
  {"0xFFBF00", 'r'},
  {"0xB31B1B", 'r'},
  {"0x006B3C", 'r'},
  {"0x1560BD", 'r'},
  {"0x872657", 'r'},
  {"0xFFBF00", 'b'},
  {"0xB31B1B", 'b'},
  {"0x006B3C", 'b'},
  {"0x1560BD", 'b'},
}
-- player location {x, y}
player = {math.random(1, wincharWidth), math.random(1, wincharHeight)}

-- is the player moving
isMoving = false

-- generate board {{x, y}, {a, b}}
board = {}
for x = 1, wincharWidth do
  board[x] = {}
  for y = 1, wincharHeight do
    board[x][y] = math.random(1, 9)
  end
end
board[player[1]][player[2]] = nil

-- set background color to black
platform.window:setBackgroundColor(0x000000)

-- game loop
function on.timer()
  -- move player
  if isMoving == true then
    -- operate in the appropriate direction
    if direction == 1 then
      -- clear next spot
      board[player[1] - 1][player[2] + 1] = nil
     
      -- change player position
      player[1] = player[1] - 1
      player[2] = player[2] + 1
    elseif direction == 2 then
      -- clear next spot
      board[player[1]][player[2] + 1] = nil
     
      -- change player position
      player[2] = player[2] + 1
    elseif direction == 3 then
      -- clear next spot
      board[player[1] + 1][player[2] + 1] = nil
     
      -- change player position
      player[1] = player[1] + 1
      player[2] = player[2] + 1
    elseif direction == 6 then
      -- clear next spot
      board[player[1] + 1][player[2]] = nil
     
      -- change player position
      player[1] = player[1] + 1
    elseif direction == 9 then
      -- clear next spot
      board[player[1] + 1][player[2] - 1] = nil
     
      -- change player position
      player[1] = player[1] + 1
      player[2] = player[2] - 1
    elseif direction == 8 then
      -- clear next spot
      board[player[1]][player[2] - 1] = nil
     
      -- change player position
      player[2] = player[2] - 1
    elseif direction == 7 then
      -- clear next spot
      board[player[1] - 1][player[2] - 1] = nil
     
      -- change player position
      player[1] = player[1] - 1
      player[2] = player[2] - 1
    elseif direction == 4 then
      -- clear next spot
      board[player[1] - 1][player[2]] = nil
     
      -- change player position
      player[1] = player[1] - 1
    end
    -- stop moving if the player reaches its destination
    if player[1] == destination[1] and player[2] == destination[2] then
      isMoving = false
    end
  end
  platform.window:invalidate()
end
timer.start(0.01)

-- controls
function on.charIn(char)
  -- make sure player isn't already moving
  if isMoving == false then
    -- move player
    if char == '1' then
      direction = 1
      distance = board[player[1] - 1][player[2] + 1]
      destination = {player[1] - distance, player[2] + distance}
      isMoving = true
    elseif char == '2' then
      direction = 2
      distance = board[player[1]][player[2] + 1]
      destination = {player[1], player[2] + distance}
      isMoving = true
    elseif char == '3' then
      direction = 3
      distance = board[player[1] + 1][player[2] + 1]
      destination = {player[1] + distance, player[2] + distance}
      isMoving = true
    elseif char == '6' then
      direction = 6
      distance = board[player[1] + 1][player[2]]
      destination = {player[1] + distance, player[2]}
      isMoving = true
    elseif char == '9' then
      direction = 9
      distance = board[player[1] + 1][player[2] - 1]
      destination = {player[1] + distance, player[2] - distance}
      isMoving = true
    elseif char == '8' then
      direction = 8
      distance = board[player[1]][player[2] - 1]
      destination = {player[1], player[2] - distance}
      isMoving = true
    elseif char == '7' then
      direction = 7
      distance = board[player[1] - 1][player[2] - 1]
      destination = {player[1] - distance, player[2] - distance}
      isMoving = true
    elseif char == '4' then
      direction = 4
      distance = board[player[1] - 1][player[2]]
      destination = {player[1] - distance, player[2]}
      isMoving = true
    end
  end
end

function on.paint(gc)
  -- render the board
  for x = 0, wincharWidth - 1 do
    for y = 0, wincharHeight - 1 do
      if board[x + 1][y + 1] ~= nil then
        -- number appearance
        gc:setColorRGB(colors[board[x + 1][y + 1]][1])
        gc:setFont("sansserif", colors[board[x + 1][y + 1]][2], 10)
     
        -- draw number
        gc:drawString(board[x + 1][y + 1], x * strWidth, y * strHeight)
      end
    end
  end
 
  -- draw the player
  gc:setColorRGB(0xFFFFFF)
  gc:drawString('P', (player[1] * strWidth) - strWidth, (player[2] * strHeight) - strHeight)
end

If someone could help me speed it up, that would be great.

Also, obligatory screenshot:

  • Calculators owned: TI Nspire CX, HP Prime
  • Consoles, mobile devices and vintage computers owned: NES

Adriweb

#1
Ah, I see you use 3.9-style (apilevel 2.4) Nspire-Lua, by using setBackgroundColor and platform.window globally.
Note that this wasn't possible before (the "proper", universal way for w/h is to declare those width/height vars globally (you still can make them local but at the global scope, out of any function I mean), and initialize them with default values first if you want (generally 318, 212) and reassigning them in the on.resize event as it will fire quite early (after construction,activate,restore) with w,h as parameters.

Regarding speedup, your code is simple enough that it's a bit sad that it's slow on the device (well, I haven't tested but you say it is).
One thing I could see speeding it up greatly (and the only thing I can think of right now quickly reading your code *edit* well, nevermind, see posts below, don't always use the timer and paint !) would be to add partial invalidates instead of a full one, in the update loop, and thus only repaint the correct area in on.paint.
It should be fairly easy with this kind of grid-like based game.
Turns out that when benchmarking this technique (see here, but you only see the end result, and while it's not what's tested there, you may get this kind of speedup), the calc can even get slightly faster than TINCS (which doesn't care about partial invalidate...).

In general, I'd advise you to take a look at my presentations on Lua optimization and advanced scripting techniques *(shown at the T3 conferences in 2013 and 2014, and the first one being based on the one Levak and I did while working at TI India a few years ago (whoo, time flies)). Of course, take a look at Steve Arnold's tutorials if you haven't already, and Inspired-Lua in general (particularly the wiki, I guess) :)

* I really need to upload those on TI-Planet at some point, free.fr messed up...
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

Strontium

#2
I'll try what you suggested. Once I am done, I'll update this reply with my results.

Edit: So, I tried having platform.window:invalidate() only update the area it needed to. Here is the resulting code:
platform.window:invalidate(player[1] * strWidth - strWidth * 3, player[2] * strHeight - strHeight * 3, strWidth * 5, strHeight * 5)
It didn't help much, not to mention how ugly it is.

I also couldn't find anywhere I could implement the optimization tips in your presentation, unfortunately.
  • Calculators owned: TI Nspire CX, HP Prime
  • Consoles, mobile devices and vintage computers owned: NES

Adriweb

Quote from: Strontium on May 04, 2015, 04:56:10 AMplatform.window:invalidate(player[1] * strWidth - strWidth * 3, player[2] * strHeight - strHeight * 3, strWidth * 5, strHeight * 5)
It didn't help much, not to mention how ugly it is.
Hmm, woot ?
When the area is properly setup, there should be no visual difference.
Also you can try mixing in gc:clipRect, see if that helps

Quote from: Strontium on May 04, 2015, 04:56:10 AMI also couldn't find anywhere I could implement the optimization tips in your presentation, unfortunately.
Yeah, that was in general, your code being sufficiently short and "easy", it wasn't really meant for this specific case.
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

Strontium

Quote from: Adriweb on May 04, 2015, 05:31:35 AM
Quote from: Strontium on May 04, 2015, 04:56:10 AMplatform.window:invalidate(player[1] * strWidth - strWidth * 3, player[2] * strHeight - strHeight * 3, strWidth * 5, strHeight * 5)
It didn't help much, not to mention how ugly it is.
Hmm, woot ?
When the area is properly setup, there should be no visual difference.
Also you can try mixing in gc:clipRect, see if that helps
I meant the actual code looked ugly :P

Also, what exactly does gc:clipRect() do? According to the documentation:
QuoteSets the clipping rectangle for subsequent graphics operations.
but I don't know what a clipping rectangle is.
  • Calculators owned: TI Nspire CX, HP Prime
  • Consoles, mobile devices and vintage computers owned: NES

Adriweb

#5
Here's a code where the timer only fires when necessary (thus the invalidating will only happen when necessary too, and that's a huge cpu gain):
Tell me if that's better (without the clipRect already), on the device (a clipping rectangle is just to say that things not in this rectangle will not be painted, but for simple things like that maybe it's the same as the partial invalidate, maybe it's better, I actually haven't tested).

-- make a global distance value, for some reason
distance = 0

-- colors {{color, style}, {color, style}}
colors = {
  {"0xFFBF00", 'r'},
  {"0xB31B1B", 'r'},
  {"0x006B3C", 'r'},
  {"0x1560BD", 'r'},
  {"0x872657", 'r'},
  {"0xFFBF00", 'b'},
  {"0xB31B1B", 'b'},
  {"0x006B3C", 'b'},
  {"0x1560BD", 'b'},
}

-- is the player moving
isMoving = false

function on.resize(w,h)
    -- font width & heights
    -- hardcoded because my other method only worked on the emulator for some reason
    strWidth, strHeight = 7,13
   
    -- window width & height
    winWidth, winHeight = w, h
   
    -- window width & height, character wise
    wincharWidth = math.floor(winWidth / strWidth)
    wincharHeight = math.floor(winHeight / strHeight)
   
    reset()
end

function reset()
    -- player location {x, y}
    player = {math.random(1, wincharWidth), math.random(1, wincharHeight)}
   
    -- generate board {{x, y}, {a, b}}
    board = {}
    for x = 1, wincharWidth do
      board[x] = {}
      for y = 1, wincharHeight do
        board[x][y] = math.random(1, 9)
      end
    end
    board[player[1]][player[2]] = nil
    platform.window:invalidate()
end
on.escapeKey = reset

-- game loop
function on.timer()
  -- move player
  if isMoving then
    -- operate in the appropriate direction
    if direction == 1 then
      -- clear next spot
      board[player[1] - 1][player[2] + 1] = nil
     
      -- change player position
      player[1] = player[1] - 1
      player[2] = player[2] + 1
    elseif direction == 2 then
      -- clear next spot
      board[player[1]][player[2] + 1] = nil
     
      -- change player position
      player[2] = player[2] + 1
    elseif direction == 3 then
      -- clear next spot
      board[player[1] + 1][player[2] + 1] = nil
     
      -- change player position
      player[1] = player[1] + 1
      player[2] = player[2] + 1
    elseif direction == 6 then
      -- clear next spot
      board[player[1] + 1][player[2]] = nil
     
      -- change player position
      player[1] = player[1] + 1
    elseif direction == 9 then
      -- clear next spot
      board[player[1] + 1][player[2] - 1] = nil
     
      -- change player position
      player[1] = player[1] + 1
      player[2] = player[2] - 1
    elseif direction == 8 then
      -- clear next spot
      board[player[1]][player[2] - 1] = nil
     
      -- change player position
      player[2] = player[2] - 1
    elseif direction == 7 then
      -- clear next spot
      board[player[1] - 1][player[2] - 1] = nil
     
      -- change player position
      player[1] = player[1] - 1
      player[2] = player[2] - 1
    elseif direction == 4 then
      -- clear next spot
      board[player[1] - 1][player[2]] = nil
     
      -- change player position
      player[1] = player[1] - 1
    end
    -- stop moving if the player reaches its destination
    if player[1] == destination[1] and player[2] == destination[2] then
      isMoving = false
      timer.stop()
    end
  end
  platform.window:invalidate(player[1] * strWidth - strWidth * 3, player[2] * strHeight - strHeight * 3, strWidth * 5, strHeight * 5)
end

-- controls
function on.charIn(char)
  -- make sure player isn't already moving
  if not isMoving then
    -- move player
    if char == '1' then
      direction = 1
      distance = board[player[1] - 1][player[2] + 1]
      destination = {player[1] - distance, player[2] + distance}
      isMoving = true
    elseif char == '2' then
      direction = 2
      distance = board[player[1]][player[2] + 1]
      destination = {player[1], player[2] + distance}
      isMoving = true
    elseif char == '3' then
      direction = 3
      distance = board[player[1] + 1][player[2] + 1]
      destination = {player[1] + distance, player[2] + distance}
      isMoving = true
    elseif char == '6' then
      direction = 6
      distance = board[player[1] + 1][player[2]]
      destination = {player[1] + distance, player[2]}
      isMoving = true
    elseif char == '9' then
      direction = 9
      distance = board[player[1] + 1][player[2] - 1]
      destination = {player[1] + distance, player[2] - distance}
      isMoving = true
    elseif char == '8' then
      direction = 8
      distance = board[player[1]][player[2] - 1]
      destination = {player[1], player[2] - distance}
      isMoving = true
    elseif char == '7' then
      direction = 7
      distance = board[player[1] - 1][player[2] - 1]
      destination = {player[1] - distance, player[2] - distance}
      isMoving = true
    elseif char == '4' then
      direction = 4
      distance = board[player[1] - 1][player[2]]
      destination = {player[1] - distance, player[2]}
      isMoving = true
    end
    timer.start(0.01)
  end
end


-- set background color to black
platform.window:setBackgroundColor(0x000000)

function on.paint(gc)
  -- render the board
  for x = 0, wincharWidth - 1 do
    for y = 0, wincharHeight - 1 do
      if board[x + 1][y + 1] then
        -- number appearance
        gc:setColorRGB(colors[board[x + 1][y + 1]][1])
        gc:setFont("sansserif", colors[board[x + 1][y + 1]][2], 10)
     
        -- draw number
        gc:drawString(board[x + 1][y + 1], x * strWidth, y * strHeight)
      end
    end
  end
 
  -- draw the player
  gc:setColorRGB(0xFFFFFF)
  gc:drawString('P', (player[1] * strWidth) - strWidth, (player[2] * strHeight) - strHeight)
end


Edit: updated the code to make it more modular.
Edit2: a bit better

General note: you could also optimize the on.paint to loop only through whatever needs to be painted (in which case, maybe not even loop at all anymore, only one time at the beginning to draw the whole board)
General note 2:regarding detecting the edges, instead of having a whole bunch of ifs (if that's what you were going to do), take a look at the __index key in the metatables (here, of the 'board' table) It's actually pretty much what I explain in my slides linked above (first one), page 13.
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

Strontium

#6
I am beginning to see performance improvements. It's still slow, but nows its at a point at which it feels more playable.

Also, if I modify the on.paint code, it will probably be a last resort. I want to try to keep the code as simple as possible.
  • Calculators owned: TI Nspire CX, HP Prime
  • Consoles, mobile devices and vintage computers owned: NES

Adriweb

#7
well, the on.paint modification is basically just an if (firstTime) then ...loops...  else ...ligher paints... end.
So it's definitely not that ugly.
And anyway, in the end, it's the performance that counts, for the user :)

Edit: see my general notes that I edited after, in my post above.
  • Calculators owned: TI-Nspire CX CAS, TI-Nspire CX, TI-Nspire CAS (x3), TI-Nspire (x2), TI-Nspire CM-C CAS, TI-Nspire CAS+, TI-80, TI-82 Stats.fr, TI-82 Plus, TI-83 Plus, TI-83 Plus.fr USB, TI-84+, TI-84+ Pocket SE, TI-84+ C Silver Edition, TI-84 Plus CE, TI-89 Titanium, TI-86, TI-Voyage 200, TI-Collège Plus, TI-Collège Plus Solaire, 3 HP, some Casios
Co-founder & co-administrator of TI-Planet and Inspired-Lua

Strontium

It's definitely much, much faster with the changes you made. I'll implement the on.paint modifications if I need to after the game is completed :P

And yeah, I was going to check for edges with a bunch of ifs. I'll be sure to use __index instead.
  • Calculators owned: TI Nspire CX, HP Prime
  • Consoles, mobile devices and vintage computers owned: NES

Powered by EzPortal