Flip The Card(プログラム)

「index.html」のコード全体は下記になります。

<!DOCTYPE html>
<html lang="ja">	
<head>
    <meta charset="UTF-8">
    <title>カードゲームアプリ</title>
	<link rel="stylesheet" href="common.css">
</head>	
<body>
    <div id="modal_frame">
        <div id="modal_box">
            <p>ゲーム終了!!</p>
            <p>正解率:<span id="success_rate">0</span>%</p>
            <button id="retry_btn">リトライ</button>
            <button class="to_start_btn">スタート画面へ</button>
        </div>
    </div>
    <header>
        <img src="./images/title.png" alt="カードゲームタイトル">
    </header>
    <main>
        <div id="start_screen">
            <p class="rank_16_frame">16マスの現在のランク:<span class="rank_16"></span></p>
            <p class="rank_36_frame">36マスの現在のランク:<span class="rank_36"></span></p>
            <div id="select_game_mode_frame">
                <p>■ゲームモード選択</p>
                <div>
                    <p>マス目の数を選択してください。</p>
                        <select id="game_mode">
                            <option value="16">16マス</option>
                            <option value="36">36マス</option>
                        </select>
                    </div>
                </div>
                <button id="start_btn">スタート</button>
                <hr>
                <button id="score_btn">スコア&ランク</button>
            </div>
            <div id="game_screen">
                <p id="message"></p>
                <p>現在のカード選択回数:<span id="select_card_count">0</span></p>
                <div id="cards_container" class="clear"></div>
                <button id="next_btn">次へ</button>
            </div>
            <div id="score_screen">
                <p id="score_title">スコア&ランク</p>
                <p class="rank_16_frame">16マスの現在のランク:<span class="rank_16"></span></p>
                <table id="score_16_list"></table>
                <hr>
                <p class="rank_36_frame">36マスの現在のランク:<span class="rank_36"></span></p>
                <table id="score_36_list"></table>
				<hr>
                <button class="to_start_btn">スタート画面</button>
            </div>
        </main>
        <script>
            let firstCard = null;             //1回目に選択したカード
            let secondCard = null;            //2回目に選択したカード
            let cardsNums = new Array();      //カードの番号用配列
            let firstPickFlg = true;          //1回目のカードの選択フラグ
            let finishedOneCycle = false;     //1サイクル文のゲーム終了フラグ
            let cardsObj = null;              //全カードオブジェクト格納用
            let successCount = 0;             //カード選択成功回数
            let selectCount = 0;              //カード選択回数
            let game_mode_num = 16;           //ゲームモード
            let score_16_data = new Array();  //16マスのスコアデータ
            let score_36_data = new Array();  //36マスのスコアデータ
	
            /**
             * カードを生成
             */
            function createCards() {
                getElmId("cards_container").innerHTML = '';              //カードをクリア
	
                cardsNums = createRandomNums();                          //カードのランダムな番号を生成
	
                for (var i = 0; i < game_mode_num; i++) {
                    let frame = document.createElement("div");           //カードフレームの作成
                    let card = document.createElement("div");            //カードの作成
                    let card_num = document.createElement("span");       //カード番号用
                    let card_cover_img = document.createElement("img");  //カバー画像表示用
                    card_cover_img.dataset.id = i;                       //データIDを設定
                    card_cover_img.src = "images/cover.png";             //カバー画像ファイル名を設定
                    card.classList.add("card");                          //クラスを追加
                    card_num.classList.add("card_num");                  //クラスを追加
                    card_num.style.display = "none";                     //カードの番号を非表示
                    card.appendChild(card_num);                          //カード番号をカードフレームに追加
                    card.appendChild(card_cover_img);                    //カバー画像をカードフレームに追加
                    frame.appendChild(card);                             //カードフレームへカードを追加
                    getElmId("cards_container").appendChild(frame);      //カードコンテナにカードフレームを追加
                }
	
                let cards = Array.from(document.getElementsByClassName("card"));  //カードを配列として取得
                cards.forEach(element => {
                    element.addEventListener("click", selectCard, false);  //カードクリック時のイベントリスナーを設定
                });
	
                //カードの横幅を設定
                let cards_frame = Array.from(getElmId('cards_container').children);  //全カード要素を取得
                if (game_mode_num === 16) {  //16マスのゲームの場合
                    cards_frame.forEach(
                        element => element.style.width = "25%"
                    );
                } else if (game_mode_num === 36) {  //36マスのゲームの場合
                    cards_frame.forEach(
                        element => element.style.width = "16%"
                    );
                }
            }
	
            /**
             * カード選択時の処理
            */
            function selectCard(e) {
                if (!finishedOneCycle) { //1サイクルのゲームが終了していない場合
                    selectCount++;                                          //カード選択カウントを+1カウントアップ                                               
                    getElmId('select_card_count').innerHTML = selectCount;  //カード選択回数を表示
                    let selectCard = cardsObj[e.target.dataset.id];         //選択カードの取得
	
                    selectCard.getElementsByTagName('img')[0].style.display = "none";                      //カバー画像を非表示
                    selectCard.getElementsByTagName('span')[0].style.display = "block";                    //カード番号用要素を表示
                    selectCard.getElementsByTagName('span')[0].innerHTML = cardsNums[e.target.dataset.id]; //カード番号を設定
                    if (firstPickFlg) { //1回目のカードを選択しているか?
                        firstCard = selectCard;  //1回目の「選択カード」を変数に設定
                        firstPickFlg = false;    //1回目の「カード選択フラグ」を設定
						getElmId("message").innerHTML = "2枚目のカードを選択してください。";  //表示メッセージを設定
                    } else { //2回目のカードを選択しているか?
                        getElmId("next_btn").disabled = false;                   //「次へ」ボタンを有効化
                        getElmId("next_btn").style.backgroundColor = "#0000ff";  //「次へ」ボタンの背景色を青に変更
                        secondCard = selectCard;                                 //2回目の選択カードを変数に設定
                        firstPickFlg = true;                                     //1回目のカード選択フラグを設定
                        finishedOneCycle = true;                                 //1サイクル分のゲーム実行フラグを設定
                        getElmId("message").innerHTML = "「次へ」ボタンをクリックしてください。";  //表示メッセージを設定
                        if (successCount === (game_mode_num / 2) - 1) {  //全てのカードが正解になっているか?
                            //「ゲーム結果」をローカルストレージへ保存
                            var d = new Date();  //日付オブジェクトを生成
                            //日付文字列を生成
                            var d_save_str = d.getFullYear() + "年" + (d.getMonth() + 1) + "月" + d.getDate() + "日" + d.getHours() + "時" + d.getMinutes() + "分";
	
                            //「配列データ」を作成
                            let regist_data = {
                                'select_count': selectCount,
                                'date': d_save_str
                            }
	
                            if (game_mode_num === 16) {                         //16マスのゲーム実行時
                                score_16_data.push(regist_data);                //スコアデータをスコア用配列に追加
                                saveScoreData('score_16_data', score_16_data);  //スコア用配列をローカルストレージへ保存
                            } else if (game_mode_num === 36) {                  //36マスのゲーム実行時
                                score_36_data.push(regist_data);                //スコアデータをスコア用配列に追加
                                saveScoreData('score_36_data', score_36_data);  //スコア用配列をローカルストレージへ保存
                            }
	
                            getElmId("message").innerHTML = "終了";                   //メッセージを表示
                            getElmId("next_btn").disabled = true;                    //「次へ」ボタンを無効化
                            getElmId("next_btn").style.backgroundColor = "#afafaf";  //「次へ」ボタンの背景色を変更
	
                            getElmId('modal_frame').style.display = 'block';                            //モーダル画面を表示
                            getElmId('success_rate').innerHTML = Math.round((16 / selectCount) * 100);  //モーダル画面へ正解率を表示
	
                            successCount = 0; //正解率をリセット
                            selectCount = 0;  //カード選択回数をリセット
	
                            finishedOneCycle = false;  //1サイクル分のゲーム実行フラグを設定
                        }
                    }
                } else {
                    alert("「次へ」ボタンをクリックしてください。");
                }
            }
	
            /**
              * 次のカードを選択
              */
            function nextSelectCard() {
                getElmId("message").innerHTML = "1枚目のカードを選択してください。";    //表示メッセージを設定
                //選択カード間違い時
                if (firstCard.getElementsByTagName('span')[0].innerHTML !== secondCard.getElementsByTagName('span')[0].innerHTML) {
                    firstCard.getElementsByTagName('img')[0].style.display = "block";   //1回目の選択カードのカバー画像を表示
                    firstCard.getElementsByTagName('span')[0].style.display = "none";   //1回目の選択カードの番号を非表示
                    secondCard.getElementsByTagName('img')[0].style.display = "block";  //2回目の選択カードのカバー画像を表示
                    secondCard.getElementsByTagName('span')[0].style.display = "none";  //2回目の選択カードの番号を非表示
                } else {  //選択カード正解時
                    successCount++;  //正解カウントを1カウントアップ
                }
                finishedOneCycle = false;                                  //1サイクル分のゲーム実行フラグを設定
                getElmId("next_btn").disabled = true;                      //「次へ」ボタンを無効化
                getElmId("next_btn").style.backgroundColor = "#afafaf";    //「次へ」ボタンの背景色を変更
            }
	
            /**
             * ランダム値のペアを生成
             */
            function createRandomNums() {
                let randNums = [];  //ランダム値格納用
                let nums = [];     //配列要素番号格納用
                let rand = 0;       //ランダム値格納用
	
                //配列要素番号を作成
                for (let i = 1; i <= (game_mode_num / 2); i++) {
                    nums.push(i);
                    nums.push(i);
                }
	
                //ランダムな配列要素番号を作成
                for (let i = 0; i < game_mode_num; i++) {
                    rand = Math.floor(Math.random() * nums.length);
                    randNums.push(nums[rand]);
                    nums.splice(rand, 1);
                }
                return randNums;
            }
	
            /**
             * 「スタート」ボタンをクリック時
             */
            function startGame() {
                displayScreen('game');                                  //ゲーム画面を表示
                getElmId("select_card_count").innerHTML = selectCount;  //カード選択回数を更新
	
                game_mode_num = parseInt(getElmId('game_mode').value);  //ゲームモードを取得
	
                createCards();                                          //カードの生成
                cardsObj = getElmClass("card");                         //カードオブジェクトを取得
	
                getElmId("message").innerHTML = "1枚目のカードを選択してください。";  //表示メッセージを設定
                getElmId("next_btn").disabled = true;                                //「次へ」ボタンを無効化
            }
	
            /**
             * 「スコア」ボタンをクリック時
             */
            function displayScore() {
                displayScreen('score');  //スコア画面を表示
                displayScoreList();      //スコア一覧を表示
            }
	
            /**
              * スコア一覧を表示
              */
            function displayScoreList() {
                //16マスのランクを表示
                let score_16_rank = getRankVal(score_16_data);  //「ランク値」を計算
                Array.from(getElmClass('rank_16')).forEach(
                    element => isNaN(score_16_rank) ? element.innerHTML = score_16_rank : element.innerHTML = createRankStar(score_16_rank)
                );
	
                //36マスのランクを表示
                let score_36_rank = getRankVal(score_36_data);  //「ランク値」を計算
                Array.from(getElmClass('rank_36')).forEach(
                    element => isNaN(score_36_rank) ? element.innerHTML = score_36_rank : element.innerHTML = createRankStar(score_36_rank)
                );
	
                //16マスの「スコア一覧」テーブルを作成&表示
                createScoreTable('score_16_list', score_16_data);
	
                //36マスの「スコア一覧」テーブルを作成&表示
                createScoreTable('score_36_list', score_36_data);
            }
	
            /**
              *  「スコア一覧」テーブルを作成&表示(最新のデータから10プレイ分を表示)
              */
            function createScoreTable(list_type, score_data) {
                getElmId(list_type).innerHTML = '';     //現在の表示内容をクリア
	
                //テーブルヘッダーの作成&表示
                let tr = document.createElement('tr');
                let td = null;
                let th = document.createElement('th');
                th.innerHTML = 'プレイ日時';
                tr.appendChild(th);
                th = document.createElement('th');
                th.innerHTML = '正解率';
                tr.appendChild(th);
                getElmId(list_type).appendChild(tr);
                if (score_data.length !== 0) {
                    //スコア一覧データを作成&表示
                    for (var i = score_data.length - 1; i > (score_data.length - 1) - 10; i--) {
                        if (i >= 0) {
                            tr = document.createElement('tr');
                            td = document.createElement('td');
                            td.innerHTML = score_data[i]['date'];
                            tr.appendChild(td);
                            td = document.createElement('td');
                            td.innerHTML = Math.round((16 / parseInt(score_data[i]['select_count'])) * 100) + "%";
                            tr.appendChild(td)
                            getElmId(list_type).appendChild(tr);
                        }
                    }
                }
            }
	
            /**
              *  指定画面を表示
             */
            function displayScreen(screen_type) {
                hideScreen();
                switch (screen_type) {
                    case 'start':  //スタート画面を表示
                        getElmId('start_screen').style.display = 'block';
                        displayScoreList();  //スコア一覧を表示
                        break;
                    case 'game':   //ゲーム画面を表示
                        getElmId('game_screen').style.display = 'block';
                        break;
                    case 'score':  //スコア一覧画面を表示
                        getElmId('score_screen').style.display = 'block';
                        break;
                }
            }
	
            /**
              *  画面を非表示
              */
            function hideScreen() {
                getElmId('start_screen').style.display = 'none';  //スタート画面を非表示
                getElmId('game_screen').style.display = 'none';   //ゲーム画面を非表示
                getElmId('score_screen').style.display = 'none';  //スコア画面を非表示
            }
	
            /**
             * 「リトライ」ボタンをクリック時(モーダル画面)
             */
            function retry() {
                getElmId('modal_frame').style.display = 'none';  //モーダル画面を非表示
                startGame();                                     //ゲームを開始
            }

            /**
             * 「スタート画面へ」ボタンをクリック時(モーダル画面)
             */
            function toStartScreen() {
                getElmId('modal_frame').style.display = 'none';  //モーダル画面を非表示
                displayScreen('start');                          //スタート画面を表示
            }
	
            /** 
             *  「ランク値」を計算
             */
            function getRankVal(scoreData) {
                if (scoreData.length < 10) {  //プレイ回数が足りない場合
                    let remain_game = 10 - scoreData.length;                       //スコア判定までの残りゲーム数
                    return 'あと' + remain_game + 'ゲームでランクが判定できます。';  //表示用メッセージの作成
                } else {
                    let sum = 0;   //合計値格納用
                    let rank = 0;  //ランク数格納用
	
                    //最新のデータから10プレイ分を元に「ランク値」を算出
                    for (var i = scoreData.length - 1; i > (scoreData.length - 1) - 10; i--) {
                        sum += parseInt(scoreData[i]['select_count']);  //スコアの合計値を計算
                    }
                    let judge_val = Math.round((160 / sum) * 100);  //スコアを算出
                    rank = Math.floor((judge_val / 10) + 1);        //ランクの計算
                    return rank;
                }
            }
	
            /** 
             *  「ランクスター」を作成
             */
            function createRankStar(rank_val) {
                let rank_star = '';  //ランクスター文字列格納用
                for (var i = 1; i <= 10; i++) {
                    i <= rank_val ? rank_star += "★" : rank_star += "☆";  //ランクスター文字列を作成
                }
                //表示用ランク文字列を作成
                return '<span class="low_label">Low</span> ' + rank_star + ' <span class="high_label">High</span> (Rank:' + rank_val + ')';
            }
	
            /** 
              *  「スコアデータ」をロード
              */
            function loadScoreData(data_type) {
                let load_json = localStorage.getItem(data_type);  //「ローカルストレージ」からデータをロード
                let score_data = JSON.parse(load_json);           //「JSON」から「スコアデータ(配列)」へ変換
	
                //スコアデータが無い場合
                if (score_data === null) {
                    score_data = new Array();
                }
	
                return score_data;
            }
	
            /** 
             *  「スコアデータ」をセーブ
             */
            function saveScoreData(data_type, save_data) {
                let save_json = JSON.stringify(save_data);    //「スコアデータ(配列)」を「JSON」へ変換
                localStorage.setItem(data_type, save_json);   //「ローカルストレージ」へセーブ
            }
	
            /**
              * 指定したIDの要素を取得
              */
            function getElmId(val) {
                return document.getElementById(val);
            }
	
            /**
             * 指定したクラスの要素を取得
             */
            function getElmClass(val) {
                return document.getElementsByClassName(val);
            }
	
            /**
             * 指定した要素名の要素を取得
             */
            function getElm(val) {
                return document.querySelector(val);
            }
	
            window.onload = function () {
                getElmId("start_btn").addEventListener("click", startGame, false);      //スタートボタンのイベントリスナー設定
                getElmId("next_btn").addEventListener("click", nextSelectCard, false);  //「次へ」ボタンのイベントリスナー設定
                getElmId("retry_btn").addEventListener("click", retry, false);          //リトライボタンのイベントリスナー設定
                let to_start_btn = Array.from(getElmClass('to_start_btn'));             //スタート画面遷移ボタンの取得   
                to_start_btn.forEach(element => {
                    element.addEventListener("click", toStartScreen, false);            //スタート画面遷移ボタンのイベントリスナー設定
                });
	
                getElmId("score_btn").addEventListener("click", displayScore, false);   //スコアボタンのイベントリスナー設定
	
                // スコアデータのロード
                score_16_data = loadScoreData('score_16_data');  //16マスのスコアデータをロード
                score_36_data = loadScoreData('score_36_data');  //36マスのスコアデータをロード
	
                displayScreen('start');                          //スタート画面を表示
                getElmId('modal_frame').style.display = 'none';  //モーダル画面を非表示
            }
        </script>
    </body>
