JavaScriptで作るミニゲーム「HIT & BLOW」

「Hit & Blow」をプレイしてみる。

まず今回の動画で制作した「Hit & Blow」をプレイしてみようという方は、コチラからどうぞ。

スマホでのプレイに対応するためのコードを追加していますが、基本的な流れは動画と同じですm(_ _)m

JavaScriptで簡単なゲームを。

JavaScriptの入門講座も第8回になりました。
今回は「HIT & BLOW」という数あてゲームをプログラミングしています。

正直、ちょちょいと簡単に終わると思っていたんだけれど、ちょっと数字入力の仕組みなんかにこだわった結果、尺も長いしその上どうにも・・・なんだかスッキリとまとまってないなぁ・・・という感じになってしまいました。

そんなこともあって、このブログ記事で少しフォローの解説を入れることにいたしました。

その動画本編はコチラ

ポイントごとにコードとその解説を書いていきます。また、全コードは下の方に掲載しておりますm(_ _)m

HTMLとCSSで大体の体裁を整える

<div style=”font-size:40px; color:blue;”>
  Hit & Blow !
  <button id=”startBtn” onclick=”newGame()”>
    START
  </button>

</div>

40ピクセルの青地で「Hit & Blow !」を表示。タイトルの横には「START」ボタンを設置しています。ボタンにはonClick属性でnewGame()関数を紐づけています。

<div id=”choiceNumber”></div>

「choiceNumber」というidで作ったdiv要素。
ここに、ユーザーが選択した数字を表示します。

<div>決定:ENTER / クリア:DEL</div>

idなしのdivタグ。
ゲームの操作方法を表示しておきます。

<div id=”field”></div>

「field」というidで作ったdiv要素。
ここには、ヒットとブローの判定結果を表示します。

これが<body></body>タグ内に書いたHTMLの部品の全体像です。

一部の装飾は、<head></head>内に<style></style>要素を作って記述しています。
それが下記。

<style>
#choiceNumber{
  font-size:60px;
  width:300px;
  height:80px;
  background-color:lavender;
  text-align:center;
  border:solid 3px blueviolet;
}
#field{
  background-color:burlywood;
  font-size:20px;
}
</style>

関数newGame()

STARTボタンがクリックされたときに呼び出される関数です。

function newGame(){
  ansNum = [“*”,”*”,”*”,”*”];
  turn = 1;
  disNum = document.getElementById(“choiceNumber”);
  disNum.innerText = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3];
  document.getElementById(“startBtn”).style.visibility = “hidden”;
  document.addEventListener(“keydown”, keyPush);
  selectNumber();
}

まずは disNum = document.getElementById(“choiceNumber”);でエレメントをオブジェクトとして取得して、
disNum.innerText = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3]; でinnerTextを設定して表示します。

document.getElementById(“startBtn”).style.visibility = “hidden”;
ここで、スタートボタンを「hidden」つまり非表示にしています。

document.addEventListener(“keydown”, keyPush);
ここで、キーを押したときに起動するイベントリスナーを設定。

関数selectNumber()

出題する4つの数字をランダムに生成する関数です。

function selectNumber(){
  do{
    let numList = [“0″,”1″,”2″,”3″,”4″,”5″,”6″,”7″,”8″,”9”];
    for(let i=0; i<4; i++){
      let ran = Math.floor(Math.random()*numList.length);
      queNum[i] = numList[ran];
      numList.splice(ran,1);
    }
  }
  while(queNum[0]==0);
  console.log(queNum);
}

今回は、0~9までの数字を格納した配列からランダムに選んでいきます。一度選んだ数字はqueNumに代入後、もとの配列から削除するので、重複しません。do~while文で先頭の数字が0だった場合は再度選びなおします。

最後のconsole.logは、確認用です。

関数keyPush()

addEventListener()で設定した関数です。
キーボードのキーが押されたときに呼び出されます。

function keyPush(e){
  if (e.key==”Enter”){judge();}
  if (e.key==”Delete” || e.key==” “){
    ansNum = [“*”,”*”,”*”,”*”];
    disNum.innerText = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3];
    return;
  }
  if (Number.isInteger(Number(e.key))==false){return;}
  if (ansNum.indexOf(“*”)!=-1){
    ansNum[ansNum.indexOf(“*”)] = e.key;
    disNum.innerText = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3];
  }
}

keyPush(e)
この「e」は、発生したイベント自体を表すオブジェクトです。
.keyプロパティはオブジェクトが持っている値を表しているので、e.keyでどのキーが押されたのか判断できます。

Number.isInteger(Number(e.key))はちょっと複雑に見えるかもしれません。
Number(e.key)は、もしe.keyが「1」「2」などの数字の文字列だったなら、その値を数値に変換します。
そしてNumber.isInteger(Number(e.key))は、引数のNumber(e.key)が数値なら「true」を、数値でないなら「false」を返します。

この処理で、e.keyが数値(正確には数字の文字列)の場合に限り、次の処理に移ります。

.indexOf(“*”)は、「*」がどの位置にあるかを返して、ansNum[ansNum.indexOf(“*”)] = e.key;で、その「*」を数字(の文字列)に置き換えます。

関数judge()

これが最後の関数。
ヒットとブローの判定をします。

