Smalltalk-72で遊ぶOOPの原点:改めて「moveship」の実装

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

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


悩ましい宇宙船制御の方法

moveshipOCR版で再掲します。

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以降を真似て、評価すると同じオブジェクトを生成できる表示にしました。

あとはクラスを問われたときの isis? への応答の実装なども欲しいところですが、今回の Spacewar の移植にはあまり出番が無さそうなのでやめておきます。

moveship の実装

準備が整ったので、本題の moveshipSmalltalk-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」の仮実装 へ続く)