Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

Level lock/unlocking
Started by Focus Lab Jan 03 2013 10:09 AM

31 replies to this topic
[TOPIC CONTROLS]
Page 1 of 2 1 2
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

I was wondering if it is possible to lock and unlock levels using storyboard. I saw a post from Peach Pellen talking about a way to easily lock and unlock levels but her example was taken down. Basically I was looking for a way/code that when you beat level 1, it unlocks level 2, and so on.
uid: 72845 topic_id: 34528 reply_id: 334528


[TOPIC: post.html]
#2

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hi @FL,
Level "unlocking" should be managed using a data file stored in the app's Documents directory, as either a text file, JSON file, or local SQLite database. The reason is, you want this data to persist after the app is closed and again when the user returns. A file in the Documents directory persists as long as the app is installed on the device, and thus it remains as a way to read and write aspects of the game like high score, levels unlocked, awards earned, etc.

Here's are 3 tutorials that you can read:
http://www.coronalabs.com/blog/2012/02/14/reading-and-writing-files-in-corona/
http://www.coronalabs.com/blog/2011/08/03/tutorial-exploring-json-usage-in-corona/
http://www.coronalabs.com/blog/2012/04/03/tutorial-database-access-in-corona/

Let me know if you have any specific questions,
Brent
uid: 200026 topic_id: 34528 reply_id: 137302


[TOPIC: post.html]
#3

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Looks like I need to get my SQL guru to help me out with this one. From what I gather Peach Pellen is no longer with Corona but their were posts that I saw stating "Level unlocking made easy with Ego". Is that something that still exists or that all archaic now? And after seeing the tutorials I feel like an idiot for using the words Lock and unlocking being that its just calling info from data tables. Gotta start somewhere though right? :)
uid: 72845 topic_id: 34528 reply_id: 137310


[TOPIC: post.html]
#4

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hi @FL,
"Ego" was just one module method based on the same practices I mentioned to you, and there are actually better and more efficient modules. I can walk you through the process step-by-step with code examples from my own projects. You certainly don't need an SQL guru to accomplish this. :)

Brent
uid: 200026 topic_id: 34528 reply_id: 137312


[TOPIC: post.html]
#5

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

If you have examples that would be awesome. I feel like Neo going down the rabbit hole right now lol
uid: 72845 topic_id: 34528 reply_id: 137314


[TOPIC: post.html]
#6

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

The first thing for you to consider here is, "how much" data do you need to save between app sessions? Only the unlocking of levels and maybe high score? Or, do you plan for an app with a huge slew of data like multiple "products", properties, player profiles, etc.? This determines the approach we'll use going forth, either text, JSON, or SQLite.

As for the "blue pill", I think I lost it... I dropped it and it rolled under the virtual sofa or something. Looks like you're stuck with the RED pill. :)

Brent
uid: 200026 topic_id: 34528 reply_id: 137317


[TOPIC: post.html]
#7

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Woohoo red pill ftw! Basically I want to have 140 levels set across 7 worlds. Beat level 1 world 1 and unlock Level 2 World 1, and so on until you unlock the next world etc. I eventually want to have it so a user can "Like" a page on facebook and unlock and extra 20 levels in the Demo (not sure if that matters right now or not). I want my "locked" levels to have a lock icon or something on them until they beat they previous level. Hopes this helps put into perspective where I'm trying to go with this. Thanks a ton Morpheus er Brent :)
uid: 72845 topic_id: 34528 reply_id: 137318


[TOPIC: post.html]
#8

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

My email is fakemode@gmail.com
Thanks for reaching out.
uid: 72845 topic_id: 34528 reply_id: 137321


[TOPIC: post.html]
#9

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hi @FL,
In your case, I think reading and writing a text file will suffice nicely (because you're going for a fairly simple level/world unlock method).

The first thing you'll need to do is create the text file when the app loads... because it won't exist on the first run. This involves merely opening an IO connection, checking that the file doesn't already exist (because obviously you don't want to overwrite it later), then writing to the text file, and finally closing the IO connection.

