## Saving a table (containing many other tables, btw)

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Maggots
Prole
Posts: 27
Joined: Sat Apr 24, 2010 10:38 pm
Location: Montréal, Québec, Canada

### Saving a table (containing many other tables, btw)

I made a kind of level editor that lets you choose what image you want to use to make a platform,
using a grid and all that good stuff, and it puts all the bodies, shapes and what-not into a table.

Everything works very well, but I'm having trouble wrapping my head around how I can save a table to a text file. Also, I'm quite
new to Lua, so please try not to suggest anything that may be too hard for me

If it's any help, here's my code.
Beware, it's not very organized (in my opinion anyway).

Code: Select all

function love.load()
editor = {}--Create a table to hold all the editor elements
editor.grids = {}--Create a table to hold the grid bodies and shapes
editor.gridOn = true--Is the grid on or not?
editor.gridWorld = love.physics.newWorld(960,704)--Create a world to hold the grids.
editor.gridWorld:setMeter(64)
editor.gridWorld:setGravity(0,0)
--It's better when it's a multiple of 64.
editor.mode = "blocks"
editor.modes = {}
editor.modes[1] = "blocks"
editor.modes[2] = "objects"
createGrid()

map = {}--Create a table to hold all the map elements
map.materials = {--Table to hold the images used for blocks
woodfloor = love.graphics.newImage("materials/wood-floor.png"),
grassdirt = love.graphics.newImage("materials/grass-dirt.png"),
testplatform = love.graphics.newImage("materials/testplatform.png"),
boxfloor = love.graphics.newImage("materials/box-floor.png"),
metalwall = love.graphics.newImage("materials/metal-wall.png"),
brickwall = love.graphics.newImage("materials/brick-wall.png"),
spacefloor = love.graphics.newImage("materials/space1-floor.png"),
}
map.blocks = {--Table to hold all the data for the static blocks (or just blocks)
--w = width, h = height, ox = offset x, oy = offset y
[1] = {i = map.materials.woodfloor, w = 64, h = 64, ox = 32, oy = 32, m = 0},
[2] = {i = map.materials.grassdirt, w = 64, h = 64, ox = 32, oy = 32, m = 0},
[3] = {i = map.materials.testplatform, w = 64, h = 64, ox = 32, oy = 32, m = 0},
[4] = {i = map.materials.boxfloor, w = 64, h = 64, ox = 32, oy = 32, m = 0},
[5] = {i = map.materials.metalwall, w = 64, h = 64, ox = 32, oy = 32, m = 0},
[6] = {i = map.materials.brickwall, w = 64, h = 64, ox = 32, oy = 32, m = 0},
[7] = {i = map.materials.spacefloor, w = 64, h = 64, ox = 32, oy = 32, m = 0},
}
map.objects = {}--Table to hold all the objects
map.entities = {}--Table to hold all the entities in the map.
map.entitynum = 0
map.world = love.physics.newWorld(960,704)
map.world:setMeter(64)
map.world:setGravity(0,0)

editor.mat = map.blocks[1]
editor.matn = 1
editor.obj = nil
editor.objn = 1

love.graphics.setLineWidth(2)--Thicker lines
editor.font = love.graphics.newFont(20)
love.graphics.setFont(editor.font)

love.graphics.setMode(1440,800)--Set the resolution to 1440*800
love.graphics.setCaption("Level Editor")
end

function love.update(dt)
editor.gridWorld:update(dt)--Update the grid.
map.world:update(dt)--Update the map

if love.mouse.isDown("l") and editor.hitGrid == true and editor.selectGrid.occupied == false then
end

if love.mouse.isDown("r") and editor.hitGrid == true and editor.selectGrid.occupied == true then
removeBlock(editor.selectGrid)
end

if love.keyboard.isDown('g') then--Turn on the grid when G is pressed.
if editor.gridOn == false then
editor.gridOn = true
elseif editor.gridOn == true then
editor.gridOn = false
end
love.timer.sleep(100)--To stop the grid from flickering
end
end

function love.draw()
editor.hitGrid, editor.selectGrid = hoverGrid()
love.graphics.setColor(255,255,255)
if editor.gridOn == true then--Draw the grid if it's on
drawGrid()
end

for i,v in pairs(map.entities) do--Draw all the map entities
love.graphics.draw(v.i, v.b:getX(), v.b:getY(), v.b:getAngle(), 1, 1, v.ox, v.oy)
end

showBlock()

printMode()
end

function drawGrid()--Draw the grid lines
love.graphics.setColor(255,255,255)
for a = 0, 14 do
for n = 0, 10 do
love.graphics.rectangle("line",a*64,n*64,64,64)
end
end
end

function createGrid()--Create bodies and shapes for the grids to detect when the mouse is placed on top of one of them
for a = 1,15 do
for n = 1, 11 do
local t = {}
t.b = love.physics.newBody(editor.gridWorld,a*64-32,n*64-32,0,0)
t.s = love.physics.newRectangleShape(t.b,0,0,64,64,0)
t.occupied = false
t.entity = nil
table.insert(editor.grids,t)
end
end
end

