Dashboard HTML CSS JavaScript Lua 5.4 Lua 5.5 Éditeur Documentation
</> Cours Lua 5.4

Cours Lua 5.4

Maîtrisez le langage Lua 5.4 : variables, tables, métatables, coroutines et programmation orientée objet.

0/12 leçons terminées

1. Introduction à Lua

Lua est un langage de programmation léger, rapide et extensible, conçu comme langage de script embarquable. Il est utilisé dans de nombreux domaines : jeux vidéo, systèmes embarqués, configuration de serveurs et bien plus.

Un peu d'histoire

Lua a été créé en 1993 à la Pontifícia Universidade Católica do Rio de Janeiro (PUC-Rio) au Brésil par Roberto Ierusalimschy, Waldemar Celes et Luiz Henrique de Figueiredo. Le nom "Lua" signifie "Lune" en portugais. Lua est distribué sous licence MIT et reste l'un des langages de scripting les plus performants au monde.

Domaines d'utilisation

  • Développement de jeux vidéo : LÖVE (framework 2D), Roblox, World of Warcraft, Garry's Mod.
  • Embarqué : Intégration dans des applications C/C++ grâce à sa bibliothèque standard mince.
  • Serveurs web : Configuration de nginx via le module lua-nginx-module (OpenResty).
  • Outillage : Wireshark, nmap, PostgreSQL utilisent Lua pour leurs extensions.

Le REPL (Read-Eval-Print Loop)

L'interpréteur Lua fournit un REPL interactif pour tester du code en direct. Tapez lua dans votre terminal pour le lancer.

-- Lancer le REPL en tapant 'lua' dans le terminal
-- puis tapez du code Lua directement :

> 2 + 2
4
> "Bonjour" .. " " .. "Lua"
Bonjour Lua
> print("Hello World!")
Hello World!
> os.date("%Y-%m-%d")
"2026-07-22"

Votre premier script Lua

Créez un fichier hello.lua et exécutez-le avec lua hello.lua.

-- hello.lua : Mon premier script Lua
print("Bonjour le monde !")
print("Bienvenue dans Lua 5.4")

Règles de syntaxe de base

Lua est un langage sensible à la casse (les majuscules et minuscules comptent). Nom, NOM et nom sont trois variables différentes.

-- Commentaire sur une seule ligne avec --

--[[ Commentaire multi-lignes
   Ceci est un commentaire
   sur plusieurs lignes
--]]

-- La fin d'une instruction est marquée par un retour à la ligne
-- ou un point-virgule (optionnel)
print("A") ; print("B")  -- point-virgule pour séparer sur une ligne

Récapitulatif

  • Lua est un langage léger, rapide et extensible créé en 1993 à la PUC-Rio.
  • Très utilisé dans le jeu vidéo (LÖVE, Roblox), l'embarqué et la configuration.
  • Le REPL permet de tester du code en interactif avec lua.
  • Les commentaires utilisent -- (une ligne) et --[[ ]] (multi-lignes).
  • Lua est sensible à la casse.

2. Variables et types de données

Comprendre les variables et les types est fondamental. Lua possède un système de types dynamique avec huit types natifs.

Variables globales et locales

En Lua, toute variable créée sans mot-clé est globale. Utilisez toujours local pour déclarer vos variables — c'est une meilleure pratique tant en termes de performance que de portée.

-- Variable globale (à éviter)
nom = "Alice"

-- Variable locale (recommandé)
local age = 25
local ville = "Paris"

-- Les variables non déclarées valent nil par défaut
print(inexistant)  --> nil

Les types de données

Lua possède huit types de valeurs : nil, boolean, number, string, function, userdata, thread et table.

-- nil : absence de valeur
local x = nil

-- boolean : true ou false
local estVrai = true
local estFaux = false

-- number : entiers et flottants (Lua 5.4 distingue les deux)
local entier = 42       -- type : integer
local flottant = 3.14   -- type : float
local grand = 1e6      -- notation scientifique = float

-- string : chaînes de caractères
local prenom = "Marie"
local message = 'Aussi entre guillemets simples'

La fonction type()

La fonction type() retourne le type d'une valeur sous forme de chaîne.

print(type(nil))         --> "nil"
print(type(true))        --> "boolean"
print(type(42))          --> "integer"  (Lua 5.4)
print(type(3.14))        --> "float"    (Lua 5.4)
print(type("salut"))     --> "string"
print(type(print))       --> "function"
print(type({}))          --> "table"

Assignation multiple

Lua permet d'assigner plusieurs variables en une seule ligne.

-- Assignation multiple
local a, b, c = 1, 2, 3
print(a, b, c)  --> 1  2  3

-- Échange de valeurs sans variable temporaire
a, b = b, a
print(a, b)  --> 2  1

-- Trop de valeurs = ignorées, pas assez = nil
local x, y = 10          -- x=10, y=nil
local p, q, r = 1, 2     -- p=1, q=2, r=nil

