986 lines
31 KiB
HTML
986 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>字母XYZ单词PK赛 - 中班英语</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
font-family: 'Comic Sans MS', '微软雅黑', sans-serif;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: clamp(10px, 2.2vw, 24px);
|
|
}
|
|
|
|
.game-container {
|
|
background: white;
|
|
border-radius: clamp(18px, 3vw, 30px);
|
|
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
|
|
width: 100%;
|
|
max-width: 1180px;
|
|
min-height: min(760px, calc(100vh - clamp(20px, 4.4vw, 48px)));
|
|
padding: clamp(18px, 3vw, 34px);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.game-container::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50px;
|
|
right: -50px;
|
|
width: 200px;
|
|
height: 200px;
|
|
background: #ffeb3b;
|
|
border-radius: 50%;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.game-container::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -80px;
|
|
left: -60px;
|
|
width: 250px;
|
|
height: 250px;
|
|
background: #4fc3f7;
|
|
border-radius: 50%;
|
|
opacity: 0.2;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
margin-bottom: clamp(12px, 2vw, 22px);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.title {
|
|
font-size: clamp(24px, 3vw, 36px);
|
|
color: #ff6b9d;
|
|
margin-bottom: 8px;
|
|
text-shadow: 2px 2px 0 #fff, 4px 4px 0 #ffd1dc;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: clamp(15px, 1.8vw, 20px);
|
|
color: #666;
|
|
}
|
|
|
|
.play-layout {
|
|
display: grid;
|
|
grid-template-columns: minmax(280px, 0.9fr) minmax(360px, 1.25fr);
|
|
gap: clamp(18px, 3vw, 34px);
|
|
align-items: stretch;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.left-panel,
|
|
.right-panel {
|
|
min-width: 0;
|
|
}
|
|
|
|
.left-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.right-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* 队伍分数栏 */
|
|
.team-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: clamp(10px, 1.8vw, 18px);
|
|
margin-bottom: clamp(12px, 2vw, 18px);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.team-card {
|
|
flex: 1;
|
|
padding: clamp(10px, 1.8vw, 16px);
|
|
border-radius: clamp(14px, 2vw, 20px);
|
|
text-align: center;
|
|
transition: all 0.3s;
|
|
border: clamp(3px, 0.45vw, 4px) solid transparent;
|
|
}
|
|
|
|
.team-red {
|
|
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
|
|
}
|
|
|
|
.team-blue {
|
|
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
|
|
}
|
|
|
|
.team-card.active {
|
|
transform: scale(1.03);
|
|
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
}
|
|
|
|
.team-red.active {
|
|
border-color: #ef5350;
|
|
}
|
|
|
|
.team-blue.active {
|
|
border-color: #42a5f5;
|
|
}
|
|
|
|
.team-name {
|
|
font-size: clamp(16px, 2vw, 22px);
|
|
font-weight: bold;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.team-red .team-name {
|
|
color: #c62828;
|
|
}
|
|
|
|
.team-blue .team-name {
|
|
color: #1565c0;
|
|
}
|
|
|
|
.team-score {
|
|
font-size: clamp(28px, 4vw, 44px);
|
|
font-weight: bold;
|
|
}
|
|
|
|
.team-red .team-score {
|
|
color: #e53935;
|
|
}
|
|
|
|
.team-blue .team-score {
|
|
color: #1e88e5;
|
|
}
|
|
|
|
.vs-text {
|
|
font-size: clamp(20px, 2.8vw, 30px);
|
|
font-weight: bold;
|
|
color: #ff9800;
|
|
text-shadow: 2px 2px 0 #fff;
|
|
}
|
|
|
|
.current-turn {
|
|
text-align: center;
|
|
font-size: clamp(17px, 2vw, 22px);
|
|
font-weight: bold;
|
|
margin-bottom: clamp(10px, 1.7vw, 16px);
|
|
padding: clamp(9px, 1.5vw, 13px);
|
|
border-radius: 15px;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.turn-red {
|
|
background: #ffebee;
|
|
color: #c62828;
|
|
}
|
|
|
|
.turn-blue {
|
|
background: #e3f2fd;
|
|
color: #1565c0;
|
|
}
|
|
|
|
.question-area {
|
|
text-align: center;
|
|
margin-bottom: clamp(14px, 2vw, 24px);
|
|
position: relative;
|
|
z-index: 1;
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
|
|
.question-prompt {
|
|
font-size: clamp(16px, 1.7vw, 20px);
|
|
color: #555;
|
|
margin-bottom: clamp(8px, 1.4vw, 12px);
|
|
}
|
|
|
|
.word-display {
|
|
font-size: clamp(42px, 7vw, 76px);
|
|
font-weight: bold;
|
|
color: #4a90e2;
|
|
margin: clamp(10px, 2vw, 18px) 0;
|
|
letter-spacing: 2px;
|
|
text-shadow: 3px 3px 0 #e3f2fd;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.sound-btn {
|
|
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
|
|
border: none;
|
|
width: clamp(58px, 6vw, 76px);
|
|
height: clamp(58px, 6vw, 76px);
|
|
border-radius: 50%;
|
|
font-size: clamp(24px, 3vw, 34px);
|
|
cursor: pointer;
|
|
box-shadow: 0 6px 15px rgba(255, 107, 157, 0.4);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.sound-btn:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.45;
|
|
box-shadow: none;
|
|
transform: none;
|
|
}
|
|
|
|
.sound-btn:hover {
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.sound-btn:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.sound-btn:disabled:hover,
|
|
.sound-btn:disabled:active {
|
|
transform: none;
|
|
}
|
|
|
|
.speech-tip {
|
|
min-height: 24px;
|
|
margin-top: clamp(8px, 1.4vw, 12px);
|
|
font-size: clamp(13px, 1.4vw, 16px);
|
|
color: #7a7a7a;
|
|
}
|
|
|
|
.speech-tip.error {
|
|
color: #d84315;
|
|
}
|
|
|
|
.options-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: clamp(14px, 2vw, 22px);
|
|
margin-bottom: clamp(12px, 2vw, 20px);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.option-card {
|
|
background: white;
|
|
border: clamp(3px, 0.45vw, 4px) solid #e0e0e0;
|
|
border-radius: clamp(14px, 2vw, 20px);
|
|
min-height: clamp(126px, 19vh, 184px);
|
|
padding: clamp(16px, 2.4vw, 28px) clamp(10px, 1.5vw, 16px);
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
font-size: clamp(22px, 3vw, 34px);
|
|
font-weight: bold;
|
|
color: #333;
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.option-card:hover {
|
|
border-color: #4fc3f7;
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 10px 20px rgba(79, 195, 247, 0.3);
|
|
}
|
|
|
|
.option-card.correct {
|
|
border-color: #66bb6a;
|
|
background: #e8f5e9;
|
|
animation: correct-bounce 0.5s ease;
|
|
}
|
|
|
|
.option-card.wrong {
|
|
border-color: #ef5350;
|
|
background: #ffebee;
|
|
animation: wrong-shake 0.5s ease;
|
|
}
|
|
|
|
.option-emoji {
|
|
font-size: clamp(42px, 6vw, 70px);
|
|
display: block;
|
|
margin-bottom: clamp(8px, 1.4vw, 12px);
|
|
}
|
|
|
|
@keyframes correct-bounce {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.1); }
|
|
}
|
|
|
|
@keyframes wrong-shake {
|
|
0%, 100% { transform: translateX(0); }
|
|
25% { transform: translateX(-10px); }
|
|
75% { transform: translateX(10px); }
|
|
}
|
|
|
|
.feedback {
|
|
text-align: center;
|
|
font-size: clamp(18px, 2.2vw, 26px);
|
|
font-weight: bold;
|
|
min-height: 40px;
|
|
margin-bottom: clamp(10px, 1.7vw, 14px);
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.feedback.correct {
|
|
color: #66bb6a;
|
|
}
|
|
|
|
.feedback.wrong {
|
|
color: #ef5350;
|
|
}
|
|
|
|
.next-btn {
|
|
display: block;
|
|
margin: 0 auto;
|
|
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
|
border: none;
|
|
padding: clamp(12px, 1.8vw, 16px) clamp(34px, 5vw, 54px);
|
|
font-size: clamp(16px, 1.8vw, 20px);
|
|
border-radius: 30px;
|
|
cursor: pointer;
|
|
color: #555;
|
|
font-weight: bold;
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
|
transition: all 0.2s;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.next-btn:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.start-screen, .end-screen {
|
|
text-align: center;
|
|
padding: clamp(32px, 6vw, 70px) clamp(18px, 3vw, 28px);
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.big-emoji {
|
|
font-size: clamp(74px, 10vw, 120px);
|
|
margin-bottom: clamp(16px, 2.5vw, 24px);
|
|
animation: float 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes float {
|
|
0%, 100% { transform: translateY(0); }
|
|
50% { transform: translateY(-15px); }
|
|
}
|
|
|
|
.start-btn {
|
|
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
|
|
border: none;
|
|
padding: clamp(15px, 2.2vw, 20px) clamp(42px, 6vw, 62px);
|
|
font-size: clamp(19px, 2.4vw, 25px);
|
|
border-radius: 40px;
|
|
cursor: pointer;
|
|
color: white;
|
|
font-weight: bold;
|
|
box-shadow: 0 8px 25px rgba(255, 107, 157, 0.4);
|
|
transition: all 0.3s;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.start-btn:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.letter-badge {
|
|
display: inline-block;
|
|
width: clamp(38px, 4.5vw, 50px);
|
|
height: clamp(38px, 4.5vw, 50px);
|
|
line-height: clamp(38px, 4.5vw, 50px);
|
|
border-radius: 12px;
|
|
font-size: clamp(22px, 2.7vw, 29px);
|
|
font-weight: bold;
|
|
color: white;
|
|
margin: 0 4px;
|
|
}
|
|
|
|
.letter-x { background: #ff7043; }
|
|
.letter-y { background: #ffca28; }
|
|
.letter-z { background: #42a5f5; }
|
|
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.progress-bar {
|
|
width: 100%;
|
|
height: clamp(9px, 1vw, 12px);
|
|
background: #f0f0f0;
|
|
border-radius: 10px;
|
|
overflow: hidden;
|
|
margin-bottom: clamp(12px, 1.8vw, 18px);
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #66bb6a, #43a047);
|
|
width: 0%;
|
|
transition: width 0.5s ease;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.winner-text {
|
|
font-size: clamp(28px, 4vw, 44px);
|
|
font-weight: bold;
|
|
margin: 20px 0;
|
|
animation: pulse 1s ease-in-out infinite;
|
|
}
|
|
|
|
.winner-red {
|
|
color: #e53935;
|
|
}
|
|
|
|
.winner-blue {
|
|
color: #1e88e5;
|
|
}
|
|
|
|
.winner-draw {
|
|
color: #ff9800;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.05); }
|
|
}
|
|
|
|
.final-scores {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: clamp(28px, 5vw, 56px);
|
|
margin: clamp(20px, 3vw, 30px) 0;
|
|
font-size: clamp(18px, 2vw, 22px);
|
|
}
|
|
|
|
@media (max-width: 820px) {
|
|
.game-container {
|
|
min-height: auto;
|
|
}
|
|
|
|
.play-layout {
|
|
grid-template-columns: 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.question-area {
|
|
min-height: 210px;
|
|
}
|
|
|
|
.option-card {
|
|
min-height: 118px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
body {
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.options-grid {
|
|
grid-template-columns: 1fr;
|
|
gap: 12px;
|
|
}
|
|
|
|
.team-bar {
|
|
gap: 8px;
|
|
}
|
|
|
|
.vs-text {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.option-card {
|
|
min-height: 96px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="game-container">
|
|
<!-- 开始界面 -->
|
|
<div id="startScreen" class="start-screen">
|
|
<div class="big-emoji">🦊 VS 🦓</div>
|
|
<h1 class="title">字母XYZ单词PK赛</h1>
|
|
<p class="subtitle">
|
|
<span class="letter-badge letter-x">X</span>
|
|
<span class="letter-badge letter-y">Y</span>
|
|
<span class="letter-badge letter-z">Z</span>
|
|
小组轮流对战
|
|
</p>
|
|
<p style="margin-top: 20px; color: #888; font-size: 16px;">
|
|
红队蓝队轮流答题,每队一题,答对加10分,答错不扣分!
|
|
</p>
|
|
<button class="start-btn" onclick="startGame()">🎮 开始PK</button>
|
|
</div>
|
|
|
|
<!-- 游戏界面 -->
|
|
<div id="gameScreen" class="hidden">
|
|
<div class="header">
|
|
<h1 class="title">单词PK大作战</h1>
|
|
</div>
|
|
|
|
<div class="play-layout">
|
|
<div class="left-panel">
|
|
<!-- 队伍分数 -->
|
|
<div class="team-bar">
|
|
<div id="teamRedCard" class="team-card team-red active">
|
|
<div class="team-name">🔴 红队</div>
|
|
<div id="scoreRed" class="team-score">0</div>
|
|
</div>
|
|
<div class="vs-text">VS</div>
|
|
<div id="teamBlueCard" class="team-card team-blue">
|
|
<div class="team-name">🔵 蓝队</div>
|
|
<div id="scoreBlue" class="team-score">0</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 当前回合提示 -->
|
|
<div id="currentTurn" class="current-turn turn-red">
|
|
🔴 红队请作答
|
|
</div>
|
|
|
|
<div class="progress-bar">
|
|
<div id="progressFill" class="progress-fill"></div>
|
|
</div>
|
|
|
|
<div class="question-area">
|
|
<p class="question-prompt">听一听,这是哪个单词?</p>
|
|
<button id="soundBtn" class="sound-btn" onclick="playWordSound()" aria-label="重新朗读当前单词">🔊</button>
|
|
<div id="wordDisplay" class="word-display">box</div>
|
|
<div id="speechTip" class="speech-tip">准备朗读中...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="right-panel">
|
|
<div id="optionsGrid" class="options-grid">
|
|
<!-- 选项卡片由JS生成 -->
|
|
</div>
|
|
|
|
<div id="feedback" class="feedback"></div>
|
|
|
|
<button id="nextBtn" class="next-btn hidden" onclick="nextQuestion()">下一题 ➡️</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 结束界面 -->
|
|
<div id="endScreen" class="end-screen hidden">
|
|
<div class="big-emoji" id="endEmoji">🏆</div>
|
|
<h1 class="title">PK结束!</h1>
|
|
<div id="winnerText" class="winner-text">红队获胜!</div>
|
|
|
|
<div class="final-scores">
|
|
<div>
|
|
<div style="color: #e53935; font-weight: bold;">🔴 红队</div>
|
|
<div id="finalRed" style="font-size: 32px; font-weight: bold; color: #e53935;">0</div>
|
|
</div>
|
|
<div>
|
|
<div style="color: #1e88e5; font-weight: bold;">🔵 蓝队</div>
|
|
<div id="finalBlue" style="font-size: 32px; font-weight: bold; color: #1e88e5;">0</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="start-btn" onclick="restartGame()">🔄 再来一局</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 12个核心单词完整列表
|
|
const words = [
|
|
{ word: 'box', emoji: '📦', letter: 'x' },
|
|
{ word: 'fox', emoji: '🦊', letter: 'x' },
|
|
{ word: 'ox', emoji: '🐂', letter: 'x' },
|
|
{ word: 'ax', emoji: '🪓', letter: 'x' },
|
|
{ word: 'yarn', emoji: '🧶', letter: 'y' },
|
|
{ word: 'yo-yo', emoji: '🪀', letter: 'y' },
|
|
{ word: 'yam', emoji: '🍠', letter: 'y' },
|
|
{ word: 'yellow', emoji: '💛', letter: 'y' },
|
|
{ word: 'zoo', emoji: '🏞️', letter: 'z' },
|
|
{ word: 'zero', emoji: '0️⃣', letter: 'z' },
|
|
{ word: 'zebra', emoji: '🦓', letter: 'z' },
|
|
{ word: 'zipper', emoji: '🧥', letter: 'z' }
|
|
];
|
|
|
|
let currentQuestion = 0;
|
|
let scoreRed = 0;
|
|
let scoreBlue = 0;
|
|
let currentTeam = 'red';
|
|
let shuffledWords = [];
|
|
let answered = false;
|
|
let audioContext = null;
|
|
let speechEngine = null;
|
|
let speechReady = false;
|
|
let speechLoaderPromise = null;
|
|
let autoSpeakTimer = null;
|
|
let pendingSpeechWord = null;
|
|
let englishVoice = null;
|
|
|
|
// 初始化音效上下文
|
|
function initAudio() {
|
|
if (!audioContext) {
|
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
}
|
|
}
|
|
|
|
function updateSpeechTip(message, isError = false) {
|
|
const tip = document.getElementById('speechTip');
|
|
if (!tip) return;
|
|
tip.textContent = message;
|
|
tip.className = isError ? 'speech-tip error' : 'speech-tip';
|
|
}
|
|
|
|
function updateSoundButtonEnabled(enabled) {
|
|
const btn = document.getElementById('soundBtn');
|
|
if (btn) btn.disabled = !enabled;
|
|
}
|
|
|
|
function loadSpeechEngine() {
|
|
if (!('speechSynthesis' in window) || !('SpeechSynthesisUtterance' in window)) {
|
|
return Promise.reject(new Error('当前浏览器不支持英文朗读'));
|
|
}
|
|
|
|
if (speechLoaderPromise) return speechLoaderPromise;
|
|
|
|
speechLoaderPromise = new Promise((resolve) => {
|
|
const pickVoice = () => {
|
|
const voices = window.speechSynthesis.getVoices();
|
|
englishVoice =
|
|
voices.find(voice => /^en[-_]?US/i.test(voice.lang)) ||
|
|
voices.find(voice => /^en/i.test(voice.lang)) ||
|
|
voices[0] ||
|
|
null;
|
|
resolve(window.speechSynthesis);
|
|
};
|
|
|
|
pickVoice();
|
|
if (!englishVoice) {
|
|
window.speechSynthesis.onvoiceschanged = pickVoice;
|
|
setTimeout(pickVoice, 1000);
|
|
}
|
|
});
|
|
|
|
return speechLoaderPromise;
|
|
}
|
|
|
|
function initSpeech() {
|
|
speechReady = false;
|
|
updateSoundButtonEnabled(false);
|
|
updateSpeechTip('正在加载英文朗读模块...', false);
|
|
|
|
loadSpeechEngine()
|
|
.then(engine => {
|
|
speechEngine = engine;
|
|
if (!speechEngine) {
|
|
throw new Error('英文朗读未初始化');
|
|
}
|
|
|
|
speechReady = true;
|
|
updateSoundButtonEnabled(true);
|
|
updateSpeechTip('英文朗读已就绪,点击喇叭可重新朗读。');
|
|
|
|
if (pendingSpeechWord) {
|
|
const word = pendingSpeechWord;
|
|
pendingSpeechWord = null;
|
|
speakWord(word);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
speechReady = false;
|
|
updateSoundButtonEnabled(false);
|
|
updateSpeechTip('英文朗读模块加载失败,请直接带读单词。', true);
|
|
});
|
|
}
|
|
|
|
function stopSpeech() {
|
|
clearTimeout(autoSpeakTimer);
|
|
pendingSpeechWord = null;
|
|
if (speechEngine && typeof speechEngine.cancel === 'function') {
|
|
speechEngine.cancel();
|
|
}
|
|
}
|
|
|
|
// 答对音效
|
|
function playCorrectSound() {
|
|
initAudio();
|
|
const osc = audioContext.createOscillator();
|
|
const gain = audioContext.createGain();
|
|
osc.connect(gain);
|
|
gain.connect(audioContext.destination);
|
|
|
|
osc.frequency.setValueAtTime(523.25, audioContext.currentTime);
|
|
osc.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1);
|
|
osc.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2);
|
|
|
|
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.4);
|
|
|
|
osc.start(audioContext.currentTime);
|
|
osc.stop(audioContext.currentTime + 0.4);
|
|
}
|
|
|
|
// 答错音效
|
|
function playWrongSound() {
|
|
initAudio();
|
|
const osc = audioContext.createOscillator();
|
|
const gain = audioContext.createGain();
|
|
osc.connect(gain);
|
|
gain.connect(audioContext.destination);
|
|
|
|
osc.frequency.setValueAtTime(200, audioContext.currentTime);
|
|
osc.frequency.setValueAtTime(150, audioContext.currentTime + 0.15);
|
|
|
|
gain.gain.setValueAtTime(0.3, audioContext.currentTime);
|
|
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
|
|
|
|
osc.start(audioContext.currentTime);
|
|
osc.stop(audioContext.currentTime + 0.3);
|
|
}
|
|
|
|
// 单词标准发音(第三方英文 TTS,尽量保证跨设备可用)
|
|
function speakWord(word) {
|
|
if (!speechEngine || !speechReady) {
|
|
pendingSpeechWord = word;
|
|
updateSpeechTip('英文朗读模块正在准备中,稍后会自动播放。', false);
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
speechEngine.cancel();
|
|
clearTimeout(autoSpeakTimer);
|
|
|
|
const utterance = new SpeechSynthesisUtterance(word);
|
|
utterance.lang = englishVoice?.lang || 'en-US';
|
|
utterance.voice = englishVoice;
|
|
utterance.rate = 0.85;
|
|
utterance.pitch = 1;
|
|
utterance.volume = 1;
|
|
speechEngine.speak(utterance);
|
|
|
|
updateSpeechTip(`正在朗读:${word}`);
|
|
return true;
|
|
} catch (err) {
|
|
pendingSpeechWord = word;
|
|
updateSpeechTip('英文朗读暂时不可用,请直接带读单词。', true);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 数组随机打乱
|
|
function shuffleArray(array) {
|
|
const newArray = [...array];
|
|
for (let i = newArray.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
|
|
}
|
|
return newArray;
|
|
}
|
|
|
|
// 切换答题队伍
|
|
function switchTeam() {
|
|
currentTeam = currentTeam === 'red' ? 'blue' : 'red';
|
|
updateTeamDisplay();
|
|
}
|
|
|
|
// 更新队伍高亮状态
|
|
function updateTeamDisplay() {
|
|
const redCard = document.getElementById('teamRedCard');
|
|
const blueCard = document.getElementById('teamBlueCard');
|
|
const turnText = document.getElementById('currentTurn');
|
|
|
|
if (currentTeam === 'red') {
|
|
redCard.classList.add('active');
|
|
blueCard.classList.remove('active');
|
|
turnText.textContent = '🔴 红队请作答';
|
|
turnText.className = 'current-turn turn-red';
|
|
} else {
|
|
blueCard.classList.add('active');
|
|
redCard.classList.remove('active');
|
|
turnText.textContent = '🔵 蓝队请作答';
|
|
turnText.className = 'current-turn turn-blue';
|
|
}
|
|
}
|
|
|
|
// 开始游戏
|
|
function startGame() {
|
|
initAudio();
|
|
stopSpeech();
|
|
initSpeech();
|
|
shuffledWords = shuffleArray(words);
|
|
currentQuestion = 0;
|
|
scoreRed = 0;
|
|
scoreBlue = 0;
|
|
currentTeam = 'red';
|
|
|
|
document.getElementById('scoreRed').textContent = 0;
|
|
document.getElementById('scoreBlue').textContent = 0;
|
|
|
|
document.getElementById('startScreen').classList.add('hidden');
|
|
document.getElementById('gameScreen').classList.remove('hidden');
|
|
document.getElementById('endScreen').classList.add('hidden');
|
|
|
|
updateTeamDisplay();
|
|
loadQuestion();
|
|
}
|
|
|
|
// 加载单道题目
|
|
function loadQuestion() {
|
|
answered = false;
|
|
const currentWord = shuffledWords[currentQuestion];
|
|
stopSpeech();
|
|
|
|
document.getElementById('wordDisplay').textContent = currentWord.word;
|
|
document.getElementById('feedback').textContent = '';
|
|
document.getElementById('feedback').className = 'feedback';
|
|
document.getElementById('nextBtn').classList.add('hidden');
|
|
|
|
// 更新进度条
|
|
const progress = (currentQuestion / words.length) * 100;
|
|
document.getElementById('progressFill').style.width = `${progress}%`;
|
|
|
|
// 生成3个错误选项+1个正确选项
|
|
const wrongOptions = words.filter(w => w.word !== currentWord.word);
|
|
const shuffledWrong = shuffleArray(wrongOptions).slice(0, 3);
|
|
const allOptions = shuffleArray([currentWord, ...shuffledWrong]);
|
|
|
|
// 渲染选项卡片
|
|
const grid = document.getElementById('optionsGrid');
|
|
grid.innerHTML = '';
|
|
|
|
allOptions.forEach(option => {
|
|
const card = document.createElement('div');
|
|
card.className = 'option-card';
|
|
card.innerHTML = `<span class="option-emoji">${option.emoji}</span>${option.word}`;
|
|
card.onclick = () => checkAnswer(option.word, currentWord.word, card);
|
|
grid.appendChild(card);
|
|
});
|
|
|
|
// 题目加载后自动朗读一次单词
|
|
updateSoundButtonEnabled(speechReady);
|
|
clearTimeout(autoSpeakTimer);
|
|
autoSpeakTimer = setTimeout(() => {
|
|
speakWord(currentWord.word);
|
|
}, 450);
|
|
}
|
|
|
|
// 点击喇叭播放单词发音
|
|
function playWordSound() {
|
|
const currentWord = shuffledWords[currentQuestion];
|
|
speakWord(currentWord.word);
|
|
}
|
|
|
|
// 校验答案
|
|
function checkAnswer(selected, correct, card) {
|
|
if (answered) return;
|
|
answered = true;
|
|
|
|
const feedback = document.getElementById('feedback');
|
|
const allCards = document.querySelectorAll('.option-card');
|
|
|
|
if (selected === correct) {
|
|
playCorrectSound();
|
|
card.classList.add('correct');
|
|
feedback.textContent = '🎉 太棒了!答对啦!+10分';
|
|
feedback.classList.add('correct');
|
|
|
|
// 对应队伍加分
|
|
if (currentTeam === 'red') {
|
|
scoreRed += 10;
|
|
document.getElementById('scoreRed').textContent = scoreRed;
|
|
} else {
|
|
scoreBlue += 10;
|
|
document.getElementById('scoreBlue').textContent = scoreBlue;
|
|
}
|
|
} else {
|
|
playWrongSound();
|
|
card.classList.add('wrong');
|
|
feedback.textContent = '😅 答错啦,轮到下一队!';
|
|
feedback.classList.add('wrong');
|
|
|
|
// 标出正确答案
|
|
allCards.forEach(c => {
|
|
if (c.textContent.includes(correct)) {
|
|
c.classList.add('correct');
|
|
}
|
|
});
|
|
}
|
|
|
|
document.getElementById('nextBtn').classList.remove('hidden');
|
|
}
|
|
|
|
// 进入下一题
|
|
function nextQuestion() {
|
|
// 每道题结束后强制轮换队伍,实现一边一次
|
|
stopSpeech();
|
|
switchTeam();
|
|
currentQuestion++;
|
|
|
|
if (currentQuestion >= words.length) {
|
|
endGame();
|
|
} else {
|
|
loadQuestion();
|
|
}
|
|
}
|
|
|
|
// 游戏结束结算
|
|
function endGame() {
|
|
stopSpeech();
|
|
document.getElementById('gameScreen').classList.add('hidden');
|
|
document.getElementById('endScreen').classList.remove('hidden');
|
|
|
|
document.getElementById('finalRed').textContent = scoreRed;
|
|
document.getElementById('finalBlue').textContent = scoreBlue;
|
|
|
|
const winnerText = document.getElementById('winnerText');
|
|
const endEmoji = document.getElementById('endEmoji');
|
|
|
|
if (scoreRed > scoreBlue) {
|
|
winnerText.textContent = '🔴 红队获胜!';
|
|
winnerText.className = 'winner-text winner-red';
|
|
endEmoji.textContent = '🏆🎉';
|
|
} else if (scoreBlue > scoreRed) {
|
|
winnerText.textContent = '🔵 蓝队获胜!';
|
|
winnerText.className = 'winner-text winner-blue';
|
|
endEmoji.textContent = '🏆🎉';
|
|
} else {
|
|
winnerText.textContent = '🤝 平局!势均力敌!';
|
|
winnerText.className = 'winner-text winner-draw';
|
|
endEmoji.textContent = '🤝✨';
|
|
}
|
|
}
|
|
|
|
// 重新开始游戏
|
|
function restartGame() {
|
|
stopSpeech();
|
|
startGame();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|