Zen Lisp #1 : Découverte de trivial-gamekit

Mon initiation à Lisp au travers de l’écriture d’un client pour jeu en ligne ; parce que pourquoi pas?

Ce billet a été originalement posté sur mon blog, en anglais. Sa vignette est le logo du site common-lisp.net, sous licence CC BY-SA 4.0.

Cela fait un moment déjà que j’ai envie de (ré)apprendre un dialecte Lisp. Par curiosité. Parce que le langage conserve une très bonne réputation. Parce qu’un jour, il faudra peut-être que je m’attelle à faire des plugins Emacs digne de ce nom pour pijul. Dans le même temps, mon petit serveur de jeu 2D en ligne commence à marchotter. Il n’en fallait pas plus pour que mon cerveau, décidément un peu joueur, ne se décide à mixer les deux.

Voilà comment je me suis retrouvé avec l’objectif un peu saugrenu de faire un client en Lisp pour lykan. J’ai demandé sur Mastodon si quelqu’un connaissait un bon cadriciel pour écrire des jeux en Lisp : on m’a parlé (entre autres suggestions) de trivial-gamekit. J’ai choisi arbitrairement de me lancer dans l’aventure en l’utilisant. Pour l’occasion, je me suis dit que j’allais documenter mes trouvailles, en écrivant de temps en temps des articles sur le sujet. Vous être en train de lire la première occurrence de cette nouvelle série ; mon but ici est de vous montrer comment j’ai obtenu un programme Lisp, graphique et minimal.

Pour le moment, le projet s’appelle lysk. Il est hébergé sur mon serveur et j’utilise pijul comme gestionnaire de version. J’ai rassemblé tout le code présenté dans ce billet dans un gist pour ceux qui ne veulent pas s’embêter (et je les comprends) avec un VCS encore largement inconnu.

Installer Common Lisp, Quicklisp et trivial-gamekit

Le site internet de trivial-gamekit liste quatre prérequis à son utilisation. Deux sont liés à Lisp :

  • Quicklisp
  • SBCL or CCL

Commençons par le commencement. Quicklisp est une sorte de gestionnaire de paquet Common Lisp dont la peinture est encore fraîche (ce qui peut sembler surprenant, vu l’âge du langage :D ). C’était facile à découvrir, parce que la documentation de trivial-gamekit fournit gentiment un lien vers le site web de Quicklisp. Par contre, je ne savais pas ce qu’était SBCL ou CCL, donc j’ai mis de côté « histoire de voir ». J’ai installé clisp (packagé pour ArchLinux), mais impossible d’installer Quicklisp.

La raison ? Elle est simple : clisp est une implémentation de Common Lisp, mais ce n’est pas la seule. sbcl et ccl en sont deux autres ! Comme Quicklisp ne fonctionne pas avec clisp, je pouvais toujours essayer, ça ne pouvait pas marcher. Heureusement, sbcl est aussi packagé dans ArchLinux et comme promis, Quicklist s’installe beaucoup mieux avec une implémentation compatible. Je laisse les curieux aller voir la procédure d’installation de Quicklisp sur le site du projet. Elle est assez simple. Quicklisp fonctionne un peu comme Go, avec une notion de workspace (~/quicklisp par défaut, et les projets doivent être dans ~/quicklisp/local-projects).

Le cadriciel trivial-gamekit n’est pas dans la distribution par défaut. Heureusement, l’auteur en fournit une qui possède le paquet. On peut l’ajouter à la configuration de Quicklisp avec cette ligne de commande :

1
sbcl --eval "(ql-dist:install-dist \"http://bodge.borodust.org/dist/org.borodust.bodge.txt\")" --quit

Le http n’est malheureusement pas une erreur. Quicklisp ne supporte(ra) pas le protocole, mais il est prévu de laisser la possibilité aux mainteneurs de paquets de signer ces derniers. C’est quand même le strict minimum.

Nom de code : Lysk

La première chose que je regarde quand je veux apprendre un langage, c’est comment organiser un projet. De ce point de vue, trivial-gamekit m’a directement orienté vers Quicklisp.

Créer un nouveau projet pour Quicklisp est assez simple, même si je ne suis pas fan de ces histoires de workspace1. D’abord, il faut créer un répertoire pour le projet. Je vais appeler mon client lysk, pour le moment (faute de mieux).

1
mkdir ~/quicklisp/local-projects/lysk

Ensuite, il faut créer un fichier qui décrit notre projet et définie notamment le ou les paquets qu’il propose. C’est un peu le Cargo.toml de Rust, ou le .cabal de Haskell. Dans le cas de Quicklisp/Common Lisp, l’extension est asd.

Voilà le contenu de lysk.asd :

1
2
3
4
5
6
7
8
9
(asdf:defsystem lysk
  :description "Lykan Game Client"
  :author "lthms"
  :license  "GPLv3"
  :version "0.0.1"
  :serial t
  :depends-on (trivial-gamekit)
  :components ((:file "package")
               (:file "lysk")))

