Sprout68k 作例集

Sprout68kとは 関数リファレンス 使い方 アプリを開く

順に読む

上から順に、1本ごとに覚えることが1つずつ増えます。写して動かして、少し変えてみるのがいちばん早い道です。

1

はじめの1本

画面に文字を出す

おぼえることプログラムの決まった形
はじめの1本の実行画面

遊び方実行すると文字が出るだけです。"HELLO X68000" の中身を書き換えてみてください。

プログラム

ide/samples/hello.c エディタで開く
#include "x68.h"

void main(void) {
  printf("HELLO X68000");
}

プログラム解説

  1. おまじないの2行
    • #include "x68.h" — この1行で、この環境の関数が全部使えるようになる
    • void main(void) — プログラムはここから始まる。int main ではないことに注意
  2. 文字を出す
    • printf() で文字を出す。普通の C と同じ書き方
    • main から抜けると停止するので、for (;;) は書かなくてよい

ひとことまずこれが動けば、あとは足していくだけです。

2

いろいろな形を描く

点・線・四角・円を並べる

おぼえること絵を描く3つの決まりごと
いろいろな形を描くの実行画面

遊び方実行すると絵が出ます。座標や色の数字を変えると、そのまま見た目が変わります。

変数の意味

black / white
よく使う色。名前を付けておくと読みやすい
i
ループ用。位置と色を少しずつずらすのに使う

プログラム

ide/samples/shapes.c エディタで開く
// Sprout68k 作例: いろいろな形を描く
//
// 絵を描くときの決まりごとは3つだけ。
//   1. x68_screen_open() で画面を開く(最初に1回だけ)
//   2. 描画関数で描く
//   3. x68_screen_flip() で画面に出す
// 3 を忘れると、いくら描いても画面は変わらない。絵が出ないときは
// まずこの3段のどれかが抜けていないかを確かめる。
//
// 座標は左上が (0, 0)、右下が (511, 511)。y は下へ行くほど大きくなる。
// 画面からはみ出した分は自動で切り取られるので、はみ出しを気にしなくてよい。

#include "x68.h"

void main(void) {
  int black = x68_rgb(0, 0, 0);
  int white = x68_rgb(255, 255, 255);
  int i;

  x68_screen_open();
  x68_cls(black);

  // 塗りつぶした四角。左から右へ色を変えていく。
  for (i = 0; i < 8; i++) {
    x68_box_fill(24 + i * 58, 40, 48, 48, x68_rgb(255 - i * 30, i * 30, 180));
  }

  // 枠だけの四角。中は塗られない。
  x68_box(24, 112, 464, 72, white);

  // 1 点から放射状に線を引く。
  for (i = 0; i < 512; i += 32) {
    x68_line(256, 296, i, 200, x68_rgb(0, 200, 255));
  }

  // 同心円。円は輪郭だけで、塗りつぶす関数は無い。
  for (i = 24; i <= 96; i += 24) {
    x68_circle(256, 384, i, x68_rgb(255, 220, 0));
  }

  // 点を 1 つずつ打って線にする。まとまった面は box_fill のほうが速い。
  for (i = 0; i < 120; i++) {
    x68_pset(24 + i, 470 + i / 8, white);
  }

  // 文字はグラフィックとは別の画面に出るので、絵の上に重なって見える。
  x68_locate(0, 0);
  printf("SHAPES");

  x68_screen_flip();
}

プログラム解説

  1. 画面を開く
    • x68_screen_open() を最初に1回だけ呼ぶ
    • 続けて x68_cls() で背景を塗る(開いた直後の画面の中身は決まっていない)
  2. 描く
    • x68_box_fill() で塗りつぶし、x68_box() で枠だけ
    • x68_line() は太さ1ドット。太くしたいときは少しずらして何本か引く
    • x68_circle() は輪郭だけ。塗りつぶす関数は無い
    • x68_pset() は点1つ。まとまった面は box_fill のほうが速い
  3. 画面に出す
    • x68_screen_flip() を呼んで初めて画面が変わる
    • 絵が出ないときは、まずこの3段のどれかが抜けていないかを疑う

