Sprout68k 関数リファレンス

Sprout68kとは 作例集 使い方 アプリを開く

この本の使い方

Sprout68k で書けるプログラムに出てくる関数を、目的から引けるようにまとめたものです。上から順に読むと一通り書けるようになり、後半の「関数図鑑」はコードを書きながら引くための一覧になっています。

できることは、いまのところこの3つです。

音を鳴らす関数はまだありません。マウスやジョイスティックも未対応です。増えたらこのページに追加します。

やりたいことから引く

やりたいこと使う関数
絵を描く準備をするx68_screen_open
描いた絵を画面に出すx68_screen_flip
画面を消す・背景色を塗るx68_cls
点を打つ/その色を読むx68_psetx68_pget
線を引くx68_line
四角を描く(枠・塗り)x68_boxx68_box_fill
円を描くx68_circle
色を作るx68_rgb
キーが押されているか調べるx68_key_downX68_KEY_*
点数やメッセージを表示するprintfputs
文字を出す位置を決めるx68_locate
ランダムな値がほしいx68_rand_int(+srand
距離を測る(符号を消す)abs
配列を初期化する・複製するmemsetmemcpy
速さを一定に揃えるx68_screen_flip(絵を描かないなら x68_vsync_wait
ディスクからデータを読むx68_iocs_disk_read(上級)

いちばん短いプログラム

まずはこれです。エディタに貼り付けて実行を押すと、X68000 の画面に文字が出ます。

#include "x68.h"

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

main から抜けると、起動時のプログラムが停止して待ち続けます。表示するだけのプログラムに for (;;) を書く必要はありません。

絵を描く — 3 つの決まりごと

絵を出すには、かならず次の 3 段が要ります。

  1. x68_screen_open() で画面を開く(最初に 1 回だけ)
  2. 描画関数で描く
  3. x68_screen_flip() で画面に出す

描画関数が描いているのは画面そのものではなく、メモリの上の「下書き用紙」です。x68_screen_flip() を呼んだときに、そこから画面へまとめて送られます。この一手間があるおかげで、描いている途中の絵が表示されてチラつくことがありません。絵が出ないときは、まずこの 3 段のどれかが抜けていないかを確かめてください。

#include "x68.h"

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

  x68_screen_open();
  x68_cls(black);
  x68_box_fill(160, 160, 192, 192, x68_rgb(255, 128, 0));
  x68_circle(256, 256, 120, x68_rgb(255, 255, 255));
  x68_line(0, 0, 511, 511, x68_rgb(0, 255, 255));
  x68_screen_flip();
}

座標

左上が (0, 0)、右下が (511, 511) です。y は下へ行くほど大きくなります(算数のグラフとは上下が逆です)。画面からはみ出した部分は自動的に切り取られるので、はみ出しを自分で計算する必要はありません。

色は x68_rgb(赤, 緑, 青) で作ります。それぞれ 0〜255 で、0 が暗く 255 が明るい値です。よく使う色は変数に入れて名前を付けておくと、あとで読み返したときに分かりやすくなります。

int black = x68_rgb(0, 0, 0);
int white = x68_rgb(255, 255, 255);
int red   = x68_rgb(255, 0, 0);

x68_screen_open();
x68_cls(black);
x68_box_fill(180, 180, 152, 152, red);
x68_box(180, 180, 152, 152, white);
x68_screen_flip();

ゲームの形

動くものを作るときは、次の 1 周をずっと繰り返します。この形はどのゲームでも変わりません。

x68_screen_open();
for (;;) {
  /* 1. 画面を消す   */
  /* 2. キーを読む   */
  /* 3. 位置を計算する */
  /* 4. 描く         */
  /* 5. 画面に出す   */
}

「5. 画面に出す」の x68_screen_flip() が画面の書き換えを待つので、この 1 周は毎秒およそ 55〜60 回になります(描く物がおよそ 60 件までなら毎秒 60 回を保てます。描く物が増えると 1 周が 2 フレーム、3 フレームとかかるようになり、遅くなります)。

下は、この形をそのまま使った短いゲームです。落ちてくる四角を、左右に動く棒で受け止めます。

#include "x68.h"

enum {
  kPlayerW = 48,
  kPlayerH = 12,
  kPlayerY = 460,
  kItemSize = 16,
  kSpeed = 4,
};

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

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

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

  item_y += 2;
  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) {
    item_y = 0;
    item_x = x68_rand_int(X68_SCREEN_W - kItemSize);
  }
}

/* 3 描く */
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();
  for (;;) {
    ReadInput();
    UpdateItem();
    Draw();
    x68_screen_flip();
  }
}

「押した瞬間だけ 1 回」にしたいときは、前のフレームの状態を変数に覚えておいて比べます。書き方は X68_KEY_* の例にあります。

速さを決める

x68_screen_flip() が画面の書き換えを待つので、間に合っているかぎり 1 周はいつも 1/60 秒です。速さが勝手にぶれることはありません。だから速さは「1 周に何ドット動かすか」だけで決まります。

1 周に足す値速さ画面(512 ドット)を横切る時間
1毎秒 60 ドット8.5 秒
2毎秒 120 ドット4.3 秒
4毎秒 240 ドット2.1 秒
8毎秒 480 ドット1.1 秒

