Smalltalk-72で遊ぶOOPの原点:宇宙船をキーで制御する方法を考える

アラン・ケイの“メッセージングによるプログラミング”という着想に基づき(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72に実際に触れてみるシリーズ 第2弾です(なお最新のSmalltalkについては Pharo などでお楽しみください!)。

今回は謎言語「Smalltalk-71」で書かれたスペースウォー・ゲームSmalltalk-72に移植して動かすことを目指します。前回(2019年)を含む他の記事はこちらから→Smalltalk-72で遊ぶOOPの原点 | Advent Calendar 2023 - Qiita


キーの押下を検知する kbck の重要性

moveship のときにも書きましたが、spaceship の仮引数の宣言と start でのその呼出しを見ると、この Smalltalk-71 のコードではジョイスティックの 横(y軸)方向の動きを :steer として、縦(x軸)方向の動きを :thrust として(そして、ジョイスティックに付属するボタンの押下を :trigger として)得ていることがわかります。

Smalltalk-72への移植に際し、宇宙船一隻だけならマウスの動き and/or ポインタの位置(トリガーについてはボタンの押下)でこれをエミュレートするという方法もありそうですが、少なくとも二隻による対戦も可能にしたいので、その方法は用いず、特定のキーの連打で対応するスティックの傾きを増減を表現するような制御方法を考えます。

Smalltalk-72 では押下されたキーはキューイングされ、それらは kbd で一つずつ取り出せます。もしキューイングされたキーが無い状態で kbd を呼ぶと、キーが押されるまで待ち状態になります。

処理待ちキーがキューに有るかどうかは kbck で調べられるので、kbd を呼ぶ前にそのチェックをすれば、キー待ちで処理がロックされるを防ぐことができます。

たとえばこんな感じ…

"prev _ mem 280.
repeat (
    repeat ((mem 280) > prev + 166 ? ("prev _ mem 280. done)).
    "key _ 0.
    prev print. sp. repeat (kbck ? ("key _ kbd print. sp) done)
    32 = key ? (done) cr.)
☞prev ← mem 280.
'prev にシステムクロック値を代入'
repeat (
'ループ処理①'
    repeat ((mem 280) > prev + 166 ⇒ (☞prev ← mem 280. done)).
    'ループ処理②'
    'システムクロック値が prev + 166 より大きければ prev を更新してループ処理②を抜ける'
    ☞key ← 0.
    'key に 0 を代入'
    prev print. sp. repeat (kbck ⇒ (☞key ← kbd print. sp) done)
    'prev出力後、スペースを出力しループ処理③'
    'キューにキーがあれば kbd でひとつ取り出し key に代入して出力、無ければループ③を抜ける'
    32 = key ⇒ (done) cr.)
    'key が 32(スペースキー)ならループ処理①を抜ける'

もし kbck によるチェックをせずにダイレクトに kbd からキーをポップするだけでこの処理を書こうとすると、この例のように「システムクロック値(prev)の等間隔での出力」のような他の並行する処理を滞りなく行うことができなくなってしまいます。

これを踏まえて、宇宙船(spaceshipインスタンス)を複数制御するためのスキームとしては、おおよそ次のようなステップが考えられそうです。

  1. ある宇宙船の thruststeer それぞれを増減させるキーのペア(trigger はそのままそれらとは別のキーと )の対応表をどこかに保持しておき
  2. キー検出を担当するアクションが自分のターンでその表を参照しながら
  3. 押下されたキーに対応する宇宙船の thruststeer(そしてtrigger)の状態を変化させる

つまり、宇宙船や魚雷のインスタンスたちと同列に、このキー検出を担当するアクションにも(疑似)並列的に制御を渡してやればよいことがわかります。

同時にようやく、Smalltalk-71版の spaceshippause until clock = :time + :movelag や、displaypause until clock = :time + :framelagSmalltalk-72 でどのように処理すればよいかがなんとなく見えてきた気がします^^;

疑似乱数発生器を実装する に続く)