Reputation: 1327
I am new to lua and fairly new to programming. I am trying to achieve the following.
I have a table of numbers from which I pick a random number.
myTable = {}
for i = 1 to 100 do
table.insert(myTable, i)
end
local numberChosen = myTable[math.random(#myTable)]
So far, so good. The next time a number is picked, I want that number to be removed from the table. I know lua doesn't remove values, they stay there as nil. So
table.remove(myTable, numberChosen)
Doesn't work as when I try to run the random function again, if the value is nil, I get "bad argument #1 to 'random' (interval is empty)"
I have tried creating a function like this:
function cleanTable(t)
local cleanTable = {}
for k, v in ipairs(t)do
if v ~= nil then
table.insert(cleanTable, v)
end
end
return cleanTable
end
myTable = cleanTable(myTable)
But that does't work either as the random function returns the same error. Can anyone help?
edit ----------------------------------------------------------------------
My table contains keys and values like so:
local columns = {1,2,3,4,5,6,7,8,9,10,11,12}
local rows = {1,2,3,4,5,6,7,8,9,10,11,12}
local coupledNumbers = {}
for i = 1, #columns do
for a = 1, #rows do
local coupledNumber = i * a
table.insert(coupledNumbers, i*a, coupledNumber)
end
end
How do I remove the key and the value? thanks
Upvotes: 2
Views: 1871
Reputation: 22421
I know lua doesn't remove values, they stay there as nil.
No, that's not how it works. That's how it works according to docs:
The table.remove function removes (and returns) an element from a given position in an array, moving down other elements to close space and decrementing the size of the array.
-- https://www.lua.org/pil/19.2.html.
Here's your problem:
local randomPosition = math.random(#myTable) -- get random POSITION
local numberChosen = myTable[randomPosition] -- get VALUE from that POSITION
table.remove(myTable, numberChosen) -- try to remove element, but instead of specifying its position as .remove documentation demands you specify some arbitrary value you've found at that position.
Your logic works for the first time purely by coincidence, only because you just happen to have an array with values matching its positions.
You need to remove by position instead: table.remove(myTable, randomPosition)
.
Your second function is not only useless, it makes no sense. for in ipairs
will never return nil value - it's end of array, so for loop will just close.
Upvotes: 0
Reputation: 560
You can use a key-value-table instead of a numeric table. Code is a bit ugly.
local columns = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
local rows = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
local coupledNumbers = {}
-- same like `i = 1, 12` - you don't even need the tables `columns` and `rows`, 'cause you aren't indexing
for i = 1, #columns do
for j = 1, #rows do
local idx = i * j
if not coupledNumbers[idx] then
coupledNumbers[idx] = true
end
end
end
local function getRand(n)
local res
if next(coupledNumbers) ~= nil then
repeat
local r = math.random(n)
if coupledNumbers[r] then
res, coupledNumbers[r] = r, nil
end
until res
end
return res
end
print(getRand(144))
print(getRand(144))
print(getRand(144))
print(getRand(144))
print('---')
for i in pairs(coupledNumbers) do
print(i)
end
Upvotes: 0
Reputation: 5031
You are improperly defining numberChosen
. when you define
local numberChosen = myTable[math.random(#myTable)]
You are mistakenly defining numberChosen to the value at some arbitrary index in the table.
Let us step through a few loops of your code
Loop: 1 numberChosen: 1 #myTable 100
Loop: 2 numberChosen: 57 #myTable 99
Loop: 3 numberChosen: 20 #myTable 98
Loop: 4 numberChosen: 82 #myTable 97
Loop: 5 numberChosen: 60 #myTable 96
Loop: 6 numberChosen: 48 #myTable 95
Loop: 7 numberChosen: 35 #myTable 94
Loop: 8 numberChosen: 91 #myTable 93
Loop: 9 numberChosen: 82 #myTable 92
Loop: 10 numberChosen: 74 #myTable 91
Loop: 11 numberChosen: 17 #myTable 90
Loop: 12 numberChosen: 86 #myTable 89
Loop: 13 numberChosen: 70 #myTable 88
Loop: 14 numberChosen: 49 #myTable 87
Loop: 15 numberChosen: 30 #myTable 86
Loop: 16 numberChosen: 3 #myTable 85
Loop: 17 numberChosen: 10 #myTable 84
Loop: 18 numberChosen: 38 #myTable 83
Loop: 19 numberChosen: 16 #myTable 82
Loop: 20 numberChosen: 17 #myTable 81
Loop: 21 numberChosen: 100 #myTable 80
Look what happens here on loop 21 we have number 100, an index which can not exist. We have removed 20 items from the list so the list is only 80 items long.
when we call math.random(#myTable)
we confine the result to be with in the bounds of our table, but when we do myTable[math.random(#myTable)]
we no longer have that confidence.
As we removed values from our table it shrinks, but the values inside the table do not change, resulting in an increase of values which reference indices outside bounds of our table.
So we need to define numberChosen
not as myTable[math.random(#myTable)]
but as math.random(#myTable)
, also to insure we have unique index values i have adjusted the for loop for building the array:
local columns = {1,2,3,4,5,6,7,8,9,10,11,12}
local rows = {1,2,3,4,5,6,7,8,9,10,11,12}
local coupledNumbers = {}
print(coupledNumbers == {})
for i = 1, #columns do
for a = 1, #rows do
local index = ((i - 1) * #rows) + a
coupledNumbers[index] = index
end
end
for i = 1, 144 do
local numberChosen = math.random(#coupledNumbers)
print(i, numberChosen, coupledNumbers[numberChosen])
table.remove(coupledNumbers, numberChosen)
end
Upvotes: 1
Reputation: 1505
The way I would prefer to tackle this is to generate a board (a 2D array) and set each element to true when it is hit. You then just check the board when each hit is chosen and if it's already true then it's a re-hit, if it's false that is a new hit.
Here is the array at the very start:
And here is it at the end of 100 random hits:
There aren't 100 trues because quite a few are re'hits which you can see from the console below:
You can change the board size from boardHeight
and boardWidth
in the playGame
function and if you need any help integrating this leave a comment and I'll help you out.
local function getNewBoard(newHeight, newWidth)
local newBoard = {}
for i = 1, newHeight do
newBoard[i] = {}
for j = 1, newWidth do
newBoard[i][j] = false
end
end
return newBoard
end
local function playGame()
local boardHeight = 10 --Change to your height
local boardWidth = 10 --Change to your width
local gameBoard = getNewBoard(boardHeight, boardWidth)
for _ = 1, 100 do
--Get a random hit, you can change this to player entered
local hitHeight = math.random(1, boardHeight)
local hitWidth = math.random(1, boardWidth)
if gameBoard[hitHeight][hitWidth] == true then --Check if already hit
print("[" .. hitHeight .. ":" .. hitWidth .. "] has already been hit")
else
gameBoard[hitHeight][hitWidth] = true --Mark as hit
print("[" .. hitHeight .. ":" .. hitWidth .. "] has just been hit")
end
end
end
playGame()
Upvotes: 0