Smalltalk-72で遊ぶOOPの原点:魚雷を実装する

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

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


torpedotorp

魚雷 torpedo とその描画用プロシージャの torp は、宇宙船 spaceship に対する ship のペアと同じ関係にあります。

継承があれば torpedo はきっと spaceship との共通部分を抽象クラス化してそれを継承して作ると少し楽ができそう(Smalltalk-72版ではコードが膨れ上がったので…)ですが、Smalltalk-72 同様に Smalltalk-71 にも継承機構は想定されていなかったようで、一部重複するコードで実装する必要があります。

まず描画用の torp ですが、これは例によって省略されているので、ship から尾翼を省いて少し小さめにした処理で済ませました。

to torp size (
    @ penup turn 180 go :size turn 90
        go 0.5 * size turn 90
        pendn go 2 * size turn 30 go size
        turn 120 go size
        turn 30 go 2 * size
        turn 30 go size
        turn 120 go size
        penup turn 120 go 0.5 * size turn `90 go size)

魚雷 torpedo は、前述のとおり spaceship と基本的なところは同じなのですが、

  1. パイロット名 pilot は無い
  2. 速度 speed と位置 locx locySmalltalk-71版では location )、そして方向 direction は射出された時点の spaceship のそれに従う
  3. 推進力 thrust は常に 0(つまり、速度 speed はそのまま)

という点で異なることが Smalltlak-71版の元コードから読み取れます。

書かれてはいませんが、向き direction も変わらず一定であるべきなので、舵 steer も当然 0 であるべきでしょう。

位置の更新に moveship を、描画用の torp を呼ぶ際に display torp を使っているので、魚雷としては不要なはずの thruststeer に加え、Smalltalk-72版で追加した直近描画の位置等情報の llocx llocy lldir lthr も宣言と初期化が必要になります。

射出時の初期位置 locx locy は元コードのままだと spaceship と重なっており、これでは crash? が反応してしまうので、direction の方向に SSIZE * 3 ほど移動して現れるように変えています。後述の時限のしくみに倣って、生成直後から一定時間 crash? の実行を行わないというやり方でも良いかもしれませんね。

Smalltalk-71の元のコードでは、画面をまたぐと消滅するようですが、moveship で位置は画面をまたぐように正規化されてしまっており、またいだことを知る方法もないため、Smalltalk-72版では時限を設けて一定時間(グローバル変数 TORPLIFE )で無効化して消滅することにしました。

時限を迎えたり他のオブジェクトと接触した時の消滅 finish SELF の際には、発射した spaceshipnumtorps のデクリメントを行う必要があるのですが、発射した spaceshipnumtorps にアクセスできるコンテキストから外れてしまうため spaceshipnumtorps をデクリメントする debumptorps(と、必要ないですがインクリメントする bumptorps も)用意しこれをコールしています。なお torpedo インスンタス生成時に、それを射出した spaceship(自身)を launcher として渡すような変更も加えています。

魚雷が時限を迎えて消滅するときのために、display アクションに消去だけする erase オプションも用意しました。

to torpedo : thrust steer locx locy speed direction time
        ftime llocx llocy ldir lthr launcher endlife (
    isnew ? (:launcher. :speed. :locx. :locy. "ldir _ :direction.
        "locx _ "llocx _ locx + (cos direction) * SSIZE * 10.
        "locy _ "llocy _ locy + (sin direction) * SSIZE * 10.
        launcher bumptorps.
        "thrust _ "lthru _ "steer _ 0. 
        "time _ "ftime _ clock.
        "endlife _ clock + TORPLIFE)
    %release ? (
        stick delete thrust.
        stick delete steer.
        stick delete trigger)
    %locx ? (!locx)
    %locy ? (!locy)
    %step ? (
        0 < clock - time + MOVELAG ? (
            "time _ clock.
            0 < clock - endlife ? (
                launcher debumptorps.
                display torp erase.
                finish SELF)
            moveship.
            crash~ SELF.
            display torp))
    %is ? (ISIT eval)
)

to display obj (
    :#obj.
    0 < clock - ftime + FRAMELAG ? (
        "ftime _ clock.
        @ penup goto llocx llocy up turn ldir + 90 pendn white.
        obj SSIZE.
        (0 < lthr ? (flame SSIZE)
        0 > lthr ? (retro flame SSIZE)).
        @ penup goto locx locy up turn direction + 90 pendn black.
        %erase ? ()
        obj SSIZE.
        (0 < thrust ? (flame SSIZE)
        0 > thrust ? (retro flame SSIZE))
        "llocx _ locx. "llocy _ locy. "ldir _ direction ."lthr _ thrust))

to spaceship newtorp : pilot thrust steer trigger numtorps locx locy speed direction time
        ftime llocx llocy ldir lthr (
    isnew ? (:pilot. "lthr _ :#thrust. :#steer :#trigger. 
        "numtorps _ "speed _ 0.
        "direction _ "ldir _ 0 + rand * 360.
        "locx _ "llocx _ rand between 50 462.
        "locy _ "llocy _ rand between 50 462.
        "time _ "ftime _ clock)
    %release ? (
        stick delete thrust.
        stick delete steer.
        stick delete trigger)
    %locx ? (!locx)
    %locy ? (!locy)
    %step ? (
        0 < clock - time + MOVELAG ? (
            "time _ clock.
            (trigger ? (3 > numtorps ? (
                "newtorp _ torpedo SELF speed locx locy direction.
                spacewar schedule newtorp))
            moveship.
            crash~ SELF.
            display ship)))
    %is ? (ISIT eval)
    %bumptorps ? ("numtorps _ numtorps + 1)
    %debumptorps ? ("numtorps _ numtorps - 1)
)

こちらが、停止している宇宙船(敵)対して魚雷を発射、一発外して二発目で当てたときの様子です。しつこいようですが^^; 航跡が残るように display の残像を消す処理はコメントアウトしてあります。

「ask」「start」の実装 へ続く )