kairo-gokko (24) リレー 3



いやー、なにはともあれ NOT ゲートが動きましたね。

とにかく動くということが分かったので、ハリボテ部分をまともな形に修正していきます。


現状ではどこがハリボテかというと、リレーによるスイッチへの作用が2段階以上になる次のような回路が正しく動いてくれません。

f:id:sonota88:20200315124541p:plain

ではどうするか。 状態更新の処理を繰り返せばよさそうですね。

前回は 2回実行されるように単純にコピペしましたが、 これをループの形に書き直します。

# before
circuit.update_tuden_state()
circuit.update_not_relays_state()
circuit.update_lamps_state()

circuit.update_tuden_state()
circuit.update_not_relays_state()
circuit.update_lamps_state()

# after
while ???
  circuit.update_tuden_state()
  circuit.update_not_relays_state()
  circuit.update_lamps_state()
end

ループにするとなると、今度は停止条件を考えなければいけません。

スイッチの状態更新
→ 通電状態の更新
→ リレーの状態更新
→ スイッチの状態更新
→ 通電状態の更新
→ …

状態更新はこんな順番で実行され、繰り返されることになります。

ランプの状態更新は通電状態から影響を受けることはあっても他に影響を与えることはないため無視します。

リレーの状態更新と、リレーによるスイッチの更新はひとセット (リレーの状態が変わったら必ずスイッチの状態も変わる) ということにしてまとめましょうか。

スイッチの状態更新
→ 通電状態の更新
→ リレーとスイッチの状態更新
→ 通電状態の更新
→ リレーとスイッチの状態更新
→ 通電状態の更新
→ …

ふーむ。となると、判断するタイミングは

  • 通電状態の更新がなかったらループを抜ける
    • 通電判定した結果、状態が変化するエッジがあったかを検出する
  • リレーとスイッチの状態更新がなかったらループを抜ける
    • 状態が変化するリレーまたはスイッチがあったかを検出する

の 2パターンありそうです。どっちがいいんでしょうか。


しばし考え……

考えた結果、「どちらでもよさそう」と思えました。


通電の状態更新の結果で判断する場合:

  • (1) 通電の状態を更新する
    • その結果、状態が変化するエッジがあった
    • リレー・スイッチの状態変更を行う必要がある
    • ループを抜けない
  • (2) リレー・スイッチの状態を更新する
  • (3) 通電の状態を更新する
    • その結果、状態が変化するエッジがなかった
    • リレー・スイッチの状態変更を行う必要がない
    • ループを抜ける

リレーまたはスイッチの状態更新の結果で判断する場合:

  • (1) リレー・スイッチの状態を更新する
    • その結果、状態が変化するリレーまたはスイッチがあった
    • 通電の状態変更を行う必要がある
    • ループを抜けない
  • (2) 通電の状態を更新する
  • (3) リレー・スイッチの状態を更新する
    • その結果、状態が変化するリレーまたはスイッチがなかった
    • 通電の状態変更を行う必要がない
    • ループを抜ける

ちょっと不思議な感じ……というか、ちゃんと理解できてないんじゃないかという気もしますが、ひとまず「どっちでもよい」という判断で進めます。

どっちでもよいなら簡単な方がいいですね。 通電判定の方はエッジが多いとちょっとめんどくさそうな気がします。 ややこしいことをやっている部分なので、できればそっとしておきたい。 リレーorスイッチの状態の変化を使うことにしましょう。

リレーとスイッチ 2つの内ではどっちかというと…… 影響の流れは リレー → スイッチ → エッジ であって、 エッジの通電に直接影響を与えているのはリレーではなくスイッチなので、 スイッチの状態の変化を使う、ということにします。


スイッチの状態の変化を使うといったん決めた上で改めて考えてみると……

スイッチの状態更新
→ 通電状態の更新
   (A) ここで判断するか、または
→ リレーとスイッチの状態更新
   (B) ここで判断するか
→ …

