Create: Computing AgesDoc Brass
Recettes : vrais projets

Carrefour intelligent

Deux Feux tricolores et deux Boucles de détection menés par une machine à états qui sert la route qui attend, avec un vert minimum, des phases orange et rouge-orange, et l'orange clignotant la nuit.

Un carrefour dans votre ville : la route principale porte les convois de wagonnets et la circulation des villageois, une route secondaire arrive des fermes. Un cycle fixe arrête la route principale toutes les trente secondes, même quand personne n'attend sur la route secondaire. Ce projet fait ce que font les vrais contrôleurs : la route principale garde le vert, et une Boucle de détection dans la route secondaire ne demande le vert que quand quelque chose se tient dessus. Chaque changement passe par l'orange, un instant de rouge des deux côtés pour vider le carrefour, puis rouge et orange ensemble avant le vert, comme les feux britanniques et allemands. La nuit, les deux feux clignotent à l'orange.

Le programme est une machine à états : une variable retient la phase en cours, et une fonction appelée quatre fois par seconde décide s'il est temps de passer à la suivante. C'est la façon la plus claire d'écrire tout ce qui avance par étapes : des feux, une gare, un ascenseur, une suite de machines.

Ce qu'il vous faut

  • Un Microcontrôleur (ou n'importe quel ordinateur à partir du Mini-ordinateur) : il est petit, et son port de Câble de données atteint tous les blocs du carrefour. Son écran fait 40 x 12 ; le programme y dessine les deux feux. Il est entraîné par le dessous.
  • Deux Feux tricolores, un face à chaque route (ou plus : chaque route prend une liste de feux). Un feu réglé par un ordinateur reste sous son contrôle et ignore la redstone jusqu'à traffic_light.release() (Feu tricolore).
  • Deux Boucles de détection, encastrées dans la route quelques blocs avant le carrefour. Une boucle voit les créatures, les joueurs, les wagonnets, les bateaux et les véhicules de Create Aeronautics jusqu'à deux blocs au-dessus d'elle (Boucle de détection).
  • Du Câble de données sous la route, qui touche le Microcontrôleur, les deux feux et les deux boucles. Chaque bloc du câble est nommé d'après son type et sa position : traffic_light@10,65,4, inductive_loop@10,63,9.

Vu de dessus :

               | route  |
               | princ. |
               |   B1   |        B1, B2 : Boucles de détection dans la route,
               |        |                 quelques blocs avant le carrefour
    -----------+        +-----------
     route  B2             F1        F1 : Feu tricolore de la route principale
     second.               F2        F2 : Feu tricolore de la route secondaire
    -----------+        +-----------
               |        | [M]        M : le Microcontrôleur au coin
               |        |
         un Câble de données sous la route relie M, F1, F2, B1 et B2

Le programme, étape par étape

1. Les routes et les durées

Chaque route a un nom, une liste de feux et une liste de boucles. La première route est la route principale : elle récupère le vert quand personne n'attend nulle part.

Brass
local ROUTES = {
  {nom = "Route principale", feux = {"traffic_light@10,65,4"}, boucles = {"inductive_loop@10,63,9"}},
  {nom = "Route secondaire", feux = {"traffic_light@14,65,0"}, boucles = {"inductive_loop@19,63,0"}},
}
local VERT_MIN = 8         -- secondes de vert avant que le feu puisse changer
local VERT_MAX = 30        -- secondes de vert au plus pendant que l'autre route attend
local ORANGE = 3           -- secondes d'orange
local TOUT_ROUGE = 1       -- secondes avec les deux routes au rouge, pour vider le carrefour
local ROUGE_ORANGE = 1.5   -- secondes de rouge et orange avant le vert
local DEBUT_NUIT = 13000   -- os.day_time() à la tombée de la nuit (19 h)
local FIN_NUIT = 23000     -- os.day_time() à l'aube (5 h)
local PAS = 0.25           -- secondes entre deux coups d'œil aux boucles

VERT_MIN empêche un feu de changer de nouveau juste après être passé au vert ; VERT_MAX garantit qu'une route principale chargée ne laisse pas une route secondaire au rouge pour toujours.

Pour trouver les noms de vos blocs, lancez le programme avec liste après son nom : il les affiche et s'arrête. L'exemple devices (devices) les montre aussi, sur une carte.

Brass
if arg ~= nil and arg[1] == "liste" then
  for _, sorte in ipairs({"traffic_light", "inductive_loop"}) do
    for _, nom in ipairs(peripheral.list(sorte)) do print(nom) end
  end
  return
end

2. Parler aux blocs

Le programme ne prend jamais les blocs une fois pour toutes. Il les appelle par leur nom avec peripheral.call, à travers pcall : un feu cassé par un creeper ou une boucle dans un chunk déchargé n'arrête pas tout le carrefour. Le nom va dans muets, et l'écran le signale.

Brass
local aspects = {"off", "off"}   -- ce que montre chaque route
local comptes = {0, 0}           -- ce qui se tient sur les boucles de chaque route
local attente = {false, false}   -- true quand quelque chose attend sur une route
local muets = {}                 -- noms des blocs qui n'ont pas répondu

local function regler_route(i, aspect)
  aspects[i] = aspect
  for _, nom in ipairs(ROUTES[i].feux) do
    local r = pcall(peripheral.call, nom, "set", aspect)
    if r.ok then muets[nom] = nil else muets[nom] = true end
  end
end

local function lire_boucles(i)
  local total, toutes_ok = 0, true
  for _, nom in ipairs(ROUTES[i].boucles) do
    local r = pcall(peripheral.call, nom, "count")
    if r.ok then
      total = total + r.value
      muets[nom] = nil
    else
      muets[nom] = true
      toutes_ok = false
    end
  end
  comptes[i] = total
  attente[i] = total > 0 or not toutes_ok
end

Une boucle qui ne répond pas compte comme une voiture qui attend. C'est le choix prudent : la route continue d'avoir son tour à intervalles réguliers, au lieu de ne plus jamais avoir le vert parce que son détecteur est cassé.

Les aspects qu'un Feu tricolore comprend sont "off", "red", "red_amber", "amber", "green" et "flashing" (orange clignotant). Tout autre mot arrête l'appel avec bad aspect 'yellow' (off, red, red_amber, amber, green, flashing).

3. La machine à états

L'état tient en trois variables : la phase, la route qui a (ou va avoir) le vert, et le moment où la phase a commencé, lu avec os.clock() (les secondes depuis le démarrage de l'ordinateur). entrer passe à une phase et note l'heure :