Here's the basic code for that... let's start with your "world" data file, which we can keep separate from your "level" data file, at least for the moment (we can combine them later if you prefer). Place this in your main.lua.
local filePath = system.pathForFile( "worlddata.txt", system.DocumentsDirectory )
local file = io.open( filePath, "r" )  --check if the file already exists
--if it doesn't exist, let's create it!
if not ( file ) then file = io.open( filePath, "w" )  --open the IO for file writing ("w")
  for i=1,7 do  --create 7 lines, for your 7 worlds
    local unlocked = "false" ; if ( i==1 ) then unlocked = "true" end  --ensure that the first world is unlocked on creation
    file:write( "world",i,": unlocked=",unlocked,"\n" ) ; unlocked = nil
  end
end ; io.close( file ) ; filePath = nil ; file = nil

That's basically it for the first stage of the process! It should create a text file in your app's Documents directory by the name of "worlddata.txt". To view this file and ensure it was created, go into the Corona Simulator, File > Show Project Sandbox, then open the folder for your app (it should be selected). In that folder, open the "Documents" subfolder, and you should see your text file there.

Its contents should simply be this:

world1: unlocked=true
world2: unlocked=false
world3: unlocked=false
(etc.)...
world7: unlocked=false

Later, you can add other parameters to each line if you need, i.e. if you want to store other information about each world. We'll deal with that later though.

Let me know how this goes, and you'll see just how deep this "rabbit hole" goes. :)

Brent
uid: 200026 topic_id: 34528 reply_id: 137388


[TOPIC: post.html]
#10

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

It did just as you said it would. By the way, mind blown about the whole sandbox thing, I haven't seen that before. Should I be storing all of my files in the sandbox? assets, etc..
uid: 72845 topic_id: 34528 reply_id: 137453


[TOPIC: post.html]
#11

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hi FL,
Don't worry too much about the "sandbox" concept at this point... and don't move your assets or lua files in there specifically when developing. Those files should remain in your project directory and subdirectories. The "project sandbox" in regards to the Simulator is where you can find those things stored in the app's Documents folder (as it would relate to the device build).

Go ahead and study over the text file code I gave you, and let me know if any lines are complete "gibberish" at this time. I can help explain them better if so.

Then, we move on to the text file read and update procedures. Those are fun! (really, they are, when the core procedure starts to make sense).

Brent
uid: 200026 topic_id: 34528 reply_id: 137455


[TOPIC: post.html]
#12

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Makes sense to me for the most part, only bit of confusion for me is here:
    file:write( "world",i,": unlocked=",unlocked,"\n" ) ; unlocked = nil
  end
end ; io.close( file ) ; filePath = nil ; file = nil

If you could explain this a bit more I think we're good to move on. My main question is about the "nil" values, and the ",unlocked,"\n" )". Other than that it all makes sense.
uid: 72845 topic_id: 34528 reply_id: 137456


[TOPIC: post.html]
#13

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

OK cool. So basically, you'll see that I create a local variable "unlocked" during each iteration of the loop (and then discard it at the end of the loop). It begins as "false" each time. Only on the first iteration... i=1... do I change it to "true", because I want only the first world to be set as unlocked=true when the text file is created. All of this is done on line 6.

Now, line 8. What you see in the parentheses is what gets written to the line in the text file. Sections of that line are written in a series of things separated by commas. Everything within quotes gets written as an absolute text value. Everything else is the value of a variable... you see the
,i,
part is writing the current iteration of the loop. And the
,unlocked,
part refers to the local variable I described above, either true or false.

The last part is the
"\n"
, which indicates a new line break. Put that anywhere you want to break to a new line when writing a text file.

Last line, I basically just nil out both "file" and "filePath", the local variables I created at the start. This makes sure all of that file stuff and IO references I opened are all removed from memory.

Tomorrow (Saturday) I'll post the read process. :)

Brent
uid: 200026 topic_id: 34528 reply_id: 137469


[TOPIC: post.html]
#14

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Awesome thanks for clearing that up. I can't wait for the next step.
uid: 72845 topic_id: 34528 reply_id: 137472


[TOPIC: post.html]
#15

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Moving on! The next step is to READ your text file. We're going to do this in a few steps.

