Alt-F4 #61 - Draftsman : Un module Python pour la création de plans. 27-05-2022
traduit par bev, Firerazer
Pour le numéro d’Alt-F4 de cette semaine, nous retournons à nos racines en tant que successeur spirituel des FFF en nous plongeant dans un sujet technique. Pour cela, redruin1 nous présente sa toute dernière invention : Factorio Draftsman. Bien sûr, il y a déjà eu bien d’autres projets visant à construire une bibliothèque pour générer des plans pour le jeu, mais celui-ci essaie d’être le nouveau standard absolu. Motivations, détails techniques, et quelques projets amusants réalisés avec lui. Tout cela, et plus encore, c’est cette semaine !
Draftsman redruin1
Il y a quelques mois, j’ai décidé de m’essayer à la création d’une usine qui se développe automatiquement dans Factorio. Après avoir vu un certain nombre d’exemples impressionnants, j’ai eu envie de m’attaquer à ce problème. J’avais déjà eu un aperçu de la logique et de la façon dont l’usine devait se contrôler elle-même, ainsi que de grandes idées sur les choses impressionnantes que je pouvais lui faire faire. Le seul problème était que je n’avais jamais utilisé les circuits logiques auparavant, et que j’avais l’intention de les utiliser pour la prise de décision.
Ce n’est pas un problème, il suffit de créer un monde brouillon, de passer en éditeur et de commencer à jouer !
Je me suis peut-être un peu emporté.
Voici une unité centrale que j’ai faite. C’est la septième révision (il me semble). Elle possède une ROM, une RAM, une pile, 256 registres, plus de 40 instructions, des points d’arrêt et des étapes de code, des interruptions matérielles et logicielles, ainsi qu’une interface de circuit généralisée pour interagir avec d’autres machines.
Ironiquement, je ne suis pas vraiment ici pour parler de tout ça. C’est juste une introduction.
Je commençais la deuxième révision de l’ordinateur et je voulais une ROM plus compacte. J’ai conçu le modèle ci-dessous, qui permet de stocker de vrais nombres de 32 bits et peut conserver 4 KiB de données par ligne :
La ROM est très dense, mais elle fonctionne sur un système où chaque valeur est scindée et stockée en deux nombres de 16 bits, qui sont ensuite recombinés à la sortie. Les ROMs sont normalement fastidieuses à créer, en encodant manuellement chaque signal que vous voulez, un par un, mais cette conception était encore plus complexe. Je devais maintenant diviser, faire un ET bit à bit, faire un décalage à droite bit à bit, et remplir non pas un signal, mais deux, à deux endroits spécifiques différents, en m’assurant qu’ils avaient tous les deux la bonne valeur et le bon type de signal. Inutile de dire qu’il allait être pénible de définir des dizaines de valeurs, sans parler des centaines, ou des milliers de valeurs que la machine était capable de stocker.
La solution ? Prendre un ordinateur pour le faire à ma place. Il peut digérer cette tâche bien mieux que je ne le pourrai jamais, et il peut aussi le faire bien plus rapidement. La fonction d’importation de chaîne de caractères de plan de Factorio peut prendre n’importe quelle chaîne correctement formatée. Tout ce que j’avais à faire était de créer cette chaîne de caractères selon mes spécifications avec les données que je voulais et je pouvais simplement la coller là où c’était nécessaire.
Ce concept n’est pas nouveau, loin de là. Même une recherche rapide permet de trouver de nombreux exemples d’utilisation pratique : le module NPM factorio-blueprint
de demipixel, le compilateur de justarandomgeek pour son ordinateur massif en circuits logiques, un langage d’instruction générique en circuits logiques de Jobarion, des convertisseurs d’images en plans, etc. La liste est longue.
Avec tous ces exemples, j’espérais pouvoir trouver des similitudes avec l’un d’entre eux et m’en servir comme base de référence pour ma solution afin d’éviter de “refaire” le même parcours. Mais quelque chose me préoccupait dans les solutions proposées : elles avaient toutes des problèmes !
- De nombreuses implémentations étaient hautement spécifiques au domaine pour lequel elles avaient été écrites. Un script de compilateur d’ordinateur en circuits logiques n’allait pas être portable pour de nombreuses autres utilisations que la compilation de code pour un ordinateur spécifique.
- Il leur manquait un langage unifié. Beaucoup étaient écrites en Lua, certaines en Python, une autre en JavaScript, une autre en C++, etc. Cela signifie que chacun devait écrire ses propres implémentations pour les mêmes opérations, au lieu de simplement avoir quelqu’un qui écrive l’implémentation dont il a besoin et la mette à la disposition des autres.
- Beaucoup d’entre elles ont été écrites pour des versions de Factorio qui sont maintenant largement dépassées.
- La documentation pour un grand nombre de ces modules était éparse et sporadique, ce qui rebute les utilisateurs comme moi qui veulent savoir ce dont le module est capable avant d’investir du temps pour apprendre à l’utiliser.
Fondamentalement insatisfait des options proposées, je me suis résigné à mon sort. J’allais devoir suivre les traces de tous ceux qui m’avaient précédé et développer ma propre mise en œuvre en partant de zéro. Quelle tâche ardue !
En un après-midi, j’ai créé un prototype de script qui faisait exactement ce dont j’avais besoin et qui fonctionnait parfaitement. Il utilisait un système de modèles et il a fallu moins d’une semaine pour le terminer complètement.
Mais ensuite, j’ai commencé à me poser des questions. Il ne serait pas si difficile de mettre à jour un module comme factorio-blueprint
pour un Factorio plus récent, et je parie que je pourrais trouver un moyen d’extraire automatiquement les données de Factorio lui-même, de sorte que vous n’auriez jamais à mettre à jour manuellement les fichiers sources à chaque version. Puis j’ai commencé à avoir des idées ambitieuses sur la façon dont je pourrais écrire de la documentation pour la clé complexe et largement non documentée control_behavior
dans les entités, ou ajouter des types d’entités personnalisés pour créer et manipuler des groupes d’entités, ou même, Dieu m’en garde, ajouter un support pour mods dans le mélange. C’était il y a trois ou quatre mois.
Bref, voici un module Python que j’ai réalisé.
Je vous présente : factorio-draftsman
Draftsman est un module Python pour créer, modifier, importer et exporter toutes sortes de chaînes de plans de Factorio. Le module vous permet de créer et de concevoir des plans par le biais d’un programme, afin d’aider au développement de plans fastidieux et répétitifs qui seraient insoutenables à créer à la main, un peu comme le problème que j’ai rencontré décrit ci-dessus. Draftsman tente de résoudre tous les défauts des implémentations existantes de plans dans Factorio :
- Draftsman fait tout. Tous les types d’entités sont reconnus, des répartiteurs aux bras haute capacité. Si vous pouvez le faire dans le jeu, vous pouvez le faire avec Draftsman. Il vous permet ainsi de vous concentrer sur le seul problème que vous essayez réellement de résoudre.
- Draftsman est uni-langage. Ecrit en Python, cela le rend exceptionnellement facile à installer, simple à utiliser, et donne à l’utilisateur l’accès à l’intégralité du vaste index de la bibliothèque Python. Il y a de fortes chances que vous puissiez le faire en Python avec Draftsman, quelle que soit la chose, liée à Factorio, que vous êtes en train de faire.
- Draftsman est facile à utiliser. Conçu dès le départ pour être simple et (le plus important) auto-documenté, Draftsman vous permet de manipuler des plans et des entités par des attributs et des méthodes qui se complètent automatiquement.
- Draftsman est bien documenté. Chaque fonction, méthode, attribut, et classe est documentée et reliée à son site Readthedocs. De plus, des tutoriels et du matériel supplémentaire sont fournis, ainsi qu’un grand nombre d’exemples de programmes pour aider à illustrer le fonctionnement de Draftsman.
- Draftsman est stable. Une suite rigoureuse de tests assure que Draftsman se comporte de manière prévisible et correcte (ou au moins suffisamment correcte), avec une couverture de code de 100%. Draftsman a été testé pour fonctionner sur les dernières versions de Python 2 et 3, et est compatible avec toutes les versions de Factorio supérieures à 1.0, avec les métriques pour le prouver.
- Draftsman est descriptif. Draftsman applique la philosophie de “Factorio-sécurité”, ce qui signifie que si un plan entraîne une erreur à l’importation, celle-ci est signalée. Draftsman essaye également de faire respecter la “Factorio- exactitude”, ce qui signifie que les valeurs qui ne sont pas cassées, mais qui sont autrement absurdes, feront apparaître des avertissements. Les erreurs et les avertissements sont explicites, de sorte que tout problème avec votre script peut être compris et résolu en quelques secondes.
-
Draftsman est proche de la source. Draftsman base toutes ses données sur le référentiel
factorio-data
de Wube, ce qui signifie que toutes les entités sont exactement comme vous vous y attendez dans le jeu, sans aucune incohérence. Cela maintient Draftsman à jour, facilite la mise à jour de Draftsman pour les futures versions de Factorio, et permet au contrôle de version de suivre les changements entre Draftsman et Factorio, si une rupture devait se produire. - Draftsman supporte les mods. Draftsman émule directement le cycle de vie des données dans Factorio, ce qui signifie que le même processus de chargement qui se produit lorsque vous lancez le jeu est reproduit grâce à une seule fonction dans Draftsman. En plus d’assurer une fidélité absolue à Factorio, cela veut aussi dire que les prototypes personnalisés issus de mods sont accessibles depuis Draftsman, comme s’il s’agissait de toute autre entité classique.
Draftsman a des classes personnalisées conçues spécialement autour de chaque entité et prototype, et est conçu pour fonctionner de manière aussi transparente que possible avec les chaînes de plans et autres logiciels. Draftsman convertit ces prototypes quand vous importez et exportez des chaînes de plans automatiquement, sans aucune étape supplémentaire. Vous pouvez importer une chaîne de plans de Factorio en tant qu’objet Blueprint
, faire tous les changements que vous voulez, et ensuite exporter cet objet Blueprint
en tant que chaîne. Ou bien, vous pouvez simplement créer un tout nouveau Blueprint
à partir de zéro. Draftsman est conçu pour travailler autour de vous, pas pour que vous travailliez autour de lui.
Draftsman a aussi une aide pour les objets personnalisés “EntityLike”, plus particulièrement les objets “Group”, qui vous permettent de créer des constructions personnalisées qui peuvent être insérées dans les plans pour aider à la clarté et au compartimentage. Par exemple, vous pouvez faire une conception pour un bloc de fonderie dans un Groupe
, et ensuite vous pouvez placer ce bloc autant de fois que vous le voulez, où vous le voulez, tourné ou inversé, agissant en fait comme un outil copier-coller dans Draftsman.
Dans un souci de brièveté, je ne vais pas entrer dans les détails du module ou de son fonctionnement. J’ai passé beaucoup de temps à rédiger une documentation à cet effet. Je vais plutôt montrer quelques objets que j’ai réalisés jusqu’à présent, ainsi que des objets susceptibles d’être réalisés à l’avenir, pour essayer d’illustrer exactement pourquoi j’ai passé tout ce temps à le fabriquer au départ.
Taille automatique des piles d’objets
Souvent, vous voulez déterminer le stockage dont vous avez besoin pour une quantité spécifique d’un objet. Cependant, le stockage dans Factorio est basé sur des emplacements, et non sur des quantités, de sorte que la quantité de stockage dépend en fait non seulement de la quantité que vous essayez de stocker, mais aussi de la taille de la pile de cet objet. Vous pouvez concevoir un mécanisme en circuits logiques pour déterminer le nombre d’emplacements dont vous avez besoin pour n’importe quel objet en entrée, mais vous auriez besoin d’un grand tableau d’émetteurs de constantes documentant les tailles des objets. C’est ennuyeux à faire, et facilement brisé si un nouvel objet est ajouté ou si la taille de la pile est modifiée. Il est clair que pour une tâche aussi simple et répétitive, un script est mieux adapté :
# Créez une grille N x 5 cellule d’émetteurs de constantes connectés, avec chaque objet et sa taille d’empilement.
from draftsman.blueprintable import Blueprint
from draftsman.constants import Direction
from draftsman.data import items
from draftsman.entity import ConstantCombinator
COMBINATOR_HEIGHT = 5
def main():
blueprint = Blueprint()
signals_added = 0
signal_index = 0
combinators_added = 0
x = 0
y = 0
combinator = ConstantCombinator(direction=Direction.SOUTH)
# Itérer sur chaque objet dans l’ordre :
for item in items.raw:
# Ignorer les objets/entités cachés
if "flags" in items.raw[item]:
if "hidden" in items.raw[item]["flags"]:
continue
# Garder la trace du nombre de signaux que nous avons traversés.
signals_added += 1
# Écrire le signal de la taille de la pile
stack_size = items.raw[item]["stack_size"]
combinator.set_signal(signal_index, item, stack_size)
signal_index += 1
# Une fois qu’on a dépassé le nombre de signaux qu’un émetteur de constantes peut contenir, on le place et on remet à zéro.
if signal_index == combinator.item_slot_count:
# Ajoutez l’émetteur de constantes au plan
combinator.id = "{}_{}".format(x, y)
blueprint.entities.append(combinator)
# Réinitialiser l’émetteur de constantes
combinators_added += 1
y = combinators_added % COMBINATOR_HEIGHT
x = int(combinators_added / COMBINATOR_HEIGHT)
combinator.set_signals(None) # Réinitialiser les signaux
combinator.tile_position = (x, y)
signal_index = 0
# Ajouter le dernier émetteur de constantes s’il est partiellement plein
if len(combinator.signals) > 0:
combinator.id = "{}_{}".format(x, y)
blueprint.entities.append(combinator)
# Ajouter des connexions vers chaque voisin
for cx in range(x):
for cy in range(COMBINATOR_HEIGHT):
here = "{}_{}".format(cx, cy)
right = "{}_{}".format(cx + 1, cy)
below = "{}_{}".format(cx, cy + 1)
try:
blueprint.add_circuit_connection("red", here, right)
except KeyError:
pass
try:
blueprint.add_circuit_connection("red", here, below)
except KeyError:
pass
print("Total number of item signals added:", signals_added)
print(blueprint.to_string())
if __name__ == "__main__":
main()
Ce script est concis et facile à suivre, mais la chose vraiment étonnante au sujet de Draftsman est que ce script est complètement dynamique et peut s’adapter à n’importe quelle version de Factorio que vous jouez. Les nouveaux objets, les objets supprimés, ou les changements de taille de pile, quel que soit l’auteur du changement, sont corrigés automatiquement avec exactement le même code. À titre d’illustration, la photo en haut à gauche montre ce qui est généré par l’exécution du script pour un jeu classique, en haut à droite un jeu avec le modpack de révision de taille moyenne Space Exploration, et en bas un méga-modpack Bob + Pyanodon :
Ceci ne s’applique pas seulement aux objets. Toutes les entités, instruments, signaux, recettes, modules, et tuiles sont extraits du processus de chargement émulé et ensuite stockés dans Draftsman pour une utilisation ultérieure. N’importe quel script peut être conçu pour être complètement flexible à travers ces catégories : des instruments supplémentaires dans un nouveau haut-parleur programmable, de nouveaux types de modules seulement dans certaines machines, des listes complètes de signaux virtuels pour les affectations de signaux, etc…, tous sont interprétés correctement par Draftsman. Sauvegarder les données de la configuration actuelle des mods en interne pour plus tard, cela signifie également que vous ne devez mettre à jour les données qu’une seule fois, chaque fois que vous changez les mods que vous utilisez.
Convertisseur d’images en plans
C’est quelque chose que j’ai fait sur un coup de tête. Il utilise la librairie d’images Pillow
pour charger une image et la convertir en un plan destiné à être visible depuis la vue de la carte, le tout en moins de 150 lignes de code :
De nombreuses améliorations pourraient y être apportées :
- Le tramage n’est implémenté que sur les tuiles 1x1. J’ai eu du mal à ajuster l’algorithme aux entités multi-tuiles.
- Les entités multi-tuiles ne parviennent pas non plus à ajuster la propriété de la métrique d’erreur. La mise en place de la mise en couleur ne fonctionne pas aussi bien lorsque certains pixels sont de tailles différentes.
- Certaines entités multi-tuiles ont des rotations différentes. Une meilleure implémentation vérifierait quelle orientation produit le moins d’erreur avant de les placer, au lieu de les placer uniquement avec l’orientation par défaut.
- Les couleurs elles-mêmes sont codées en dur. Ce serait bien de pouvoir les charger dynamiquement à partir du jeu, surtout avec les couleurs de cartes modifiées…
Malgré ces défauts, le résultat est assez bon pour une implémentation rapide et simple. Cela montre que Draftsman est assez polyvalent pour des buts plutôt diversifiés.
Résurrection du lecteur vidéo de Factorio
Il y a quelques temps, en cherchant des projets à utiliser comme exemples pour cet article, je suis tombé sur le classique Factorio - Sandstorm. Un projet parfait à adapter. Ou, il l’aurait été, si j’avais pu charger la carte dans le jeu ! La version originale de la carte était encore datée de la tendre version 0.14.20
. De plus, un grand nombre des migrations qui ont eu lieu sur une si longue période ont cassé la fonctionnalité du script permettant de convertir les images en données cartographiques, ainsi que certaines fonctionnalités de la carte elle-même, de sorte qu’il ne suffisait pas de télécharger une ancienne version de Factorio et de la contraindre pour faire revivre l’ancienne sauvegarde. Par exemple, le script build.lua
en charge de l’encodage appelait encore les objets automation-science-pack
et logistic-science-pack
, science-pack-1
et science-pack-2
. Cela devrait vous donner une idée de l’ancienneté de la carte !
Cela m’a troublé, de voir une telle pièce emblématique de l’histoire de Factorio tomber en désuétude. J’ai donc pris le temps de corriger les problèmes et de le migrer jusqu’à la version 1.1.57 :
Changer le script de construction pouvait être fait à la main, mais le signal raw-wood
que la machine utilisait en interne n’existe plus dans le Factorio moderne. Afin de le corriger, j’ai remplacé toutes les occurrences du signal dans la carte par artillery-wagon
(puisque je savais qu’il serait unique), et cette mise à jour de la carte a été faite avec un script exécutant du code de Draftsman. J’ai également ajouté un certain nombre d’autres scripts pour extraire les images de la source, ainsi que pour prendre des captures d’écran du résultat et les assembler dans une vidéo de sortie, ce qui est assez facile en Python. J’ai utilisé ces scripts pour produire le résultat ci-dessus.
Je me suis également penché sur l’idée d’utiliser Draftsman pour créer une version du script de construction basée sur un plan, au lieu de la méthode originale basée sur la console. Cela vous permettrait de voir exactement quelles parties de la mémoire vous modifiez en plaçant un plan aligné sur une grille au-dessus d’elles, et vous permettrait également d’étendre la mémoire de la machine en ajoutant simplement plus de blocs (la mémoire fournie sur la carte est seulement suffisante pour 4800 images). Je me suis dit que c’était moins important que de mettre à jour ce qui était là à l’origine, surtout quand je savais que le code qui était déjà là fonctionnait. Il pourrait y avoir d’autres préoccupations : la quantité de données encodées est assez importante, surtout pour les chaînes de caractères du plan qui, je pense, sont moins denses que les scripts de la console, qui sont si volumineux qu’ils sont transférés dans des fichiers texte lors de la construction. J’ai pensé que la détermination de la faisabilité d’une méthode de plan était possible jusqu’à ce que j’aie un peu plus de temps libre.
Pour plus d’informations sur ce qui a été modifié ou ajouté, ainsi que sur la sauvegarde mise à jour, vous pouvez consulter ma fourche Github ici.
Et ensuite ?
Bien que j’aie fait un certain nombre de choses avec ce module, le but de celui-ci était de permettre à quiconque de faire facilement tout un tas d’autres choses également. Certaines idées que j’aimerais faire, mais que je n’ai pas encore faites, je les reporterai ici pour alimenter la réflexion :
-
De nombreux compilateurs d’ordinateurs en logique sont écrits via un script. Peut-être que quelqu’un pourrait concevoir un équivalent de LLVM, un compilateur générique, où vous pouvez lui dire quelles instructions votre CPU peut exécuter et lui faire compiler des langages établis de haut niveau comme le C. Peut-être que vous pourriez même utiliser LLVM lui-même ?
-
Tirer parti du comportement des cas limites pour des problèmes d’optimisation spécifiques. Saviez-vous que vous pouvez configurer les entités pour demander n’importe quel objet, et pas seulement des modules ? Vous ne pouvez demander des objets qu’au moment de leur construction initiale, ce qui limite quelque peu leur utilité, mais les robots répondront à la demande, ce qui vous permettra de “relancer” la production dans certains cas. Avec quelque chose comme le mod Recursive Blueprints, en plaçant des machines d’assemblage avec des demandes d’objets, puis en les déconstruisant lorsqu’elles ont fini de consommer leurs ingrédients, vous pouvez théoriquement construire une usine entièrement automatisée avec seulement des robots de construction. Peut-être que la Micro Factory peut être optimisée davantage ? Ou un nouvel objectif pourrait être d’avoir le plus petit nombre total d’entités pour lancer une fusée sans intervention de l’utilisateur ?
Un cas d’utilisation peut-être plus généralement applicable est ce plan, une tourelle à un seul canon qui demande automatiquement 200 munitions rouges au réseau logistique lorsqu’elle est placée. C’est particulièrement utile dans les efforts offensifs sur les grands nids. Vous pouvez spammer les tourelles autour de vous et les robots de votre inventaire créeront les tourelles et livreront les munitions automatiquement, ce qui vous permet de vous concentrer sur le fait de ne pas vous dissoudre dans la bave de cracheurs.
-
Problèmes d’acheminement : passer par un ensemble d’avant-poste et utiliser un algorithme pour les relier automatiquement afin de minimiser la distance, le nombre de traversées, etc. Peut-être pourriez-vous le faire lire à partir des fichiers de sauvegarde eux-mêmes ?
-
Les dispositifs de circuits logiques sont généralement très complexes et peu intuitifs à décoder pour le profane, comme je l’ai constaté lors de mon exploration approfondie dans l’introduction. Cela est dû à la densité des circuits, aux opérations cachées de chaque entité, au fouillis de fils généré sans possibilité de les connecter proprement dans de petites zones, et à l’impossibilité de donner des étiquettes aux circuits logiques ou aux fils qui les connectent. Ce serait bien d’avoir un logiciel capable de prendre un plan en entrée et de permettre à l’utilisateur de faire tout ce qui précède pour créer une documentation facilement décodable et compréhensible pour des montages de circuits complexes. J’adorerais utiliser une telle chose pour créer des schémas de circuit vectorisés pour mon ordinateur. Quand je l’aurai terminé. Éventuellement.
-
Utiliser la satisfaction de contraintes pour concevoir des plans. J’ai expérimenté cela dans le passé, et cela devrait être possible, en supposant que vous puissiez réduire la complexité temporelle de
O(MFG)
. Si je décide d’écrire un autre article pour Alt-F4, ce sera peut-être sur ce sujet. -
Vous pouvez également utiliser les réseaux neuronaux pour l’objectif précédent. C’est Python après tout ! Je me demande si vous pourriez vraiment obtenir des plans utilisables de cette façon. Peut-être que si vous les formiez sur l’ensemble de Factorio Prints…
Avec un peu de chance, cela devrait donner aux plus créatifs d’entre vous quelques idées intéressantes. Et peut-être, juste peut-être, lorsque quelqu’un aura besoin d’écrire un script pour un problème spécifique afin de gagner du temps, il pourra effectivement gagner du temps au lieu de passer trois à quatre mois à écrire un module Python à partir de rien.
Eh, c’était quand même assez amusant à faire. Éducatif, aussi.
Ah, enfin. Retour au vrai travail.
Contribuer
Comme toujours, nous attendons vos contributions pour les Alt-F4, que cela soit par la soumission d’un article ou en aidant pour les traductions. Si vous avez quelque chose d’intéressant en tête que vous souhaitez partager avec la communauté, vous êtes au bon endroit. Si vous n’êtes pas sûr, nous serons heureux de vous aider en discutant structure, contenu et idées. Donc si vous voulez vous impliquer dans les Alt-F4, rejoignez-le Discord pour ne rien rater !