Boîte à outils
Ce qu'un script calcule, convertit ou met en forme entre deux appels au jeu. Rien ici ne parle au serveur : ce sont les fonctions qui vous évitent d'écrire import("math"), import("strconv") ou import("encoding/json") en haut de chaque fichier.
Calculer
| Fonction | Rend | |
|---|---|---|
Min(x, y) | 1 : un nombre | Le plus petit des deux. |
Max(x, y) | 1 : un nombre | Le plus grand des deux. |
Abs(x) | 1 : un nombre | La valeur absolue. |
Ceil(x) | 1 : un nombre | L'entier au-dessus. |
Floor(x) | 1 : un nombre | L'entier en dessous. |
Round(x) | 1 : un nombre | L'entier le plus proche. |
Pow(x, y) | 1 : un nombre | x à la puissance y. |
Sqrt(x) | 1 : un nombre | La racine carrée. |
Random(min, max) | 1 : un entier | Un entier tiré au hasard, bornes comprises. |
Les huit premières travaillent en nombres à virgule. Vous pouvez leur passer des entiers sans y penser (Min(3, 5) s'écrit bien ainsi), mais ce qu'elles rendent est un flottant, et c'est ce qui surprend à l'affichage.
Round arrondit en s'éloignant de zéro : Round(2.5) donne 3 et Round(-2.5) donne -3. Beaucoup de langages arrondissent 2.5 vers 2 ; pas celui-ci.
Le piège est à l'affichage, pas au calcul
Print(Pow(10, 7)) // ✗ 1e+07, et non 10000000
Print(Itoa(Pow(10, 7))) // ✓ 10000000 : passez par Itoa pour afficher
Printf("%d", Min(3, 5)) // ✗ %!d(float64=3) : %d ne prend pas un flottant
Printf("%.0f", Min(3, 5)) // ✓ 3
Le calcul lui-même ne souffre de rien : Min(3, 5) + 1 vaut 4, l[Floor(1.9)] indexe bien la deuxième case, for i = 0; i < Min(3, 5); i++ boucle trois fois, et "total " + Min(3, 5) écrit total 3. Seuls Print sur un grand nombre et Printf avec %d trahissent le type réel.
Random
Si vous inversez les bornes, la fonction ne se plaint pas et rend la première : Random(10, 1) vaut 10, toujours. Un tirage qui donne obstinément la même valeur vient presque toujours de là. Random(5, 5) vaut 5, sans surprise.
Convertir un nombre et une chaîne
| Fonction | Rend | |
|---|---|---|
Atoi(texte) | 1 : un entier | Lit un entier dans un texte. |
Itoa(n) | 1 : une chaîne | Écrit un entier en texte. |
Dotify(n) | 1 : une chaîne | Met en forme un grand nombre à la façon du jeu : 1000000 donne 1.000.000. |
Bytes2Str fait la conversion inverse depuis des octets ; elle est décrite avec les commodités de la page Sortie.
Le piège d'Atoi : un texte illisible vaut zéro
Atoi ne rend pas d'erreur. Ce qu'elle n'arrive pas à lire vaut zéro, en silence.
Atoi("42") // 42
Atoi(" 42 ") // 42, les espaces autour sont ignorés
Atoi("+42") // 42
Atoi("12.5") // 0 ← ce n'est pas un entier
Atoi("42 000") // 0 ← l'espace au milieu, lui, casse tout
Atoi("abc") // 0
Atoi("") // 0
Atoi("9223372036854775808") // 0 ← trop grand pour un entier
Atoi("3.3628068e+07") // 33628068 ← la notation scientifique passe
Atoi("1.5e+00") // 0 ← un exposant, mais 1,5 n'est pas entier
La notation scientifique est lue parce que c'est le moteur lui-même qui la produit : un nombre à virgule concaténé à du texte s'écrit 1e+06 dès le million. Elle n'est acceptée que lorsqu'elle désigne un entier exact, donc 1.5e+00 vaut toujours zéro, comme 12.5.
C'est voulu, et c'est le comportement de Ninja : la fonction s'emploie au milieu d'un calcul, 1 + Atoi("2"), et une seconde valeur de retour y ferait porter l'addition sur une tranche. Le prix à payer est qu'un zéro peut vouloir dire « zéro » comme « je n'ai pas su lire ». Si la distinction compte, vérifiez le texte avant de le convertir.
Itoa tronque au lieu d'arrondir quand on lui passe un nombre à virgule :
Itoa(2.7) // "2"
Itoa(-2.7) // "-2", la troncature va vers zéro des deux côtés
Itoa(Round(2.7)) // "3"
Encoder en base64
| Fonction | Rend | |
|---|---|---|
Base64(texte) | 1 : une chaîne | Encode. |
Base64Decode(texte) | 2 : le texte et une erreur | Décode. |
code = Base64("coucou")
clair, err = Base64Decode(code) // ← DEUX cibles
if err != nil { LogError(err) }
Print(clair) // coucou
Écrite avec une seule cible, Base64Decode rend [coucou <nil>], de longueur 2, et non le texte. C'est le premier piège. Une entrée qui n'est pas du base64 valide rend une chaîne vide et une erreur qui commence par Base64Decode :.
Lire du JSON
| Fonction | Rend | |
|---|---|---|
JsonDecode(données) | 2 : la valeur et une erreur | Décode du JSON. |
C'est la fonction qui accompagne FetchURL et PageContent, qui rendent toutes deux du texte.
corps, err = FetchURL("https://exemple.net/api")
if err != nil { LogError(err); return }
o, err = JsonDecode(corps) // ← DEUX cibles, encore
if err != nil { LogError(err); return }
Print(o["nom"])
Print(len(o["liste"]))
Print(o["bloc"]["sous"][0]) // les niveaux s'enchaînent
Un objet JSON revient en carte indexable par chaîne, une liste en tranche. Une clé absente rend nil, sans erreur : testez-la si son absence change quelque chose.
Un entier décodé reste un entier, quelle que soit sa taille. Il se compare, s'additionne, sert de clé de table et s'affiche en clair :
o, err = JsonDecode(corps)
Print(o["planet_id"]) // 33628068
Print(o["planet_id"] == 33628068) // true
Atoi("" + o["planet_id"]) // 33628068
Ce n'était pas le cas jusqu'à la 1.82.239 incluse : tout nombre revenait en flottant, et un flottant ne se compare pas à un entier par sa valeur mais par son écriture. o["planet_id"] == 33628068 était donc faux au-dessus d'un million, sans la moindre erreur, et Atoi("" + o["planet_id"]) rendait zéro. Un identifiant de six chiffres survivait, un de sept disparaissait. Si vous avez contourné cela, le contournement reste juste.
Les nombres à virgule, eux, restent des flottants : o["prix"] sur 12.5 vaut bien 12.5.
Le seul endroit qui reste piégeux : la division
/ rend toujours un nombre à virgule, même entre deux entiers. Sa comparaison directe à un entier retombe donc dans le piège ci-dessus :
o["planet_id"] / 2 == 16814034 // false
Floor(o["planet_id"] / 2) == 16814034 // true
Floor, Ceil et Round rendent un entier quand le résultat en est un : c'est la sortie de secours. Les autres opérateurs - +, -, *, %, <, > - n'ont pas ce défaut.
Et les identifiants rendus par le bot ne sont pas des entiers nus
c.GetID() rend un identifiant de céleste, qui est un type à lui. Il se compare sans problème à un entier, mais comme clé de table il n'en est pas un :
t = {}
t[c.GetID()] = "trouvé"
Print(t[33620526]) // nil ← la clé est là, mais pas sous ce type
Print(t[c.GetID()]) // trouvé
Mesuré en jeu le 22/09/2026. Si vous indexez par identifiant, gardez la même expression des deux côtés, ou passez par Itoa : une clé en texte n'a jamais ce problème.
L'accolade littérale ne passe pas
Ninja donne pour exemple JsonDecode({"a": 1, "b": 2.1}), avec une accolade écrite directement dans le script. Chez nous cela échoue.
o, err = JsonDecode({"a": 1}) // ✗ err : JsonDecode : json: unsupported
// type: map[interface {}]interface {}
// et o vaut nil
o, err = JsonDecode("{\"a\": 1}") // ✓ passez par du texte
L'erreur est franche, elle arrive bien dans err, mais un script qui ne la teste pas continue avec un o nul et s'arrête plus loin sur un message d'index qui ne désigne pas le vrai coupable. Une liste littérale, JsonDecode([1, 2, 3]), fonctionne, elle : c'est la carte qui bloque.
Écrire du JSON
| Fonction | Rend | |
|---|---|---|
JsonEncode(valeur) | 2 : le texte et une erreur | Écrit du JSON. |
JsonEncodeIndent(valeur, retrait) | 2 : le texte et une erreur | Le même, mis en forme. |
Le pendant de JsonDecode, et il passe là où le décodage bloque : l'accolade littérale s'encode sans rien dire.
charge = {}
charge["compte"] = "Vega"
charge["flotte"] = [204, 205, 206]
charge["vide"] = nil
texte, err = JsonEncode(charge) // ← DEUX cibles
if err != nil { LogError(err); return }
Print(texte)
// {"compte":"Vega","flotte":[204,205,206],"vide":null}
Les tableaux, les listes, les nombres, les textes, les booléens et nil - en null - passent tous, avec l'échappement qu'il faut : une apostrophe, un guillemet ou un antislash dans une valeur ne cassent pas le résultat. Les clés ressortent en texte, comme l'exige JSON, donc un tableau indexé par identifiant de planète donne {"33628068": ...}.
JsonEncodeIndent prend le retrait en seconde place, écrit comme vous voulez : JsonEncodeIndent(charge, " ") et JsonEncodeIndent(charge, 2) donnent la même chose. C'est la forme à employer pour écrire au journal ; pour envoyer à un service, prenez JsonEncode, plus court sur le réseau.
Ni l'une ni l'autre n'échappe &, < et > : le résultat est du JSON, pas du HTML, et tout analyseur le relit.
Parcourir un tableau
| Fonction | Rend | |
|---|---|---|
MapKeys(tableau) | 1 : la liste des clés | Triées. |
MapValues(tableau) | 1 : la liste des valeurs | Dans l'ordre des clés. |
Anko n'a pas de keys(), et for c in tableau ne compile pas. Un tableau s'écrit et se lit par clé, mais son contenu ne s'énumère pas - d'où ces deux fonctions.
cache = {}
cache[33628068] = "Vega"
cache[33630201] = "Astrid"
for i = 0; i < len(MapKeys(cache)); i++ {
Print(MapKeys(cache)[i], MapValues(cache)[i])
}
// 33628068 Vega
// 33630201 Astrid
L'ordre est stable d'un appel à l'autre, et c'est ce qui rend la paire utilisable : MapKeys(t)[i] et MapValues(t)[i] désignent toujours la même entrée. Les clés numériques se trient en nombres - 2 avant 10, pas l'inverse
- et les autres par leur écriture.
Un argument qui n'est pas un tableau rend une liste vide, pas une erreur : ces deux fonctions ne rendent qu'une valeur, il n'y a pas de place pour en signaler une. Testez len() si la différence compte.
Construire une valeur à passer
Quatre fonctions fabriquent les valeurs que les autres attendent en argument. Aucune ne parle au jeu.
| Fonction | Rend | |
|---|---|---|
NewShipsInfos() | 1 : une flotte vide | À remplir avec .Set(id, nombre), puis à passer à FlightTime, Speed ou Cargo. |
NewResources(metal, cristal, deut) | 1 : des ressources | Les trois quantités d'un coup. |
NewCoordinate(galaxie, système, position, type) | 1 : une coordonnée | Le type est PLANET_TYPE, MOON_TYPE ou DEBRIS_TYPE. |
ParseCoord(chaîne) | 2 : la coordonnée et une erreur | Lit "1:2:3" ou "M:1:2:3". |
flotte = NewShipsInfos()
flotte.Set(LARGECARGO, 20)
coord, err = ParseCoord("M:1:2:3") // DEUX cibles
if err != nil { LogError(err); return }
ParseCoord accepte le préfixe P, M ou D, en majuscule comme en minuscule, et les crochets d'un affichage du jeu : "[1:2:3]" passe. Sans préfixe, elle rend une planète. Un préfixe qui n'est aucune de ces trois lettres rend une erreur.
Partout où une fonction attend une coordonnée, une chaîne fait aussi l'affaire : ParseCoord ne sert que lorsque vous voulez la coordonnée elle-même, ou vérifier qu'une saisie est lisible avant de vous en servir.
Nommer un identifiant du jeu
| Fonction | Rend | |
|---|---|---|
ID2Str(id) | 1 : une chaîne | Le nom lisible d'un vaisseau, d'une défense, d'un bâtiment ou d'une technologie. |
Print(ID2Str(204)) // LightFighter
Print(ID2Str(LIGHTFIGHTER)) // LightFighter, le nombre et la constante marchent
Print(ID2Str(METALMINE)) // MetalMine
Print(ID2Str(999)) // Invalid(999) : un identifiant inconnu ne
// provoque pas d'erreur, il se lit au journal
Elle ne sert que sur un nombre nu. Un identifiant qui vient du jeu ou d'un tableau de constantes porte déjà son nom, et s'affiche ainsi tout seul :
for id in ShipsArr {
Print(id) // SmallCargo, LargeCargo, LightFighter, …
Print("vaisseau " + id) // vaisseau SmallCargo, même en concaténation
}
Gardez donc ID2Str pour un nombre que vous avez rangé avec Put, relu d'un JSON ou calculé : Print(202) écrit 202, Print(ID2Str(202)) écrit SmallCargo.
Un mot sur les noms rendus : ils sont en anglais, tels que les écrit la bibliothèque de jeu. Ils ne suivent pas la langue de votre compte, et GetLang n'y change rien.
Les constantes nommées et les quatre tableaux sont décrits dans Constantes et valeurs.
Décrire une flotte en une ligne
| Fonction | Rend | |
|---|---|---|
ShortShipsInfos(flotte) | 1 : une chaîne | Résume une flotte : LightFighter: 120, SmallCargo: 40. |
ships, err = GetShips(planete)
if err == nil {
LogInfof("%s : %s", planete.Coordinate, ShortShipsInfos(ships))
}
Elle accepte indifféremment ce que rend GetShips et ce que rend NewShipsInfos, qui ne sont pas de la même forme.
Seuls les vaisseaux présents sont écrits, les zéros sont omis, et l'ordre est celui du jeu : les vaisseaux de combat d'abord, les transporteurs ensuite. Ce n'est donc pas l'ordre de ShipsArr, qui commence par le petit transporteur, et elle peut nommer SolarSatellite, que ShipsArr ne contient pas.
Une flotte vide rend une chaîne vide. Un argument qui n'est pas une flotte rend lui aussi une chaîne vide, et pose une ligne d'avertissement page Journaux, pas dans la console du script. Si votre message sort amputé de sa liste de vaisseaux sans que rien n'apparaisse à l'écran, c'est là qu'il faut regarder.
Comparer deux versions
| Fonction | Rend | |
|---|---|---|
VersionCompare(a, b) | 1 : un entier | -1 si a est plus ancienne, 0 si elles sont égales, 1 si a est plus récente. |
La comparaison lit les suites de chiffres où qu'elles se trouvent, ce qui laisse passer 0.1.1, v2.0 et Kepler 0.1 du même geste. Les segments manquants comptent pour zéro : 1.2 et 1.2.0 sont égales.
Retirez les gardes de version recopiées d'ailleurs. Beaucoup de scripts Ninja commencent par refuser de tourner sur un robot trop vieux :
if VersionCompare(VERSION, "0.91.8") == -1 { // ✗ VERSION vaut « Kepler 0.1 »
Print("You need version 0.91.8 or higher")
Exit()
}
Chez nous VERSION vaut Kepler 0.1, donc cette comparaison rend -1 et le script s'arrête sur-le-champ, alors que tout ce dont il a besoin est présent. Supprimez le bloc, ou comparez à un numéro de Kepler.
Print(VersionCompare("1.2", "1.2.0")) // 0 : les segments absents valent zéro
Dernière limite, sans conséquence pratique : un suffixe de pré-version se lit comme un segment de plus, si bien que 1.0.0-rc1 passe pour supérieure à 1.0.0.
Objets, matière noire, enchère et missiles
| Fonction | Rend | |
|---|---|---|
GetItems(céleste) | 2 : les objets et une erreur | Tous les objets du céleste. |
GetActiveItems(céleste) | 2 : les objets et une erreur | Les seuls en cours d'effet. |
ActivateItem(référence, céleste) | 1 : une erreur | Allume un objet de l'inventaire. |
RecruitOfficer(officier, jours) | 1 : une erreur | Engage un officier. 2 commandant, 3 amiral, 4 ingénieur, 5 géologue, 6 technocrate ; 7 ou 90 jours. |
GetAuction() | 2 : l'enchère et une erreur | L'enchère en cours. |
BuyOfferOfTheDay() | 1 : une erreur | Achète l'offre du marchand. |
SendIPM(planète, cible, nombre, défense) | 2 : les missiles partis et une erreur | Lance des missiles. |
c = GetCachedCelestial("1:2:3")
objets, err = GetItems(c.GetID())
for o in objets { Print(o) }
actifs, err = GetActiveItems(c.GetID())
Print("en cours :", len(actifs))
// La référence est celle que rend GetItems, jamais le nom affiché.
for o in objets {
if o.Amount > 0 {
err = ActivateItem(o.Ref, c.GetID())
Print("allumé :", o.Name, err)
break
}
}
ench, err = GetAuction()
Print(ench, err)
Print(RecruitOfficer(5, 7)) // un géologue pour sept jours
Print(BuyOfferOfTheDay())
partis, err = SendIPM(c.GetID(), "1:1:1", 10, 0)
Print(partis, "missiles partis", err)
La matière noire se dépense depuis un script, jamais toute seule
BuyOfferOfTheDay et RecruitOfficer dépensent votre matière noire. Elles sont là parce qu'un script est un geste explicite : c'est vous qui écrivez la ligne, avec le nom de ce qu'elle dépense.
UseDM reste fermée pour le moment, et c'est une prudence assumée : elle vide une réserve en quelques appels, sans rien demander, et une boucle mal gardée aurait fait le tour de la caisse avant que son auteur ne s'en aperçoive.
Le robot, lui, n'y touche jamais de sa propre initiative. Aucun worker, aucun plan, aucune tâche de tutoriel ne peut dépenser de la matière noire, et un test le vérifie en relisant tout le code du robot. La seule porte est celle que vous ouvrez vous-même, dans votre script.
Une précaution qui vaut d'être écrite : ces deux fonctions dépensent à chaque appel, sans rien demander. N'engagez pas un officier dans une boucle de surveillance, et n'appelez pas l'offre du jour ailleurs qu'une fois par jour.
ActivateItem est à part et ne coûte rien : elle allume un objet déjà présent dans votre inventaire, et c'est l'objet qui se consomme. Le jeu refuse d'activer ce que vous ne possédez pas, donc rien ne peut être acheté au passage.
Les missiles, eux, se paient en ressources : ils n'ont jamais rien eu à voir avec tout ceci.
Ce qu'il faut savoir de GetAuction
Une vente terminée n'est pas une erreur. GetAuction rend alors HasFinished vrai, et Endtime compte les secondes jusqu'à la prochaine vente. Pendant une vente, Endtime est le temps qu'il lui reste, en secondes, tel que le jeu l'arrondit à la minute.
Une erreur veut dire que la page n'a pas pu être lue, jamais « pas d'enchère » : son texte cite ce que le jeu a répondu. Jusqu'à la 1.82.374, sur les serveurs en version 13, elle rendait toujours « failed to find end time approx », que la vente tourne ou non : le jeu avait changé l'adresse du commissaire-priseur.
Enchérir depuis un script n'est pas encore possible.
Démolir des missiles
| Fonction | Rend | |
|---|---|---|
DestroyRockets(planète, abm, ipm) | 1 : une erreur | Détruit des missiles de votre silo. |
Les deux nombres sont des quantités à DÉTRUIRE, pas des niveaux à atteindre. DestroyRockets(p, 5, 0) retire cinq intercepteurs, il n'en laisse pas cinq. C'est la forme de l'outil de référence, et l'erreur inverse coûterait un silo entier.
Les missiles ne vivent que sur une planète : une lune passée à leur place rend une erreur qui la nomme, plutôt que le refus muet du jeu.
Ce qu'il faut savoir de SendIPM
Elle rend DEUX valeurs, et les deux comptent. Le jeu peut envoyer moins de missiles que demandé — portée, stock — sans que ce soit une panne. Un script qui ne lirait que l'erreur croirait sa salve entière partie. Le quatrième argument est la défense visée ; zéro laisse le jeu choisir.
Les missiles ne partent que d'une planète. Une lune passée à leur place rend une erreur qui la nomme, plutôt que le refus muet du jeu.
Les officiers ne sont pas là
RecruitOfficer n'existe pas, pour la raison de la section précédente : les officiers se paient en matière noire.
Journaliser en cours de calcul
Ces quatre-là appartiennent à la même famille que les fonctions ci-dessus. Le détail des niveaux et leurs jumelles sans f sont page Sortie, journal et stockage.
| Fonction | Rend | |
|---|---|---|
LogDebugf(format, ...) | rien | La même, avec un format. |
LogInfof(format, ...) | rien | Le déroulé normal. |
LogWarnf(format, ...) | rien | Ce qui mérite un œil sans être grave. |
LogErrorf(format, ...) | rien | Ce qui a échoué. |
Une précision qui compte : LogDebug et LogDebugf sont journalisées au niveau Info, pas Debug. C'est voulu, Ninja donne LogDebug pour l'équivalent exact de Print, et un robot réglé sur Info les perdrait sinon. Vous ne pouvez donc pas les faire disparaître du journal en remontant le niveau.
Ce qui se calcule sans rien demander au jeu
| Fonction | Rend | |
|---|---|---|
ShipsAttackStrength(flotte, recherches) | 1 : un entier | La puissance de feu d'une flotte entière. |
ShipsAttackStrengthUsingOwnResearches(flotte) | 1 : un entier | La même, avec vos recherches. |
NewTemperature(min, max) | 1 : une température | Les deux bornes, comme le jeu les affiche. |
SolarSatelliteProduction(température, nombre) | 1 : un entier | L'énergie produite par des satellites. |
GetRequirements(id) | 1 : une table | Les prérequis directs d'une construction. |
ConvertIntoCoordinate(quoi) | 2 : la coordonnée et une erreur | Normalise tout ce qui désigne une position. |
GetPlayerCoordinates(joueur) | 1 : une liste de coordonnées | Les positions relevées d'un joueur. |
SetHomeWorld(céleste) | 1 : une erreur | Retient votre base. |
GetHomeWorld() | 1 : un céleste, ou rien | La relit. |
Aucune ne coûte un aller-retour. Les unes calculent sur ce que le compte porte déjà en cache, les deux dernières relisent le relevé que le scanner a rangé. Les appeler dans une boucle sur cent joueurs est sans conséquence — ce qui n'est vrai d'aucune fonction de la page des célestes.
flotte, err = GetShips(GetCachedCelestials()[0].GetID())
Print("puissance :", ShipsAttackStrengthUsingOwnResearches(flotte))
temp = NewTemperature(-12, 40)
Print("énergie :", SolarSatelliteProduction(temp, 123))
Print(GetRequirements(LIGHTLASER)) // chantier 2, technologie laser 3
GetRequirements rend les prérequis DIRECTS, pas la chaîne entière : le laser léger demande un chantier de niveau 2 et la technologie laser de niveau 3, mais ce que ces deux-là demandent à leur tour n'y figure pas. Remonter la chaîne se fait en rappelant la fonction.
Écart avec Ninja sur SolarSatelliteProduction, et il joue en votre faveur. Là-bas, le calcul suppose un compte sans classe. La classe Collector produit pourtant un dixième d'énergie en plus, et Kepler la lit sur votre compte : le chiffre rendu est celui de vos satellites, pas celui d'un compte théorique. Un script recopié n'a pas une ligne à changer.
GetPlayerCoordinates lit le relevé LOCAL, pas le jeu. Un joueur jamais balayé rend une liste vide, ce qui ne veut pas dire qu'il n'a pas de planètes — seulement qu'on n'en a pas vu. Passez le scanner d'abord.
SetHomeWorld range une valeur, et rien d'autre. Le robot n'en fait rien, aucun worker ne la consulte, et la changer ne déplace évidemment rien dans le jeu. Elle existe parce que la plupart des scripts ont une base — celle d'où partent les vols, celle où rentrent les ressources — et que la coder en dur oblige à rouvrir le script chaque fois qu'on en change. Contrairement à Put/Get, elle est partagée : tous vos scripts lisent le même monde natal. GetHomeWorld rend nil si rien n'a été rangé, ou si ce qui l'a été n'est plus à vous.