歩いたら休め

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

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

【Python】クラス内のプライベート変数の名前解決方法についての調査

モジュールの外からアクセスするクラス( from sample import SampleClass )のために、以下のようなモジュール内のprivateな関数に利用するコードを書いていました。

# sample.py
class SampleClass(object):
    def public_method(self):
        __private_function()

def __private_function():
    print("ok")

if __name__ == "__main__":
    instance = SampleClass()
    instance.public_method()

そうすると次のようなエラーが出ました。どうやら"__"が冒頭につく変数の探索時に、自動的に '_SampleClass__private_function' という名前に置き換えられています。

docs.python.org

今まで self.__private_method() みたいに呼び出す場合だけ名前の置き換えが発生すると思っていたので少々びっくりしました。初めて知ったのですが、名前マングリング( name mangling )と呼ぶそうです。

$ python sample.py 
Traceback (most recent call last):
  File "sample.py", line 14, in <module>
    instance.public_method()
  File "sample.py", line 3, in public_method
    __private_function()
NameError: global name '_SampleClass__private_function' is not defined

実験

ここで気になるのが通常のローカル変数がセットされる時点でも、同様に名前がマングリングされているのかということです。そこで以下のサンプルコードで実験してみました。 locals は、そのスコープのローカル変数を辞書として取得する関数です。

class ExampleClass(object):
    def public_method(self):
        __val = 1
        print(locals())

if __name__ == "__main__":
    instance = ExampleClass()
    instance.public_method()

結果、 '_ExampleClass__val' に名前が書き換えられていることが分かります。

$ python sample.py 
{'_ExampleClass__val': 1, 'self': <__main__.ExampleClass object at 0x10dd12310>}

また、外の関数名を _SampleClass__private_function に直したちょっと無理やりな感じのコードも動作しました。

# sample.py
class SampleClass(object):
    def public_method(self):
        __private_function()

def _SampleClass__private_function():
    print("ok")

if __name__ == "__main__":
    instance = SampleClass()
    instance.public_method()

クラスではなく、関数だとどうでしょうか?

def main():
    __val = 1
    print(locals())


if __name__ == "__main__":
    main()

こちらは名前が置き換えられていません。

$ python sample.py
{'__val': 1}

まとめ

というわけで、Pythonのクラス内のプライベートな変数("__"がつくもの)は以下のような挙動をすることが確認できました。

  • クラス内の変数の宣言時にも探索時に名前が置き換えられた上で動作する
  • クラス外ではこのような動作をしない

実は公式ドキュメントにも該当する項目はありました。「コードが生成される前により長い形式に変換されます」とあるので、実行前のASTの変換処理あたりで挟まれてるんじゃないかと思います。

docs.python.org

プライベートな名前のマングリング: クラス定義内に書かれた識別子で、2つ以上のアンダースコアから始まり、末尾が2つ以上のアンダースコアで終わっていないものは、そのクラスの プライベートな名前 とみなされます。プライベートな名前は、コードが生成される前により長い形式に変換されます。この変換によって、クラス名の先頭にアンダースコアがあれば除去し、先頭にアンダースコアを1つ付加し、名前の前に挿入されます。

【Rust】Rustで数値計算プロジェクトを試す その11

振動子の減衰系で、rulinalgの練習も兼ねて実装してみました。この場合のケースならいちいちクロージャ使う必要はなかったですね。

kiito.hatenablog.com

若干ハマった点としては、 rulinalg::vector::Vector<f64> * f64 は実行されるものの、 f64 * rulinalg::vector::Vector<f64>コンパイルエラーになることです。あまり調べてないですが、組み込み型に他の型引数でtraitを実装するってできないのかな。

#[macro_use]
extern crate rulinalg;

use rulinalg::vector::Vector;

use gnuplot::{Figure, Caption, Color};


const M: f64 = 1.0;
const K: f64 = 1.0;
const A: f64 = 2.0;
const X0: f64 = 0.0;
const V0: f64 = 1.0;
const DELTA_T: f64 = 0.01;


fn main() {
  let mut vsp = Vector::new(vec![X0, V0]);
  let mut xs = vec![];
  let mut ts = vec![];

  for i in 1..1000 {
    let t = (i as f64) * DELTA_T;
    ts.push(t);
    xs.push(vsp[0]);
    vsp += rk4(&vsp, |vsp| {
      &matrix![0.0, 1.0; - K / M, - A / M] * vsp
    });
  }

  plot(ts, xs);
}

fn rk4<F>(vsp: &Vector<f64>, f: F) -> Vector<f64>
    where F : Fn(&Vector<f64>) -> Vector<f64> {
  let k1 = &(f(vsp) * DELTA_T);
  let k2 = &(f(&(vsp + k1 / 2.0)) * DELTA_T);
  let k3 = &(f(&(vsp + k2 / 2.0)) * DELTA_T);
  let k4 = &(f(&(vsp + k3)) * DELTA_T);
  (k1 + k2 * 2.0 + k3 * 2.0 + k4) / 6.0
}

fn plot(x: Vec<f64>, y: Vec<f64>) {
  let mut fg = Figure::new();
  fg.set_terminal("pngcairo", "output.png");
  fg.axes2d()
    .lines(&x, &y, &[Caption("A line"), Color("black")]);
  fg.show();
}

結果です。

f:id:takeshi0406:20190225215525p:plain