前回は状態更新処理をループさせることで 2段、3段とリレーが連なった場合でも動くようにしました。
前回の回路はうまく動きましたが、そういえば無限ループになる場合はないのでしょうか?
あります。 たとえば次の回路では無限ループが発生します。
困ったことに初回の状態更新で無限ループが発生し、 いつまでたっても画面に何も描画されません。
うーむ、困りましたね…… とりあえずは無限ループが発生したら異常終了させときましょうか。 つまり、こういう状態更新が収束しないようなウロボロス的回路は不正とみなすことにするわけです。
--- a/main.rb +++ b/main.rb @@ -57,8 +57,11 @@ end def update_tuden_relay_switch_lamp(circuit) switch_changed = true + count = 0 while switch_changed + count += 1 + raise "Too many state updates" if 1000 < count + circuit.update_tuden_state() switch_changed = circuit.update_not_relays_state() circuit.update_lamps_state()
とりあえずはこれでいいかな……と思ってしばらくこれで進めていましたが、 次に挙げる 2つの理由により、無限ループになるような回路でも不正とみなさず動かし続けることにしました。
理由の1つめは、ループが止まらない回路があとで実際に登場してくるためです。
今作っているのは 2周目で、あとで必要になることがすでに分かっているので、 もうやってしまってもいいかなと。
しばらくは「そういう回路を描かなければいい」で回避してもいいんですけどね (プロトタイプのときは実際そうしてました)。 上に上げた回路の例も無限ループになる例として挙げただけであって、今そういう回路を動かしたいわけではありませんし。
なので、1つ目は今の時点ではちょっと弱い理由です。
もう一つの理由の説明のために、子回路が4つある前回の回路を動かした GIF 画像を再掲します。
これ、うまく動いて万々歳でしたね。すばらしい。
すばらしくはありますが、何かこう、物足りなさが……。
何が物足りないか?
一番左のスイッチをマウスでクリックして切り替えると、 4つの子回路の状態が瞬時に切り替わります。
瞬時に切り替わるのはなぜか。 それは、一番左のスイッチを切り替えると、 状態変更が収束するまでループして、 状態変更が止まったらようやくそこで結果だけをポンと描画しているからですね。
リレーによって順番に左から右へと影響が波及しているはずなのに、 最終的な結果だけが一瞬で表示される作りになっているため、 その過程が見えず、つまらないなと。 あと、せっかくがんばって(?)処理してるのにそれが見えないのはちょっともったいないような。
また、「理屈ではそうなると分かっていて、実際に動かした結果も予想に合致しているけど、実感が伴わない」ような感じもあります。
ドミノのように、パタパタと順番に切り替わっていく様子が目に見えると、 ピタゴラ装置感が出ておもしろくなるんじゃないでしょうか!?
……というのが 2つ目の理由です。 どっちかというとこっちの理由の方を優先します。
というわけで、「結果だけポンと描画」方式をやめて、 影響が波及していく過程が見えるように変更します。 これにより、状態変更が収束しない回路も動かし続けられるようになります。
まずはさっきの修正(無限ループが発生したら異常終了するようにする)を取り消し。
それから
- (1) while ループをなくし、
update_tuden_relay_switch_lamp()
内では状態更新を繰り返さないようにする - (2) ローカル変数
switch_changed
の代わりにCircuit#switch_changed
に(リレーによる)スイッチの状態変化を記憶させる- メソッドのスコープを越えて記憶させておくため
- (3)
update_tuden_relay_switch_lamp()
を呼び出す条件にCircuit#switch_changed
を追加- 手動によるスイッチの変化、またはリレーによるスイッチの変化があったら 通電判定以下をやりなおす
--- a/circuit.rb +++ b/circuit.rb @@ -13,6 +13,7 @@ end class Circuit attr_reader :child_circuits + attr_accessor :switch_changed def initialize(child_circuits) @child_circuits = child_circuits --- a/main.rb +++ b/main.rb @@ -56,13 +56,9 @@ def on_push_switch(pushed_switch) end 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.switch_changed = circuit.update_not_relays_state() circuit.update_lamps_state() - end end def draw(view, circuit, mx, my) @@ -130,7 +126,7 @@ def main_loop(circuit, view) end end - if switch_changed + if switch_changed || circuit.switch_changed update_tuden_relay_switch_lamp(circuit) end
さらっとした diff ですが、ここらへんもプロトタイプの時はいろいろあったんですよ…… (割愛)。
これで途中のステップごとに描画されるようになったはずなんですが、実際動かしてみると速すぎて一瞬で切り替わってるように見えますね……。
そこで、一定の時間が過ぎるまで状態更新をスキップするような制御を追加します。
--- a/circuit.rb +++ b/circuit.rb @@ -14,9 +14,11 @@ end class Circuit attr_reader :child_circuits attr_accessor :switch_changed + attr_accessor :last_update def initialize(child_circuits) @child_circuits = child_circuits + @last_update = Time.at(0) end def to_plain --- a/main.rb +++ b/main.rb @@ -56,6 +56,9 @@ def on_push_switch(pushed_switch) end def update_tuden_relay_switch_lamp(circuit) + return if Time.now < circuit.last_update + 0.2 + circuit.last_update = Time.now + circuit.update_tuden_state() circuit.switch_changed = circuit.update_not_relays_state() circuit.update_lamps_state()
こうなりました!
うーん、良い。断然こっちの方が楽しいでしょ。
状態更新が収束しない回路も動かせるようになりました!
あ、そうだ、状態更新のタイミングがずれるようになったので、 リレーの状態が変化したときも音を出すようにしましょう。
--- a/main.rb +++ b/main.rb @@ -62,6 +62,8 @@ def update_tuden_relay_switch_lamp(circuit) circuit.update_tuden_state() circuit.switch_changed = circuit.update_not_relays_state() circuit.update_lamps_state() + + Sound[:relay].play if circuit.switch_changed end def draw(view, circuit, mx, my) @@ -140,14 +142,15 @@ end circuit = Circuit.from_plain(parse_json($data_json)) -update_tuden_relay_switch_lamp(circuit) view = View.new(PPC) Sound.register(:click, "click.wav") +Sound.register(:relay, "relay.wav") Window.load_resources do hide_loading() + update_tuden_relay_switch_lamp(circuit) Window.bgcolor = C_BLACK --- a/run.sh +++ b/run.sh @@ -11,6 +11,9 @@ fi bundle exec ruby gen_sound.rb \ out=click.wav amp=0.05 msec=30 hz=1000 +bundle exec ruby gen_sound.rb \ + out=relay.wav amp=0.05 msec=30 hz=500 + ruby preprocess.rb "$@" > data.rb if [ "$BROWSER" = "1" ]; then
以下の iframe で実際に動かせます。 音量小さめにしていますが音が出ます。
こちらも同じものです。
https://sonota88.github.io/kairo-gokko/pages/25/index.html