[Labyrinthe Noir]>[Top]>[こども工作教室]>

「スロットゲーム3」のソースと解説

前回までのプログラムで十分にゲームとして遊べますが、実際に動かしてみるといくつか問題点が出て来ます。
公開して他人が使うプログラムの場合、想定外の使い方をされる場合があるため、実際に動かして問題点がないか探します。
出来るだけ多くの状況を想定して、プログラム内にその対処方法を盛り込んで行きます。

このように、動作テストをして問題点を洗い出すことを、デバッグと言います。問題点がある状態のことを虫食いがある、虫がいるという意味でバグと呼ぶのです。

何度かのテストの結果、下記の問題点が見つかりました。また、使い勝手の改善もしたいと思います。

【問題点】

「おす」ボタンを3つ押さずにリセットボタンを押したとき、その前の絵柄が履歴に出現してしまう。

リセットボタンを何度か押すと、履歴のカウンターが不正に増えてしまう。

【改良点】

「おす」ボタンを押すまで、スロットの絵柄をクルクル動くようにしたい。

履歴の絵柄を更に綺麗に揃えて表示させたい。

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>スロットゲーム</title> </head> <body> <h3>スロットゲーム</h3> <hr> <form name="slot"> <table border="2"> <tr> <th colspan="3" bgcolor="#000000"><font color="#FFFFFF"><div id="score">0点</div></font></th> </tr> <tr bgcolor="#CCCCCC"> <td><div id="dram0">☆</div></td> <td><div id="dram1">☆</div></td> <td><div id="dram2">☆</div></td> </tr> <tr> <td><input type="button" value="おす" onClick="dramstop(0)"></td> <td><input type="button" value="おす" onClick="dramstop(1)"></td> <td><input type="button" value="おす" onClick="dramstop(2)"></td> </tr> <tr> <td colspan="3"><input type="button" value="リセット" onClick="dramreset()"></td> </tr> </table> </form> <hr> <div id="rireki"></div> <script type="text/javascript"> img = new Array("<img src='buta.gif'>" ,"<img src='usi.gif'>","<img src='saru.gif'>","<img src='kuma.gif'>","<img src='tora.gif'>","<img src='panda.gif'>"); rrk = ""; rrk_num = 0; dramreset(); function dramreset() { var s = ""; for (i=0; i<3; i++) { if (rrk_num > 0) { s += img[kiroku[i]]; } document.getElementById("dram" + i).innerHTML = img[0]; document.slot.elements[i].disabled = false; } if (rrk_num > 0) { rrk = "<tr><td>" + rrk_num + "</td><th>" + s + "</th><th>" + scr + "点</th></tr>" + rrk; document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; } rrk_num++; scr = 0; kiroku = new Array(); } function dramstop(btn) { r = Math.floor(Math.random() * 6); document.getElementById("dram"+btn).innerHTML = img[r]; document.slot.elements[btn].disabled = true; kiroku[btn] = r; scr += r * 10; if ((kiroku[0] == kiroku[1]) && (kiroku[0] == kiroku[2])) {scr += r * 20;} document.getElementById("score").innerHTML = scr + "点"; } </script> </body> </html>

【この時点の slot.html を別枠で表示】

変更のない部分は省略して表示します。追記や変更は、場所の移動はです。

問題点:不正な履歴の出力(1)

3つの「おす」ボタンを押さずに「リセット」すると、表示の出目がそのまま履歴に表示されてしまいます。

ゲームの途中で履歴を出力できないように改善します。