迷ったら、自分で動かすものは 4、勝手に動くものは 2 から試すとちょうどよく感じられます。作例の move.c は 4、catch.c の落ちてくる四角は 2 です。

もっとゆっくり動かしたい

1 周に 1 ドットでも速すぎるときに、間引いてはいけません。次のように書くと、動かない周が 4 回続いてから 6 ドット飛ぶので、カクカクして見えます。

static int count = 0;
static int x = 100;

if (count % 5 == 0) {   /* これはカクつく */
  x += 6;
}
count++;
x68_box_fill(x, 200, 16, 16, x68_rgb(255, 255, 0));

そうではなく、本当の位置を 16 倍した整数で持ちます。この環境に小数はありませんが、16 倍で持てば 1/16 ドット単位で動かせます。画面に出すときだけ 16 で割ります(>> 4 が 16 で割ることにあたります)。

static int x16 = 100 * 16;   /* 本当の位置を 16 倍で持つ */

x16 += 12;                    /* 1 周に 0.75 ドット = 毎秒 45 ドット */
x68_box_fill(x16 >> 4, 200, 16, 16, x68_rgb(255, 255, 0));

16 のかわりに 10 や 100 を使っても構いませんが、16・256 のような 2 の累乗にしておくと、割り算が >> で済むぶん速くなります。

描ける量には上限がある

1 周を 1/60 秒に収めるには、描く回数がおよそ 80 回までである必要があります(全部が止まっているなら 100 回まで)。x68_pset x68_box_fill x68_line x68_circle がそれぞれ 1 回で、x68_box だけは 4 辺を描くので 4 回と数えます。x68_cls は数えません。

超えると 1 周が 2 フレーム、3 フレームとかかるようになり、ゲーム全体がそのぶん遅くなります。玉もパドルも点数も、まとめて半分の速さになります。「なんとなく重い」と感じたら、まず 1 周に何回描いているかを数えてみてください。

大きさは関係ありません。画面いっぱいの四角を 1 個描くのも、小さな四角を 1 個描くのも、同じ 1 回です。上限は「面積」ではなく「回数」で決まります。

動かないものがたくさんあるとき

ブロック崩しのブロックのように、置いたまま動かないものが多いときは、書き方が 2 通りあります。

1. 毎周ぜんぶ描き直すx68_cls を使う、素直な書き方)。同じ場所に同じものを描き直しても、変わっていないことをライブラリが見分けるので、塗り直しの費用はかかりません。ただし描く回数は数えるので、上の 80 回の上限には入ります。作例の catch.clife.cstars.c はこの書き方です。

2. 動くものだけ描くx68_frame_begin を使う書き方)。動かないものは最初に 1 回だけ描いておき、以後は「前の位置を背景色で消して、新しい位置に描く」だけにします。描く回数が動くものの分だけで済むので、上限に余裕ができます。作例の breakout がこの書き方です。

2 の書き方では、動いていないものまで毎周消して描き直すと、かえって遅くなります。「前の位置と今の位置が違うときだけ消して描く」と書いてください。ここを守らないと、1 の素直な書き方より遅くなることがあります。

画面を開く・絵を出す

絵を描くには、まず画面を開きます。描画関数が描くのは画面そのものではなく「裏バッファ」という下書き用紙で、x68_screen_flip() を呼んだときに初めて画面へ出ます。この一手間があるおかげで、絵が描きかけのまま表示されてチラつくことがありません。

x68_screen_openグラフィック画面を使えるようにする

void x68_screen_open(void)

512×512 ドット・65536 色のグラフィック画面に切り替え、絵を描くための裏バッファ(メインメモリ上の下書き用紙、512KB)を用意します。

プログラムの最初に 1 回だけ呼びます。以降の描画関数はすべてこの裏バッファに描きます。

#include "x68.h"

void main(void) {
  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  x68_box_fill(200, 200, 100, 100, x68_rgb(255, 255, 0));
  x68_screen_flip();
}

画面を開いて、黄色い四角を 1 つ描くだけの最小の例です。x68_screen_flip() を呼んで初めて画面に出ます。

  • これを呼ぶ前に x68_pset() などを呼んでも、何も起きません(エラーにはならないので、絵が出ないときは最初にこれを疑ってください)。
  • 呼んだ直後の画面の中身は決まっていません。まず x68_cls() で背景色を塗ってから描き始めます。

関連x68_clsx68_screen_flip

x68_screen_flip描いた絵を画面に出す

void x68_screen_flip(void)

裏バッファに描いた内容を、実際の画面へ送ります。1 フレームに 1 回呼びます。

送る前に垂直同期(画面の書き換えが一巡する瞬間)を待つので、絵が途中でちぎれて見えることがありません。ゲームの速さがこの待ち時間で決まります(毎秒およそ 55〜60 回)。

送るのは画面全部ではなく「前のフレームと今のフレームで描いた範囲」だけです。動く物が少ないほど速くなります。

#include "x68.h"

void main(void) {
  int x = 0;

  x68_screen_open();
  for (;;) {
    x68_cls(x68_rgb(0, 0, 32));
    x68_box_fill(x, 240, 32, 32, x68_rgb(255, 128, 0));
    x68_screen_flip();
    x = (x + 1) % X68_SCREEN_W;
  }
}

