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(); ?>