Ice allows you save and load data when using the Corona ® SDK. It is very simple to use and allows you to create as many 'iceboxes' as you like.
Great for storing local high scores, settings and other data.
I hope to update my site here - http://grahamranson.co.uk/lib.php?id=6 - with proper usage instructions but until then you can get the majority of stuff done pretty easily.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | require( "ice" ) local settings = ice:loadBox( "settings" ) settings( "difficulty", "easy" ) settings( "volume", 0.7 ) settings( "playerPosition", { x = 100, y = 45 } ) settings:save() local scores = ice:loadBox( "scores" ) scores:store( "best", 100 ) scores:storeIfHigher( "best", 65 ) scores:storeIfHigher( "best", 105 ) scores:save() print( scores:retrieve( "best" ) ) |
Here is some simple code to show how you could initialise some values on first load only:
1 2 3 4 | require( "ice" ) settings = ice:loadBox( "settings" ) settings:storeIfNew( "volume", 0.5 ) |
Here is a very simple demo to show incrementing values ( useful for scores ). You are also able to decrement value just as easily.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | require( "ice" ) -- Load or Create our Score box local scores = ice:loadBox( "scores" ) local description = display.newText( "Tap each player to increment score.", 0, 0, "Helvetica", 19 ) description.x = display.contentCenterX description.y = 100 -- Increment Player 1 Score local onPlayer1Tap = function( event ) scores:increment( "player1", 1 ) scores:save() end -- Increment Player 2 Score local onPlayer2Tap = function( event ) scores:increment( "player2", 1 ) scores:save() end -- Reset both scores local onResetButtonTap = function( event ) scores:store( "player1", 0 ) scores:store( "player2", 0 ) scores:save() end -- Create Player 1 local player1 = display.newRect( 0, 0, 150, 150 ) player1:setFillColor( 255, 0, 0 ) player1.x = display.contentCenterX - player1.contentWidth / 2 player1.y = display.contentCenterY player1:addEventListener( "tap", onPlayer1Tap ) player1.text = display.newText( "Player 1", 0, 0, "Helvetica", 30 ) player1.text.x = player1.x player1.text.y = player1.y - player1.text.contentHeight player1.score = display.newText( "0", 0, 0, "Helvetica", 30 ) player1.score.x = player1.x player1.score.y = player1.y -- Create Player 2 local player2 = display.newRect( 0, 0, 150, 150 ) player2:setFillColor( 0, 0, 255 ) player2.x = display.contentCenterX + player2.contentWidth / 2 player2.y = display.contentCenterY player2:addEventListener( "tap", onPlayer2Tap ) player2.text = display.newText( "Player 2", 0, 0, "Helvetica", 30 ) player2.text.x = player2.x player2.text.y = player2.y - player2.text.contentHeight player2.score = display.newText( "0", 0, 0, "Helvetica", 30 ) player2.score.x = player2.x player2.score.y = player2.y -- Create Reset Button local resetButton = display.newRect( 0, 0, 200, 100 ) resetButton:setFillColor( 0, 255, 0 ) resetButton.x = display.contentCenterX resetButton.y = display.contentHeight - resetButton.contentHeight + 20 resetButton:addEventListener( "tap", onResetButtonTap ) resetButton.text = display.newText( "Reset Scores", 0, 0, "Helvetica", 30 ) resetButton.text.x = resetButton.x resetButton.text.y = resetButton.y resetButton.text:setTextColor( 0, 0, 0 ) -- Update the score displays local onEnterFrame = function( event ) player1.score.text = scores:retrieve( "player1" ) or 0 player2.score.text = scores:retrieve( "player2" ) or 0 end Runtime:addEventListener( "enterFrame", onEnterFrame ) |
Hopefully it's useful to more than just me :-)
Thank you for sharing this!
Happy to :-)
I was just looking around for a scoring system, but since i'm lame still they seemed to complicated. I think I'm going to give this a try.
I'd like to see a simple example of a game in use. Something like I don't know take a rectangle, add event listener to it to add 1 to the score, then seeing how this fancy pants score thing works and how it loads and saves data. Or somethin'
I'll be back if I can figure it out and post. Off to coding magic land fun time.
-ng
Just added a demo to the description to show how easy it is to add 1 to the score on a button press. Could easily be hooked up to any other event as well as add more than just 1 to the value.
I finally got a chance to check it out.
It's cool!
I noticed, that after I launch it tap a few times on player 1 or 2 that the score stays there. I think that's really cool.
Ok Mr. fancy pants, this is very nice. I also noticed you got away from the package...seeall stuff as well.
I'm going to start with this and put it into my game. Then find a way to send scores to facebook, twitter, openfient, gamecenter etc etc.
I suppose I could also use this and cook up a way to show when a level is complete (something like bubble ball, when you complete a level, it shows it marked). Looks like the logic is there, but I don't know if would be suitable for something like that.
Maybe one day I can have a
"Developed with Corona"
ALSO
"MADE WITH LIME AND ICE"...sounds like a tasty drink to me. Of course you also have "Rum" so if i make a game with all of that, people will think I have a pub or something...lol.
Anyway, great stuff as always!
ng
@GrahamRanson is it me or ice cannot save/load table which has more than 2 elements/fields?
code snippet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | -- definition local myTable = {} -- initialization for i=1, 351 do myTable[i] = { x, y, path, isBusy = false } end -- somewhere inside the code, inside some loop myTable[i] = display.newImage ( img_path ) myTable[i].path = img_path myTable[i].x = 10 myTable[i].y = 20 myTable[i].isVisible = true myTable[i].isBusy = true -- now trying to save that local myBox = ice:newBox ( "myBox" ) myBox:store( "myTable", myTable ) myBox:save() |
1 2 3 4 5 6 7 8 9 10 11 | Runtime error Encoding of function: 0AABD3B8 unsupported stack traceback: [C]: ? [C]: in function '?' ?: in function 'WriteError' ?: in function 'WriteFunction' ?: in function 'Write' ?: in function 'WriteTable' ?: in function 'Write' ?: in funct |
what I am doing wrong? O_o
ok, I guess I got it!
you can't save this
1 | myTable[i] = display.newImage ( img_path ) |
1 2 3 4 5 6 7 8 9 10 11 12 | local _myTable = {} for i = 1, #myTable do _myTable[i] = { x, y, path, isBusy } _myTable[i].x = myTable[i].x _myTable[i].y = myTable[i].y _myTable[i].path = myTable[i].path _myTable[i].isBusy = myTable[i].isBusy end local myBox = ice:newBox ( "myBox" ) myBox:store( "myTable", _myTable ) myBox:save() |
@ nicholasclayg - As long as you call the save function on your box ( or set it to autosave ) then the data will be persistent between app loads etc.
Yup, completely moved away from packaging, and Lime only has Utils really that uses that ( and the actual lime.lua file ) so I am close to being completely rid of it.
I'm actually thinking of putting a generic upload function in just to be able to upload a box to a web server and then you could download it again on another device ( given a unique url and code or something ) allowing basic backing up and syncing possibly.
For level locking it would certainly be suitable, you could do something like this:
1 2 3 4 5 6 7 | local levels = ice:loadBox( "levels" ) levels:store( "level1", true ) levels:store( "level2", false ) levels:store( "level3", false ) levels:store( "level4", true ) levels:store( "level5", false ) levels:save() |
And then you can just change their values to true/false when unlocking/locking and then change the graphic of a level icon to be locked or not depending on their values.
@ hsardaryan - You won't be able to save out the displayObject ( display.newImage ) using Ice as I have now idea how to serialise / deserialise those. You could just save out the path to the image and then recreate / destroy the image when loading / saving.
@GrahamRanson yes! I already figured it out, thank you for such a great module!
p.s.
pls make some documentation/how-to on it :)
Yea you beat me to it well done :-)
Documentation is next on the to-do list, I didn't want to spend ages on docs if it turned out no one actually wanted the module but as atleast 2 people are using it without the docs I can only assume that atleast 4 would use it with :-)
I confirm ICE is working EXCELLENT on iDevice!!
Thanks again @GrahamRanson
Woooo! Glad to hear it, thank you for checking. *In theory* it should work on Android as well, but you never know :-)
Thank you for sharing, Graham.
I just downloaded the ice.lua and main.lua package to take a quick look, and I noticed there is no mention of MIT license in ice.lua, or any mention of it being free to use (for commercial or personal purposes). I'm wondering if it's just an oversight, or it is indeed copyrighted material with reserved rights, and if so I feel I shouldn't really include it in my game project...
Oops this was a silly oversight on my side. Please assume anything I put on my GitHub account to be MIT licensed. I will get round to adding in the licences when I get a chance but you are free to do whatever you like with it.
That sounds great! Thank you, Graham!
No worries, hope it helps. I can confirm that it does work on devices and I currently have two apps on the app store that use it. So far no issues :-)
Thank you very much Graham for this! It's great and very - very useful.
A simple documentation would be also nice.
Thank you!
:)
Documentation is coming, when I find some time :-) In the mean time, is there anything specific you would like explained?
Hey Graham, I'm finally able to start incorporating ice.lua to my game project, replacing the simple json based save/load functions I've worked out a few months ago.
I have a quick question (and let me know if I should email you directly from your website for questions like this). I have:
1 2 | -- in my main.lua json = require("json") |
Since ice.lua requires json and sqlite3, I'm replacing the above with requiring ice, but I'm not sure which way I should go:
1 2 3 4 5 6 | -- should I simply require like this: require( "ice" ) -- or... can I require like this? -- could there be any harm in doing so? ice = require( "ice" ) |
Hey, you will be fine with the first line and should be fine with the second, I can't see any reason why it wouldn't like that.
Feel free to either post on here with questions or email me, whichever you prefer.
Thank you Graham. I'm a real newbie, and if I'm asking too many things that I should know already, please just let me know.
Anyhow, as I mentioned, I have save/load system using json. I'm going to keep it in place as I establish a new ice based system.
So, after I require ice, what do I do next? How do I initialize the icebox? I'm guessing, my code must first check if any icebox is already saved, and if it is saved, load the icebox, if not I can safely create and save it at run time.
When using json, I initialize the data table and then, after that, I call load function like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | -- initialize data local myData = {} local numLoop; local maxDataEntry = 100 -- this is just for example, and in this case, it assumes there are 100 data entry -- initializing the variables for numLoop=1,maxDataEntry myData[numLoop] = 0; end --this is how I load data local function loadData() -- io.open opens a file at path. Returns nil if no file is found file = io.open( path, "r" ); -- nothing is loaded if there's no file found if file then -- read all contents of file into a string contents = file:read( "*a" ); scoreList = json.decode( contents ); io.close( file ); end end loadData(); -- during the game play, myData table will be modified -- every time any data value changes, I call saveData function like this: local function SaveGame() file = io.open( path, "w" ); if file then contents = json.encode( myData ); file:write( contents ); io.close( file ); end end |
How do we do this using ice? How can I find out if there is icebox already saved? If icebox is found, I'd want to load it after I initialize the data. Or should I be thinking differently?
When you call "loadBox" ice will either load an existing one if it is there or create a new empty one for you and then load that, so you don't need to check if it exists already to use it, you simply call this line, probably in main.lua:
1 | data = ice:loadBox( "data" ) |
So for your code as above but with Ice you could do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | require( "ice" ) data = nil function loadData() data = ice:loadBox( "data" ) end function saveData() data:save() end function addOrChangeData( name, value ) data:store( name, value ) end loadData() -- during gameplay addOrChangeData( "score", 20 ) addOrChangeData( "lifes", 4 ) addOrChangeData( "playerPosition", { x = 30, y = 100 } ) -- then after gameplay like at the end of the level or something saveData() |
The reason I only call save once is that that function will do the actual file access, which is something you don't really want happening during gameplay as it can cause slowdown. But Ice will still store all your changes during gameplay fine and can be retrieved and edited fine throughout the whole time the app is running, you just need to save it to disk before the app closes.
Technically if you just wanted persistant data while the app is running, say between director screens etc, you wouldn't even need to save the data as it would then just get wiped on app reload.
Hope that helps!
A quick note, about how to require ice.
ice = require( "ice" ) gave me run time error: attempt to index global 'ice' (a boolean value) when I added the following in main.lua:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ice = require("ice") print( "" ) print( " -- BOX: credits -- " ) local box1 = ice:loadBox( "credits" ) box1:print() print( "" ) print( " -- BOX: food -- " ) local box2 = IceBox:new( "food" ) box2:load() box2:print() print( "" ) print( " -- BOX: player -- " ) local box3 = IceBox:new( "player" ) box3:load() box3:print() print( "" ) |
Changing ice = require("ice") to simply require("ice") worked just fine.
Thank you, Graham, for the post #24 above. OMG, your ice class sounds sooooo incredibly easy to use! I feel like I now know all I need to know to convert my save/load system using ice!
This is an awesome class!!
Very happy to hear it's going well. Any more questions just ask and feel free to tell your friends about Ice :-)
I sure will mention ice class to anyone who might be looking to save/load data. This is just too amazing not to use.
By the way, can anyone simply edit the .ice file using text editor and manipulate the saved data? How is the saved data protected?
It is first encoded into json and then saved into a single field in an sqlite database and given the extension of .ice, naturally if someone first realised what type of file it is they would then have to open it up in an sqlite viewer and then copy out the data and decode it from json, so it's not overly complicated to get it out and edit it but it will stop the opportunists. Other ways if needed would be to obfuscate the data ( tricky but doable ) or encrypt it ( not possible in Corona at present and also brings in a whole lot more paperwork ).
One thing I am going to possibly do is allow for the .ice file to be easily saved, uploaded to a server and then deleted on app finish, and then re-downloaded, parsed and deleted on app start etc so that it is only ever held in memory on the phone however haven't had time to do this yet.
That sounds great, Graham! Thank you!!
Hey Graham, I'm wondering how I might wipe clean the saved data after it's been saved.
I have Reset Game button, which enable users to revert the game back to original/initial state, i.e., all high score wiped, all unlocked levels go back to locked, etc. Is there a magic function I could call to wipe out all saved data? If not, how might I delete each individual data entry, i.e., what syntax do I use for individual data entry to remove it?
You can call "clear()" on your box and then you will want to save it, like so:
1 2 | box:clear() box:save() |
Awesome! That's the magic function I was looking for. Thank you Graham!!!
No worries :-)
A quick note to newbies like me who are interested in using ice library, here are basics that should get you going. (Graham, please correct me if I haven't gotten this right.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | --------------------------- -- in main.lua --------------------------- myData = nil -- I believe myData should be a global variable -- load previously saved data -- if no data has been saved, it will simply return nil myData = ice:loadBox( "myData" ) --------------------------------------------------------- -- in main.lua or in any module where you want to work with myData --------------------------------------------------------- myData:clear() -- clears all saved data from disk myData:save() -- saves all data in memory to disk myData:store( name, value ); -- add or modify data entry in memory at runtime myData:retrieve( "saved_data" ) -- retrieves the data entry (from disk?) -- Example: -- if you want to save high score, and you want to name the high score entry as highScore -- and if it is 10000, then: myData:store("highScore", 10000) -- Example: -- you use variable bestscore for high score then: local bestscore = 10000; myData:store("highScore", bestscore) -- Example (edited): -- if you want to retrieve previously stored highscore -- and assign the value of the retrieved highscore to a local variable: local highScore = myData:retrieve( "highScore" ) |
This is all I needed to know to use this library. So amazing.
That all looks good to me, who needs documentation? :-)
Actually one small thing, this line:
1 | myData = ice:loadBox( "myData" ) |
If a box with that name hasn't already been created it won't return nil, it will first create an empty box and then return that.
Graham, thanks for the clarification. I think Asnca should include this library with Corona SDK. It's so easy to use, and it would immensely benefit users with very little coding/programming background.
You're welcome to suggest it to them :-)
If anyone from Ansca is reading this, it's all yours if you want to include it.
When I downloaded ICE, there was also a main.lua file with examples. In that file you use both ice:newBox and IceBox:new.
Are those 2 functionally the same or is there some reason to use one or the other?
They will both result in a box just the same however the small difference ( that I really should have explained but will do when I write proper docs ) is that the ice:newBox function also stores that box in a table owned by Ice which allows you to create them at anytime ( rather than just in main.lua ) and access them from anywhere via Ice:getBox( name ).
You can also do most of the other functions through helper functions on Ice, it really just depends on how you like to work.
Brilliant work Graham. Keep it going ... :-)
I intend to :-)
Hi Graham, excellent code :)
i have a question. In my game, i have 5 levels and i want to keep a different highscore for each of them.
So in each level, at the end of the game, i have a code like this:
1 2 3 4 5 6 7 8 9 10 11 12 | myData = ice:loadBox( "myData" ) myData:retrieve( "highscore1") if highscore1 == nil then highscore1 = points; myData:store("highscore1", points) myData:save() end if highscore1 < points then highscore1 = points; myData:store("highscore1", points) myData:save() end |
1 2 3 4 5 6 7 | require( "ice" ) myData = ice:loadBox( "myData" ) myData:retrieve( "highscore1") myData:retrieve( "highscore2") myData:retrieve( "highscore3") myData:retrieve( "highscore4") myData:retrieve( "highscore5") |
In your first code block the separate "highscore1" variable will never be anything as you aren't assigning it. You would want instead to do this:
1 | highscore1 = myData:retrieve( "highscore1") |
You could instead do something like this:
1 2 3 | local myData = ice:loadBox( "myData" ) myData:storeIfHigher( "highscore1", points ) myData:save() |
And then in your second block again you have the same issue, you are not assigning the retrieved variables to anything you are just calling the functions, you would want to do something like this:
1 2 3 4 5 6 | local myData = ice:loadBox( "myData" ) local highscore1 = myData:retrieve( "highscore1") local highscore2 = myData:retrieve( "highscore2") local highscore3 = myData:retrieve( "highscore3") local highscore4 = myData:retrieve( "highscore4") local highscore5 = myData:retrieve( "highscore5") |
You then have all the scores that you can display in your menu.
worked like a charm :) thank you a lot Graham, you are the best! :)
when you use myData:save(), it saves the myData to the "disk"?
Wouldn't it be better to check if the score is better than the highscore before saving to disk, because it uses more battery?
True, you could change to this:
1 2 3 4 5 | local myData = ice:loadBox( "myData" ) local wasAdded = myData:storeIfHigher( "highscore1", points ) if wasAdded then myData:save() end |
This isn't working, like at all. Its acting like it isn't even registering the
first:store() : (
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | require( "ice" ) system.activate( "multitouch" ) local scores = ice:loadBox( "scores" ) local first = ice:loadBox("first") local function setScore() --first:increment("data", 1) first:store("value", 0) first:save() print (first:retrieve("value")) print "setScore called" -- print (first:retrieve("data")) if (first:retrieve("data"))==1 then savedPts=2000 --print "first =" .. " ".. (first:retrieve("data")) print "this should only happen once" scores:store("best", 5000) scores:save() print "this worked" -- print "savedPts =" .. " " .. (savedPts) -- once = (first:retrieve("data")) end end timer.performWithDelay( 1000, setScore, 1) <text> Can anybody shed some light on the subject. (sorry its such a mess... LOL) |
Hey Joe, on line#9, you've stored "value". On line #18, you're trying to retrieve "data" -- but when did you store "data"?
If you have not stored "data", first:retrieve("data") will return nil. Maybe "data" was a typo? Did you want first:retrieve("value") instead?
As for first:retrieve("value"), it would return 0, because you've stored it on line #9.
Naomi
As Naomi has pointed out, you seem to have got a copy and paste error on line 18 and you are always saving "value" as 0 on line 9.
Oh... oops. That was a typo. They all should say "data".
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | require( "ice" ) system.activate( "multitouch" ) local scores = ice:loadBox( "scores" ) local first = ice:loadBox("first") local function setScore() --first:increment("data", 1) first:store("data", 0) first:save() print (first:retrieve("data")) print "setScore called" -- print (first:retrieve("data")) if (first:retrieve("data"))==1 then savedPts=2000 --print "first =" .. " ".. (first:retrieve("data")) print "this should only happen once" scores:store("data", 5000) scores:save() print "this worked" -- print "savedPts =" .. " " .. (savedPts) -- once = (first:retrieve("data")) end end timer.performWithDelay( 1000, setScore, 1) It wasn't working like this either. They don't return 0, They don't even return nil... the terminal doesn't show anything |
Excellent - thanks for sharing!