ひとこと座標は左上が (0,0)、右下が (511,511)。y は下へ行くほど大きくなります。

3

どのキーが押されたか

押したキーの名前を表示する

おぼえることキーの読み方
どのキーが押されたかの実行画面

遊び方実行画面をクリックしてから、カーソルキー・スペース・A・1 などを押してください。

プログラム

ide/samples/keyboard-input.c エディタで開く
#include "x68.h"

enum {
  kNoKey = 0,
  kLeftKey,
  kUpKey,
  kRightKey,
  kDownKey,
  kSpaceKey,
  kEnterKey,
  kEscapeKey,
  kAKey,
  kOneKey,
};

static int ReadPressedKey(void) {
  if (x68_key_down(X68_KEY_LEFT)) return kLeftKey;
  if (x68_key_down(X68_KEY_UP)) return kUpKey;
  if (x68_key_down(X68_KEY_RIGHT)) return kRightKey;
  if (x68_key_down(X68_KEY_DOWN)) return kDownKey;
  if (x68_key_down(X68_KEY_SPACE)) return kSpaceKey;
  if (x68_key_down(X68_KEY_ENTER)) return kEnterKey;
  if (x68_key_down(X68_KEY_ESC)) return kEscapeKey;
  if (x68_key_down(X68_KEY_A)) return kAKey;
  if (x68_key_down(X68_KEY_1)) return kOneKey;
  return kNoKey;
}

static void PrintKeyName(int key) {
  switch (key) {
    case kNoKey:
      printf("\n[RELEASE]");
      break;
    case kLeftKey:
      printf("\n[LEFT]");
      break;
    case kUpKey:
      printf("\n[UP]");
      break;
    case kRightKey:
      printf("\n[RIGHT]");
      break;
    case kDownKey:
      printf("\n[DOWN]");
      break;
    case kSpaceKey:
      printf("\n[SPACE]");
      break;
    case kEnterKey:
      printf("\n[ENTER]");
      break;
    case kEscapeKey:
      printf("\n[ESC]");
      break;
    case kAKey:
      printf("\n[A]");
      break;
    default:
      printf("\n[1]");
      break;
  }
}

void main(void) {
  int previous = kNoKey;

  x68_screen_open();
  printf("[READY]");
  for (;;) {
    int current = ReadPressedKey();
    if (current != previous) {
      PrintKeyName(current);
      previous = current;
    }
  }
}

プログラム解説

  1. キーを調べる
    • x68_key_down() は押している間ずっと真になる
    • 調べたいキーの数だけ if を並べる
  2. 変わったときだけ表示する
    • 前回の状態を覚えておき、違ったときだけ表示する
    • そうしないと、押しているあいだ延々と同じ文字が流れ続ける

ひとこと使えるキーの一覧は関数リファレンスの X68_KEY_* にあります。

4

四角を動かす

カーソルキーで動かす

おぼえることゲームの1周の形
四角を動かすの実行画面

遊び方実行画面をクリックしてから、カーソルキーで動かします。左上に座標が出ます。

変数の意味

player_x / player_y
四角の左上の位置
kSize / kSpeed
四角の大きさと、1周で動く距離

プログラム

ide/samples/move.c エディタで開く
// Sprout68k 作例: 四角を動かす
//
// 動くものを作るときは、この1周をずっと繰り返す。どのゲームでも形は同じ。
//   1. 画面を消す
//   2. キーを読む
//   3. 位置を計算する
//   4. 描く
//   5. 画面に出す
//
// 5 の x68_screen_flip() が画面の書き換えを待つので、この1周は毎秒およそ
// 55〜60回になる。速さを自分で調整する必要はない。

#include "x68.h"

enum {
  kSize = 40,   // 四角の大きさ
  kSpeed = 4,   // 1周で動く距離
};

static int player_x = 236;
static int player_y = 236;

