Dual N Backアプリを作ってみた

IT・プログラミング

概要

脳トレアプリとして有名なDual N Backをできる簡易アプリを作成してみました。

コード全体


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dual N-Back Game</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 0;
            padding: 0;
        }
        #game-board {
            display: grid;
            grid-template-columns: repeat(3, 100px);
            grid-template-rows: repeat(3, 100px);
            gap: 10px;
            justify-content: center;
            margin: 20px auto;
        }
        .cell {
            width: 100px;
            height: 100px;
            background-color: lightgray;
            border: 1px solid #ccc;
        }
        .cell.active {
            background-color: yellow;
        }
        button {
            padding: 10px 20px;
            font-size: 16px;
            margin: 10px;
        }
        #feedback {
            font-size: 20px;
            color: red;
            margin: 10px;
        }
    </style>
</head>
<body>
    <h1>Dual N-Back Game</h1>
    <div>
        <label for="n-value">N値を設定:</label>
        <input type="number" id="n-value" value="2" min="1" style="width: 50px;">
    </div>
    <div id="game-board"></div>
    <button id="start-button">スタート</button>
    <button id="stop-button" disabled>ストップ</button>
    <button id="panel-match-button" disabled>パネル Match!</button>
    <button id="audio-match-button" disabled>音声 Match!</button>
    <div id="feedback"></div>
    <div>
        <p>正解数: <span id="correct-count">0</span></p>
        <p>不正解数: <span id="wrong-count">0</span></p>
    </div>
    <script>
        const board = document.getElementById("game-board");
        const startButton = document.getElementById("start-button");
        const stopButton = document.getElementById("stop-button");
        const panelMatchButton = document.getElementById("panel-match-button");
        const audioMatchButton = document.getElementById("audio-match-button");
        const feedback = document.getElementById("feedback");
        const correctCountEl = document.getElementById("correct-count");
        const wrongCountEl = document.getElementById("wrong-count");
        const nInput = document.getElementById("n-value");

        const cells = [];
        const alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
        let n = 2;
        let sequence = [];
        let audioSequence = [];
        let currentStep = 0;
        let panelMatchHappened = false;
        let audioMatchHappened = false;
        let activeIndex = -1;
        let gameInterval;
        let correctCount = 0;
        let wrongCount = 0;
        let displayMessage = '';

        // ゲームボードの初期化
        for (let i = 0; i < 9; i++) {
            const cell = document.createElement("div");
            cell.classList.add("cell");
            board.appendChild(cell);
            cells.push(cell);
        }

        function highlightCell(index) {
            cells.forEach((cell, i) => {
                cell.classList.toggle("active", i === index);
            });
        }

        function speakText(text) {
            const utterance = new SpeechSynthesisUtterance(text);
            speechSynthesis.speak(utterance);
        }

        function displayFeedback(message, isCorrect = null) {
            feedback.textContent = message;
            feedback.style.color = isCorrect === null ? 'red' : isCorrect ? 'green' : 'red';
            setTimeout(() => {
                feedback.textContent = '';
            }, 1000); // 1秒後にフィードバックを消去
        }

        function startGame() {
            n = parseInt(nInput.value) || 2;
            resetGame();
            sequence = [];
            audioSequence = [];
            currentStep = 0;
            gameInterval = setInterval(nextStep, 4000); // 3秒の間隔
            startButton.disabled = true;
            stopButton.disabled = false;
            panelMatchButton.disabled = false;
            audioMatchButton.disabled = false;
            feedback.textContent = "ゲーム開始!";
        }

        function stopGame() {
            clearInterval(gameInterval);
            startButton.disabled = false;
            stopButton.disabled = true;
            panelMatchButton.disabled = true;
            audioMatchButton.disabled = true;
            feedback.textContent = "ゲーム終了!";
            highlightCell(-1);
        }

        function resetGame() {
            correctCount = 0;
            wrongCount = 0;
            correctCountEl.textContent = correctCount;
            wrongCountEl.textContent = wrongCount;
        }

        function nextStep() {
            if (currentStep >= sequence.length) {
                let nextIndex;
                let audio_nextIndex;

                if (currentStep >= n && Math.random() < 0.3) { // 1/3の確率でMatchを発生
                    nextIndex = sequence[currentStep - n];
                    panelMatchHappened = true;
                } else {
                    nextIndex = Math.floor(Math.random() * cells.length);
                    panelMatchHappened = false;
                }

                if (currentStep >= n && Math.random() < 0.3) { // 1/3の確率でMatchを発生
                    audio_nextIndex = audioSequence[currentStep - n];
                    audioMatchHappened = true;
                } else {
                    audio_nextIndex = alphabet[Math.floor(Math.random() * alphabet.length)];
                    audioMatchHappened = false;
                }

                sequence.push(nextIndex);
                audioSequence.push(audio_nextIndex); // ランダムなアルファベットを音声配列に追加
            }

            activeIndex = sequence[currentStep];
            highlightCell(activeIndex);
            speakText(audioSequence[currentStep]); // 音声を再生

            setTimeout(() => {
                highlightCell(-1);
                // パネルが表示された後、マッチを見逃した場合の判定
                if (panelMatchHappened) {
                    displayMessage += "マッチを見逃しました!";

                    wrongCount++;
                    wrongCountEl.textContent = wrongCount;
                    panelMatchHappened = false; // リセット
                }

                if (audioMatchHappened) {
                    displayMessage +="音声Matchを見逃しました!";
                    wrongCount++;
                    wrongCountEl.textContent = wrongCount;
                    audioMatchHappened = false; // リセット
                }

                displayFeedback(displayMessage);
                displayMessage = "";

            }, 3000); // パネルが1秒後に消える

            currentStep++;
        }

        panelMatchButton.addEventListener("click", () => {
            if (panelMatchHappened) {
                displayFeedback("パネル正解!", true);
                correctCount++;
                correctCountEl.textContent = correctCount;
                panelMatchHappened = false; // 正解時にリセット
            } else {
                displayFeedback("パネル間違い!");
                wrongCount++;
                wrongCountEl.textContent = wrongCount;
            }
        });

        audioMatchButton.addEventListener("click", () => {
            if (audioMatchHappened) {
                displayFeedback("音声正解!", true);
                correctCount++;
                correctCountEl.textContent = correctCount;
                audioMatchHappened = false; // 正解時にリセット
            } else {
                displayFeedback("音声間違い!");
                wrongCount++;
                wrongCountEl.textContent = wrongCount;
            }
        });

        startButton.addEventListener("click", startGame);
        stopButton.addEventListener("click", stopGame);
    </script>
</body>
</html>

ポイント

  • タイルのマッチだけではなく、SpeechSynthesisUtteranceを使うことで音声に対応してDual N Backにした
  • N部分は動的設定に対応
  • そのままだとマッチ確率が低いのでマッチ確率を30%に設定
タイトルとURLをコピーしました