アラン・ケイの“メッセージングによるプログラミング”という着想に基づき(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72に実際に触れてみるシリーズ 第2弾です(なお最新のSmalltalkについては Pharo などでお楽しみください!)。
今回は謎言語「Smalltalk-71」で書かれたスペースウォー・ゲームを Smalltalk-72に移植して動かすことを目指します。前回(2019年)を含む他の記事はこちらから→Smalltalk-72で遊ぶOOPの原点 | Advent Calendar 2023 - Qiita
キーの押下の回数をスティックの傾きに代用する
実装をなるべくサボるため^^; 指定したキーの連打の回数でジョイスティックの傾きの度合いを表す仕様にしました。
stick
のインスタンスは、アクティベート( ≒ 参照)されるとスティックの定められた方向 (x軸 or y軸) の傾きを表す数値を返す。- あらかじめ定められたキーの押下をなんらかの方法で知ることで、傾きを表す数値(インスタンス変数
val
)を増減させる。
面倒なのでジョイスティックのボタン(発射ボタン)も stick
のインスタンスにまとめてしまいます。すみません。
stick
のインスタンスは、定められたキーが増減の2キーならスティックを、1キーのみなら発射ボタンとして振る舞う。- 発射ボタンとして振る舞う
stick
のインスタンスは、あらかじめ定められたキーの押下をなんらかの方法で知ると、状態を表真偽値(インスタンス変数val
)にtrue
をセットする。 - アクティベート( ≒ 参照)されると、
val
を返す。同時にval
はfalse
にリセットされる。
問題は、「キーの押下をなんらかの方法で知る」というところです。
まず、キーセンサー keysens
アクションを用意します。ゲーム中にプレイヤーがキーを押下するとそれぞ stick
クラスに知らせ( stick process kbd
)あとは stick
側で良きに計らってもらうことにします。
to keysens (repeat (kbck ? (stick process kbd) done). #keysens)
なおこの keysens
アクションは spaceship
のインスタンスと同様にあらかじめ spacewar
に登録しておくことで、ゲーム中に繰り返し呼ばれるようにしておきます。
Smalltalk-72では、インスタンス(そしてインスタンスを返すインスタンス生成能を持つ普通のクラス)はメソッド途中でリターン( ⇑
)アクションへのメッセージ式で処理の中断と返り値を明示しない限りインスタンス自身(SELF
)を返しますが、関数的に用いられるアクションは最後に評価した値(なければ nil
)を返します。そのため、この spacewar
への登録とその後のハンドリングに備えて、メソッドの終わりに自身の参照 #keysens
を追加しています。
さて。クラス stick
には kmap
というクラス変数(256要素の配列 vector 256
)を持たせます。そして stick
インスタンス生成の際に指定されたキーのコード + 1 の場所に生成したインスタンスを保持しておき(スティックの場合は2キーそれぞれに)、これを前述の process
時に逆引きの辞書として使います。該当するキーにアサインされたインスタンスが見つかればそれに handle <キーコード>
を送信し、見つからなければ無視します。
メッセージ handle <キーコード>
を受け取ったインスタンスは、自分が発射ボタンなら(指定されたキー keys
の数が 1
なら)val
に true
を、そうでない場合は <キーコード>
が最初の文字と一致するなら val
をデクリメント、そうでなければインクリメントします。
アクティベートされても何もメッセージを受け取らなかったとき(≒ 参照されたとき)は、val
が true
なら val
を false
にリセットして true
を、そうでなければ(つまり、false
時の発射ボタンやスティックなら) val
をそのまま返します。
今更ですが念のための注意として、spaceship
もそうなのですが、 ただアクティベートされただけで何か処理をする(特に値を返す)ようなインスタンスの書き方や使い方は、Smalltalk-72 ではあまり想定されていないらしく、いろいろと問題を引き起こします。あくまで Smalltalk-71 の元のコードの見た目に寄せるためだけの遊びの一環としてとらえていただければさいわいです。
to stick x y i kcode : keys val : kmap ( %delete ? (%all ? ("kmap _ nil) :#x. for i to 256 do ("y _ kmap[i]. eq #x #y ? (kmap[i] _ nil)). ) (null kmap ? ("kmap _ vector 256)) %process ? ("x _ kmap[1+:kcode]. null #x ? (!false) x handle kcode. ) isnew ? ( (1 = :keys length ? ("val _ false) "val _ 0). for i to keys length do (kmap[1+keys[i]] _ #SELF)) %print ? (disp _ '(stick '. disp _ 39. disp _ keys. disp _ 39. disp _ ') ') %handle ? ( :kcode. 1 = keys length ? ("val _ true) 1 = keys[1 to 2] find first kcode ? ("val _ val - 1) "val _ val + 1) eq val true ? ("val _ false. !true) !val )
こちらのコードで宇宙船がキーで操作できることを確認しましょう。航跡が残るように display
の残像を消す処理はコメントアウトしてあります。(残像を消す処理を入れたのは早すぎましたね…^^;)
"SSIZE _ 6. "MOVELAG _ "FRAMELAG _ 0. "SPSCALE _ 1.0. "DIRSCALE _ 1.0. "LSCALE _ 1.0. @ erase. disp display. disp clear "s1x _ stick 'jl'. "s1y _ stick 'ki'. "s1but _ stick ','. "s1 _ spaceship 'Jimmy' s1y s1x s1but. spacewar delete all. spacewar schedule keysens spacewar schedule s1. spacewar run
かなり根気がいりますが2艇以上でもいけそうです。
@ erase. disp display. disp clear "s1x _ stick 'jl'. "s1y _ stick 'ki'. "s1but _ stick ','. "s1 _ spaceship 'Jimmy' s1y s1x s1but. "s2x _ stick 'ad'. "s2y _ stick 'sw'. "s2but _ stick 'x'. "s2 _ spaceship 'Beth' s2y s2x s2but. spacewar delete all. spacewar schedule keysens spacewar schedule s1. spacewar schedule s2. spacewar run