// キーを読んで位置を動かす。
// x68_key_down() は押している間ずっと真になるので、押しっぱなしで動き続ける。
// 上下左右を別々に調べているため、2つ同時に押せば斜めに動く。
static void MovePlayer(void) {
  if (x68_key_down(X68_KEY_LEFT)) player_x -= kSpeed;
  if (x68_key_down(X68_KEY_RIGHT)) player_x += kSpeed;
  if (x68_key_down(X68_KEY_UP)) player_y -= kSpeed;
  if (x68_key_down(X68_KEY_DOWN)) player_y += kSpeed;

  // 動かしたあとで、画面からはみ出していたら引き戻す。
  if (player_x < 0) player_x = 0;
  if (player_y < 0) player_y = 0;
  if (player_x + kSize > X68_SCREEN_W) player_x = X68_SCREEN_W - kSize;
  if (player_y + kSize > X68_SCREEN_H) player_y = X68_SCREEN_H - kSize;
}

void main(void) {
  int background = x68_rgb(0, 0, 48);
  int player_color = x68_rgb(0, 255, 255);

  x68_screen_open();
  for (;;) {
    x68_cls(background);
    MovePlayer();
    x68_box_fill(player_x, player_y, kSize, kSize, player_color);

    // 末尾に空白を入れているのは、桁が減ったときに前の数字を消すため。
    x68_locate(0, 0);
    printf("X=%d Y=%d  ", player_x, player_y);

    x68_screen_flip();
  }
}

プログラム解説

  1. ゲームの1周
    • 消す → キーを読む → 位置を計算する → 描く → 画面に出す
    • x68_screen_flip() が画面の書き換えを待つので、毎秒およそ55〜60周になる
    • 速さを自分で調整する必要はない
  2. キーで動かす
    • 上下左右を別々に調べているので、2つ同時に押せば斜めに動く
    • 動かしたあとで、はみ出していたら引き戻す

ひとことkSpeed を大きくすると速くなります。まずここを変えてみてください。

5

落ちてくる四角を受け止める

当たり判定と点数

おぼえること当たり判定
落ちてくる四角を受け止めるの実行画面

遊び方実行画面をクリックしてから、左右キーで棒を動かして受け止めます。

変数の意味

player_x
棒の横位置。縦は動かさない
item_x / item_y
落ちてくる四角の位置
score
受け止めた数

プログラム

ide/samples/catch.c エディタで開く
// Sprout68k 作例: 落ちてくる四角を受け止める
//
// move.c のゲームの形に「当たり判定」と「点数」を足したもの。
// 当たり判定は難しく見えるが、四角どうしなら4つの辺を比べるだけでよい。

#include "x68.h"

enum {
  kPlayerW = 48,     // 棒の幅
  kPlayerH = 12,     // 棒の高さ
  kPlayerY = 460,    // 棒の縦位置(動かさない)
  kItemSize = 16,    // 落ちてくる四角の大きさ
  kPlayerSpeed = 6,  // 棒が1周で動く距離
  kFallSpeed = 4,    // 四角が1周で落ちる距離
};

static int player_x = 232;
static int item_x = 100;
static int item_y = 0;
static int score = 0;

// 落ちてくる四角を、画面の上のどこかに置き直す。
static void ResetItem(void) {
  item_y = 0;
  item_x = x68_rand_int(X68_SCREEN_W - kItemSize);
}

// キーを読んで棒を動かす。
static void ReadInput(void) {
  if (x68_key_down(X68_KEY_LEFT)) player_x -= kPlayerSpeed;
  if (x68_key_down(X68_KEY_RIGHT)) player_x += kPlayerSpeed;
  if (player_x < 0) player_x = 0;
  if (player_x + kPlayerW > X68_SCREEN_W) player_x = X68_SCREEN_W - kPlayerW;
}

