All posts by Levak Borok

Co-founder/admin of TI-Planet.org and Inspired-Lua.org                                    Jérémy Anselme ("Levak Borok")

How to create a Screen Manager

When you create a game or some tool in a “complete” way, it is often desired to achieve some sort of a set of screens/views throughout which the user navigates. There are several ways to achieve an engine that will allow us to manage all these screens, from simple code (but less convenient), to the most complex and/or difficult to understand, but easy to use after all. The goal of this tutorial is obviously not to give you a fixed idea of what a screen manager may look like (since there are many different types), but to offer you a set of choice. This tutorial is quite long as it presents comprehensive examples for the practice part and some necessary theory too, so stay focused !

First, what we mean by “Screen Manager” is a way of organizing the code so in such way that we can isolate the “drawing” parts from the functional parts. The clearer and visible this border is,  the neater and more understandable the code gets, therefore allowing easier integration of our “Screen Manager”.

Take this example : we want to make a game with : a simple menu, the game itself, a help page and a page of high-scores. At first glance, the number of screens is limited and fixed before the creation by our specifications. That number is low (4) and so it is easy to consider something like:

function on.create()
  screen = 0 -- 0=menu, 1=game, 2=help, 3=highscores
end
 
function on.paint(gc)
  if screen == 0 then -- we draw the menu
  elseif screen == 1 then -- we draw the game
  elseif screen == 2 then -- we draw the help
  elseif screen == 3 then -- we draw the highscores
  end
end
 
function on.enterKey()
  if screen == 0 then
    screen == 1
  elseif screen == 1 then
....

Ok! No need to go further! We get it. To create an ugly, tedious-to-write code, this would be the best solution.
It is primarily designed for very small applications easily doable.

There is however a less ugly way, using arrays of functions :

function on.create() -- or on.construction with OS >= 3.2
  menu, game, help, highscores = 1, 2, 3, 4
  screen = menu
  paints = {}
  enterKeys = {}
end
 
function on.paint(gc)
  paints[screen](gc)
end
 
function on.enterKey(gc)
  enterKeys[screen](gc)
end
 
paints[menu] = function(gc)... end -- we draw the menu here
paints[jeu] = function(gc) ... end -- we draw the game here
...
enterKeys[menu] = function(gc)... end -- menu handling here
enterKeys[jeu] = function(gc) ... end -- game handling here
....

Here, it is clear that reading the code will be much easier than before. Moreover, the use of labels instead of the values themselves ​​significantly improves reading the code !
Feel free to “abuse” of that… ! You can, if you have too many “constants”, using an array of constants in order to organize them, like this:

screen = {menu=1, jeu=2, aide=3, records=4}
--> screen.menu == 1

Or, if you want something even more “automatic” (no need to count/shift the numbers) :

function createEnv(t)
  local env = {}
  for i, v in ipairs(t) do
    env[v] = i
  end
  return env
end
screen = createEnv({"menu", "game", "help", "highscores"})
--> screen.menu == 1

That being said, handling different screens remains archaic for other uses than games. It is of course possible to create a more elaborate system that facilitates the use and coding. What could be better?

You guessed it … use classes ! (if you’re not familiar with them, go to our tutorial !)

What we will code is actually a superset of the TI-Nspire API. You know the on.paint(), on.arrowKey() etc. events … Well, we will pass them along to our screen manager. The interest of coding a “parent class” here is that we are sure that a screen has specific methods, which will be called from standard events, without the fear of forgetting any when we create a new screen.

First, we must create our Screen class:

Screen = class()
function Screen:init() end

First, a screen does not need special initialization. Let’s recall that again : what we define is a superset of the API TI-Nspire, so we will write (without defining the content!) an exhaustive list of event-driven methods that will be available:

function Screen:paint(gc) end
function Screen:arrowKey(key) end
function Screen:timer() end
function Screen:charIn(ch) end
....

These functions are called “virtual”, and will be redefined later, for each particular screen. Because the class contains here only virtual functions, it is also called “virtual” (a bit of vocabulary isn’t bad 🙂 ). The Screen Manager involves creating a virtual class, a model, a “buffer” that guarantee us the existence of functions when we will code the specific event-driven parts:

activeScreen = Screen()
function on.paint(gc) activeScreen:paint(gc) end
function on.arrowKey(key) activeScreen:arrowKey(key) end
function on.timer() activeScreen:timer() end
function on.charIn(ch) activeScreen:charIn(ch) end

Thus, when we want to add the code for a screen, we code as if we coded a screen! Just think that “Menu” is “on.” :