1) First, you need to just read the entire thing into a Lua string. Once it's there, you can cherry-pick aspects of it. Here's the code for reading it all in:
local filePath = system.pathForFile( "worlddata.txt", system.DocumentsDirectory )
local contents = ""  --create empty string for the contents that will be read in
local file = io.open( filePath, "r" )  --IO open the file ("r" argument = "read" )
if ( file ) then
  contents = file:read( "*a" )  --set "contents" to text file contents ("*a" gets everything)
  io.close( file ) ; file = nil ; filePath = nil  --close the IO and nil out file references
end
Reference on
io.read
is here, if you want to check it out:
http://docs.coronalabs.com/api/library/io/read.html

2) Now, you have a Lua string "contents" which contains the entire text file contents. You can confirm that it read everything properly by just saying
print( contents )
and looking at the Terminal to see its output.

Let that soak in, then let me know when you're ready for the next step: cherry-picking and reading just one tiny element (or line) of the contents.

Brent
uid: 200026 topic_id: 34528 reply_id: 137511


[TOPIC: post.html]
#16

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Works like a charm. Seems like I'll be battling agents in no time!
uid: 72845 topic_id: 34528 reply_id: 137567


[TOPIC: post.html]
#17

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Go ahead and look at the woman in the red dress. :) I'll answer the next part tomorrow (Sunday).

Brent
uid: 200026 topic_id: 34528 reply_id: 137589


[TOPIC: post.html]
#18

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hi FL,
Let's move on to reading that "content" file in a loop. This might look a bit weird at first, but it'll soon make sense.

Lua has several string find/manipulate functions which you can read about here:
http://docs.coronalabs.com/api/library/string/index.html

In this case, I'll deal with
string.gmatch
. This will search through a big block of text and return a series of matches based on the string "patterns" you define. It makes sense to use that in your case, because you probably want to loop through the entire text document at once and determine which of your 140 levels (or which of the 7 worlds) are unlocked. You could do it one by one, but that would be incredibly tedious!

Here's the basic code we'll use here. What this does is search through your entire "contents" string looking for a common "pattern" that will reveal, to you, the status of each world (locked or unlocked)
for worldNumber, isUnlocked in
  string.gmatch( contents, "world(%d+):%sunlocked=(%w+)\n" ) do
  print(worldNumber,isUnlocked)
end
You can read all about string matching here (under "Patterns"), but it'll probably be like looking at the Matrix. Just concentrate on the simple usage for now; as you practice it'll become clearer.
http://developer.coronalabs.com/content/strings

Let's go through line by line. Remember, the common line in your text string looks something like this:
world1: unlocked=true

So...
for worldNumber, isUnlocked in
This tells Lua that you want to search for two "matches" in each line, so you can use them in your code. You'll need the world number and its unlocked value (true or false).

Next is the string matching, which looks nutty.
string.gmatch( contents, "world(%d+):%sunlocked=(%w+)\n" ) do
contents
: the first argument: your text string that you read from the file.
world
: here, you're reading a constant value "world", because each of your lines in the file start with the constant "world".
(%d+)
: now this is focal! By putting this in parentheses, you're defining it as one of your wildcard pattern matches.
%d
means that you only want to search for digits, not letters or other characters. This is fine because all of your worlds end with a number.
+
following the
%d
means that you want to search for at least one number in the match... so it could be 1, 2, or 2022033 and return a match.
:
is another constant, just a colon following your world number, as you see in the text file.
%s
is a reserved wildcard for a space, which lies between the colon and the next constant bit
unlocked=
(%w+)
is your second wildcard match, the part following "unlocked". It's either "true" or "false", so you want to use
%w
as the matching pattern, which seeks all alphanumeric characters. As before, the
+
says that you want to look for multiple characters... if you omit that, the search will fail because "true" and "false" are obviously not just one letter in length!
\n
tells Lua that a line break has been found, so you stop searching on that line.
print(worldNumber,isUnlocked)
prints out your matches. See the Terminal!

I think that's more than enough for now... I suspect this will need some follow-up Q&A, so let me know what you get from this.

Brent
uid: 200026 topic_id: 34528 reply_id: 137630


[TOPIC: post.html]
#19

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Took me an extra day to process all this but I think I understand.

