Omnimaga

Calculator Community => TI Calculators => Lua => Topic started by: AvianIsTheTerm on August 18, 2013, 04:51:28 am

Title: Converting a Text Adventure from nSpire BASIC to Lua
Post by: AvianIsTheTerm on August 18, 2013, 04:51:28 am
Hello!

I haven't used these forums before, though I have lurked here occasionally to look for solutions to bugs etc. while debugging calculator programs.

I've gotten into a little bit of casual programming on TI Calculators, having used them for school, and being interested in programming more generally. Most of what I've done is on nSpire calculators, and as such I have an nSpire CX CAS model.

I've got a fair amount of experience using the built-in nSpire-BASIC language to do mathematical things and the like. I've only done minimal coding in Lua, though I know the general syntax of it, and I am familiar with other interpreter-based languages (by which I mean Python, mostly), in addition to C# and certain other compiled languages.

-----

Basically, I have the following scenario, and I'm just wondering if anyone has any particular ideas about how I could go about this.

Over the past couple of years, every now and again while bored I'll do a bit of development in a silly text adventure thing in the form of a 'Choose Your Own Adventure' story, made in nSpire-BASIC. The structure of it is really poorly done, since it wasn't planned at all. Basically, it just pops up dialogs with each bit of text in it, with Yes / No questions (done within a subroutine using RequestStr, accepting 1 as yes, 0 as no) at particular points. Each Yes / No decision sets the variable 'qa' to either 1 (for yes) or 0 (for no), and an IF statement then 'splits' the story into two paths.

As it's been developed haphazardly over a few years, there's no particular structure to the 'tree' of decisions - paths just split off with their own nested IF statements, and the 'story' is quite large - while I haven't counted the number of decisions and path splits that exist, I'd venture to say that there's well over a hundred of them in total (some paths are split off into their own subroutines so as not to lag out the device when developing on-handheld).

I'm interested in converting / rewriting this in Lua for reasons mainly related to the complete and utter lack of real I/O functions in nSpire-BASIC. I would like to be able to have the game run in such a way that you can actually quit the game while it's running - the dialog box popups prevent you from interacting with the OS interface. In addition, I'd like a better laid out UI - with buttons for yes / no rather than typing 1 or 0 into a dialog.

Now, here's the issue:

I'm sure I'm competent enough to be able to make a functional UI etc. in Lua. My problem is more general: since Lua is primarily event-based rather than imperative as nSpire-BASIC is, how can I convert my tree of decisions such that they will function, and can be simply and quickly added to at any time?

To explain further:

The BASIC version of my game essentially freezes the OS, and the entire thing is within a single subroutine (with subroutines splitting off from that). In Lua, I can't simply do that and maintain a functional UI that the user can interact with. Everything has to go through timer events, the UI is drawn in the paint event etc.

Hence, I can't simply just store the content of my text adventure in a massive nested tree of IF-ELSE statements.

Other solutions I've thought of either would be tricky to build in such a way that the user could interact with it, or alternately with these solutions it would be difficult to add new content at any point in the tree.

If I store the tree content within any 'indexed' data structure (like nested tables) then once I've set a path into that table I can't just easily go and 'split' that path into two subpaths at any given point.

So, to stop waffling on, my basic question is, in this scenario, how would you manage the storage of the game content in such a way that it can be played by the user and edited/modified easily and elegantly?