四角を 1 ドットずつ右へ動かします。x68_cls() で前の位置を消し、描いて、flip で出す——これがゲームの基本の 1 周です。

  • 呼び忘れると、いくら描いても画面は変わりません。
  • 1 フレームに 2 回以上呼ぶと、そのたびに垂直同期を待つので単純に遅くなります。

関連x68_screen_openx68_clsx68_frame_begin

x68_cls前のフレームに描いた絵を消す

void x68_cls(int color)

名前は clear screen(画面消去)ですが、毎回画面全部を塗り直すわけではありません。前のフレームで描いた場所だけを背景色で塗り戻します。動く物が少ないゲームほど速くなる仕組みです。

見た目は「背景色で画面を消した」のと同じになります。全画面を塗り直すのは、最初の 1 回と、前回と違う色を渡したときだけです。

color
背景の色。x68_rgb() で作った値を渡します。
#include "x68.h"

void main(void) {
  x68_screen_open();
  for (;;) {
    x68_cls(x68_rgb(0, 0, 64));
    x68_pset(x68_rand_int(X68_SCREEN_W), x68_rand_int(X68_SCREEN_H), x68_rgb(255, 255, 255));
    x68_screen_flip();
  }
}

毎フレーム、濃い青で消してから点を打ち直しています。

  • 引数は「消す量」ではなく背景の色です。
  • 毎フレーム違う色を渡すと、そのたびに全画面を塗り直すことになって遅くなります。背景色は固定にしておくのが無難です。

関連x68_frame_beginx68_rgb

x68_frame_begin画面は消さずに、描画の記録だけ新しくする

void x68_frame_begin(void)

x68_cls() の代わりに呼びます。裏バッファの絵はそのまま残し、「今のフレームでどこを描いたか」の記録だけを空にします。

背景の絵(地面や星空など)を一度描いたら消したくない、動く物だけ描き替えたい、という場合に使います。前の位置を消す作業は自分で行います(そこに背景色の四角を描くなど)。

#include "x68.h"

void main(void) {
  int x = 0;
  int i;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (i = 0; i < 200; i++) {
    x68_pset(x68_rand_int(X68_SCREEN_W), x68_rand_int(X68_SCREEN_H), x68_rgb(255, 255, 255));
  }
  for (;;) {
    x68_frame_begin();
    x68_box_fill(x, 240, 32, 32, x68_rgb(0, 0, 0));
    x = (x + 1) % X68_SCREEN_W;
    x68_box_fill(x, 240, 32, 32, x68_rgb(0, 255, 128));
    x68_screen_flip();
  }
}

星空は最初に 1 回だけ描き、以降は消しません。四角の前の位置だけを自分で黒く塗って消しています。

  • 消す処理を自分で書かないと、動いた跡がそのまま残り続けます(それを狙って線を引く使い方もできます)。

関連x68_clsx68_screen_flip

図形を描く

座標は左上が (0, 0)、右下が (511, 511) です。画面からはみ出した部分は自動的に切り取られるので、はみ出しを気にせず書けます(プログラムが落ちることはありません)。

x68_pset点を 1 つ打つ

void x68_pset(int x, int y, int color)

指定した座標に 1 ドットだけ色を置きます。いちばん基本の描画です。

x
横位置。0 が左端、511 が右端。
y
縦位置。0 が上端、511 が下端。
color
点の色。x68_rgb() で作った値。
#include "x68.h"

void main(void) {
  int i;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (i = 0; i < 512; i++) {
    x68_pset(i, i, x68_rgb(255, 255, 255));
  }
  x68_screen_flip();
}

斜めの線を点で描いています。画面外に出た分は自動的に無視されます。

  • 画面の外の座標を渡しても何も起きません(落ちません)。
  • 点を何万個も打つのは遅いので、まとまった面は x68_box_fill() を使ってください。

関連x68_pgetx68_line

x68_pgetその点に今ある色を読む

int x68_pget(int x, int y)

指定した座標の色を読みます。読むのは裏バッファなので、まだ x68_screen_flip() していない、今のフレームで描いたばかりの色も見えます。

「そこに壁があるか」を色で判定する、といった当たり判定に使えます。

x
横位置。
y
縦位置。

返り値その点の色。画面の外を指定した場合は 0。

#include "x68.h"

void main(void) {
  int wall = x68_rgb(255, 0, 0);

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  x68_box_fill(240, 0, 32, 512, wall);
  if (x68_pget(250, 100) == wall) {
    printf("HIT WALL");
  }
  x68_screen_flip();
}

画面の中央に壁を描き、その色と読み取った色が同じかどうかで当たりを判定しています。

  • 0 は「黒」でもあり「画面の外」でもあります。端をまたぐ判定では、座標のほうを先に確かめてください。

関連x68_pset

x68_line2 点を結ぶ直線を引く

void x68_line(int x1, int y1, int x2, int y2, int color)

(x1, y1) から (x2, y2) まで、太さ 1 ドットの線を引きます。