My terminal now shows:

2013-01-07 13:47:52.339 Corona Simulator[35179:903] 1 true
2013-01-07 13:47:52.340 Corona Simulator[35179:903] 2 false
2013-01-07 13:47:52.340 Corona Simulator[35179:903] 3 false
2013-01-07 13:47:52.340 Corona Simulator[35179:903] 4 false
2013-01-07 13:47:52.340 Corona Simulator[35179:903] 5 false
2013-01-07 13:47:52.340 Corona Simulator[35179:903] 6 false
2013-01-07 13:47:52.341 Corona Simulator[35179:903] 7 false

which if I'm not mistaken is where I want to be right now.
uid: 72845 topic_id: 34528 reply_id: 137736


[TOPIC: post.html]
#20

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hi @FL,
Apologies for the delayed response. It's been "one of those weeks". :)

So yeah, this is exactly where you want to be (what you see in the Terminal). In that loop that I showed above, you can now set up a table with these values that you can reference elsewhere in your code. Like this:

local worlds = {}

for worldNumber, isUnlocked in
  string.gmatch( contents, "world(%d+):%sunlocked=(%w+)\n" ) do
  print(worldNumber,isUnlocked)
  worlds["W"..worldNumber] = { unlocked=isUnlocked }
end

This will give you an indexed table with sub-tables in Lua, looking like this internally:
worlds = {
  W1 = { unlocked="true" },
  W2 = { unlocked="false" },
  W3 = { unlocked="false" },
  W4 = { unlocked="false" },
  W5 = { unlocked="false" },
  W6 = { unlocked="false" },
  W7 = { unlocked="false" }
}

And now, you can just read a value when you need! Like this:
local thisWorld = 1
if ( worlds["W"..thisWorld]["unlocked"] == "true" ) then
  print("world"..thisWorld.." is unlocked!")
end

Now, the thing you're going to kick yourself over is this: anything that is read from a text file is inherently considered a -string- (text) in Lua, because, well... it's a string from a string file! As you might have studied, Lua is a "typeless" language, as in, variables can be strings, numbers, booleans, other tables, etc... but unlike many languages, you don't need to declare them in advance as a specific type. Lua, on the other hand, let's you switch the type of a variable whenever you want (in most cases), from a number to a string, from a string to a boolean, from a boolean to a table, etc. This drives some programmers coming from other languages nuts, but I personally enjoy the flexibility.

Now, compare these two lines:
if ( worlds["W"..thisWorld]["unlocked"] == "true" ) then
if ( worlds["W"..thisWorld]["unlocked"] == true ) then
The difference here is that Lua is looking for (1) to be a STRING value of
"true"
(the quotes meaning it's a string), while the second one looks for a BOOLEAN value of
true
. Big difference! Well, not a BIG difference, but big enough. If you try to conditionally search for a boolean value, but what you're actually searching against is a string value (as in, an item from your text file), then you won't get a match.

Hmmm... let's let that soak in, and then continue later. :)
Brent
uid: 200026 topic_id: 34528 reply_id: 138006


[TOPIC: post.html]
#21

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

No worries, I know you have a life outside the matrix :P

Ok so now in the terminal I have:

2013-01-09 00:39:42.170 Corona Simulator[35179:903]
world1: unlocked=true
world2: unlocked=false
world3: unlocked=false
world4: unlocked=false
world5: unlocked=false
world6: unlocked=false
world7: unlocked=false
2013-01-09 00:39:42.170 Corona Simulator[35179:903] 1 true
2013-01-09 00:39:42.170 Corona Simulator[35179:903] 2 false
2013-01-09 00:39:42.170 Corona Simulator[35179:903] 3 false
2013-01-09 00:39:42.170 Corona Simulator[35179:903] 4 false
2013-01-09 00:39:42.170 Corona Simulator[35179:903] 5 false
2013-01-09 00:39:42.171 Corona Simulator[35179:903] 6 false
2013-01-09 00:39:42.171 Corona Simulator[35179:903] 7 false
2013-01-09 00:39:42.172 Corona Simulator[35179:903] world1 is unlocked!

We good?

I changed W to world as you can tell. This is definitely A LOT more complex than I had anticipated but F it, I'm riding this red pill till the bitter end : )
uid: 72845 topic_id: 34528 reply_id: 138025


[TOPIC: post.html]
#22

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Was wondering if we could continue with this? have the next 2 days off work : )
uid: 72845 topic_id: 34528 reply_id: 139019


