歩いたら休め

なんでこんな模様をしているのですか?

【メモ】Githubの草を剥がさずにリポジトリを移行(結合)した

Rustの練習用にいくつかリポジトリを作って書き捨てのプログラムを書いていたのですが、あまり「〇〇-test」とかリポジトリが乱立してるのは見栄えがいいものではありません。

というわけでこんなディレクトリ構成で一つのリポジトリで管理し、過去のリポジトリは削除してしまうことにしたのですが、最近「毎日とりあえずコード書いてGithubに草を生やす(コミット履歴を残す)」ことにハマっていて、普通にリポジトリを消してしまうとせっかく生やした草が剥げてしまいます。

$ tree
.
├── README.md
├── duplex-pendulum
│   ├── Cargo.lock
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── python-rust-linkage-test
    ├── Cargo.toml
    ├── main.py
    └── src
        └── lib.rs

4 directories, 7 files

こちらの記事を参考に、gitのUpstream機能でなんとかできました。まあ本質的じゃないのでこだわらなくてもいいですが一応…。

qiita.com

コミットログを見るとひどいので見ないでください。

github.com

【GB】複数スプライトのキャラクターを動かす

こちらの実装を元にしていますが、「複数スプライトを利用していること(それをなんちゃってオブジェクト指向で隠蔽していること)」「斜め移動に対応していること」が工夫した点です。

github.com

こんな感じのコードを実装しています。

const unsigned char sprite[] = {
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x1F, 0x00, 0x3F,
    0x00, 0x7F, 0x00, 0x77,
    0x08, 0x6B, 0x40, 0x77,
    0x80, 0xFF, 0x00, 0x7F,
    0x00, 0x3F, 0x00, 0x1F,
    0x10, 0x1F, 0x30, 0x30
};


typedef struct _Player {
    UBYTE i, x, y;
} Player;


void Player_init(Player* player, UBYTE i, UBYTE x, UBYTE y) {
    // copyのため?
    player->i = i * 1;
    player->x = x * 1;
    player->y = y * 1;
    SPRITES_8x16;
    set_sprite_data(player->i, 0, sprite);
    set_sprite_data(player->i + 2, 0, sprite);
    // スプライトを反転させる(キャラクターが左右対称のため)
    set_sprite_prop(player->i + 2, S_FLIPX);
    set_sprite_tile(player->i, 0);
    set_sprite_tile(player->i + 2, 0);
    move_sprite(player->i, player->x, player->y);
    // 横にずらして配置
    move_sprite(player->i + 2, player->x + 7, player->y);
    SHOW_SPRITES;
}

void Player_move(Player* player, UBYTE joystate) {
    if (!joystate) {
        return;
    }

    if (joystate & J_RIGHT) player->x++;
    else if (joystate & J_LEFT) player->x--;

    if (joystate & J_UP) player->y--;
    else if (joystate & J_DOWN) player->y++;

    move_sprite(player->i, player->x, player->y);
    move_sprite(player->i + 2, player->x + 7, player->y);
    delay(10);
}

こういう副作用多くて、なんとか隠蔽しなきゃいけないときオブジェクト指向的な発想って便利ですね。また、自分自身は組み込みプログラミングに慣れていないので joystate & J_DOWN みたいな「共通するビットが立っているとき実行する」ような発想のコードはちょっと新鮮でした。

kiito.hatenablog.com

また、割り込みでBGM出してみています。というか実はこちらが主目的です。

sakana38.hatenablog.com

こちらで実装中です。

github.com

組込み開発のための実践的プログラミング

組込み開発のための実践的プログラミング

【GB】TODO:: C言語の関数内での構造体への値のセットについて調べる

gbdkでのプログラミング中、次のようなコードを書いていたところ、「 Player_init 関数内では player->x などに50がセットしたつもりなのに、main 関数に戻ると0という値になる」という事象が発生しました。

typedef struct _Player {
    UBYTE i, x, y;
} Player;