x1
始点の横位置。
y1
始点の縦位置。
x2
終点の横位置。
y2
終点の縦位置。
color
線の色。
#include "x68.h"

void main(void) {
  int i;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (i = 0; i < 512; i += 32) {
    x68_line(256, 256, i, 0, x68_rgb(0, 255, 255));
  }
  x68_screen_flip();
}

画面の中心から放射状に線を引いています。両端が画面の外でも落ちません。

  • 太い線は引けません。太くしたいときは少しずらして何本か引きます。

関連x68_psetx68_box

x68_box四角の枠を描く

void x68_box(int x, int y, int w, int h, int color)

左上が (x, y)、幅 w、高さ h の四角形の枠(太さ 1 ドット)を描きます。中は塗りません。

x
左上の横位置。
y
左上の縦位置。
w
幅(ドット)。0 以下なら何も描きません。
h
高さ(ドット)。0 以下なら何も描きません。
color
枠の色。
#include "x68.h"

void main(void) {
  int i;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (i = 0; i < 8; i++) {
    x68_box(i * 16, i * 16, 512 - i * 32, 512 - i * 32, x68_rgb(255, 255 - i * 30, 0));
  }
  x68_screen_flip();
}

入れ子の枠を描いています。

  • w と h は「右端・下端の座標」ではなく「幅・高さ」です。

関連x68_box_fill

x68_box_fill四角を塗りつぶす

void x68_box_fill(int x, int y, int w, int h, int color)

左上が (x, y)、幅 w、高さ h の四角形を塗りつぶします。

この環境でいちばん速い描画です。自機・敵・ブロック・棒グラフなど、たいていの物はこれで作れます。

x
左上の横位置。
y
左上の縦位置。
w
幅(ドット)。0 以下なら何も描きません。
h
高さ(ドット)。0 以下なら何も描きません。
color
塗る色。
#include "x68.h"

void main(void) {
  int i;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (i = 0; i < 16; i++) {
    x68_box_fill(i * 32, 100, 32, 300, x68_rgb(i * 16, 0, 255 - i * 16));
  }
  x68_screen_flip();
}

色の帯を 16 本並べています。

  • 画面からはみ出す分は切り取られます。はみ出しを自分で計算する必要はありません。

関連x68_boxx68_cls

x68_circle円の輪郭を描く

void x68_circle(int x, int y, int r, int color)

中心 (x, y)、半径 r の円を、太さ 1 ドットの輪郭で描きます。中は塗りません。

x
中心の横位置。
y
中心の縦位置。
r
半径(ドット)。0 以下なら何も描きません。
color
線の色。
#include "x68.h"

void main(void) {
  int r;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (r = 16; r < 256; r += 16) {
    x68_circle(256, 256, r, x68_rgb(0, 255, 128));
  }
  x68_screen_flip();
}

同心円を描いています。

  • 塗りつぶした円を描く関数はありません。半径を 1 ずつ変えて何重にも描くと塗りつぶせます(少し遅くなります)。

関連x68_box_fill

色を作る

色は赤・緑・青の3つの強さ(0〜255)から作ります。作った色の値を、描画関数の color に渡します。

x68_rgb赤・緑・青の強さから色を作る

int x68_rgb(int r, int g, int b)

赤・緑・青をそれぞれ 0〜255 の強さで指定して、描画関数に渡せる色の値を作ります。0 が最も暗く、255 が最も明るい値です。

画面が実際に区別できるのは各色 32 段階なので、たとえば 100 と 103 のような近い値は同じ色になります。

r
赤の強さ(0〜255)。範囲外は 0 か 255 に丸められます。
g
緑の強さ(0〜255)。
b
青の強さ(0〜255)。

返り値描画関数の color に渡す色の値。

#include "x68.h"

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

  x68_screen_open();
  x68_cls(black);
  x68_box_fill(100, 100, 300, 100, red);
  x68_box_fill(100, 300, 300, 100, white);
  x68_screen_flip();
}

よく使う色をあらかじめ変数に入れておくと読みやすくなります。毎フレーム呼んでも構いませんが、名前が付くぶん変数にする利点が大きいです。

  • 色の値をそのまま数字で書く(0xF81F など)こともできますが、並び順が独特なので x68_rgb() を使うのが安全です。

関連X68_SCREEN_W

X68_SCREEN_W画面の幅(512)

#define X68_SCREEN_W 512

画面の横のドット数です。使える x 座標は 0 〜 X68_SCREEN_W - 1(0〜511)。

画面の端を扱うときは 512 と直接書かず、この名前を使っておくと後で見返したときに意図が分かります。

#include "x68.h"

void main(void) {
  int x = 0;
  int dx = 3;
  int w = 32;

  x68_screen_open();
  for (;;) {
    x68_cls(x68_rgb(0, 0, 0));
    x += dx;
    if (x < 0 || x + w > X68_SCREEN_W) dx = -dx;
    x68_box_fill(x, 240, w, 32, x68_rgb(255, 255, 0));
    x68_screen_flip();
  }
}

画面の右端で跳ね返る処理です。

関連X68_SCREEN_H

X68_SCREEN_H画面の高さ(512)

#define X68_SCREEN_H 512

画面の縦のドット数です。使える y 座標は 0 〜 X68_SCREEN_H - 1(0〜511)。