// 四角を落として、受け止めたかどうかを見る。
static void UpdateItem(void) {
  int caught;

  item_y += kFallSpeed;

  // 四角どうしの当たり判定。「重なっている」= 縦も横も重なっている。
  caught = (item_y + kItemSize >= kPlayerY) &&
           (item_x + kItemSize > player_x) &&
           (item_x < player_x + kPlayerW);
  if (caught) score++;

  // 受け止めたときも、下まで落ちきったときも、上に戻す。
  if (caught || item_y > X68_SCREEN_H) ResetItem();
}

static void Draw(void) {
  x68_cls(x68_rgb(0, 0, 48));
  x68_box_fill(player_x, kPlayerY, kPlayerW, kPlayerH, x68_rgb(0, 255, 255));
  x68_box_fill(item_x, item_y, kItemSize, kItemSize, x68_rgb(255, 224, 0));
  x68_locate(0, 0);
  printf("SCORE %d  ", score);
}

void main(void) {
  x68_screen_open();

  // 乱数の出発点。同じ値を渡すと毎回同じ並びになるので、動きを確かめる
  // ときに便利。毎回違う動きにしたいときは、この行を消してみるとよい。
  srand(20260823);
  ResetItem();

  for (;;) {
    ReadInput();
    UpdateItem();
    Draw();
    x68_screen_flip();
  }
}

プログラム解説

  1. 落とす
    • 1周ごとに item_y を増やすだけで落ちて見える
  2. 当たったか調べる
    • 四角どうしなら、縦と横が重なっているかを && で並べるだけ
    • 難しく見えるが、4つの辺を比べているだけ
  3. 置き直す
    • 受け止めたときも、下まで落ちきったときも、上へ戻す
    • 横位置はそのつどくじを引く(x68_rand_int)

ひとことsrand() に渡す数を変えると、落ちてくる場所の並びが変わります。

6

ブロック崩し

ここまでの全部

おぼえること総合
ブロック崩しの実行画面

遊び方実行画面をクリックしてから、左右キーでパドルを動かします。

変数の意味

paddle_x
パドルの位置
ball_x / ball_y
ボールの位置
ball_dx / ball_dy
ボールの進む向き。跳ね返るときに符号を反転する
block_alive[][]
ブロックが残っているか。2次元配列

プログラム

samples/breakout/block.c エディタで開く
// Sprout68k 作例: ブロック崩し
//
// 学習用ライブラリのL1と標準名の層だけを使う。
// L0 (x68_iocs_* など) は直接呼ばない。文字列リテラル以外の
// ポインタは使わず、配列と添字で書く。
// 最初にブロックをすべて描く。
// 以後は動いた部分だけ描き直す。

#include "x68.h"

enum {
  kScreenWidth = X68_SCREEN_W,
  kScreenHeight = X68_SCREEN_H,
  kPaddleWidth = 64,
  kPaddleHeight = 8,
  kPaddleY = kScreenHeight - 24,
  kPaddleSpeed = 4,
  kBallSize = 8,
  kBallSpeed = 2,
  kBlockRows = 4,
  kBlockColumns = 8,
  kBlockWidth = 56,
  kBlockHeight = 16,
  kBlockGapX = 4,
  kBlockGapY = 4,
  kBlockStartX = 8,
  kBlockStartY = 40,
  kBlockColorCount = 4,
};

static int block_alive[kBlockRows][kBlockColumns];
static int block_color_index[kBlockRows][kBlockColumns];

static int paddle_x;
static int ball_x;
static int ball_y;
static int ball_dx;
static int ball_dy;
static int score;
static long last_destroyed_index;

static int background_color;
static int paddle_color;
static int ball_color;
static int block_color[kBlockColorCount];

static void ResetBall(void) {
  ball_x = kScreenWidth / 2 - kBallSize / 2;
  ball_y = kScreenHeight / 2;
  ball_dx = kBallSpeed;
  ball_dy = -kBallSpeed;
}

