[idées de bug](./NOTES.org)



# Création d'un niveau


## stucture d'un niveau

Un niveau se compose de:
* main.c [structure d'un main](#le-main)
* level.py [structure d'un level.py](#levelpy)
* map.tmx [création de carte](#carte)
* un engine dans la majorité des cas [Utilisation d'un engine](#engine)

## Fonctionnement d'un niveau

Les niveaux dans agdbentures se trouve tous dans le répertoire levels.\
Cependant comme ce jeu suppose d'avoir différentes versioon d'un code pour en avoir une bugé et l'autre non
nous allons copier les fichiers de notre niveau de manière différente selon le mode choisi. \
Pour voir les modes taper la commande suivante depuis le répertoire principal
```bash
./agdbentures -h
```
En réalité lorsque vous allez lancer le jeu vous ne jouerez jamais depuis le répertoire dans lequel vous créer le niveau.


# Engine

Très vite dans le jeu les niveaux vont commencer à utiliser des engine (moteur de jeu) afin de pouvoir séparer dans différents fichgiers les actions du joueur et les interaction avec la carte.\
Ces engine évolueront et ne se trouveront pas directement dans votre répertoire de travail mais dans le répertoire engine_versions.\
Afin de spécifier au jeu quel engine vous utiliser vous l'écrirais dans les métadonnées [Plus d'informations ici](#utilisation-des-métadonnées-)\
Ce qui permettra qu'au lancement du jeu la partie de l'engine utile pour ce niveau soit donnée au joueur dans son répertoire à lui (working_directory)\
Pour en savoir plus sur le fonctionnement interne des engines se réferer à ce fichier [here](./engines/API.md)\
En réalité, l'engine source se trouve dans le répertoire engine et toutes les copies que nous avons se font via des #ifdef .


# Le Main

Le fichier main va contenir beaucoup d'information utile au jeu via ses [Métadonnées](#utilisation-des-métadonnées-)

En régle génèrale vous devrez indiquez cette ligne au début de votre code :
```c
#include "engine/agdbentures.h"
```
cela va permettre de récupérer le moteur de votre jeu, même si celui ci ne se trouve pas dans votre répertoire ce chemin est bien le bon car vous jouerez toujours dans un répertoire ou l'engine utilisé a était copié



L'objectif d'un niveau étant de faire découvrir des bugs au étudiants il faut évidemmment en glisser dans votre propre main.c.\
Cependant nous voulons avoir deux versions, une fonctionelle et l'autre bugé, pour ce faire nous utilisons cette structure:
```c
bout de code
#ifdef BUG
 code bugué ici 
#else
 code servant de solution 
#endif
bout de code
```

Si vous voulez rajouter des lignes uniquement dans certaines circonstances vous pouvez aussi faire:
```c
#ifdef BUG
code bugé
#endif

ou bien si vous voulez ne rien avoir dans le code bugé

#ifndef BUG
solution ici
#endif
```
Vous pouvez retrouver des exemples de ce cette structure dans plusieurs niveaux, voici deux exemples [grosse structure avec if else et end](./levels/medium/local_struct/main.c) et 
[petite structure avec juste en ifndef](./levels/tutorial/01_first_bug/main.c)
.\
Vous pouvez aussi créer des bugs dans l'engine du jeu pour certains niveaux, auquel cas vous le spécifirez ddans les métadonnées pour rendre l'utilisation future de votre niveau plus simple.
Un exemple de niveau avec bug sur l'engine se trouve [la](./levels/basic/command_args)



# Carte

Le jeu utilise des cartes en .tmx, ici le plus simple reste d'aller voir un exemple comme [ici](./levels/medium/local_struct/main.tmx)\
Les lignes commencant par tileset permettent de récupérer les spirtes utilisés dans notre jeu et seront communes à tous les niveaux tant que rien ne sera rajouté dans les ressources.\
Les champs width et height sont ceux qui permettent de choisir la taille de votre carte, il fautq ue ceux définis tout en haut de votre fichier soit cohérent avec ceux de chaque layer\
Une layer est une couche du niveau, ayant certaines propriétés, vous retrouverez surtout les champs :
( floor , decorations, decorations_top et walls)\
Chacune de ces layers va être une matrice de taille width * height contenant des numéros. Ces numéros correspondent à l'ID d'un sprite.\
Pour construire un niveau nous vous recommandons d'utiliser tiled, si vous ne l'avez pas installé sur votre machine faite cete commande:
```bash
apt-get install tiled
```
ensuite vous n'avez plus qu'à lancer l'application en tapant 
```bash
tiled
```
dans votre terminal.\
Une fois ici vous pourrez observer tout les objets à votre disposition pour la création de votre niveau ainsi que leur id correspondant si vous souhaiter construire via le .tmx



# Level.py

Le fichier level.py va avoir plusieurs rôles dans l'utilisation d'un niveau 
* tester qu'il est bien fait dans votre conception, voir [Test](#utilisation-du-module-de-test)
* Valider la solution proposée par un joueur, voir [Validation](#utilisation-du-module-de-validation)
* Discuter avec l'interface graphique, voir [Interface graphique](#lien-avec-linterface-graphique)\


Sa structure va toujours être la même e commencant par inclure les différentes ressources qui sont nécessaires ainsi que le fait que ce soit un éxécutable python:
```python
#!/usr/bin/env python3

from level.level_abc import AbstractLevel
from loader.loader import load_level, run_level, test_level
from level.objects import find_var_in_frame, get_top_map
from utils import lvl
import graphic.constants as cst
```

ensuite vous allez vouloir instancier votre niveau et pour ce faire vous allez utiliser cette ligne :
```python
class Level(AbstractLevel):
```

Une fois cela fait vous allez pouvoir définir différentes fonctions qui vont être appelées automatiquement lors de l'utilisation du jeu. Ces fonctions sont :
```python
def arcade_custom_first_start(self): # cette fonction est appélé quand le joueur clique sur restart
def arcade_custom_restart(self): # cette fonction est appélé quand le joueur va lancer le niveau 
def pre_validation(self): # cette fonction sert à vérifier que la solution du joueur n'est pas une triche 
# en placant des repéres avant l'éxécution de sa solution une fois qu'il aura atteint la fin du niveau      
def post_validation(self): # cette fonction vient vérifier que les repères placées au début du contrôle
# ont bien atteint les valeurs désirées et sinon peut renvoyer un message au joueur disant que sa réponse 
# n'est pas celle attendu 
def test(self): # cette fonction sert en interne à vérifier qu'un niveau que l'on développe a bien le 
# comportement attendu en mode BUG et en mode réponse
```

Une fois ces prérequis faits, vous allez pouvoir définir d'autres fonctions spécifiques à votre niveau afin de faire différentes choses.
En régle génèrale vous utiliserait pour les appeler l'une de ces fonctions :
```python
self.register_breakpoint("{x}", y) # cette fonction permet que lors de l'éxècution du code c,
# si la fonction x est appelée alors votre fonction du level.py nommée y va se lancer.
self.register_leave_function("{x}", y) # cette fonction de la même manière va effectuer une action
# à la sortie de la fonction x dans le code c en appelant la fonction y de votre level.py
```

Vous pouvez aussi consulter l'état d'une variable du code c depuis votre code python grâce à ces fonctions:
```python
chk = self.checker.tracker
chk.get_variable_value_as_str('{x}',"{type") # cette fonction renvoie la valeur de la variable x du code c 
# en la typant avec type, exemple pour récupérer l'entier : var_entier et la stocker dans une var : ma_var 
# vous feriez :
ma_var = chk.get_variable_value_as_str('var_entier',"int")
# la fonction suivante permet d'appeler une fonction à vous dès que la valeur d'une variable sera changé
self.checker.register_watch('{x}', y) # dès que la variable x change dans le code c alors votre fonction y sera appelée
```



# Utilisation du module de validation
Les tests fait lors de la validation du niveau par le joueur se font en deux parties
Pour être appelées, ces fonctions attendent que le joueur ait atteint la sortie du niveau\
A ce moment s'enclenche la validation.

* pre_validation : ici on initialise les fonctions dont on va avoir besoin pour vérifier que les changements éffectues par le joueur dans son main.c sont bien ceux qui sont attendu de notre côté

Au début nous allons donc initialiser des valeurs dans pre_validation ainsi que donner les différents inputs
nécessaires au bon fonctionnement du niveau.\
Pour ce faire nous allons toujours utiliser la même fonction qui sera celle ci:
```python
self.checker.append_inputs() # à placer dans votre pre_validation pour éviter une boucle infinie
# cette fonction va stocker des inputs et les envoyer au jeu lors de la validation quand celui ci
# attend une entrée de la part du joueur.

# Par exemple dans un niveau qui nous demanderait d'aller à droite, puis en haut, puis à gauche puis 
# de taper BUY puis de descendre nous ferions :
self.checker.append_inputs(
    [
        'RIGHT',
        'UP',
        'LEFT',
        'BUY',
        'DOWN',
    ]
)
```

* post_validation : ici on vérifie que tout s'est bien passé et si ce n'est pas le cas on peut faire remonter l'erreur afin d'annuler la validation du niveau en cas de comportements "non désiré"

A ce stade on ve observer que tout s'est déroulé comme prévue et si ce n'est pas le cas refuser la validation
Pour ce faire nous allons fonctionner de cette manière :

```python
if valeur_actuelle != valeur_voulue:
    self.checker.failed("{message expliquant au joueur pourquoi sa solution est refusée}")
```

Une fois que toute cette fonction est finie et si il n'a renvoyé aucune erreur alors le joueur à une solution
qui nous semble valide et alors il pourra passer au niveau suivant


# Utilisation du module de test

Les niveaux doivent être testés pour s'assurer de leur bon fonctionnement lors de leur mise en place
dans le jeu et pour cela, il existe un fichier permettant de tester automatiquement les niveaux.
Le fonctionnement de ce fichier consiste à lancer votre niveau puis éxecuter les actions définies
dans le la fonction test de votre level.py.
Cette fonction test aura toujours la même structure qui est la suivante:
```python
def test(self):
    import tests.lib_test as T
    # si le test vérifie les actions censés engendrer une défaite alors
    self.recompile_bug()
    {faire des actions}
    T.expect_defeat()
    # si les actions sont cénsés valider le niveau alors
    self.recompile_answer()
    {faire des actions}
    T.expect_victory()
    #puis ensuite dans tout les cas
    self.run()
    
    # plusieurs test peuvent ainsi être fait en enchainant cette structure de 
    # compile dnas le mode testé
    # spécifie les actions à éxécuter
    # résultat attendu
    # run
```
Maintenant vient la question de comment envoyer des commandes.
Pour le savoir rendez vous [ici](./lib/tests/lib_test.py) ou vous trouverez une classe _Cmd\
Dans cette classe sont définies plusieurs instruction qui ensuite pourront être envoyées directement à GDB
ensuite lorsque vous effectuer une action du type T.quelquechose()
cette commande va directement être envoyé dans une fonction créee dynamiquement permettant de construire la commande 
qui ensuite sera interprétée dans le fichier [ici](./lib/level/level_test.py) dans la fonction run \
Ainsi, les commandes sont stockées et puis quand vous faites le self.run() elle seront éxécutées dans l'ordre dans lequel vous les avez écrites. \
A noter que pour l'envoi d'input avec la commande T.send_input("ma_chaine") il faut les faire avant T.ccontinue car sinon vous serez dans la boucle sans pouvoir envoyer les commandes car celles ci ne sont plus lues.


Pour lancer vos tests il suffit de lancer la commande suivante depuis le répertoire principal
```bash
./tests/testLevels.py # pour tout tester
./tests/testLevels.py - h # pour connaitre les différentes options proposées par ce module la commande 
```

# Lien avec l'interface graphique

Pour pouvoir faire des choses dans l'interface graphique depuis votrre fichier level.py
vous allez utiliser des payloads dont l'usage sera fait majoritairement dans ce fichier [ici](./lib/graphic/sprites/sp_objects.py)
leur structure génèrale consiste à donner:
- un topic 
- une action
- un objet sur lequel faire cette action 
- si cette action a besoin de plus d'information alors ses paramétres.
Par exemple si je voulais cacher un élèment dans ma layer décoration_top je ferais

```python
payload = {
    "topic": "sprites",
    "action": "hide",
    "layer": "decorations_top",
    "locations": [
        ({position en x}, {position en  y}),
    ],
}
self.send_to_gui(payload)
```

Pour retrouver plus d'exmple consutler les level.py des niveaux déjà existant, vous trouverez surement quelque chose ressemblant à ce que vous voulez faire



# Utilisation des métadonnées 

Les métadonnées sont essentielles au fonctionnement du jeu.
Ces métadonnées vont préciser plusieurs choses pour l'éxécution dont :
- le nom du niveau
- le mode de jeu
- les commandes utilisables dans l'interface graphique
- le nom de l'engine utilisé
- la ou les cartes utilisées pour ce niveau
- les messages à envoyées au joueur par le WOP 
- les coordonnées de certains objets ainsi que certains de leurs attributs comme le caractére qui les représente
- des vérifications automatique potentiellement

Pour voir comment toutes ces choses sont initialisées il vous est recommandé de consulter les niveaux déjà existants
Vous avez un bon exemple [ici](./levels/medium/crepes/main.c).\
Les métadonnées sont initialisées grâce à des bornes de cette forme
```c
/* @AGDB
 * 
 * ici vous mettez les informations nécessaires
 *
 *
 */
```

Nous vous recommandons aussi d'y introduire des descriptions de votre bug et de l'objectif génèral de votre
niveau dans la perspective ou quelqu'un devrait consulter votre niveau.
mettre les commentaires ici permet qu'ils ne soient pas affichés au joueur et évite donc de lui donner
accés à des informations destinées aux autres développeurs de agdbentures