feat: adding drag and drop feat for quiz game

This commit is contained in:
Carolus Bramantyo Seno Mahesworo 2025-12-18 09:49:16 +07:00
parent c0f2f8f19d
commit 5cadb19238
4 changed files with 241 additions and 9 deletions

View File

@ -117,7 +117,8 @@
.container-first .content .quiz {
width: 1000px;
height: 200px;
min-height: 200px;
height: auto;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
@ -132,6 +133,7 @@
flex-direction: column;
border: 10px solid white;
border-radius: 10px;
padding-bottom: 20px;
}
.container-first .content .quiz h2 {
@ -262,3 +264,70 @@
background-repeat: no-repeat;
background-size: cover;
}
/* Container utama area Drag and Drop */
.dnd-container {
display: flex;
justify-content: space-around;
align-items: flex-start;
width: 100%;
height: 100%;
gap: 20px;
padding-top: 10px;
}
/* Kolom Kiri (Source) dan Kanan (Target) */
.dnd-items, .dnd-targets {
display: flex;
flex-direction: column;
gap: 10px;
width: 45%;
}
/* Style Item yang bisa ditarik */
.draggable-item {
background-color: #f7e62e34;
color: #ffdf12;
padding: 8px;
border: 2px solid #ffdf12;
border-radius: 5px;
cursor: grab;
font-size: 20px; /* Lebih kecil dari tombol biasa biar muat */
transition: transform 0.2s;
}
.draggable-item:active {
cursor: grabbing;
transform: scale(0.95);
}
/* Style Kotak Target */
.drop-zone {
background-color: rgba(255, 255, 255, 0.1);
border: 2px dashed #ffdf12;
border-radius: 5px;
min-height: 45px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
transition: background-color 0.3s;
}
/* Visual Feedback saat benar/salah */
.drop-zone.correct {
background-color: rgba(0, 255, 0, 0.3);
border-style: solid;
border-color: #00ff00;
}
.drop-zone.incorrect {
animation: shake 0.3s ease-in-out;
border-color: red;
}
/* Override tinggi quiz box khusus saat DnD (Opsional, kalau mau otomatis) */
.quiz.dnd-mode {
height: auto;
min-height: 200px;
}

View File

@ -45,7 +45,7 @@
document.addEventListener("DOMContentLoaded", () => {
const bgm = document.getElementById("bgm");
bgm.volume = 0.1;
bgm.loop(true)
bgm.loop = true
bgm.play()
});

View File