void main(void) {
  x68_screen_open();
  srand(1);

  paddle_x = (kScreenWidth - kPaddleWidth) / 2;
  score = 0;
  last_destroyed_index = -1;

  background_color = x68_rgb(0, 0, 0);
  paddle_color = x68_rgb(255, 255, 255);
  ball_color = x68_rgb(255, 255, 0);
  // ブロックの色は4色から乱数で選ぶ。
  // 当たり判定や検証には
  // 影響しない。x68_rand_intの使用例も兼ねている。
  block_color[0] = x68_rgb(255, 64, 64);
  block_color[1] = x68_rgb(64, 255, 64);
  block_color[2] = x68_rgb(64, 64, 255);
  block_color[3] = x68_rgb(255, 255, 64);

  for (int row = 0; row < kBlockRows; ++row) {
    for (int column = 0; column < kBlockColumns; ++column) {
      block_alive[row][column] = 1;
      block_color_index[row][column] = x68_rand_int(kBlockColorCount);
    }
  }

  ResetBall();

  // 動かないブロックは最初に一度だけ描く。
  // 毎回32個を描き直すと
  // 68000ではゲームの動きそのものが遅くなる。
  x68_cls(background_color);
  for (int row = 0; row < kBlockRows; ++row) {
    for (int column = 0; column < kBlockColumns; ++column) {
      int block_x =
          kBlockStartX + column * (kBlockWidth + kBlockGapX);
      int block_y = kBlockStartY + row * (kBlockHeight + kBlockGapY);
      int color = block_color[block_color_index[row][column]];
      x68_box_fill(block_x, block_y, kBlockWidth, kBlockHeight, color);
    }
  }
  x68_box_fill(paddle_x, kPaddleY, kPaddleWidth, kPaddleHeight,
               paddle_color);
  x68_box_fill(ball_x, ball_y, kBallSize, kBallSize, ball_color);
  x68_locate(0, 0);
  printf("SCORE:%d", score);
  x68_screen_flip();

  for (;;) {
    int old_paddle_x = paddle_x;
    int old_ball_x = ball_x;
    int old_ball_y = ball_y;

    // 入力: パドル移動。
    if (x68_key_down(X68_KEY_LEFT)) paddle_x -= kPaddleSpeed;
    if (x68_key_down(X68_KEY_RIGHT)) paddle_x += kPaddleSpeed;
    if (paddle_x < 0) paddle_x = 0;
    if (paddle_x > kScreenWidth - kPaddleWidth) {
      paddle_x = kScreenWidth - kPaddleWidth;
    }

    // ボール移動。
    ball_x += ball_dx;
    ball_y += ball_dy;
    if (ball_x <= 0) {
      ball_x = 0;
      ball_dx = -ball_dx;
    }
    if (ball_x >= kScreenWidth - kBallSize) {
      ball_x = kScreenWidth - kBallSize;
      ball_dx = -ball_dx;
    }
    if (ball_y <= 0) {
      ball_y = 0;
      ball_dy = -ball_dy;
    }

    // 下向きに落ちてきてパドルへ触れたら跳ね返す。
    if (ball_dy > 0 && ball_y + kBallSize >= kPaddleY &&
        ball_y + kBallSize <= kPaddleY + kPaddleHeight &&
        ball_x + kBallSize >= paddle_x &&
        ball_x <= paddle_x + kPaddleWidth) {
      ball_dy = -ball_dy;
    }

    // 画面下に落ちたら中央へ戻す。
    if (ball_y > kScreenHeight) ResetBall();

    // 最初に当たったブロック1個だけ壊す。
    int hit = 0;
    int hit_x = 0;
    int hit_y = 0;
    for (int row = 0; row < kBlockRows && !hit; ++row) {
      for (int column = 0; column < kBlockColumns && !hit; ++column) {
        if (!block_alive[row][column]) continue;
        int block_x =
            kBlockStartX + column * (kBlockWidth + kBlockGapX);
        int block_y = kBlockStartY + row * (kBlockHeight + kBlockGapY);
        if (ball_x + kBallSize > block_x &&
            ball_x < block_x + kBlockWidth &&
            ball_y + kBallSize > block_y &&
            ball_y < block_y + kBlockHeight) {
          block_alive[row][column] = 0;
          ball_dy = -ball_dy;
          score += 10;
          last_destroyed_index =
              (long)(row * kBlockColumns + column);
          hit_x = block_x;
          hit_y = block_y;
          hit = 1;
        }
      }
    }

    // 前の動く物を消し、変わった所だけを描く。
    x68_frame_begin();
    x68_box_fill(old_paddle_x, kPaddleY, kPaddleWidth, kPaddleHeight,
                 background_color);
    x68_box_fill(old_ball_x, old_ball_y, kBallSize, kBallSize,
                 background_color);
    if (hit) {
      x68_box_fill(hit_x, hit_y, kBlockWidth, kBlockHeight,
                   background_color);
    }
    x68_box_fill(paddle_x, kPaddleY, kPaddleWidth, kPaddleHeight,
                 paddle_color);
    x68_box_fill(ball_x, ball_y, kBallSize, kBallSize, ball_color);

    // "SCORE:" と数字4桁程度なので、画面の64桁に収まる。
    if (hit) {
      x68_locate(0, 0);
      printf("SCORE:%d", score);
    }

    x68_screen_flip();
  }
}

