À l'origine, Asci est un jeu de rôle en Python pour Graph 90. Le jeu est aujourd'hui accompagné d'un moteur pour que vous puissiez réaliser vos jeux de rôles en Python avec un minimum d'effort.
Le moteur repose sur l'évolution de points d'expérience pour déterminer votre avancée dans le jeu. Ces points d'expériences sont donnés au joueur à l'issu d'évènements.
Les statistiques sont une liste de variables dont le premier élément est nécessairement les points de vie. Cette liste est donnée au moteur lors de l'initialisation, néanmoins, le moteur ne *modifie en aucun cas* cette variable. Il la stocke pour vous et la modifie sur votre demande, lors des évènements. Il s'agit donc d'une variable maîtresse dans les mécaniques de votre jeu.
Les statistiques ne peuvent être modifées que lors d'évènements. Comme vous le verrez dans la suite, les évènements sont des listes dont les derniers éléments ne sont pas déterminés : il s'agit des points que vous pouvez ajouter ou enlever à vos statistiques.
Vous aurez à plusieurs reprise des fonctions à programmer, ces fonctions auront accès à des variables du moteurs, elles sont toutes en lecture seule, exceptée les statistiques qui peuvent être modifiée par effet de bord.
Vous allez devoir créer une carte du monde, mais aussi les intérieurs des maisons. Pour cela vous allez avoir besoin d'un tuple qui va devoir remplir quelques conditions :
- toutes les cartes sont des r-docstrings `r""" ... """`
- le premier élément du tuple est la carte du monde
- les autres éléments sont eux-même des tuples de la forme : `(carte, (x_entree, y_entree), (x_sortie, y_sortie))` où `(x_entree, y_entree)` sont les coordonnées de la porte de la maison *dans la carte du monde* et où `(x_sortie, y_sortie)` sont les coordonnées de la porte de la maison *dans la carte de la maison*
Les dialogues permettent au joueur de choisir sa réponse. Si le joueur ne peux pas choisir de réponse, mettez `nb` à 0, sinon mettez `nb` sur le nombre de réponse possible. Les différentes réponses sont à afficher avec un numéro dans le corps du message. Par exemple : `[0, "Ceci est une question ? 1.Première réponse. 2.Seconde réponse.", 2]`. Le numéro de la question correspond alors au nombre de points d'expérience gagnée par le joueur (il est, de se fait, conseillé de ne pas donner de points d'expérience au joueur lors d'un dialogue). Si il n'y a pas de réponses possible, vous pouvez omettre cet argument. Attention, cette astuce n'est valable que si vous ne comptez pas modifier les statistiques.
Ces évènements sont stockés dans des dictionnaires dont les clefs correspondent aux points d'expériences, c'est peut-être pas très clair, mais avec un exemple, ça ira mieux.
Par simplicité, j'utiliserai un dictionnaire par PnJ, vous faites bien comme vous voulez, du moment que vous obtenez un truc qui marche… Ces dictionnaires d'évènements doivent être placés dans une fonction qui doit respecter certaines règles :
- la fonction doit prendre en argument l'expérience, l'id de la map actuelle (qui correspond à son index dans el tuple des maps), les coordonnées du joueur et les statistiques
- la fonction doit renvoyer un dictionnaire d'évènement ou un évènement.
Ici, on voit peut-être un peu mieux le fonctionnement : les clefs du dictionnaires d'évènements correspondent à des points d'expérience : si le joueur à `xp_1` points d'expérience, alors c'est ce dialogue là qui sera lu, et si aucun évènement ne correspond, mais que le PnJ a quand même un texte à dire à un moment, l'évènement `"base"` sera lu.
Vous êtes (presque) totalement libre ! Votre fonction doit respecter quelques points :
- la fonction, comme pour les évènements, doit prendre en argument l'expérience, le numéro de la carte, les coordonnées du joueur et les statistiques. (vous pouvez toujours objecter que ça fait beaucoup de variables, mais ça peut servir)
- la fonction doit retourner un booleen égal à `True` si le joueur a gagné, `False` sinon.
Vous devriez avoir une fonction qui ressemble un peu à ça :
- la fonction devra prendre en argument la liste des statistiques (les points d'expérience, au sens du moteur, sont considérés comme ne faisant pas partie des statistiques)
- la fonction devra afficher elle-même les statistiques.
La fonction libre est une fonction qui est appelée lorsque l'utilisateur presse la touche 8. Vous pouvez afficher n'importe quoi, y compris rien, ou même modifier des trucs. Cette fonction doit être complètement programmée par vous. Elle ne doit répondre qu'à un seul impératif :
- la fonction prend en argument les statistiques du joueur et renvoie `None`
Il ne reste plus qu'à faire une petite fonction de nom du votre jeu qui va créer un modèle vierge de jeu rôle et qui va transmettre toutes ces données au modèle. Il vous restera ensuite à lancer cette fonction pour jouer.
Nous avons un PnJ aux coordonnées `(2, 5)`, et pas d'adversaire, il nous reste à faire les dialogues avec cet unique PnJ, la fonction de combat ne sera pas traitée ici vu qu'il n'y a pas d'ennemi.
Pour les évènements, rien de nouveau, étant donné que nous n'avons a priori pas de statistiques à part la vie, il n'y a pas forcément grand chose à faire, vous pouvez vous amuser à faire des dialogues un peu complexes avec différentes fins.
Maintenant que vous êtes bien chaud, on va commencer les choses sérieuses. Enfin "sérieuses" je vous propose de faire pas à pas un petit jeu avec disons une quête et quelques mécaniques un peu bidon (ça reste un exemple). On peut déjà poser quelques petits trucs :
- le joueur a des points de vie, d'attaque et de défense
- le scénario se résumera à une seule quête : tuer un adversaire. La quête peut être refusée.
On peut déjà rapidement voir à quoi va ressembler l'arbre d'XP (j'en fait pas mal et je vous invite à en faire aussi, ça aide vraiment à voir le déroulement de l'histoire) :
```
+--1--3--4--5-
-0--+
+--2-X
```
Pour expliquer rapidement :
- 0 : début de l'histoire, énoncé de la quête
- 1 : quête acceptée
- 2 : quête refusée
- 3 : combat avec le bandit
- 4 : debrief avec le PnJ
- 5 : fin de la partie (limite de l'XP pour le jeu)
Comme on est vraiment chaud, on va essayer de faire une map un peu plus joli que d'habitude avec des PnJ qui vont avoir des dialogues sans forcément avoir un rôle dans l'histoire, ça va être l'occasion de sortir un peu du modèle "1 PnJ = 1 dictionnaire d'évènements" et de vous rappeller que vous pouvez renvoyer un évènement seul.
Allez ! On est parti avec une map et deux maisons :
```
cartes = (
r"""
__
/ \___ ### *
|<> \ ##### _
|^|____| ### / \
/_\ |^| *
____
$ ## / \___/ \ ##
#### |<> <>| ####
## |___|^|___| ##
|| ||""",
(r"""
+--+--+--------+--+--+
| | | | | |
| + + + + |
| |
| + + + + |
+--/ *\--------/ \--+
| |
+---|^|--------------+""",
(1, 3), (5, 7)),
(r"""
+-------+
| |
| |
| |
+--|^|--+
""",
(19, 4), (4, 4))
)
```
On va commencer par faire le PnJ qui n'a pas un rôle énorme dans le scénario. On va faire un médecin (comme y a du combat, c'est pratique de mettre un medecin dans la place). L'idée est simple : si le joueur a moins de 100 points de vie, il est blessé, donc on lui rajoute des points de vie avec un texte. Si il est en pleine forme, on le lui signale et on ne touche pas aux points de vie.
On a donc la fonction qui ressemble à ça (je met des `...` au niveau des trucs pas encore définis):
```
def evenements(xp, carte_actuelle, x, y, stat):
coords = (x, y)
if carte_actuelle == 0:
if coords == (...):
# Joueur blessé
if stat[0] <100:return[0,"Oh,maistuesblesse!",0,50]
else: return [0, "Reviens me voir quand tu seras blesse."]
return [0, "Hmm ?"]
```
Comme il s'agit d'un personnage très secondaire (voir inutile) on peut prendre pour médecin le PnJ tout à l'est, aux coordonnées `(24, 4)`. On prend alors l'autre PnJ pour le personnage qui donne la quête au joueur (aux coordonnées `(16, 1)`)
On peut compléter notre fonction avec la proposition de la quête :
```
def evenements(xp, carte_actuelle, x, y, stat):
coords = (x, y)
if carte_actuelle == 0:
if coords == (24, 4):
if stat[0] <100:return[0,"Oh,maistuesblesse!",0,50]
else: return [0, "Reviens me voir quand tu seras blesse."]
elif coords == (16, 1): return {
"base": [0, "Alors ? T'en sorts-tu ?"],
0: [0, "J'ai une quete pour toi ! Un ami a moi a des problemes : un personnage louche traine autour de sa maison... Si tu pouvais l'en debarasser, il t'en serai reconnaissant. 1. Je m'en charge ! 2. Trouve quelqu'un d'autre.", 2],
1: [2, "J'etais sur que je pouvais compter sur toi ! Tiens, voila une dague et une petit bouclier.", 0, 0, 10, 10],
2: [2, "Si un jour tu as besoin de moi, tu seras sympa de m'oublier."],
3: [0, "Alors ? Il est mort ce bandit ?"],
4: [1, "Merci, tu as rendu un grand service a mon ami !"]
Maintenant il reste à programmer la fin du combat, en d'autre terme, si le joueur gagne le combat, la fonction `evenements` va être appelée, il faut donc renvoyer un petit texte qui signale que le combat est gagné et qui incrémente les points d'XP d'un point (pour passer de 3 à 4). Le bandit est sur les coordonnées `(4, 7)`.
if stat[0] <100:return[0,"Oh,maistuesblesse!",0,50]
else: return [0, "Reviens me voir quand tu seras blesse."]
elif coords == (16, 1): return {
"base": [0, "Alors ? T'en sorts-tu ?"],
0: [0, "J'ai une quete pour toi ! Un ami a moi a des problemes : un personnage louche traine autour de sa maison... Si tu pouvais l'en debarasser, il t'en serai reconnaissant. 1. Je m'en charge ! 2. Trouve quelqu'un d'autre.", 2],
1: [2, "J'etais sur que je pouvais compter sur toi ! Tiens, voila une dague et une petit bouclier.", 0, 0, 10, 10],
2: [2, "Si un jour tu as besoin de moi, tu seras sympa de m'oublier."],
3: [0, "Alors ? Il est mort ce bandit ?"],
4: [1, "Merci, tu as rendu un grand service a mon ami !"]
}
elif coords == (4, 7):
# Si le bandit vient d'être tué
if xp == 3: return [1, "Vous avez reussi la quete !"]
# Si le bandit est encore vivant
elif xp <3:return[0,"Qu'est-cequeturegardestoi?Casses-toi!"]
# Si le bandit est déjà mort
else: return [0, "Vous regardez le cadavre froid du bandit."]
return [0, "Hmm ?"]
```
Il reste à faire une fonction pour les combats !
Le principe est simple, la fonction va regarder de quel bandit il s'agit, si c'est le moment du combat, la fonction va lancer le combat, sinon elle déclare le combat gagnée ce qui va déclencher la fonction `evenements` (donc afficher le texte prévu juste avant)
```
def combats(xp, carte_actuelle, x, y, stat):
coords = (x, y)
if carte_actuelle == 0:
if coords == (4, 7):
if xp == 3: ennemi_stat = [75, randint(5, 10), randint(5, 10)]