function judge(){
  if (ansNum[3]==”*”){return;}
  hit = 0;
  blow = 0;
  for (let i=0; i<4; i++){
    if (ansNum[i]==queNum[i]){hit += 1;}

ここでヒットの判定。

  else if (queNum.indexOf(ansNum[i])!=-1){blow += 1;}

そしてコッチでブローの判定をしています。

ここからは、判定後の表示を。

let str = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3];
let result = document.createElement(“div”);
result.innerText = turn + “回目のチャレンジ[ ” + str + ” ] ” + hit + ” HIT / ” + blow + ” BLOW”;
field.appendChild(result);

ここからが、ゲームクリアの表示ですね。

if (hit==4){
  window.alert(“おめでとうございます。\n” + turn + “回目で正解しました。”);
  document.getElementById(“startBtn”).style.visibility = “visible”;
  field.innerText = “”;
  ansNum = [“”, “”, “”, “”];
  disNum.innerText = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3];
  document.removeEventListener(“keydown”, keyPush);
}

まだクリアでないときは、ここで回数を+1、数字選択をリセットして再びキーイベントを待ちます。

else{
  turn += 1;
  ansNum = [“*”,”*”,”*”,”*”];
  disNum.innerText = ansNum[0]+ansNum[1]+ansNum[2]+ansNum[3];
  return;
}

最後に全コードをまとめて掲載してきます。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
</head>
<body>
	<canvas id="canId" width=600 height=500></canvas><br>
	<button onclick="stop()">STOP</button>

	<script>
		let getE = document.getElementById("canId");
		getE.style.backgroundColor = "black";
		let can = getE.getContext("2d");

		let interval = setInterval(loop, 10);	// 繰り返し処理
		
		function stop(){
			 clearInterval(interval);
			 Smiley.clearField();
		}

		//******************** スマイリークラス ********************
		class Smiley{
			constructor(x, y, moveX, moveY){	// コンストラクター
				this.x = x;
				this.y = y;
				this.moveX = moveX;
				this.moveY = moveY;
				this.color = "yellow";
			}
			static clearField(){			// クリア関数
				can.fillStyle = "#000";
				can.fillRect(0,0,600,500);
			}
			draw(){							// ドロー関数
				can.beginPath();						// ニコちゃん輪郭
				can.arc(50+this.x, 50+this.y, 50, 0, Math.PI*2);
				can.fillStyle = this.color;
				can.fill();
				can.beginPath();						// ニコちゃんの口
				can.arc(50+this.x, 50+this.y, 35, 0, Math.PI);
				can.strokeStyle = "black";
				can.lineWidth = 4;
				can.stroke();
				can.beginPath();						// ニコちゃんの目
				can.ellipse(35+this.x, 40+this.y, 5, 8, 0, 0, Math.PI*2);
				can.ellipse(65+this.x, 40+this.y, 5, 8, 0, 0, Math.PI*2);
				can.fillStyle = "black";
				can.fill();
			}
			move(){							// ムーブ関数
				if (this.x<0 || this.x>500){ this.moveX *= -1;}		// 左右壁との衝突判定
				if (this.y<0 || this.y>400){ this.moveY *= -1;}		// 天井と床との衝突判定

				this.x += this.moveX;								// 次回描画位置を設定
				this.y += this.moveY;
			}
		}

		//******************** スマイリー2クラス ********************
		class Smiley2 extends Smiley{	//スマイリークラスを継承
			move(){						//move()メソッドだけ変更→天井と床で反射と同時に色が変化
				let arrayColor = ["white","red","blue","green"];
				if (this.x<0 || this.x>500){ this.moveX *= -1;}	
				if (this.y<0 || this.y>400){
					this.moveY *= -1;
					this.color = arrayColor[Math.floor(Math.random()*4)];
				}	
				this.x += this.moveX;	
				this.y += this.moveY;
			}
		}

		//******************** スマイリー3 ********************
		class Smiley3 extends Smiley2{	//スマイリー2クラスを継承
			draw(){						//draw()メソッドだけ変更→四角いニコちゃん
				can.beginPath();						
				can.rect(this.x, this.y, 100, 100);
				can.fillStyle = this.color;
				can.fill();
				can.beginPath();						
				can.arc(50+this.x, 50+this.y, 35, 0, Math.PI);
				can.strokeStyle = "black";
				can.lineWidth = 4;
				can.stroke();
				can.beginPath();						
				can.ellipse(35+this.x, 40+this.y, 5, 8, 0, 0, Math.PI*2);
				can.ellipse(65+this.x, 40+this.y, 5, 8, 0, 0, Math.PI*2);
				can.fillStyle = "black";
				can.fill();
			}
		}


		// インスタンス化など
		let smileys = [];
		let nico_1 = new Smiley3(0,0,3,5);
		let nico_2 = new Smiley2(0,0,1,5);
		smileys.push(nico_1);
		smileys.push(nico_2);
		for (let i=0; i<10; i++){
			let moveX = Math.floor(Math.random()*5)+1;
			let moveY = Math.floor(Math.random()*5)+1;
			let smile = new Smiley(0,0,moveX,moveY);
			smileys.push(smile);			
		}

		function loop(){
			Smiley.clearField();
			for (let i=0; i<smileys.length; i++){
				smileys[i].draw();
				smileys[i].move();
			}
		}



	</script>
</body>
</html>

サイトマップ