アラン・ケイの“メッセージングによるプログラミング”という着想に基づき(非同期処理などいろいろ足りていないながらも──)比較的忠実に実装された1970年代の非常に古いSmalltalk-72に実際に触れてみるシリーズ 第2弾です(なお最新のSmalltalkについては Pharo などでお楽しみください!)。
今回は謎言語「Smalltalk-71」で書かれたスペースウォー・ゲームを Smalltalk-72に移植して動かすことを目指します。前回(2019年)を含む他の記事はこちらから→Smalltalk-72で遊ぶOOPの原点 | Advent Calendar 2023 - Qiita
悩ましい宇宙船制御の方法
moveship
を OCR版で再掲します。
to moveship make :speed be :speed + (:spscale * :thrust) make :direction be :direction + (:dirscale * :steer) rem 360 make :location:x be :location:x + (:lscale * :speed * cos :direction) rem 1024 make :location:y be :location:y + (:lscale * :speed * sin :direction) rem 1024 end to
:spscale
、:dirscale
、:lscale
はそれぞれ、速度、方向、座標の補正用パラメータで、グローバル変数として与えられているようです。
:thrust
と :steer
は、start
を見ると、spaceship
のインスタンス化の際に、第二、第三引数として渡される関数で、その時点のジョイスティックの状態を返しているように読めます。恐らくは、スティックの倒し具合と倒した向きが返ってくるのでしょう。
Smalltalk-72 への移植では、このジョイスティックの代わりをどのように実現するかも悩ましいところです。マウスを使ってジョイスティックをシミュレートする方法もありますが、対戦時には使えないので、やはり、加減速や転回のためのキーをそれぞれのプレイヤーにアサインしておき、それを連打することで、 :thrust
および :steer
が返す値を変える…というのがよさそうです。
ただここにもひとつ問題があって、キー押下のイベントについては、Alto および Smalltalk-72 のスペックどおりなら mem 65052
で取れそうに思うのですが(ALLDEFS 参照)、残念ながら Lively-Web 版エミュレーターではこれが機能していないようで、マウスクリックのイベント取得を可能にしたときのように JavaScript レベルで VM にパッチを当てるか、あるいは何らかのキーを押さないと処理がそこで止まってしまうという制約付きながら kbd
を使うか等のワークアラウンドが必要になると思います。
[追記:その後、キー入力の有無を返す kbck
を見つけた^^; ので、これと入力されたキーを順に返す kbd
を組み合わせることで問題はなさそうだと分かりました。]
:location:(:x :y)
を表現するための point
の定義
Smalltalk-72 には Smalltalk-76 以降にある Point
というクラスが無いので、今回の :location:(:x :y)
を扱うために定義します。
to point : x y ( %x ? (%_ ? (!:x) !x) %y ? (%_ ? (!:y) !y) %print ? (disp _ '(point '. x print. disp _ 32. y print. disp _ ')') isnew ? (:x. :y) ) "pt _ point 3 4. pt pt x pt y pt x _ pt x + 10. pt pt y _ pt y + 20. pt
これは Smalltalk-72 のクラスの定義の典型例にもなっています。
まず、インスタンス生成をしないアクションでは、to
に続く名前の直後にテンポラリ変数を宣言していましたが、インスタンスを生成する場合は :
で区切ってからインスタンス変数を(必要なら)宣言します。フェッチアクション :
と紛らわしいですが、この区切りの :
はただの記号です。この分かりづらさを解消するためか、Smalltalk-72 の後のバージョンでは、|
に変更されます。今回は使用しませんが、さらに :
で区切ってクラス変数(インスタンスとクラスで共有)を宣言することもできます。
次に、クラスにインスタンス生成能を持たせるためにはメソッド中に必ず isnew
の評価が必要です。isnew
はクラスのコンテキストではインスタンス(非偽値)を返してコンテキストをそれにスイッチし、インスタンスのコンテキストでは false
を返します。したがって、そこで条件分岐( テスト ⇒ ( 非偽値時処理 ) 偽時処理
)を書けば、非偽値時処理をコンストラクタ代わりに使えるというカラクリです。
この point
の例は、メッセージとして送られてきた2つの値をフェッチ(:
)して、それぞれ x
および y
にアサイン(:x
:y
)しています。
ᗉx ⇒ ...
のメソッドセクションは、アクセッサーに相当します。メッセージとして x
を受け取ったとき、続くメッセージが代入を意図する ←
ならば続くメッセージを(評価して)フェッチ :
し、x
に代入してから改めてその値を返します(⇑:x
)。続くメッセージが ←
でなかったときは、そのまま x
を返します(⇑x
)。y
についても同様です。
ᗉprint ⇒ ...
のセクションでは、point
のインスタンスを表示するための処理を記述しています。コピペ文化の無い Smalltalk-72 の場合あまり意味はないのですが、Smalltalk-80以降を真似て、評価すると同じオブジェクトを生成できる表示にしました。
あとはクラスを問われたときの is
や is?
への応答の実装なども欲しいところですが、今回の Spacewar の移植にはあまり出番が無さそうなのでやめておきます。
moveship
の実装
準備が整ったので、本題の moveship
を Smalltalk-72 で書いてみましょう。
to moveship ( "speed _ speed + (spscale * thrust). "direction _ (direction + dirscale * steer) mod 360. location x _ (location x + (cos direction) * lscale * speed) mod 512. location y _ (location y + (sin direction) * lscale * speed) mod 512. ) "location _ point 200 200. "speed _ 10. "spscale _ 1.0. "thrust _ 10. "direction _ 270. "dirscale _ 0.5. "steer _ 90. "lscale _ 1.0. do 10 (moveship. {speed direction location} print. cr).
図では、必要なパラメータをあらかじめセットしておき、moveship
を 10回ほどコールしてみています。
数値だけだとわかりにくいですが、どうやらうまく動いているようです。
(「retro」と「display」の仮実装 へ続く)