function dramreset() {
	var s = "";
	for (i = 0; i < 3; i++) {
		if (rrk_num > 0) {
			s += img[kiroku[i]];
		}
		document.getElementById("dram" + i).innerHTML = img[0]; 
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0) {
		rrk = "<tr><td>" + rrk_num + "</td><th>" + s + "</th><th>" + scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	rrk_num++;
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
}
 
function dramstop(btn) {
	r = Math.floor(Math.random() * 6);
	document.getElementById("dram"+btn).innerHTML = img[r];
	document.slot.elements[btn].disabled = true;
	kiroku[btn] = r;
	scr += r * 10;
	if ((kiroku[0] == kiroku[1]) && (kiroku[0] == kiroku[2])) {scr += r * 20;} 
	document.getElementById("score").innerHTML = scr + "点";
	btn_num++;
}

ボタンを何個押したか数えておけば対処できそうです。
変数btn_numを用意しましょう。
1つはユーザー関数dramreset()の中で、変数の初期化を行います。ここで変数の中は0です。
もう1つはユーザー関数dramstopの中で、処理の最後に変数btn_numを1つ増やしています。

これで「おす」ボタンを1つ押す度に変数btn_numの値は1つ増えます。3つ押した段階で3となり、これが最大値です。

問題点:不正な履歴の出力(2)

次に、リセットボタンを押したときの処理を見直します。
変数btn_numが「3」のときだけ、履歴を更新します。

function dramreset() {
	var s = "";
	for (i = 0; i < 3; i++) {
		if (rrk_num > 0) {
			s += img[kiroku[i]];
		}
		document.getElementById("dram" + i).innerHTML = img[0]; 
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0 && btn_num == 3) {
		rrk = "<tr><td>" + rrk_num + "</td><th>" + s + "</th><th>" + scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	rrk_num++;
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
}

履歴の表示はすでにif文の中にありますが、ここに2つ目の条件を書き加えます。
今回は2つの条件を同時に満たすことが条件となるので、2つの条件の間に「&&」を使います。このような条件をAND条件と言います。
これで、if文の中は、変数rrk_numが0より大きいときと、変数btn_numが3のときという2つの条件が真の場合のみ実行されます。

「btn_num == 3」の部分が2つの値が等しいという条件です。
「=」が1つだと代入式になってしまいますので、条件式では「==」と2つ重ねて書く決まりになっています。
もし間違って「=」にしてしまうと、代入式と判断され、条件は常に真となってしまいます。

問題点:不正なカウントを止める

リセットボタンを押すと、その分だけ変数rrk_numが増えてしまいます。
そこで、変数rrk_numが増える条件を追加します。

function dramreset() {
	var s = "";
	for (i=0; i<3; i++) {
		if (rrk_num > 0) {
			s += img[kiroku[i]];
		}
		document.getElementById("dram" + i).innerHTML = img[0]; 
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0 && btn_num == 3) {
		rrk = "<tr><td>" + rrk_num + "</td><th>" + s + "</th><th>" + scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	if (rrk_num == 0 || btn_num == 3) {rrk_num++;}
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
} 

変数rrk_numは履歴の表示が何回目かを表すカウンターとしても機能しています。元々は、ユーザー関数dramreset()が何回実行されたか調べるためのものでした。特に最初の1回目が0ということが重要です。

そして、今回はスロットの出目が3つ揃う前にリセットしたときにも、このユーザー関数が実行されるために起きる問題です。
このような不正なリセットを禁止するという考えを、逆に考えるとこのカウントが正しく実行される条件にもなります。
ここでは条件を実行する条件として考えてみましょう。

カウントを増やして良い条件は2つあります。1つは、初期設定の直後で、変数rrk_numが0のときです。もう1つは、ボタンが3つ押された後、変数btn_numが3の場合です。

それぞれの条件は「rrk_num == 0」「btn_num == 3」となりますが、今回は2つの条件を同時に満たすのではなく、どちらかが真であればif文の中を実行さます。このように2つの条件のうち1つが真だと全体が真になることをOR条件と言います。2つの条件の間に「||」を入れて並べます。

AND条件とOR条件
複数の条件を組み合わせて判断する考え方です。
AND条件では、全ての条件が真にならないと全体は真にはなりません。逆に言うと、1つでも偽があると全体が偽となります。
OR条件では、1つでも真があると全体が真となり、全てが偽の場合のみ全体が偽となります。
また、この他にNOT条件(否定)というものもあり、真なら偽、偽なら真と条件と結果を逆にします。
さらにANDとORにNOTを組み合わることもあります。

【この時点の slot.html を別枠で表示】

改良点:絵柄をクルクル回す(1)

スロットのドラムが回転するイメージをそのままアニメーションで表現するのは大変ですが、もっと簡単な方法でそれらしく見せることにしましょう。
今回は表示する絵柄を入れ換えて変化させ、擬似的にですが、ドラムを回しているように見えるはずです。
また、これに乱数を利用することで、乱数表に変化を与えて、運の要素を強くすることができます。

乱数は実は、乱数表に基づいて順番が決められています。そのため、乱数にも規則性ができてしまいます。
そこで、乱数を使わないときにも乱数を発生させることで、乱数を使うときの順番をずらすことができます。これによって、偏りのない乱数表がタイミングによって変化し、時に偏りのある結果も生み出すことができるようになります。

dramreset();

dramstart();

function dramreset() {
	(省略)
}

function dramstart() {
	for (i=0; i<3; i++) {
		if (!document.slot.elements[i].disabled) {
			r = Math.floor(Math.random() * 6);
			document.getElementById("dram" + i).innerHTML = img[r]; 
		}
	}
}

初期設定の最後、ユーザー関数dramreset()が実行された後に、ドラムの回転を始めましょう。
新しいユーザー関数dramstart()を作ります。

ユーザー関数dramstart()では、3つのドラムを順に変化させます。
for文を使って、3つの絵柄を乱数によって入れ換えます。
どれもこれまでに作った部分からコピーして寄せ集めることができそうです。

for文の中を詳しく見てみましょう。
最初にif文があります。条件式の頭に「!」があるのはNOT条件です。「document.slot.elements[i]」は「おす」ボタンのことです。それが「disabled」の状態、すなわち使用不可の状態を示しています。このプロパティ自身がtrue(真)かfalse(偽)の値を持っています。trueならば使用不可、falseなら使用可能なのです。今回はボタンを押す前の使用可能の状態なら真にしたいので、falseを「!」で否定することで「true」にしています。
そして、if文が真のとき、乱数を発生させて、配列変数imgによって画像を表示しています。

if文の逆を考えると、ボタンを押したときは絵が入れ替わらないため、スロットが止まるということになります。
このままでは一度きりですが、後でこれを繰り返し処理することでドラムの回転を表現します。

改良点:いらない記述を削除する

機能的には全く影響はないのですが、ドラムの回転によっていらなくなった部分があります。
その部分を削除しましょう。

function dramreset() {
	var s = "";
	for (i=0; i<3; i++) {
		if (rrk_num > 0) {
			s += img[kiroku[i]];
		}
		document.getElementById("dram" + i).innerHTML = img[0]; 
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0 && btn_num == 3) {
		rrk = "<tr><td>" + rrk_num + "</td><th>" + s + "</th><th>" + scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	if (rrk_num == 0 || btn_num == 3) {rrk_num++;} 
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
}

ユーザー関数dramreset()の中で、絵柄を初期状態にする部分です。(青色で抹消)
初期化のすぐ後にドラムが回るため、初期状態は画面に残りません。これはやっても意味のない処理になってしまったのです。
この行を削除してください。

改良点:絵柄をクルクル回す(2)

それでは、タイマー機能を使って、ユーザー関数daramstart()を繰り返して実行しましょう。
これで一度だけでなく何度も画像が入れ替わります。

function dramstart() {
	for (i=0; i<3; i++) {
		if (!document.slot.elements[i].disabled) {
			r = Math.floor(Math.random() * 6);
			document.getElementById("dram" + i).innerHTML = img[r]; 
		}
	}
	setTimeout("dramstart()",200);
}

仕掛けは非常に単純です。
ユーザー関数dramstart()の最後にタイマーを置いて、再び同じユーザー関数を呼び出します。こうすることによって、永久にこのユーザー関数は実行されます。

タイマーのsetTimeout命令には2つの引数があります。1つ目が呼び出すユーザー関数名、2つ目が実行間隔です。
実行間隔は1/1000秒単位で書きますので、ここでは200/1000秒、すなわち0.2秒後にユーザー関数を呼び出すことになります。およそ1秒間に5回絵柄が変わることです。

このタイマーによる自己循環は永久に繰り返されます。
「おす」ボタンが押されと、if文によって画像の入れ替えが実行されなくなるため、ドラムの回転が止まったように見えます。しかし、ユーザー関数dramstart()が止まる訳ではありません。

【この時点の slot.html を別枠で表示】

これを動かすと、明らかに新たな問題が発生します。
絵柄によって画像のサイズが違うため、表が動いてしまうのです。
次はこの新たな問題に対処してみましょう。

問題点:絵柄の大きさが違う

スロット用の画像を入れ換えると、それぞれの画像の大きさが違うため、それに合わせてテーブルの大きさが毎回調節されて動いてしまいます。
スロットを回転させると0.2秒毎に絵が入れ替わり、テーブルもボタンも動いてしまい、とても見難く使いにくくなってしまいました。
ここではスクリプトは触らず、テーブルの大きさを調整して問題を解決してみましょう。

<form name="slot">
<table border="2">
	<tr>
		<th colspan="3" bgcolor="#000000"><font color="#FFFFFF"><div id="score">0点</div></font></th>
	</tr>
	<tr bgcolor="#CCCCCC">
		<td width="68" height="52"><div id="dram0">☆</div></td>
		<td width="68" height="52"><div id="dram1">☆</div></td>
		<td width="68" height="52"><div id="dram2">☆</div></td>
	</tr>
	<tr>
		<td><input type="button" value="おす" onClick="dramstop(0)"></td>
		<td><input type="button" value="おす" onClick="dramstop(1)"></td>
		<td><input type="button" value="おす" onClick="dramstop(2)"></td>
	</tr>
	<tr>
		<td colspan="3"><input type="button" value="リセット" onClick="dramreset()"></td>
	</tr>
</table>
</form>

スロット用に用意した画像で一番幅が大きいのはネズミで64ドットあります。
高さの方は、ウサギとウシの48ドットが一番大きなサイズになります。

画像のサイズに合わせてテーブルが動くので、これを最大のサイズに合わせて固定してしまえば、動かなくすることができます。
丁度の数字にすると、画像の周囲に余白があるのでその分も足して考えます。全体に2ドット分の余白を見て、縦と横に4ドットの幅を足して固定サイズにします。

スロットの表示に使っている<td>タブに、幅を「width」、高さを「height」で設定します。

改良点:レイアウトの調節(1)

細かなレイアウトの修正をしておきましょう。
表のボタン類を枠の中央に合わせて配置します。

<form name="slot">
<table border="2">
	<tr>
		<th colspan="3" bgcolor="#000000"><font color="#FFFFFF"><div id="score">0点</div></font></th>
	</tr>
	<tr bgcolor="#CCCCCC">
		<th width="68" height="52"><div id="dram0">☆</div></th>
		<th width="68" height="52"><div id="dram1">☆</div></th>
		<th width="68" height="52"><div id="dram2">☆</div></th>
	</tr>
	<tr>
		<th><input type="button" value="おす" onClick="dramstop(0)"></th>
		<th><input type="button" value="おす" onClick="dramstop(1)"></th>
		<th><input type="button" value="おす" onClick="dramstop(2)"></th>
	</tr>
	<tr>
		<th colspan="3"><input type="button" value="リセット" onClick="dramreset()"></th>
	</tr>
</table>
</form>

表内の<td>を全て<th>に変更します。

<th>はヘッダーという仕様で、文字を太字にして中央に寄せます。
スロットやボタンは文字ではないため、太字の効果がないので中央寄せだけが意味をなします。
文字が入っている場合、<th>タグを使うと太字になってしまうため、中央寄せだけをしたいときは<td>タグに「align="center"」という属性を加えます。

改良点:レイアウトの調節(2)

履歴に出力される絵柄が微妙に縦に揃っていないので、これも修正しましょう。

function dramreset() {
	var s = "";
	for (i=0; i<3; i++) {
		if (rrk_num > 0) {
			s += "<th>" + img[kiroku[i]] + "</th>";
		}
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0 && btn_num == 3) {
		rrk = "<tr><td>" + rrk_num + "</td><th>" + s + "</th><th>" + scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	if (rrk_num == 0 || btn_num == 3) {rrk_num++;} 
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
}

3つの絵柄を変数sにまとめてから<th>タグで囲っていたので、これを1つ1つ<th>タグに入れます。これにより、絵柄の3つの列が揃います。

1つ目のif文の中で変数sに配列変数imgを付け足す部分がありますので、その配列変数の前後を<th>タグで囲います。

2つ目if文の中、変数rrkへの代入式で変数sの前後にある<th>タグは不要になるため、こちらは削除します。

改良点:レイアウトの調節(3)

あと少し履歴の表示に手を加えましょう。
履歴のカウンターの数字と得点を右に寄せて、列の桁を揃えます。
数字のデータは右に寄せた方が桁が揃って見やすくなります。

function dramreset() {
	var s = "";
	for (i=0; i<3; i++) {
		if (rrk_num > 0) {
			s += "<th>" + img[kiroku[i]] + "</th>";
		}
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0 && btn_num == 3) {
		rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'>" + scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	if (rrk_num == 0 || btn_num == 3) {rrk_num++;} 
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
}

変数rrkのへ代入式で、変数rrk_numの前にある<td>と、変数scrの前にある<th>内に「align='right'」の属性を入れましょう。
これでそれぞれの枠内が右寄せで表示されるようになります。

【この時点の slot.html を別枠で表示】

改良点:累計得点の表示(1)

スロットを連続でやっていると、そこまでの累計点も知りたくなります。
そこで履歴に累計得点の表示を付け加えます。

<script type="text/javascript">
img = new Array("<img src='buta.gif'>" ,"<img src='usi.gif'>","<img src='saru.gif'>","<img src='kuma.gif'>","<img src='tora.gif'>","<img src='panda.gif'>");
rrk = "";
rrk_num = 0;
rrk_scr = 0;
 
 (省略)
 
function dramreset() {
	var s = "";
	for (i=0; i<3; i++) {
		if (rrk_num > 0) {
			s += "<th>" + img[kiroku[i]] + "</th>";
		}
		document.slot.elements[i].disabled = false;
	}
	if (rrk_num > 0 && btn_num == 3) {
		rrk_scr += scr;
		rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'>" + scr + "点</th><th align='right'>" + rrk_scr + "点</th></tr>" + rrk;
		document.getElementById("rireki").innerHTML = "<table>" + rrk +"</table>"; 
	}
	if (rrk_num == 0 || btn_num == 3) {rrk_num++;} 
	scr = 0;
	btn_num = 0;
	kiroku = new Array();
}

累計得点用の変数rrk_scrを初期設定に置きます。

履歴の表示の前に変数rrk_scrに対してその回の得点である変数scrを足します。

累計得点の表示はこれまでの得点表示の右側に新しい<th>タグを使って用意します。

改良点:累計得点の表示(2)

履歴が増えてきたときに、2つの得点部分を見やすくするため色分けなどして、見せ方に工夫をしてみましょう。

いくつかパターンを考えてみます。

  スロット履歴 得点 累計
1 ぶた ぶた とら 30点 30点
2 ぱんだ とら さる +110点 =140点
3 うし ぶた ぱんだ 60点 200点
4 くま ぱんだ さる 100点 (300点)
5 うし ぶた とら 80点 380点
6 ぶた さる さる 90点 (470点)

どのデザインが見やすいでしょうか。使いたいデザインを見つけたら、同じ番号のソースを下記から探して、変数rrkの代入式をその通りに修正します。
1については変化のないこれまでのデザインです。

  ソース
1 rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'>" + scr + "点</th><th align='right' >" + rrk_scr + "点</th></tr>" + rrk;
2 rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'>+" + scr + "点</th><th align='right' >=" + rrk_scr + "点</th></tr>" + rrk;
3 rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'>" + scr + "点</th><th align='right' ><font color='#FF0000'>" + rrk_scr + "点</font></th></tr>" + rrk;
4 rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'>" + scr + "点</th><th align='right' >(" + rrk_scr + "点)</th></tr>" + rrk;
5 rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'><font color='#FF0000'>" + scr + "点</font></th><th align='right' ><font color='#0000FF'>" + rrk_scr + "点</font></th></tr>" + rrk;
6 rrk = "<tr><td align='right'>" + rrk_num + "</td>" + s + "<th align='right'><font color='#FF0000'>" + scr + "点</font></th><th align='right' ><font color='#0000FF'>(" + rrk_scr + "点)</font></th></tr>" + rrk;

右の実例に対応した番号のソースを利用します。もちろん、色は好きな色に変更してかまいません。
下記は、6番のソースを使っています。

履歴のリセットはありませんので、ブラウザのリロードでページを読み込みし直すと、初期状態に戻ります。

【この時点の slot.html を別枠で表示】

やっと完成

スロットゲームの製作はこれで終了です。

デザイン的な変更はまだまだ色々考えられるでしょう。テーブルの色や大きさ、枠の幅を変えても雰囲気が変わります。
スロットの絵柄をオリジナルなものに変えて楽しんでみてください。

完成させたスロットゲームはHPなどで公開して、みんなに遊んでもらっても良いでしょう。
その場合は、「完成品を公開したい場合の注意点」を読んでください。

戻る