プログラム解説

  1. 跳ね返り
    • 壁に当たったら、その向きの符号を反転するだけで跳ね返る
  2. ブロックを消す
    • 2次元配列で「残っているか」を持つ
    • 当たったら 0 にして、描くときに飛ばす

ひとことここまでの作例で出てきたものの組み合わせです。読めるようになっていれば大丈夫。

眺めて楽しむ

短いのに見ていて楽しいものを集めました。数字をいじると見た目が変わるので、好きなように壊してみてください。

7

星空を流す

96個の星が右から左へ

おぼえること配列でまとめて動かす
星空を流すの実行画面

遊び方実行するだけで流れ続けます。

変数の意味

star_x[] / star_y[]
星の位置。配列なので96個ぶん持てる
star_speed[]
星ごとの速さ。速い星ほど手前に見える

プログラム

ide/samples/stars.c エディタで開く
// Sprout68k 作例: 星空を流す
//
// 配列を使うと、たくさんの物をまとめて動かせる。ここでは星を220個持ち、
// 1周ごとに全部を少しずつ左へずらしている。
// 星ごとに速さを変えると、遠近感が出る(速い星ほど手前に見える)。

#include "x68.h"

enum {
  kStarCount = 220,
};

static int star_x[kStarCount];
static int star_y[kStarCount];
static int star_speed[kStarCount];

// 星を1つ、指定した横位置に置き直す。縦位置と速さはその都度くじを引く。
static void ResetStar(int index, int x) {
  star_x[index] = x;
  star_y[index] = x68_rand_int(X68_SCREEN_H);
  star_speed[index] = 1 + x68_rand_int(3);
}

void main(void) {
  int background = x68_rgb(0, 0, 0);
  int i;

  x68_screen_open();
  srand(20260823);

  // 最初は画面のあちこちに散らばらせる。
  for (i = 0; i < kStarCount; i++) {
    ResetStar(i, x68_rand_int(X68_SCREEN_W));
  }

  for (;;) {
    x68_cls(background);
    for (i = 0; i < kStarCount; i++) {
      star_x[i] -= star_speed[i];

      // 左端まで来たら右端へ戻す。こうすると星が尽きない。
      if (star_x[i] < 0) ResetStar(i, X68_SCREEN_W - 1);

      // 速い星ほど明るく描くと、手前にあるように見える。
      x68_pset(star_x[i], star_y[i],
               x68_rgb(70 * star_speed[i], 70 * star_speed[i],
                       85 * star_speed[i]));
    }
    x68_screen_flip();
  }
}

プログラム解説

  1. 配列にする
    • 同じ扱いの物がたくさんあるときは配列にして、for でまとめて動かす
    • 星を増やしたいときは kStarCount を変えるだけ
  2. 遠近感を出す
    • 速さをばらけさせ、速い星ほど明るく描く
    • たったこれだけで奥行きが出る

