思いつきでライフゲームを実装してみた

何の気の迷いか、それとも魔が差したのか、夜中に思いついてライフゲームを実装するという暴挙*1に出てみた。

夜中で頭の働き低下していたので、取りあえず書きなれているC++を使用。本来はC言語の方が書きなれているはずだが、ここ最近C++を弄くっている影響で、ついついbetter Cになってしまうのだ。

一通り動くようになったので満足して睡眠をとり、起きて雑事をこなし、ちょうど今コードを整理したところ。

/// @brief   コンソール版ライフゲーム
/// @author  eel3 @ TRASH BOX
/// @date    2010/11/06
///
/// @note
///   - ビルド時、環境に応じて「WINDOWS」ないし「UNIX」をdefineすること。
///   - 「UNIX」はLinuxディストリ、FreeBSD、Mac OS X 等に対応しているはず。
///   - ビルド後、コンソール上で引数無しで実行すれば動作する。
///
/// @par 動作確認済み環境:
///   - Microsoft Windows XP Professional SP3
///   - Ubuntu 10.04 LTS x86
///
/// @par 確認済みコンパイラ:
///   - Microsoft Visual Studio 2005 Professional SP1
///   - g++ (GCC) 3.4.2 (MinGW 5.0.3)
///   - g++ (GCC) 4.4.3 (Ubuntu)


#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>

#if defined(WINDOWS)
    #include <windows.h>
#elif defined(UNIX)
    #include <unistd.h>
#else
    #error "Need define WINDOWS or UNIX"
#endif


/* ---------------------------------------------------------------------- */
/* コンフィグレーション */
/* ---------------------------------------------------------------------- */

static const int MAX_GENERATION = 1000;     ///< シミュレーションする世代数
static const int NUM_INIT_MEMBERS = 431;    ///< 初期の個体数

static const int TABLE_WIDTH = 78;          ///< 盤面の横幅
static const int TABLE_HEIGHT = 22;         ///< 盤面の縦幅

static const char BIT_ON = '*';             ///< ビットマップON
static const char BIT_OFF = ' ';            ///< ビットマップOFF


/// 画面消去用コマンド。
/// system(3)を使用して、UI切り替え時の画面消去を実現する。
/// Windowsではcls、Unix環境ではclearを使用すればよいはず。
#ifdef WINDOWS
    static const char * const CLEAR_TERM_CMD = "cls";
#else
    static const char * const CLEAR_TERM_CMD = "clear";
#endif


/* ---------------------------------------------------------------------- */
/* 各種変数 */
/* ---------------------------------------------------------------------- */

/// 個体の有無を記録する盤面 兼 出力内容のビットマップ
static char bitmap[TABLE_HEIGHT][TABLE_WIDTH+1];

/// 隣り合う個体の数を記録する盤面。初期値は全て 0。
static int neighbor[TABLE_HEIGHT][TABLE_WIDTH];


/* ---------------------------------------------------------------------- */
/* 関数 */
/* ---------------------------------------------------------------------- */

/// msec ミリ秒スリープする
#ifdef WINDOWS
inline static void msleep(unsigned msec)
{
    Sleep(static_cast<DWORD>(msec));
}
#else
inline static void msleep(unsigned msec)
{
    (void) usleep(static_cast<useconds_t>(msec * 1000));
}
#endif

/// 0 以上 max 未満の範囲の乱数値を返す。
///
/// 乱数の質を上げる為、特定の範囲の値を捨てている。
/// comp.lang.c の C-FAQ 13.16 を参照。
static int random(int max)
{
    int x = static_cast<int>((RAND_MAX + 1.0) / max);
    int y = x * max;
    int r;

    assert(max >= 0);

    do {
        r = rand();
    } while (r >= y);

    return r / x;
}

/// 改ページ処理
inline static void nextpage()
{
    (void) system(CLEAR_TERM_CMD);
}

/// Y軸(縦軸)のカウンタをインクリメントした値を返す
inline static int y_incr(int y)
{
    return (y == (TABLE_HEIGHT-1)) ? 0 : (y+1);
}

/// Y軸(縦軸)のカウンタをデクリメントした値を返す
inline static int y_decr(int y)
{
    return (y == 0) ? (TABLE_HEIGHT-1) : (y-1);
}

/// X軸(横軸)のカウンタをインクリメントした値を返す
inline static int x_incr(int x)
{
    return (x == (TABLE_WIDTH-1)) ? 0 : (x+1);
}

/// X軸(横軸)のカウンタをデクリメントした値を返す
inline static int x_decr(int x)
{
    return (x == 0) ? (TABLE_WIDTH-1) : (x-1);
}

/// 初期化処理
static void initialize()
{
    srand(static_cast<unsigned int>(time(NULL)));

    for (int y = 0; y < TABLE_HEIGHT; ++y) {
        (void) memset(bitmap[y], BIT_OFF, TABLE_WIDTH);
    }

    for (int i = 0; i < NUM_INIT_MEMBERS; ++i) {
        bitmap[random(TABLE_HEIGHT)][random(TABLE_WIDTH)] = BIT_ON;
    }
}

/// メインルーチン
int main()
{
    initialize();

    for (int generation = 1; generation <= MAX_GENERATION; ++generation) {
        nextpage();
        (void) printf("Generation: %4d\n", generation);

        int y, x;

        // 隣り合う個体の数をカウントしつつ、現在の世代を描画する
        for (y = 0; y < TABLE_HEIGHT; ++y) {
            for (x = 0; x < TABLE_WIDTH; ++x) {
                if (bitmap[y][x] == BIT_ON) {
                    ++neighbor[y_decr(y)][x_decr(x)];
                    ++neighbor[y_decr(y)][x];
                    ++neighbor[y_decr(y)][x_incr(x)];

                    ++neighbor[y][x_decr(x)];
                    ++neighbor[y][x_incr(x)];

                    ++neighbor[y_incr(y)][x_decr(x)];
                    ++neighbor[y_incr(y)][x];
                    ++neighbor[y_incr(y)][x_incr(x)];
                }
            }
            (void) puts(bitmap[y]);
        }

        // 隣り合う個体の数を元に次世代の様子をシミュレートしながら、
        // 隣り合う個体数のカウント値をクリアしていく
        for (y = 0; y < TABLE_HEIGHT; ++y) {
            for (x = 0; x < TABLE_WIDTH; ++x) {
                if (neighbor[y][x] != 2) {
                    bitmap[y][x] = (neighbor[y][x] == 3) ? BIT_ON : BIT_OFF;
                }
                neighbor[y][x] = 0;
            }
        }

        msleep(100);
    }

    return EXIT_SUCCESS;
}

無理やり描画しているので、環境によっては結構ちらつくと思う。

100ミリ秒ごとに描画しているが、コマンドプロンプト上ではもう少し遅く動作しているように感じる。Ubuntu 10.04 LTS上のGNOME端末ではほぼ100ミリ秒周期で動作している感じだった*2

*1:体調管理とか健康とかに対する暴挙だと思う。

*2:Intel Atom 330マシンなので、マシンパワーはそれほど潤沢ではない。