Éclairage jour et nuit
Allumer les lampes de plusieurs zones au crépuscule et les éteindre à l'aube par des fréquences de Liaison de redstone, avec un bouton pour forcer et un écran d'état qui affiche l'heure.
Des réverbères qui s'allument quand le soleil se couche, une gare qui reste éclairée un peu plus longtemps, des vitrines qui s'éteignent à 23:00, et tout éteint le matin : pas de capteurs de lumière partout, un seul ordinateur pour toute la ville. Cette recette pilote plusieurs zones de lampes à travers des Liaisons de redstone de Create, chacune avec ses horaires, avec un bouton pour tout forcer allumé ou éteint et un écran d'état qui affiche l'heure en gros chiffres.
C'est un bon exemple de programme guidé par l'horloge du monde : il ne compte jamais les heures lui-même, il regarde os.day_time et en déduit ce qui doit être allumé maintenant. Dormir dans un lit, /time set ou un redémarrage du serveur ne peuvent pas le tromper.
Ce qu'il vous faut
- Un ordinateur à écran couleur. Un Microcontrôleur est parfait : bon marché, en couleurs, avec sa propre mémoire de 16 Ko, entraîné par un arbre par-dessous. L'écran est prévu pour ses 40 × 12 caractères et tient aussi sur les plus grands. Tout ordinateur à partir de l'Ordinateur à transistors peut le faire tourner (la grande horloge a besoin de
gfx), mais seuls ceux en couleur montrent les zones allumées en jaune. - Une Liaison de redstone par zone, en mode réception, à côté des lampes de la zone (ou reliée à elles). Ses deux emplacements portent les deux objets de la fréquence de la zone, les mêmes que dans le programme : de la poudre de pierre lumineuse et un lingot de fer pour les rues, par exemple. L'ordinateur émet lui-même sur cette fréquence avec
link.set: il n'a pas besoin de bloc de liaison à lui. Les zones doivent être à portée de Liaison de redstone de l'ordinateur (lelinkRangede Create, 256 blocs par défaut). - Un bouton sur le dessus de l'ordinateur, pour forcer à la main.
- Si vous voulez, des moniteurs contre l'ordinateur, pour lire l'heure de loin.
Salle de contrôle (vue de côté) : Dans chaque zone :
[bouton] [Liaison de redstone] réception : poudre lumineuse + lingot de fer
[MC] Microcontrôleur [lampe][lampe][lampe] (alimentées par la liaison)
==arbre== par-dessousComment ça marche
1. Les réglages
Chaque zone a un nom pour l'écran, les deux objets de sa fréquence, et ses horaires : quand elle s'allume (allume) et quand elle s'éteint (eteint), écrits comme sur une horloge.
local ZONES = {
{nom = "RUES", a = "minecraft:glowstone_dust", b = "minecraft:iron_ingot", allume = "18:30", eteint = "05:30"},
{nom = "GARE", a = "minecraft:glowstone_dust", b = "minecraft:gold_ingot", allume = "18:00", eteint = "06:30"},
{nom = "USINE", a = "minecraft:glowstone_dust", b = "minecraft:copper_ingot", allume = "19:00", eteint = "05:00"},
{nom = "BOUTIQUES", a = "minecraft:glowstone_dust", b = "minecraft:emerald", allume = "18:30", eteint = "23:00"},
}Dans Minecraft, le soleil se couche vers 18:00 et il fait nuit à partir de 19:00 environ ; le ciel s'éclaircit vers 05:00 et le jour commence à 06:00. Un ordinateur peut émettre sur 32 fréquences au plus : 32 zones, donc.
2. L'horloge du monde
os.day_time donne l'heure du jour en ticks, de 0 à 23999. Le jour commence à 6:00, pas à minuit, et une heure fait 1000 ticks : 18:30 vaut 12500, minuit 18000, 05:30 23500. Deux petites fonctions convertissent dans les deux sens :
local function ticks_de(hhmm)
local p = string.split(hhmm, ":")
local h, m = tonumber(p[1]), tonumber(p[2])
if #p ~= 2 or h == nil or m == nil then error("heure invalide '" .. hhmm .. "', écrivez-la comme 18:30") end
return ((h - 6) % 24) * 1000 + m * 1000 // 60
end
local function heure_de(t)
return string.format("%02d:%02d", (t // 1000 + 6) % 24, t % 1000 * 60 // 1000)
endUne faute de frappe dans les réglages ("1830") arrête aussitôt le programme avec un message clair, plutôt que d'allumer la mauvaise zone à une heure bizarre.
Le cœur du programme est entre : l'heure est-elle dans la plage d'une zone ? Une plage passe d'ordinaire par minuit (18:30 à 05:30), et en ticks elle peut aussi passer par 6:00, où le compte repart de 0. Quand le début est après la fin, la plage fait le tour : l'heure en fait partie si elle est après le début ou avant la fin.
local function entre(maintenant, debut, fin)
if debut <= fin then return maintenant >= debut and maintenant < fin end
return maintenant >= debut or maintenant < fin
end3. Les lampes
voulu tranche pour une zone : toujours allumée ou toujours éteinte dans un mode forcé, ses horaires sinon. mettre_a_jour compare avec l'état actuel de la zone (z.allumee) et n'appelle link.set que s'il change, avec true ou false (15 ou 0). Le premier appel après le démarrage envoie toutes les zones : les lampes sont justes dès le départ.
local function mettre_a_jour(maintenant)
for _, z in ipairs(ZONES) do
local allumee = voulu(z, maintenant)
if allumee ~= z.allumee then
z.allumee = allumee
link.set(z.a, z.b, allumee)
end
end
endlink.set arrête le programme avec unknown item '...' quand un identifiant d'objet est faux : vérifiez d'abord ceux des réglages si cela arrive.
4. Le bouton
Un bouton envoie un évènement redstone quand on l'enfonce et un autre quand il ressort. Pour n'agir qu'une fois par appui, le programme retient l'entrée à l'évènement précédent et ne réagit que quand elle passe d'éteinte à allumée :
local function sur_redstone()
local enfonce = rs.get(BOUTON) > 0
if enfonce and not appuye then mode_suivant() end
appuye = enfonce
endChaque appui passe au mode suivant : AUTO, TOUT ALLUMÉ, TOUT ÉTEINT, puis AUTO à nouveau. Un clic sur la ligne du mode à l'écran fait de même, pratique sur un moniteur.
5. L'écran d'état
L'heure est dessinée avec gfx.text en taille 4, avec un soleil ou une lune à côté (deux disques : un blanc, et un noir qui le croque). Elle n'est redessinée que quand la minute change. Les zones sont du texte simple, en jaune quand elles sont allumées, avec leurs horaires, et la dernière ligne annonce la suite, trouvée par prochain_changement : le début ou la fin la plus proche parmi les zones, compté avec % 24000 pour qu'une heure après minuit vienne après 23:00.
6. La boucle principale
Chaque tour de boucle met à jour les lampes et l'écran, puis attend un évènement : la minuterie d'une seconde, le bouton ou un clic. Une minute du monde dure moins d'une seconde réelle (1000 ticks par heure, 20 ticks par seconde) : l'horloge de l'écran avance d'une ou deux minutes à chaque tour.
Le programme complet
-- Éclairage jour et nuit : les lampes de chaque zone s'allument au crépuscule et
-- s'éteignent à l'aube, par des fréquences de Liaison de redstone de Create. Un bouton sur
-- l'ordinateur (ou un clic sur la ligne du mode) passe de AUTO à TOUT ALLUMÉ et TOUT ÉTEINT.
-- 1. Réglages : chaque zone est une fréquence (ses deux objets) et ses horaires
local ZONES = {
{nom = "RUES", a = "minecraft:glowstone_dust", b = "minecraft:iron_ingot", allume = "18:30", eteint = "05:30"},
{nom = "GARE", a = "minecraft:glowstone_dust", b = "minecraft:gold_ingot", allume = "18:00", eteint = "06:30"},
{nom = "USINE", a = "minecraft:glowstone_dust", b = "minecraft:copper_ingot", allume = "19:00", eteint = "05:00"},
{nom = "BOUTIQUES", a = "minecraft:glowstone_dust", b = "minecraft:emerald", allume = "18:30", eteint = "23:00"},
}
local BOUTON = "top"
local MODES = {"AUTO", "TOUT ALLUMÉ", "TOUT ÉTEINT"}
local mode = 1 -- numéro dans MODES
local appuye = false -- l'entrée du bouton au dernier évènement : un appui passe d'éteint à allumé
local heure_affichee = nil -- l'heure à l'écran, redessinée quand elle change
-- 2. L'heure : os.day_time compte les ticks depuis 6:00, 1000 ticks par heure
local function ticks_de(hhmm)
local p = string.split(hhmm, ":")
local h, m = tonumber(p[1]), tonumber(p[2])
if #p ~= 2 or h == nil or m == nil then error("heure invalide '" .. hhmm .. "', écrivez-la comme 18:30") end
return ((h - 6) % 24) * 1000 + m * 1000 // 60
end
local function heure_de(t)
return string.format("%02d:%02d", (t // 1000 + 6) % 24, t % 1000 * 60 // 1000)
end
-- maintenant est-il dans [debut, fin) ? La plage peut passer par 6:00, où les ticks repartent de 0
local function entre(maintenant, debut, fin)
if debut <= fin then return maintenant >= debut and maintenant < fin end
return maintenant >= debut or maintenant < fin
end
-- 3. Les lampes
local function voulu(z, maintenant)
if MODES[mode] == "TOUT ALLUMÉ" then return true end
if MODES[mode] == "TOUT ÉTEINT" then return false end
return entre(maintenant, z.debut, z.fin)
end
-- N'envoie une fréquence que quand sa zone change
local function mettre_a_jour(maintenant)
for _, z in ipairs(ZONES) do
local allumee = voulu(z, maintenant)
if allumee ~= z.allumee then
z.allumee = allumee
link.set(z.a, z.b, allumee)
end
end
end
local function mode_suivant()
mode = mode % #MODES + 1
end
-- Le prochain changement en mode AUTO : le début ou la fin la plus proche parmi les zones
local function prochain_changement(maintenant)
local meilleur, texte = 24000, ""
for _, z in ipairs(ZONES) do
local quand, quoi = z.debut, ", allumage " .. z.allume
if z.allumee then quand, quoi = z.fin, ", extinction " .. z.eteint end
local attente = (quand - maintenant) % 24000
if attente < meilleur then meilleur, texte = attente, z.nom .. quoi end
end
return texte
end
-- Le bouton : un évènement "redstone" arrive à l'appui et au relâchement
local function sur_redstone()
local enfonce = rs.get(BOUTON) > 0
if enfonce and not appuye then mode_suivant() end
appuye = enfonce
end
-- 4. L'écran : une grande horloge et le ciel en gfx, les zones en texte
local W = gfx.size().w
local function dessiner_ciel(maintenant)
local x, y = W - 16, 14
gfx.rect(x - 13, y - 13, 27, 27, "black", true)
if entre(maintenant, 12500, 23500) then
gfx.circle(x, y, 8, "white", true) -- la lune : un disque...
gfx.circle(x + 4, y - 3, 7, "black", true) -- ...croqué d'un côté
else
gfx.circle(x, y, 5, "yellow", true) -- le soleil et ses rayons
for k = 0, 7 do
local a = k * math.pi / 4
gfx.line(x + math.round(8 * math.cos(a)), y + math.round(8 * math.sin(a)),
x + math.round(11 * math.cos(a)), y + math.round(11 * math.sin(a)), "yellow")
end
end
end
local function ecrire_en(x, y, texte, couleur)
term.set_cursor(x, y)
term.set_fg(couleur)
term.write(texte)
end
local function dessiner(maintenant)
local heure = heure_de(maintenant)
if heure ~= heure_affichee then
gfx.rect(1, 1, W - 30, 26, "black", true)
gfx.text(6, 5, heure, "white", 4)
dessiner_ciel(maintenant)
heure_affichee = heure
end
term.set_cursor(1, 4)
term.clear_line()
local couleur = term.colors.lime
if mode > 1 then couleur = term.colors.orange end
ecrire_en(2, 4, "Mode : " .. MODES[mode], couleur)
ecrire_en(22, 4, "(bouton : suivant)", term.colors.gray)
for i, z in ipairs(ZONES) do
local y = 5 + i
term.set_cursor(1, y)
term.clear_line()
local c = term.colors.gray
if z.allumee then c = term.colors.yellow end
ecrire_en(2, y, z.nom, c)
ecrire_en(12, y, z.allumee and "ALLUMÉ" or "éteint", c)
ecrire_en(20, y, z.allume .. "-" .. z.eteint, term.colors.light_gray)
end
term.set_cursor(1, 11)
term.clear_line()
if mode == 1 then
ecrire_en(2, 11, "Ensuite : " .. prochain_changement(maintenant), term.colors.light_gray)
else
ecrire_en(2, 11, "Forcé : bouton pour revenir en AUTO", term.colors.orange)
end
end
-- 5. Boucle principale : un coup d'œil à l'horloge chaque seconde, le bouton tout de suite
for _, z in ipairs(ZONES) do
z.debut, z.fin = ticks_de(z.allume), ticks_de(z.eteint)
end
term.clear()
gfx.clear()
appuye = rs.get(BOUTON) > 0
local minuterie = os.start_timer(1)
while true do
local maintenant = os.day_time()
mettre_a_jour(maintenant)
dessiner(maintenant)
local e = os.pull_event()
if e.name == "timer" and e.id == minuterie then
minuterie = os.start_timer(1)
elseif e.name == "redstone" then
sur_redstone()
elseif e.name == "click" and e.y == 4 then
mode_suivant()
end
endÀ quoi ça ressemble
Le même code sur un Microcontrôleur, avec l'heure mise à la main à 19:42 (13700 ticks) au lieu de os.day_time, et sans Liaison de redstone à piloter :
local ZONES = {
{nom = "RUES", allume = "18:30", eteint = "05:30"},
{nom = "GARE", allume = "18:00", eteint = "06:30"},
{nom = "USINE", allume = "19:00", eteint = "05:00"},
{nom = "BOUTIQUES", allume = "18:30", eteint = "23:00"},
}
local MODES = {"AUTO", "TOUT ALLUMÉ", "TOUT ÉTEINT"}
local mode = 1
local heure_affichee = nil
local function ticks_de(hhmm)
local p = string.split(hhmm, ":")
return ((tonumber(p[1]) - 6) % 24) * 1000 + tonumber(p[2]) * 1000 // 60
end
local function heure_de(t)
return string.format("%02d:%02d", (t // 1000 + 6) % 24, t % 1000 * 60 // 1000)
end
local function entre(maintenant, debut, fin)
if debut <= fin then return maintenant >= debut and maintenant < fin end
return maintenant >= debut or maintenant < fin
end
local function prochain_changement(maintenant)
local meilleur, texte = 24000, ""
for _, z in ipairs(ZONES) do
local quand, quoi = z.debut, ", allumage " .. z.allume
if z.allumee then quand, quoi = z.fin, ", extinction " .. z.eteint end
local attente = (quand - maintenant) % 24000
if attente < meilleur then meilleur, texte = attente, z.nom .. quoi end
end
return texte
end
local W = gfx.size().w
local function dessiner_ciel(maintenant)
local x, y = W - 16, 14
gfx.rect(x - 13, y - 13, 27, 27, "black", true)
if entre(maintenant, 12500, 23500) then
gfx.circle(x, y, 8, "white", true)
gfx.circle(x + 4, y - 3, 7, "black", true)
else
gfx.circle(x, y, 5, "yellow", true)
for k = 0, 7 do
local a = k * math.pi / 4
gfx.line(x + math.round(8 * math.cos(a)), y + math.round(8 * math.sin(a)),
x + math.round(11 * math.cos(a)), y + math.round(11 * math.sin(a)), "yellow")
end
end
end
local function ecrire_en(x, y, texte, couleur)
term.set_cursor(x, y)
term.set_fg(couleur)
term.write(texte)
end
local function dessiner(maintenant)
local heure = heure_de(maintenant)
if heure ~= heure_affichee then
gfx.rect(1, 1, W - 30, 26, "black", true)
gfx.text(6, 5, heure, "white", 4)
dessiner_ciel(maintenant)
heure_affichee = heure
end
term.set_cursor(1, 4)
term.clear_line()
local couleur = term.colors.lime
if mode > 1 then couleur = term.colors.orange end
ecrire_en(2, 4, "Mode : " .. MODES[mode], couleur)
ecrire_en(22, 4, "(bouton : suivant)", term.colors.gray)
for i, z in ipairs(ZONES) do
local y = 5 + i
term.set_cursor(1, y)
term.clear_line()
local c = term.colors.gray
if z.allumee then c = term.colors.yellow end
ecrire_en(2, y, z.nom, c)
ecrire_en(12, y, z.allumee and "ALLUMÉ" or "éteint", c)
ecrire_en(20, y, z.allume .. "-" .. z.eteint, term.colors.light_gray)
end
term.set_cursor(1, 11)
term.clear_line()
if mode == 1 then
ecrire_en(2, 11, "Ensuite : " .. prochain_changement(maintenant), term.colors.light_gray)
else
ecrire_en(2, 11, "Forcé : bouton pour revenir en AUTO", term.colors.orange)
end
end
local maintenant = 13700 -- 19:42
for _, z in ipairs(ZONES) do
z.debut, z.fin = ticks_de(z.allume), ticks_de(z.eteint)
z.allumee = entre(maintenant, z.debut, z.fin)
end
term.clear()
gfx.clear()
dessiner(maintenant)
Le tester
- Mettez les Liaisons de redstone en mode réception (accroupi et clic droit, ou clic droit avec une Clé) avec les bons objets, et lancez le programme. À midi, toutes les zones sont éteintes et la dernière ligne affiche
Ensuite : GARE, allumage 18:00. Avancez l'horloge avec les commandes du jeu et regardez les zones suivre en moins d'une seconde :
/time set 12100 /time set 12600 /time set 17100 /time set 23600
Soit 18:06 (la gare s'allume), 18:36 (les rues et les boutiques), 23:06 (les boutiques s'éteignent) et 05:36 (seule la gare est encore allumée).
- Appuyez sur le bouton : TOUT ALLUMÉ, toutes les lampes s'allument quelle que soit l'heure. Encore : TOUT ÉTEINT. Encore : retour en AUTO, et chaque zone retrouve ses horaires.
- Dormez dans un lit la nuit : au matin, les lampes s'éteignent d'elles-mêmes, parce que le programme lit l'heure au lieu de la compter.
- Cassez un instant l'arbre de l'ordinateur : le programme se fige avec l'ordinateur et les fréquences gardent leur dernière valeur ; la rotation revenue, il continue. Un redémarrage (Ctrl+R) est différent : toutes les fréquences sont relâchées, les lampes s'éteignent un instant, puis le programme renvoie toutes les zones.
Variantes
- Un capteur de lumière du jour au lieu de l'horloge. Les orages assombrissent le ciel sans changer l'heure. Posez un capteur de lumière du jour contre l'ordinateur et allumez les rues quand
rs.get("left")passe sous un niveau de votre choix : il lit la vraie lumière, pluie comprise. - Un forçage par zone. Rendez les lignes des zones cliquables (
e.y == 5 + i) et donnez à chaque zone son propre mode, affiché à côté de son nom. - Une Liaison d'affichage.
display.set({heure_de(maintenant), prochain_changement(maintenant)})met l'heure et le prochain changement sur un Panneau d'affichage de Create, à la gare, par une Liaison d'affichage posée contre l'ordinateur (display). - Une zone proche sans liaison. Pour des lampes juste à côté de l'ordinateur,
rs.set("left", allumee)fait le travail d'une fréquence. - Prévenir avant la fermeture. Faites clignoter les boutiques à 22:55 : dans
mettre_a_jour, quand l'heure est à quelques minutes dez.fin, envoyeztrueetfalseune seconde sur deux.