ひとことkStarCount を増やすと濃くなります。速さの範囲を広げるほど奥行きが出ます。

8

ライフゲーム

3つの規則だけで模様が動く

おぼえること2次元配列を2枚使う
ライフゲームの実行画面

遊び方実行するだけで世代が進みます。

変数の意味

cells[][]
今の世代。生きていれば1
work[][]
次の世代を書く紙。書き終わってから cells へ移す

プログラム

ide/samples/life.c エディタで開く
// Sprout68k 作例: ライフゲーム
//
// 格子の上のマスが「生きている」か「死んでいる」かを、周りの8マスの数だけで
// 決めていく。規則はこの3つだけ。
//   ・生きているマスは、周りが2つか3つなら生き残る
//   ・生きているマスは、それ以外なら死ぬ
//   ・死んでいるマスは、周りがちょうど3つなら生まれる
// たったこれだけで、模様が動いたり増えたりする。
//
// 2次元配列を2枚使うのが要点。今の世代を見ながら次の世代を別の紙に書き、
// 書き終わってから入れ替える。1枚で書き換えると、まだ見ていないマスが
// 書き換わってしまって規則が崩れる。

#include "x68.h"

enum {
  kCols = 32,   // 横のマス数
  kRows = 32,   // 縦のマス数
  kCell = 16,   // 1マスの大きさ(ドット)
};

static char cells[kRows][kCols];
static char work[kRows][kCols];

// 周りの8マスのうち、生きている数を数える。
// 画面の外は「死んでいる」として数えない。
static int CountNeighbors(int row, int col) {
  int count = 0;
  int dr;
  int dc;

  for (dr = -1; dr <= 1; dr++) {
    for (dc = -1; dc <= 1; dc++) {
      int r = row + dr;
      int c = col + dc;

      if (dr == 0 && dc == 0) continue;
      if (r < 0 || r >= kRows || c < 0 || c >= kCols) continue;
      if (cells[r][c]) count++;
    }
  }
  return count;
}

// 次の世代を work に書き、書き終わってから cells へ移す。
static void StepGeneration(void) {
  int row;
  int col;

  for (row = 0; row < kRows; row++) {
    for (col = 0; col < kCols; col++) {
      int neighbors = CountNeighbors(row, col);

      if (cells[row][col]) {
        work[row][col] = (neighbors == 2 || neighbors == 3);
      } else {
        work[row][col] = (neighbors == 3);
      }
    }
  }
  memcpy(cells, work, sizeof(cells));
}

static void Draw(void) {
  int alive = x68_rgb(0, 255, 128);
  int row;
  int col;

  x68_cls(x68_rgb(0, 0, 32));
  for (row = 0; row < kRows; row++) {
    for (col = 0; col < kCols; col++) {
      if (cells[row][col]) {
        x68_box_fill(col * kCell, row * kCell, kCell - 1, kCell - 1, alive);
      }
    }
  }
}

void main(void) {
  int row;
  int col;

  x68_screen_open();
  srand(20260823);

  // 最初の世代をくじで決める。3回に1回くらい生きているようにする。
  for (row = 0; row < kRows; row++) {
    for (col = 0; col < kCols; col++) {
      cells[row][col] = (x68_rand_int(3) == 0);
    }
  }

  for (;;) {
    Draw();
    x68_screen_flip();
    StepGeneration();
  }
}

プログラム解説

  1. 規則は3つだけ
    • 生きている → 周りが2つか3つなら生き残る
    • 生きている → それ以外なら死ぬ
    • 死んでいる → 周りがちょうど3つなら生まれる
  2. 紙を2枚使う理由
    • 今の世代を見ながら、次の世代は別の紙に書く
    • 1枚で書き換えると、まだ見ていないマスが書き換わって規則が崩れる
    • 書き終わってから memcpy でまとめて移す

ひとことkCell を小さくするとマスが増えて細かくなりますが、そのぶん遅くなります。