Archives pour l'étiquette tutorial

Démarrer en Lua sur Nspire

Bonjour et bienvenue sur la page d’accueil des tutoriaux !

Vous avez plusieurs possibilités pour apprendre la programmation Lua sur TI-Nspire.

Inspired-Lua contient un certain nombre de tutoriaux, mais pointe aussi vers d’autres sites (comme celui de Steve Arnold).

Voici quelques liens utiles :

Nous vous conseillons de commencer… par le début, et pour cela, veuillez suivre ce lien pour des tutoriaux de A-à-Z.

Ce lien pointe vers une page où sont rédigées toute une série d’excellents tutoriaux mais en Anglais. Inspired-Lua a commencé à en traduire en Français (voir le lien quelques lignes plus tard sur les tutoriaux de « pratique »)

Si vous voulez vous plonger dans les tutoriaux théoriques sur la programmation Nspire Lua, clique ici !

Sinon, si vous voulez directement commencer par des tutoriaux de pratique, en apprenant par l’exemple, c’est par là que ça se passe.

 

Bonne programmation Lua sur TI-Nspire !

Comment avoir une petite fonction « input » bien sympa en Lua…

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

Vous pourriez être très surpris par le fait qu’il n’y ait pas de manière native d’avoir une fonction d’entrée clavier (input) texte sur l’API Lua Nspire. En effet, c’est un peu bizarre car ce sont souvent utilisés pour de nombreux types de programmes (que ce soit dans les jeux pour taper le nom d’utilisateur, ou d’autres applications pour entrer des données utilisateur, etc.)

Quoi qu’il en soit, ne vous inquiétez pas, vous pouvez le programmer vous-même avec un petit bout de code à ajouter à votre script! Il capture en fait les touches appuyées avec la fonction on.charIn , et les enregistre ce que l’utilisateur tape dans une chaîne (concaténation). Il suffit ensuite d’afficher cette chaîne à l’écran, et c’est tout !

Si vous voulez que l’utilisateur sera en mesure de supprimer quelques lettres, il suffit d’ajouter du code pour la fonction on.backspaceKey, et c’est tout!

Dans l’exemple ci-dessous, nous avons fixé une limite de caractères à 25, mais vous pouvez ajuster cette valeur vous-même.

Bref, voilà le code dans sa totalité :

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

L’Essentiel pour bien comprendre

Cette partie va tenter de vous expliquer comment le Lua fonctionne concrètement dans l’OS et vous aider à comprendre ce que vous faites lorsque vous écrivez un script pour TI-Nspire. Toute connaissances préalable en Lua ou d’un quelconque langage évènementiel n’est pas inutile, mais pas nécessaire.

Le Lua est un langage script interprété, ce qui signifie que ce n’est pas aussi rapide que l’ASM/C mais que cela reste bien mieux que le TI-BASIC. Une bonne chose à noter, c’est que ce langage est en parfaite harmonie avec l’OS car lié par une structure évènementielle et un assez confortable environnement graphique (GC). Tout d’abord, nous devons comprendre comment tout cela fonctionne.

Le Lua est normalement un langage script séquentiel. Par exemple, quand on demande d’afficher une valeur avec la commande print(), on peut très facilement deviner dans le code quand est-ce que la commande sera lancée. Voici un code d’exemple :

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

En sortie :

1
2

