ゲームボーイソフトを作成の練習のために、gbdkを使ってライフゲームを実装しました。
例えば今後は以下のようなことがしたいと考えています。
sakana38.hatenablog.com
LIFEGAMEを実装した意図
サンプル実装として「ある程度実装方法が想像ついて、適度に難しい」課題を探していました。
ja.wikipedia.org
この本にMITでライフゲームを実装したハッカーの話が出てきたのでそれを参考にしています。
以前プログラマーの友達に相談したところ「新しい言語を学ぶ時、けっこういろいろなことをしなきゃいけない簡単なLisp処理系を実装している」と聞いたのですが、そもそもゲームボーイでプログラム(文字列)を打ち込むのは言語処理系そのものより入力処理が難しそうなので、最初に何を実装するのか悩んでいました。ちょうどいいアイデアが見つかってよかったです。
実装したもの
ソフトはこちらです。エミュレータ等で試してみてください。
drive.google.com
最初にランダムに配置し続けて、初期値を決めています。ランダム配置なのは「とりあえず完成して達成感を得る」ために手を抜いているためです。
スタートボタンを押し続けるとライフゲームが始まります。大抵、だんだん数と減って動きも無くなってくるので悲しい気分になります。
Bボタンを押し続けるとランダム配置モードの戻ります。
今後の展望
インタラクティブに黒丸を配置したいのですが、「時間のかかる描画中の合間にしかボタンの押下をチェックしていない」ので、「しばらく押し続けないとライフゲームモード」に遷移しない状態になっています。
実際にゲームボーイのCPUにも割り込み処理が存在し、GBDKにもその機能があるのですが、ドキュメントの意味が分からないのと、他の人の実装を見てもよくわかんないので困っています。
gbdk.sourceforge.net
あまり実装方針や用語が分からないので、ひたすら大学(母校)の図書館で「組み込み系プログラミングの割り込み処理」のことを書いた本を探して、この本を買ってしまいました。
ついでにこれも買いました。
正直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);
}
}
}