ポンクソフト

オセロの作成 - C言語とelで様々なゲームを作ろう

前ページ C言語とelで様々なゲームを作ろう TOP 次ページ

目次

  1. C言語とelで様々なゲームを作ろう
  2. Visual C++ .NET での設定
  3. テンプレートファイルの解説
  4. シューティングゲームの作成(チュートリアル)
  5. パックマン的ゲームの作成(チュートリアル)
  6. ブロック崩しの作成
  7. 15パズルの作成
  8. 横スクロールジャンピングゲームの作成
  9. オセロの作成
  10. 神経衰弱の作成
  11. 7ならべの作成
  12. テトリスの作成
  13. ぷよぷよの作成

ソース・プロジェクトファイル

ソースファイル(思考1)
ソースファイル(思考2)
プロジェクトファイル(Visual C++ 6.0)

ゲームについて

白うさぎがプレイヤーの駒、黒うさぎがコンピュータの駒です。
othello_ban.gif

コンピュータの思考について

othello1.cpp の方は、最も多く置けるマスを探し、その中でひとつランダムに置きます。
othello2.cpp の方は、それぞれのマスに優先順位を付け、優先順位の高いマスの中で最も多く取れるマスに駒を置きます。

プログラムの解説

グローバル変数

DDOBJ bmpKoma, bmpBan;
駒と盤のスプライトオブジェクトです。大きさは
int ban[8][8];
盤面の状態データです。ban[y座標][x座標] で表わし、それぞれ 0~7 の 8 マス分あります。中身の値は -1 のとき空のマス、0 のときプレイヤーの駒、1 のときコンピュータの駒が置かれている状態です。

PutKoma 指定した位置に駒を置く関数

