À la fin du premier article, nous avions laissé notre héros dans son appartement, juste capable de se déplacer de pièce en pièce via les verbes de direction (nord, sud, est et ouest).

Si nous voulons qu’il soit capable d’interagir avec son environnement, il va falloir aller un peu plus loin dans la gestion des commandes. Certains verbes, comme le classique EXAMINER, ne peut pas s’utiliser seul, il lui faut un objet, qui sera la cible de notre regard.

Ajout d’un objet à notre scène

Agrémentons donc le quotidien de notre héros en meublant son salon :

let objects = {
    // [...]
    // ajouter la télévision dans la liste des objets
    'tele':{
        'synonyms': ["TELE", "TELEVISION", "TV"],
        'name': "la télévision",
        'location': 'salon',
        'description': "Un petit poste de télévision"
    }
};

On ajoute une télévision en précisant qu’elle est dans le salon grâce à la propriété “location”.

Pensez à modifier la description du salon pour que le joueur sache qu’il y a une télévision :

'salon': {
	'synonyms': ["SALON"],
	'name': "le salon",
	'location': null,
	'description': "Un joli salon avec une télévision. En allant au sud, vous accéderez à la cuisine.",
	'sud': 'cuisine'
},

On ajoute le verbe “examiner” à la liste de nos verbes :

const verbs = {
    // [...]
    "examiner": { // Examiner
        'synonyms':["EXAM", "X"],
        'default': "Vous ne voyez rien de tel"
    }
};

Identifier un objet

De la même manière que l’on avait écrit une méthode pour retrouver le verbe saisi par l’utilisateur, il va nous falloir identifier l’objet :

function matchObject(word, objectIndex) {
    return objects[objectIndex].synonyms.indexOf(word) > -1;
}

function getObjectByWord(word) {
    for(let key in objects) {
        if (matchObject(word, key)) {
            return key;
        }
    }
    
    return null;
}

Ainsi, on va pouvoir modifier notre méthode d’analyse syntaxique :

function parseCommand(commandString) {
    commandString = removeAccents(commandString).toUpperCase();

    let command = {
        "verb": null,
        "object": null
    };

    var words = commandString.split(/[ '_.,;:]/);

    if (words.length > 0) {
        command.verb = getVerbByWord(words[0]);
    }

    if (words.length > 1) {
        command.object = getObjectByWord(words[1]);
    }
    
    return command;
}

Au lieu d’un simple verbe, on gère maintenant une structure composée du verbe et de l’objet éventuel. On fait appel à notre méthode getObjectByWord avec le 2ème mot de la phrase, en considérant que l’utilisateur aura saisi quelque chose comme “examiner télévision”.

Pour que cela fonctionne, il faut également modifier la méthode executeCommand afin de lui ajouter la gestion de notre nouveau verbe :

function executeCommand(command) {
    if (command.verb) {
            switch(command.verb) {
                case "nord":
                case "sud":
                case "est":
                case "ouest":
                    if (objects[currentLocation][command.verb]) {
                        changeLocation(objects[currentLocation][command.verb]);
                        return;
                    }
                    break;
                case "examiner":
                    if(command.object != null) {
                        print(objects[command.object].description);
                        return;
                    }
                    break;
            }
        print(verbs[command.verb].default);
        return;
    } else {
        print("Je ne connais pas ce verbe");
        return;
    }
}

Au lieu de prendre juste un verbe, on prend maintenant une commande.

À présent, si vous tapez “examiner télévision”, vous devriez voir ceci :

Gestion du contexte

Si vous avez expérimenté un peu avec ce qui précède, vous avez peut-être constaté un léger souci : si vous vous rendez dans la cuisine et que vous examinez la télévision, vous la voyez toujours ! Comme notre héros est un peu vieux jeu, il n’a pas de cuisine ouverte, il ne devrait donc pas pouvoir regarder la télé depuis sa cuisine.

Pour remédier à cela, nous allons utiliser l’objet “context” dans la liste des objets :

let objects = {
    'context':{
        'synonyms': ["CONTEXT"],
        'name': "contexte",
        'location': null,
        'description': "Tout ce qui est visible actuellement par le joueur doit être dans le contexte, directement ou indirectement"
    },
    // [...]
}

L’objet contexte est un peu le champ de perception du joueur : tout ce qui est directement ou indirectement dans cet objet est “visible” par le joueur : le lieu courant, les objets qu’il contient, le joueur lui-même…

Il nous faut maintenant une fonction qui va chercher les objets dans un autre :

function findObjectIn(objectIndex, locationIndex) {

    if (!objectIndex) return false;

    if (objectIndex === locationIndex) {
        return true;
    }

    const containerIndex = objects[objectIndex].location;
    if (containerIndex) {
        return findObjectIn(containerIndex, locationIndex);
    }

    return false;
}

Cette fonction est “récursive”, c’est à dire qu’elle s’appelle elle-même. Elle prend l’objet recherché et essaie de remonter jusqu’à l’objet conteneur désigné par locationIndex.

Pour l’utiliser, ajoutez simplement les lignes suivantes à la fin de la fonction parseCommand, juste avant le return :

if (!findObjectIn(command.object, "context")) {
    command.object = null;
}

Ainsi, si l’objet n’est pas dans le contexte, on le remet à null, ce qui aura pour effet de déclencher le message par défaut de la commande “examiner” dans la fonction executeCommand().

Conclusion

Voilà qui clôture notre implémentation des objets et de la commande examiner. Retrouvez le code complet de ce tuto sur github (fichier interactions.html). Vous pouvez d’ores et déjà vous amuser à imaginer d’autres verbes.

Pour aller plus loin

Vous voilà maintenant avec un bon point de départ pour développer votre propre implémentation de moteur de jeu à parser. Voici différentes pistes d’amélioration :

  • Ajouter la gestion d’un inventaire et un verbe prendre (pour ça, il y a un exemple un peu plus avancé sur le projet github, dans le fichier index.html
  • Créer des objets conteneurs (et un verbe fouiller) qui masque leur contenu lorsqu’ils sont fermés (via la gestion du contexte)
  • Ajouter d’autres verbes selon vos besoins et votre imagination, comme parler, sentir, dormir…
  • Gérer une condition de victoire, des trophées, des points
  • Gérer le temps (en nombre de tours par exemple)
  • Mettre en place un système de sauvegarde (via un export de fichier ou dans le local storage du navigateur)

Bien sur, cela représente un travail assez important et il vous faudra beaucoup d’huile de coude pour atteindre le niveau des moteurs éprouvés comme inform 6, inform 7 ou Donjon FI. Alors, que vous cherchiez l’inspiration pour les fonctionnalités de votre parser maison ou que vous souhaitiez apprendre à utiliser un outil complet déjà prêt, ça vaut la peine de s’intéresser à ces outils.