void Player_init(Player* player, UBYTE i, UBYTE x, UBYTE y) {
    player->i = i;
    player->x = x;
    player->y = y;
    // spriteの表示は略
}

void Player_move(Player* player, UBYTE joystate) {
  // 略
}

int main(void) {
    Player player;
    Player_init(&player, 0, 50, 75);
    for (;;) {
        Player_move(&player, joypad());
    }
}

「引数にセットされた値をそのまま構造体のメンバに代入しているのが悪いんじゃないか」と思い、 player->x = x * 1; とするときちんと main 関数内でも player->x などの値がセットされたままでした。このためゲーム(?)の制作自体は続けられそうです。

C言語の仕様なのか、コンパイラの仕様なのかも今の自分には分からないため、一度アセンブラのレベルで見ていくべきなのかもしれません。

【GB】C言語でオブジェクト指向風のプログラミングを試してみる

C言語によるオブジェクト指向プログラミング入門』を参考に、以下のようなコードを書いています。「第一引数に構造体を与える関数を用意してあげれば、オブジェクト指向っぽくプログラミングできるだろう」という発想です。

ポリモルフィズム的なのを考えると、マクロでがんばるとか、自前でトレイトオブジェクト的な実装するとか必要そうな気がしているのですが、今のところややこしそうなので目を瞑ります。

C言語によるオブジェクト指向プログラミング入門

C言語によるオブジェクト指向プログラミング入門

#include "Player.h"
#include <gb/gb.h>

int main(void) {
    Player player;
    // 初期値 
    Player_Init(&player, 0, 0);
    for (;;) {
        Player_Move(&player, 1, 1);
        delay(100);
    }
}

実は以下のように「 Player の構造体を返す関数」を書こうとしたのですが、 Function cannot return aggregate. Func body ignored というようなエラーが出てしまいました。

Player player = Player_Init(0, 0);

以下のページを見る限り、「gbdkを利用するために利用しているコンパイラが古すぎて、構造体を返す関数に対応していない」ようですね。これはつらい。

sourceforge.net

$ lcc -v
lcc $Id: lcc.c,v 1.6 2001/10/28 18:38:13 michaelh Exp $

【雑記】組み込み機器のC言語プログラミングと、最近読んだ本

今日はいろいろやりたいことあった(掃除とか)んですが、結局カフェでプログラミングの本とか読んでしてしまっていました。

組み込み機器のC言語プログラミング

kiito.hatenablog.com

まず、ゲームボーイ上の実装の参考になるかと思って『組み込み開発の実践的プログラミング』という本を読んでました。

組込み開発のための実践的プログラミング

組込み開発のための実践的プログラミング

この中のいくつかの処理を自分でも再現しながら勉強に使えそうです。

ただ、やはりmain関数が「繰り返し処理の中で、switch文で切り替える」ような処理になって、プログラミングが煩雑になってしまうようです。こういう場合にやっぱりオブジェクト指向的な設計したいです。

C言語によるオブジェクト指向プログラミング入門

C言語によるオブジェクト指向プログラミング入門

以前買ったこの本も参考にしようと思います。

あと最近後輩とC++の画像処理のバッチ処理の高速化の話になって、立ち読みしてなんとなく面白そうだった『Optimized c++』って本を買ってみたのですが、今の自分のレベルでは気合入れないと読めない感じの本でした。

Optimized C++ ―最適化、高速化のためのプログラミングテクニック

Optimized C++ ―最適化、高速化のためのプログラミングテクニック

遠藤雅伸ゲームデザイン講義実況中継

遠藤雅伸のゲームデザイン講義実況中継

遠藤雅伸のゲームデザイン講義実況中継

最近仕事で「WEBサービスのプロトタイプ」を作っているんですが、自分で試していても「なんだか面白くないもの」が出来上がってしまいました。

うまくゲーム性を取り込んで実装できないかと思って、以下の記事で薦められていた本も読んでいました。ゲームとして見ると何か発展させるアイデアが出るんじゃないかと。

という話を友達にしていたら、「ラジオに出てた」といって遠藤雅伸さんの本を薦められました。

