Tag Archives: tutorial

Starting with Lua programming on the Nspire

Hello and welcome to the tutorials index page!

You have several options to learn TI-Nspire Lua programming.

Inspired-Lua hosts several tutorials, but also links to other websites (like Steve Arnold’s). Here are some good resources:

We highly advise you to start…. by the beginning, and for that, click here for A-to-Z TI-Nspire Lua tutorials.

If you want to dive in the theory-based tutorials about Lua on the TI-Nspire, explaining how things actually work, head over here.

However, if you want to directly start Nspire-Lua scripting on a practice-based way, learning by example, you can go there.

Happy Lua programming on the TI-Nspire !

How to have a nice little “input” function in Lua…

(improved version of Nick Steen’s website’s example)

You could be quite surprised by the fact that there isn’t any native way to have a keyboard text input function on the Nspire Lua API. Indeed, it’s a little weird since this is often used for many types of programs (whether in games for typing the username, or other apps to type user data etc.).

Anyway, don’t worry, you can program it yourself with a little piece of code to add to your script ! It actually catches the keypresses with the on.charIn function, and stores what the user types in a string (appending it). You then just have to display this string on the screen and that’s all.

If you want the user to be able to delete some letters, just add some code to the on.backspaceKey function, and that’s all !

In the example below, we set a character limit to 25, but that’s totally up to you.

Well, here’s the complete code ready to be used :

input = ""   
 
function on.paint(gc)
    gc:drawString(input,5,5,"top")  -- display string
end
 
function on.charIn(char)
    if string.len(input) <= 25 then   -- limit of 25 chars
        input = input..char   -- concatenate
        platform.window:invalidate()   --screen refreh
    end
end
 
function on.backspaceKey()
    input = string.usub(input,0,-2)  -- deleting last char
    platform.window:invalidate()  
end

Bye !

Concepts and Basics

This part will explain you how the Lua actually works inside the OS and help you to figure out what you’re doing when you write a script for the TI-Nspire. It is recommended to have some basics on Lua programming or some knowledge of event-driven languages, but keep in mind that it is not required.

The Lua is an interpreted script language which means that it isn’t as fast as ASM/C programs, but is still better than the TI-BASIC. One good thing is that this language is in a complete harmony with the OS with basic events and a powerful graphic context.

Lua is normally a sequential script language. For example, when we use the print() command to display a value, we can easily guess when the command will be run in the script. Here’s an example :

a = 1
print(a)
a = a + 1
print(a)

Output :

1
2

