リアルタイム・オセロ - Flash(ActionScript)で様々なゲームを作ろう
目次
- Flash(ActionScript)で様々なゲームを作ろう
- イライラ棒もどき
- 忍者アクション
- ブロック崩し
- リアルタイム・オセロ
- パットゴルフ
実行画面
オセロですが、プログラムを簡略化するため先手・後手の順番が無く、リアルタイムでゲームが進行します。EASY と HARD は、コンピュータの思考時間の違いです。HARD はかなり卑怯な感じです。.fla ファイルダウンロード
今回のポイント
- コンピュータの思考
画面作成
まずステージ・ステージに乗せるシンボルなどを作ってゆきます。ステージのプロパティ
最初にステージのプロパティを適当に設定します。この解説で使っているムービーでは、サイズを「320×320」、背景色を「#00CF63」、フレームレートを「30fps」としています。ゲーム開始画面の作成
1フレーム目にゲーム開始画面を適当に作成して、「EASY」「HARD」と書いたボタンシンボルを乗せてください。ゲーム画面の作成
2フレーム目のメインのゲーム画面を作成します。まず2フレーム目にキーフレームを作成し中身を全て削除します。次に下図のような格子状のオセロの盤面を描いてください。メニューの「表示」→「グリッド」→「グリッドの編集」で表示されるウインドウで「グリッドの吸着」にチェックを入れて、グリッドのサイズを 40px × 40px にすると描きやすいでしょう。ゲーム終了画面の作成
次に3フレーム目にゲーム終了画面を作成します。まず3フレーム目にキーフレームを作成し中身を全て削除し、下図のようにダイナミックテキストが入るエリアを2つ作ってください。上の部分には「PLAYER WIN!」「PLAYER LOSE!」などの勝ち負けメッセージ、下の部分には「YOU:32 COM:32」のように終了時のコマ数が表示されます。上の部分の変数名を「msg1」下の部分の変数名を「msg2」としてください。その後、リプレイ用のボタンシンボルを作り乗せてください。コマの作成
次にコマをムービークリップとして作ります。メニューの「挿入」→「新規シンボル」でムービークリップを選んでください。ムービークリップの編集画面で、下図のように3フレーム作成し、1フレーム目は何も無いフレーム、2フレーム目には黒、3フレーム目には白のコマを描いてください(下図は200%に拡大してあります)。完成したら、ライブラリウインドウのコマのシンボルを右クリックし「リンケージ」を選び、「リンケージプロパティ」のウインドウで「ActionScriptに書き出し」にチェックを入れ、「識別子」の欄に「koma」と書きます。
ActionScript の記述
続いて、フレームやムービークリップに ActionScript を記述して行きます。1フレーム目のアクション
1フレーム目をクリックして、以下のフレームアクションを記述してください。
stop();
ゲーム開始ボタンのアクション
1フレーム目の「EASY」ボタンをクリックして、以下のボタンアクションを記述してください。
on(press) {
ctime = 60;
gotoAndStop(2);
}
次に1フレーム目の「HARD」ボタンをクリックして、以下のボタンアクションを記述してください。
on(press) {
ctime = 15;
gotoAndStop(2);
}
2フレーム目のアクション
2フレーム目をクリックして、以下のフレームアクションを記述してください。
// 初期設定
var masu_w = Stage.width / 8; // マスの幅
var masu_h = Stage.height / 8; // マスの高さ
var ban = new Array(); // 盤面のコマ配列
var msflag = false; // マウスが押されたら true
var ms_x, ms_y; // マウスが押されたときの座標
var ccount = 0; // コンピュータ思考時間カウント用
// 初期盤面表示
var kno = 0, fno;
for (var y = 0; y < 8; y++) {
ban[y] = new Array();
for (var x = 0; x < 8; x++) {
if (x == 3 && y == 3 || x == 4 && y == 4) fno = 2;
else if (x == 3 && y == 4 || x == 4 && y == 3) fno = 3;
else fno = 1;
ban[y][x] = attachMovie("koma", "koma" + kno, kno);
ban[y][x]._x = masu_w * x + masu_w / 2;
ban[y][x]._y = masu_h * y + masu_h / 2;
ban[y][x].gotoAndStop(fno);
kno++;
}
}
// 毎フレーム処理
onEnterFrame = function() {
// プレイヤー
if (msflag) {
putKoma(ms_x, ms_y, 2, true);
msflag = false;
}
// コンピュータ
if (++ccount > ctime) {
cthink();
ccount = 0;
}
// ゲームオーバー判定
var pc = 0, cc = 0;
for (var y = 0; y < 8; y++) {
for (var x = 0; x < 8; x++) {
if (ban[y][x]._currentframe == 2) pc++;
else if (ban[y][x]._currentframe == 3) cc++;
}
}
if (cc == 0 || pc == 0 || pc + cc == 64) {
if (pc > cc) msg1 = "YOU WIN!";
else if (cc > pc) msg1 = "YOU LOSE!";
else msg1 = "DRAW!";
msg2 = "YOU:" + pc + " COM:" + cc;
for (var y = 0; y < 8; y++) {
for (var x = 0; x < 8; x++) {
ban[y][x].removeMovieClip();
}
}
gotoAndStop(3);
}
}
// マウス押下時
onMouseDown = function() {
msflag = true;
ms_x = Math.floor(_xmouse / masu_w);
ms_y = Math.floor(_ymouse / masu_h);
}
// 指定した位置にコマを置く
// x, y: 座標(0~7) c:色(2:黒, 3:白)
// pf:調べるだけの場合は false, 実際に置く場合は true
// 戻り値:裏返った数
function putKoma(x, y, c, pf) {
var rx = new Array(8);
var ry = new Array(8);
var rnum = 0;
if (ban[y][x]._currentframe != 1) return 0;
for (var ay = -1; ay <= 1; ay++) {
for (var ax = -1; ax <= 1; ax++) {
var kx = x + ax; var ky = y + ay; var rn = 0;
for (;;) {
var fno = ban[ky][kx]._currentframe;
if (kx < 0 || kx > 7 || ky < 0 || ky > 7) break;
else if (fno == 1) break;
else if (fno == c) {
if (pf) for (i = 0; i < rn; i++) ban[ry[i]][rx[i]].gotoAndStop(c);
rnum += rn;
break;
} else {
rx[rn] = kx; ry[rn] = ky; rn++;
kx += ax; ky += ay;
}
}
}
}
if (rnum && pf) ban[y][x].gotoAndStop(c);
return rnum;
}
// コンピュータの思考
// 戻値:コマが置けたら true, パスなら false
function cthink() {
// 優先順位
var pr = [
[0, 6, 2, 1, 1, 2, 6, 0],
[6, 6, 5, 4, 4, 5, 6, 6],
[2, 5, 2, 3, 3, 2, 5, 2],
[1, 4, 3, 3, 3, 3, 4, 1],
[1, 4, 3, 3, 3, 3, 4, 1],
[2, 5, 2, 3, 3, 2, 5, 2],
[6, 6, 5, 4, 4, 5, 6, 6],
[0, 6, 2, 1, 1, 2, 6, 0]
];
var x, y, max = 0;
var rn = new Array();
for (y = 0; y < 8; y++) rn[y] = new Array();
for (var p = 0; p <= 6; p++) {
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
if (pr[y][x] == p) {
rn[y][x] = putKoma(x, y, 3, false);
if (rn[y][x] > max) max = rn[y][x];
}
}
}
if (max > 0) {
do {
x = Math.floor(Math.random() * 8);
y = Math.floor(Math.random() * 8);
} while (rn[y][x] < max);
putKoma(x, y, 3, true);
return true;
}
}
return false;
}
3フレーム目のアクション
3フレーム目をクリックして、以下のフレームアクションを記述してください。
delete onEnterFrame;
リプレイボタンのアクション
3フレーム目のリプレイボタンをクリックして、以下のボタンアクションを記述してください。
on(press) {
gotoAndStop(1);
}
実行
以上が完成したら保存してゲームを実行してみてください。ActionScript 解説
1フレーム目のボタンアクション解説
「EASY」「HARD」それぞれのボタンを押したときの処理です。
on(press) {
ctime = 60;
gotoAndStop(2);
}
「EASY」ボタンの処理です。ctime はコンピュータが一手打つのに要するフレーム数です。30fps ならば上記の場合、2秒となります。
on(press) {
ctime = 15;
gotoAndStop(2);
}
「HARD」ボタンの処理です。コンピュータが一手打つのに要するフレーム数は 15 となり、30fps ならば 0.5 秒となります。2フレーム目のフレームアクション(初期設定)解説
ここのフレームアクションには、ゲームのメイン処理が記述してあります。まず初期設定部分から解説して行きます。
var masu_w = Stage.width / 8;
var masu_h = Stage.height / 8;
masu_w, masu_h には盤の1マスの幅、高さが入ります。
var ban = new Array();
コマのムービークリップが格納される変数です。8×8の二次元配列となっています。
var msflag = false;
var ms_x, ms_y;
マウスの状態を管理します。マウスが押されたら msflag が true となり、その時に盤面のどのマスを指しているかが (ms_x, ms_y) に入ります。
var ccount = 0;
コンピュータが一手打つ時間をカウントするための変数です。2フレーム目のフレームアクション(初期盤面表示)解説
ゲーム開始状態の盤面を作っている部分です。
var kno = 0, fno;
kno には attachMovie 用のコマ番号、fno にはコマのムービークリップの表示するフレームが入ります。
for (var y = 0; y < 8; y++) {
ban[y] = new Array();
for (var x = 0; x < 8; x++) {
盤面のマスの数(64)だけループしています。ついでに途中で二次元配列を生成しています。
if (x == 3 && y == 3 || x == 4 && y == 4) fno = 2;
else if (x == 3 && y == 4 || x == 4 && y == 3) fno = 3;
else fno = 1;
ゲーム開始時に黒コマを表示する位置ならばフレーム番号を2、白コマを表示する位置ならばフレーム番号を3、それ以外ならばフレーム番号を1にしています。コマのムービークリップの対応するフレームに予め描画してあるので適切な画像が描画されます。
ban[y][x] = attachMovie("koma", "koma" + kno, kno);
二次元配列 ban の各要素にムービークリップのインスタンスを生成して割り当てています。
ban[y][x]._x = masu_w * x + masu_w / 2;
ban[y][x]._y = masu_h * y + masu_h / 2;
各ムービークリップインスタンスの座標を、それぞれのマスの中央にくるように設定しています。
ban[y][x].gotoAndStop(fno);
各コマのインスタンスのフレーム番号を先ほど指定してフレーム番号にセットしています。2フレーム目のフレームアクション(毎フレーム処理)解説
onEnterFrame = function() {
_root ムービークリップの毎フレーム処理をここで定義しています。このソースを記述してある部分は _root ムービークリップのフレームアクションなので _root は省略できます。省略しない場合は _root.onEnterFrame と書きます。
if (msflag) {
putKoma(ms_x, ms_y, 2, true);
msflag = false;
}
msflag が true、つまりマウスが押されたならば putKoma 関数(後述)を呼び出してマウスの位置にプレイヤーのコマを置きます。
if (++ccount > ctime) {
cthink();
ccount = 0;
}
コンピュータが一手打つ時間用のカウンタをインクリメントし、「EASY」「HARD」ボタンによって決めた時間を過ぎたら、cthink 関数(後述)を呼び出してコンピュータの思考を行います。
var pc = 0, cc = 0;
ここより下はゲームオーバーの判定部分です。pc は盤上に出ているプレイヤーのコマ、cc はコンピュータのコマを数える変数です。
for (var y = 0; y < 8; y++) {
for (var x = 0; x < 8; x++) {
if (ban[y][x]._currentframe == 2) pc++;
else if (ban[y][x]._currentframe == 3) cc++;
}
}
盤面のマス数だけループして、プレイヤーのコマとコンピュータのコマをカウントしています。
if (cc == 0 || pc == 0 || pc + cc == 64) {
コンピュータかプレイヤーのコマ数が0、または全てのマスが埋まったときはゲーム終了なので以下の処理を行います。プレイヤー・コンピュータ共にコマが置けない場合もゲーム終了ですが、今回は面倒なのでその処理は行っていません。
if (pc > cc) msg1 = "YOU WIN!";
else if (cc > pc) msg1 = "YOU LOSE!";
else msg1 = "DRAW!";
プレイヤーとコンピュータのコマ数に応じて適切なメッセージを3フレーム目のダイナミックテキストに設定しています。
msg2 = "YOU:" + pc + " COM:" + cc;
プレイヤーとコンピュータのコマ数を3フレーム目のダイナミックテキストに設定しています。
for (var y = 0; y < 8; y++) {
for (var x = 0; x < 8; x++) {
ban[y][x].removeMovieClip();
}
}
コマ用のムービークリップを全て削除しています。
gotoAndStop(3);
最後に、3フレーム目のゲーム終了画面にジャンプしています。2フレーム目のフレームアクション(マウス押下時の処理)解説
onMouseDown = function() {
_root ムービークリップでマウスが押されたときの処理をここで定義しています。ここでも _root は省略してあります。
msflag = true;
マウス押下用のフラグをセットしています。
ms_x = Math.floor(_xmouse / masu_w);
ms_y = Math.floor(_ymouse / masu_h);
マウスがどこのマスを指しているかを計算して (ms_x, ms_y) にセットしています。それぞれ 0 ~ 7 の値を取ります。2フレーム目のフレームアクション(putKoma 関数)解説
引数で指定した位置にコマを置く関数です。
function putKoma(x, y, c, pf) {
(x, y) はコマを置くマスの座標(0 ~ 7)、c はコマの種類で 2 ならば黒、3 ならば白です。pf は実際にコマを置く場合は true、コマが置けるか調べるだけの場合は false となるフラグです。関数の戻り値として裏返ったコマの数が返ります。
var rx = new Array(8);
var ry = new Array(8);
rx と ry は一列をチェックするときに裏返すマスの座標を保存しておく配列です。
var rnum = 0;
裏返った数の合計が入ります。
if (ban[y][x]._currentframe != 1) return 0;
まず置こうとする場所が空白以外ならば置けないので、裏返った数 0 で関数を終了します。
for (var ay = -1; ay <= 1; ay++) {
for (var ax = -1; ax <= 1; ax++) {
左上、上、右上、左、中央、右、左下、下、右下の方向の順で駒が裏返すことができるかチェックして行きます。
var kx = x + ax; var ky = y + ay; var rn = 0;
(kx, ky) は現在裏返すことができるかどうかチェック中のマスです。rn には一列分の裏返す数が入ります。
var fno = ban[ky][kx]._currentframe;
現在チェック中のコマの種類を fno に代入しています。
if (kx < 0 || kx > 7 || ky < 0 || ky > 7) break;
盤面の外に出たら裏返すことができないので、ループを抜けます。
else if (fno == 1) break;
チェック方向に空白マスがあったら裏返すことができないので、ループを抜けます。
else if (fno == c) {
チェック方向に自駒があった場合、裏返し処理を行ないます。
if (pf) for (i = 0; i < rn; i++) ban[ry[i]][rx[i]].gotoAndStop(c);
裏返しフラグがセットされている場合のみ、rx と ry に保存されている座標をもとに駒を裏返して行きます。
rnum += rn;
裏返った数の合計に今裏返した数を加算します。
} else {
rx[rn] = kx; ry[rn] = ky; rn++;
kx += ax; ky += ay;
}
チェック方向に敵駒があった場合、rx, ry に駒の座標を保存しておき、チェック方向をひとつ進めます。
if (rnum && pf) ban[y][x].gotoAndStop(c);
一枚以上裏返されていて、なおかつ裏返しフラグがセットされている場合、自駒を置きます。
return rnum;
裏返された合計の数を戻り値として返します。2フレーム目のフレームアクション(cthink 関数)解説
コンピュータの思考用の関数です。今回はそれぞれのマスに優先順位を付け、優先順位の高いマスの中で最も多く取れるマスに駒を置く思考ルーチンにしています。優先順位は二次元配列 pr で表します。
var pr = [
[0, 6, 2, 1, 1, 2, 6, 0],
[6, 6, 5, 4, 4, 5, 6, 6],
[2, 5, 2, 3, 3, 2, 5, 2],
[1, 4, 3, 3, 3, 3, 4, 1],
[1, 4, 3, 3, 3, 3, 4, 1],
[2, 5, 2, 3, 3, 2, 5, 2],
[6, 6, 5, 4, 4, 5, 6, 6],
[0, 6, 2, 1, 1, 2, 6, 0]
];
数値の低いマスほど優先順位が高くなっています。
var x, y, max = 0;
max にはひとつのマスが裏返すことができる最大の数が入ります。
var rn = new Array();
for (y = 0; y < 8; y++) rn[y] = new Array();
rn は各マスに置いたときに、裏返る数を保存しておく配列です。
for (var p = 0; p <= 6; p++) {
優先順位が高いものから順にチェックして行きます。現在の優先順位は p に入ります。
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
if (pr[y][x] == p) {
rn[y][x] = putKoma(x, y, 3, false);
if (rn[y][x] > max) max = rn[y][x];
}
}
}
putKoma 関数を使って、裏返すことができる数をすべてのマスにおいてチェックし、配列 rn に格納します。そしてその中の最大の数を max に格納します。
if (max > 0) {
max が 0 より大きいときは置ける場所があるということなので、以下の処理を行います。
do {
x = Math.floor(Math.random() * 8);
y = Math.floor(Math.random() * 8);
} while (rn[y][x] < max);
putKoma(x, y, 3, true);
最大の数を裏返すことのできるマスからひとつを乱数によって選び、その位置に駒を置きます。3フレーム目のフレームアクション解説
delete onEnterFrame;
_root ムービークリップの毎フレーム処理(イベントハンドラメソッド)を削除しています。これを行わないと、3フレーム目表示中もゲーム処理が行われ、表示がおかしくなる場合があります。onMouseDown ハンドラも削除すべきですが、こちらは害はないのでそのままにしてあります。3フレーム目のボタンアクション解説
リプレイボタンを押したときの処理です。
on(press) {
gotoAndStop(1);
}
押されたらゲーム開始画面に戻ります。