Webbserver - Love Blomberg

Show sourcecode

The following files exists in this folder. Click to view.

public_html/GYA2/js/

common.js
maze.js
reaction.js
simon.js

simon.js

190 lines UTF-8 Unix (LF)
'use strict';

(function() {
    const pid = getParticipantId();
    const levelEl = document.getElementById('level');
    const statusEl = document.getElementById('status');
    const buttons = document.querySelectorAll('.simon-btn');

    // Audio context for tones
    let audioCtx = null;
    const FREQUENCIES = [261.63, 329.63, 392.00, 523.25]; // C4, E4, G4, C5

    function playTone(index, duration) {
        if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();
        osc.type = 'sine';
        osc.frequency.value = FREQUENCIES[index];
        gain.gain.value = 0.3;
        osc.connect(gain);
        gain.connect(audioCtx.destination);
        osc.start();
        gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration / 1000);
        osc.stop(audioCtx.currentTime + duration / 1000);
    }

    // Game state
    let sequence = [];
    let inputIndex = 0;
    let level = 0;
    let roundData = [];
    let responseTimes = [];
    let lastPressTime = 0;
    let gameStartTime = 0;
    let processing = false; // guard against double-clicks
    let state = 'IDLE'; // IDLE | SHOWING | AWAITING | GAME_OVER

    function lightUp(index, duration) {
        return new Promise(function(resolve) {
            buttons[index].classList.add('lit');
            playTone(index, duration);
            setTimeout(function() {
                buttons[index].classList.remove('lit');
                setTimeout(resolve, 200); // gap between flashes
            }, duration);
        });
    }

    async function showSequence() {
        state = 'SHOWING';
        statusEl.textContent = 'Titta på sekvensen...';
        setButtonsDisabled(true);

        // Brief pause before starting playback
        await sleep(600);

        for (let i = 0; i < sequence.length; i++) {
            await lightUp(sequence[i], 500);
        }

        // Now it's the player's turn
        state = 'AWAITING';
        inputIndex = 0;
        responseTimes = [];
        lastPressTime = performance.now();
        statusEl.textContent = 'Din tur!';
        setButtonsDisabled(false);
    }

    function setButtonsDisabled(disabled) {
        buttons.forEach(function(btn) {
            if (disabled) {
                btn.classList.add('disabled');
            } else {
                btn.classList.remove('disabled');
            }
        });
    }

    function sleep(ms) {
        return new Promise(function(resolve) { setTimeout(resolve, ms); });
    }

    function nextLevel() {
        level++;
        levelEl.textContent = level;
        // Add a random color to the sequence
        sequence.push(Math.floor(Math.random() * 4));
        showSequence();
    }

    async function gameOver() {
        state = 'GAME_OVER';
        setButtonsDisabled(true);

        // Record the failed round
        roundData.push({
            level_number: level,
            success: 0,
            response_times: responseTimes.slice()
        });

        const totalTimeMs = Math.round(performance.now() - gameStartTime);
        const maxLevel = level - 1; // last successfully completed level

        statusEl.textContent = 'Spelet slut! Du nådde nivå ' + maxLevel + '. Sparar resultat...';

        // Store for results page
        sessionStorage.setItem('simon_level', maxLevel);
        sessionStorage.setItem('simon_time', totalTimeMs);

        try {
            await apiPost('../api/save_simon.php', {
                participant_id: pid,
                max_level: maxLevel,
                total_time_ms: totalTimeMs,
                rounds: roundData
            });
        } catch (e) {
            console.error('Failed to save Simon results:', e);
        }

        setTimeout(function() {
            window.location.href = '../results.php';
        }, 2000);
    }

    function handleButtonClick(index) {
        if (state !== 'AWAITING' || processing) return;
        processing = true;

        const now = performance.now();
        responseTimes.push(Math.round(now - lastPressTime));
        lastPressTime = now;

        // Flash the button briefly
        buttons[index].classList.add('lit');
        playTone(index, 200);
        setTimeout(function() {
            buttons[index].classList.remove('lit');
            processing = false; // allow next click after animation
        }, 200);

        if (index === sequence[inputIndex]) {
            // Correct
            inputIndex++;
            if (inputIndex === sequence.length) {
                // Level complete
                setButtonsDisabled(true);
                roundData.push({
                    level_number: level,
                    success: 1,
                    response_times: responseTimes.slice()
                });
                statusEl.textContent = 'Nivå ' + level + ' klar!';
                setTimeout(nextLevel, 1000);
            }
        } else {
            // Wrong — game over
            processing = true; // keep locked during game over
            buttons[index].style.outline = '4px solid #dc2626';
            setTimeout(function() { buttons[index].style.outline = ''; }, 500);
            gameOver();
        }
    }

    // Attach click handlers
    buttons.forEach(function(btn) {
        btn.addEventListener('click', function() {
            handleButtonClick(parseInt(btn.dataset.index, 10));
        });
    });

    // Instruction overlay — wait for dismiss before starting game
    function startGame() {
        gameStartTime = performance.now();
        nextLevel();
    }

    const overlay = document.getElementById('simon-instructions-overlay');
    if (overlay) {
        document.getElementById('simon-start-btn').addEventListener('click', function() {
            overlay.style.display = 'none';
            startGame();
        });
    } else {
        startGame();
    }
})();