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:
(https://i.imgur.com/sL4J4Ey.jpg)
Ah, I see you use 3.9-style (http://wiki.inspired-lua.org/Changes_in_OS_3.9) (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 (http://bit.ly/1I71leG), 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 (http://www.mirari.fr/biD5) and advanced scripting techniques (http://www.mirari.fr/YZgJ) *(shown at the T3 conferences in 2013 and 2014, and the first one being based on the one (https://tiplanet.org/forum/archives_voir.php?id=6720) 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 (http://compasstech.com.au/TNS_Authoring/Scripting/) if you haven't already, and Inspired-Lua (http://inspired-lua.org) in general (particularly the wiki (https://wiki.inspired-lua.org), I guess) :)
* I really need to upload those on TI-Planet at some point, free.fr messed up...
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.
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 (http://wiki.inspired-lua.org/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.
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 (http://wiki.inspired-lua.org/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.
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.
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.
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.
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.