Tri automatique des objets
Vider un coffre d'entrée dans des coffres de rangement sur un Câble de données, par objet, par mod ou par fin d'identifiant, avec un coffre de surplus et un écran d'état.
Toute base finit par avoir un coffre plein de tout : ce que les perceuses mécaniques ont creusé, ce que les fermes ont lâché, ce que vous avez rapporté de voyage. Create sait trier avec des filtres sur les entonnoirs et les chutes intelligentes, mais chaque filtre prend un seul objet, et changer l'organisation oblige à reconstruire. Un ordinateur sur un Câble de données peut trier avec des règles : « tout ce qui finit par _ore va au coffre des minerais », « tout ce qui vient de Create va au coffre des pièces », et les changer en modifiant une table.
Le programme vide un coffre d'entrée dans des coffres de rangement, une pile à la fois, en choisissant le coffre de la première règle qui correspond à l'objet. Ce qu'aucune règle ne veut, et ce qui ne tient plus dans un coffre plein, part dans un coffre de surplus. Un écran montre chaque coffre avec ses règles, le nombre d'objets reçus et s'il est plein.
Ce qu'il vous faut
- Un Mini-ordinateur ou plus récent. Les coffres de rangement sont atteints par un Câble de données, et seuls le Mini-ordinateur, le Micro-ordinateur, le Microcontrôleur et l'Ordinateur moderne ont un port réseau. Un Micro-ordinateur affiche les couleurs de l'écran ci-dessous ; un Mini-ordinateur le montre en ambre.
- Du Câble de données, qui longe tous les coffres. Chaque bloc qui touche le câble devient un périphérique nommé d'après son type et sa position :
inventory@12,64,-3pour un coffre en x 12, y 64, z -3 (F3 affiche les coordonnées du bloc que vous regardez). - Un coffre d'entrée qui touche le câble (ou l'ordinateur), rempli à la main, par un entonnoir de Minecraft ou par un entonnoir de Create au bout d'un tapis.
- Des coffres de rangement, un par catégorie, plus un coffre de surplus. Les tonneaux sont pratiques : deux coffres côte à côte fusionneraient en un grand coffre. Un Coffre à objet renforcé de Create fait un grand surplus.
Vu de dessus, un bloc par case :
+--------+--------+--------+--------+--------+
|minerais|lingots | create | bois |surplus | tonneaux de rangement
+--------+--------+--------+--------+--------+
| = | = | = | = | = | Câble de données, contre chaque tonneau
+--------+--------+--------+--------+--------+
| Mini | ENTRÉE | Mini-ordinateur et coffre d'entrée,
+--------+--------+ tous deux contre le câbleLe programme, étape par étape
1. Les coffres et les règles
COFFRES donne à chaque coffre de rangement un nom court pour l'écran, et son nom de périphérique sur le câble. REGLES se lit de haut en bas, et la première règle qui correspond l'emporte. Une règle a l'une de ces trois formes :
| règle | correspond à |
|---|---|
"minecraft:iron_ingot" | cet objet seulement |
"create:*" | tout identifiant qui commence par create: : tout le mod Create |
"*_ore" | tout identifiant qui finit par _ore : minerai de fer, minerai d'or des abîmes, minerai de zinc... |
local ENTREE = "inventory@11,64,-2" -- le coffre que vous remplissez : nom de câble ou côté
local COFFRES = { -- nom court = nom de câble du coffre
minerais = "inventory@10,64,-4",
lingots = "inventory@11,64,-4",
create = "inventory@12,64,-4",
bois = "inventory@13,64,-4",
surplus = "inventory@14,64,-4",
}
local SURPLUS = "surplus" -- où vont les objets sans règle
local REGLES = {
{motif = "minecraft:raw_*", vers = "minerais"},
{motif = "*_ore", vers = "minerais"},
{motif = "*_ingot", vers = "lingots"},
{motif = "*_nugget", vers = "lingots"},
{motif = "create:*", vers = "create"},
{motif = "*_log", vers = "bois"},
{motif = "*_planks", vers = "bois"},
}
local REPOS = 1 -- secondes entre deux coups d'œil à un coffre d'entrée vide
local ACTIF = 0.25 -- secondes entre deux passes tant que des objets arriventL'ordre compte : create:brass_ingot correspond à la fois à *_ingot et à create:*, et va dans lingots parce que cette règle vient d'abord. Mettez les règles précises en haut et les règles larges en dessous.
2. Reconnaître un objet
string.starts et string.ends font le travail. Une étoile au début d'une règle veut dire « finit par », une étoile à la fin « commence par » :
local function correspond(motif, id)
if motif:starts("*") then return id:ends(motif:sub(2)) end
if motif:ends("*") then return id:starts(motif:sub(1, -2)) end
return id == motif
end
local function destination(id)
for _, regle in ipairs(REGLES) do
if correspond(regle.motif, id) then return regle.vers end
end
return SURPLUS
endmotif:sub(2) est la règle sans son premier caractère, motif:sub(1, -2) sans son dernier (une position négative se compte depuis la fin).
local function correspond(motif, id)
if motif:starts("*") then return id:ends(motif:sub(2)) end
if motif:ends("*") then return id:starts(motif:sub(1, -2)) end
return id == motif
end
print(correspond("*_ore", "minecraft:deepslate_iron_ore"))
print(correspond("create:*", "create:andesite_alloy"))
print(correspond("create:*", "minecraft:andesite"))true true false
3. Vérifier les réglages
Une faute de frappe dans une table doit arrêter le programme tout de suite avec un message clair, pas envoyer la moitié des objets nulle part. preparer prend le coffre d'entrée, vérifie que chaque règle désigne un coffre connu, note les coffres absents et prépare le texte des règles pour l'écran :
local entree = nil
local deplaces = {} -- nom court -> objets envoyés là depuis le démarrage
local etat = {} -- nom court -> "ok", "PLEIN" ou "ABSENT"
local regles_de = {} -- nom court -> ses règles, en texte pour l'écran
local function preparer()
entree = peripheral.wrap(ENTREE)
if entree == nil or entree.push == nil then
error("pas de coffre d'entrée '" .. ENTREE .. "'")
end
if COFFRES[SURPLUS] == nil then
error("le coffre de surplus '" .. SURPLUS .. "' n'est pas dans COFFRES")
end
for _, regle in ipairs(REGLES) do
if COFFRES[regle.vers] == nil then
error("la règle '" .. regle.motif .. "' envoie vers '" .. regle.vers .. "', absent de COFFRES")
end
if regles_de[regle.vers] == nil then
regles_de[regle.vers] = regle.motif
else
regles_de[regle.vers] = regles_de[regle.vers] .. ", " .. regle.motif
end
end
for cle, nom in pairs(COFFRES) do
deplaces[cle] = 0
if peripheral.type(nom) == nil then etat[cle] = "ABSENT" else etat[cle] = "ok" end
if regles_de[cle] == nil then regles_de[cle] = "(aucune règle)" end
end
if regles_de[SURPLUS] == "(aucune règle)" then regles_de[SURPLUS] = "tout le reste" end
endpairs parcourt les entrées nommées d'une table dans l'ordre où elles ont été écrites : l'écran liste donc les coffres dans l'ordre de COFFRES.
4. Déplacer une pile : push
Le cœur du trieur est entree.push(cible, emplacement, quantite) (inventory.push()). Ses règles valent la peine d'être bien connues :
- Il s'appelle sur l'inventaire source, et la cible est le nom d'un autre périphérique du même ordinateur (un côté comme
"right", ou un nom de câble), pas un objet obtenu parwrap. emplacementlimite le déplacement à cet emplacement de la source ;quantiteest le maximum d'objets à déplacer (64 si on l'omet).- Il renvoie le nombre d'objets réellement déplacés :
0quand la cible est pleine, moins que demandé quand elle se remplit en route. Rien ne se perd : ce qui ne rentre pas reste dans la source. - Quand le nom ne mène à aucun inventaire (coffre cassé, faute de frappe, chunk déchargé), il lève l'erreur
no inventory 'inventory@14,64,-4'.
envoyer l'appelle donc à travers pcall, et traduit le résultat en état du coffre :
local function envoyer(cle, emplacement, quantite)
local r = pcall(entree.push, COFFRES[cle], emplacement, quantite)
if not r.ok then
etat[cle] = "ABSENT"
return 0
end
if r.value < quantite then etat[cle] = "PLEIN" else etat[cle] = "ok" end
deplaces[cle] = deplaces[cle] + r.value
return r.value
end5. Une passe de tri
Une passe lit le coffre d'entrée une fois avec entree.list(), puis envoie chaque pile vers son coffre. Ce que le coffre choisi refuse (plein ou absent) a une seconde chance dans le surplus. Les piles qui restent quand même sont comptées comme « bloquées » : leur coffre et le surplus sont pleins.
local total = 0 -- objets triés depuis le démarrage
local bloquees = 0 -- piles restées en entrée après la dernière passe
local entree_ok = true
local dernier = "rien pour l'instant"
local function passe_de_tri()
local r = pcall(entree.list)
entree_ok = r.ok
if not r.ok then return 0 end
local fait = 0
bloquees = 0
for emplacement, pile in pairs(r.value) do
local cle = destination(pile.name)
local n = envoyer(cle, emplacement, pile.count)
local ou = cle
if n < pile.count and cle ~= SURPLUS then
local en_plus = envoyer(SURPLUS, emplacement, pile.count - n)
if en_plus > 0 then ou = SURPLUS end
n = n + en_plus
end
if n > 0 then dernier = n .. " x " .. pile.name .. " -> " .. ou end
if n < pile.count then bloquees = bloquees + 1 end
fait = fait + n
end
total = total + fait
return fait
endentree.list() renvoie les piles indexées par numéro d'emplacement (les emplacements vides manquent) : c'est pourquoi la boucle utilise pairs, et pourquoi emplacement peut être passé tel quel à push.
6. L'écran et la boucle
L'écran liste les coffres, puis l'état de l'entrée et le dernier déplacement. Un clic n'importe où suspend ou reprend le tri, pratique pendant que vous réorganisez les coffres.
local en_pause = false
local function dessiner()
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
local titre = "TRI DES OBJETS"
if en_pause then titre = "TRI DES OBJETS (EN PAUSE)" end
term.write(string.format(" %-34s%15s ", titre, total .. " triés"))
term.set_bg(term.colors.black)
term.set_fg(term.colors.light_gray)
term.set_cursor(1, 3)
term.write(string.format(" %-9s %-25s%7s %-7s", "Coffre", "Règles", "Objets", "État"))
local y = 4
for cle, _ in pairs(COFFRES) do
term.set_cursor(1, y)
term.set_fg(term.colors.white)
term.write(string.format(" %-9s %-25s%7d ", cle:sub(1, 9), regles_de[cle]:sub(1, 25), deplaces[cle]))
if etat[cle] == "ok" then
term.set_fg(term.colors.green)
elseif etat[cle] == "PLEIN" then
term.set_fg(term.colors.yellow)
else
term.set_fg(term.colors.red)
end
term.write(etat[cle])
y = y + 1
end
term.set_cursor(2, 16)
if not entree_ok then
term.set_fg(term.colors.red)
term.write("Coffre d'entrée introuvable : " .. ENTREE)
elseif bloquees > 0 then
term.set_fg(term.colors.yellow)
term.write(bloquees .. " pile(s) bloquée(s) en entrée : coffres pleins")
else
term.set_fg(term.colors.green)
term.write("Coffre d'entrée vide, en attente d'objets.")
end
term.set_cursor(2, 17)
term.set_fg(term.colors.light_gray)
term.write("Dernier : " .. dernier)
term.set_cursor(2, 19)
term.set_fg(term.colors.gray)
term.write("Cliquez sur l'écran pour suspendre ou reprendre.")
endLa boucle attend son minuteur. Après une passe qui a déplacé quelque chose, elle regarde de nouveau bientôt (ACTIF), car un tapis alimente sans doute encore le coffre ; après une passe vide, elle attend plus longtemps (REPOS) : un trieur au repos ne coûte presque rien.
preparer()
dessiner()
local minuteur = os.start_timer(REPOS)
while true do
local e = os.pull_event()
if e.name == "timer" and e.id == minuteur then
local fait = 0
if not en_pause then fait = passe_de_tri() end
dessiner()
if fait > 0 then minuteur = os.start_timer(ACTIF) else minuteur = os.start_timer(REPOS) end
elseif e.name == "click" then
en_pause = not en_pause
dessiner()
end
endLe programme complet
-- Tri des objets : vide un coffre d'entrée dans des coffres de rangement sur
-- un Câble de données, selon des règles ; ce qu'aucune règle ne veut part au
-- coffre de surplus.
local ENTREE = "inventory@11,64,-2" -- le coffre que vous remplissez : nom de câble ou côté
local COFFRES = { -- nom court = nom de câble du coffre
minerais = "inventory@10,64,-4",
lingots = "inventory@11,64,-4",
create = "inventory@12,64,-4",
bois = "inventory@13,64,-4",
surplus = "inventory@14,64,-4",
}
local SURPLUS = "surplus" -- où vont les objets sans règle
-- Lues de haut en bas, la première qui correspond l'emporte :
-- "minecraft:iron_ingot" cet objet seulement
-- "create:*" tout identifiant qui commence par "create:"
-- "*_ore" tout identifiant qui finit par "_ore"
local REGLES = {
{motif = "minecraft:raw_*", vers = "minerais"},
{motif = "*_ore", vers = "minerais"},
{motif = "*_ingot", vers = "lingots"},
{motif = "*_nugget", vers = "lingots"},
{motif = "create:*", vers = "create"},
{motif = "*_log", vers = "bois"},
{motif = "*_planks", vers = "bois"},
}
local REPOS = 1 -- secondes entre deux coups d'œil à un coffre d'entrée vide
local ACTIF = 0.25 -- secondes entre deux passes tant que des objets arrivent
local entree = nil
local deplaces = {} -- nom court -> objets envoyés là depuis le démarrage
local etat = {} -- nom court -> "ok", "PLEIN" ou "ABSENT"
local regles_de = {} -- nom court -> ses règles, en texte pour l'écran
local total = 0 -- objets triés depuis le démarrage
local bloquees = 0 -- piles restées en entrée après la dernière passe
local entree_ok = true
local dernier = "rien pour l'instant"
local en_pause = false
-- Règles ---------------------------------------------------------------------------
local function correspond(motif, id)
if motif:starts("*") then return id:ends(motif:sub(2)) end
if motif:ends("*") then return id:starts(motif:sub(1, -2)) end
return id == motif
end
local function destination(id)
for _, regle in ipairs(REGLES) do
if correspond(regle.motif, id) then return regle.vers end
end
return SURPLUS
end
local function preparer()
entree = peripheral.wrap(ENTREE)
if entree == nil or entree.push == nil then
error("pas de coffre d'entrée '" .. ENTREE .. "'")
end
if COFFRES[SURPLUS] == nil then
error("le coffre de surplus '" .. SURPLUS .. "' n'est pas dans COFFRES")
end
for _, regle in ipairs(REGLES) do
if COFFRES[regle.vers] == nil then
error("la règle '" .. regle.motif .. "' envoie vers '" .. regle.vers .. "', absent de COFFRES")
end
if regles_de[regle.vers] == nil then
regles_de[regle.vers] = regle.motif
else
regles_de[regle.vers] = regles_de[regle.vers] .. ", " .. regle.motif
end
end
for cle, nom in pairs(COFFRES) do
deplaces[cle] = 0
if peripheral.type(nom) == nil then etat[cle] = "ABSENT" else etat[cle] = "ok" end
if regles_de[cle] == nil then regles_de[cle] = "(aucune règle)" end
end
if regles_de[SURPLUS] == "(aucune règle)" then regles_de[SURPLUS] = "tout le reste" end
end
-- Déplacer les objets -------------------------------------------------------------
local function envoyer(cle, emplacement, quantite)
local r = pcall(entree.push, COFFRES[cle], emplacement, quantite)
if not r.ok then
etat[cle] = "ABSENT"
return 0
end
if r.value < quantite then etat[cle] = "PLEIN" else etat[cle] = "ok" end
deplaces[cle] = deplaces[cle] + r.value
return r.value
end
local function passe_de_tri()
local r = pcall(entree.list)
entree_ok = r.ok
if not r.ok then return 0 end
local fait = 0
bloquees = 0
for emplacement, pile in pairs(r.value) do
local cle = destination(pile.name)
local n = envoyer(cle, emplacement, pile.count)
local ou = cle
if n < pile.count and cle ~= SURPLUS then
local en_plus = envoyer(SURPLUS, emplacement, pile.count - n)
if en_plus > 0 then ou = SURPLUS end
n = n + en_plus
end
if n > 0 then dernier = n .. " x " .. pile.name .. " -> " .. ou end
if n < pile.count then bloquees = bloquees + 1 end
fait = fait + n
end
total = total + fait
return fait
end
-- Écran ------------------------------------------------------------------------------
local function dessiner()
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
local titre = "TRI DES OBJETS"
if en_pause then titre = "TRI DES OBJETS (EN PAUSE)" end
term.write(string.format(" %-34s%15s ", titre, total .. " triés"))
term.set_bg(term.colors.black)
term.set_fg(term.colors.light_gray)
term.set_cursor(1, 3)
term.write(string.format(" %-9s %-25s%7s %-7s", "Coffre", "Règles", "Objets", "État"))
local y = 4
for cle, _ in pairs(COFFRES) do
term.set_cursor(1, y)
term.set_fg(term.colors.white)
term.write(string.format(" %-9s %-25s%7d ", cle:sub(1, 9), regles_de[cle]:sub(1, 25), deplaces[cle]))
if etat[cle] == "ok" then
term.set_fg(term.colors.green)
elseif etat[cle] == "PLEIN" then
term.set_fg(term.colors.yellow)
else
term.set_fg(term.colors.red)
end
term.write(etat[cle])
y = y + 1
end
term.set_cursor(2, 16)
if not entree_ok then
term.set_fg(term.colors.red)
term.write("Coffre d'entrée introuvable : " .. ENTREE)
elseif bloquees > 0 then
term.set_fg(term.colors.yellow)
term.write(bloquees .. " pile(s) bloquée(s) en entrée : coffres pleins")
else
term.set_fg(term.colors.green)
term.write("Coffre d'entrée vide, en attente d'objets.")
end
term.set_cursor(2, 17)
term.set_fg(term.colors.light_gray)
term.write("Dernier : " .. dernier)
term.set_cursor(2, 19)
term.set_fg(term.colors.gray)
term.write("Cliquez sur l'écran pour suspendre ou reprendre.")
end
-- Boucle principale --------------------------------------------------------------
preparer()
dessiner()
local minuteur = os.start_timer(REPOS)
while true do
local e = os.pull_event()
if e.name == "timer" and e.id == minuteur then
local fait = 0
if not en_pause then fait = passe_de_tri() end
dessiner()
if fait > 0 then minuteur = os.start_timer(ACTIF) else minuteur = os.start_timer(REPOS) end
elseif e.name == "click" then
en_pause = not en_pause
dessiner()
end
endÀ quoi ressemble l'écran
La même fonction dessiner, avec les compteurs d'un trieur qui tourne depuis un moment : le tonneau du bois est plein, si bien que des planches sont parties au surplus.
local ENTREE = "inventory@11,64,-2"
local COFFRES = {
minerais = "inventory@10,64,-4",
lingots = "inventory@11,64,-4",
create = "inventory@12,64,-4",
bois = "inventory@13,64,-4",
surplus = "inventory@14,64,-4",
}
local regles_de = {
minerais = "minecraft:raw_*, *_ore",
lingots = "*_ingot, *_nugget",
create = "create:*",
bois = "*_log, *_planks",
surplus = "tout le reste",
}
local deplaces = {minerais = 1408, lingots = 896, create = 517, bois = 1728, surplus = 75}
local etat = {minerais = "ok", lingots = "ok", create = "ok", bois = "PLEIN", surplus = "ok"}
local total = 4624
local bloquees = 0
local entree_ok = true
local dernier = "11 x minecraft:oak_planks -> surplus"
local en_pause = false
local function dessiner()
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
local titre = "TRI DES OBJETS"
if en_pause then titre = "TRI DES OBJETS (EN PAUSE)" end
term.write(string.format(" %-34s%15s ", titre, total .. " triés"))
term.set_bg(term.colors.black)
term.set_fg(term.colors.light_gray)
term.set_cursor(1, 3)
term.write(string.format(" %-9s %-25s%7s %-7s", "Coffre", "Règles", "Objets", "État"))
local y = 4
for cle, _ in pairs(COFFRES) do
term.set_cursor(1, y)
term.set_fg(term.colors.white)
term.write(string.format(" %-9s %-25s%7d ", cle:sub(1, 9), regles_de[cle]:sub(1, 25), deplaces[cle]))
if etat[cle] == "ok" then
term.set_fg(term.colors.green)
elseif etat[cle] == "PLEIN" then
term.set_fg(term.colors.yellow)
else
term.set_fg(term.colors.red)
end
term.write(etat[cle])
y = y + 1
end
term.set_cursor(2, 16)
if not entree_ok then
term.set_fg(term.colors.red)
term.write("Coffre d'entrée introuvable : " .. ENTREE)
elseif bloquees > 0 then
term.set_fg(term.colors.yellow)
term.write(bloquees .. " pile(s) bloquée(s) en entrée : coffres pleins")
else
term.set_fg(term.colors.green)
term.write("Coffre d'entrée vide, en attente d'objets.")
end
term.set_cursor(2, 17)
term.set_fg(term.colors.light_gray)
term.write("Dernier : " .. dernier)
term.set_cursor(2, 19)
term.set_fg(term.colors.gray)
term.write("Cliquez sur l'écran pour suspendre ou reprendre.")
end
dessiner()
L'essayer
Trouvez les noms de vos coffres. Dans le shell, l'interpréteur interactif liste tous les inventaires du câble :
> brass Brass 1.0 - type 'exit' to leave. brass> for _, n in ipairs(peripheral.list("inventory")) do print(n) end inventory@11,64,-2 inventory@10,64,-4 inventory@11,64,-4 inventory@12,64,-4 inventory@13,64,-4 inventory@14,64,-4 brass> exit
Regardez chaque coffre avec F3 pour savoir quelles coordonnées sont les siennes, ou lancez l'exemple
devices(devices), qui dessine l'ordinateur, son câble et chaque bloc branché dessus.- Recopiez les noms dans
ENTREEetCOFFRES, enregistrez sousstartupet lancez. Un mauvais nom arrête le programme aussitôt avecpas de coffre d'entrée '...', ou afficheABSENTà côté du coffre. - Jetez un mélange dans le coffre d'entrée : minerai de fer, cuivre brut, bûches de chêne, roues dentées de Create, terre. En une seconde, tout part vers son coffre ; la terre va au surplus.
- Remplissez le tonneau du bois jusqu'en haut, et ajoutez des bûches : la ligne du bois passe à
PLEINet les bûches partent au surplus. - Cassez le tonneau de surplus et remplissez celui du bois : les bûches restent en entrée, et la ligne d'état compte les piles bloquées.
Variantes
- Arrêter le tapis en cas de blocage. Quand
bloquees > 0, allumez une sortie redstone qui atteint l'entonnoir de Create qui remplit le coffre d'entrée : un entonnoir alimenté ne déplace plus rien, et le tapis s'accumule au lieu de déborder.rs.set("top", bloquees > 0)après chaque passe. - Trier par nom. Chaque pile a aussi un champ
display, son nom tel qu'il s'affiche en jeu. Renommez des objets sur une enclume (« Retour à la base ») et ajoutez une sorte de règle qui comparepile.display. - Garder les compteurs. Enregistrez
deplacesdans un fichier avecfs.writechaque minute et relisez-le danspreparer: les compteurs survivent à un redémarrage. - Ressortir des objets. Prenez un coffre de rangement et poussez de lui vers un coffre de sortie :
peripheral.wrap(COFFRES.lingots)puis.push(SORTIE, emplacement, 64). Un écran tactile avec un bouton par objet fait une petite boutique : voirPanneau de commande tactilepour les boutons. - Beaucoup de coffres. Sur un Ordinateur moderne, l'écran a 24 lignes : il y a la place pour 15 coffres. Pour toute une salle de stockage, faites de chaque entrée de
COFFRESune liste de noms (minerais = {"inventory@10,64,-4", "inventory@10,65,-4"}), et laissezenvoyerles essayer tour à tour jusqu'à ce que l'un prenne la pile.