console
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>比例的组成大挑战</title>
<style>
:root {
--primary-color: #1a5200;
--secondary-color: #3cb371;
--error-color: #ff6666;
--warning-color: #ff9900;
--bg-color: #e0f0e6;
--card-bg: #f0f8f0;
--text-color: #1a3a1a;
}
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: 'Microsoft YaHei', sans-serif;
background-color: var(--bg-color);
margin: 0;
padding: 0;
color: var(--text-color);
touch-action: manipulation;
overflow-x: hidden;
}
#menu, #game-container, #game-over {
width: 100%;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#menu {
position: fixed;
top: 0;
left: 0;
z-index: 100;
background-color: var(--bg-color);
}
#game-container {
display: none;
position: relative;
max-width: 800px;
margin: 0 auto;
}
#title {
font-size: clamp(24px, 5vw, 36px);
color: var(--primary-color);
text-align: center;
font-weight: bold;
margin: 10px 0;
}
#game-info {
display: flex;
justify-content: space-between;
width: 100%;
max-width: 600px;
margin: 15px 0;
font-size: clamp(16px, 4vw, 20px);
}
#timer, #score, #lives {
padding: 8px 12px;
background-color: var(--card-bg);
border: 2px solid var(--primary-color);
border-radius: 8px;
min-width: 80px;
text-align: center;
}
#timer {
color: var(--warning-color);
}
#question {
margin: 20px 0;
font-size: clamp(18px, 4vw, 24px);
text-align: center;
min-height: 60px;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
width: 100%;
max-width: 600px;
}
.blank {
display: inline-flex;
width: 40px;
height: 40px;
min-width: 40px;
border: 2px dashed var(--primary-color);
margin: 5px;
position: relative;
align-items: center;
justify-content: center;
border-radius: 5px;
background-color: var(--card-bg);
transition: all 0.2s ease;
}
.blank.highlight {
background-color: #d4e8d4;
transform: scale(1.05);
}
.number {
display: inline-flex;
padding: 5px 10px;
font-size: clamp(18px, 4vw, 20px);
margin: 0 5px;
align-items: center;
justify-content: center;
}
#options {
display: flex;
flex-wrap: wrap;
justify-content: center;
margin: 20px auto;
width: 100%;
max-width: 600px;
gap: 10px;
}
.option {
width: 60px;
height: 60px;
min-width: 60px;
margin: 0;
background-color: var(--card-bg);
border: 2px solid var(--primary-color);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
font-size: clamp(18px, 4vw, 20px);
cursor: pointer;
user-select: none;
transition: all 0.3s ease;
touch-action: none;
}
.option:hover, .option:active {
background-color: #b3e0b3;
transform: scale(1.05);
}
.option.selected {
border-color: var(--secondary-color);
box-shadow: 0 0 10px var(--secondary-color);
transform: scale(1.05);
}
.option.wrong {
border-color: var(--error-color);
box-shadow: 0 0 10px var(--error-color);
}
#reset-btn {
margin-top: 20px;
padding: 12px 24px;
background-color: var(--card-bg);
border: 2px solid var(--primary-color);
border-radius: 20px;
font-size: clamp(16px, 4vw, 18px);
cursor: pointer;
transition: all 0.3s ease;
}
#reset-btn:hover, #reset-btn:active {
background-color: #b3e0b3;
transform: scale(1.05);
}
#designer {
margin-top: 20px;
font-size: clamp(12px, 3vw, 14px);
color: var(--text-color);
text-align: center;
}
.crystal-btn {
padding: 12px 24px;
background-color: var(--card-bg);
border: 2px solid var(--primary-color);
border-radius: 20px;
font-size: clamp(16px, 4vw, 18px);
cursor: pointer;
transition: all 0.3s ease;
margin: 10px;
width: 150px;
max-width: 80%;
}
.crystal-btn:hover, .crystal-btn:active {
background-color: #b3e0b3;
transform: scale(1.05);
}
#instructions {
margin: 20px auto;
max-width: 500px;
text-align: center;
line-height: 1.6;
font-size: clamp(14px, 3.5vw, 16px);
padding: 0 15px;
}
#game-over {
position: fixed;
top: 0;
left: 0;
background-color: rgba(255, 255, 255, 0.95);
display: none;
z-index: 200;
}
#game-over-title {
font-size: clamp(28px, 6vw, 36px);
color: var(--error-color);
margin-bottom: 20px;
text-align: center;
}
#final-score {
font-size: clamp(20px, 4.5vw, 24px);
margin-bottom: 30px;
text-align: center;
}
.drag-mirror {
position: fixed;
width: 60px;
height: 60px;
background-color: var(--card-bg);
border: 2px solid var(--secondary-color);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
z-index: 1000;
pointer-events: none;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
transform: translate(-50%, -50%);
opacity: 0.9;
transition: transform 0.1s ease;
}
@media (max-width: 600px) {
.blank, .option {
width: 50px;
height: 50px;
min-width: 50px;
font-size: 18px;
}
#question {
min-height: 50px;
}
#options {
gap: 8px;
}
.drag-mirror {
width: 50px;
height: 50px;
font-size: 18px;
}
}
@media (max-width: 400px) {
.blank, .option {
width: 40px;
height: 40px;
min-width: 40px;
font-size: 16px;
margin: 2px;
}
#game-info {
flex-direction: column;
align-items: center;
gap: 10px;
}
#question {
font-size: 16px;
}
.drag-mirror {
width: 40px;
height: 40px;
font-size: 16px;
}
}
</style>
</head>
<body>
<div id="menu">
<h1 id="title">比例的组成大挑战</h1>
<div id="instructions">
<p>游戏说明:</p>
<p>拖动正确的备选数字到虚线框中,组成正确的比例。</p>
<p>选择难度开始游戏:</p>
</div>
<button class="crystal-btn" onclick="startGame('简单')">简单</button>
<button class="crystal-btn" onclick="startGame('一般')">一般</button>
<button class="crystal-btn" onclick="startGame('困难')">困难</button>
</div>
<div id="game-container">
<div id="title">比例的组成大挑战</div>
<div id="game-info">
<div id="timer">时间: 0</div>
<div id="score">分数: 0</div>
<div id="lives">生命: 3</div>
</div>
<div id="question"></div>
<div id="options"></div>
<button id="reset-btn" class="crystal-btn" onclick="showMenu()">重选难度</button>
<div id="designer">蒋明老师设计制作</div>
</div>
<div id="game-over">
<h2 id="game-over-title">游戏结束!</h2>
<div id="final-score">你的最终分数: 0</div>
<button class="crystal-btn" onclick="restartGame()">重新开始</button>
<button class="crystal-btn" onclick="backToMenu()">返回主菜单</button>
</div>
<script>
const proportions = [
[2,4,6,12], [2,4,5,10], [2,5,6,15], [3,4,6,8], [3,6,10,20],
[3,6,9,18], [3,4,9,12], [3,5,9,15], [4,8,9,18], [4,6,10,15],
[4,6,12,18], [4,5,12,15], [4,5,20,25], [4,5,16,20], [5,10,15,30],
[5,6,10,12], [5,6,15,18], [5,7,10,14], [5,7,20,28], [6,7,18,21],
[6,7,12,14], [6,8,9,12], [7,8,14,16], [7,8,21,24], [8,9,16,18],
[8,9,24,27], [9,10,27,30], [9,10,18,20], [9,11,27,33], [10,11,20,22],
[10,11,30,33], [10,12,15,18], [10,12,20,24], [10,15,20,30], [10,15,30,45],
[12,24,25,50], [12,18,20,30], [12,18,24,36], [15,30,31,62], [15,30,35,70],
[15,30,45,90], [15,16,45,48], [15,18,45,54], [15,18,30,36], [18,24,36,48],
[18,21,54,63], [19,24,38,48], [19,24,57,72]
];
let difficulty = null;
let score = 0;
let lives = 3;
let currentQuestion = null;
let options = [];
let selectedOption = null;
let timer = 0;
let timerInterval = null;
let gameOver = false;
let userAnswers = [];
let blanks = [];
let isDragging = false;
let dragElement = null;
let dragMirror = null;
let startX = 0;
let startY = 0;
let audioContext;
let sounds = {};
function initSounds() {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
sounds.correctSound = createSound(523.25, 0.3);
sounds.wrongSound = createSound(349.23, 0.3);
sounds.timeoutSound = createSound(220.00, 0.5);
document.body.addEventListener('touchstart', function() {
if (audioContext.state === 'suspended') {
audioContext.resume();
}
}, { once: true });
} catch (e) {
console.error('音频初始化失败:', e);
}
}
function createSound(frequency, duration) {
return function() {
try {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'sine';
oscillator.frequency.value = frequency;
oscillator.connect(gainNode);
gainNode.gain.setValueAtTime(0.5, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + duration);
} catch (e) {
console.error('播放音效失败:', e);
}
};
}
function playSound(type) {
if (sounds[type]) {
sounds[type]();
}
}
function startGame(selectedDifficulty) {
difficulty = selectedDifficulty;
document.getElementById('menu').style.display = 'none';
document.getElementById('game-container').style.display = 'flex';
generateQuestion();
startTimer();
initSounds();
}
function showMenu() {
clearInterval(timerInterval);
document.getElementById('game-container').style.display = 'none';
document.getElementById('menu').style.display = 'flex';
resetGameState();
}
function resetGameState() {
score = 0;
lives = 3;
currentQuestion = null;
options = [];
selectedOption = null;
timer = 0;
gameOver = false;
userAnswers = [];
blanks = [];
document.getElementById('score').textContent = `分数: ${score}`;
document.getElementById('lives').textContent = `生命: ${lives}`;
}
function generateQuestion() {
const prop = proportions[Math.floor(Math.random() * proportions.length)];
if (difficulty === "简单") {
const blankIndex = Math.floor(Math.random() * 4);
currentQuestion = prop.map((num, index) => index === blankIndex ? '____' : num);
const correctOption = prop[blankIndex];
options = [correctOption];
while (options.length < 4) {
const option = Math.floor(Math.random() * 50) + 1;
if (!options.includes(option) && !prop.includes(option)) {
options.push(option);
}
}
options = shuffleArray(options);
} else if (difficulty === "一般") {
const blankIndices = [];
while (blankIndices.length < 2) {
const index = Math.floor(Math.random() * 4);
if (!blankIndices.includes(index)) {
blankIndices.push(index);
}
}
currentQuestion = prop.map((num, index) => blankIndices.includes(index) ? '____' : num);
const correctOptions = blankIndices.map(index => prop[index]);
options = [...correctOptions];
while (options.length < 4) {
const option = Math.floor(Math.random() * 50) + 1;
if (!options.includes(option) && !prop.includes(option)) {
options.push(option);
}
}
options = shuffleArray(options);
} else if (difficulty === "困难") {
const blankIndices = [];
while (blankIndices.length < 3) {
const index = Math.floor(Math.random() * 4);
if (!blankIndices.includes(index)) {
blankIndices.push(index);
}
}
currentQuestion = prop.map((num, index) => blankIndices.includes(index) ? '____' : num);
const correctOptions = blankIndices.map(index => prop[index]);
options = [...correctOptions];
while (options.length < 4) {
const option = Math.floor(Math.random() * 50) + 1;
if (!options.includes(option) && !prop.includes(option)) {
options.push(option);
}
}
options = shuffleArray(options);
}
updateQuestionDisplay();
updateOptionsDisplay();
userAnswers = currentQuestion.map(num => num === '____' ? null : num);
blanks = currentQuestion.map((num, index) => num === '____' ? index : -1).filter(index => index !== -1);
}
function updateQuestionDisplay() {
const questionElement = document.getElementById('question');
questionElement.innerHTML = '';
let formattedQuestion = '';
for (let i = 0; i < currentQuestion.length; i++) {
if (i === 2) {
formattedQuestion += '=';
}
if (currentQuestion[i] === '____') {
if (userAnswers[i] !== null && userAnswers[i] !== undefined) {
formattedQuestion += '<span class="number">' + userAnswers[i] + '</span>';
} else {
formattedQuestion += '<span class="blank" data-index="' + i + '"></span>';
}
} else {
formattedQuestion += '<span class="number">' + currentQuestion[i] + '</span>';
}
if (i % 2 === 0 && i !== 3) {
formattedQuestion += ':';
}
}
questionElement.innerHTML = formattedQuestion;
const blankElements = document.querySelectorAll('.blank');
blankElements.forEach(blank => {
blank.addEventListener('dragover', allowDrop);
blank.addEventListener('drop', drop);
});
}
function updateOptionsDisplay() {
const optionsElement = document.getElementById('options');
optionsElement.innerHTML = '';
options.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.className = 'option';
optionElement.textContent = option;
optionElement.dataset.index = index;
optionElement.dataset.value = option;
optionElement.draggable = true;
optionElement.addEventListener('dragstart', dragStart);
optionElement.addEventListener('dragend', dragEnd);
optionElement.addEventListener('touchstart', handleTouchStart, { passive: false });
optionElement.addEventListener('touchmove', handleTouchMove, { passive: false });
optionElement.addEventListener('touchend', handleTouchEnd);
optionsElement.appendChild(optionElement);
});
}
function dragStart(event) {
selectedOption = {
value: parseInt(event.target.textContent),
index: parseInt(event.target.dataset.index)
};
event.dataTransfer.setData('text/plain', event.target.textContent);
event.target.classList.add('selected');
}
function dragEnd(event) {
event.target.classList.remove('selected');
}
function allowDrop(event) {
event.preventDefault();
}
function drop(event) {
event.preventDefault();
if (!selectedOption) return;
const blankIndex = parseInt(event.target.dataset.index);
userAnswers[blankIndex] = selectedOption.value;
if (difficulty === "简单") {
checkAnswer();
} else {
if (userAnswers.every(num => num !== null)) {
checkAnswer();
}
}
updateQuestionDisplay();
}
function handleTouchStart(event) {
event.preventDefault();
const touch = event.touches[0];
const optionElement = event.target;
if (optionElement.classList.contains('option')) {
isDragging = true;
dragElement = optionElement;
selectedOption = {
value: parseInt(optionElement.textContent),
index: parseInt(optionElement.dataset.index)
};
startX = touch.clientX;
startY = touch.clientY;
createDragMirror(optionElement, touch.clientX, touch.clientY);
document.querySelectorAll('.blank').forEach(blank => {
blank.classList.add('highlight');
});
}
}
function createDragMirror(element, x, y) {
dragMirror = document.createElement('div');
dragMirror.className = 'drag-mirror';
dragMirror.textContent = element.textContent;
dragMirror.style.left = x + 'px';
dragMirror.style.top = y + 'px';
document.body.appendChild(dragMirror);
}
function handleTouchMove(event) {
if (!isDragging) return;
event.preventDefault();
const touch = event.touches[0];
if (dragMirror) {
dragMirror.style.left = touch.clientX + 'px';
dragMirror.style.top = touch.clientY + 'px';
}
const blankElements = document.querySelectorAll('.blank');
blankElements.forEach(blank => {
const rect = blank.getBoundingClientRect();
const isOver = touch.clientX >= rect.left &&
touch.clientX <= rect.right &&
touch.clientY >= rect.top &&
touch.clientY <= rect.bottom;
blank.classList.toggle('highlight', isOver);
});
}
function handleTouchEnd(event) {
if (!isDragging) return;
event.preventDefault();
const touch = event.changedTouches[0];
const blankElements = document.querySelectorAll('.blank');
let targetBlank = null;
for (const blank of blankElements) {
const rect = blank.getBoundingClientRect();
if (touch.clientX >= rect.left &&
touch.clientX <= rect.right &&
touch.clientY >= rect.top &&
touch.clientY <= rect.bottom) {
targetBlank = blank;
break;
}
}
if (targetBlank && selectedOption) {
const blankIndex = parseInt(targetBlank.dataset.index);
userAnswers[blankIndex] = selectedOption.value;
if (difficulty === "简单") {
checkAnswer();
} else {
if (userAnswers.every(num => num !== null)) {
checkAnswer();
}
}
updateQuestionDisplay();
}
cleanupDrag();
}
function cleanupDrag() {
if (dragMirror) {
dragMirror.remove();
dragMirror = null;
}
document.querySelectorAll('.blank').forEach(blank => {
blank.classList.remove('highlight');
});
isDragging = false;
dragElement = null;
}
function checkAnswer() {
let isCorrect = true;
let filledQuestion = [...currentQuestion];
blanks.forEach(index => {
filledQuestion[index] = userAnswers[index];
});
if (difficulty === "简单") {
const blankIndex = currentQuestion.indexOf('____');
const propIndex = proportions.findIndex(prop =>
arraysEqual(prop, filledQuestion)
);
if (propIndex !== -1) {
score += 10;
playSound('correctSound');
} else {
lives--;
playSound('wrongSound');
userAnswers[blankIndex] = null;
isCorrect = false;
}
}
else {
let matchFound = false;
const originalProp = proportions.find(prop => arraysEqual(prop, filledQuestion));
if (originalProp) {
matchFound = true;
} else {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (i !== j && filledQuestion[i] !== undefined && filledQuestion[j] !== undefined) {
const swapped = [...filledQuestion];
[swapped[i], swapped[j]] = [swapped[j], swapped[i]];
if (proportions.some(prop => arraysEqual(prop, swapped))) {
matchFound = true;
break;
}
}
}
if (matchFound) break;
}
}
if (matchFound) {
score += difficulty === "一般" ? 20 : 30;
playSound('correctSound');
} else {
lives--;
playSound('wrongSound');
blanks.forEach(index => {
userAnswers[index] = null;
});
isCorrect = false;
}
}
document.getElementById('score').textContent = `分数: ${score}`;
document.getElementById('lives').textContent = `生命: ${lives}`;
if (isCorrect) {
generateQuestion();
startTimer();
} else {
updateQuestionDisplay();
}
if (lives <= 0) {
endGame();
}
}
function startTimer() {
clearInterval(timerInterval);
timer = difficulty === "简单" ? 15 : difficulty === "一般" ? 20 : 30;
document.getElementById('timer').textContent = `时间: ${timer}`;
timerInterval = setInterval(() => {
timer--;
document.getElementById('timer').textContent = `时间: ${timer}`;
if (timer <= 0) {
clearInterval(timerInterval);
lives--;
playSound('timeoutSound');
document.getElementById('lives').textContent = `生命: ${lives}`;
userAnswers = currentQuestion.map(num => num === '____' ? null : num);
updateQuestionDisplay();
startTimer();
if (lives <= 0) {
endGame();
}
}
}, 1000);
}
function endGame() {
clearInterval(timerInterval);
document.getElementById('final-score').textContent = `你的最终分数: ${score}`;
document.getElementById('game-over').style.display = 'flex';
gameOver = true;
}
function restartGame() {
document.getElementById('game-over').style.display = 'none';
resetGameState();
generateQuestion();
startTimer();
}
function backToMenu() {
document.getElementById('game-over').style.display = 'none';
showMenu();
}
function arraysEqual(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
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;
}
</script>
</body>
</html>