Files
gadasin-quiz/script.js
2026-02-14 15:31:14 +03:00

499 lines
21 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
document.addEventListener('DOMContentLoaded', function () {
// Элементы DOM
const startScreen = document.getElementById('start-screen');
const quizScreen = document.getElementById('quiz-screen');
const resultScreen = document.getElementById('result-screen');
const reviewScreen = document.getElementById('review-screen');
const startBtn = document.getElementById('start-btn');
const prevBtn = document.getElementById('prev-btn');
const checkBtn = document.getElementById('check-btn');
const nextBtn = document.getElementById('next-btn');
const submitBtn = document.getElementById('submit-btn');
const restartBtn = document.getElementById('restart-btn');
const reviewBtn = document.getElementById('review-btn');
const backToResultsBtn = document.getElementById('back-to-results-btn');
const questionElement = document.getElementById('question');
const optionsContainer = document.getElementById('options-container');
const selectionInfo = document.getElementById('selection-info');
const selectedCountElement = document.getElementById('selected-count');
const totalOptionsElement = document.getElementById('total-options');
const feedbackContainer = document.getElementById('feedback-container');
const correctFeedback = document.getElementById('correct-feedback');
const partialFeedback = document.getElementById('partially-feedback');
const incorrectFeedback = document.getElementById('incorrect-feedback');
const partialCorrectCount = document.getElementById('partial-correct-count');
const totalCorrectCount = document.getElementById('total-correct-count');
const correctAnswersText = document.getElementById('correct-answers-text');
const progressBar = document.querySelector('.progress-bar');
const progressText = document.getElementById('progress-text');
const scoreElement = document.getElementById('score');
const percentageElement = document.getElementById('percentage');
const correctCountElement = document.getElementById('correct-count');
const partialCountElement = document.getElementById('partial-count');
const incorrectCountElement = document.getElementById('incorrect-count');
const reviewContainer = document.getElementById('review-container');
// Переменные состояния
let questions = [];
let shuffledQuestions = [];
let currentQuestionIndex = 0;
let userAnswers = []; // Массив массивов выбранных индексов
let questionStatus = []; // Статус ответа на каждый вопрос: 'unanswered', 'correct', 'partial', 'incorrect'
let score = 0;
let letters = ['а', 'б', 'в', 'г', 'д', 'е'];
// Загрузка вопросов из JSON
async function loadQuestions() {
try {
const response = await fetch('questions.json');
if (!response.ok) {
throw new Error('Не удалось загрузить вопросы');
}
questions = await response.json();
console.log('Вопросы загружены:', questions.length);
} catch (error) {
console.error('Ошибка загрузки вопросов:', error);
createDemoQuestions();
}
}
// Создание демо-вопросов, если файл не найден
function createDemoQuestions() {
questions = [
{
"text": "Составляющие мультимедиа могут быть разбиты на основные группы?",
"options": [
{ "text": "Текстовая, визуальная и звуковая информация.", "correct": false },
{ "text": "Текстовая, визуальная, звуковая информация и данные.", "correct": true },
{ "text": "Текстовая, визуальная, звуковая информация, данные и графическая информация.", "correct": false },
{ "text": "Только текстовая и визуальная информация.", "correct": false }
]
},
{
"text": "С точки зрения передачи мультимедиа могут быть классифицированы на?",
"options": [
{ "text": "Передаваемые в реальном времени.", "correct": true },
{ "text": "Передаваемые в on-line.", "correct": false },
{ "text": "Передаваемые в не реальном времени.", "correct": true },
{ "text": "Только передаваемые по запросу.", "correct": false }
]
}
];
console.log('Созданы демо-вопросы с множественным выбором');
}
// Инициализация теста
function initializeQuiz() {
// Перемешиваем вопросы
shuffledQuestions = [...questions];
shuffleArray(shuffledQuestions);
// Перемешиваем варианты ответов в каждом вопросе
shuffledQuestions.forEach(question => {
shuffleArray(question.options);
// Находим индексы правильных ответов после перемешивания
question.correctOptionIndices = [];
question.options.forEach((option, index) => {
if (option.correct) {
question.correctOptionIndices.push(index);
}
});
// Сохраняем текст правильных ответов для отображения
question.correctAnswersText = question.correctOptionIndices
.map(idx => `${letters[idx]}) ${question.options[idx].text}`)
.join('; ');
});
// Инициализируем массивы
userAnswers = new Array(shuffledQuestions.length).fill(null).map(() => []);
questionStatus = new Array(shuffledQuestions.length).fill('unanswered');
// Сбрасываем индекс текущего вопроса и счет
currentQuestionIndex = 0;
score = 0;
// Обновляем UI
updateProgress();
showQuestion();
}
// Перемешивание массива (алгоритм Фишера-Йетса)
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// Показать вопрос
function showQuestion() {
const question = shuffledQuestions[currentQuestionIndex];
questionElement.textContent = `${currentQuestionIndex + 1}. ${question.text}`;
// Обновляем информацию о выборе
totalOptionsElement.textContent = question.options.length;
updateSelectionInfo();
// Скрываем блок обратной связи, если вопрос еще не проверен
if (questionStatus[currentQuestionIndex] === 'unanswered') {
feedbackContainer.classList.add('hidden');
} else {
showFeedback();
}
// Очищаем контейнер с вариантами
optionsContainer.innerHTML = '';
// Создаем варианты ответов
question.options.forEach((option, index) => {
const optionElement = document.createElement('div');
optionElement.className = 'option';
// Проверяем, выбран ли этот вариант
const isSelected = userAnswers[currentQuestionIndex].includes(index);
const isChecked = questionStatus[currentQuestionIndex] !== 'unanswered';
if (isSelected) {
optionElement.classList.add('selected');
}
// Если вопрос проверен, показываем правильность ответов
if (isChecked) {
optionElement.classList.add('checked');
const isCorrectOption = question.correctOptionIndices.includes(index);
if (isSelected && isCorrectOption) {
optionElement.classList.add('correct');
} else if (isSelected && !isCorrectOption) {
optionElement.classList.add('incorrect');
} else if (!isSelected && isCorrectOption) {
optionElement.classList.add('correct');
}
}
optionElement.innerHTML = `
<div class="option-checkbox"></div>
<div class="option-text">${letters[index]}) ${option.text}</div>
`;
// Если вопрос еще не проверен, добавляем обработчик клика
if (!isChecked) {
optionElement.addEventListener('click', () => toggleOption(index));
}
optionsContainer.appendChild(optionElement);
});
// Обновляем состояние кнопок навигации
updateNavigationButtons();
}
// Переключить выбор варианта
function toggleOption(optionIndex) {
const currentAnswers = userAnswers[currentQuestionIndex];
const index = currentAnswers.indexOf(optionIndex);
if (index === -1) {
// Добавляем вариант
currentAnswers.push(optionIndex);
} else {
// Удаляем вариант
currentAnswers.splice(index, 1);
}
// Обновляем отображение
updateSelectionInfo();
showQuestion();
}
// Обновить информацию о выбранных вариантах
function updateSelectionInfo() {
const selectedCount = userAnswers[currentQuestionIndex].length;
selectedCountElement.textContent = selectedCount;
// Подсвечиваем информацию в зависимости от количества выбранных ответов
if (selectedCount === 0) {
selectionInfo.style.borderColor = '#e0e0e0';
selectionInfo.style.backgroundColor = '#f0f4ff';
} else {
selectionInfo.style.borderColor = '#4a00e0';
selectionInfo.style.backgroundColor = '#e6f7ff';
}
}
// Обновить кнопки навигации
function updateNavigationButtons() {
const isAnswered = questionStatus[currentQuestionIndex] !== 'unanswered';
const hasSelection = userAnswers[currentQuestionIndex].length > 0;
prevBtn.disabled = currentQuestionIndex === 0;
if (isAnswered) {
checkBtn.classList.add('hidden');
nextBtn.classList.remove('hidden');
} else {
checkBtn.classList.remove('hidden');
nextBtn.classList.add('hidden');
checkBtn.disabled = !hasSelection;
}
// Показываем кнопку завершения на последнем проверенном вопросе
const allQuestionsAnswered = questionStatus.every(status => status !== 'unanswered');
const isLastQuestion = currentQuestionIndex === shuffledQuestions.length - 1;
if (isLastQuestion && isAnswered) {
nextBtn.classList.add('hidden');
submitBtn.classList.remove('hidden');
} else {
submitBtn.classList.add('hidden');
}
}
// Проверить ответ и показать результат
function checkAnswer() {
const question = shuffledQuestions[currentQuestionIndex];
const userSelected = userAnswers[currentQuestionIndex];
const correctIndices = question.correctOptionIndices;
// Находим количество правильных выбранных ответов
const correctSelected = userSelected.filter(idx => correctIndices.includes(idx)).length;
const incorrectSelected = userSelected.length - correctSelected;
const missedCorrect = correctIndices.length - correctSelected;
// Определяем статус ответа
let status;
let feedbackType;
if (incorrectSelected === 0 && missedCorrect === 0) {
// Все правильные выбраны, ничего лишнего
status = 'correct';
feedbackType = 'correct';
score += 1;
} else if (correctSelected > 0 && (incorrectSelected > 0 || missedCorrect > 0)) {
// Частично правильно
status = 'partial';
feedbackType = 'partial';
score += 0.5; // Половина балла за частично правильный ответ
} else {
// Полностью неправильно
status = 'incorrect';
feedbackType = 'incorrect';
}
// Сохраняем статус вопроса
questionStatus[currentQuestionIndex] = status;
// Показываем обратную связь
showFeedback(correctSelected, correctIndices.length);
// Обновляем навигацию
updateNavigationButtons();
// Обновляем отображение вариантов
showQuestion();
}
// Показать обратную связь
function showFeedback(correctSelected = 0, totalCorrect = 0) {
const question = shuffledQuestions[currentQuestionIndex];
const status = questionStatus[currentQuestionIndex];
// Скрываем все виды обратной связи
correctFeedback.classList.add('hidden');
partialFeedback.classList.add('hidden');
incorrectFeedback.classList.add('hidden');
// Показываем соответствующий вид обратной связи
switch (status) {
case 'correct':
correctFeedback.classList.remove('hidden');
break;
case 'partial':
partialFeedback.classList.remove('hidden');
partialCorrectCount.textContent = correctSelected;
totalCorrectCount.textContent = totalCorrect;
break;
case 'incorrect':
incorrectFeedback.classList.remove('hidden');
correctAnswersText.textContent = question.correctAnswersText;
break;
}
feedbackContainer.classList.remove('hidden');
}
// Обновление прогресса
function updateProgress() {
const progress = ((currentQuestionIndex + 1) / shuffledQuestions.length) * 100;
progressBar.style.setProperty('--width', `${progress}%`);
progressText.textContent = `Вопрос ${currentQuestionIndex + 1} из ${shuffledQuestions.length}`;
}
// Переход к следующему вопросу
function nextQuestion() {
if (currentQuestionIndex < shuffledQuestions.length - 1) {
currentQuestionIndex++;
updateProgress();
showQuestion();
}
}
// Переход к предыдущему вопросу
function prevQuestion() {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
updateProgress();
showQuestion();
}
}
// Завершение теста и подсчет результатов
function finishQuiz() {
// Подсчет детальной статистики
let correctCount = 0;
let partialCount = 0;
let incorrectCount = 0;
questionStatus.forEach(status => {
switch (status) {
case 'correct':
correctCount++;
break;
case 'partial':
partialCount++;
break;
case 'incorrect':
incorrectCount++;
break;
}
});
// Обновление экрана результатов
scoreElement.textContent = Math.round(score * 10) / 10; // Округляем до одного знака после запятой
const percentage = Math.round((score / shuffledQuestions.length) * 100);
percentageElement.textContent = `${percentage}%`;
correctCountElement.textContent = correctCount;
partialCountElement.textContent = partialCount;
incorrectCountElement.textContent = incorrectCount;
// Обновление кругового индикатора
const circle = document.querySelector('.circle');
circle.style.background = `conic-gradient(#4a00e0 ${percentage}%, #e0e0e0 ${percentage}%)`;
// Переключение экранов
quizScreen.classList.add('hidden');
resultScreen.classList.remove('hidden');
}
// Показать обзор ответов
function showReview() {
reviewContainer.innerHTML = '';
shuffledQuestions.forEach((question, index) => {
const reviewItem = document.createElement('div');
reviewItem.className = 'review-item';
const status = questionStatus[index];
const userSelected = userAnswers[index];
const correctIndices = question.correctOptionIndices;
// Определяем статус для отображения
let statusText, statusClass;
switch (status) {
case 'correct':
statusText = 'Правильно';
statusClass = 'status-correct';
break;
case 'partial':
statusText = 'Частично правильно';
statusClass = 'status-partial';
break;
case 'incorrect':
statusText = 'Неправильно';
statusClass = 'status-incorrect';
break;
default:
statusText = 'Не отвечено';
statusClass = 'status-incorrect';
}
let optionsHTML = '';
question.options.forEach((option, optionIndex) => {
let optionClass = '';
const isCorrect = correctIndices.includes(optionIndex);
const isSelected = userSelected.includes(optionIndex);
if (isCorrect && isSelected) {
optionClass = 'user-correct';
} else if (isCorrect && !isSelected) {
optionClass = 'correct-answer';
} else if (!isCorrect && isSelected) {
optionClass = 'user-incorrect';
} else if (status === 'partial' && isSelected && !isCorrect) {
optionClass = 'user-partial';
}
optionsHTML += `
<div class="review-option ${optionClass}">
<strong>${letters[optionIndex]})</strong> ${option.text}
</div>
`;
});
reviewItem.innerHTML = `
<div class="review-question">
<span>${index + 1}. ${question.text}</span>
<span class="question-status ${statusClass}">${statusText}</span>
</div>
<div class="review-options">
${optionsHTML}
</div>
<div style="margin-top: 15px; font-style: italic;">
Ваши ответы: ${userSelected.length > 0
? userSelected.map(idx => letters[idx]).join(', ')
: 'нет ответа'}
</div>
`;
reviewContainer.appendChild(reviewItem);
});
resultScreen.classList.add('hidden');
reviewScreen.classList.remove('hidden');
}
// Начало теста
function startQuiz() {
startScreen.classList.add('hidden');
quizScreen.classList.remove('hidden');
initializeQuiz();
}
// События
startBtn.addEventListener('click', startQuiz);
prevBtn.addEventListener('click', prevQuestion);
checkBtn.addEventListener('click', checkAnswer);
nextBtn.addEventListener('click', nextQuestion);
submitBtn.addEventListener('click', finishQuiz);
restartBtn.addEventListener('click', () => {
resultScreen.classList.add('hidden');
startScreen.classList.remove('hidden');
});
reviewBtn.addEventListener('click', showReview);
backToResultsBtn.addEventListener('click', () => {
reviewScreen.classList.add('hidden');
resultScreen.classList.remove('hidden');
});
// Загружаем вопросы при загрузке страницы
loadQuestions();
});