From 5cadb19238016516e79a942231cfaf3059b9422c Mon Sep 17 00:00:00 2001 From: brambang Date: Thu, 18 Dec 2025 09:49:16 +0700 Subject: [PATCH] feat: adding drag and drop feat for quiz game --- src/css/game.css | 71 ++++++++++++++++++- src/game/firstperson.html | 2 +- src/js/firstperson.js | 140 ++++++++++++++++++++++++++++++++++++-- src/js/soal.js | 37 ++++++++++ 4 files changed, 241 insertions(+), 9 deletions(-) diff --git a/src/css/game.css b/src/css/game.css index 85ac9ae..9fa49d5 100644 --- a/src/css/game.css +++ b/src/css/game.css @@ -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; +} diff --git a/src/game/firstperson.html b/src/game/firstperson.html index 1a0e96e..b1bd705 100644 --- a/src/game/firstperson.html +++ b/src/game/firstperson.html @@ -45,7 +45,7 @@ document.addEventListener("DOMContentLoaded", () => { const bgm = document.getElementById("bgm"); bgm.volume = 0.1; - bgm.loop(true) + bgm.loop = true bgm.play() }); diff --git a/src/js/firstperson.js b/src/js/firstperson.js index 84080ae..72d24e1 100644 --- a/src/js/firstperson.js +++ b/src/js/firstperson.js @@ -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); diff --git a/src/js/soal.js b/src/js/soal.js index 01323f8..e4db7c0 100644 --- a/src/js/soal.js +++ b/src/js/soal.js @@ -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"} + ] + } ]; \ No newline at end of file