int PutKoma(int x, int y, int c, int pf = true)
(x, y) は駒を置く場所、c は駒の種類で 0 がプレイヤー、1 がコンピュータの駒です。pf は実際に駒を置く場合は true、チェックのみの場合は false を設定するフラグです(省略時は true)。
int rx[8], ry[8], rn;
rx と ry は一列をチェックするときに裏返すマスの座標を保存しておく配列、rn は一列分の裏返す数が入る変数です。
int ret = 0;
戻り値です。裏返った数の合計が入ります。
if (ban[y][x] != -1) return 0;
まず置こうとする場所が空白以外ならば置けないので、裏返った数 0 で関数を終了します。
for (int ay = -1; ay <= 1; ay++) {
  for (int ax = -1; ax <= 1; ax++) {
左上、上、右上、左、中央、右、左下、下、右下の方向の順で駒が裏返すことができるかチェックして行きます。
int kx = x + ax; int ky = y + ay; rn = 0;
(kx, ky) は現在裏返すことができるかどうかチェック中のマスです。
if (kx < 0 || kx > 7 || ky < 0 || ky > 7) break;
盤面の外に出たら裏返すことができないので、ループを抜けます。
else if (ban[ky][kx] == -1) break;
チェック方向に空白マスがあったら裏返すことができないので、ループを抜けます。
else if (ban[ky][kx] == c) {
チェック方向に自駒があった場合、裏返し処理を行ないます。
if (rn > 0) {
まず、敵駒を一枚以上挟んでいるかチェックします。
if (pf) for (int i = 0; i < rn; i++) ban[ry[i]][rx[i]] = c;
敵駒を挟んでいたら、裏返しフラグがセットされている場合のみ、rx と ry に保存されている座標をもとに駒を裏返して行きます。
ret += rn;
戻り値に裏返した数を加算します。
} else {
  rx[rn] = kx; ry[rn] = ky; rn++;
  kx += ax; ky += ay;
}
チェック方向に敵駒があった場合、rx, ry に駒の座標を保存しておき、チェック方向をひとつ進めます。
if (ret && pf) ban[y][x] = c;
一枚以上裏返されていて、なおかつ裏返しフラグがセットされている場合、自駒を置きます。
return ret;
裏返された合計の数を戻り値として返します。

Think コンピュータ思考関数 (othello1.cpp)

最も多く置けるマスを探し、その中でひとつランダムに置く思考です。
bool Think() {
この関数 Think がコンピュータの思考です。駒を置いたなら true、パスならば false が返る関数です。
int rn[64] = {0};
rn はそれぞれのマスが裏返すことができる数を保存しておく配列です。マスの座標を (x, y) とすると、rn[y * 8 + x] に裏返す数が入ります。最初は全ての要素を 0 で初期化しておきます。
int i, max = 0;
変数 max にはひとつのマスが裏返すことができる最大の数が入ります。
for (i = 0; i < 64; i++) {
  rn[i] = PutKoma(i % 8, i / 8, 1, false);
  if (rn[i] > max) max = rn[i];
}
PutKoma 関数を使って、裏返すことができる数をすべてのマスにおいてチェックし、配列 rn に格納します。そしてその中の最大の数を max に格納します。
if (max == 0) return false;
max が 0、つまりひとつも裏返すことのできるマスがない場合 false を返します。
do {i = rand() % 64;} while (rn[i] < max);
PutKoma(i % 8, i / 8, 1);
最大の数を裏返すことのできるマスからひとつを乱数によって選び、その位置に駒を置きます。

Think コンピュータ思考関数 (othello2.cpp)

それぞれのマスに優先順位を付け、優先順位の高いマスの中で最も多く取れるマスに駒を置く思考です。
優先順位は以下の通り。
othello_priority.gif
数値の低いマスほど優先順位が高くなっています。
int pr[64] = {
  ・・・
};
各マスの優先順位が入る配列です。
int rn[64] = {0};
int i, max = 0;
rn はそれぞれの裏返すことができる数を保存しておく配列、max はひとつのマスが裏返すことができる最大の数が入ります。
for (int p = 0; p <= 6; p++) {
優先順位が高いものから順にチェックして行きます。現在の優先順位は p に入ります。
for (i = 0; i < 64; i++) {
  if (pr[i] == p) {
    rn[i] = PutKoma(i % 8, i / 8, 1, false);
    if (rn[i] > max) max = rn[i];
  }
}
if (max > 0) {
  do {i = rand() % 64;} while (rn[i] < max);
  PutKoma(i % 8, i / 8, 1);
  return true;
}
基本的に othello1.cpp と同じ処理ですが、現在の優先順位のものだけをチェックします。

ウインドウ生成関数

bmpKoma = elDraw::LoadObject("koma.bmp");
bmpBan = elDraw::LoadObject("ban.bmp");
駒と盤の画像をスプライトに読み込んでいます。
for (int y = 0; y < 8; y++) {
  for (int x = 0; x < 8; x++) {
    if (x == 3 && y == 3 || x == 4 && y == 4) ban[y][x] = 0;
    else if (x == 3 && y == 4 || x == 4 && y == 3) ban[y][x] = 1;
    else ban[y][x] = -1;
  }
}
盤面の状態を表わす配列 ban を初期化しています。中央にプレイヤー駒とコンピュータ駒を2つずつ置き、それ以外は空白マスにしています。

メイン画面 変数

static int pl = 0;
現在プレイ中のプレイヤーを表わす変数です。0 ならプレイヤー、1 ならコンピュータです。
static int passc = 0;
連続でパスした数を数えるカウンタです。プレイヤー・コンピュータ共に置ける場所がない場合のチェックに使用します。
static float pw = 0;
パスをしたときのメッセージを出している時間です。単位は秒。

メイン画面 パスメッセージ待ち

if (pw > 0) {
  pw -= FrameTime;
  if (pw <= 0) pl = 1 - pl;
パスの待ち時間がある場合、待ち時間を減らして、その結果待ち時間が無くなったら、プレイヤーを交代します。FrameTime は el が用意している変数で、1フレームの秒数、つまり MainScreen 関数が呼ばれてから次に MainScreen 関数が呼ばれるまでの時間を持っています。

メイン画面 プレイヤーの番

} else if (pl == 0) {
プレイヤーの番かどうかをチェックしています。
for (int i = 0; i < 64; i++) {
  if (PutKoma(i % 8, i / 8, 0, false)) break;
}
まず全てのマスについて PutKoma 関数を呼び出して置けるかどうかチェックしています。
if (i >= 64) {
  pw = 1.0;
  passc++;
ループを最後まで抜けなかった、つまりひとつも置ける場所がない場合は、パス待ち時間を1秒に設定してパスカウンタをインクリメントします。
} else if (MouseCL) {
  if (PutKoma(MousePX / 48, MousePY / 48, 0)) {
    pl = 1;
    passc = 0;
  }
}
それ以外の場合、マウス左ボタンが押されたかチェックし、押されていたらマウスの座標 (MousePX, MousePY) から押されたマスの座標を算出し、その場所に駒を置くことを試みます。コマが置けたならプレイヤーを交代し、パスカウンタをクリアします。

メイン画面 コンピュータの番

if (Think()) {
  pl = 0;
  passc = 0;
} else {
  pw = 1.0;
  passc++;
}
まず思考ルーチンを呼び出し、駒が置けるならばプレイヤーを交代し、パスカウンタをクリアします。コマが置けなかったらパス待ち時間を1秒に設定してパスカウンタをインクリメントします。

メイン画面 描画

elDraw::Layer(0, 0, bmpBan, 0, 0, 384, 384);
まず背景となる盤面を描画します。
for (y = 0; y < 8; y++) {
  for (x = 0; x < 8; x++) {
    int b = ban[y][x];
    if (b != -1) elDraw::Layer(x * 48, y * 48, bmpKoma, b * 48, 0, b * 48 + 48, 48);
  }
}
盤面の全てのマスについて、プレイヤー駒またはコンピュータ駒を描画するか、何も描画しないかの処理を行ないます。bmpKoma は左にプレイヤー駒、右にコンピュータ駒が並んでいる画像で、盤面の状態データによって座標をずらして描画することにより、プレイヤーまたはコンピュータのどちらかの駒を描画しています。
if (pw > 0) {
  if (pl == 0) SHOW(150, 186, " PLAYER PASS! ");
  else SHOW(145, 186, " COMPUTER PASS! ");
}
パス待ち時間がある場合は、パスのメッセージを出力します。
ShowCursor(TRUE);
マウスカーソルを描画します。

メイン画面 ゲーム終了チェック

int pc = 0, cc = 0;
for (y = 0; y < 8; y++) {
  for (x = 0; x < 8; x++) {
    if (ban[y][x] == 0) pc++;
    else if (ban[y][x] == 1) cc++;
  }
}
ゲーム終了条件チェックのために、盤面データよりプレイヤーの駒とコンピュータの駒の数を数え、それぞれ pc と cc に格納します。
char* msg = NULL;
msg には終了時に表示するメッセージが入ります。
if (cc == 0 || pc == 0 || pc + cc == 64 || passc >= 2) {
終了条件は4つあります。
if (pc > cc) msg = "あなたの勝ち";
プレイヤーの駒の方が多ければ「あなたの勝ち」というメッセージを設定します。
else if (cc > pc) msg = "あなたの負け";
コンピュータの駒の方が多ければ「あなたの負け」というメッセージを設定します。
else  msg = "引き分け";
プレイヤーとコンピュータの駒の数が同数ならば「引き分け」というメッセージを設定します。
if (msg) {
  MESG("%d対%dで、%sです。", pc, cc, msg);
  elDraw::Exit();
}
メッセージが設定されているときは、プレイヤー・コンピュータの駒の数とメッセージを出力して終了します。
前ページ C言語とelで様々なゲームを作ろう TOP 次ページ
このエントリーをはてなブックマークに追加 そっか0