Rien de très surprenant. Seulement, sur TI-Nspire, le Lua a une toute autre approche. On rencontre cette approche lorsqu’on programme avec des langages de haut niveau ou objet (tel que le C++/Qt4, C#, etc …). Dans ces langages, nous ne sommes pas maîtres de l’exécution de tel ou tel code. Oui, c’est étrange d’entendre dire ça, et pourtant c’est la vérité. Nous allons programmer dans un langage qui ne nous obéit pas ? En quelque sorte, oui.

Pas d’inquiétude ! Nous sommes là pour vous aider à ce passage difficile si c’est la première fois que vous le franchissez !

Tout d’abord, il faut vous placer de l’autre côté du miroir. Avant, vous étiez le patron, c’est à dire que vous ordonniez à la machine de vous calculer 1 + 1 et elle vous renvoyait fièrement 2. Maintenant, vous êtes un ouvrier. On vous donne l’autorisation de faire une tâche, vous dites comment faire à la machine. Concrètement, les « autorisations » sont des évènements. Vous avez le droit de faire ce que vous voulez, à moins que vous en ayez reçu l’autorisation. Voici un pseudo code qui montre que faire lorsque l’évènement « FaireLaCuisine » est appelé :

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

Vous comprenez bien que vous n’allez pas faire la cuisine une fois qu’on vous a embauché. Vous allez attendre que votre maître de maison vous en donne l’autorisation/l’ordre pour la faire ! Et bien c’est la même chose en Lua sur TI-Nspire. Tout est question d’évènements ! Dans notre exemple – en imaginant que FaireLaCuisine est un évènement de l’API – l’API appelle la fonction FaireLaCuisine si elle existe lorsque les conditions nécessaires sont réunies.

Donc, si on résume, tout code de notre part doit être mis dans des fonctions que la TI-Nspire va lancer. Mais comment savoir lesquelles ? Et bien c’est là que nous vous invitons à jeter un œil à cette liste des évènements. Lorsqu’un évènement est appelé, il lui est passé zéro ou plusieurs arguments que l’on peut réutiliser dans le corps de la fonction. Cela permet par exemple de savoir quelle touche est actuellement active, car lorsque l’évènement charIn() est appelé, on lui passe en plus une chaîne de caractère correspondant à cette touche. Par contre, lorsque l’évènement enterKey() est appelé, aucun argument n’est nécessaire, donc aucun n’est passé.

Avant, dans un langage quelconque n’étant pas évènementiel (BASIC, C, PHP, etc …), on programmait de cette manière :

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

Maintenant, cela ressemblera à ça :

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

>> Partie 2

Classes d’objets

Retour à la partie 4

Dans cette partie nous allons étudier une notion assez pratique du Lua, celle de manipuler non plus des variables, mais des objets.

Si vous avez déjà étudié le Lua auparavant, vous devriez savoir que c’est un langage utilisant excessivement les tables indexées par clés. Cela veut dire que chaque élément d’un tableau (ou liste) est associé à une clé qui permet de la référencer. Par exemple la table suivante est indexée par des chaines de caractères :

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

En Lua, les clés comme les valeurs sont polymorphes, c’est à dire qu’on peut aussi bien stocker des chaines de caractères avec des tables ou encore des fonctions ou tout autre type de valeur :

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

Affichera :

1                 exemple
2                 de
function: 10f6840 table
false             polymorphe

Même l’environnement global ( _G ) est une table !

C’est donc dans cette optique là que l’on peut manipuler le Lua sous forme de langage objet, avec chaque objet une table et chacun des éléments de cet objet une méthode ou une propriété. Par la suite, si l’on veut créer un nouvel objet, il suffira de copier la table « modèle » (concept des meta-table). Pour créer des classes d’objets hérités on procédera de la même manière, sauf qu’on modifiera certaines méthodes.

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
}

Et accéder très facilement à chacun des éléments :

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

Il faut comprendre cependant qu’il n’y a pas de notion d’espaces publiques et privés comme en C++/C#/Java, car c’est simplement une représentation, pas une réelle implémentation.

De cette manière vous devez enfin comprendre pourquoi on écrit platform.window:width() et non une variable ressemblant à « GetWindowWidth() ». En effet, width() est une méthode de l’objet window qui est une propriété de l’objet platform. Pareil pour platform.gc() que l’on retrouve abrégé en gc. En réalité, gc est un objet et platform.gc() en est son constructeur. D’un côté, c’est donc la même chose (ils ont tous les deux les mêmes méthodes) mais de l’autre non (ils ont des propriétés quelque peu différentes).

Nous allons donc, pour illustrer ce principe, créer ensemble un script Lua qui nous permettra de créer une et une seule fois une classe Forme ayant plusieurs méthodes dont un constructeur permettant de créer plusieurs formes.

Dans l’API Lua de la TI-Nspire, on trouve une méthode « class() » qui ne provient pas du Lua. En réalité, cette méthode économise certaines lignes qui peuvent paraître incompréhensibles.

Ces lignes sont :

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)

Source : http://lua-users.org/wiki/SimpleLuaClasses

Maintenant, la même chose en utilisant 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)

Avouez que cela est plus clair ?
Il est à noter également que init() est un constructeur. Le nom de ce constructeur est contraint par la méthode class() qui appelle ce dernier pour compléter la construction avec setmetatable().

Nous pouvons donc créer très facilement notre classe Forme :

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

Vous aurez surement remarqué l’utilisation de variables globales, xmin, ymin xmax et ymax.

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

Nous pouvons enfin jouer avec notre classe Forme ! Pour cela nous allons créer deux images et les convertir en TI.Image en utilisant l’outil préliminaire de développement :

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)

Dorénavant il ne nous reste plus qu’à créer la structure principale en utilisant divers évènements afin de tester toutes les possibilités de la classe que nous venons de créer.

-- 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

Et voilà le résultat !