X68000 の画面は横長ですが、このライブラリが使うモードでは縦横とも 512 ドットの正方形です。

#include "x68.h"

void main(void) {
  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  x68_line(X68_SCREEN_W / 2, 0, X68_SCREEN_W / 2, X68_SCREEN_H - 1, x68_rgb(64, 64, 64));
  x68_line(0, X68_SCREEN_H / 2, X68_SCREEN_W - 1, X68_SCREEN_H / 2, x68_rgb(64, 64, 64));
  x68_screen_flip();
}

画面のちょうど真ん中に印を描きます。

関連X68_SCREEN_W

キーボードを読む

キーボードは「今このキーが押されているか」を聞く形で読みます。押した瞬間だけ反応させたいときは、前のフレームの状態を自分で覚えておいて比べます。

x68_key_downそのキーが今押されているかを調べる

int x68_key_down(int key)

キーが押されている「間ずっと」真を返します。押した瞬間だけ・離した瞬間だけを取りたい場合は、前のフレームの状態を変数に覚えておいて、変わったかどうかを自分で比べます。

複数のキーを同時に調べられるので、斜め移動もそのまま書けます。

key
調べたいキー。X68_KEY_ で始まる定数(下の表)を渡します。

返り値押されていれば 0 以外、押されていなければ 0。知らないキー番号を渡しても落ちず、0 を返します。

#include "x68.h"

void main(void) {
  int x = 240;
  int y = 240;

  x68_screen_open();
  for (;;) {
    x68_cls(x68_rgb(0, 0, 0));
    if (x68_key_down(X68_KEY_LEFT)) x -= 4;
    if (x68_key_down(X68_KEY_RIGHT)) x += 4;
    if (x68_key_down(X68_KEY_UP)) y -= 4;
    if (x68_key_down(X68_KEY_DOWN)) y += 4;
    x68_box_fill(x, y, 32, 32, x68_rgb(0, 255, 255));
    x68_screen_flip();
  }
}

カーソルキーで四角を動かします。上下左右を別々に調べているので、斜めにも動きます。

  • 押しっぱなしでも毎フレーム真になります。「1 回押したら 1 回だけ」にしたいときは前回の状態と比べてください。
  • この関数を呼んでもキー入力は溜まりません。x68_screen_flip() のタイミングで 1 回読む、という書き方が基本です。

関連X68_KEY_LEFT

X68_KEY_LEFTキーを表す定数の一覧

#define X68_KEY_LEFT 0x3b  /* ほか X68_KEY_* 定数 */

x68_key_down() に渡す定数です。値は X68000 のキーボードが実際に送るコード(スキャンコード)で、実測して確かめたものです。

ここに無いキーも、コードさえ分かれば数値を直接渡せば読めます。

定数キー
移動・操作
X68_KEY_LEFT← カーソル左
X68_KEY_UP↑ カーソル上
X68_KEY_RIGHT→ カーソル右
X68_KEY_DOWN↓ カーソル下
X68_KEY_SPACEスペース
X68_KEY_ENTERリターン
X68_KEY_ESCESC
数字
X68_KEY_0 〜 X68_KEY_90 〜 9(最上段の数字キー)
アルファベット
X68_KEY_A 〜 X68_KEY_ZA 〜 Z
#include "x68.h"

void main(void) {
  int previous = 0;
  int count = 0;

  x68_screen_open();
  for (;;) {
    int current = x68_key_down(X68_KEY_SPACE);
    if (current && !previous) {
      count++;
      x68_locate(0, 0);
      printf("COUNT=%d", count);
    }
    previous = current;
    x68_screen_flip();
  }
}

スペースキーを「押した瞬間」だけ拾う書き方です。押しっぱなしでは 1 回しか数えません。

関連x68_key_down

文字を出す

文字はグラフィック画面とは別の「テキスト画面」に出ます。絵の上に重ねて出るので、点数やデバッグ表示にそのまま使えます。

printf書式を指定して文字を出す

int printf(const char *fmt, ...)

C でおなじみの printf です。文字列の中の %d などの場所に、後ろに並べた値が埋め込まれて表示されます。

この環境の printf は自前の小さな実装なので、使える書式は下の表のものだけです。表に無い書式を書くと、その場所に [BADFMT] と表示されます(黙って無視したり、変な値を出したりはしません)。

1 回の出力は 256 バイトまでです。超えた分は切り捨てられます(落ちはしません)。

fmt
書式を含む文字列。改行したいところには \n を書きます。
...
書式に埋め込む値。左から順に対応します。

返り値出力した文字数。

書式意味
使える書式
%dint を 10 進数で(符号つき)
%uunsigned int を 10 進数で(符号なし)
%xunsigned int を 16 進数で(小文字 a〜f)
%c1 文字として
%s文字列として
%%% の文字そのもの
使えない書式([BADFMT] と表示されます)
%f %e %g小数。この環境に小数の表示はありません
%3d %02x %.2s桁数・0 埋め・精度の指定
%ld %lulong などの長さ指定
%q など上に無い文字
#include "x68.h"