[TOPIC: post.html]
#23

Brent Sorrentino

[GLOBAL: userInfoPane.html]
Brent Sorrentino
  • Corona Staff
  • 4,652 posts
  • Jedi

Hello Neo!
OK, let's move on... next step is a function to individually set (update) one of these values, i.e. if the player has cleared an entire world, you want to set the next one as unlocked.
--first, open your file and set a string "content" to the entire value of it
local path = system.pathForFile( "worlddata.txt", system.DocumentsDirectory )
local locfile = io.open( path, "r" )
local content = locfile:read( "*a" ) ; io.close( locfile )
--next, create an empty string "newContent"
local newContent = ""
--reopen your file to "write" mode... this will clear the entire thing, as a side note!
locfile = io.open( path, "w" )
--set your values... in this case, you want to set the "world3" unlocked to "true"
local worldToUpdate = "world3"
local valueToSet = "true"
--loop through the string "content" and search for the proper pattern.
--as it progresses line by line, it does one of two things:
--1) if the first part matches (worldToUpdate), it "fixes" that line and appends it to "newContent"
--2) if it doesn't match, it simply uses the previous line's content and appends it to "newContent"
for thisWorld, currentValue in string.gmatch( content, "(%w+):%s([%w\%d\%p]-)\n" ) do
  if ( thisWorld == worldToUpdate ) then
    newContent = newContent..worldToUpdate..": unlocked="..valueToSet.."\n"
  else
    newContent = newContent..thisWorld..": "..currentValue.."\n"
  end
end
--loop is done... now write "newContent" to your text file
locfile:write( newContent )
--close your file and nil out all local variables.
io.close( locfile ) ; path = nil ; content = nil ; newContent = nil ; locfile = nil
--DONE!

Let me know if this works... I'm typing all of this really, really fast so I possibly made a small syntax error somewhere. :)
uid: 200026 topic_id: 34528 reply_id: 139040


[TOPIC: post.html]
#24

Focus Lab

[GLOBAL: userInfoPane.html]
Focus Lab
  • Starter
  • PipPipPipPipPipPip
  • 129 posts
  • Jedi

Assuming I put this in my main.lua my terminal output is.
2013-01-15 13:14:50.836 Corona Simulator[10810:903]
world1: unlocked=true
world2: unlocked=false
world3: unlocked=true
world4: unlocked=false
world5: unlocked=false
world6: unlocked=false
world7: unlocked=false
2013-01-15 13:14:50.836 Corona Simulator[10810:903] 1 true
2013-01-15 13:14:50.837 Corona Simulator[10810:903] 2 false
2013-01-15 13:14:50.837 Corona Simulator[10810:903] 3 true
2013-01-15 13:14:50.837 Corona Simulator[10810:903] 4 false
2013-01-15 13:14:50.837 Corona Simulator[10810:903] 5 false
2013-01-15 13:14:50.837 Corona Simulator[10810:903] 6 false
2013-01-15 13:14:50.838 Corona Simulator[10810:903] 7 false
2013-01-15 13:14:50.838 Corona Simulator[10810:903] world1 is unlocked!
2013-01-15 13:14:53.843 Corona Simulator[10810:903] in load splashscreen file
uid: 72845 topic_id: 34528 reply_id: 139044


[TOPIC: post.html]
#25

Elliott

[GLOBAL: userInfoPane.html]
Elliott
  • Starter
  • PipPipPip
  • 36 posts
  • Advanced Member

This is really helpful but I wondered what would happen if I provided an update to my app?
So to clarify they install myApp v1.0 and unlock world 1 which I store in the worlddata text file.
I then release myApp v1.1 which they install, will this remove the text file?

Thanks.
E
uid: 158620 topic_id: 34528 reply_id: 139065



[topic_controls]
Page 1 of 2 1 2
 
[/topic_controls]