</html>

「common.css」のコード全体は下記になります。

body {
    margin: 0;
}
    
header {
    margin: 10px 0 20px 0;
}
    
hr {
    margin: 30px 0;
}
    
header img,
#start_screen,
#score_screen,
#game_screen,
#score_16_list,
#score_36_list,
#select_game_mode_frame {
    max-width: 600px;
    min-width: 300px;
}
    
header img,
#start_screen,
#score_screen,
#game_screen,
#start_btn,
#score_btn,
#start_screen select,
.card>span,
#retry_btn,
.to_start_btn,
#next_btn,
#modal_box button {
    display: block;
}
    
#select_game_mode_frame>p,
#score_title {
    background: -moz-linear-gradient(#5f5f5f, #ffffff);
    background: -webkit-linear-gradient(#5f5f5f, #ffffff);
    background: linear-gradient(#5f5f5f, #ffffff);
}
    
header img,
#start_screen,
#score_screen,
#game_screen {
    width: 100%;
    margin: 0 auto;
    padding: 5px;
}
    
#start_btn,
#score_btn {
    width: 200px;
    padding: 10px 0;
    margin: 0 auto;
}
    
#select_game_mode_frame>p {
    color: #ffffff;
    font-weight: bold;
    margin: 10px 0 0 0;
    padding: 15px 10px 15px 10px;
    border: solid 1px #bfbfbf;
    border-radius: 10px 10px 0 0;
}
    