void main(void) {
  int score = 1200;
  int x = 100;

  x68_screen_open();
  x68_locate(0, 0);
  printf("SCORE=%d\n", score);
  printf("X=%d HEX=%x\n", x, x);
  x68_screen_flip();
}

点数と座標を表示しています。%d が 2 つあるので、値も 2 つ渡します。

  • %3d のような桁揃えは使えません。桁を揃えたいときは、自分で空白を足すか x68_locate() で位置を決めてください。
  • 小数は扱えません。1.5 倍したいときは 10 倍した整数で計算する、といった工夫をします。

関連putsx68_locate

puts文字列を出して改行する

int puts(const char *s)

文字列をそのまま表示し、最後に必ず改行を 1 つ足します(本物の C の puts と同じ動きです)。

書式が要らないときは printf より短く書けます。

s
表示する文字列。

返り値常に 0。

#include "x68.h"

void main(void) {
  x68_screen_open();
  puts("SPROUT68K");
  puts("READY.");
  puts("PRESS SPACE");
  x68_screen_flip();
}

3 行表示します。改行は puts が自動で入れるので \n は書きません。

  • 改行が必ず付きます。付けたくない場合は printf("%s", s) を使ってください。

関連printf

x68_locate次に文字を出す位置を決める

void x68_locate(int col, int row)

printf や puts が次に文字を出す位置を指定します。文字の画面は横 96 桁 × 縦 32 行です(グラフィックの座標とは別の数え方です)。

毎フレーム同じ場所に点数を出したいときは、描く前にここで位置を戻します。

col
桁(横位置)。0 が左端、95 が右端。
row
行(縦位置)。0 が最上段、31 が最下段。
#include "x68.h"

void main(void) {
  int score = 0;

  x68_screen_open();
  for (;;) {
    x68_cls(x68_rgb(0, 0, 48));
    score++;
    x68_locate(70, 1);
    printf("SCORE %d", score);
    x68_screen_flip();
  }
}

画面の右上に点数を出し続けます。毎フレーム同じ位置に戻すので、行が流れていきません。

  • 画面の外の位置を渡しても何も起きません(落ちません)。
  • 文字はグラフィックの上に重なって出ます。x68_cls() では消えないので、上書きするか同じ位置に出し直します。

関連printfputs

数と乱数

計算まわりの小さな道具です。敵の出現位置をばらけさせたいときなどに乱数を使います。

srand乱数の出方を決める

void srand(unsigned int seed)

乱数の並びの出発点を決めます。同じ seed を渡せば、毎回まったく同じ順番の乱数が出ます。

そのため、テスト中は固定の値を渡して「毎回同じ動き」にしておくと、不具合を追いやすくなります。

seed
乱数の出発点となる数。
#include "x68.h"

void main(void) {
  int i;

  x68_screen_open();
  srand(1234);
  for (i = 0; i < 5; i++) {
    printf("%d ", x68_rand_int(100));
  }
  x68_screen_flip();
}

同じ seed からは同じ並びが出ます。この例は実行のたびに同じ 5 つの数を表示します。

  • 呼ばなくても乱数は使えますが、その場合は毎回同じ並びになります。
  • この環境には時計がありません。「毎回違う」にしたいときは、タイトル画面でキーが押されるまでに回った回数を seed にする、といった工夫をします。

関連randx68_rand_int

rand乱数を 1 つ取り出す

int rand(void)

0 〜 X68_RAND_MAX(32767)の範囲の値を返します。範囲を決めたい場合は、割り算をするより x68_rand_int() のほうが安全で読みやすいです。

返り値0 〜 X68_RAND_MAX の値。

#include "x68.h"

void main(void) {
  x68_screen_open();
  printf("%d\n", rand());
  printf("MAX=%d\n", X68_RAND_MAX);
  x68_screen_flip();
}

そのままの乱数を表示します。範囲を絞りたいときは x68_rand_int() を使ってください。

  • 暗号やくじ引きのような、予測されて困る用途には使えない簡単な方式です。

関連x68_rand_intsrand

x68_rand_int0 〜 n-1 の乱数を取り出す

int x68_rand_int(int n)

欲しい範囲を直接指定できる乱数です。たとえば x68_rand_int(512) なら、画面のどこかの座標として使える 0〜511 が返ります。

n
欲しい範囲の大きさ。0 以下を渡した場合は 0 を返します(0 で割って落ちることはありません)。

返り値0 以上 n 未満の値。

#include "x68.h"

void main(void) {
  int i;

  x68_screen_open();
  x68_cls(x68_rgb(0, 0, 0));
  for (i = 0; i < 50; i++) {
    x68_box_fill(x68_rand_int(X68_SCREEN_W), x68_rand_int(X68_SCREEN_H), 24, 24,
                 x68_rgb(x68_rand_int(256), x68_rand_int(256), x68_rand_int(256)));
  }
  x68_screen_flip();
}

ランダムな位置・ランダムな色の四角を 50 個ばらまきます。

  • n は「最大値」ではなく「個数」です。1〜6 のサイコロは x68_rand_int(6) + 1 と書きます。

関連randsrand

X68_RAND_MAXrand() が返す最大値(32767)

#define X68_RAND_MAX 0x7fff

rand() が返しうるいちばん大きい値です。

#include "x68.h"