Conversion de types

Les fonctions tostring() et tonumber() permettent de convertir entre types.

-- tostring : convertir en chaîne
local nb = 42
local txt = tostring(nb)
print(txt .. " est un texte")  --> "42 est un texte"
print(tostring(true))          --> "true"
print(tostring(nil))           --> "nil"

-- tonumber : convertir en nombre
print(tonumber("123"))        --> 123
print(tonumber("3.14"))       --> 3.14
print(tonumber("abc"))        --> nil (conversion échoue)
print(tonumber(true))         --> nil

Entiers et flottants en Lua 5.4

Lua 5.4 introduit une distinction stricte entre entiers (integer) et flottants (float). C'est un changement majeur par rapport à Lua 5.3 et avant.

local i = 5       -- integer
local f = 5.0     -- float
print(type(i))    --> "integer"
print(type(f))    --> "float"
print(i == f)     --> true (comparaison de valeur)

-- Division entière avec //
print(7 // 2)    --> 3 (integer)
print(7 / 2)     --> 3.5 (float)

Récapitulatif

  • Toujours utiliser local pour déclarer vos variables.
  • Les types principaux : nil, boolean, number (integer/float), string, table, function.
  • type() retourne le type d'une valeur.
  • Lua 5.4 distingue integer et float.
  • tostring() et tonumber() pour les conversions.

3. Les opérateurs

Lua fournit un ensemble complet d'opérateurs pour les calculs, les comparaisons, la logique et la manipulation de chaînes.

Opérateurs arithmétiques

local a, b = 10, 3

print(a + b)    -- Addition       : 13
print(a - b)    -- Soustraction   : 7
print(a * b)    -- Multiplication : 30
print(a / b)    -- Division       : 3.3333333333333
print(a // b)   -- Division entière : 3
print(a % b)    -- Modulo (reste)  : 1
print(a ^ 2)   -- Puissance       : 100
print(-a)       -- Négation       : -10

Opérateurs de comparaison

print(5 == 5)    -- Égal           : true
print(5 ~= 3)    -- Différent      : true (pas de != en Lua)
print(5 < 3)     -- Inférieur      : false
print(5 > 3)     -- Supérieur      : true
print(5 <= 5)   -- Inférieur ou =  : true
print(5 >= 6)   -- Supérieur ou =  : false

Opérateurs logiques

En Lua, and, or et not ne retournent pas forcément true ou false — ils retournent les opérandes elles-mêmes.

-- not : inverse la valeur logique
print(not true)     --> false
print(not nil)      --> true

-- and : retourne le premier opérande faux, ou le dernier si tout est vrai
print(true and "oui")     --> "oui"
print(nil and "non")      --> nil
print(false and "non")    --> false

-- or : retourne le premier opérande vrai, ou le dernier si tout est faux
print(nil or "défaut")     --> "défaut"
print("oui" or "non")     --> "oui"

L'opérateur de concaténation (..)

local prenom = "Jean"
local nom = "Dupont"
local nomComplet = prenom .. " " .. nom
print(nomComplet)  --> "Jean Dupont"

-- Concaténation avec des nombres (conversion automatique)
print("J'ai " .. 5 .. " pommes")  --> "J'ai 5 pommes"

L'opérateur de longueur (#)

-- Longueur d'une chaîne
print(#"Bonjour")    --> 7
print(#"")          --> 0

-- Longueur d'un tableau
local fruits = {"pomme", "banane", "cerise"}
print(#fruits)       --> 3

Précédence des opérateurs

De la précédence la plus élevée (en haut) à la plus faible (en bas) :

  • ^ (puissance, associatif à droite)
  • not, #, - (unaire)
  • *, /, //, %
  • +, - (binaire)
  • .. (concaténation, associatif à droite)
  • <, <=, >, >=, ~=, ==
  • and
  • or

4. Les conditions (if/elseif/else)

Les structures conditionnelles permettent d'exécuter différentes parties du code selon des conditions.

La structure if/then/end

local age = 18

if age >= 18 then
    print("Vous êtes majeur.")
end

if/elseif/else

local note = 14

if note >= 16 then
    print("Très bien")
elseif note >= 14 then
    print("Bien")
elseif note >= 12 then
    print("Assez bien")
elseif note >= 10 then
    print("Passable")
else
    print("Insuffisant")
end
-- Affiche : "Bien"

Conditions imbriquées

local connecte = true
local admin = false

if connecte then
    if admin then
        print("Panneau d'administration")
    else
        print("Espace membre")
    end
else
    print("Veuillez vous connecter.")
end

Les valeurs truthy et falsy

En Lua, seuls false et nil sont considérés comme faux. Tout le reste est vrai, y compris 0, "" (chaîne vide) et {} (table vide).

if 0 then print("0 est truthy en Lua !") end
if "" then print("La chaîne vide est truthy !") end
if {} then print("Une table vide est truthy !") end
if nil then print("Jamais affiché") else print("nil est falsy") end

Le pattern ternaire avec and/or

Lua n'a pas d'opérateur ternaire ? :, mais on peut simuler le même comportement avec and et or.

local age = 20
local statut = (age >= 18) and "majeur" or "mineur"
print(statut)  --> "majeur"

-- Fonctionne comme : condition ? valeurSiVrai : valeurSiFaux
local x = 5
local resultat = x > 0 and "positif" or "négatif"
print(resultat)  --> "positif"

Récapitulatif

  • La structure est if condition then ... elseif ... else ... end.
  • En Lua, seuls false et nil sont falsy.
  • 0, "" et {} sont truthy.
  • On peut simuler un ternaire avec condition and valVrai or valFaux.

5. Les boucles (for, while, repeat)

Les boucles permettent de répéter des instructions. Lua propose plusieurs types de boucles adaptés à différents besoins.

La boucle for numérique

La boucle for numérique itère de start à stop avec un step optionnel (par défaut : 1).

-- Boucle de 1 à 5 (pas de 1 par défaut)
for i = 1, 5 do
    print(i)
end
-- Affiche : 1, 2, 3, 4, 5

-- Boucle avec un pas personnalisé
for i = 0, 10, 2 do
    print(i)
end
-- Affiche : 0, 2, 4, 6, 8, 10

-- Boucle descendante
for i = 5, 1, -1 do
    print(i)
end
-- Affiche : 5, 4, 3, 2, 1

La boucle for générique (pairs/ipairs)

La boucle for générique parcourt un itérateur. Les plus courants sont pairs() et ipairs().

-- ipairs : itère sur un tableau ordonné (de l'index 1)
local fruits = {"pomme", "banane", "cerise"}
for index, valeur in ipairs(fruits) do
    print(index, valeur)
end
-- 1 pomme, 2 banane, 3 cerise

-- pairs : itère sur toutes les clés d'une table (hash + array)
local personne = {nom = "Lucie", age = 22, ville = "Lyon"}
for cle, valeur in pairs(personne) do
    print(cle .. " = " .. tostring(valeur))
end

La boucle while

local compteur = 1

while compteur <= 5 do
    print("Tour n°" .. compteur)
    compteur = compteur + 1
end

La boucle repeat/until

La boucle repeat exécute le code au moins une fois, puis vérifie la condition avec until.

local n = 1
repeat
    print(n)
    n = n * 2
until n > 100
-- Affiche : 1, 2, 4, 8, 16, 32, 64

break et goto

break sort d'une boucle. goto avec des labels (introduit en Lua 5.2) permet de sauter dans le code.

-- break : sortir de la boucle
for i = 1, 100 do
    if i == 5 then
        print("On arrête à 5 !")
        break
    end
    print(i)
end

-- goto et labels (Lua 5.2+)
for i = 1, 10 do
    if i == 3 then
        goto fin
    end
    print(i)
end
::fin::
print("Terminé")

Récapitulatif

  • for i = start, stop, step do : boucle numérique.
  • for k, v in pairs(table) do : itère sur toutes les paires clé/valeur.
  • for i, v in ipairs(table) do : itère sur un tableau ordonné.
  • while condition do : boucle tant que la condition est vraie.
  • repeat ... until condition : boucle au moins une fois.
  • break sort de la boucle, goto saute à un label.

6. Les fonctions

Les fonctions sont des valeurs de première classe en Lua. Elles peuvent être assignées à des variables, passées en argument et retournées par d'autres fonctions.

Syntaxe de base

-- Déclaration d'une fonction
local function direBonjour(nom)
    print("Bonjour, " .. nom .. " !")
end

direBonjour("Alice")   --> "Bonjour, Alice !"

-- Équivalent avec assignation de variable
local direBonjour2 = function(nom)
    print("Bonjour, " .. nom .. " !")
end

Valeurs de retour multiples

Une fonction peut retourner plusieurs valeurs en même temps.

local function diviser(a, b)
    return a // b, a % b  -- quotient et reste
end

local quotient, reste = diviser(17, 5)
print(quotient, reste)  --> 3  2

-- Si on ne récupère qu'une valeur, les autres sont ignorées
local q = diviser(17, 5)
print(q)  --> 3

Les arguments variables (vararg)

Le syntaxe ... permet de créer des fonctions acceptant un nombre variable d'arguments.

local function somme(...)
    local args = {...}
    local total = 0
    for _, v in ipairs(args) do
        total = total + v
    end
    return total
end

print(somme(1, 2, 3, 4, 5))  --> 15

Les closures (fermetures)

Une closure est une fonction qui capture les variables de son environnement lexical.

local function créerCompteur()
    local n = 0
    return function()
        n = n + 1
        return n
    end
end

local compteur = créerCompteur()
print(compteur())  --> 1
print(compteur())  --> 2
print(compteur())  --> 3

Les fonctions comme valeurs

-- Les fonctions peuvent être stockées dans des tableaux
local operations = {
    addition = function(a, b) return a + b end,
    multiplication = function(a, b) return a * b end,
}

print(operations.addition(3, 4))        --> 7
print(operations.multiplication(3, 4)) --> 12

-- Fonction passée en argument (callback)
local function appliquer(fn, x)
    return fn(x)
end

local doubler = function(x) return x * 2 end
print(appliquer(doubler, 5))  --> 10

Récapitulatif

  • Les fonctions se déclarent avec function ... end.
  • Elles peuvent retourner plusieurs valeurs avec return a, b.
  • ... capture les arguments variables.
  • Les closures capturent les variables de leur environnement.
  • Les fonctions sont des valeurs de première classe.

7. Les tables (arrays et dictionnaires)

La table est la seule structure de données en Lua. Elle remplace les tableaux, les dictionnaires, les ensembles, les objets et bien plus.

Tables de style array (indexées à partir de 1 !)

En Lua, les tableaux commencent à l'index 1, pas 0 !

local fruits = {"pomme", "banane", "cerise"}

print(fruits[1])   --> "pomme" (pas fruits[0] !)
print(fruits[2])   --> "banane"
print(#fruits)     --> 3 (longueur du tableau)

Manipulation des tableaux

local liste = {10, 20, 30}

-- table.insert : ajouter un élément
table.insert(liste, 40)           -- ajoute à la fin
table.insert(liste, 2, 15)      -- insère à l'index 2
print(table.concat(liste, ", "))  --> "10, 15, 20, 30, 40"

-- table.remove : supprimer un élément
table.remove(liste, 1)           -- supprime l'élément à l'index 1
print(table.concat(liste, ", "))  --> "15, 20, 30, 40"

Tables de style hash (dictionnaires)

local personne = {
    nom = "Alice",
    age = 30,
    ville = "Marseille",
    ["clé avec espaces"] = "valeur spéciale",
}

print(personne.nom)   --> "Alice"
print(personne["age"])  --> 30 (syntaxe alternative)
print(personne.ville)  --> "Marseille"

Tables mixtes

Une table peut mélanger style array et hash dans la même table.

local joueur = {
    "Épée",          -- [1] = "Épée"
    "Bouclier",      -- [2] = "Bouclier"
    nom = "Héros",   -- clé nommée
    pv = 100,        -- clé nommée
}

print(#joueur)        --> 2 (compte seulement les clés numériques)
print(joueur.nom)     --> "Héros"

Les fonctions utilitaires de table

-- table.sort : trier un tableau
local nombres = {5, 2, 8, 1, 9, 3}
table.sort(nombres)
print(table.concat(nombres, ", "))  --> "1, 2, 3, 5, 8, 9"

-- Tri personnalisé avec une fonction de comparaison
local mots = {"banane", "cerise", "abricot"}
table.sort(mots, function(a, b) return a > b end)
print(table.concat(mots, ", "))  --> "cerise, banane, abricot"

-- table.pack / table.unpack
local t = table.pack(1, 2, 3, 4, 5)
print(t.n)  --> 5
local a, b, c = table.unpack(t, 2, 4)
print(a, b, c)  --> 2  3  4

pairs() vs ipairs()

  • ipairs() parcourt les clés numériques consécutives à partir de 1. S'arrête au premier trou.
  • pairs() parcourt toutes les clés (numériques et nommées), dans un ordre non garanti.
local t = {10, 20, nom = "test", 30}

-- ipairs s'arrête au trou (manque l'index 4 puis le champ "nom")
for i, v in ipairs(t) do
    print(i, v)
end
-- 1 10, 2 20 (s'arrête car index 3 est "nom" pas un number)

-- pairs parcourt tout
for k, v in pairs(t) do
    print(k, v)
end
-- 1 10, 2 20, nom test, 4 30

Récapitulatif

  • La table est la seule structure de données de Lua.
  • Les tableaux commencent à l'index 1.
  • table.insert(), table.remove(), table.sort(), table.concat() sont les fonctions clés.
  • ipairs() pour les tableaux ordonnés, pairs() pour toutes les clés.

8. Les strings et conversion

Les chaînes de caractères en Lua sont immuables. La bibliothèque string offre de nombreuses fonctions pour les manipuler.

Création et concaténation

-- Différentes façons de créer une chaîne
local s1 = "Bonjour"
local s2 = 'Aussi avec des guillemets simples'
local s3 = [[Chaîne multi-ligne
qui peut contenir
plusieurs lignes
sans échapper les retours à la ligne]]

-- Concaténation avec ..
local nom = "Lua"
local version = 5.4
print(nom .. " " .. tostring(version))  --> "Lua 5.4"

La fonction # (longueur)

print(#"Hello")     --> 5
print(#"")          --> 0
print(#"café")      --> 4 (octets, pas caractères UTF-8)

La bibliothèque string

local s = "Bonjour le monde"

-- string.sub : extraire une sous-chaîne
print(string.sub(s, 1, 7))    --> "Bonjour"
print(string.sub(s, -6))      --> "monde" (depuis la fin)

-- string.find : chercher un motif
local debut, fin = string.find(s, "monde")
print(debut, fin)  --> 11  16

-- string.gsub : remplacement global
local nouveau = string.gsub(s, "monde", "Lua")
print(nouveau)  --> "Bonjour le Lua"

-- string.upper / string.lower
print(string.upper("hello"))  --> "HELLO"
print(string.lower("HELLO"))  --> "hello"

-- string.rep : répéter une chaîne
print(string.rep("ha", 3))  --> "hahaha"

-- string.reverse : inverser
print(string.reverse("abc"))  --> "cba"

string.format : formatage

string.format fonctionne comme printf en C.

local nom = "Alice"
local age = 25
local pi = 3.14159

print(string.format("Bonjour %s, vous avez %d ans.", nom, age))
--> "Bonjour Alice, vous avez 25 ans."

print(string.format("Pi = %.2f", pi))
--> "Pi = 3.14"

-- Codes de format courants :
-- %s : chaîne, %d : entier, %f : flottant
-- %x : hexadécimal, %o : octal, %% : pourcentage littéral
print(string.format("Hex : %x, Oct : %o", 255, 255))
--> "Hex : ff, Oct : 377"

Strings multi-lignes avec [[ ]]

local sql = [[
SELECT nom, age
FROM utilisateurs
WHERE ville = 'Paris'
ORDER BY nom
]]
print(sql)

-- Pour ignorer l'indentation, utilisez [= n [ ... ]= n ]
local texte = [=[
   Ceci est indenté
   mais le résultat ne le sera pas
]=]

Récapitulatif

  • Les chaînes sont immuables en Lua.
  • La concaténation se fait avec ...
  • # retourne la longueur en octets.
  • string.sub, string.find, string.gsub, string.format sont les fonctions principales.
  • [[ ]] permet les chaînes multi-lignes.

9. Les métatables et l'héritage

Les métatables sont l'un des concepts les plus puissants de Lua. Elles permettent de redéfinir le comportement des opérations sur les tables, et constituent la base de la programmation orientée objet en Lua.

setmetatable et getmetatable

local monTable = {a = 1, b = 2}
local mt = {}  -- la métatable

setmetatable(monTable, mt)
print(getmetatable(monTable))  --> table: 0x... (la table mt)

__index : intercepter les accès

L'event __index est déclenché quand on accède à une clé inexistante dans une table.

local personne = {nom = "Alice"}
local mt = {
    __index = function(table, cle)
        return "Clé '" .. cle .. "' non trouvée"
    end
}
setmetatable(personne, mt)

print(personne.nom)      --> "Alice" (clé existante)
print(personne.age)      --> "Clé 'age' non trouvée"
print(personne.ville)    --> "Clé 'ville' non trouvée"

-- __index peut aussi être une table (plus courant)
local defauts = {couleur = "bleu", taille = 10}
local obj = {nom = "Widget"}
setmetatable(obj, {__index = defauts})
print(obj.couleur)  --> "bleu" (trouvé via __index)

__newindex : intercepter les écritures

local donnees = {}
local mt = {
    __newindex = function(table, cle, valeur)
        print("Interdit de modifier '" .. cle .. "'")
    end
}
setmetatable(donnees, mt)

donnees.nom = "test"  --> "Interdit de modifier 'nom'"

Redéfinir les opérateurs arithmétiques

local Vecteur = {}
Vecteur.__index = Vecteur

function Vecteur.new(x, y)
    return setmetatable({x = x, y = y}, Vecteur)
end

function Vecteur:__add(autre)
    return Vecteur.new(self.x + autre.x, self.y + autre.y)
end

function Vecteur:__sub(autre)
    return Vecteur.new(self.x - autre.x, self.y - autre.y)
end

function Vecteur:__mul(scalaire)
    return Vecteur.new(self.x * scalaire, self.y * scalaire)
end

function Vecteur:__tostring()
    return string.format("(%d, %d)", self.x, self.y)
end

function Vecteur:__len()
    return math.sqrt(self.x^2 + self.y^2)
end

local v1 = Vecteur.new(3, 4)
local v2 = Vecteur.new(1, 2)

print(v1 + v2)    --> (4, 6)
print(v1 - v2)    --> (2, 2)
print(v1 * 2)    --> (6, 8)
print(#v1)        --> 5 (norme)

__call : rendre la table appelable

local Timer = {}
Timer.__index = Timer

function Timer.new(secondes)
    return setmetatable({duree = secondes, restant = secondes}, Timer)
end

function Timer:__call()
    if self.restant > 0 then
        self.restant = self.restant - 1
        return true
    else
        return false
    end
end

local t = Timer.new(3)
print(t())  --> true (2 restant)
print(t())  --> true (1 restant)
print(t())  --> true (0 restant)
print(t())  --> false (terminé)

Héritage et POO

local Animal = {}
Animal.__index = Animal

function Animal.new(nom, espece)
    return setmetatable({nom = nom, espece = espece}, Animal)
end

function Animal:parler()
    print(self.nom .. " fait un bruit.")
end

-- Héritage : Chien hérite d'Animal
local Chien = setmetatable({}, {__index = Animal})
Chien.__index = Chien

function Chien.new(nom)
    local self = Animal.new(nom, "Chien")
    return setmetatable(self, Chien)
end

function Chien:parler()
    print(self.nom .. " fait Wouf !")
end

local rex = Chien.new("Rex")
rex:parler()   --> "Rex fait Wouf !"
print(rex.espece)  --> "Chien" (hérité d'Animal)

Récapitulatif

  • Les métatables modifient le comportement des tables avec des "events".
  • __index intercepte les lectures de clés inexistantes.
  • __newindex intercepte les écritures.
  • __add, __sub, __mul, __tostring, __call, __len redéfinissent les opérateurs.
  • L'héritage s'implémente avec __index chaîné.

10. Les coroutines

Les coroutines sont des fonctions suspendables qui peuvent reprendre leur exécution à un point précis. Elles sont particulièrement utiles pour les générateurs, les patrons producteur-consommateur et les dialogues dans les jeux.

Création et exécution

-- Créer une coroutine
local co = coroutine.create(function()
    print("Début")
    coroutine.yield()  -- suspendre
    print("Reprise !")
end)

print(coroutine.status(co))  --> "suspended"
coroutine.resume(co)          --> Affiche "Début"
print(coroutine.status(co))  --> "suspended"
coroutine.resume(co)          --> Affiche "Reprise !"
print(coroutine.status(co))  --> "dead"

Transmission de données

coroutine.resume peut envoyer des données à la coroutine, et coroutine.yield peut en retourner.

local co = coroutine.create(function(prenom)
    print("Bonjour, " .. prenom)
    local age = coroutine.yield("Quel âge as-tu ?")  -- renvoie la question
    print("Tu as " .. age .. " ans.")
end)

local ok, msg = coroutine.resume(co, "Alice")
print(msg)  --> "Quel âge as-tu ?"

ok, msg = coroutine.resume(co, 25)
-- Affiche "Tu as 25 ans."

Le pattern producteur-consommateur

-- Producteur : génère des nombres
local function producteur(max)
    for i = 1, max do
        coroutine.yield(i * 10)
    end
end

-- Consommateur : traite les données
local co = coroutine.create(function()
    producteur(5)
end)

while coroutine.status(co) ~= "dead" do
    local ok, valeur = coroutine.resume(co)
    if ok and valeur then
        print("Reçu :", valeur)
    end
end
-- Affiche : 10, 20, 30, 40, 50

coroutine.wrap : alternative plus simple

coroutine.wrap retourne une fonction au lieu d'une coroutine. Les erreurs sont propagées directement.

local function compter(n)
    for i = 1, n do
        coroutine.yield(i)
    end
end

local gen = coroutine.wrap(compter)

print(gen(5))  --> 1
print(gen())    --> 2
print(gen())    --> 3
print(gen())    --> 4
print(gen())    --> 5

Coroutines asymétriques vs symétriques

  • Asymétriques (Lua) : On reprend toujours une coroutine avec resume depuis l'appelant. La coroutine suspend avec yield.
  • Symétriques : On peut transférer le contrôle directement d'une coroutine à une autre (Lua n'implémente pas ce modèle nativement).
-- Exemple : dialogue asynchrone pour un jeu
local function dialogueNPC()
    print("NPC : Salut aventurier !")
    coroutine.yield()  -- attend la réponse du joueur
    print("NPC : Tu veux une quête ?")
    coroutine.yield()  -- attend la réponse
    print("NPC : Va tuer 10 slimes !")
end

local npc = coroutine.create(dialogueNPC)
coroutine.resume(npc)  -- NPC parle
io.read()             -- joueur appuie sur Entrée
coroutine.resume(npc)  -- NPC continue
io.read()             -- joueur appuie sur Entrée
coroutine.resume(npc)  -- NPC donne la quête

Récapitulatif

  • coroutine.create(fn) crée une coroutine.
  • coroutine.resume(co, ...) démarre ou reprend une coroutine.
  • coroutine.yield(...) suspend l'exécution et retourne des valeurs.
  • coroutine.status(co) retourne "suspended", "running" ou "dead".
  • coroutine.wrap(fn) retourne une fonction appelable.

11. Modules et packages

Le système de modules de Lua permet d'organiser le code en fichiers réutilisables. C'est essentiel pour tout projet d'une certaine taille.

Le mot-clé require()

require() charge un module. Il cherche le fichier dans les chemins définis par package.path.

-- Charger le module "math" (déjà fourni par Lua)
local math = require("math")
print(math.sqrt(16))  --> 4

-- Charger un module personnalisé
local utils = require("utils")

Créer un module

Un module Lua est simplement un fichier qui retourne une table contenant les fonctions et valeurs à exposer.

-- fichier : utils.lua
local Utils = {}

function Utils.majuscule(texte)
    return string.upper(texte)
end

function Utils.chevaucher(t1, t2)
    for _, v in ipairs(t1) do
        for _, v2 in ipairs(t2) do
            if v == v2 then return true end
        end
    end
    return false
end

return Utils
-- fichier : main.lua
local Utils = require("utils")

print(Utils.majuscule("bonjour"))  --> "BONJOUR"

local a = {1, 2, 3}
local b = {3, 4, 5}
print(Utils.chevaucher(a, b))  --> true

package.loaded et package.path

-- package.loaded contient tous les modules déjà chargés
for nom, mod in pairs(package.loaded) do
    print(nom)
end

-- package.path définit où require() cherche les modules
print(package.path)
-- Contient quelque chose comme :
-- ./?.lua;/usr/local/share/lua/5.4/?.lua;...
-- Le ? est remplacé par le nom du module

Isolation des modules

Chaque module a son propre environnement. Les variables local d'un module ne sont pas visibles de l'extérieur.

-- fichier : config.lua
local CONFIG = {}

-- Variable privée (pas accessible de l'extérieur)
local secret = "ma_clé_123"

-- Variable publique (accessible via le module)
CONFIG.version = "1.0.0"
CONFIG.debug = true

function CONFIG.getSecret()
    return secret
end

return CONFIG
-- fichier : main.lua
local Config = require("config")

print(Config.version)    --> "1.0.0"
print(Config.debug)      --> true
print(Config.secret)     --> nil (variable privée)
print(Config.getSecret()) --> "ma_clé_123" (via fonction publique)

Le pattern mod.lua

Pour les gros projets, on utilise souvent un répertoire mod/ avec un fichier mod/init.lua.

-- Structure de fichiers :
-- projet/
--   main.lua
--   mod/
--     init.lua
--     utils.lua

-- mod/init.lua
local M = {}
M.utils = require("mod.utils")
return M

-- main.lua
local mod = require("mod")
print(mod.utils)  -- accès au sous-module

Récapitulatif

  • require("nom") charge et retourne un module.
  • Un module est un fichier Lua qui retourne une table.
  • package.path définit les répertoires de recherche.
  • Les variables local dans un module sont privées.
  • Un module ne charge qu'une seule fois (cache dans package.loaded).

12. Projet final

Maintenant que vous maîtrisez Lua 5.4, il est temps de créer un projet complet ! Nous allons construire un mini jeu de RPG textuel utilisant toutes les notions apprises : tables, fonctions, métatables, coroutines et modules.

Ce que vous devez implémenter

  • Tables pour l'inventaire, les statistiques du joueur et les ennemis.
  • Fonctions pour les actions (attaquer, fuir, utiliser objet).
  • Métatables pour les entités (joueur, ennemis) avec héritage.
  • Coroutines pour les dialogues et événements scriptés.
  • Modules pour organiser les objets, les combats et l'inventaire.

Le module des entités (entities.lua)

-- entities.lua : Module des entités de jeu
local Entite = {}
Entite.__index = Entite

function Entite.new(nom, pv, attaque)
    return setmetatable({
        nom = nom,
        pv = pv,
        pvMax = pv,
        attaque = attaque,
    }, Entite)
end

function Entite:estVivant()
    return self.pv > 0
end

function Entite:subirDegats(degats)
    self.pv = self.pv - degats
    if self.pv < 0 then self.pv = 0 end
    print(self.nom .. " subit " .. degats .. " dégâts. PV : " .. self.pv)
end

function Entite:__tostring()
    return string.format("%s [PV: %d/%d, ATK: %d]",
        self.nom, self.pv, self.pvMax, self.attaque)
end

-- Héritage : Joueur hérite d'Entite
local Joueur = setmetatable({}, {__index = Entite})
Joueur.__index = Joueur

function Joueur.new(nom)
    local self = Entite.new(nom, 100, 15)
    self.inventaire = {}
    self.xp = 0
    self.niveau = 1
    return setmetatable(self, Joueur)
end

function Joueur:ajouterObjet(objet)
    table.insert(self.inventaire, objet)
end

function Joueur:gagnerXP(montant)
    self.xp = self.xp + montant
    if self.xp >= self.niveau * 50 then
        self.niveau = self.niveau + 1
        self.pvMax = self.pvMax + 20
        self.pv = self.pvMax
        self.attaque = self.attaque + 3
        print("*** Niveau supérieur ! Niveau " .. self.niveau .. " ***")
    end
end

-- Héritage : Monstre hérite d'Entite
local Monstre = setmetatable({}, {__index = Entite})
Monstre.__index = Monstre

function Monstre.new(nom, pv, attaque, xp)
    local self = Entite.new(nom, pv, attaque)
    self.xp = xp
    return setmetatable(self, Monstre)
end

Entite.Joueur = Joueur
Entite.Monstre = Monstre
return Entite

Le module des objets (items.lua)

-- items.lua : Module des objets
local Objets = {
    potion = {
        nom = "Potion de soin",
        type = "consommable",
        effet = function(cible)
            cible.pv = math.min(cible.pv + 30, cible.pvMax)
            print(cible.nom .. " récupère 30 PV !")
        end,
    },
    epee = {
        nom = "Épée de fer",
        type = "arme",
        bonus = 5,
    },
    bouclier = {
        nom = "Bouclier en bois",
        type = "armure",
        bonus = 3,
    },
}

function Objets.obtenir(cle)
    return Objets[cle]
end

return Objets

Les coroutines pour les dialogues

-- dialogues.lua : Module de dialogues avec coroutines
local Dialogues = {}

function Dialogues.marchand()
    return coroutine.create(function()
        print("--- Le Marchand ---")
        print("Marchand : Bienvenue ! J'ai des objets à vendre.")
        coroutine.yield({action = "menu"})

        print("Marchand : La potion te soigne de 30 PV.")
        coroutine.yield({action = "vendre", objet = "potion", prix = 20})

        print("Marchand : Reviens quand tu veux !")
        coroutine.yield({action = "fin"})
    end)
end

function Dialogues.boss()
    return coroutine.create(function()
        print("--- Le Dragon ---")
        print("Dragon : Tu oses défier le Dragon de Feu ?!")
        coroutine.yield()

        print("Dragon : GOUTTE MON FEU !")
        coroutine.yield()

        print("Dragon : Impréssionnant... Tu es digne de ce combat !")
    end)
end

return Dialogues

Le programme principal (main.lua)

-- main.lua : Programme principal du RPG
local math = require("math")
local Entite = require("entities")
local Objets = require("items")
local Dialogues = require("dialogues")

-- Créer le joueur
local joueur = Entite.Joueur.new("Héros")
joueur:ajouterObjet(Objets.obtenir("potion"))

-- Créer les ennemis
local ennemis = {
    Entite.Monstre.new("Gobelin", 30, 8, 15),
    Entite.Monstre.new("Squelette", 50, 12, 25),
    Entite.Monstre.new("Dragon de Feu", 120, 20, 100),
}

-- Fonction de combat
local function combat(j, e)
    print("\n=== COMBAT : " .. j.nom .. " vs " .. e.nom .. " ===")
    print(e)

    while j:estVivant() and e:estVivant() do
        -- Tour du joueur
        local degats = math.random(1, j.attaque)
        e:subirDegats(degats)

        if not e:estVivant() then
            print(e.nom .. " est vaincu !")
            j:gagnerXP(e.xp)
            return true
        end

        -- Tour de l'ennemi
        degats = math.random(1, e.attaque)
        j:subirDegats(degats)

        if not j:estVivant() then
            print(j.nom .. " a été vaincu...")
            return false
        end

        print("  " .. j .. " | " .. e)
    end
end

-- Boucle principale du jeu
print("========================================")
print("   RPG LUA 5.4 - LE DÉFI DU DRAGON")
print("========================================")
print()

-- Dialoguer avec le marchand (coroutine)
local dialog = Dialogues.marchand()
coroutine.resume(dialog)
coroutine.resume(dialog)
coroutine.resume(dialog)

-- Combattre les monstres
for i, ennemi in ipairs(ennemis) do
    print("\n--- Étape " .. i .. " ---")

    -- Dialogue du boss avant le dernier combat
    if i == 3 then
        local bossDialog = Dialogues.boss()
        coroutine.resume(bossDialog)
        coroutine.resume(bossDialog)
        coroutine.resume(bossDialog)
    end

    local victoire = combat(joueur, ennemi)
    if not victoire then
        print("\nGame Over... Vous avez péri.")
        return
    end
end

print("\n========================================")
print("  FÉLICITATIONS ! Vous avez vaincu le Dragon !")
print("  Niveau final : " .. joueur.niveau)
print("========================================")

Conseils pour réussir

  • Commencez par les modules séparément avant de les assembler dans main.lua.
  • Testez chaque module individuellement avec lua -i.
  • Expérimentez : ajoutez des sorts, des pièges, un système de défense.
  • Utilisez string.format pour un affichage plus soigné.
  • Pensez à gérer les erreurs avec pcall pour les inputs utilisateur.
  • Relisez les leçons précédentes si un concept n'est pas clair.

Bienvenue