function hoverGrid()--Display a yellow square over the grid where the mouse is
for i,v in pairs(editor.grids) do
local mx,my = love.mouse.getPosition()
local hit = v.s:testPoint(mx, my)
if hit == true then
love.graphics.setColor(255,255,0)
love.graphics.rectangle("fill",v.b:getX()-32,v.b:getY()-32,64,64)
return hit,v
end
end
end

function printMode()--Print the current brush mode
love.graphics.setColor(0,150,255)
love.graphics.print("Mode: "..editor.mode, 0, 800)
end

function showBlock()--Show the image of the currently selected block
love.graphics.setColor(255,255,255)
love.graphics.draw(editor.mat.i, 1100, 100, 0, 1, 1, editor.mat.ox, editor.mat.oy)
end

function love.keypressed(k)
if k == "1" then--Use number keys to change the brush mode
editor.mode = editor.modes[1]
elseif k == "2" then
editor.mode = editor.modes[2]
end

if k == "right" then--Use the arrow keys to change the selected block
if editor.mode == "blocks" then
editor.matn = editor.matn + 1
if editor.matn > table.getn(map.blocks) then editor.matn = 1 end--Loop the list
editor.mat = map.blocks[editor.matn]
elseif editor.mode == "objects" then
editor.objn = editor.objn + 1
if editor.matn > table.getn(map.blocks) then editor.matn = 1 end--Loop the list
editor.obj = map.blocks[editor.objn]
end
elseif k == "left" then--Use the arrow keys to change the selected block
if editor.mode == "blocks" then
editor.matn = editor.matn - 1
if editor.matn < 1 then editor.matn = table.getn(map.blocks) end--Loop the list
editor.mat = map.blocks[editor.matn]
elseif editor.mode == "objects" then
editor.objn = editor.objn - 1
if editor.objn < 1 then editor.objn = table.getn(map.blocks) end--Loop the list
editor.obj = map.blocks[editor.objn]
end
elseif k == "s" then table.save(map,"level.txt") end--ignore that!

end

function addBlock(grid)--Add a block to the map at the currently selected grid
if editor.mode == "blocks" then
local t = {}
local x, y = grid.b:getPosition()
t.b = love.physics.newBody(map.world, x, y, editor.mat.m, 0)
t.s = love.physics.newRectangleShape(t.b,
t.b:getX(),
t.b:getY(),
editor.mat.w,
editor.mat.h,
0)
t.i = editor.mat.i
t.ox = editor.mat.ox
t.oy = editor.mat.oy
table.insert(map.entities,t)
grid.occupied = true
map.entitynum = map.entitynum + 1
grid.entity = map.entities[map.entitynum]
elseif editor.mode == "objects" then
local t = {}
local x, y = grid.b:getPosition()
t.b = love.physics.newBody(map.world, x, y, editor.obj.m, 0)
t.s = love.physics.newRectangleShape(t.b,
t.b:getX(),
t.b:getY(),
editor.obj.w,
editor.obj.h,
0)
t.i = editor.obj.i
t.ox = editor.obj.ox
t.oy = editor.obj.oy
table.insert(map.entities,t)
grid.occupied = true
map.entitynum = map.entitynum + 1
grid.entity = map.entities[map.entitynum]
end
end
function removeBlock(grid)--Remove a block
local entity = grid.entity
entity.b:setPosition(-10000,-10000)
love.graphics.draw(entity.i, entity.b:getX(), entity.b:getY(), entity.b:getAngle(), 1, 1, entity.ox, entity.oy)
table.remove(entity)
grid.occupied = false
end
Fruchthieb
Prole
Posts: 20
Joined: Sat Sep 26, 2009 12:04 pm
Location: austria

### Re: Saving a table (containing many other tables, btw)

Dont have read your code, I´m sorry, no time for that

Maybe you need something like the following

Code: Select all


tablefile = love.filesystem.newFile("filename")
tablefile:open('w')
tablefile:write("table = {} \n")
tablefile:write("table.dumdidum = 'value of dumdidum, this is a string for example' \n")


Iterating over lines should be clear. You should also be able to load the table with love.filesystem.load("filename").

Hope i could help. Regards! Fruchthieb.
Last edited by Fruchthieb on Sat Apr 24, 2010 11:27 pm, edited 1 time in total.
Maggots
Prole
Posts: 27
Joined: Sat Apr 24, 2010 10:38 pm
Location: Montréal, Québec, Canada

### Re: Saving a table (containing many other tables, btw)

Thanks a lot! That does clear a lot of things up, but I'm worried about having to use a for loop to find all the data.

As for anybody else who may want to shed some more light on my situation, go ahead, I could probably use some.
Fruchthieb
Prole
Posts: 20
Joined: Sat Sep 26, 2009 12:04 pm
Location: austria

### Re: Saving a table (containing many other tables, btw)

Find data? Its a table, via loading it, you can use it as normal. Anyway, for finding data you might take a look at that: http://www.lua.org/manual/5.1/manual.html#5.4
Exspecially this function: string.find (s, pattern [, init [, plain]])
Regards!
Maggots
Prole
Posts: 27
Joined: Sat Apr 24, 2010 10:38 pm
Location: Montréal, Québec, Canada

