Ci-dessous, on montre comment on peut créer un jeu[7] de problème du rendu de monnaie basé sur le système {2,5,10}:
Format xml
Par exemple, un début de fichier Enigma ressemble à ceci[8]:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<el:level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://enigma-game.org/schema/level/1 level.xsd" xmlns:el="http://enigma-game.org/schema/level/1">
<el:protected >
<el:info el:type="level">
<el:identity el:title ="Enignumisma" el:subtitle="Brother, can you spare a dime or forty-three?" el:id="20100108alain32"/>
<el:version el:score="1" el:release="1" el:revision="0" el:status="test"/>
<el:author el:name="Alain Busser" el:email=""/>
<el:copyright>Copyright © 2010 Alain Busser</el:copyright>
<el:license el:type="GPL v2.0 or above" el:open="true"/>
<el:compatibility el:enigma="1.10"/>
<el:modes el:easy="false" el:single="true" el:network="false"/>
<el:comments>
<el:credits el:showinfo="false" el:showstart="false"></el:credits>
</el:comments>
<el:score el:easy="2:07" el:difficult="2:07"/>
</el:info>
<el:luamain><![CDATA[
ti[" "] = {"fl_concrete"}
ti["x"] = {"fl_gravel_framed"}
ti["1"] = ti[" "]..{"st_door_d","door1",faces="ns",state=CLOSED}
ti["2"] = ti[" "]..{"st_door_d","door2",faces="ns",state=CLOSED}
ti["3"] = ti[" "]..{"st_door_d","door3",faces="ns",state=CLOSED}
ti["4"] = ti[" "]..{"st_door_d","door4",faces="ns",state=CLOSED}
ti["#"] = {"st_concrete"}
ti["o"] = {"st_oxyd"}
ti["@"] = {"#ac_marble"}
ti["i"] = {"st_switch",action="callback",target="GimmeMoney"}
ti["l"] = {"it_coin_l","large#"}
ti["m"] = {"it_coin_m","medium#"}
ti["s"] = {"it_coin_s","small#"}
ti["d"] = {"it_document",text="text1"}
wo(ti, " ", {
"####################",
"#o lllll#o#",
"# mmmmm#1#",
"# sssss#2#",
"# @ #3#",
"# #4#",
"# ####### #",
"# #xxxxx# d#",
"# xxxxx# #",
"# #xxxxx# #",
"# ####### #",
"# i#",
"####################"
})
ndoors=4
wealth=150 -- cannot have that
function GimmeMoney()
if ndoors>0 then
somme=0
tens=no["large#*"]
for n in tens do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+10 end
end
fives=no["medium#*"]
for n in fives do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+5 end
end
twos=no["small#*"]
for n in twos do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+2 end
end
if somme==wealth then
st(no["door"..ndoors]):open()
ndoors=ndoors-1
end
wealth=10*random(5)+5*random(5)+2*random(5)
wo[po(18,9)]={"it_document",text=wealth.." cents"}
end
end
]]></el:luamain>
<el:i18n>
<el:string el:key="title">
<el:english el:translate="false"/>
</el:string>
<el:string el:key="subtitle">
<el:english el:translate="true"/>
<el:translation el:lang="fr">T'as pas 43 balles, ou alors 51?</el:translation>
</el:string>
<el:string el:key="text1">
<el:english el:translate="false">To open the door, you must pay. To pay you put some coins inside the golden safe. You must give the exact change.</el:english>
<el:translation el:lang="fr">Pour ouvrir la porte il faut payer. Pour payer il faut placer des pièces dans le coffre doré. Vous êtes prié de faire l'appoint.</el:translation>
</el:string>
</el:i18n>
</el:protected>
</el:level>
Pour tester ce jeu, il suffit de copier le texte ci-dessus dans un fichier dont l'extension est xml (par exemple alain32_1.xml qui est son nom original), puis de placer le fichier dans le dossier auto des niveaux d'Enigma.
On y lit d'abord que c'est du xml, puis à la ligne 3, l'adresse internet où on peut chercher la description du format de fichier d'Enigma, puis l'arborescence des éléments. L'élément info indique que c'est un niveau (et non une bibliothèque par exemple); l'élément identity donne le nom du jeu, ainsi qu'un sous-titre et un identifiant; suivent alors le numéro de version du jeu, le fait qu'il est en statut test (c'est-à-dire qu'il ne fait pas partie des niveaux d'Enigma), le nom de l'auteur du niveau, la licence GNU GPL qui rappelle que le jeu est libre... L'élément score donne les records du monde du niveau en mode facile et en mode difficile. Enfin vient le code lua, lui-même dans un élément. Il est décrit ci-dessous. Les derniers éléments décrivent les éléments texte du niveau, avec des indications pour la traduction dans différentes langues.
Code lua
Un niveau d'Enigma est une base de données d'objets qui occupent chacun une position à coordonnées entières. Les objets sont de l'un des types suivants :
- dalle de sol (permet de faire un damier en alternant deux couleurs); les dalles d'eau sont à éviter parce que Blackball s'y noie.
- pierres (permettent de construire des murs de labyrinthes); certaines de ces pierres peuvent bouger lorsque Blackball les heurte.
- objets posés par terre s'il n'y a pas de pierres; Blackball peut les ramasser en passant dessus, et les utiliser en cliquant dessus dans son inventaire
- acteurs, dont Blackball lui-même.
Pour définir un jeu, on doit donc commencer par établir une sorte de dictionnaire représentant par un caractère chaque élément du jeu, puis en plaçant les caractères sur une carte du jeu (comme les niveaux de Sokoban), et enfin en écrivant les méthodes de chacun de ces objets en lua.
Définition des dalles
On peut convenir de représenter par des espaces les dalles ayant les propriétés du béton, en écrivant[9]
ti[" "] = {"fl_concrete"}
(fl est une abréviation pour floor).
Puis de même, le sol de la chambre peut être représenté par des x (du gravier encadré):
ti["x"] = {"fl_gravel_framed"}
Le niveau sera muni de 4 portes numérotées de 1 à 4, qui s'appelleront door1, door2, door3 et door4; elles auront leur battants au nord et au sud, et seront initialement fermées et par-dessus le sol en béton (la concaténation est notée par deux points en lua[10]):
ti["1"] = ti[" "]..{"st_door_d","door1",faces="ns",state=CLOSED}
ti["2"] = ti[" "]..{"st_door_d","door2",faces="ns",state=CLOSED}
ti["3"] = ti[" "]..{"st_door_d","door3",faces="ns",state=CLOSED}
ti["4"] = ti[" "]..{"st_door_d","door4",faces="ns",state=CLOSED}
Pour les murs de la chambre et le couloir fermé par les portes, on utilise des pierres ayant elles aussi l'aspect du béton, afin de donner un look "urbain" à ce jeu (st veut dire stone).
ti["#"] = {"st_concrete"}
Pour que ce jeu soit jouable, il faut au moins deux pierres oxyd (celles que Blackball devra toucher pour gagner le jeu); elles seront représentées par le caractère o sur la carte:
Une seule dalle sera représentée par une arobase, l'acteur Blackball lui-même (précédé par un dièse pour le centrer sur la dalle):
Une dernière pierre est représentée par un i: C'est un interrupteur ("switch"), paramétré pour qu'à son contact, il lance ("callback") la fonction lua appelée "GimmeMoney()":
ti["i"] = {"st_switch",action="callback",target="GimmeMoney"}
Ce jeu utilise des pièces de monnaie de valeurs respectives 10, 5 et 2 cents. Elles seront représentées respectivement par les lettres l, m et s (comme large, medium et small):
ti["l"] = {"it_coin_l","large#"}
ti["m"] = {"it_coin_m","medium#"}
ti["s"] = {"it_coin_s","small#"}
Enfin, la règle du jeu est dans un document que Blackball peut ramasser en roulant dessus, puis lire en cliquant dessus; sur la carte, ce document est représenté par la lettre d:
ti["d"] = {"it_document",text="text1"}
Ainsi constitué, l'objet ti (comme tile) est un tableau associatif, dont les éléments sont des objets du jeu. Constituer le jeu, c'est puiser ces éléments et les placer dans une carte appelée wo (comme world) ci-dessous
Carte du niveau
La construction du jeu ressemble à un jeu de construction: On puise les éléments dans le tableau ti, avec une valeur par défaut pour le sol (le caractère espace) et en suivant les instructions qui figurent dans la carte (une chaîne de caractères elle aussi):
wo(ti, " ", {
"####################",
"#o lllll#o#",
"# mmmmm#1#",
"# sssss#2#",
"# @ #3#",
"# #4#",
"# ####### #",
"# #xxxxx# d#",
"# xxxxx# #",
"# #xxxxx# #",
"# ####### #",
"# i#",
"####################"
})
Une fois rendue, cette carte ressemble à ceci dans le jeu Enigma:
Pour finir l'initialisation du jeu, on affecte deux variables wealth (le montant à déposer dans la chambre, initialement trop grand pour que ce soit possible avant d'actionner l'interrupteur) et ndoors qui représente le numéro de la prochaine porte à ouvrir (c'est-à-dire le nombre de portes fermées):
ndoors=4
wealth=150 -- cannot have that
Algorithmes
Le principe du jeu est le suivant: Chaque fois que Blackball actionne l'interrupteur, on lui apporte un ordre de mission, comportant un montant à déposer dans la chambre, et si le montant correspond à celui qui est demandé, la prochaine action de l'interrupteur ouvrira la porte portant le numéro ndoors. La variable ndoors est alors décrémentée jusqu'à ce que toutes les portes soient ouvertes ce qui permet de gagner le jeu. C'est le rôle de la fonction GimmeMoney() appelée à chaque action de l'interrupteur:
function GimmeMoney()
if ndoors>0 then
somme=0
tens=no["large#*"]
for n in tens do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+10 end
end
fives=no["medium#*"]
for n in fives do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+5 end
end
twos=no["small#*"]
for n in twos do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+2 end
end
if somme==wealth then
st(no["door"..ndoors]):open()
ndoors=ndoors-1
end
wealth=10*random(5)+5*random(5)+2*random(5)
wo[po(18,9)]={"it_document",text=wealth.." cents"}
end
end
Si ndoors est déjà égal à zéro, cette fonction ne fait rien puisque les portes étant ouvertes, la fonction GimmeMoney() est devenue inutile. Sinon on calcule le montant présent dans la chambre en initialisant une variable somme à zéro, puis en y additionnant 10 fois le nombre de pièces de 10, 5 fois le nombre de pièces de 5 et 2 fois le nombre de pièces de 2 présentes dans la pièce (c'est-à-dire, dont l'abscisse est comprise entre 2 et 8, et l'ordonnée entre 6 et 10 exclus). Par exemple, pour avoir le nombre de pièces de 10, on fait
tens=no["large#*"]
for n in tens do
if n.x>2 and n.x<8 and n.y>6 and n.y<10 then somme=somme+10 end
end
(On place dans l'objet tens les objets dont le nom commence par large, no étant une abréviation pour named objects; on peut alors boucler sur les éléments de tens); on fait pareil avec les pièces de 5 et de 2, puis si les deux montants coïncident, on adresse à la pierre dont le nom est doors concaténé avec le numéro ndoors, un message (représenté par un double-point) lui demandant de s'ouvrir:
if somme==wealth then
st(no["door"..ndoors]):open()
ndoors=ndoors-1
end
Enfin, on calcule un nouveau montant aléatoire (à placer ensuite dans la chambre) et on l'écrit sur un document suivi du mots cents, avant de placer ce document sur la dalle de coordonnées (18;9):
wealth=10*random(5)+5*random(5)+2*random(5)
wo[po(18,9)]={"it_document",text=wealth.." cents"}
Théorie de ce jeu
En fait, malgré l'absence de pièces d'un cent, le jeu n'est pas très difficile puisqu'on peut toujours trouver une manière d'arranger des pièces de 10, 5 et 2 avec l'algorithme glouton.