Nothing special. However, on the TI-Nspire, Lua has a completely different approach. We meet this approach with high-level programming or with object-oriented languages (like C++, C#, …). In those languages, we don’t have the ability to control the flow/execution of any function. Yes, it can be quite strange to hear that, but it’s the way it is. Are we going to learn a language that doesn’t do what we tell it to do ? Well, in a way, yes.

But don’t worry ! We’re here to learn how to cross this quite unstable bridge ! A wonderful world is on the other side.

First of all, you have to “change team”. As in before, you were the boss, this means you used to tell the machine to compute 1 + 1 and it would proudly output “2”. Now, you are a worker. A task authorization is given to you, thus, you explain to the machine how to do this work. Actually, those “authorizations” are called events. When the event is called, you can do what ever you want. Here is a pseudo-code explaining what to do when the “Cook” event is called :

function FaireLaCuisine()
	organiserLaPaillasse()
	couperLesIngredients()
	assembler()
	chauffer()
	servir()
end

You can easily understand that you won’t cook when you get the job. You’ll wait for your boss’ order ! Well, it is exactly the same thing between the TI-Nspire and you. But this time, the TI-Nspire framework is the boss. Everything is event-based.

In a nutshell, our functions have to be called by the TI-Nspire. But how to be sure that they will be executed ? It is the moment to look at the Events list. When an event is fired, the TI-Nspire gives zero, one or multiple parameters (“arguments”) that we can use in our function. This lets us know, for example, which key has been pressed, because when the TI-Nspire executes the charIn(ch) event, it gives also a string as an argument corresponding to the key. However, when the enterKey() event is fired, no argument is given.

Before, in a non-event-based language (like BASIC, C, PHP …) we used to program like this :

-- Initialisation des constantes ici
...
 
k = 0
while k != 0 do
	k = getKey()
end
if k == 72 then
	-- quelque chose
end

Now, it looks like that :

-- Initialisation des constantes ici
...
 
function on.charIn(ch)
    if ch == "7" then
	-- quelque chose
    end
end

We hope you understood that very important part…

 

Let’s go to Part 2 now !

Object Classes

Back to part 4

In this part, we will talk about a quite interesting and useful topic in Nspire Lua programming, which relies on working with “objects” instead of (or in addition to…) classic variables.

If you already have some Lua knowledge, you should know that it is using a lot of key-indexed or key/value tables. This means that each element of a table (or list) is linked to a key that lets us to refer with. For example the table below is key-indexed with strings :

tab = {"cle1"="valeur1", "cle2"="valeur2"}
print(tab["cle1"]) -- va afficher "valeur1"

In Lua, keys are polymorphic, that way we can easily store strings, tables or functions as key or as value :

tab = {
  [1]="exemple",
  "2"="de",
  [function() return 3 end]="table",
  [false]="polymorphe"
}
 
for k, v in pairs(tab) do
  print(k, v)
end

Will display :

1                 exemple
2                 de
function: 10f6840 table
false             polymorphe

Even the Lua global environment (_G) is a table.

It’s in this way we’re going to introduce Lua object-oriented-programming (“OOP”) : Each object is a table and each object has multiple elements named methods or properties. In the future if we would like to create a new object we simply duplicate the table from its meta-table. If we would like to create inherited objects, we will also duplicate its meta-table and change it a little bit to make it different from the main object.

myObject = {
    nom="toto",
    age=21,
    ChangerNom=function(self, nom)
        if #nom > 1 then
            self.nom = nom
        end
    end,
    ChangerAge=function(self, age)
        if age > 1 then
            self.age = age
        end
    end
}

We can easily access/write stuff on any element. For example :

print(myObject.age) -- affiche 21
myObject:ChangerAge(18) -- identique à myObject.ChangerAge(myObject, 18)
print(myObject.age) -- affiche 18

You have to understand that there is no such thing as private/public things in Lua as you could find in C++/C#/Java…, since this is just a representation, and not a real implementation. So it can be easier in Lua, especially for beginners.

You can now understand why you call  platform.window:width()  this way, and not with a global function such as  “GetWindowWidth()”.  In fact, width() is a method of the window object, which itself is a property of the platform object. Same thing for platform.gc() which we can find shortened as ‘gc‘. Actually, gc is an object and platform.gc() is its “constructor”.
In one hand, it’s pretty much the same thing (they both have the same methods), but in the other hand, they have some differences (some of their properties aren’t the same).
(NB : In Nspire Lua code, try to avoid using gc by calling platform.gc(), because of some unexpected behaviors you can get if you don’t know exactly what you’re doing. You should always use the gc that the on.paint(gc) event provides as a parameter)

Let’s tell you what classes look like in code and how to create them.

In the Nspire Lua API, we can find a “class()” method, that usualy doesn’t come with Lua. In fact, this method was created to help programmers create their class by having a much easier and shorter way to do it. For the curious people among our readers, here’s what we would have to do to create and use a class without the class() method (here, for the “Account” class).
(it can be scary if you don’t know Lua well – you can skip this paragraph if you want)

Account = {}
Account.__index = Account
 
function Account.create(balance)
    local acnt = {}             -- our new object
    setmetatable(acnt,Account)  -- make Account handle lookup
    acnt.balance = balance      -- initialize our object
    return acnt
end
 
function Account:withdraw(amount)
    self.balance = self.balance - amount
end
 
-- create and use an Account
acc = Account.create(1000)
acc:withdraw(100)
More info here : http://lua-users.org/wiki/SimpleLuaClasses

Now, with the Nspire Lua API, with  class() :

Account = class()
 
function Account:init(balance)
    self.balance = balance      -- initialize our object
end
 
function Account:withdraw(amount)
    self.balance = self.balance - amount
end
 
-- create and use an Account
acc = Account(1000)
acc:withdraw(100)

That’s much better, right ?

Here, “init()” is the constructor. You *have* to have an “init(…)” method for your classes since it’s written that way in the class() method. The init(…) function will be the one that will be called when your objects are going to be created.

We are going to show you a real example of OOP programming in Lua, by creating a script that will create a class, called “Shape”, which will have several methods including a constructor allowing us to creates shapes.

Forme = class()
 
-- Constructeur
function Forme:init(x, y, image)
    self.xpos = x
    self.ypos = y
    self.pic = image
end
 
-- Dessiner la forme
function Forme:paint(gc)
    gc:drawImage(self.pic, self.xpos, self.ypos)
end
 
-- Repositionner l'objet en indiquant les nouvelles coordonnees
function Forme:setxy(newx, newy)
    if newx > xmin and newx < xmax and newy > ymin and newy < ymax then
        self.xpos = newx
        self.ypos = newy
   end
end
 
-- Repositionner l'objet en indiquant le deplacement a effectuer
function Forme:move(dx, dy)
    local newx,newy
    self:setxy(self.xpos + dx,self.ypos + dy)
end

You may have noticed the use of global variables like xmin, xmax. These are defined earlier in the script, without . For example :

xmin = 20
ymin = 30
xmax = 250
ymax = 200

We can now play around with our “Shape” class. We’re now going to create two images and convert them into TI.Images using the official developement tool :

carre_rouge = image.new("\010\000\000\000\010\000\000\000\000\000\000\000\020\000\000\000\016\000\001\000\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252\227\252")
smiley = image.new("\016\000\000\000\018\000\000\000\000\000\000\000 \000\000\000\016\000\001\000\156\243\156\243\156\243\189\247\213\222\235\193\136\181g\181\135\181g\181\137\185O\202\189\247\156\243\156\243\156\243\156\243\156\243{\239O\206\168\222F\243D\243d\247d\247#\243\004\235E\214\170\1858\231\156\243\156\243\156\243\213\222m\210\231\230d\247\132\251\195\255\131\255\131\255\131\255C\251\003\247\132\226\233\193r\210z\239\156\243\204\189\198\226\132\247\166\218\007\198\198\226\195\255\131\255#\243\231\197\007\198\002\251\131\230\135\185\246\222\212\218g\210D\243\166\218\166\218\197\226D\239\131\255\131\255C\247\165\218d\222\006\206\194\242\196\205O\206\170\185\005\239\131\255\197\226D\239\131\255\131\255\131\255\131\255C\251\034\255\002\255E\218\226\250b\234\167\193G\173C\247\131\255\131\255\130\218\001\198\162\222\131\255c\251\002\243\161\197\161\197\226\250\226\250\162\246\133\193f\181#\243C\251\131\255\194\193\194\156\002\202C\251\034\255\162\234\194\156\194\156\193\250\193\254\193\254\133\193g\181\003\247c\251\034\255\196\230e\218\196\234\034\255\034\255\226\242%\218%\218\226\250\226\250\161\254\133\193&\169\226\242\034\255\034\255\034\255\034\255\034\255\002\255\002\255\193\254\193\254\226\250\161\254\161\254\161\254d\189G\173\163\234\002\255\226\242\194\242\163\234\162\242\162\242\162\242\130\238\130\242B\242\130\242\162\246B\242\133\193\014\198E\214\194\242f\181\198\156\231\156\231\156\231\156\231\156\231\156\231\156\198\156\231\156B\242\227\213\235\197Y\235\136\185\130\238\193\254\132\189s\206\222\251\222\251\222\251\222\251{\239\198\156\130\242\129\254d\189\179\214\156\243\237\193\196\205\162\242\162\242\198\201)\165)\165)\165)\165H\173B\242\129\254\227\213\169\193\023\227\156\243z\239\137\185\034\226\162\246\130\242B\234\034\230\034\230B\234\034\234\129\254\130\242D\181\213\222\156\243\156\243\156\243\023\227\201\197\002\222\162\246\161\254\129\254\161\254\129\254\129\250\034\234d\189\147\214\156\243\156\243\156\243\156\243\156\243\246\222\011\206\196\205\002\222B\238\034\238\034\230\196\209\165\197\147\214{\239\156\243\156\243\156\243\189\247\156\243\156\243\023\227p\210\011\206\198\205\198\205\232\205O\210\147\214\156\243\156\243\156\243\156\243")
forme1 = Forme (xmin + 20, ymin + 20, smiley)
forme2 = Forme (xmax - 30, ymax - 30, carre_rouge)

You can see how we’re not explicitely calling the init() constructor of the Shape class.
In fact, we just have to call Shape(…) and the init will be called with the arguments.

Now we just have to create the main structure of the lua script, with the basic events you should know by now. Here’s what it looks like :

-- Affichage
function on.paint(gc)
    gc:setColorRGB(231, 231, 231)
    gc:fillRect(xmin, ymin, xmax-xmin+15, ymax-ymin+15)
    forme1:paint(gc)
    forme2:paint(gc)
    gc:setColorRGB(0, 0, 255)
    gc:setFont("sansserif" , "b", 11)
    x = gc:drawString("(" .. forme1.xpos..", ".. forme1.ypos .. ")", 50, 0, "top")
end
-- Permet de deplacer forme1 avec les fleches directionnelles
function on.arrowLeft()
    forme1:move(-5, 0)
end
 
function on.arrowRight()
    forme1:move(5, 0)
end
 
function on.arrowUp()
    forme1:move(0, -5)
end
 
function on.arrowDown()
    forme1:move(0, 5)
end
-- Permet de choisir l'emplacement de forme2 avec la souris
function on.mouseDown(wx, wy)
    forme2:setxy(wx, wy)
end

Here’s what you get !

Have fun !

NB : you can find examples of classes and OOP programming in real games programmed in the Nspire Lua API. For instance : the BreakOut game by Adriweb, which uses different classes to have objects such as the ball(s), the blocks, the paddle, the bonuses etc.