@ -23,6 +23,7 @@ function startQuiz(){
score = 0;
shuffle(questions);
showQuestion();
updateHearts()
}
@ -31,6 +32,20 @@ function showQuestion(){
let currentQuestion = questions[currentQuestionIndex]
let questionNumber = currentQuestionIndex + 1
questionElement.innerHTML = questionNumber + ". " + currentQuestion.question;
// Dispatcher Logic
if (currentQuestion.type === "drag_drop") {
// Fallback ke MC kalau properti type belum ada di soal lama
document.getElementById("border").classList.add("dnd-mode")
renderDragAndDrop(currentQuestion);
} else {
// Default ke Multiple Choice
document.getElementById("border").classList.remove("dnd-mode");
renderMultipleChoice(currentQuestion);
}
}
function renderMultipleChoice(currentQuestion){ // <--- TAMBAH PARAMETER INI
currentQuestion.answers.forEach(answer => {
const button = document.createElement("button");
button.innerHTML = answer.text;
@ -41,6 +56,105 @@ function showQuestion(){
});
}
function renderDragAndDrop(q) {
const dndContainer = document.createElement("div");
dndContainer.className = "dnd-container";
const itemsCol = document.createElement("div");
itemsCol.className = "dnd-items";
const targetsCol = document.createElement("div");
targetsCol.className = "dnd-targets";
let correctMatches = 0;
const totalMatches = q.targets.length;
// Shuffle items biar tidak segaris lurus jawabannya
const shuffledItems = shuffle([...q.items]);
// Render Items (Kiri)
shuffledItems.forEach(item => {
const el = document.createElement("div");
el.className = "draggable-item";
el.draggable = true;
el.textContent = item.text;
el.id = item.id;
el.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", item.id);
setTimeout(() => el.style.opacity = "0.5", 0);
});
el.addEventListener("dragend", () => {
el.style.opacity = "1";
});
itemsCol.appendChild(el);
});
// Render Targets (Kanan)
q.targets.forEach(target => {
const el = document.createElement("div");
el.className = "drop-zone";
el.textContent = target.text;
el.dataset.matchId = target.matchId;
el.addEventListener("dragover", (e) => {
e.preventDefault();
});
el.addEventListener("drop", (e) => {
e.preventDefault();
const draggedId = e.dataTransfer.getData("text/plain");
const draggedEl = document.getElementById(draggedId);
if (draggedId === target.matchId) {
// BENAR
el.classList.add("correct");
el.innerHTML = "";
el.appendChild(draggedEl);
draggedEl.draggable = false;
draggedEl.style.cursor = "default";
draggedEl.style.opacity = "1";
const bgm = document.getElementById("sfx");
bgm.play();
shake(document.getElementById("boss"));
showFloatingText();
score += 10;
correctMatches++;
if (correctMatches === totalMatches) {
setTimeout(nextQuestion, 1000);
}
} else {
// SALAH
el.classList.add("incorrect");
setTimeout(() => el.classList.remove("incorrect"), 500);
wrongCount++;
shake(document.getElementById("hrt"));
updateHearts();
dmg();
const bgm = document.getElementById("sfxhrt");
bgm.play();
if(wrongCount >= maxWrong){
showScore();
}
}
});
targetsCol.appendChild(el);
});
dndContainer.appendChild(itemsCol);
dndContainer.appendChild(targetsCol);
answerBtn.appendChild(dndContainer);
}
function resetState(){
while(answerBtn.firstChild){
answerBtn.removeChild(answerBtn.firstChild)
@ -121,17 +235,29 @@ function selectAnswer(e){
function showScore(){
resetState();
answerBorder.remove();
document.getElementById("h1").remove();
document.getElementById("h2").remove();
document.getElementById("h3").remove();
const kotakSoal = document.getElementById("border");
if (kotakSoal) {
kotakSoal.remove(); // Hapus elemen kotak soal dari layar
}
if(answerBorder) answerBorder.remove()
const h1 = document.getElementById("h1");
const h2 = document.getElementById("h2");
const h3 = document.getElementById("h3");
if(h1) h1.remove();
if(h2) h2.remove();
if(h3) h3.remove();
const bgm = document.getElementById("win");
bgm.play()
bgm.play();
setTimeout(()=> {
const alerts = document.getElementById("scoreAlert")
alerts.classList.add("border")
const alerts = document.getElementById("scoreAlert");
alerts.classList.add("border"); // CSS class .border membuat kotak skor muncul
alerts.textContent = `you scored ${score} out of 200 !`;
}, 200);
setTimeout(()=> {
postScore(score);
}, 3200);

View File

@ -1,5 +1,7 @@
export const questions = [
// --- SOAL PILIHAN GANDA (MULTIPLE CHOICE) ---
{
type: "multiple_choice",
question: "what is Python ?",
answers: [
{text: "Programming Lang", correct: true},
@ -9,6 +11,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Data type at python ?",
answers: [
{text: "Docx", correct: false},
@ -18,6 +21,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Index at python start from ?",
answers: [
{text: "1", correct: false},
@ -27,6 +31,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "How do you start a comment in Python code ?",
answers: [
{text: "// This is a comment", correct: true},
@ -36,6 +41,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "What is the result of the following operation ? (print('5'+'5'))",
answers: [
{text: "10", correct: false},
@ -45,6 +51,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Correct string format ?",
answers: [
{text: "'bahlil'", correct: true},
@ -54,6 +61,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Correct loop syntax ?",
answers: [
{text: "for i in range(5):", correct: true},
@ -63,6 +71,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Function to get user input ?",
answers: [
{text: "scan()", correct: false},
@ -72,6 +81,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Convert integer to string ?",
answers: [
{text: "text(5)", correct: false},
@ -81,6 +91,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Stop a loop ?",
answers: [
{text: "break", correct: true},
@ -90,6 +101,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Symbol for tuples?",
answers: [
{text: "[ ]", correct: false},
@ -99,6 +111,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Output of int(3.9) ?",
answers: [
{text: "4", correct: false},
@ -108,6 +121,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Find max number ?",
answers: [
{text: "top()", correct: false},
@ -117,6 +131,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: '(3 * "A") output ?',
answers: [
{text: "error", correct: false},
@ -126,6 +141,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Logical OR operator ?",
answers: [
{text: "||", correct: false},
@ -135,6 +151,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "result of not True ?",
answers: [
{text: "true", correct: false},
@ -144,6 +161,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Check if item exists ?",
answers: [
{text: "exists", correct: false},
@ -153,6 +171,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "x += 1 means?",
answers: [
{text: "x = 1", correct: false},
@ -162,6 +181,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "Define a class ?",
answers: [
{text: "struct", correct: false},
@ -171,6 +191,7 @@ export const questions = [
]
},
{
type: "multiple_choice",
question: "how much data type in python ?",
answers: [
{text: "5", correct: true},
@ -179,4 +200,20 @@ export const questions = [
{text: "100", correct: false},
]
},
// --- SOAL DRAG AND DROP (DND) ---
{
type: "drag_drop",
question: "Pasangkan Tipe Data Python Berikut",
items: [
{id: "item1", text: "Integer"}, // Item yang ditarik (kiri)
{id: "item2", text: "String"},
{id: "item3", text: "Boolean"}
],
targets: [
{id: "target1", text: "100", matchId: "item1"}, // Target drop (kanan)
{id: "target2", text: "'Hello World'", matchId: "item2"},
{id: "target3", text: "True", matchId: "item3"}
]
}
];