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.
User avatar
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)

Post by Maggots »

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 :nyu:

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
		addBlock(editor.selectGrid)
	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
Thanks in advance!
User avatar
Fruchthieb
Prole
Posts: 20
Joined: Sat Sep 26, 2009 12:04 pm
Location: austria

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

Post by Fruchthieb »

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.
User avatar
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)

Post by Maggots »

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. :P
User avatar
Fruchthieb
Prole
Posts: 20
Joined: Sat Sep 26, 2009 12:04 pm
Location: austria

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

Post by Fruchthieb »

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!
User avatar
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)

Post by Maggots »

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? :D
User avatar
Benamas
Prole
Posts: 28
Joined: Wed Apr 14, 2010 10:23 pm

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

Post by Benamas »

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.
User avatar
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)

Post by Robin »

You might find one of these helpful: http://lua-users.org/wiki/TableSerialization
Help us help you: attach a .love.
User avatar
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)

Post by Maggots »

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
User avatar
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)

Post by Robin »

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.
User avatar
schme16
Party member
Posts: 127
Joined: Thu Oct 02, 2008 2:46 am

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

Post by schme16 »

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')
	tableAsTable = table.load(fr)
works like a charm!
Attachments
tablePersistence.lua
This adds table.load and table.save into your table function list
(6.4 KiB) Downloaded 172 times
My Development Diary - http://shanegadsby.info
Post Reply

Who is online

Users browsing this forum: Bing [Bot] and 3 guests