La plupart des champs sont documentés rien que par leur nom. Je n’ai eu de mal à comprendre que :serial. Cette option spécifie en fait que chaque fichier qui constitue notre package dépend de celui qui précède. Dans notre cas, donc, lysk.lisp dépend de package.lisp. On aurait pu le spécifier à la main :

1
(:file "lysk" :depends-on "package")

Le fichier package.lisp nous permet de définir l’interface du paquet, en quelque sorte. Voilà son contenu :

1
2
3
(defpackage :lysk
  (:use :cl)
  (:export run app))

De ce que j’ai comprs, :cl est la bibliothèque standard de Common Lisp ; la ligne suivante nous permet de dire que l’on exporte deux symboles : run et app.


  1. À ce sujet, il existe des méthodes pour assouplir un peu la chose (par exemple, créer des liens symboliques dans le workspace vers les projets, qui peuvent être ailleurs dans l’arborescence. 

Un programme graphique en Lisp

Mon but désormais est d’obtenir un programme minimal, qui crée une fenêtre, se met en plein écran et s’arrête de lui-même quand l’utilisateur ou l’utilisatrice relâche le bouton gauche de sa souris. Cela peut paraître peu, mais je rappelle que je suis novice en Lisp. À noter qu’en plus de cela, j’ai dû remonter un bug à l’upstream de trivial-gamekit ! Ce n’est pas forcément rassurant, mais dans le même temps, le problème a été résolu en moins d’une demi-heure et l’auteur a été très bienveillant avec moi quand j’ai rejoint le channel #lispgames sur Freenode.

Bref, le contenu du fichier lysk.lisp est très court, mais permet d’obtenir le comportement attendu :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(cl:in-package :lysk)

(gamekit:defgame app () ()
                 (:fullscreen-p 't))

(defmethod gamekit:post-initialize ((app app))
  (gamekit:bind-button :mouse-left :released
                       (lambda () (gamekit:stop))))

(defun run ()
  (gamekit:start 'app))

La fonction run, exportée par notre package, permet de lancer effectivement notre « jeu ». Depuis un terminal, il suffit d’écrire :

1
2
3
$ sbcl # lance le REPL lisp
* (ql:quickload :lysk)
* (lysk:run)

Créer un exécutable

Lisp a l’air très tourné vers le REPL-driven development ; c’est encore du chinois pour moi. Tout ce que je sais, c’est que si un jour mon client doit être utilisé par des joueurs, je ne vais par leur demander de lancer leur Lisp. Il me faut un moyen de leur donner un exécutable. « Fort heureusement », trivial-gamekit fournit une fonction pour ça. En suivant les conseils de l’auteur du cadriciel, je me suis retrouvé à définir un second package, dont le but est de fournir une fonction qui créer l’exécutable.

Première étape : modifier le fichier lysk.ads :

1
2
3
4
5
6
7
8
(asdf:defsystem lysk/bundle
  :description "Bundle the Lykan Game Client"
  :author "lthms"
  :license  "GPLv3"
  :version "0.0.1"
  :serial t
  :depends-on (trivial-gamekit/distribution lysk)
  :components ((:file "bundle")))

La fonction deliver est implémentée dans le fichier bundle.lisp :

1
2
3
4
5
6
7
8
(cl:defpackage :lysk.bundle
  (:use :cl)
  (:export deliver))

(cl:in-package :lysk.bundle)

(defun deliver ()
  (gamekit.distribution:deliver :lysk 'lysk:app))

Pour obtenir l’exécutable, on utilise cette fonction depuis le REPL de sbcl. Une bonne manière de faire est d’utiliser l’option --eval pour ne pas avoir à le faire à la main :

1
sbcl --eval "(ql:quickload :lysk/bundle)" --eval "(lysk.bundle:deliver)" --quit

Tada !


Voilà qui conclue la première étape de mon voyage à la conquête du Lisp. Prochaine étape ? Installer slime, un plugin Emacs pour avoir un mode Lisp qui patate.

Merci d’avoir lu !

En bonus, le Makefile que j’ai écris pour ne plus avoir à lancer manuellement lysk depuis le REPL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
run:
        @sbcl --eval "(ql:quickload :lysk)" \
              --eval "(lysk:run)"

bundle:
        @echo -en "[ ] Remove old build"
        @rm -rf build/
        @echo -e "\r[*] Remove old build"
        @echo "[ ] Building"
        @sbcl --eval "(ql:quickload :lysk/bundle)" --eval "(lysk.bundle:deliver)" --quit
        @echo "[*] Building"

.PHONY: bundle run

9 commentaires

Petites coquilles :

  • J’ai ressemblé tout le code présenté dans ce billet dans un gist
  • Heureusement, sbcl est aussé packagé dans ArchLinux
  • s’arrête de lui-même que l’utilisateur ou l’utilisatrice relâche le bouton gauche

Je trouve toujours ce genre de retours intéressants même si je ne ferais probablement jamais de Lisp et encore moins un jeux avec.

Merci — et désolé ! — pour la remontée de coquilles. Je mettrai à jour quand le billet aura quitté la page d’accueil. :)

Content que le contenu t’aie plu. Je compte en refaire assez régulièrement, ça me fera du bien pour me changer les idées entre deux séances d’écriture de manuscrit.

+0 -0

Mon niveau en programmation est assez faible, et je ne connais pas vraiment lisp, même si je suis intéressé par le sujet.

Je note plusieurs choses après avoir lu ce texte.

Premièrement, selon le site de quicklisp, clisp est compatible

"Quicklisp is easy to install and works with ABCL, Allegro CL, Clasp, Clozure CL, CLISP, CMUCL, ECL, LispWorks, MKCL, SBCL, and Scieneer CL, on Linux, Mac OS X, and Windows."

À quel niveau as-tu eu des problèmes ?

Deuxièmement, J’ai essayé de reproduire les étapes expliquées dans ce billet sans succès.

Je continue d’obtenir une erreur en faisant (ql:quickload :lysk) :

1
2
3
debugger invoked on a QUICKLISP-CLIENT:SYSTEM-NOT-FOUND in thread
#<THREAD "main thread" RUNNING {10005585B3}>:
  System "lysk" not found

J’ai donc commencé à relire les intructions et tu ne donnes à aucun moment le contenu du fichier package.lisp, pourtant il est écrit (:file "package") dans le fichier lysk.ads mais juste en-dessous en exposant l’autre méthode tu écris (:file "lysk" :depends-on "project")

Que faut-il comprendre ? J’imagine que tu as changé le nom de certain fichiers. Donc j’ai essayé de modifier package par project, puis de refaire (ql:quickload :lysk). J’obtiens la même erreur. J’ai aussi essayé de lancer le repl depuis mon dossier lysk et depuis mon home, même résultat.

Je me demandais si tu savais d’où peut venir le problème. Si tu penses que ce n’est pas le bon endroit pour en parler je suis prêt à changer de canal de communication.

Troisièmement, je trouve étrange qu’il n’y ait pas d’historique dans le repl de sbcl. sbcl qui date des années 2000 n’est pas si vieux (surtout comparé aux premiers lisp), et pourtant, même bash possède un historique (grâce à readline). Pour un langage repl-driven, je trouve ça bizarre que personne n’ait essayé d’améliorer l’existent (même si tout le monde utilise emacs j’imagine).

Merci pour tes retours !

Premièrement, selon le site de quicklisp, clisp est compatible

(…)

À quel niveau as-tu eu des problèmes ?

C’est trivial-gamekit qui n’est pas compatible avec clisp, en fait. Ce n’était peut-être pas clair dans mon billet, désolé !

Je continue d’obtenir une erreur en faisant (ql:quickload :lysk)

Est-ce que tu as bien placé ton projet dant ~/quicklisp/local-projects/ ?

Bien d’accord pour sbcl. J’ai encore un peu de mal à m’en servir à cause de ça, il faut que j’arrive à prendre slime en main du coup…

+0 -0

C’est trivial-gamekit qui n’est pas compatible avec clisp, en fait. Ce n’était peut-être pas clair dans mon billet, désolé !

Je m’en suis rendu compte après coup en lisant rapidement le blog qui présente trivial-gamekit

Est-ce que tu as bien placé ton projet dant ~/quicklisp/local-projects/ ?

Oui.

un pwd me donne /home/tamwile/quicklisp/local-projects/lysk

et un tree :

1
2
3
4
.
├── lysk.ads
├── lysk.lisp
└── project.lisp

à mon avis le problème est au niveau de mon quicklisp, parce que j’ai essayé le code de base de trivial-gamekit en le tapant à la main dans le repl et une fenêtre s’affiche bien.

+0 -0

j’ai réussi à aller au bout des étapes, malgré les imprécisions et erreurs du billet.

  • l’extension de fichier est .asd et pas .ads comme écrit de multiple fois ici
  • si votre fichier system-index.txt ne s’est pas mis à jour automatiquement, rajouter une ligne lysk/lysk.asd dedans
  • entre project.lisp et package.lisp, il faut choisir

c’était les erreurs les plus gênantes que j’ai rencontrées, même si le fait que les codes donnés ici et dans le gist soient différents m’a aussi pas mal embrouillé.

Oups. Désolé ! Je vais corriger les erreurs le plus rapidement possible et je ferais en sorte de mieux gérer les prochaines fois (notamment, ne pas faire un gist, mais bien pointer vers des projets).

Merci pour tes retours et tes remontées !

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte