歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【GB】ゲームボーイでライフゲームを実装しました

ゲームボーイソフトを作成の練習のために、gbdkを使ってライフゲームを実装しました。

例えば今後は以下のようなことがしたいと考えています。

  • NESファミコン)向けに作成されたmmlファイルを、GBソフトに変換(トランスコンパイル?)して再生したい
    • いろいろな経緯で、実機で楽曲を演奏する場合、GBではLSDjnanoloop等の専用ソフトで作曲することが多いのに対し、NESではWindowsでFamiTracker等で作曲したソフトを再生するのが主流だそうです
    • 既に「GameBoy Music Compiler」もあるのですが、凝った曲がうまく再生できませんでした
  • 何らかのアルゴリズムでPC上で音楽を生成して、USB→通信ケーブル経由で再生したい
  • ついでにゲームボーイ用のゲームソフトを作ってみたい(かっこいい)

sakana38.hatenablog.com

LIFEGAMEを実装した意図

サンプル実装として「ある程度実装方法が想像ついて、適度に難しい」課題を探していました。

ja.wikipedia.org

この本にMITでライフゲームを実装したハッカーの話が出てきたのでそれを参考にしています。

ハッカーズ

ハッカーズ

以前プログラマーの友達に相談したところ「新しい言語を学ぶ時、けっこういろいろなことをしなきゃいけない簡単なLisp処理系を実装している」と聞いたのですが、そもそもゲームボーイでプログラム(文字列)を打ち込むのは言語処理系そのものより入力処理が難しそうなので、最初に何を実装するのか悩んでいました。ちょうどいいアイデアが見つかってよかったです。

実装したもの

ソフトはこちらです。エミュレータ等で試してみてください。

drive.google.com

最初にランダムに配置し続けて、初期値を決めています。ランダム配置なのは「とりあえず完成して達成感を得る」ために手を抜いているためです。

スタートボタンを押し続けるとライフゲームが始まります。大抵、だんだん数と減って動きも無くなってくるので悲しい気分になります。

Bボタンを押し続けるとランダム配置モードの戻ります。

今後の展望

インタラクティブに黒丸を配置したいのですが、「時間のかかる描画中の合間にしかボタンの押下をチェックしていない」ので、「しばらく押し続けないとライフゲームモード」に遷移しない状態になっています。

実際にゲームボーイのCPUにも割り込み処理が存在し、GBDKにもその機能があるのですが、ドキュメントの意味が分からないのと、他の人の実装を見てもよくわかんないので困っています。

gbdk.sourceforge.net

あまり実装方針や用語が分からないので、ひたすら大学(母校)の図書館で「組み込み系プログラミングの割り込み処理」のことを書いた本を探して、この本を買ってしまいました。

12ステップで作る組込みOS自作入門

12ステップで作る組込みOS自作入門

ついでにこれも買いました。

低レベルプログラミング

低レベルプログラミング

正直OSより低レイヤーの話は今までほとんど知らなくてけっこう楽しいのですが、正直100%役に立たないと思ってるので、会社の同僚(真面目な人が多い)には黙ってこっそりやっています。大目に見てください。

ソースコード

コードはだいたいこんな感じです。まだC言語の実装に慣れていないので、けっこういろいろツッコミどころあると思います。

github.com

コードが今後もどんどん変わっていく可能性高いので貼っておきます。

#include <gb/gb.h>
#include <gb/drawing.h>
#include <rand.h>

#define RADIUS 2
#define X_MIN 4
#define X_MAX 156
#define X_NODES (X_MAX - X_MIN) / 8 + 1
#define Y_MIN 4
#define Y_MAX 140
#define Y_NODES (Y_MAX - Y_MIN) / 8 + 1

UBYTE current_map[X_NODES][Y_NODES] = {0};
UBYTE next_map[X_NODES][Y_NODES] = {0};


void init(void);
void init_map(void);
void update_map(void);
UBYTE count_neighbors(UBYTE i, UBYTE j);
void draw(void);
enum State {
    INPUT,
    DRAW
};


int main(void) {
    enum State state = INPUT;
    init();
    while (1) {
        switch (state) {
            case INPUT:
                init_map();
                draw();
                state = (joypad() == J_START) ? DRAW : INPUT;
                break;
            case DRAW:
                draw();
                update_map();
                state = (joypad() == J_B) ? INPUT : DRAW;
                break;
            default:
                break;
        }
    }
}


void init(void) {
    UBYTE x1, y1;
    for (x1 = X_MIN; x1 <= X_MAX; x1 += 8) {
        line(x1, Y_MIN, x1, Y_MAX);
        delay(16);
    }

    for (y1 = Y_MIN; y1 <= Y_MAX; y1 += 8) {
        line(X_MIN, y1, X_MAX, y1);
        delay(16);
    }

    for (x1 = X_MIN; x1 <= X_MAX; x1 += 8) {
        for (y1 = Y_MIN; y1 <= Y_MAX; y1 += 8) {
            color(WHITE, WHITE, SOLID);
            circle(x1, y1, RADIUS, M_FILL);
            delay(1);
        }
    }
}


void init_map(void) {
    UBYTE i, j;
    for (i = 0; i < X_NODES; i++) {
        for (j = 0; j < Y_NODES; j++) {
            current_map[i][j] = rand() % 2;
        }
    }
}


void update_map(void) {
    UBYTE count, i, j;
    for (i = 0; i < X_NODES; i++) {
        for (j = 0; j < Y_NODES; j++) {
            count = count_neighbors(i, j);
            if (current_map[i][j]) {
                if (count <= 1 || count >= 4) {
                    next_map[i][j] = 0;
                } else {
                    next_map[i][j] = 1;
                }
            } else {
                if (count == 3) {
                    next_map[i][j] = 1;
                } else {
                    next_map[i][j] = 0;
                }
            }
        }
    }

    for (i = 0; i < X_NODES; i++) {
        for (j = 0; j < Y_NODES; j++) {
            current_map[i][j] = next_map[i][j];
        }
    }
}


UBYTE count_neighbors(UBYTE i, UBYTE j) {
    UBYTE k, l;
    UBYTE result = 0;
    for (k = 0; k < 3; k++) {
        for (l = 0; l < 3; l++) {
            if (k == 1 && l == 1) continue;
            if (i + k == 0 || i + k >= X_NODES) continue;
            if (j + l == 0 || j + l >= Y_NODES) continue;
            result += current_map[i + k - 1][j + l - 1];
        }
    }
    return result;
}


void draw(void) {
    UBYTE i, j;
    for (i = 0; i < X_NODES; i++) {
        for (j = 0; j < Y_NODES; j++) {
            color(current_map[i][j] ? BLACK : WHITE, WHITE, SOLID);
            circle(i * 8 + 4, j * 8 + 4, RADIUS, M_FILL);
        }
    }
}