Menu = class(Screen)
function Menu:init() end
function Menu:paint(gc) gc:drawRectangle(0, 0, 50, 50) end
function Menu:arrowKey(key) ... end
activeScreen = Menu()

This technique is widely used but also has a weakness: each screen change leads to recreating a screen object. In some cases it can be very useful (reset the game), in others, it is useless (the menu does not move). One can always store it in variables, or only pass the class itself as parameter. But there are even smarter choices to make.

Indeed, we can often see that to couple this technique with a stack. A stack is a special list: a data structure called “first in, first out” (or FIFO). The use of a stack here is to store initialized displays in an ordered list, while keeping the previous process as we want.

So we will add the stack management part. To do this, we must change the access to ActiveScreen by ActiveScreen(), replace the assignments of ActiveScreen by a function that will add to the screen stack (PushScreen()) and  we must not forget to pop the screens out once it’s no longer used.

Here’s how that goes, and this is the final tutorial code:

------ Screen Manager
Screen = class()
 
function Screen:init() end
 
-- virtual functions to override
function Screen:paint(gc) end
function Screen:timer() end
function Screen:charIn(ch) end
function Screen:arrowKey(key) end
function Screen:escapeKey() end
function Screen:enterKey() end
function Screen:tabKey() end
function Screen:contextMenu() end
function Screen:backtabKey() end
function Screen:backspaceKey() end
function Screen:clearKey() end
function Screen:mouseMove(x, y) end
function Screen:mouseDown(x, y) end
function Screen:mouseUp() end
function Screen:rightMouseDown(x, y) end
function Screen:help() end
 
local Screens = {}
 
function PushScreen(screen)
    table.insert(Screens, screen)
    platform.window:invalidate()
end
 
function PullScreen()
    if #Screens > 0 then
        table.remove(Screens)
        platform.window:invalidate()
    end
end
 
