BYPASS SHELL BY ./RAZORGANZ
Server: nginx/1.20.1
System: Linux iZdzfnv9mwfppeZ 5.10.134-19.2.al8.x86_64 #1 SMP Wed Oct 29 22:47:09 CST 2025 x86_64
User: apache (48)
PHP: 8.2.30
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/themes/elearning-education/page-cognitive-blitz-defense.php
<?php
/*
Template Name: 认知闪击塔防游戏(2025终极完美版)
Description   全部需求已实现:真实难度 + 试卷名 + 错题回顾 + 结束页居中 + 正确记录用时与时间戳
*/

if (isset($_POST['game_ajax_action'])) {
    if (!wp_verify_nonce($_POST['game_nonce'], 'game_actions')) {
        wp_send_json_error('验证失败');
        exit;
    }

    $action = $_POST['game_ajax_action'];
    global $wpdb;

    // 加载题目(ID → 真实URL)
    if ($action === 'load_questions') {
        $quiz_id = intval($_POST['quiz_id'] ?? 0);
        if ($quiz_id <= 0) { wp_send_json_error('试卷ID错误'); exit; }

        $questions = $wpdb->get_results($wpdb->prepare(
            "SELECT question_text AS text, options, answer, question_image AS image_id
             FROM {$wpdb->prefix}edu_questions 
             WHERE quiz_id = %d AND game_id = 3 
             ORDER BY id",
            $quiz_id
        ));

        if (empty($questions)) { wp_send_json_error('没有题目'); exit; }

        foreach ($questions as &$q) {
            $q->image = (!empty($q->image_id) && is_numeric($q->image_id))
                ? wp_get_attachment_url((int)$q->image_id) ?: ''
                : '';

            $opts = maybe_unserialize($q->options);
            if (!is_array($opts)) $opts = json_decode($q->options, true) ?: [];
            $new_opts = [];
            foreach ($opts as $opt) {
                if (is_string($opt)) {
                    $new_opts[] = ['text' => $opt];
                } else {
                    if (isset($opt['image']) && is_numeric($opt['image'])) {
                        $opt['image'] = wp_get_attachment_url((int)$opt['image']) ?: '';
                    }
                    $new_opts[] = $opt;
                }
            }
            $q->options = $new_opts;

            $q->answer = is_numeric($q->answer) ? (int)$q->answer
                : array_search($q->answer, array_column($new_opts, 'text'));
            $q->answer = ($q->answer === false) ? 0 : (int)$q->answer;
        }

        shuffle($questions);
        $questions = array_slice($questions, 0, 5);
        wp_send_json_success(['questions' => $questions]);
        exit;
    }

    // 保存成绩 —— 修复 time 与 timestamp
    if ($action === 'save_game_score') {
        $start_ms = intval($_POST['start_time'] ?? 0);                    // 前端传的开始时间戳(毫秒)
        $end_ms   = intval($_POST['end_time'] ?? ($_SERVER['REQUEST_TIME'] * 1000));
        $used_seconds = max(1, round(($end_ms - $start_ms) / 1000));       // 答题用时(秒)

        $wpdb->insert("{$wpdb->prefix}edu_scores", [
            'student_id' => intval($_POST['student_id'] ?? 0),
            'quiz_id'    => intval($_POST['quiz_id'] ?? 0),
            'game_id'    => 3,
            'score'      => intval($_POST['score'] ?? 0),
            'time'       => $used_seconds,                    // 正确写入用时(秒)
            'timestamp'  => current_time('mysql'),            // 正确写入 datetime
            'errors'     => sanitize_text_field($_POST['errors'] ?? ''),
            'difficulty' => 1,
        ]);

        wp_send_json_success(['used_seconds' => $used_seconds]);
        exit;
    }
}

get_header();

if (!is_user_logged_in()) {
    echo '<div class="container mt-5"><div class="alert alert-warning">请先登录</div></div>';
    get_footer(); exit;
}

global $wpdb;
$user = wp_get_current_user();
$student = $wpdb->get_row($wpdb->prepare(
    "SELECT student_id FROM {$wpdb->prefix}edu_students WHERE user_id = %d", $user->ID
));
if (!$student) {
    echo '<div class="alert alert-danger">未找到学生信息</div>';
    get_footer(); exit;
}

