Archives pour la catégorie Tutorials

Leçon 3 : Varier le contenu d’une table et opérateur conditionnel

Dans cette leçon, nous allons continuer notre travail sur les tables et sur les variable issues de TI-Nspire. Nous allons facilement customiser notre document, et avec un changement d’une ligne, nous allons créer une page qui peut être utilse à ceux qui veulent tirer avantage du Lua dans leur programmes TI-Nspire.
Mais avant, nous allons quitter Oclua pour TI-Nspire Scripting Tools, ou pour Luna. Tous deux sont légers et faciles à utilser, mais différents de Oclua. Pour cela, je vous envoie vers ce lienqui présente les façons d’exécuter un script écrit en Lua.

 

Leçon 3.1 : Modifions le contenu de nos lignes d’affichage

Un simple changement dans notre script transforme notre affichage de plusieurs lignes en un outils plus…utile.
Souvenez-vous : la ligne du script qui créait chacune des lignes d’affichage était celle-ci :

table[k] = "Ligne#"..k
Elle concaténait la valeur actuelle de k avec le string « Ligne# », ce qui faisait de la première « Ligne#1 ».
Souvenez-vous : la commande var.recallva chercher la valeur d’une variable TI-Nspire et l’attribue à une variable du script lua. Dans notre exemple, nous l’avions utilisé pour saisir le nombre de lignes que l’on voulait afficher.Maintenant, supposons que nous ayons une variable appelée ligne1, contenant ce que l’on veut afficher en première ligne. Puis une autre appelée ligne2 et ainsi de suite ? On pourait facilement contrôler le contenu de notre affichage, et les modifier n’importe quand depuis la Ti-Nspire. Pour cela, on pourrait simplement changer la ligne ci-dessus par :
table[k] = (var.recall("line"..k) or "Line #"..k)

Etudiez ces nouvelles lignes et essayez de comprendre ce qui se passe. Lorsque k = 1, var.recall recherche une variable appelée ligne1 et la range dans table[1] (La première entrée de la table, « table »). Si on ne la trouve pas, alors elle est stockée en tant que « ligne #1 ». (On aurait très bien pu laisser un blanc, avec juste «  ».) Et ainsi de suite pour les différentes valeurs de k.

Donc, en stockant ces variables dans un document TI-Nspire, elles sont immédiatemment affichées dans la page du script Lua.

Leçon 3.2 : Les conditions

Pour notre introduction au lua, nous avons besoin d’autres ingrédients : les structures conditionnelles. Ces dernières se ressemblent fortement en Lua et en TI-Nspire.
if condition then
  instructions
else
  instructions
end

Seul le endif se transforme en end !

Prenons un exemple : on veut que la première et la dernière ligne soient de la même couleur. Reprenons notre script.

for k = 1, linecount do
  gc:setFont("sansserif", "r", 10)
  gc:setColorRGB(158, 5, 7)
  table[k] = (var.recall("line"..k) or "Line #"..k)
  strwidth = gc:getStringWidth(table[k])
  strheight = gc:getStringHeight(table[k])
  gc:drawString(table[k], w/2 - strwidth/2 ,h*k/(linecount+1) + strheight/2)
end
Maintenant, il suffit d’ajouter une condition dans le bloc For. Si la ligne actuelle est la première / dernière, alors elle sera colorée.
if k == 1 or k == linecount then
  gc:setFont("sansserif", "b", 10)
  gc:setColorRGB(20, 20, 137)
else
 gc:setFont("sansserif", "r", 10)
 gc:setColorRGB(158, 5, 7)
 
end
Il faut bien entendu mettre le tout dans la fonction on.paint(gc). Aussi, notez l’emploi du double signe égal que l’on utilise pour tester une égalité. Un simple signe égal est utilisé pour une définition. (k = 1)
A part cela, vous êtes largement en mesure de comprendre ce morceau de script. Sinon, retournez voir les leçons précédentes !Et de 3 ! Vous venez de finir la troisième leçon de ce tutoriel. Maintenant vous pouvez afficher du texte (un peu plus intéressant) et utiliser les conditions ! Prenez un peu de temps avant de passer à la suite ! ;)Dans le prochain cours, nous apprendrons à écrire du texte directement dans notre fenêtre Lua, ce qui est la base pour les jeux de type quizz !

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 réaliser un Gestionnaire d’Ecrans

Lorsque l’on crée un jeu ou un outil quelconque de manière « complète », on désire souvent réaliser un panel d’écrans dans lequel l’utilisateur naviguera. Il existe plusieurs manières de réaliser un moteur qui nous permettra de gérer tous ces écrans, du plus simple à coder mais le moins pratique, au plus difficile à comprendre mais facile d’utilisation. L’objectif de ce tutoriel n’est bien évidemment pas de vous donner une idée fixe de ce à quoi ressemble un gestionnaire d’écran (il en existe une multitude de types), mais de vous présenter un panel de choix. Ce tutoriel est long car exhaustif en exemples et théorie, ne lâchez pas le fil!