function activeScreen()
    return Screens[#Screens] and Screens[#Screens] or Screen
end
 
-- Link events to ScreenManager
function on.paint(gc)
   for _, screen in pairs(Screens) do
        screen:paint(gc)
    end
end
 
function on.timer()
    for _, screen in pairs(Screens) do
        screen:timer()
    end
end
 
function on.charIn(ch) activeScreen():charIn(ch) end
function on.arrowKey(key) activeScreen():arrowKey(key) end
function on.escapeKey()	activeScreen():escapeKey() end
function on.enterKey() activeScreen():enterKey() end
function on.tabKey() activeScreen():tabKey() end
function on.contextMenu() activeScreen():contextMenu() end
function on.backtabKey() activeScreen():backtabKey() end
function on.backspaceKey() activeScreen():backspaceKey() end
function on.clearKey() activeScreen():clearKey() end
function on.mouseDown(x, y) activeScreen():mouseDown(x, y) end
function on.mouseUp() activeScreen():mouseUp() end
function on.mouseMove(x, y) activeScreen():mouseMove(x, y) end
function on.rightMouseDown(x, y) activeScreen():rightMouseDown(x, y) end
function on.help() activeScreen():help() end
 
function on.create() PushScreen(Menu()) end
function on.resize() end

NB: being exhaustive at this point is not required.
However, the number of functions defined in the Screen class must match the number of defined events.

Call the master class constructor

You are obviously a lot that enjoyed the easy-go of Lua classes on TI-Nspire, but also noticed that it is too simple to let you work correctly on a specific case. Let’s explain.

This case is the ability to call the master class constructor in the inherited class in order to avoid repetitions. Indeed, if you change one field in the master class, you must change that field in every inherited classes.

Here is an example :

A = class()
function A:init(x)
  self.t = 0
  self.x = x
end
 
B = class(A)
function B:init(x, y)
  self.t = 0
  self.x = x
  self.y = y
end

Here, if we would like to change t = 0 from the master class constructor, we also have to change it in the B class constructor in order to be coherent. Ones could think like that :

A = class()
function A:init(x)
  self.t = 0
  self.x = x
end
 
B = class(A(0, 0)) -- construction of the object before inheriting
function B:init(x, y)
  self.x = x
  self.y = y
end

But here again, the field x is repeated.

One thing we forgot to take care here is that B is an inherited class of A, so, each field of A are obviously in B. But for the constructor, we’re just redefining it. Is there a way to call the previous constructor from A giving B as parameter ?

Actually, yes !

Something important to know in order to understand the solution, is that this :

dummy = class()
function dummy:doSomething()
  self.mark = 0
end
dummy:doSomething()

is exactly the same of that :

dummy = class()
function dummy.doSomething(self)
  self.mark = 0
end
dummy.doSomething(dummy)

or again :

dummy = class()
function dummy:doSomething()
  self.mark = 0
end
dummy.doSomething(dummy)

The colons allow to give as first parameter the object that has this method. So nothing prevents us to not use this tip and then give to the method a manual first parameter as below !

A = class()
function A:init(x)
  self.t = 0
  self.x = x
end
 
B = class(A)
function B:init(x, y)
  A.init(self, x) -- call the A class constructor in order to avoir repetitions
  self.y = y
end

 

Create the is() operator to test an object type

On TI-Nspire, Lua lets you work easily with classes with class() function, that is also used to create inherited classes, based on the mother class. Sadly, since this class concept is purely superficial, there is neither function nor syntax to work with specific kind of classes. In this tutorial, we’re going to see how to reproduce the “is” feature that we meet in C# for example.

The is() operator lets you distinguish between an object created with one class and another object created with another class. We are going to create the is() operator specific to Lua because it doesn’t support inherited classes tests.

Here is an example of what we are trying to do :

function is(obj, class)
  -- we are going to create it
end
 
A = class()
function A:init(x)
  self.x = x
end
 
B = class(A)
function B:init(x)
  self.x = x * 2
end
 
t = { A(1),  B(1) }
for k, v in pairs(t) do
  if is(v, A) then
    print("A class, value = "..v.x)
  elseif is(v, B) then
    print("B class, value = "..(v.x / 2))
  end
end

The goal is here obvious : if we store in a table objects from different classes we some common fields, we d’like to be able to distinguish objects from a particular class.

But how write such a function in that case ? Add a specific field ? No need !
Indeed, the class() function does half of the work. It stores something in the __index field of your object (in reality a table). This one contains a reference to the mother class. When you do a = A(), a.__index is equal to A.__index (by reference) !

As simple as you guessed :

function is(obj, class)
  return obj.__index == class.__index
end

NB : Since the edge between classes and object is not so far big in Lua (contrary to other OOP langages where an object is created by a class), it is possible to test whether or not two objects have the same mother class with one test less :

A = class()
function A:init() end
a = A()
b = A()
is(a, b) -- returns true

 

Save a high score (or anything) without cheating possibility !

In this tutorial we’re going to take a look at a pretty interesting feature of the TI-Nspire framework in Lua : save and restore any kind of data directly in Lua, that lets us playing around with a high score for a game or any other configuration data for a more complex program.

Example : You just released a wonderful game. You’d like to share it with your friends, that way they can challenge you thanks to your high-scores system. But how is that working?

The first way you could think of is to use the var API. Therefore, we save the high score in the document as a global variable (math variable).

Your high score system is done ! Hum … wait a second, one of your friend managed to score 10 000 000 points to your game ! What the heck ? He simply modified the global variable you’re using as an high score, since it’s freely accessible via a Calculator application. Here, it is not hard to cheat !

A second way is to protect the first one by using math.eval() which lets us launch any TI-Basic instruction, as the Lock one for example. Then, if we do like so :

math.eval("Unlock highscore")
var.store("highscore", highscore)
math.eval("Lock highscore")

our high score variable will be write protected. But, since Lock is a TI-Basic command, it can be launch outside the Lua program. Things aren’t going to be better.

What about looking for a specific Lua command ? Actually, there is var.monitor() that probes a specified variable and checks if this variable will be changed. If so, on.varChange() event is called. This lets us to control any variable changes.

Here is an example :

function on.create()
  if not var.recall("highscore") then
    highscore = 0
    var.store("highscore", highscore)
  end
  var.monitor("highscore")
end
 
function on.varChange(list)
  for k, v in pairs(list) do
    if k == "highscore" then
      if var.recall(k) ~= highscore then
        return 1 -- it is an external change, block modifications
      else
        return 0 -- it is an internal change, allow modifications
      end
    end
  end
  return 0 -- allow modifications of other monitored variables if any
end

This code will disallow any external modification of the highscore variable, with the following error message :

Cannot accept change : Invalid input

Obviously, if you have to do this for multiple variable, for example configuration data, don’t do this since it is a heavy and repetitive method …
It is at this moment that we take a look to the documentation. We can see two trivial events : on.save() & on.restore().
In fact, this event couple does exactly what we expected to do from the beginning !

Actually, when the widget get closed (when we close the document, or copy/cut the widget) the on.save() event is called. You have to define on.save() to make it return any kind of value (boolean, number, string, table etc …). The next time the widget will open (when we open the document, or paste the widget), the on.restore() event will be called, with the data returned by on.save() as the parameter !

Here we are, easy-going :

function on.save()
  return highscore
end
 
function on.restore(data)
  if type(data) == "number" then
    highscore = data
  end
end
 
function on.create()
  if not highscore then
    highscore = 0 -- first time we initialize highscore
  end
end

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.

Using a predefined variable in a TI-Nspire document

Back to part 3

This part will show you how well the Lua language is integrated in the TI-Nspire framework.
For this, we’re going to study the var API and expected events.

Retour à la partie 3

Cette partie vous montrera que le Lua est bel et bien en parfaite harmonie avec la TI-Nspire et son framework. Pour cela nous vous proposons d’étudier l’API var et les évènements qui en découlent.

Tout d’abord , créez un nouveau classeur TI-Nspire et définissez y une variable n, par exemple un curseur permettant d’en faire varier la valeur entre 0 et 10 ou directement dans la page de l’application calculs.

C’est cette variable que nous allons récupérer dans le script Lua !

Pour cela, créez un nouveau fichier Lua avec votre éditeur de texte préféré et nous allons commencer à le remplir.

L’API var permet de faire pleins de choses intéressantes et nous allons les étudier.

Tout d’abord, notre script doit savoir si la variable n change. Nous trouvons donc cette ligne :

 

-- On indique ici qu'il faut surveiller le contenu de la variable n
var.monitor("n")

Au passage, nous pouvons écrire une fonction récursive qui renverra la factorielle d’un nombre, rien que pour l’exemple :

function factorielle(n)
	if math.floor(n) < n or n < 0 then
		return ("Erreur")
	elseif n == 0 then
		return(1)
	else
		return(n * factorielle(n - 1))
	end
end

Maintenant, nous allons nous intéresser au couplage des évènements. Il nous faut deux évènements. D’abord celui pour dessiner à l’écran, c’est à dire on.paint(), mais également un évènement déclenché lorsque notre variable change. Cet évènement est on.varChange() et est lancé chaque fois qu’une variable appartenant à la liste des variables surveillées (avec var.monitor()) est modifiée.

function on.varChange(varlist)
	-- On provoque une reactualisation de la fenetre
	platform.window:invalidate()
end

Concrètement, dès qu’une variable surveillée change, on demande à ce que l’écran soit actualisé. Donc maintenant, il faut faire cette partie d’affichage en créant la fonction on.paint(). En effet, appeler platform.window:invalidate() marque l’écran comme “invalide”, donc bon à être actualisé. Au prochain tour de boucle de processus, comme l’écran doit être actualisé, le framework de la TI-Nspire lancera la fonction on.paint()

function on.paint(gc)
	local wh, ww, n, x
	-- nombre de pixels (hauteur et largeur)
	wh = platform.window:height()
	ww = platform.window:width()
 
	-- affichage d'un trait de separation
	gc:setPen("medium", "smooth")
	gc:drawLine(0, 60, ww, 60)
 
	-- affichage de la taille de la fenetre en bas de l'ecran
	-- il s'agit juste d'un exemple !
	gc:setColorRGB(200, 200, 200)
	x = gc:drawString(wh, 10, wh - 10, "bottom")
	gc:drawString(ww, x + 10, wh - 10, "bottom")
 
	-- affichage de n!
	n = var.recall("n")
	if math.floor(n) < n or n < 0 then
		gc:setColorRGB(255, 0, 0)
		gc:setFont("sansserif" , "b", 10)
		gc:drawString("n n'est pas un ",10,10,"top")
		gc:drawString("entier naturel ",10,25,"top")
	else
		gc:setColorRGB(0, 0, 255)
		gc:setFont("sansserif" , "b", 20)
		x = gc:drawString(n.."! = ", 10, 10, "top")
		x = gc:drawString(factorielle(n), x + 5, 10, "top")
	end
end

Voilà ! C’est tout !

Lancez ensuite TI-Nspire Scripting tools et cliquez sur le premier bouton (ou choisissez tools, Lua Script to Clipboard) puis sélectionnez le fichier contenant le script que nous venons de créer. Allez dans le logiciel TI-Nspire, et utilisez Ctrl V pour copier le code Lua.

Faites ensuite varier la valeur de n, et la miracle !

La valeur dans la fenêtre Lua est actualisée !

Tip : Si vous avez modifié le script, il vous suffit dans TI-Nspire Scripting Tools de

  • cliquer sur le second bouton (ou choisissez tools, Reload Script).
  • Allez ensuite dans le logiciel TI-Nspire, cliquer sur la fenêtre correspondant au code Lua.
  • Ctrl K pour sélectionner cette fenêtre
  • Ctrl V pour copier le nouveau script Lua

Idées d’améliorations :

Vous pouvez tester une première “amélioration” de ce script en ajoutant les lignes suivantes, permettant de “piloter” n avec les touches “vers le haut” ou “vers le bas” :

function on.arrowUp()
	var.store("n", var.recall("n") + 1)
end
 
function on.arrowDown()
	var.store("n", var.recall("n") - 1)
end

>> Partie 5

Linking Events

Back to part 2

This part will explain how each event is linked to the core.

First of all, we have to understand how this works. Basicly the script is launched before the executive engine (master). This lets us initialize our variables. Sadly, this also means that we can neither evaluate expressions nor use some of the TI-Nspire API (like platform, var) until the engine is launched. Here is a call summary :

  • Launch string library
  • Launch math library
  • Launch TI-Nspire FrameWork (toolpalette, image, …)
  • Open and launch Lua user’s script
  • Launch var API (store, recall, …)
  • Launch platform API (window, gc, …)
  • Link events (pseudo code)
while(Exit)
	------- Some OS routines here
 
 	---- Begin of Event link
	buffer:captureDataFromKeyPad() 	--  (N) some underground routines to catch events
	if buffer.charInput ~= "" then 	 	--  (N)
		on.charIn(buffer.charInput)
		buffer.charInput= "" 	 	 	--  (N)
	end
	if buffer.arrowKey ~= "" then 	 	--  (N)
		on.arrowKey(buffer.arrowKey)
		buffer.arrowKey = "" 	 	 	--  (N)
	end
	----- etc ... 	
	if platform.window.isInvalidate then 
	 	local gc = platform.gc()	 	  	--  Create a new graphical context
	 	gc:begin()	 		 	   		-- initialize the graphical context
		on.paint(gc) 	  	 	  		-- save all the things we have to draw
	 	gc:finish()  	  	  	    		--  (N) draw all those things	
		platform.window.isInvalidate = false 	-- say that the window has been drawn
	end
	----- End of Event link
end

Note : the (N) commented line only indicates the meaning of the routine. Those functions don’t really exist.

Now we can understand how everything is linked, just by a main loop. This helps you to understand that you don’t have to code a loop yourself, because the screen wouldn’t be refreshed. This also helps to see when the screen gets refreshed. In other words, we cannot use niether gc nor platform.gc() in order to draw somthing on the screen if we are outside of on.paint() (except if your outside function is called within on.paint() ).

Here is an example of a simple Lua program that displays a message only when a key is pressed (and let the screen blank otherwise).

function on.paint(gc)
	if message then
	 	gc:setFont("sansserif", "r", 10)	-- initialize font drawing
	 	gc:drawString(message, 0, 0, "top")	-- display the message at (0, 0) coordinates
	 	message = nil 				-- erase the message
	 	timer.start(1) 	 	 	-- start a timer to exit on.paint() but keep in mind that we have to redraw the screen
	end
end
 
function on.timer()
	timer.stop()
	platform.window:invalidate()
end
 
function on.charIn(ch)
	message = "Hello World !"		-- store a message
	platform.window:invalidate()		-- force display
end

When you open the document, the script is read once. It initializes and overwrites all the functions and globals with the ones you defined. Thus, message is nil. Once the on.paint() event is called, message is nil, thus, nothing is done. When you press a key that calls on.charIn()message is now “Hello World” and we tell the platform that the screen has to be refreshed. Then, on.paint() is called again, message is not nil then we display it, erase message and launch a timer. Why is that ? Because if we call platform.window:invalidate() right there, we won’t refresh the screen. Why again ? Just look at the pseudo-code above. We set the window as drawn after each call of on.paint(). Thus a timer is necessary to manually recallon.paint() and exit the on.paint() function to draw the screen. When the timer is ended, on.timer() is called and we refresh the screen. The screen is then redrawn but there is nothing to draw because message is nil. Thus, the graphic context lets the screen blank.

>> Partie 4

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 Cook()
	organize_the_bench()
	cut_ingredients()
	assemble()
	heat_up()
	serve()
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 :

-- Init Constants here
...
k = 0
while k == 0 do
	k = getKey()
end
if k == 72 then
	-- do something
end

Now, it looks like that :

-- Init Constants here
...
function on.charIn(ch)
	if ch == "7" then
		-- do something
	end
end

We hope you understood that very important part…

 

Let’s go to Part 2 now !