void main(void) {
  int value = rand() * 100 / (X68_RAND_MAX + 1);

  x68_screen_open();
  printf("%d", value);
  x68_screen_flip();
}

乱数を 0〜99 の範囲に直しています(この計算をしてくれるのが x68_rand_int() です)。

関連rand

abs符号を取って絶対値にする

int abs(int n)

負の数なら正にして返します。2 点の距離を測るときなどに使います。

n
対象の値。

返り値n の絶対値。

#include "x68.h"

void main(void) {
  int ax = 100;
  int bx = 130;

  x68_screen_open();
  if (abs(ax - bx) < 40) {
    printf("NEAR");
  } else {
    printf("FAR");
  }
  x68_screen_flip();
}

2 つの四角が近づいたかどうかを、中心の距離で判定しています。

  • int で表せる最小の値 -2147483648 だけは、符号を反転した 2147483648 が int に収まらないため、負のまま返ります。C 言語の仕様でそうなる場所で、パソコン側の C コンパイラでも同じ結果になります。

メモリと文字列

C の標準ライブラリでおなじみの関数のうち、この環境で使えるものです。名前と使い方は本物の C と同じです。

memcpyメモリの内容をまとめて複製する

void *memcpy(void *dst, const void *src, unsigned long n)

src から dst へ n バイトぶんそのまま複製します。配列まるごとのコピーに使います。

dst
複製先。
src
複製元。
n
複製するバイト数。

返り値dst をそのまま返します。

#include "x68.h"

void main(void) {
  int source[4];
  int copy[4];
  int i;

  for (i = 0; i < 4; i++) source[i] = i * 10;
  memcpy(copy, source, sizeof(source));

  x68_screen_open();
  printf("%d %d", copy[0], copy[3]);
  x68_screen_flip();
}

配列を丸ごと複製しています。sizeof を使うと、要素数を変えても書き直さずに済みます。

  • 複製元と複製先が重なっている場合の動きは保証しません(重なるときは自分でループを書いてください)。

関連memset

memsetメモリを同じ値で埋める

void *memset(void *dst, int c, unsigned long n)

dst から n バイトぶんを、値 c で埋めます。配列を 0 で初期化するのに使うのが定番です。

dst
埋める先。
c
埋める値(1 バイトぶんだけ使われます)。
n
埋めるバイト数。

返り値dst をそのまま返します。

#include "x68.h"

void main(void) {
  char alive[40];

  memset(alive, 0, sizeof(alive));

  x68_screen_open();
  printf("%d", alive[10]);
  x68_screen_flip();
}

ブロックの生存フラグをまとめて 0 に戻しています。ゲームをやり直すときの初期化に便利です。

  • int の配列を 1 で埋めると、各要素は 1 ではなく 0x01010101 になります。0 埋め以外は 1 バイトの配列に使うのが安全です。

関連memcpy

strlen文字列の長さを数える

unsigned long strlen(const char *s)

文字列の文字数を返します(終端の 0 は数えません)。

s
対象の文字列。

返り値文字数。

#include "x68.h"

void main(void) {
  const char *title = "SPROUT68K";
  int col = (96 - (int)strlen(title)) / 2;

  x68_screen_open();
  x68_locate(col, 10);
  puts(title);
  x68_screen_flip();
}

文字列の長さから、画面の中央に来る桁位置を計算しています。

  • 返り値は unsigned long です。引き算に使うときは (int) を付けて int にしておくと、負の数になったときの事故を防げます。

関連printf

生の層(上級・X68000 を直接触る)

ここから下は、X68000 のハードウェアや IOCS(本体 ROM の機能)をそのまま呼ぶ関数です。上の関数と違って、範囲チェックも安全策もありません。渡す値を間違えるとプログラムが止まります。最初のうちは読み飛ばして構いません。

x68_vsync_wait画面の書き換えが一巡するまで待つ

void x68_vsync_wait(void)

垂直同期(表示期間から帰線期間へ移る瞬間)を 1 回待ちます。x68_screen_flip() が内部で呼んでいるのと同じものです。

グラフィック画面を使わず、文字だけのプログラムで速度を揃えたいときに使えます。

#include "x68.h"

void main(void) {
  int i;

  for (i = 0; i < 60; i++) {
    x68_vsync_wait();
    x68_locate(0, 0);
    printf("FRAME %d  ", i);
  }
}

1 秒あたりおよそ 55〜60 回のペースで数字を数えます。

関連x68_screen_flip

x68_iocs_printIOCS を直接呼んで文字列を出す

void x68_iocs_print(const char *msg)

本体 ROM の機能(IOCS $21)をそのまま呼びます。printf や puts はこの上に作られています。

書式の解釈も改行の追加もしません。渡した文字列をそのまま出します。

msg
0 で終わる文字列。
#include "x68.h"

void main(void) {
  x68_iocs_print("RAW OUTPUT");
}

printf を通さずに直接出しています。

関連puts

x68_iocs_locateIOCS を直接呼んで表示位置を決める

void x68_iocs_locate(int col, int row)

本体 ROM の機能(IOCS $23)をそのまま呼びます。x68_locate() はこれをそのまま呼び直しているだけなので、動きは同じです。