note.mu

直接役立ちそうなアイデアは出ませんでしたが、「数学系パズル」などの項目には、試しにゲームボーイで実装してみるには面白そうなものもありました。セレンディピティ

Puppeteerを試した

雑な運用をしているクローラーがいくつかあるのでちゃんとコード書こうと思っています。あとはGAEのnode.jsとPuppeteerでHeadless Chromeを試してました。

cloud.google.com

一時期Pythonのpyppeteerを利用していたのですが、やはりGCPでも公式でサポートされているのでnode.jsのほうが楽そうだと判断しています。

kiito.hatenablog.com

文化社会学入門

文化社会学入門―テーマとツール

文化社会学入門―テーマとツール

社会学周辺ジャンルを紹介するディスクガイドみたいな本でした。私は今は「発信している思想がコミュニティに与える影響」について興味があるので、『宗教社会学』の分野が興味湧きました。

ja.wikipedia.org

【GB】gbdkで割り込み処理を実装しました

ボタンを押すたびにカウントアップする実装です。

github.com

といっても大したことはなく、CPUにボタン押下時の割り込み処理が存在するので、そのときに実行する関数を指定しているだけです。

私は今までOSが存在しない低レイヤーのプログラミングをしたことがなく、「割り込み」の機能がOSレイヤーで実装されていると思いこんでいた(もしかしたらOSレイヤーでも実装されているのかもしれませんが)ので、CPUの機能として実装されていることがちょっと意外でした。

低レベルプログラミング

低レベルプログラミング

『低レベルプログラミング』から引用すると、フォン・ノイマンアーキテクチャから現代のCPU(書籍ではIntel 64が例ですが)いくつかの拡張があるようです。

このうち「割り込み」によって、対話的なプログラミングできるようになったそうです。このうちゲームボーイでは「プロテクションリング」「仮想メモリ」のような、プログラムの機構は無さそうですね。また、以前海外の記事で「GBのアーキテクチャがGBA以前は"register-oriented strategy"向けに設計されていて、関数でスタックを利用するC言語では遅い場合がある」というような話を読んだことあるのですが、それはハードウェアスタックが存在しないことを指しているのかもしれません。

ただ、割り込みの実装自体はできたものの、ライフゲームの実装をするためにはいくつか困った点があります。

kiito.hatenablog.com

割り込み処理中に joypad() でボタンの情報を取得できない

もともと、以下のようにグローバル変数joypad_state )を書き換え、ボタンの処理によって分岐しようとしていました。

int main(void) {
    disable_interrupts();
    add_JOY(onjoy);
    enable_interrupts();
    set_interrupts(JOY_IFLAG);

    while (1) {
        switch (joypad_state) {
            // 分岐
        }
    }
}

void onjoy(void) {
    joypad_state = joypad();
}

ただ、公式ドキュメントにもある通り、 add_JOY の処理中には、ボタンの状態を受け取れません( joypad 関数が動作しません)でした。これは少し工夫する必要があります。

gbdk.sourceforge.net

Interrupts in GBDK are handled using the functions disable_interrupts(), enable_interrupts(), set_interrupts(UBYTE ier) and the interrupt service routine (ISR) linkers add_VBL, add_TIM, add_LCD, add_SIO and add_JOY which add interrupt handlers for the vertical blank, timer, LCD, serial and joypad interrupts respectively.

set_interrupts をした場合に画面の表示がズレてしまう

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

void onjoy(void);

int main(void) {
    disable_interrupts();
    add_JOY(onjoy);
    enable_interrupts();
    // set_interrupts(JOY_IFLAG); // <- ここをコメントアウトしたりする
    line(16, 4, 16, 140);
}

コメントアウトした場合はこうなのですが、

コメントアウトを外し、 set_interrupts を行うとこうなります。

こういう場合のデバッグ方法が分からずに困っています。コンパイル後のアセンブリを見ていくしかないと思うんですが、どういう観点で見れば良いのか分かりません。

【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);
        }
    }
}