I've been trying this issue I have been having for the past few hours, and I am extremely frustrated by it right now.
In the game I am working on, a port of the CLI game 'greed' for Linux. If the player tries to move right when their player is on the very right side of the screen (or if they try to move left when the player is on the very left side of the screen), the game crashes, with this error:
attempt to index field '?' (a nil value)
This happens in on.charIn, where the player movement code is.
Can someone help me with this? I have tried literally everything I can think of and it has not helped.
Current code:
-- colors {{color, style}, {color, style}}
local colors = {
{"0xFFBF00", 'r'},
{"0xB31B1B", 'r'},
{"0x006B3C", 'r'},
{"0x1560BD", 'r'},
{"0x872657", 'r'},
{"0xFFBF00", 'b'},
{"0xB31B1B", 'b'},
{"0x006B3C", 'b'},
{"0x1560BD", 'b'},
}
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()
score = 0
var.store('score', score)
var.unmonitor('score')
-- 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
-- set background color to black
platform.window:setBackgroundColor(0x000000)
-- controls
function on.charIn(char)
-- WARNING:
-- I use "notblocked" and "notblocked_" here a lot. I tried to be descriptive, k?
-- make sure player isn't already moving
if not isMoving then
-- find how far and what direction to move the player
if char == '1' then
notBlocked, notBlocked_ = pcall(function() return board[player[1] - 1][player[2] + 1] end)
print(notBlocked, notBlocked_)
if not notBlocked or notBlocked_ then
distance = board[player[1] - 1][player[2] + 1]
destination = {player[1] - distance, player[2] + distance}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1] - i][player[2] + i] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1] - i][player[2] + i] = nil
end
-- change player position
player[1] = player[1] - distance
player[2] = player[2] + distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '2' and board[player[1]][player[2] + 1] then
notBlocked, notBlocked_ = pcall(function() return board[player[1]][player[2] + i] end)
if notBlocked or notBlocked_ then
distance = board[player[1]][player[2] + 1]
destination = {player[1], player[2] + distance}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1]][player[2] + i] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1]][player[2] + i] = nil
end
-- change player position
player[2] = player[2] + distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '3' and board[player[1] + 1][player[2] + 1] then
distance = board[player[1] + 1][player[2] + 1]
destination = {player[1] + distance, player[2] + distance}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1] + i][player[2] + i] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1] + i][player[2] + i] = nil
end
-- move player
player[1] = player[1] + distance
player[2] = player[2] + distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '6' and board[player[1] + 1][player[2]] then
distance = board[player[1] + 1][player[2]]
destination = {player[1] + distance, player[2]}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1] + i][player[2]] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1] + i][player[2]] = nil
end
-- move player
player[1] = player[1] + distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '9' and board[player[1] + 1][player[2] - 1] then
distance = board[player[1] + 1][player[2] - 1]
destination = {player[1] + distance, player[2] - distance}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1] + i][player[2] - i] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1] + i][player[2] - i] = nil
end
-- move player
player[1] = player[1] + distance
player[2] = player[2] - distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '8' and board[player[1]][player[2] - 1] then
distance = board[player[1]][player[2] - 1]
destination = {player[1], player[2] - distance}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1]][player[2] - i] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1]][player[2] - i] = nil
end
--move player
player[2] = player[2] - distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '7' and board[player[1] - 1][player[2] - 1] then
distance = board[player[1] - 1][player[2] - 1]
destination = {player[1] - distance, player[2] - distance}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1] - i][player[2] - i] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1] - i][player[2] - i] = nil
end
-- move player
player[1] = player[1] - distance
player[2] = player[2] - distance
-- increase score
score = score + distance
isMoving = false
end
elseif char == '4' and board[player[1] - 1][player[2]] then
distance = board[player[1] - 1][player[2]]
destination = {player[1] - distance, player[2]}
isMoving = true
-- make sure move is valid
for i = 1, distance do
notBlocked, notBlocked_ = pcall(function() return board[player[1] - i][player[2]] end)
if not notBlocked or not notBlocked_ then
isMoving = false
break
end
end
if isMoving then
-- clear spots
for i = 1, distance do
board[player[1] - i][player[2]] = nil
end
-- move player
player[1] = player[1] - distance
-- increase score
score = score + distance
isMoving = false
end
end
var.store('score', score)
end
if char == '5' then
reset()
end
platform.window:invalidate()
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] 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:setFont("sansserif", 'r', 10)
gc:drawString('P', (player[1] * strWidth) - strWidth, (player[2] * strHeight) - strHeight)
end
TL;DR if I move off the screen on the X axis, the program crashes. If I move of the screen on the Y axis, the program is fine.
Well, I don't have time to go into specifics to actually give you some code, but do NOT use pcall for things like this. Really.
I mean, it's much worse than a bunch of ifs.
And well, what you need to do is just bound checking, AFAICS. It's clearly an easily solved problem when you think about it for a bit.
What I'd recommend doing is to store in some temporary variable the next position of what the player *would* be. Then, if it's an allowed value, use is as an index for getting the table element. So you won't get errors due to nil values.
So, instead of directly having unchecked read/writes of/to "board[player[1] - i][player[2] + i]", look at what player[1]-i is, same for +i.
Also, you should hugely refactor the on.charIn to do the least amount of code duplication possible. It should be doable at great length, considering the code look similar in many situations.
Let me know how that goes, and I'll come back later
I tried many different ways to avoid using pcall. The first solution, and really the only solution I could think of, involved using metatables. This somewhat worked. For example, I was able to have table[invalidindex] return whatever I wanted, but table[invalidindex][otherinvalidindex] would always error.
As for fixing code duplication, I *just* came up with a method of doing that.
I think the best way of fixing my problems is just to rewrite the game, since at this point, the code is spaghetti.