はじめに
・NumerOnのルールについてはこちらでご確認ください。
・今回、実際に作成したNumerOnの完成版はこちら
・jQueryなどのライブラリ一切使わず、純粋なJavaScriptのみ作成しました。
・自分自身で復習という意味も込めて、コードの説明を順々に行っていきます。
是非参考にしていただければと思います。
完成した、全体のコード
全体のコードといっても、ゲーム進行ページ全体のコードです。
<head>
<meta charset="utf-8" />
<title>JS_numeron_main</title>
<meta name="description" content="JS_numeron_TOP" />
<link rel="stylesheet" href="../css/styles.css" />
</head>
<body>
<div class="three_number_wrap">
<div class="number_wrap">
<div class="my_number">
<p class="my_number_text">my number</p>
<p class="my_num" id="my_num"></p>
</div>
</div>
<div class="numeron-form">
<input type="text" maxlength='3' id="input_num" value="">
<input type="submit" id='numeron_btn' value="numeron!">
</div>
<div class="eatBite_wrap" id="eat_bite"></div>
<ul class="" id="result_block"></ul>
</div>
<script src="../js/numeron.js"></script>
</body>
{
'use strict';
// ランダムな三桁の数字
let makeRivalNumArray = (function() {
let rivalNumArray = [];
return function() {
// 配列の長さが3になるまで繰り返す。
for (let i = 0; rivalNumArray.length < 3; i++) {
const randNum = Math.floor(Math.random() * 10) // 0~9までのランダムな整数
if(!rivalNumArray.includes(randNum)){ // 一度出た数字がなければ、配列に数字を足す
rivalNumArray.push(randNum);
}
}
return rivalNumArray;
}
})();
let rivalNumText = makeRivalNumArray().join('');
console.log(rivalNumText);
// eat(場所も数字も同じ数)
function eat(myNumArray) { // eatは変化する値なので、const × → let
let eatNum = 0;
for(let i=0; i < myNumArray.length; i++) { // eatの数
if (myNumArray[i] === makeRivalNumArray()[i]) {
eatNum++;
}
}
return eatNum;
}
// bite(数字のみ同じ数)
function bite(myNumArray) {
let EatBiteArray = makeRivalNumArray().concat(myNumArray), // 2つの配列を足した配列
// 2つの配列の重複した要素を、重複せずに取り出したもの
biteArray = EatBiteArray.filter(function(val, i, arr) {
// arr.indexOf(val):配列の中でその要素が最初に現れるindex番号の取得
return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
});
return biteArray.length - eat(myNumArray);
}
// その時々の結果の描画
function writeResultText(myNum, myNumArray, gameNum) { // 引数の順番は大事!
const result_block = document.getElementById('result_block'),
eatBite = document.getElementById('eat_bite'),
li = document.createElement('li');
let writeEatNum = eat(myNumArray),
writeBiteNum = bite(myNumArray),
writeEatBiteNum = `${writeEatNum}EAT ${writeBiteNum}BITE`;
li.textContent = `${gameNum}回目:${myNum} → ${writeEatBiteNum}`;
result_block.appendChild(li);
eatBite.textContent = writeEatBiteNum; // ?eat ?bite 描画(main)
}
// 数字が重複していないかのチェック
function checkDuplicate(inputNum) {
let newInputNum = new Set(inputNum);
return newInputNum.size === inputNum.length
}
function advanceGame(gameNum) {
const input = document.getElementById('input_num'),
inputNum = input.value.replace(/[^\d-.]/g,''); // 数字であれば値を返す(replace(/[^\d-.]/g,''))
// 3文字以内、かつ、異なる数字→テキスト表示 / そうでないなら、ゲーム進まない
if(inputNum.length === 3 && checkDuplicate(inputNum)) {
let myNumArray = [], // 選んだ数字の定義
my_num = document.getElementById('my_num');
my_num.innerHTML = inputNum; // my numberの欄に打った数字表示
myNumArray = my_num.innerHTML.split('').map(str => parseInt(str, 10)); // my_numの数字の文字列をデータ型numberとして配列に格納
setTimeout(function() {
writeResultText(my_num.innerHTML, myNumArray, gameNum); // その時々の結果の描画
// 3eat 0biteになった時
if(eat(myNumArray) === 3) {
setTimeout(function() {
alert(`NumerOn! 相手の数字は、'${rivalNumText}'でした!`);
location.reload(); // ゲームリセット(ページの再読み込み)
}, 1000);
}
}, 1000);
} else {
alert('※3ケタの異なる数字(半角)で入力してください!');
}
input.value = ''; // ボタンクリック後、inputの中をカラに。
}
let btnClick = (function() {
let gameNum = 0; // ゲーム回数
return function() {
gameNum++; // ゲーム回数のカウント
advanceGame(gameNum);
}
})();
document.getElementById('numeron_btn').addEventListener('click', function() {
btnClick();
});
}
順に、説明していきます。
コードの説明
①ランダムな三桁の数字
// ランダムな三桁の数字
let makeRivalNumArray = (function() {
let rivalNumArray = [];
return function() {
// 配列の長さが3になるまで繰り返す。
for (let i = 0; rivalNumArray.length < 3; i++) {
const randNum = Math.floor(Math.random() * 10) // 0~9までのランダムな整数
if(!rivalNumArray.includes(randNum)){ // 一度出た数字がなければ、配列に数字を足す
rivalNumArray.push(randNum);
}
}
return rivalNumArray;
}
})();
let rivalNumText = makeRivalNumArray().join('');
console.log(rivalNumText);
この部分でもそうですが、グローバル変数をできるだけ使わないために**「クロージャー」**を使用しています。
クロージャーが分からない人はこちらの記事を参考にしてみてください。
const randNum = Math.floor(Math.random() * 10)
:Math.random() * 10で0~10までのランダムな数字を生成。
Math.floor()で、その数値を整数へと変換し。変数randNum
へと代入しています。
②場所も数字も同じ数:eat
// eat(場所も数字も同じ数)
function eat(myNumArray) {
// eatは変化する値なので、const × → let
let eatNum = 0;
for(let i=0; i < myNumArray.length; i++) { // eatの数
if (myNumArray[i] === makeRivalNumArray()[i]) {
eatNum++;
}
}
return eatNum;
}
・引数myNumArray
には、別関数でプレイヤーが入力した数字の配列が代入されます。
myNumArray[i] === makeRivalNumArray()[i]
:プレイヤーが選んだ数字の配列と、隠された数字の配列の[i]番目の数字がそれぞれ一致するか比較しています。
比較をmyNumArrayの配列の長さの文だけループを回しています。
③場所は違うが、数字が同じ数:bite
function bite(myNumArray) {
let EatBiteArray = makeRivalNumArray().concat(myNumArray), // 2つの配列を足した配列
// 2つの配列の重複した要素を、重複せずに取り出したもの
biteArray = EatBiteArray.filter(function(val, i, arr) {
// arr.indexOf(val):配列の中でその要素が最初に現れるindex番号の取得
return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
});
return biteArray.length - eat(myNumArray);
}
・引数myNumArray
には、別関数でプレイヤーが入力した数字の配列が代入されます。
let EatBiteArray = makeRivalNumArray().concat(myNumArray)
:concat()ですが、2つ以上の配列を結合させるためのメソッドです。
結合した新たな配列を変数EatBiteArray
へと代入しています。
続いて、
// 2つの配列の重複した要素を、重複せずに取り出したもの
biteArray = EatBiteArray.filter(function(val, i, arr) {
// arr.indexOf(val):配列の中でその要素が最初に現れるindex番号の取得
return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
});
この部分のコードですが、
filter()メソッドでは、条件を満たす要素で新たな配列を作成するメソッドです。
その条件は、()の中の(val, i, arr)
この部分です。
・val
:コールバック関数の各要素
・i
:要素のindex
・arr
:array自身
上記のコードではあまりにも伝わりにくいので
【例】filter()を噛み砕いて説明
let array = [0, 2, 2, 3, 4, 4, 4, 5];
// 重複を削除した配列
let array = [0, 2, 2, 3, 4, 4, 4, 5];
// 重複を削除した配列
let result = array.filter(function(val, i, arr) {
// console.log(arr); // [0, 2, 2, 3, 4, 4, 4, 5]
// console.log(arr.indexOf(val)); // 0, 1, 1, 3, 4, 4, 4, 7
// console.log(arr.lastIndexOf(val)); // 0, 2, 2, 3, 6, 6, 6, 7
// console.log(arr.indexOf(val) === i); // true true false true true false false true
// console.log(i !== arr.lastIndexOf(val)); // false true false false true ture false false
return arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
});
console.log(result); // [2, 4]
indexOf()
メソッド:指定された値が最初に現れたインデックスを返します。
lastIndexOf() メソッド
:逆方向に検索し、指定された値が最後に現れるインデックスを返します。
上記の例で言えば、
console.log(arr.indexOf(val)); // 0, 1, 1, 3, 4, 4, 4, 7
となるため、
console.log(arr.indexOf(val) === i); // true true false true true false false true
で、重複する値を削除した配列を返しています。
続いて、こちらも同じく「逆方向に検索」おこなって
console.log(arr.lastIndexOf(val)); // 0, 2, 2, 3, 6, 6, 6, 7
console.log(i !== arr.lastIndexOf(val)); // false true false false true ture false false
arr.indexOf(val) === i && i !== arr.lastIndexOf(val);
④結果の描画
// その時々の結果の描画
function writeResultText(myNum, myNumArray, gameNum) { // 引数の順番は大事!
const result_block = document.getElementById('result_block'),
eatBite = document.getElementById('eat_bite'),
li = document.createElement('li');
let writeEatNum = eat(myNumArray),
writeBiteNum = bite(myNumArray),
writeEatBiteNum = `${writeEatNum}EAT ${writeBiteNum}BITE`;
li.textContent = `${gameNum}回目:${myNum} → ${writeEatBiteNum}`;
result_block.appendChild(li);
eatBite.textContent = writeEatBiteNum; // ?eat ?bite 描画(main)
}
・result_block.appendChild(li);
でゲーム回数を積み重ねるごとに結果である
⑤入力した数字が重複していないかのチェック
function checkDuplicate(inputNum) {
let newInputNum = new Set(inputNum);
return newInputNum.size === inputNum.length
}
new Set(inputNum)
:Setオブジェクトでは重複した要素を格納できません。
MDN
つまり、重複した要素を弾くことができます。
inputNum(入力された文字)は3桁の正の整数が入りますが、重複した数字の場合ははじきます。(3桁がどうかのチェックはこの後です)
ここで、異なる数字を返すことができればOKです。
ですが、次が複雑です。
sizeとlengthでチェック?このコードについて説明します。
return newInputNum.size === inputNum.length
・newInputNum.size
:sizeプロパティは、Set オブジェクト内の要素の数を返します。
・inputNum.length
:lengthプロパティは配列の最後のインデックスに1加えた値を取得することができます。
詳しくは↓を参考に
配列の要素の数を取得する
この後に出てきますが、inputNum
には3桁の数字しかtrueにならない条件分岐をおこないます。
つまり、newInputNum.size === 3
となるような値を返すため、結果として重複しない3桁の正の整数が出来上がります。
JavaScriptで配列の重複チェックを超簡単&スマートにやる方法
⑥ゲーム進行の関数
function advanceGame(gameNum) {
const input = document.getElementById('input_num'),
inputNum = input.value.replace(/[^\d-.]/g,'');
// 3文字以内、かつ、異なる数字→テキスト表示 / そうでないなら、ゲーム進まない
if(inputNum.length === 3 && checkDuplicate(inputNum)) {
let myNumArray = [],
my_num = document.getElementById('my_num');
my_num.innerHTML = inputNum; // my numberの欄に打った数字表示
myNumArray = my_num.innerHTML.split('').map(str => parseInt(str, 10));
setTimeout(function() {
writeResultText(my_num.innerHTML, myNumArray, gameNum); // その時々の結果の描画
// 3eat 0biteになった時
if(eat(myNumArray) === 3) {
setTimeout(function() {
alert(`NumerOn! 相手の数字は、'${rivalNumText}'でした!`);
location.reload(); // ゲームリセット(ページの再読み込み)
}, 1000);
}
}, 1000);
} else {
alert('※3ケタの異なる数字(半角)で入力してください!');
}
input.value = ''; // ボタンクリック後、inputの中をカラに。
}
3行目:input.value.replace(/[^\d-.]/g,'');
:数字であれば値を返す
・replace(/[^\d-.]/g, '')
では、第一引数を第二引数へ置き換えています。
inputNum.length === 3 && checkDuplicate(inputNum)
:入力された文字が3文字以内、かつ、異なる数字ならばtrue
myNumArray = []
:ここに、選んだ数字を格納します。
myNumArray = my_num.innerHTML.split('').map(str => parseInt(str, 10));
:my_numの数字の文字列をデータ型numberとして配列に格納しています