col
桁(0〜95)。
row
行(0〜31)。
#include "x68.h"

void main(void) {
  x68_iocs_locate(10, 5);
  x68_iocs_print("HERE");
}

位置を決めてから直接出力しています。

関連x68_locate

x68_iocs_bitsnsキーボードの押下状態を 8 キーまとめて読む

unsigned char x68_iocs_bitsns(unsigned long group)

本体 ROM の機能(IOCS $04)をそのまま呼びます。キーは 8 個ずつの組に分かれていて、組の番号(0〜15)を渡すと、その 8 個ぶんの押下状態がビットで返ります。

x68_key_down() はこれを使って 1 キーぶんだけ取り出しています。同時押しを大量に調べるときは、こちらのほうが呼び出し回数を減らせます。

group
キーの組の番号(0〜15)。スキャンコードを 8 で割った商が組の番号です。

返り値8 キーぶんの押下ビット(1 が押されている状態)。

#include "x68.h"

void main(void) {
  for (;;) {
    x68_locate(0, 0);
    printf("GROUP7=%x  ", x68_iocs_bitsns(X68_KEY_LEFT / 8));
    x68_vsync_wait();
  }
}

カーソルキーが含まれる組を丸ごと読み、16 進数で表示しています。

  • どのビットがどのキーかは、スキャンコードを 8 で割った余りで決まります。

関連x68_key_down

x68_gvram_mode_65536_1page65536 色 1 ページのグラフィック画面に切り替える

void x68_gvram_mode_65536_1page(void)

画面のモードだけを切り替えます。裏バッファの用意はしません(それをするのが x68_screen_open() です)。

自分で GVRAM($00C00000 から、1 ライン 512 ワード)を直接書きたい場合に使います。

#include "x68.h"

void main(void) {
  x68_vu16 *gvram = (x68_vu16 *)0x00C00000UL;
  int i;

  x68_gvram_mode_65536_1page();
  for (i = 0; i < 512; i++) {
    gvram[i * 512 + i] = 0xFFFF;
  }
}

モードだけ切り替えて、GVRAM に直接書き込んでいます。裏バッファも範囲チェックもありません。

  • x68_screen_open() を使う場合、この関数を自分で呼ぶ必要はありません。

関連x68_screen_open

x68_gvram_copy_movemメインメモリから GVRAM へ高速に転送する

void x68_gvram_copy_movem(void *dst, const void *src, unsigned long batch_count)

MOVEM 命令を使った、この環境でいちばん速い転送です。x68_screen_flip() が内部で使っています。

転送量の単位はバイトではなく「32 バイトのまとまりの個数」です。

dst
転送先(GVRAM など)。
src
転送元(メインメモリ)。
batch_count
32 バイト単位の個数。バイト数ではありません。
#include "x68.h"

void main(void) {
  static unsigned short pattern[512];
  int i;

  for (i = 0; i < 512; i++) pattern[i] = 0x07E0;
  x68_gvram_mode_65536_1page();
  x68_gvram_copy_movem((void *)0x00C00000UL, pattern, 1024 / 32);
}

1024 バイト(32 バイト × 32 個)を GVRAM の先頭へ転送しています。

  • バイト数を渡すと 32 倍の量を転送してしまい、画面の外まで壊します。
  • 転送先・転送元とも 4 バイト境界に揃っている必要があります。

関連x68_screen_flip

x68_iocs_disk_readフロッピーディスクから直接読み込む

long x68_iocs_disk_read(unsigned long d1, unsigned long d2, unsigned long byte_count, void *dst)

本体 ROM の機能(IOCS $46)をそのまま呼びます。プログラム本体より後ろのセクタに置いた画像や面データを、実行中に読み込むために使います。

引数はレジスタに入る値そのままの形です。d1 は装置とモード、d2 はトラック・面・セクタの位置を 1 つの数に詰めた値です。

d1
装置番号 << 8 | モード。1 台目のフロッピーからの通常読み込みは 0x00009070。
d2
セクタ長 << 24 | トラック << 16 | 面 << 8 | セクタ。セクタ長 3 が 1024 バイト。トラックと面は 0 から、セクタは 1 から数えます。
byte_count
読み込むバイト数。
dst
読み込み先のメモリ。

返り値IOCS の戻り値。成功していても 0 以外が返ることがあるので、-1(0xFFFFFFFF)のときだけ失敗と判定してください。

#include "x68.h"

void main(void) {
  static unsigned char buffer[1024];
  long result = x68_iocs_disk_read(0x00009070UL, (3UL << 24) | (30UL << 16) | (0UL << 8) | 1UL,
                                   sizeof(buffer), buffer);

  x68_screen_open();
  if (result == -1) {
    puts("DISK ERROR");
  } else {
    printf("FIRST=%x", buffer[0]);
  }
  x68_screen_flip();
}

トラック 30・面 0・セクタ 1 から 1024 バイト読み込みます。判定は「-1 のときだけ失敗」です。

  • 戻り値が 0 以外でも成功していることがあります。0 かどうかで判定すると、正常なのに失敗として扱ってしまいます。
  • プログラム本体が置かれているセクタを読み書きしないよう、置き場所には十分な余裕を取ってください。