Brass
local phase = "vert"    -- "vert", "orange", "tout_rouge", "rouge_orange" ou "nuit"
local passe = 1         -- la route qui a, ou va avoir, le vert
local depuis = 0        -- os.clock() au début de la phase
local force = false     -- clignotant forcé à la main (la touche f)

local function entrer(nouvelle)
  phase = nouvelle
  depuis = os.clock()
end

local function est_la_nuit()
  local t = os.day_time()
  return force or (t >= DEBUT_NUIT and t < FIN_NUIT)
end

etape tourne quatre fois par seconde. Elle lit les boucles, puis regarde la phase en cours :

phasefeux de la route passepasse à la suite quandvers
vertvertl'autre route attend et passe est vide (ou VERT_MAX est écoulé), après VERT_MINorange
orangeorangeORANGE secondes sont écouléestout_rouge
tout_rougerougeTOUT_ROUGE secondes sont écoulées (l'autre route devient passe)rouge_orange
rouge_orangerouge et orangeROUGE_ORANGE secondes sont écouléesvert
nuitclignotantla nuit finittout_rouge
Brass
local function etape()
  lire_boucles(1)
  lire_boucles(2)
  local autre = 3 - passe
  local ecoule = os.clock() - depuis
  if est_la_nuit() then
    if phase ~= "nuit" then
      regler_route(1, "flashing")
      regler_route(2, "flashing")
      entrer("nuit")
    end
  elseif phase == "nuit" then
    regler_route(1, "red")
    regler_route(2, "red")
    passe = 2                     -- pour que la route principale ait le prochain vert
    entrer("tout_rouge")
  elseif phase == "vert" then
    local changer = false
    if ecoule >= VERT_MIN then
      if attente[autre] and (not attente[passe] or ecoule >= VERT_MAX) then changer = true end
      if passe ~= 1 and not attente[passe] then changer = true end   -- retour à la route principale
    end
    if changer then
      regler_route(passe, "amber")
      entrer("orange")
    end
  elseif phase == "orange" and ecoule >= ORANGE then
    regler_route(passe, "red")
    entrer("tout_rouge")
  elseif phase == "tout_rouge" and ecoule >= TOUT_ROUGE then
    passe = autre
    regler_route(passe, "red_amber")
    entrer("rouge_orange")
  elseif phase == "rouge_orange" and ecoule >= ROUGE_ORANGE then
    regler_route(passe, "green")
    entrer("vert")
  end
end

3 - passe est l'autre route : 2 quand passe vaut 1, 1 quand passe vaut 2. Les feux ne changent que quand la phase change : le reste du temps, etape ne coûte presque rien.

4. L'écran

La moitié gauche de l'écran liste les routes avec leur aspect et leur boucle ; la moitié droite dessine les deux têtes de feux avec gfx : un corps gris et trois lampes rondes, allumées ou noires. En mode clignotant, la lampe orange suit os.clock(), allumée une demi-seconde, éteinte une demi-seconde.

Brass
local TEXTE_ASPECT = {off = "ÉTEINT", red = "ROUGE", red_amber = "ROUGE+ORANGE", amber = "ORANGE",
  green = "VERT", flashing = "CLIGNOTANT"}
local COULEUR_ASPECT = {off = "gray", red = "red", red_amber = "orange", amber = "orange",
  green = "lime", flashing = "orange"}
local TEXTE_PHASE = {vert = "vert", orange = "orange", tout_rouge = "tout rouge",
  rouge_orange = "rouge et orange", nuit = "nuit, clignotant"}

local function heure_texte()
  local t = (os.day_time() + 6000) % 24000  -- 0 = 6 h du matin
  return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end

local function lampe(x, y, allumee, couleur)
  if allumee then gfx.circle(x, y, 8, couleur, true) else gfx.circle(x, y, 8, "black", true) end
end

local function dessiner_feu(x, aspect, etiquette)
  local clignote = math.floor(os.clock() * 2) % 2 == 0
  gfx.rect(x, 14, 26, 62, "gray", true)
  lampe(x + 13, 25, aspect == "red" or aspect == "red_amber", "red")
  lampe(x + 13, 45, aspect == "amber" or aspect == "red_amber" or (aspect == "flashing" and clignote), "orange")
  lampe(x + 13, 65, aspect == "green", "lime")
  gfx.text(x + 11, 81, etiquette, "light_gray")
end

local function dessiner()
  local l = term.get_size().w
  term.set_bg(term.colors.black)
  term.clear()
  gfx.clear()
  term.set_cursor(1, 1)
  term.set_bg(term.colors.blue)
  term.set_fg(term.colors.white)
  term.write(string.format(" %-" .. (l - 8) .. "s%6s ", "CARREFOUR", heure_texte()))
  term.set_bg(term.colors.black)
  for i, route in ipairs(ROUTES) do
    term.set_cursor(2, i * 3)
    term.set_fg(term.colors.white)
    term.write(i .. " " .. route.nom)
    term.set_cursor(4, i * 3 + 1)
    term.set_fg(term.colors[COULEUR_ASPECT[aspects[i]]])
    term.write(string.format("%-12s", TEXTE_ASPECT[aspects[i]]))
    term.set_fg(term.colors.light_gray)
    term.write("boucle : " .. comptes[i])
    dessiner_feu(l * 6 - 84 + (i - 1) * 40, aspects[i], tostring(i))
  end
  term.set_cursor(2, 9)
  term.set_fg(term.colors.light_gray)
  term.write(string.format("%s, %d s", TEXTE_PHASE[phase], math.floor(os.clock() - depuis)))
  local absents = 0
  for _, _ in pairs(muets) do absents = absents + 1 end
  if absents > 0 then
    term.set_cursor(2, 10)
    term.set_fg(term.colors.red)
    term.write(absents .. " bloc(s) muet(s)")
  end
  term.set_cursor(2, 11)
  term.set_fg(term.colors.gray)
  term.write("f : clignotant oui/non")
end

5. Le démarrage et la boucle principale

Au démarrage, la route principale reçoit le vert et la route secondaire le rouge. La boucle attend son minuteur, et la touche f, qui force le mode clignotant à la main : pratique pendant des travaux.

Brass
regler_route(1, "green")
regler_route(2, "red")
entrer("vert")
local minuteur = os.start_timer(PAS)
while true do
  local e = os.pull_event()
  if e.name == "timer" and e.id == minuteur then
    etape()
    dessiner()
    minuteur = os.start_timer(PAS)
  elseif e.name == "char" and e.char == "f" then
    force = not force
  end
end

Le programme complet

startup
-- Carrefour intelligent : deux routes, chacune avec ses Feux tricolores et ses
-- Boucles de détection. La route principale garde le vert jusqu'à ce que
-- quelque chose attende sur la route secondaire ; la nuit, tous les feux
-- clignotent à l'orange. "startup liste" affiche les noms des feux et des
-- boucles du câble.

local ROUTES = {
  {nom = "Route principale", feux = {"traffic_light@10,65,4"}, boucles = {"inductive_loop@10,63,9"}},
  {nom = "Route secondaire", feux = {"traffic_light@14,65,0"}, boucles = {"inductive_loop@19,63,0"}},
}
local VERT_MIN = 8         -- secondes de vert avant que le feu puisse changer
local VERT_MAX = 30        -- secondes de vert au plus pendant que l'autre route attend
local ORANGE = 3           -- secondes d'orange
local TOUT_ROUGE = 1       -- secondes avec les deux routes au rouge, pour vider le carrefour
local ROUGE_ORANGE = 1.5   -- secondes de rouge et orange avant le vert
local DEBUT_NUIT = 13000   -- os.day_time() à la tombée de la nuit (19 h)
local FIN_NUIT = 23000     -- os.day_time() à l'aube (5 h)
local PAS = 0.25           -- secondes entre deux coups d'œil aux boucles

if arg ~= nil and arg[1] == "liste" then
  for _, sorte in ipairs({"traffic_light", "inductive_loop"}) do
    for _, nom in ipairs(peripheral.list(sorte)) do print(nom) end
  end
  return
end

local TEXTE_ASPECT = {off = "ÉTEINT", red = "ROUGE", red_amber = "ROUGE+ORANGE", amber = "ORANGE",
  green = "VERT", flashing = "CLIGNOTANT"}
local COULEUR_ASPECT = {off = "gray", red = "red", red_amber = "orange", amber = "orange",
  green = "lime", flashing = "orange"}
local TEXTE_PHASE = {vert = "vert", orange = "orange", tout_rouge = "tout rouge",
  rouge_orange = "rouge et orange", nuit = "nuit, clignotant"}

local aspects = {"off", "off"}   -- ce que montre chaque route
local comptes = {0, 0}           -- ce qui se tient sur les boucles de chaque route
local attente = {false, false}   -- true quand quelque chose attend sur une route
local muets = {}                 -- noms des blocs qui n'ont pas répondu
local phase = "vert"             -- "vert", "orange", "tout_rouge", "rouge_orange" ou "nuit"
local passe = 1                  -- la route qui a, ou va avoir, le vert
local depuis = 0                 -- os.clock() au début de la phase
local force = false              -- clignotant forcé à la main (la touche f)

-- Blocs ---------------------------------------------------------------------------------

local function regler_route(i, aspect)
  aspects[i] = aspect
  for _, nom in ipairs(ROUTES[i].feux) do
    local r = pcall(peripheral.call, nom, "set", aspect)
    if r.ok then muets[nom] = nil else muets[nom] = true end
  end
end

local function lire_boucles(i)
  local total, toutes_ok = 0, true
  for _, nom in ipairs(ROUTES[i].boucles) do
    local r = pcall(peripheral.call, nom, "count")
    if r.ok then
      total = total + r.value
      muets[nom] = nil
    else
      muets[nom] = true
      toutes_ok = false
    end
  end
  comptes[i] = total
  attente[i] = total > 0 or not toutes_ok
end

-- Machine à états -----------------------------------------------------------------------

local function entrer(nouvelle)
  phase = nouvelle
  depuis = os.clock()
end

local function est_la_nuit()
  local t = os.day_time()
  return force or (t >= DEBUT_NUIT and t < FIN_NUIT)
end

local function etape()
  lire_boucles(1)
  lire_boucles(2)
  local autre = 3 - passe
  local ecoule = os.clock() - depuis
  if est_la_nuit() then
    if phase ~= "nuit" then
      regler_route(1, "flashing")
      regler_route(2, "flashing")
      entrer("nuit")
    end
  elseif phase == "nuit" then
    regler_route(1, "red")
    regler_route(2, "red")
    passe = 2                     -- pour que la route principale ait le prochain vert
    entrer("tout_rouge")
  elseif phase == "vert" then
    local changer = false
    if ecoule >= VERT_MIN then
      if attente[autre] and (not attente[passe] or ecoule >= VERT_MAX) then changer = true end
      if passe ~= 1 and not attente[passe] then changer = true end   -- retour à la route principale
    end
    if changer then
      regler_route(passe, "amber")
      entrer("orange")
    end
  elseif phase == "orange" and ecoule >= ORANGE then
    regler_route(passe, "red")
    entrer("tout_rouge")
  elseif phase == "tout_rouge" and ecoule >= TOUT_ROUGE then
    passe = autre
    regler_route(passe, "red_amber")
    entrer("rouge_orange")
  elseif phase == "rouge_orange" and ecoule >= ROUGE_ORANGE then
    regler_route(passe, "green")
    entrer("vert")
  end
end

-- Écran ------------------------------------------------------------------------------------

local function heure_texte()
  local t = (os.day_time() + 6000) % 24000  -- 0 = 6 h du matin
  return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end

local function lampe(x, y, allumee, couleur)
  if allumee then gfx.circle(x, y, 8, couleur, true) else gfx.circle(x, y, 8, "black", true) end
end

local function dessiner_feu(x, aspect, etiquette)
  local clignote = math.floor(os.clock() * 2) % 2 == 0
  gfx.rect(x, 14, 26, 62, "gray", true)
  lampe(x + 13, 25, aspect == "red" or aspect == "red_amber", "red")
  lampe(x + 13, 45, aspect == "amber" or aspect == "red_amber" or (aspect == "flashing" and clignote), "orange")
  lampe(x + 13, 65, aspect == "green", "lime")
  gfx.text(x + 11, 81, etiquette, "light_gray")
end

local function dessiner()
  local l = term.get_size().w
  term.set_bg(term.colors.black)
  term.clear()
  gfx.clear()
  term.set_cursor(1, 1)
  term.set_bg(term.colors.blue)
  term.set_fg(term.colors.white)
  term.write(string.format(" %-" .. (l - 8) .. "s%6s ", "CARREFOUR", heure_texte()))
  term.set_bg(term.colors.black)
  for i, route in ipairs(ROUTES) do
    term.set_cursor(2, i * 3)
    term.set_fg(term.colors.white)
    term.write(i .. " " .. route.nom)
    term.set_cursor(4, i * 3 + 1)
    term.set_fg(term.colors[COULEUR_ASPECT[aspects[i]]])
    term.write(string.format("%-12s", TEXTE_ASPECT[aspects[i]]))
    term.set_fg(term.colors.light_gray)
    term.write("boucle : " .. comptes[i])
    dessiner_feu(l * 6 - 84 + (i - 1) * 40, aspects[i], tostring(i))
  end
  term.set_cursor(2, 9)
  term.set_fg(term.colors.light_gray)
  term.write(string.format("%s, %d s", TEXTE_PHASE[phase], math.floor(os.clock() - depuis)))
  local absents = 0
  for _, _ in pairs(muets) do absents = absents + 1 end
  if absents > 0 then
    term.set_cursor(2, 10)
    term.set_fg(term.colors.red)
    term.write(absents .. " bloc(s) muet(s)")
  end
  term.set_cursor(2, 11)
  term.set_fg(term.colors.gray)
  term.write("f : clignotant oui/non")
end

-- Boucle principale -------------------------------------------------------------------------

regler_route(1, "green")
regler_route(2, "red")
entrer("vert")
local minuteur = os.start_timer(PAS)
while true do
  local e = os.pull_event()
  if e.name == "timer" and e.id == minuteur then
    etape()
    dessiner()
    minuteur = os.start_timer(PAS)
  elseif e.name == "char" and e.char == "f" then
    force = not force
  end
end

À quoi ressemble l'écran

Le code de dessin du programme sur le Microcontrôleur, au moment où la route principale passe à l'orange parce qu'un wagonnet attend sur la boucle de la route secondaire :

Brass
local ROUTES = {{nom = "Route principale"}, {nom = "Route secondaire"}}
local TEXTE_ASPECT = {off = "ÉTEINT", red = "ROUGE", red_amber = "ROUGE+ORANGE", amber = "ORANGE",
  green = "VERT", flashing = "CLIGNOTANT"}
local COULEUR_ASPECT = {off = "gray", red = "red", red_amber = "orange", amber = "orange",
  green = "lime", flashing = "orange"}
local TEXTE_PHASE = {vert = "vert", orange = "orange", tout_rouge = "tout rouge",
  rouge_orange = "rouge et orange", nuit = "nuit, clignotant"}
local aspects = {"amber", "red"}
local comptes = {0, 1}
local muets = {}
local phase = "orange"
local depuis = os.clock() - 1

local function heure_texte()
  local t = (os.day_time() + 6000) % 24000
  return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end

local function lampe(x, y, allumee, couleur)
  if allumee then gfx.circle(x, y, 8, couleur, true) else gfx.circle(x, y, 8, "black", true) end
end

local function dessiner_feu(x, aspect, etiquette)
  local clignote = math.floor(os.clock() * 2) % 2 == 0
  gfx.rect(x, 14, 26, 62, "gray", true)
  lampe(x + 13, 25, aspect == "red" or aspect == "red_amber", "red")
  lampe(x + 13, 45, aspect == "amber" or aspect == "red_amber" or (aspect == "flashing" and clignote), "orange")
  lampe(x + 13, 65, aspect == "green", "lime")
  gfx.text(x + 11, 81, etiquette, "light_gray")
end

local function dessiner()
  local l = term.get_size().w
  term.set_bg(term.colors.black)
  term.clear()
  gfx.clear()
  term.set_cursor(1, 1)
  term.set_bg(term.colors.blue)
  term.set_fg(term.colors.white)
  term.write(string.format(" %-" .. (l - 8) .. "s%6s ", "CARREFOUR", heure_texte()))
  term.set_bg(term.colors.black)
  for i, route in ipairs(ROUTES) do
    term.set_cursor(2, i * 3)
    term.set_fg(term.colors.white)
    term.write(i .. " " .. route.nom)
    term.set_cursor(4, i * 3 + 1)
    term.set_fg(term.colors[COULEUR_ASPECT[aspects[i]]])
    term.write(string.format("%-12s", TEXTE_ASPECT[aspects[i]]))
    term.set_fg(term.colors.light_gray)
    term.write("boucle : " .. comptes[i])
    dessiner_feu(l * 6 - 84 + (i - 1) * 40, aspects[i], tostring(i))
  end
  term.set_cursor(2, 9)
  term.set_fg(term.colors.light_gray)
  term.write(string.format("%s, %d s", TEXTE_PHASE[phase], math.floor(os.clock() - depuis)))
  local absents = 0
  for _, _ in pairs(muets) do absents = absents + 1 end
  if absents > 0 then
    term.set_cursor(2, 10)
    term.set_fg(term.colors.red)
    term.write(absents .. " bloc(s) muet(s)")
  end
  term.set_cursor(2, 11)
  term.set_fg(term.colors.gray)
  term.write("f : clignotant oui/non")
end

dessiner()
Écran
Écran

L'essayer

  1. Construisez le carrefour, lancez startup liste et recopiez les noms dans ROUTES : le feu et la boucle de la route principale sur la première ligne, ceux de la route secondaire sur la seconde.
  2. Lancez startup. La route principale est au vert, la route secondaire au rouge, et les deux boucles lisent 0.
  3. Montez sur la boucle de la route secondaire et attendez. Au bout de VERT_MIN secondes au plus, la route principale passe à l'orange, puis au rouge ; une seconde plus tard, la route secondaire montre rouge et orange, puis vert.
  4. Descendez de la boucle : après VERT_MIN secondes de vert, la route secondaire repasse par l'orange, et la route principale retrouve le vert.
  5. Occupez les deux boucles (avec un ami, ou une vache laissée sur l'une) : le vert alterne, chaque route le gardant au plus VERT_MAX secondes.
  6. Tapez f dans le terminal : les deux feux clignotent à l'orange. Tapez f de nouveau, ou attendez la nuit (/time set night) puis le matin : le carrefour repasse au rouge des deux côtés, puis au vert sur la route principale.
  7. Cassez la boucle de la route secondaire : l'écran signale 1 bloc(s) muet(s), et la route secondaire a toujours son tour à intervalles réguliers.

Variantes

  • Un bouton pour piétons. Un bouton au bord de la route, relié à l'ordinateur : dans etape, traitez rs.get("left") > 0 comme une voiture qui attend sur la route secondaire.
  • Compter la circulation. Comptez les fronts montants de chaque boucle (comptes[i] qui passe de 0 à plus) et ajoutez-les à un fichier toutes les heures : voir Enregistreur de données et graphique.
  • Quatre routes. Avec quatre arrivées, gardez les deux paires qui ne se croisent jamais (nord-sud, est-ouest) et donnez à chaque paire une liste de feux et de boucles : le programme ne change pas, ROUTES a juste des listes plus longues.
  • Rendre les feux à la redstone. Avant de quitter le programme, appelez traffic_light.release() sur chaque feu (peripheral.call(nom, "release")) : les feux suivent de nouveau la redstone, comme un Feu tricolore sans ordinateur. Sans cet appel, un feu garde le dernier aspect réglé par le programme, même après un redémarrage ou une fois l'ordinateur cassé.
  • Un passage à niveau. Remplacez la route secondaire par une voie de wagonnets : une Boucle de détection sous les rails voit chaque wagonnet, et la route passe au rouge quand l'un d'eux arrive.