$quiz_id = intval($_GET['quiz_id'] ?? 0);
if ($quiz_id <= 0) {
    echo '<div class="alert alert-danger">无效的试卷ID</div>';
    get_footer(); exit;
}

$quiz_name = $wpdb->get_var($wpdb->prepare(
    "SELECT quiz_name FROM {$wpdb->prefix}edu_quizzes WHERE id = %d", $quiz_id
));
$quiz_name = $quiz_name ? $quiz_name : '未命名试卷';
?>

<style>
.site-header, header, .main-navigation, .ast-main-header-wrap {position:relative!important;z-index:99999!important;background:white!important;}
.main-navigation a, .site-title a {color:#333!important;font-weight:bold!important;}

body,html{margin:0;padding:0;height:100%;background:#0a001f;color:#fff;overflow:hidden;font-family:"Microsoft YaHei",sans-serif;}
#game-app{position:relative;width:100%;height:100vh;background:linear-gradient(135deg,#1a0033,#000428);}

#start-screen{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;}
#result-screen{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:40px 20px;overflow-y:auto;}

h1{font-size:4.2em;margin:0 0 20px;text-shadow:0 0 30px #a855ff;color:#fff;}
.difficulty-btn{font-size:1.8em;padding:25px 60px;margin:12px;background:#8e44ff;color:#fff;border:none;border-radius:20px;box-shadow:0 10px 40px rgba(142,68,255,.6);cursor:pointer;}
.difficulty-btn:hover{background:#a855ff;transform:translateY(-6px);}

#fixed-question{position:fixed;bottom:120px;left:0;right:0;text-align:center;z-index:10;pointer-events:none;}
#fixed-question h2{font-size:2.6em;margin:15px 0;text-shadow:0 0 25px #6affd4;color:#6affd4;font-weight:bold;}
#fixed-question img{max-height:320px;border-radius:20px;box-shadow:0 20px 50px rgba(0,0,0,.9);border:4px solid #8e44ff;}

.wave{position:absolute;width:100%;top:-300px;z-index:999;text-align:center;}
.options{display:flex;flex-wrap:wrap;gap:28px;justify-content:center;padding:32px 0;background:rgba(0,0,0,.25);border-radius:22px;box-shadow:0 15px 40px rgba(0,0,0,.8);}
.option-btn{min-width:180px;padding:25px 45px;background:rgba(255,255,255,0.25)!important;border:4px solid transparent;border-radius:25px;cursor:pointer;font-size:2em!important;font-weight:bold!important;color:#fff!important;text-shadow:0 0 15px #a855ff;transition:all .3s;box-shadow:0 15px40px rgba(0,0,0,.8);}
.option-btn:hover{background:rgba(255,255,255,0.4)!important;transform:scale(1.15);}
.option-btn.correct{border-color:#0ff!important;background:rgba(0,255,255,0.6)!important;transform:scale(1.3);}
.option-btn.wrong{border-color:#f33!important;background:rgba(255,50,50,0.7)!important;}

#missed-zone{position:fixed;bottom:0;left:0;right:0;background:rgba(200,0,50,.9);padding:20px 0;min-height:100px;border-top:8px solid #ff0033;text-align:center;z-index:20;}
.missed-item{display:inline-block;margin:10px 18px;padding:14px 30px;background:#900;color:#fff;border-radius:14px;font-size:1.3em;animation:fallIn .6s;}
#score-board{position:fixed;top:15px;left:50%;transform:translateX(-50%);font-size:1.9em;background:rgba(0,0,0,.7);padding:12px 40px;border-radius:16px;z-index:30;white-space:nowrap;}
#timer-bar{position:fixed;top:0;left:0;height:10px;background:#00ff9d;width:100%;z-index:31;}
#game-container{position:relative;width:100%;height:100%;overflow:hidden;}
@keyframes fallIn{from{transform:translateY(-100px);opacity:0;}to{transform:none;opacity:1;}}
</style>

<div id="game-app"></div>

<script>
const currentPageUrl = '<?php echo get_permalink(); ?>';
const quizId = <?php echo $quiz_id; ?>;
const quizName = <?php echo json_encode($quiz_name); ?>;

let questions = [], currentQ = 0, baseScore = 0, records = [], currentTimeout = null;
let gameDuration = 18, maxScore = 100;
let gameStartTime = 0;   // 全局变量:游戏开始时间戳(毫秒)

document.addEventListener('DOMContentLoaded', () => {
    document.getElementById('game-app').innerHTML = `
        <div id="start-screen">
            <h1>认知闪击塔防<br>
                <small style="font-size:0.45em;color:#a0a0ff;">Cognitive Blitz Defense</small>
            </h1>
            <p style="font-size:2.4em;margin:30px 0;color:#0ff;">${quizName}</p>
            <p style="font-size:1.8em;color:#fff;margin-bottom:40px;">请选择难度(共5题)</p>
            <button onclick="startGame('easy')"    class="difficulty-btn" style="background:#4CAF50;">简单模式<br><span style="font-size:0.7em;">下降慢 · 全对 80 分</span></button>
            <button onclick="startGame('medium')"  class="difficulty-btn" style="background:#FF9800;">中等模式<br><span style="font-size:0.7em;">下降中 · 全对 90 分</span></button>
            <button onclick="startGame('hard')"   class="difficulty-btn" style="background:#F44336;">困难模式<br><span style="font-size:0.7em;">下降快 · 全对 100 分</span></button>
        </div>
    `;
});

function startGame(diff) {
    const config = { easy:{time:24,score:80}, medium:{time:20,score:90}, hard:{time:18,score:100} };
    gameDuration = config[diff].time;
    maxScore = config[diff].score;

    document.getElementById('game-app').innerHTML = '<div id="start-screen"><h1 style="color:#0ff;">加载中...</h1></div>';

    fetch(currentPageUrl, {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: new URLSearchParams({
            game_ajax_action: 'load_questions',
            game_nonce: '<?php echo wp_create_nonce("game_actions"); ?>',
            quiz_id: quizId
        })
    })
    .then(r => r.json())
    .then(res => {
        if (!res.success) throw new Error(res.data || '加载失败');
        questions = res.data.questions;
        baseScore = 0; currentQ = 0; records = [];

        gameStartTime = Date.now();   // 记录游戏正式开始时间(毫秒)

        document.getElementById('game-app').innerHTML = `
            <div id="score-board">得分:0 / ${maxScore}</div>
            <div id="timer-bar"></div>
            <div id="game-container"></div>
            <div id="fixed-question"></div>
            <div id="missed-zone">漏防区:本题0分!</div>
        `;
        startNextQuestion();
    })
    .catch(err => {
        document.getElementById('game-app').innerHTML = `<div style="color:red;font-size:2em;padding:100px;">${err.message}</div>`;
    });
}

/* 下面的 startNextQuestion、addMissed、endGame 函数保持不变,只在 endGame 中多传时间参数 */
function startNextQuestion() {
    if (currentQ >= questions.length) { endGame(); return; }

    const q = questions[currentQ];
    const perScore = maxScore / questions.length;

    document.getElementById('game-container').innerHTML = '';
    document.getElementById('fixed-question').innerHTML = `
        <h2>第 ${currentQ+1}/${questions.length} 题:${q.text}</h2>
        ${q.image ? `<img src="${q.image}?t=${Date.now()}" style="max-height:320px;border-radius:20px;box-shadow:0 20px 50px rgba(0,0,0,.9);border:4px solid #8e44ff;" onerror="this.style.display='none'">` : ''}
    `;

    const shuffled = q.options.map((opt,i) => ({
        opt: opt.text || opt,
        isImage: !!opt.image,
        image: opt.image || '',
        i, r: Math.random()
    })).sort((a,b) => a.r - b.r);

    const correctIdx = shuffled.findIndex(x => x.i === q.answer);

    const wave = document.createElement('div');
    wave.className = 'wave';
    wave.innerHTML = `<div class="options">
        ${shuffled.map((x,i) => `
            <button class="option-btn" data-i="${i}">
                ${x.isImage && x.image ? `<img src="${x.image}?t=${Date.now()}" style="max-height:80px;" onerror="this.style.display='none';this.parentNode.innerHTML='${x.opt}'">` : x.opt}
            </button>
        `).join('')}
    </div>`;

    document.getElementById('game-container').appendChild(wave);
    wave.offsetHeight;
    wave.style.top = '-300px';

    requestAnimationFrame(() => {
        wave.style.transition = `top ${gameDuration}s linear`;
        wave.style.top = 'calc(100% + 500px)';
    });

    const timerBar = document.getElementById('timer-bar');
    timerBar.style.width = '100%';
    requestAnimationFrame(() => {
        timerBar.style.transition = `width ${gameDuration}s linear`;
        timerBar.style.width = '0%';
    });

    clearTimeout(currentTimeout);
    currentTimeout = setTimeout(() => {
        if (document.body.contains(wave)) {
            addMissed("漏题");
            records.push({qtext:q.text, user:"(未答)", correct:shuffled[correctIdx].opt, result:"missed"});
            currentQ++; startNextQuestion();
        }
    }, gameDuration * 1000);

    wave.querySelectorAll('.option-btn').forEach(btn => {
        btn.onclick = function() {
            if (this.classList.contains('correct') || this.classList.contains('wrong')) return;
            clearTimeout(currentTimeout);

            const clicked = +this.dataset.i;
            const correct = clicked === correctIdx;

            if (correct) {
                baseScore += perScore;
                this.classList.add('correct');
            } else {
                this.classList.add('wrong');
                addMissed(shuffled[clicked].opt + " 选错");
            }

            records.push({
                qtext: q.text,
                user: shuffled[clicked].opt || "(图片)",
                correct: shuffled[correctIdx].opt || "(图片)",
                result: correct ? "correct" : "wrong"
            });

            document.getElementById('score-board').textContent = `得分:${Math.round(baseScore)} / ${maxScore}`;

            setTimeout(() => {
                if (document.body.contains(wave)) wave.remove();
                currentQ++; startNextQuestion();
            }, 700);
        };
    });
}

function addMissed(t) {
    const s = document.createElement('span');
    s.className = 'missed-item';
    s.textContent = t;
    document.getElementById('missed-zone').appendChild(s);
}

function endGame() {
    const final = Math.round(baseScore);
    const endTime = Date.now();

    // 保存成绩 —— 正确写入 time(秒) + timestamp(datetime)
    fetch(currentPageUrl, {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: new URLSearchParams({
            game_ajax_action: 'save_game_score',
            game_nonce: '<?php echo wp_create_nonce("game_actions"); ?>',
            student_id: <?php echo $student->student_id; ?>,
            quiz_id: quizId,
            score: final,
            errors: JSON.stringify(records),
            start_time: gameStartTime,    // 开始时间戳(毫秒)
            end_time: endTime             // 结束时间戳(毫秒)
        })
    });

    // 错题回顾
    let review = '';
    const wrongs = records.filter(r => r.result !== 'correct');
    if (wrongs.length === 0) {
        review = '<p style="color:#0ff;font-size:2.6em;margin:50px 0;">全对!完美!</p>';
    } else {
        review = '<div style="width:90%;max-width:900px;margin:30px auto;"><h2 style="color:#ff0;">错题回顾</h2>';
        wrongs.forEach((item,i) => {
            review += `
                <div style="background:rgba(255,255,255,0.1);margin:20px 0;padding:25px;border-radius:15px;text-align:left;">
                    <p style="color:#ff6b6b;font-size:1.4em;margin:0 0 10px;"><strong>第${i+1}题:</strong>${item.qtext}</p>
                    <p style="color:#ff9800;margin:5px 0;">你选:${item.user}</p>
                    <p style="color:#4caf50;margin:5px 0;">正确答案:${item.correct}</p>
                </div>`;
        });
        review += '</div>';
    }

    document.getElementById('game-app').innerHTML = `
        <div id="result-screen">
            <h1 style="font-size:4.8em;color:#0ff;margin:20px 0;">游戏结束!</h1>
            <p style="font-size:6.5em;color:#0ff;margin:40px 0;">${final} / ${maxScore} 分</p>
            ${review}
            <button onclick="location.reload()" style="font-size:2.3em;padding:20px 120px;margin-top:40px;background:#8e44ff;border:none;border-radius:25px;cursor:pointer;">
                再来一局
            </button>
        </div>
    `;
}
</script>

<?php get_footer(); ?>