#select_game_mode_frame>div {
    margin: 0 0 10px 0;
    padding: 10px 10px 15px 10px;
    border: solid 1px #bfbfbf;
    border-top: none;
    border-radius: 0 0 10px 10px;
}
    
#start_screen select {
    width: 155px;
    margin: 10px 0;
    padding: 6px 0 10px 12px;
    border: solid 1px #bfbfbf;
    border-radius: 30px;
    font-size: 18px;
}
    
    
#message {
    font-size: 24px;
    font-weight: bold;
    color: #ffffff;
    text-align: center;
    border-top: solid 2px #0000ff;
    border-bottom: solid 2px #0000ff;
    border-right: solid 2px #0000ff;
    border-left: solid 2px #0000ff;
    margin: 10px 0;
    padding: 5px 0;
    background: -moz-radial-gradient(#0000ff, #ffffff);
    background: -webkit-radial-gradient(#0000ff, #ffffff);
    background: radial-gradient(#0000ff, #ffffff);
}
    
#cards_container {
    margin: 0 auto;
    border: solid 1px #dfdfdf;
}
    
#cards_container>div {
    width: 25%;
    height: 100px;
    padding: 5px;
    float: left;
    box-sizing: border-box;
}
    
.card {
    text-align: center;
    width: 100%;
    height: 100%;
    border: solid 1px #bfbfbf;
}
    