Tout d’abord, ce que l’on entend par « gestionnaire d’écrans » c’est une manière d’organiser le code afin que l’on puisse isoler les parties de dessin des parties fonctionnelles. Plus cette frontière est marquée, plus le code est propre et lisible, et permettra donc d’intégrer plus facilement notre fameux « gestionnaire d’écrans ».

Prenons un exemple, nous souhaitons réaliser un jeu simple avec un menu, le jeu, une page d’aide et une page de records. A première vue, le nombre d’écrans est limité et fixé avant la création par notre cahier des charges. Ce même nombre est faible (4) et il est donc facile d’envisager quelque chose en ce sens :

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 ! inutile d’aller plus loin ! On a compris. Pour créer un code moche, long à écrire et j’en passe, c’est la meilleure solution. Il est avant tout destiné aux applications de très petite taille car facilement implémentable.

Il y a cependant moyen de faire moins moche, en utilisant des tableaux de fonctions.

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

Ici, il est évident que la lecture du code sera bien plus simple qu’auparavant. Par ailleurs, l’utilisation d’étiquettes au lieu de valeurs propres améliore considérablement la lecture du code ! N’hésitez pas à en abuser ! Vous pouvez, si vous avez trop de « constantes », utiliser un tableau de constantes afin de les organiser, comme ceci :

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

Ou encore pour faire quelque chose d’automatisé (plus besoin de compter/décaler les chiffres) :

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

Ceci étant, la gestion des différents écrans reste archaïque pour des usages autres que les jeux. Il est bien entendu possible de créer un système plus élaboré qui facilite l’utilisation et le coding. Quoi demander de plus ?

Vous l’aurez deviné, il faut utiliser … les classes !

Ce que l’on va s’apprêter à coder est en fait une surcouche de l’API TI-Nspire. Vous connaissez les évènements on.paint(), on.arrowKey() etc … et bien nous allons by-passer leur utilisation par notre gestionnaire d’écrans. L’intérêt de coder une classe mère ici est que nous sommes sûr qu’un écran a telles ou telles méthodes; méthodes que l’on appellera depuis les évènements standards sans risquer d’en oublier lorsque l’on code un nouvel écran.

Tout d’abord, il faut créer notre classe Screen :

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

Pour commencer, un écran n’a pas besoin d’initialisation spéciale. Rappelons le encore une fois, ce que nous définissons est une surcouche de l’API TI-Nspire, nous allons donc écrire (sans en définir le contenu !!) exhaustivement la liste des fonctions évènementielles qui sont disponibles :

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

Ces fonctions sont qualifiées de « virtuelles » qui devrons être redéfinies . Parce que la classe ne comprend ici que des fonctions virtuelles, elle est également qualifiée de virtuelle (un peu de vocabulaire ne fait pas de mal). Le gestionnaire d’écrans passe par la création d’une classe virtuelle, un modèle, un tampon qui nous garanti l’existence des fonctions lorsque l’on va coder la liaison évènementielle :

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

Ainsi, lorsque l’on veut rajouter le code pour un écran, on code comme si on ne codait qu’un écran ! Il suffit de penser que « Menu: » est « 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()

Cette technique est très utilisée mais elle a cependant un point faible : à chaque changement d’écran on recréer un objet d’écran. Dans certains ça peut être très utile (réinitialisation du jeu), dans d’autres c’est inutile (le menu ne bouge pas). On peut toujours, stocker ça dans des variables, ou bien passer uniquement la classe elle même en paramètre. Mais il y a encore mieux.

En effet, il est souvent d’usage de coupler cette technique avec un pile. Une pile est liste spéciale : une structure de donnée surnommée « first in, first out » (ou encore FIFO), littéralement, « premier rentré, premier sorti ». L’utilisation d’une pile ici permet de stocker les écrans initialisés dans une liste ordonnée, tout en conservant le fonctionnement précédent.

Nous allons donc rajouter la gestion de la pile. Pour cela il faut changer les accès à activeScreen par activeScreen(), remplacer les assignations de activeScreen par une fonction qui va rajouter l’écran à la pile (PushScreen()) et ne pas oublier de dépiler les écrans une fois qu’on ne s’en sert plus.

Voici ce que cela donne :

------ 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 : le fait d’être exhaustif à ce point n’est pas obligé. Il faut par contre que le nombre de fonctions dans la classe Screen corresponde au nombre d’évènements définis.

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

Enregistre un record (ou autre) sans possibilité de triche !

Dans ce tutoriel nous allons voir une notion assez intéressante du framework de la TI-Nspire en Lua : enregistrer et restaurer des données dans un format quelconque directement en Lua ce qui permet par exemple de se souvenir d’un record pour un jeu, ou une quelconque configuration pour un programme plus évolué.

Mise en situation : Nous venons de réaliser un jeu génial. Nous voulons que nos camarades puissent se mesurer à notre record, mais comment faire ?

La première méthode consiste à utiliser l’API var et d’appeler var.store(). Ainsi, on sauvegarde le record dans le classeur comme variable globale.

Votre système de record est en place ! Hum, pas si vite : un de vos camarades a réussi à faire un score de 10 000 000 ! Comment est-ce possible ?? Il a simplement édité la variable qui vous sert de record. Etant donné qu’elle est accessible via une application Calcul et modifiable à souhait cela n’est pas compliqué !