### Re: Saving a table (containing many other tables, btw)

Well, I made a function that saves using this code:

Code: Select all

function saveLevel(filename)
local f = love.filesystem.newFile(filename)
f:open('w')
f:write("map = {}\n")
for i,v in pairs(map) do

if type(v) == "table" then

local subt = i

for i,v in pairs(v) do

if type(v) == "table" then

local subt2 = i

for i,v in pairs(v) do

f:write("map."..subt.."."..subt2.."."..i.."="..tostring(v))

end

else f:write("map."..subt.."."..i.."."..tostring(v))

end

end

else f:write("map."..i.."."..tostring(v))

end

end
end
But when I press the S button to save, it doesn't do anything. When I press it a second time, it tells me that I can't write to file, cause the file isn't open.

...Help?
Benamas
Prole
Posts: 28
Joined: Wed Apr 14, 2010 10:23 pm

### Re: Saving a table (containing many other tables, btw)

This is how I managed to do it, but I use a bizarre method to store my data by creating two discrete tables and then looking up an element by number in both.

Code: Select all

function loadhiscores()

hiscore_scores = {} -- score
hiscore_wizard = {} -- character used
if love.filesystem.exists( "hiscores.txt" ) then
-- read scores from file
file = love.filesystem.newFile("hiscores.txt")
file:open('r')
currentline = file:lines()
for i = 1, 10 do
if file:eof() then -- Did something weird happen? BAIL OUT AND BS THE WHOLE THING
hiscore_scores = { 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100}
hiscore_wizard = { 1, 2, 3, 2, 3, 2, 1, 2, 3, 2 }
file:close()
break
end
hiscore_wizard[i] = tonumber(currentline())
hiscore_scores[i] = tonumber(currentline())
end
file:close()

else
-- BS ten scores from scratch
hiscore_scores = { 1000, 900, 800, 700, 600, 500, 400, 300, 200, 100}
hiscore_wizard = { 1, 2, 3, 1, 2, 3, 1, 2, 3, 1 }
end

end
then later to save the file:

Code: Select all

  -- replace hiscores.txt with current tables in memory
file = love.filesystem.newFile("hiscores.txt")
file:open('w')
for i = 1, 10 do
file:write(hiscore_wizard[i] .. "\r\n")
file:write(hiscore_scores[i] .. "\r\n")
end
file:close()
This ends up with each element in the table separated in the hiscore.txt by a linebreak, which is working just fine for me.
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

### Re: Saving a table (containing many other tables, btw)

You might find one of these helpful: http://lua-users.org/wiki/TableSerialization
Help us help you: attach a .love.
Maggots
Prole
Posts: 27
Joined: Sat Apr 24, 2010 10:38 pm
Location: Montréal, Québec, Canada

### Re: Saving a table (containing many other tables, btw)

Okay, I was playing around with the save function that I made... It doesn't work very well. I mean, I tell it to save every table and variable's data.
Sadly, it doesn't show me all the data. Instead of showing me something like

Code: Select all

map.entities[4].image = map.objects[2].i (Used for drawing the images and stuff)
instead it'll show me something like

Code: Select all

map.entities[4].image = Image
without actually telling me what the image is.

Any idea what went wrong? Here's my save code:

Code: Select all

function saveLevel(filename)
local f = love.filesystem.newFile(filename)
f:open('w')
f:write("map = {}\n")
for i,v in pairs(map) do

if type(v) == "table" then

local subt = i

for i,v in pairs(v) do

if type(v) == "table" then

local subt2 = i

for i,v in pairs(v) do

f:write("map."..subt.."."..subt2.."["..i.."]".."="..tostring(v).."\n")

end

else f:write("map."..subt.."["..i.."]"..tostring(v).."\n")

end

end

else f:write("map.".."["..i.."]"..tostring(v).."\n")

end

end
end
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

### Re: Saving a table (containing many other tables, btw)

That's because Images etc. don't have a string representation. One option would be to do:

Code: Select all

map.entities[4].image = 2
and when drawing:

Code: Select all

love.graphics.draw(map.objects[map.entities[4].image].i, x, y) --you get the idea
Furthermore, I strongly suggest you use one of the Serialization examples on the Lua-Users Wiki. They are stable and the more complex ones even work well with recursion and cross-referencing.
Help us help you: attach a .love.
schme16
Party member
Posts: 127
Joined: Thu Oct 02, 2008 2:46 am

### Re: Saving a table (containing many other tables, btw)

I use this script, it adds a couple of values to the table call: .save and .load

eg:

Code: Select all

require 'tablePersistence.lua'

newTable = {}
newTable.data = 'this is some sting data!'
newTable.tableData = {'This is a sub table with string data!', 1337,{'sub, sub table with string data'}}

--Serialise the table
tableAsString = table.save(newTable)

--Save the serialised table to a file
local f = love.filesystem.newFile('myTable.txt')
f:open('w')
f:write(tableAsString)

--now we'll load it again
local fr = love.filesystem.read('myTable.txt')