.card>span {
    padding-top: 28px;
    font-size: 24px;
}
    
.card>img {
     width: 100%;
    height: 100%;
}
    
.rank_16_frame,
.rank_36_frame {
    border: solid 1px #bfbfbf;
    padding: 15px 10px;
    border-radius: 25px;
    box-shadow: 0 5px 5px 0 rgba(0, 0, 0, .5);
}
    
.low_label {
    color: #ffffff;
    padding: 5px;
    background: -moz-radial-gradient(#8f8f8f, #ffffff);
    background: -webkit-radial-gradient(#8f8f8f, #ffffff);
    background: radial-gradient(#8f8f8f, #ffffff);
    border-radius: 15px;
}
    
.high_label {
    color: #ffffff;
    padding: 5px;
    background: -moz-radial-gradient(#ff0000, #ffffff);
    background: -webkit-radial-gradient(#ff0000, #ffffff);
    background: radial-gradient(#ff0000, #ffffff);
    border-radius: 7px;
}
    
#start_btn,
#score_btn,
#retry_btn,
.to_start_btn {
    padding: 20px;
    border-radius: 35px;
    box-shadow: 0 5px 5px 0 rgba(0, 0, 0, .5);
    border: none;
    margin: 10px auto;
}
    
#next_btn {
    font-size: 20px;
    color: #ffffff;
    clear: both;
    margin: 20px auto 0 auto;
    padding: 20px 60px;
    border: none;
    border-radius: 40px;
}
    
.clear:after {
    content: "";
    display: block;
    clear: both;
}
    
#score_title {
    color: #ffffff;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
    border: solid 3px #bfbfbf;
    padding: 15px 0;
}
    
#modal_frame {
    background: rgba(45, 45, 45, 0.5);
    position: absolute;
    width: 100%;
    height: 100%;
}
    
#modal_box {
    width: 50%;
    background-color: #ffffff;
    border: solid 1px #8f8f8f;
    opacity: 1.0;
    margin: 30px auto 0 auto;
    padding: 50px;
    font-size: 20px;
    text-align: center;
    box-shadow: 0 15px 15px 0 rgba(0, 0, 0, .5);
    background-image: url("./images/finished_game_back.png");
    background-repeat: no-repeat;
}
    
#modal_box p {
    color: #3f3f3f;
    font-weight: bold;
}
    
#modal_box button {
    width: 200px;
    padding: 10px 50px;
    margin: 20px auto;
}
    
table {
    border-collapse: collapse;
    margin-bottom: 30px;
}
    
#score_16_list,
#score_36_list {
    margin: 30px auto 40px auto;
    border: solid 5px #5f5f5f;
    box-shadow: 0 15px 15px 0 rgba(0, 0, 0, .5);
}
    
#score_16_list td,
#score_16_list th,
#score_36_list td,
#score_36_list th {
    color: 18px;
    border: solid 2px #2f2f2f;
    padding: 6px 20px;
    text-align: center;
}
    
#score_16_list th,
#score_36_list th {
    color: #ffffff;
    background-color: #8f8f8f;
    background: -moz-linear-gradient(#8f8f8f, #cfcfcf);
    background: -webkit-linear-gradient(#8f8f8f, #cfcfcf);
    background: linear-gradient(#8f8f8f, #cfcfcf);
}
    
#testDisplay td {
    border: solid 1px #000000;
    text-align: right;
}
HOMEへ