La deuxième méthode utilise la première, à ceci près que nous disposons de math.eval() qui nous permet d’exécuter n’importe quelle commande du TI-Basic, comme la commande Lock par exemple. Ainsi, si nous faisons :

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

Notre variable est protégée contre l’écriture. Seulement, étant donné que Lock est une commande présente dans le TI-Basic, elle peut très bien être exécutée à l’extérieur du programme Lua. Donc cela ne fait que repousser le problème.

Et si nous nous tournions vers le Lua en lui même ? En effet, il existe var.monitor() qui permet de poser une sentinelle qui vérifie si la variable pointée va être changée. Si oui, alors l’évènement on.varChange() est appelé. L’intérêt d’utiliser cette technique est qu’elle permet de contrôler la modification de la variable. En effet :

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

Ce code empêchera toute modification extérieure de record par un message d’erreur :

Changement non autorisé : Entrée non valide.

Seulement voilà … C’est une méthode lourde et répétitive si on doit le faire pour plusieurs données, telle des données de configuration … C’est là que l’on regarde la documentation et qu’on aperçoit deux évènements qui pourraient paraître banals à première vue : on.save() et on.restore(). En réalité, ce couple d’évènement gère ce que nous essayons de faire depuis le début !

En effet, lorsque le widget est fermé (on ferme le classeur, on copie/coupe le widget), l’évènement on.save() est appelé. on.save() doit être écrit de telle sorte qu’il puisse renvoyer une donnée quelconque (booléen, nombre, chaîne de caractères, table etc …).
Lorsque le widget sera ouvert la prochaine fois (on ouvre le classeur, on colle le widget), l’évènement on.restore() sera appelé avec en paramètre cette donnée que nous avions renvoyé depuis on.save() !

Ainsi, plus de prise de tête :

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

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

Couplage des événements

Retour à la partie 2

Cette partie va montrer comment sont déroulés les appels des évènements, ce qu’on appelle plus couramment des « couplages » de l’anglais link.

En fait, le script utilisateur est lancé avant tout le code maître (celui qui contrôle votre programme). Cela permet entre autre d’initialiser des valeurs. Seulement on ne peut ni évaluer d’expressions ni utiliser l’API fournie par la TI-Nspire (comme platform, var …) avant que le code maître soit lancé. Voici un résumé des appels :

  • Lancement de la bibliothèque string
  • Lancement de la bibliothèque math
  • Lancement du framework de la TI-Nspire (toolpalette, image, … )
  • Ouverture et lancement du script Lua utilisateur
  • Lancement de l’API var (store, recall, …)
  • Lancement de l’API platform (window, gc, …)
  • Liaison des évènements (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 : Tous les commentaires préfixés par (N) sont là uniquement pour permettre de comprendre le sens de la routine. Ces fonctions n’existent pas vraiment.

Maintenant, nous pouvons comprendre comment tout cela est lié, uniquement par une boucle principale. Ceci peut vous aider à comprendre que ça ne sert à rien de faire une boucle vous-même, car de toute manière l’écran ne sera pas actualisé. Cela peut également vous permettre de voir quand est-ce que l’écran est dessiné. Dit plus clairement, nous ne pouvons pas utiliser ni gc ni platform.gc(), pour dessiner quelque chose à l’écran, si l’on se trouve en dehors de on.paint() (fonctions auxiliaires appelées depuis on.paint() exceptées bien sûr).

Voici un exemple simple de programme Lua qui affiche un message seulement lorsqu’une touche est active (et laisse donc l’écran blanc lorsqu’aucune touche n’est active) :

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

Lorsque l’on ouvre le document, le script est lu une première fois. Il initialise et écrase toutes les fonctions et variables globales avec celles que vous avez défini. Donc message vaut nil (valeur nulle). Lorsque l’évènement on.paint() est appelé, message vaut nil, donc rien ne se passe. Seulement, lorsque l’on appuie sur une touche, on.charIn() est appelé, message vaut maintenant « Hello world » et nous indiquons à platform que l’écran doit être rafraîchi. Donc au tour de boucle suivant, on.paint() est appelé encore une fois. message n’est pas nul, donc nous l’affichons tout en l’effaçant puis nous lançons un timer. Pourquoi ? Parce que si nous appelons platform.window:invalidate() juste ici, l’écran ne sera pas actualisé. Pourquoi (encore une fois) ? Regardez le pseudo-code juste de tout à l’heure. A la fin de la boucle l’OS considère l’écran comme actualisé. Donc l’utilisation d’un timer est nécessaire pour appeler manuellement on.paint() tout en étant sorti de ce dernier. Lorsque le timer a fini le décompte, on.timer() est appelé et nous forçons donc l’écran à s’actualiser. L’écran est donc actualisé mais il n’y a rien à dessiner car message est nul. Ainsi l’environnement graphique laisse l’écran blanc.

>> Partie 4

Utilisation d’une variable existante dans un classeur TI-Nspire

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

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 !