(B) のタイミングで判断する場合、通電状態の変化がなければ リレーとスイッチの状態の変化もない (リレーとスイッチの状態更新の処理は実行されるが空振りする)。

……と考えると……?   悪くない気も、やっぱりなんか怪しい感じもしますが、とにかく作って動かしてみます。


では修正。

同じ処理が 2箇所(初回の更新とメインループ内)あるので先にリファクタリングしておきましょうか。

--- a/main.rb
+++ b/main.rb
@@ -55,6 +55,16 @@ def on_push_switch(pushed_switch)
   pushed_switch.toggle()
 end
 
+def update_tuden_relay_switch_lamp(circuit)
+  circuit.update_tuden_state()
+  circuit.update_not_relays_state()
+  circuit.update_lamps_state()
+
+  circuit.update_tuden_state()
+  circuit.update_not_relays_state()
+  circuit.update_lamps_state()
+end
+
 def draw(view, circuit, mx, my)
   view.draw_grid(11, 11)
 
@@ -121,13 +131,7 @@ def main_loop(circuit, view)
   end
 
   if switch_changed
-    circuit.update_tuden_state()
-    circuit.update_not_relays_state()
-    circuit.update_lamps_state()
-
-    circuit.update_tuden_state()
-    circuit.update_not_relays_state()
-    circuit.update_lamps_state()
+    update_tuden_relay_switch_lamp(circuit)
   end
 
   draw(view, circuit, mx, my)
@@ -137,13 +141,7 @@ end
 
 circuit = Circuit.from_plain(parse_json($data_json))
 
-circuit.update_tuden_state()
-circuit.update_not_relays_state()
-circuit.update_lamps_state()
-
-circuit.update_tuden_state()
-circuit.update_not_relays_state()
-circuit.update_lamps_state()
+update_tuden_relay_switch_lamp(circuit)
 
 view = View.new(PPC)

微妙な名前のメソッドに抽出しました。 かっこつけて何やってるかよく分からない名前にするくらいならストレートな名前の方がまだよい……ということにしておきたい……。


スイッチの状態変化の検出は ChildCircuit#update_not_relays で行います。

--- a/child_circuit.rb
+++ b/child_circuit.rb
@@ -220,13 +220,22 @@ class ChildCircuit
   end
 
   def update_not_relays(circuit)
+    switch_changed = false
+
     @not_relays.each { |not_relay|
       edge = @edges.find { |edge| edge.include_pos?(not_relay.pos) }
       not_relay.update(edge.on?)
 
       neighbor_switch = circuit.find_neighbor_switch(not_relay.pos)
+      state_before_update = neighbor_switch.on?
       neighbor_switch.update(! not_relay.on?)
+
+      if neighbor_switch.on? != state_before_update
+        switch_changed = true
+      end
     }
+
+    switch_changed
   end
 
   def pretty_inspect

Circuit#update_not_relays_state も似た感じで。

--- a/circuit.rb
+++ b/circuit.rb
@@ -389,8 +389,16 @@ class Circuit
   end
 
   def update_not_relays_state
+    switch_changed = false
+
     @child_circuits.each { |child_circuit|
-      child_circuit.update_not_relays(self)
+      _switch_changed = child_circuit.update_not_relays(self)
+
+      if _switch_changed
+        switch_changed = true
+      end
     }
+
+    switch_changed
   end
 end

スイッチの変化が得られるようになったら、 それを使ってループを抜けるようにします。

# main.rb

def update_tuden_relay_switch_lamp(circuit)
  switch_changed = true

  while switch_changed
    circuit.update_tuden_state()
    switch_changed = circuit.update_not_relays_state()
    circuit.update_lamps_state()
  end
end

f:id:sonota88:20200315143410g:plain

うまく動いているようです……!

もう一つ増やしてもいけるはず!

f:id:sonota88:20200315143609g:plain

ヨシ!


以下の iframe で実際に動かせます。

こちらも同じものです。
https://sonota88.github.io/kairo-gokko/pages/24/index.html