Thanks for any help you guys can provide!
Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: Jim Bauwens on August 18, 2013, 06:32:59 pm
Could you give a 'schematic' on how it's structured? That would help me get a better idea :)
Also, it is possible to still have a loop (so non-event based) if you use coroutines. That would make a port very easy I suppose. But it's of course still better to try and use the event system.
Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: AvianIsTheTerm on August 19, 2013, 01:45:56 am
Yeah, I guess just an example of the BASIC would be something like this (not taken from the actual code, just a really simplified example of how it's structured):

Code: [Select]
Text "Choose your own adventure!"
Text "You wake up."
sq("Get out of bed?")
If qa = 1 Then
 Text "You get out of bed."
 sq("Eat some breakfast?")
 If qa = 1 Then
  Text "You eat some delicious breakfast."
  Text "You win! You ate delicious breakfast."
 Else
  Text "You go without breakfast."
  Text "You should always start your day with breakfast!"
  Text "You lose!"
 Endif
Else
 Text "You stay in bed all day."
 Text "You lose. You shouldn't be so lazy."
Endif

sq("prompt") is the question function, which will repeatedly pop up until a valid yes/no is given. Since you can't return a value from a sub ('program') and can't use I/O in a function, it just stores the result in the qa variable.

Obviously within the actual program the paths are much longer, and there are the occasional bits which e.g. 'join up' to other paths through a GOTO or a common subroutine.

I know that it would be possible to just straight port it to Lua and not use the events system at all, but it's probably better practise to use the events system as it was intended through the API.

I guess in a lot of ways this question isn't Lua specific, but more general to converting a program designed to run in a completely imperative way (statement -> statement -> statement) to one that runs on an event based object oriented system (on buttonPress -> statement).

The hard part is figuring out how to store the data so it can be retrieved by the program in a way that would be considered 'elegant', and so that it's not fixed to a set number of paths, and that I can edit an arbitrary path somewhere in the middle of the program.
Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: Jim Bauwens on August 19, 2013, 06:08:52 am
What about something like this?

Code: [Select]
local jumpTable = {
    [1]  = {"Choose your own adventure!"},
    [2]  = {"You wake up."},
    [3]  = {"Get out of bed?", {y=4, n=11}},
    [4]  = {"You get out of bed"},
    [5]  = {"Eat some breakfast?", {y=6, n=8}},
    [6]  = {"You eat some delicious breakfast."},
    [7]  = {"You win! You ate delicious breakfast.", "END"},
    [8]  = {"You go without breakfast."},
    [9]  = {"You should always start your day with breakfast!"},
    [10] = {"You lose!", "END"},
    [11] = {"You stay in bed all day."},
    [12] = {"You lose. You shouldn't be so lazy", "END"}
   
}

local position = 1
local question = false
local input = ""

local lines = {}

function printLine(str)
    table.insert(lines, str)
end

function drawLines(gc)
    local height = platform.window:height()
    local lineHeight = 14
    local linesMax = math.floor(height / lineHeight) - 1
    local linesTotal = #lines
    local startLine = math.max(linesTotal - linesMax, 0)
    local line = startLine
    for y=1, math.min(linesMax, linesTotal)  do
        line = line + 1
        gc:drawString(lines[line] or "", 2, y * lineHeight - lineHeight, "top")
    end
end

function jump()
    local text
    local data
    local line
   
    while not data do
        line = jumpTable[position]
        text = line[1]
        data = line[2]
       
        printLine(text)
        position = position + 1
    end
   
    if type(data) == "table" then
        position = position - 1
        question = true
        printLine(">")
    end
   
    platform.window:invalidate()
end

function on.charIn(ch)
    if question then
        input = input .. ch:lower()
        lines[#lines] = ">" .. input
        platform.window:invalidate()
    end
end


function on.enterKey()
    if question then
        if input == "y" or input == "yes" then
            position = jumpTable[position][2].y
        else
            position = jumpTable[position][2].n
        end
        input = ""
        question = false
        jump()
    end
end

function on.paint(gc)
    drawLines(gc)
end

function on.construction()
    jump()
end


You got a jumpTable containing all the statements and locations where you need to jump to in case something happens. Of course you still could extend it to make other stuff happen in other cases, this is just an example.
You could also easily add something so that if you would have a line such as {123} it would jump to 123. That way you can combine join and do lot's of other fun stuff. Maybe add a stack so you can have subroutine like stuff where you put your current location on the stack, jump to another spot and then return back when finished.
Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: Jonius7 on August 20, 2013, 01:19:56 am
Hi AvianIsTheTerm! I've coded for TI-nspire Basic and Lua too, though I still have a lot to learn and understand as well.
What I thought about your "Choose your own Adventure" game in Basic when converting to Lua was to use a table of some sort, with an ID system to identify each line of text. That way, all your decisions and choices text stays in one place, and the functions for interpreting yes and no will be separate from this. Looks like Jim Bauwens has outlined a cohesive structure to what you could do with your program.

Also that code there will work in TI-nspire OS 3.2 (Lua API v2.0), as on.construction() function was introduced in v2.0.

Also be sure to introduce yourself in this forum board here:
www.omnimaga.org/index.php?board=10.0

Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: AvianIsTheTerm on August 20, 2013, 01:40:51 am
@Jim Bauwens -

That structure probably looks like the easiest way to implement it. Thanks!

I think I'd probably extend it into multiple tables for the purposes of my program, since there are several hundred 'lines'. I guess I would have a variable denoting which table I'm currently in, and have some sort of syntax in a line, say:

Code: [Select]
[5] = {"Going to next table...","jumpTableExample2"}
And then modify the table parser to recognise that line[2] is a string, and then jump to that table.


I'll give that a go when I have a bit of free time. The help is much appreciated!

@Jonius7

Yeah, I guess I'd be planning to make it compatible with OS3.2 only, since that's what I've got installed on my calc, and there's not a lot of point using the API level 1.0 just to use 'on.create' instead of 'on.construction'.

And I may as well make a thread in the introductions forum, so thanks for the link.
Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: Adriweb on August 20, 2013, 02:13:32 am
Making things work in 3.1 and 3.2 is often not hard.
As long as the code doesn't use some specific things that are only doable with apilevel 2.0
Title: Re: Converting a Text Adventure from nSpire BASIC to Lua
Post by: Jim Bauwens on August 20, 2013, 03:08:58 pm
I think I'd probably extend it into multiple tables for the purposes of my program, since there are several hundred 'lines'. I guess I would have a variable denoting which table I'm currently in, and have some sort of syntax in a line, say:

Code: [Select]
[5] = {"Going to next table...","jumpTableExample2"}
And then modify the table parser to recognise that line[2] is a string, and then jump to that table.

Yes, of course you should modify it to your needs. My code is really more of a concept showing how I would do it. Anyway, glad that I could help :)