diff --git a/www/html/Admin/admin.css b/www/html/Admin/admin.css index 355bd35..4b5daa5 100644 --- a/www/html/Admin/admin.css +++ b/www/html/Admin/admin.css @@ -669,14 +669,92 @@ legend { border-radius: 999px; white-space: nowrap; } -.sq-card-grid { + +/* —— เลือกการ์ดพิเศษ (card-1..7 จาก trycss/10-Game-1-result) —— */ +.sq-card-picker-wrap { + display: flex; + flex-wrap: wrap; + gap: 1.25rem; + align-items: flex-start; + margin-bottom: 1rem; +} +.sq-card-picker { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); + gap: 0.65rem; + flex: 1 1 420px; + max-width: 720px; +} +.sq-card-pick { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + padding: 0.45rem 0.35rem 0.5rem; + border: 2px solid var(--border); + border-radius: 12px; + background: rgba(255, 255, 255, 0.03); + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s, transform 0.12s; +} +.sq-card-pick:hover { + border-color: rgba(122, 162, 247, 0.55); + transform: translateY(-1px); +} +.sq-card-pick.is-selected { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(122, 162, 247, 0.35), 0 4px 16px rgba(0, 0, 0, 0.25); + background: var(--accent-dim); +} +.sq-card-pick img { + width: 72px; + height: auto; + max-height: 96px; + object-fit: contain; + display: block; + image-rendering: auto; +} +.sq-card-pick-cap { + font-size: 0.68rem; + font-weight: 600; + color: var(--muted); + text-align: center; + line-height: 1.15; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.sq-card-pick.is-selected .sq-card-pick-cap { color: var(--accent); } +.sq-card-preview { + flex: 0 0 auto; + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: rgba(0, 0, 0, 0.25); + text-align: center; +} +.sq-card-preview-label { + margin: 0 0 0.5rem; + font-size: 0.78rem; + color: var(--muted); +} +.sq-card-preview-img { + width: 120px; + height: auto; + max-height: 160px; + object-fit: contain; + display: block; + margin: 0 auto; +} +.sq-card-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.85rem; + margin-top: 0.25rem; } -.sq-card-grid .sq-card-url { grid-column: 1 / -1; } -.sq-card-grid input, -.sq-card-grid select { width: 100%; } +.sq-card-meta input, +.sq-card-meta select { width: 100%; } .sq-count { font-weight: 400; font-size: 0.8rem; diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index 715309a..024ea11 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -248,8 +248,129 @@ /* ===== ชุดคำถามพิเศษ (Level×Case = 15 ชุด) รับการ์ดพิเศษ ===== */ var SPECIAL_CARD_RARITIES = ['common', 'rare', 'legendary']; + var SPECIAL_CARD_ASSET_BASE = '/Game/img/special-cards/'; + var SPECIAL_CARD_CATALOG = [ + { id: 1, en: 'Time Dilation', th: 'เพิ่มเวลาเล่นมินิเกมทันที (+10 วินาที)' }, + { id: 2, en: 'Extension', th: 'เพิ่มเวลาในห้องพิจารณาคดี (+10 วิ FINAL VOTE)' }, + { id: 3, en: 'Ban Card', th: 'ห้ามเล่นมินิเกม 1 ตา' }, + { id: 4, en: 'Silence Card', th: 'การ์ดปิดปาก' }, + { id: 5, en: 'Bail Coin', th: 'เหรียญประกันตัว' }, + { id: 6, en: 'Fund', th: 'รับ coin เพิ่ม' }, + { id: 7, en: 'Free Evidence', th: 'รับหลักฐานฟรี' }, + ]; var specialQuizSetsState = {}; var specialQuizCurrentKey = 'L1_C1'; + var specialCardPickerBuilt = false; + + function specialCardTxtFilename(id) { + return id === 7 ? 'card--7txt.png' : 'card-' + id + '-txt.png'; + } + + function specialCardImageUrl(id) { + return SPECIAL_CARD_ASSET_BASE + 'card-' + id + '.png'; + } + + function specialCardTxtImageUrl(id) { + return SPECIAL_CARD_ASSET_BASE + specialCardTxtFilename(id); + } + + function specialCardCatalogEntry(id) { + for (var i = 0; i < SPECIAL_CARD_CATALOG.length; i++) { + if (SPECIAL_CARD_CATALOG[i].id === id) return SPECIAL_CARD_CATALOG[i]; + } + return null; + } + + function resolveSpecialCardIdFromSaved(card) { + if (!card || typeof card !== 'object') return null; + var cid = Number(card.cardId); + if (cid >= 1 && cid <= 7) return cid; + var url = String(card.imageUrl || ''); + var m = url.match(/\/card-(\d)\.png/i); + return m ? Number(m[1]) : null; + } + + function updateSpecialCardPreview() { + var prev = el('special-card-preview'); + var img = el('special-card-preview-img'); + var cid = parseInt(el('special-card-id') && el('special-card-id').value, 10); + if (!prev || !img) return; + if (!(cid >= 1 && cid <= 7)) { + prev.hidden = true; + img.removeAttribute('src'); + return; + } + img.src = specialCardImageUrl(cid); + img.alt = (specialCardCatalogEntry(cid) || {}).en || ('Card ' + cid); + prev.hidden = false; + } + + function setSpecialCardPickerSelection(cardId) { + var picker = el('special-card-picker'); + if (!picker) return; + picker.querySelectorAll('.sq-card-pick').forEach(function (btn) { + var on = String(btn.getAttribute('data-card-id')) === String(cardId); + btn.classList.toggle('is-selected', on); + btn.setAttribute('aria-checked', on ? 'true' : 'false'); + }); + } + + function selectSpecialCard(cardId, fillMeta) { + var idInp = el('special-card-id'); + var imgInp = el('special-card-image'); + var txtInp = el('special-card-txt-image'); + if (!(cardId >= 1 && cardId <= 7)) { + if (idInp) idInp.value = ''; + if (imgInp) imgInp.value = ''; + if (txtInp) txtInp.value = ''; + setSpecialCardPickerSelection(null); + updateSpecialCardPreview(); + return; + } + var entry = specialCardCatalogEntry(cardId); + if (idInp) idInp.value = String(cardId); + if (imgInp) imgInp.value = specialCardImageUrl(cardId); + if (txtInp) txtInp.value = specialCardTxtImageUrl(cardId); + if (fillMeta && entry) { + if (el('special-card-th')) el('special-card-th').value = entry.th; + if (el('special-card-en')) el('special-card-en').value = entry.en; + } + setSpecialCardPickerSelection(cardId); + updateSpecialCardPreview(); + } + + function ensureSpecialCardPicker() { + if (specialCardPickerBuilt) return; + var picker = el('special-card-picker'); + if (!picker) return; + picker.innerHTML = ''; + SPECIAL_CARD_CATALOG.forEach(function (entry) { + var btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'sq-card-pick'; + btn.setAttribute('role', 'radio'); + btn.setAttribute('aria-checked', 'false'); + btn.setAttribute('data-card-id', String(entry.id)); + btn.title = entry.en + ' — ' + entry.th; + var img = document.createElement('img'); + img.src = specialCardImageUrl(entry.id); + img.alt = entry.en; + img.width = 72; + img.height = 96; + img.decoding = 'async'; + img.loading = 'lazy'; + var cap = document.createElement('span'); + cap.className = 'sq-card-pick-cap'; + cap.textContent = entry.en; + btn.appendChild(img); + btn.appendChild(cap); + btn.addEventListener('click', function () { + selectSpecialCard(entry.id, true); + }); + picker.appendChild(btn); + }); + specialCardPickerBuilt = true; + } function specialQuizSelectorKey() { var L = el('special-quiz-level'); @@ -260,7 +381,7 @@ function updateSpecialQuizCount() { var c = el('special-quiz-count'); if (!c) return; - var n = document.querySelectorAll('#special-quiz-questions-list .quiz-admin-q-row').length; + var n = document.querySelectorAll('#special-quiz-questions-list .sq-mcq-row').length; c.textContent = '(' + n + ' ข้อ)'; } @@ -345,14 +466,18 @@ } function renderSpecialQuizSet(key) { + ensureSpecialCardPicker(); var set = specialQuizSetsState[key] || { questions: [], specialCard: {} }; var card = (set.specialCard && typeof set.specialCard === 'object') ? set.specialCard : {}; - if (el('special-card-th')) el('special-card-th').value = card.th || ''; - if (el('special-card-en')) el('special-card-en').value = card.en || ''; + var cardId = resolveSpecialCardIdFromSaved(card); + selectSpecialCard(cardId, false); + if (el('special-card-th')) el('special-card-th').value = card.th || (cardId ? (specialCardCatalogEntry(cardId) || {}).th : '') || ''; + if (el('special-card-en')) el('special-card-en').value = card.en || (cardId ? (specialCardCatalogEntry(cardId) || {}).en : '') || ''; if (el('special-card-rarity')) { el('special-card-rarity').value = SPECIAL_CARD_RARITIES.indexOf(card.rarity) >= 0 ? card.rarity : 'rare'; } - if (el('special-card-image')) el('special-card-image').value = card.imageUrl || ''; + if (cardId && el('special-card-image')) el('special-card-image').value = card.imageUrl || specialCardImageUrl(cardId); + if (cardId && el('special-card-txt-image')) el('special-card-txt-image').value = card.txtImageUrl || specialCardTxtImageUrl(cardId); var list = el('special-quiz-questions-list'); if (list) { list.innerHTML = ''; @@ -386,13 +511,17 @@ questions.push({ type: 'mcq', text: t, choices: choices, correctIndex: correctIndex }); }); var rar = el('special-card-rarity') ? el('special-card-rarity').value : 'rare'; + var cardId = parseInt(el('special-card-id') && el('special-card-id').value, 10); + if (!(cardId >= 1 && cardId <= 7)) cardId = null; specialQuizSetsState[key] = { questions: questions, specialCard: { + cardId: cardId, th: el('special-card-th') ? String(el('special-card-th').value || '').trim() : '', en: el('special-card-en') ? String(el('special-card-en').value || '').trim() : '', rarity: SPECIAL_CARD_RARITIES.indexOf(rar) >= 0 ? rar : 'rare', imageUrl: el('special-card-image') ? String(el('special-card-image').value || '').trim() : '', + txtImageUrl: el('special-card-txt-image') ? String(el('special-card-txt-image').value || '').trim() : '', }, }; } diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index 6478626..2407bb0 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -10,7 +10,7 @@ - + @@ -269,12 +269,23 @@
2 · การ์ดพิเศษของชุดนี้ -
+

เลือกการ์ดจากชุดโบนัส /Game/img/special-cards/ (card-1 ถึง card-7) — แสดงในโซน «โบนัสพิเศษ» ตาม mockup สรุปผล · English: Pick one bonus card asset (Time Dilation, Bail Coin, Free Evidence, etc.)

+
+
+ +
+ + + +
-
@@ -1002,6 +1010,6 @@ - + diff --git a/www/html/Game/data/quiz-settings.json b/www/html/Game/data/quiz-settings.json index 42f9d9a..cce4f92 100644 --- a/www/html/Game/data/quiz-settings.json +++ b/www/html/Game/data/quiz-settings.json @@ -290,138 +290,1503 @@ ], "specialQuizSets": { "L1_C1": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การเข้าถึงระบบคอมพิวเตอร์ของผู้อื่นโดยไม่ได้รับอนุญาต ผิดกฎหมายใด?", + "choices": [ + "พ.ร.บ.จราจร", + "พ.ร.บ.ว่าด้วยการกระทำผิดเกี่ยวกับคอมพิวเตอร์", + "กฎหมายแรงงาน", + "กฎหมายที่ดิน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ข้อมูล log การเข้าใช้งานระบบ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานเอกสารอิเล็กทรอนิกส์", + "พยานแวดล้อม", + "ไม่ถือเป็นหลักฐาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การหลอกให้เหยื่อกรอกข้อมูล/โอนเงินผ่านข้อความหรือเว็บปลอม เรียกว่าอะไร?", + "choices": [ + "ฟิชชิง (Phishing)", + "ดีบัก (Debug)", + "แบ็กอัป (Backup)", + "ฟอร์แมต (Format)" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ผู้ที่ทำหน้าที่พิจารณาพิพากษาคดีในศาลคือใคร?", + "choices": [ + "ทนายความ", + "ผู้พิพากษา", + "เสมียนศาล", + "ตำรวจ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "บุคคลที่ถูกกล่าวหาว่ากระทำผิดและถูกฟ้องต่อศาลเรียกว่า?", + "choices": [ + "โจทก์", + "จำเลย", + "พยาน", + "ผู้เสียหาย" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เอกสารที่ศาลออกเพื่ออนุญาตให้จับกุมบุคคลเรียกว่า?", + "choices": [ + "หมายค้น", + "หมายจับ", + "หมายเรียก", + "หมายนัด" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ผู้ที่ว่าความและให้คำปรึกษากฎหมายแก่คู่ความคือ?", + "choices": [ + "ผู้พิพากษา", + "ทนายความ", + "อัยการ", + "พยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "สถานที่ที่ใช้พิจารณาคดีเรียกว่าอะไร?", + "choices": [ + "สถานีตำรวจ", + "ศาล", + "เรือนจำ", + "ที่ว่าการอำเภอ" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 1, + "th": "เพิ่มเวลาเล่นมินิเกมทันที (+10 วินาที)", + "en": "Time Dilation", + "rarity": "common", + "imageUrl": "/Game/img/special-cards/card-1.png", + "txtImageUrl": "/Game/img/special-cards/card-1-txt.png" } }, "L1_C2": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การลักทรัพย์โดยใช้กำลังประทุษร้ายหรือขู่เข็ญ เป็นความผิดฐานใด?", + "choices": [ + "ลักทรัพย์", + "ชิงทรัพย์/ปล้นทรัพย์", + "ยักยอกทรัพย์", + "ฉ้อโกง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ลายนิ้วมือที่พบในที่เกิดเหตุ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานวัตถุ", + "พยานเอกสาร", + "คำรับสารภาพ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การปล้นทรัพย์ที่ร่วมกันตั้งแต่สามคนขึ้นไป กฎหมายกำหนดโทษอย่างไร?", + "choices": [ + "เบากว่าปกติ", + "เท่าเดิม", + "หนักขึ้น", + "ยกเว้นโทษ" + ], + "correctIndex": 2 + }, + { + "type": "mcq", + "text": "ผู้ที่ทำหน้าที่พิจารณาพิพากษาคดีในศาลคือใคร?", + "choices": [ + "ทนายความ", + "ผู้พิพากษา", + "เสมียนศาล", + "ตำรวจ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "บุคคลที่ถูกกล่าวหาว่ากระทำผิดและถูกฟ้องต่อศาลเรียกว่า?", + "choices": [ + "โจทก์", + "จำเลย", + "พยาน", + "ผู้เสียหาย" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เอกสารที่ศาลออกเพื่ออนุญาตให้จับกุมบุคคลเรียกว่า?", + "choices": [ + "หมายค้น", + "หมายจับ", + "หมายเรียก", + "หมายนัด" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ผู้ที่ว่าความและให้คำปรึกษากฎหมายแก่คู่ความคือ?", + "choices": [ + "ผู้พิพากษา", + "ทนายความ", + "อัยการ", + "พยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "สถานที่ที่ใช้พิจารณาคดีเรียกว่าอะไร?", + "choices": [ + "สถานีตำรวจ", + "ศาล", + "เรือนจำ", + "ที่ว่าการอำเภอ" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 2, + "th": "เพิ่มเวลาในห้องพิจารณาคดี", + "en": "Extension", + "rarity": "common", + "imageUrl": "/Game/img/special-cards/card-2.png", + "txtImageUrl": "/Game/img/special-cards/card-2-txt.png" } }, "L1_C3": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "ผู้ที่นำคดีอาญาขึ้นฟ้องต่อศาลแทนรัฐ เรียกว่าอะไร?", + "choices": [ + "ทนายจำเลย", + "พนักงานอัยการ", + "ผู้พิพากษา", + "เจ้าพนักงานบังคับคดี" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักการ 'สันนิษฐานว่าบริสุทธิ์ไว้ก่อน' หมายความว่าอย่างไร?", + "choices": [ + "จำเลยผิดจนกว่าจะพิสูจน์ได้", + "จำเลยบริสุทธิ์จนกว่าจะพิสูจน์ว่าผิด", + "ศาลตัดสินทันที", + "ไม่ต้องมีพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การอุทธรณ์ หมายถึงอะไร?", + "choices": [ + "การยอมรับโทษ", + "การขอให้ศาลสูงพิจารณาคดีใหม่", + "การถอนฟ้อง", + "การประนีประนอม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ผู้ที่ทำหน้าที่พิจารณาพิพากษาคดีในศาลคือใคร?", + "choices": [ + "ทนายความ", + "ผู้พิพากษา", + "เสมียนศาล", + "ตำรวจ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "บุคคลที่ถูกกล่าวหาว่ากระทำผิดและถูกฟ้องต่อศาลเรียกว่า?", + "choices": [ + "โจทก์", + "จำเลย", + "พยาน", + "ผู้เสียหาย" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เอกสารที่ศาลออกเพื่ออนุญาตให้จับกุมบุคคลเรียกว่า?", + "choices": [ + "หมายค้น", + "หมายจับ", + "หมายเรียก", + "หมายนัด" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ผู้ที่ว่าความและให้คำปรึกษากฎหมายแก่คู่ความคือ?", + "choices": [ + "ผู้พิพากษา", + "ทนายความ", + "อัยการ", + "พยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "สถานที่ที่ใช้พิจารณาคดีเรียกว่าอะไร?", + "choices": [ + "สถานีตำรวจ", + "ศาล", + "เรือนจำ", + "ที่ว่าการอำเภอ" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 3, + "th": "ห้ามเล่นมินิเกม 1 ตา", + "en": "Ban Card", + "rarity": "common", + "imageUrl": "/Game/img/special-cards/card-3.png", + "txtImageUrl": "/Game/img/special-cards/card-3-txt.png" } }, "L2_C1": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การเข้าถึงระบบคอมพิวเตอร์ของผู้อื่นโดยไม่ได้รับอนุญาต ผิดกฎหมายใด?", + "choices": [ + "พ.ร.บ.จราจร", + "พ.ร.บ.ว่าด้วยการกระทำผิดเกี่ยวกับคอมพิวเตอร์", + "กฎหมายแรงงาน", + "กฎหมายที่ดิน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ข้อมูล log การเข้าใช้งานระบบ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานเอกสารอิเล็กทรอนิกส์", + "พยานแวดล้อม", + "ไม่ถือเป็นหลักฐาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การหลอกให้เหยื่อกรอกข้อมูล/โอนเงินผ่านข้อความหรือเว็บปลอม เรียกว่าอะไร?", + "choices": [ + "ฟิชชิง (Phishing)", + "ดีบัก (Debug)", + "แบ็กอัป (Backup)", + "ฟอร์แมต (Format)" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "บุคคลที่ได้รับความเสียหายจากการกระทำผิดเรียกว่า?", + "choices": [ + "จำเลย", + "ผู้เสียหาย", + "พยาน", + "อัยการ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การให้การโดยบุคคลที่รู้เห็นเหตุการณ์ คือพยานประเภทใด?", + "choices": [ + "พยานวัตถุ", + "พยานบุคคล", + "พยานเอกสาร", + "พยานแวดล้อม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หมายที่ศาลออกเพื่อให้เข้าตรวจค้นสถานที่เรียกว่า?", + "choices": [ + "หมายจับ", + "หมายค้น", + "หมายเรียก", + "หมายขัง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การที่ผู้ต้องหายอมรับว่าได้กระทำผิดจริงเรียกว่า?", + "choices": [ + "การปฏิเสธ", + "คำรับสารภาพ", + "การอุทธรณ์", + "การไกล่เกลี่ย" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เงินหรือหลักทรัพย์ที่วางเพื่อขอปล่อยตัวชั่วคราวเรียกว่า?", + "choices": [ + "ค่าปรับ", + "หลักประกัน (ประกันตัว)", + "ค่าธรรมเนียมศาล", + "สินบน" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 4, + "th": "การ์ดปิดปาก", + "en": "Silence Card", + "rarity": "common", + "imageUrl": "/Game/img/special-cards/card-4.png", + "txtImageUrl": "/Game/img/special-cards/card-4-txt.png" } }, "L2_C2": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การลักทรัพย์โดยใช้กำลังประทุษร้ายหรือขู่เข็ญ เป็นความผิดฐานใด?", + "choices": [ + "ลักทรัพย์", + "ชิงทรัพย์/ปล้นทรัพย์", + "ยักยอกทรัพย์", + "ฉ้อโกง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ลายนิ้วมือที่พบในที่เกิดเหตุ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานวัตถุ", + "พยานเอกสาร", + "คำรับสารภาพ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การปล้นทรัพย์ที่ร่วมกันตั้งแต่สามคนขึ้นไป กฎหมายกำหนดโทษอย่างไร?", + "choices": [ + "เบากว่าปกติ", + "เท่าเดิม", + "หนักขึ้น", + "ยกเว้นโทษ" + ], + "correctIndex": 2 + }, + { + "type": "mcq", + "text": "บุคคลที่ได้รับความเสียหายจากการกระทำผิดเรียกว่า?", + "choices": [ + "จำเลย", + "ผู้เสียหาย", + "พยาน", + "อัยการ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การให้การโดยบุคคลที่รู้เห็นเหตุการณ์ คือพยานประเภทใด?", + "choices": [ + "พยานวัตถุ", + "พยานบุคคล", + "พยานเอกสาร", + "พยานแวดล้อม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หมายที่ศาลออกเพื่อให้เข้าตรวจค้นสถานที่เรียกว่า?", + "choices": [ + "หมายจับ", + "หมายค้น", + "หมายเรียก", + "หมายขัง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การที่ผู้ต้องหายอมรับว่าได้กระทำผิดจริงเรียกว่า?", + "choices": [ + "การปฏิเสธ", + "คำรับสารภาพ", + "การอุทธรณ์", + "การไกล่เกลี่ย" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เงินหรือหลักทรัพย์ที่วางเพื่อขอปล่อยตัวชั่วคราวเรียกว่า?", + "choices": [ + "ค่าปรับ", + "หลักประกัน (ประกันตัว)", + "ค่าธรรมเนียมศาล", + "สินบน" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 5, + "th": "เหรียญประกันตัว", + "en": "Bail Coin", + "rarity": "common", + "imageUrl": "/Game/img/special-cards/card-5.png", + "txtImageUrl": "/Game/img/special-cards/card-5-txt.png" } }, "L2_C3": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "ผู้ที่นำคดีอาญาขึ้นฟ้องต่อศาลแทนรัฐ เรียกว่าอะไร?", + "choices": [ + "ทนายจำเลย", + "พนักงานอัยการ", + "ผู้พิพากษา", + "เจ้าพนักงานบังคับคดี" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักการ 'สันนิษฐานว่าบริสุทธิ์ไว้ก่อน' หมายความว่าอย่างไร?", + "choices": [ + "จำเลยผิดจนกว่าจะพิสูจน์ได้", + "จำเลยบริสุทธิ์จนกว่าจะพิสูจน์ว่าผิด", + "ศาลตัดสินทันที", + "ไม่ต้องมีพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การอุทธรณ์ หมายถึงอะไร?", + "choices": [ + "การยอมรับโทษ", + "การขอให้ศาลสูงพิจารณาคดีใหม่", + "การถอนฟ้อง", + "การประนีประนอม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "บุคคลที่ได้รับความเสียหายจากการกระทำผิดเรียกว่า?", + "choices": [ + "จำเลย", + "ผู้เสียหาย", + "พยาน", + "อัยการ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การให้การโดยบุคคลที่รู้เห็นเหตุการณ์ คือพยานประเภทใด?", + "choices": [ + "พยานวัตถุ", + "พยานบุคคล", + "พยานเอกสาร", + "พยานแวดล้อม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หมายที่ศาลออกเพื่อให้เข้าตรวจค้นสถานที่เรียกว่า?", + "choices": [ + "หมายจับ", + "หมายค้น", + "หมายเรียก", + "หมายขัง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การที่ผู้ต้องหายอมรับว่าได้กระทำผิดจริงเรียกว่า?", + "choices": [ + "การปฏิเสธ", + "คำรับสารภาพ", + "การอุทธรณ์", + "การไกล่เกลี่ย" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เงินหรือหลักทรัพย์ที่วางเพื่อขอปล่อยตัวชั่วคราวเรียกว่า?", + "choices": [ + "ค่าปรับ", + "หลักประกัน (ประกันตัว)", + "ค่าธรรมเนียมศาล", + "สินบน" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 6, + "th": "รับ coin เพิ่ม", + "en": "Fund", + "rarity": "common", + "imageUrl": "/Game/img/special-cards/card-6.png", + "txtImageUrl": "/Game/img/special-cards/card-6-txt.png" } }, "L3_C1": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การเข้าถึงระบบคอมพิวเตอร์ของผู้อื่นโดยไม่ได้รับอนุญาต ผิดกฎหมายใด?", + "choices": [ + "พ.ร.บ.จราจร", + "พ.ร.บ.ว่าด้วยการกระทำผิดเกี่ยวกับคอมพิวเตอร์", + "กฎหมายแรงงาน", + "กฎหมายที่ดิน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ข้อมูล log การเข้าใช้งานระบบ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานเอกสารอิเล็กทรอนิกส์", + "พยานแวดล้อม", + "ไม่ถือเป็นหลักฐาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การหลอกให้เหยื่อกรอกข้อมูล/โอนเงินผ่านข้อความหรือเว็บปลอม เรียกว่าอะไร?", + "choices": [ + "ฟิชชิง (Phishing)", + "ดีบัก (Debug)", + "แบ็กอัป (Backup)", + "ฟอร์แมต (Format)" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การขอให้ศาลสูงพิจารณาคดีใหม่หลังไม่พอใจคำพิพากษาเรียกว่า?", + "choices": [ + "การฟ้องร้อง", + "การอุทธรณ์/ฎีกา", + "การไกล่เกลี่ย", + "การสืบพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักฐานที่ไม่ได้ชี้โดยตรงแต่ทำให้อนุมานข้อเท็จจริงได้เรียกว่า?", + "choices": [ + "พยานแวดล้อม", + "พยานบุคคล", + "คำรับสารภาพ", + "พยานเอกสาร" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ระยะเวลาที่กฎหมายกำหนดให้ฟ้องคดีได้เรียกว่า?", + "choices": [ + "อายุความ", + "วันนัด", + "กำหนดโทษ", + "ทัณฑ์บน" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การกระทำที่ครบองค์ประกอบความผิดตามกฎหมายอาญาเรียกว่า?", + "choices": [ + "การละเมิด", + "ความผิดอาญา", + "การผิดสัญญา", + "การละเว้น" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เจ้าหน้าที่ที่ทำหน้าที่สืบสวนสอบสวนคดีอาญาคือ?", + "choices": [ + "พนักงานสอบสวน", + "ผู้พิพากษา", + "เสมียนศาล", + "ทนายความ" + ], + "correctIndex": 0 + } + ], "specialCard": { - "th": "", - "en": "", + "cardId": 7, + "th": "รับหลักฐานฟรี", + "en": "Free Evidence", "rarity": "rare", - "imageUrl": "" + "imageUrl": "/Game/img/special-cards/card-7.png", + "txtImageUrl": "/Game/img/special-cards/card--7txt.png" } }, "L3_C2": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การลักทรัพย์โดยใช้กำลังประทุษร้ายหรือขู่เข็ญ เป็นความผิดฐานใด?", + "choices": [ + "ลักทรัพย์", + "ชิงทรัพย์/ปล้นทรัพย์", + "ยักยอกทรัพย์", + "ฉ้อโกง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ลายนิ้วมือที่พบในที่เกิดเหตุ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานวัตถุ", + "พยานเอกสาร", + "คำรับสารภาพ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การปล้นทรัพย์ที่ร่วมกันตั้งแต่สามคนขึ้นไป กฎหมายกำหนดโทษอย่างไร?", + "choices": [ + "เบากว่าปกติ", + "เท่าเดิม", + "หนักขึ้น", + "ยกเว้นโทษ" + ], + "correctIndex": 2 + }, + { + "type": "mcq", + "text": "การขอให้ศาลสูงพิจารณาคดีใหม่หลังไม่พอใจคำพิพากษาเรียกว่า?", + "choices": [ + "การฟ้องร้อง", + "การอุทธรณ์/ฎีกา", + "การไกล่เกลี่ย", + "การสืบพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักฐานที่ไม่ได้ชี้โดยตรงแต่ทำให้อนุมานข้อเท็จจริงได้เรียกว่า?", + "choices": [ + "พยานแวดล้อม", + "พยานบุคคล", + "คำรับสารภาพ", + "พยานเอกสาร" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ระยะเวลาที่กฎหมายกำหนดให้ฟ้องคดีได้เรียกว่า?", + "choices": [ + "อายุความ", + "วันนัด", + "กำหนดโทษ", + "ทัณฑ์บน" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การกระทำที่ครบองค์ประกอบความผิดตามกฎหมายอาญาเรียกว่า?", + "choices": [ + "การละเมิด", + "ความผิดอาญา", + "การผิดสัญญา", + "การละเว้น" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เจ้าหน้าที่ที่ทำหน้าที่สืบสวนสอบสวนคดีอาญาคือ?", + "choices": [ + "พนักงานสอบสวน", + "ผู้พิพากษา", + "เสมียนศาล", + "ทนายความ" + ], + "correctIndex": 0 + } + ], "specialCard": { - "th": "", - "en": "", + "cardId": 1, + "th": "เพิ่มเวลาเล่นมินิเกมทันที (+10 วินาที)", + "en": "Time Dilation", "rarity": "rare", - "imageUrl": "" + "imageUrl": "/Game/img/special-cards/card-1.png", + "txtImageUrl": "/Game/img/special-cards/card-1-txt.png" } }, "L3_C3": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "ผู้ที่นำคดีอาญาขึ้นฟ้องต่อศาลแทนรัฐ เรียกว่าอะไร?", + "choices": [ + "ทนายจำเลย", + "พนักงานอัยการ", + "ผู้พิพากษา", + "เจ้าพนักงานบังคับคดี" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักการ 'สันนิษฐานว่าบริสุทธิ์ไว้ก่อน' หมายความว่าอย่างไร?", + "choices": [ + "จำเลยผิดจนกว่าจะพิสูจน์ได้", + "จำเลยบริสุทธิ์จนกว่าจะพิสูจน์ว่าผิด", + "ศาลตัดสินทันที", + "ไม่ต้องมีพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การอุทธรณ์ หมายถึงอะไร?", + "choices": [ + "การยอมรับโทษ", + "การขอให้ศาลสูงพิจารณาคดีใหม่", + "การถอนฟ้อง", + "การประนีประนอม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การขอให้ศาลสูงพิจารณาคดีใหม่หลังไม่พอใจคำพิพากษาเรียกว่า?", + "choices": [ + "การฟ้องร้อง", + "การอุทธรณ์/ฎีกา", + "การไกล่เกลี่ย", + "การสืบพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักฐานที่ไม่ได้ชี้โดยตรงแต่ทำให้อนุมานข้อเท็จจริงได้เรียกว่า?", + "choices": [ + "พยานแวดล้อม", + "พยานบุคคล", + "คำรับสารภาพ", + "พยานเอกสาร" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ระยะเวลาที่กฎหมายกำหนดให้ฟ้องคดีได้เรียกว่า?", + "choices": [ + "อายุความ", + "วันนัด", + "กำหนดโทษ", + "ทัณฑ์บน" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การกระทำที่ครบองค์ประกอบความผิดตามกฎหมายอาญาเรียกว่า?", + "choices": [ + "การละเมิด", + "ความผิดอาญา", + "การผิดสัญญา", + "การละเว้น" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "เจ้าหน้าที่ที่ทำหน้าที่สืบสวนสอบสวนคดีอาญาคือ?", + "choices": [ + "พนักงานสอบสวน", + "ผู้พิพากษา", + "เสมียนศาล", + "ทนายความ" + ], + "correctIndex": 0 + } + ], "specialCard": { - "th": "", - "en": "", + "cardId": 2, + "th": "เพิ่มเวลาในห้องพิจารณาคดี", + "en": "Extension", "rarity": "rare", - "imageUrl": "" + "imageUrl": "/Game/img/special-cards/card-2.png", + "txtImageUrl": "/Game/img/special-cards/card-2-txt.png" } }, "L4_C1": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การเข้าถึงระบบคอมพิวเตอร์ของผู้อื่นโดยไม่ได้รับอนุญาต ผิดกฎหมายใด?", + "choices": [ + "พ.ร.บ.จราจร", + "พ.ร.บ.ว่าด้วยการกระทำผิดเกี่ยวกับคอมพิวเตอร์", + "กฎหมายแรงงาน", + "กฎหมายที่ดิน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ข้อมูล log การเข้าใช้งานระบบ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานเอกสารอิเล็กทรอนิกส์", + "พยานแวดล้อม", + "ไม่ถือเป็นหลักฐาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การหลอกให้เหยื่อกรอกข้อมูล/โอนเงินผ่านข้อความหรือเว็บปลอม เรียกว่าอะไร?", + "choices": [ + "ฟิชชิง (Phishing)", + "ดีบัก (Debug)", + "แบ็กอัป (Backup)", + "ฟอร์แมต (Format)" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "หลักการที่ห้ามนำบุคคลมาฟ้องซ้ำในความผิดเดียวกันเรียกว่า?", + "choices": [ + "อายุความ", + "หลักห้ามฟ้องซ้ำ", + "หลักสันนิษฐานบริสุทธิ์", + "ภาระการพิสูจน์" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ภาระการพิสูจน์ในคดีอาญาตกอยู่ที่ฝ่ายใด?", + "choices": [ + "จำเลย", + "โจทก์/พนักงานอัยการ", + "ศาล", + "พยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การกระทำผิดโดยไม่มีเจตนาแต่ขาดความระมัดระวังเรียกว่า?", + "choices": [ + "เจตนา", + "ประมาท", + "บันดาลโทสะ", + "ป้องกันโดยชอบ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "คำสั่งศาลให้คุมขังผู้ต้องหาระหว่างพิจารณาเรียกว่า?", + "choices": [ + "หมายขัง", + "หมายค้น", + "หมายเรียก", + "หมายปล่อย" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การยุติข้อพิพาทโดยมีคนกลางช่วยให้ตกลงกันเรียกว่า?", + "choices": [ + "การพิพากษา", + "การไกล่เกลี่ย", + "การอุทธรณ์", + "การบังคับคดี" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", + "cardId": 3, + "th": "ห้ามเล่นมินิเกม 1 ตา", + "en": "Ban Card", "rarity": "rare", - "imageUrl": "" + "imageUrl": "/Game/img/special-cards/card-3.png", + "txtImageUrl": "/Game/img/special-cards/card-3-txt.png" } }, "L4_C2": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การลักทรัพย์โดยใช้กำลังประทุษร้ายหรือขู่เข็ญ เป็นความผิดฐานใด?", + "choices": [ + "ลักทรัพย์", + "ชิงทรัพย์/ปล้นทรัพย์", + "ยักยอกทรัพย์", + "ฉ้อโกง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ลายนิ้วมือที่พบในที่เกิดเหตุ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานวัตถุ", + "พยานเอกสาร", + "คำรับสารภาพ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การปล้นทรัพย์ที่ร่วมกันตั้งแต่สามคนขึ้นไป กฎหมายกำหนดโทษอย่างไร?", + "choices": [ + "เบากว่าปกติ", + "เท่าเดิม", + "หนักขึ้น", + "ยกเว้นโทษ" + ], + "correctIndex": 2 + }, + { + "type": "mcq", + "text": "หลักการที่ห้ามนำบุคคลมาฟ้องซ้ำในความผิดเดียวกันเรียกว่า?", + "choices": [ + "อายุความ", + "หลักห้ามฟ้องซ้ำ", + "หลักสันนิษฐานบริสุทธิ์", + "ภาระการพิสูจน์" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ภาระการพิสูจน์ในคดีอาญาตกอยู่ที่ฝ่ายใด?", + "choices": [ + "จำเลย", + "โจทก์/พนักงานอัยการ", + "ศาล", + "พยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การกระทำผิดโดยไม่มีเจตนาแต่ขาดความระมัดระวังเรียกว่า?", + "choices": [ + "เจตนา", + "ประมาท", + "บันดาลโทสะ", + "ป้องกันโดยชอบ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "คำสั่งศาลให้คุมขังผู้ต้องหาระหว่างพิจารณาเรียกว่า?", + "choices": [ + "หมายขัง", + "หมายค้น", + "หมายเรียก", + "หมายปล่อย" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การยุติข้อพิพาทโดยมีคนกลางช่วยให้ตกลงกันเรียกว่า?", + "choices": [ + "การพิพากษา", + "การไกล่เกลี่ย", + "การอุทธรณ์", + "การบังคับคดี" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", + "cardId": 4, + "th": "การ์ดปิดปาก", + "en": "Silence Card", "rarity": "rare", - "imageUrl": "" + "imageUrl": "/Game/img/special-cards/card-4.png", + "txtImageUrl": "/Game/img/special-cards/card-4-txt.png" } }, "L4_C3": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "ผู้ที่นำคดีอาญาขึ้นฟ้องต่อศาลแทนรัฐ เรียกว่าอะไร?", + "choices": [ + "ทนายจำเลย", + "พนักงานอัยการ", + "ผู้พิพากษา", + "เจ้าพนักงานบังคับคดี" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักการ 'สันนิษฐานว่าบริสุทธิ์ไว้ก่อน' หมายความว่าอย่างไร?", + "choices": [ + "จำเลยผิดจนกว่าจะพิสูจน์ได้", + "จำเลยบริสุทธิ์จนกว่าจะพิสูจน์ว่าผิด", + "ศาลตัดสินทันที", + "ไม่ต้องมีพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การอุทธรณ์ หมายถึงอะไร?", + "choices": [ + "การยอมรับโทษ", + "การขอให้ศาลสูงพิจารณาคดีใหม่", + "การถอนฟ้อง", + "การประนีประนอม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักการที่ห้ามนำบุคคลมาฟ้องซ้ำในความผิดเดียวกันเรียกว่า?", + "choices": [ + "อายุความ", + "หลักห้ามฟ้องซ้ำ", + "หลักสันนิษฐานบริสุทธิ์", + "ภาระการพิสูจน์" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ภาระการพิสูจน์ในคดีอาญาตกอยู่ที่ฝ่ายใด?", + "choices": [ + "จำเลย", + "โจทก์/พนักงานอัยการ", + "ศาล", + "พยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การกระทำผิดโดยไม่มีเจตนาแต่ขาดความระมัดระวังเรียกว่า?", + "choices": [ + "เจตนา", + "ประมาท", + "บันดาลโทสะ", + "ป้องกันโดยชอบ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "คำสั่งศาลให้คุมขังผู้ต้องหาระหว่างพิจารณาเรียกว่า?", + "choices": [ + "หมายขัง", + "หมายค้น", + "หมายเรียก", + "หมายปล่อย" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "การยุติข้อพิพาทโดยมีคนกลางช่วยให้ตกลงกันเรียกว่า?", + "choices": [ + "การพิพากษา", + "การไกล่เกลี่ย", + "การอุทธรณ์", + "การบังคับคดี" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", + "cardId": 5, + "th": "เหรียญประกันตัว", + "en": "Bail Coin", "rarity": "rare", - "imageUrl": "" + "imageUrl": "/Game/img/special-cards/card-5.png", + "txtImageUrl": "/Game/img/special-cards/card-5-txt.png" } }, "L5_C1": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การเข้าถึงระบบคอมพิวเตอร์ของผู้อื่นโดยไม่ได้รับอนุญาต ผิดกฎหมายใด?", + "choices": [ + "พ.ร.บ.จราจร", + "พ.ร.บ.ว่าด้วยการกระทำผิดเกี่ยวกับคอมพิวเตอร์", + "กฎหมายแรงงาน", + "กฎหมายที่ดิน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ข้อมูล log การเข้าใช้งานระบบ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานเอกสารอิเล็กทรอนิกส์", + "พยานแวดล้อม", + "ไม่ถือเป็นหลักฐาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การหลอกให้เหยื่อกรอกข้อมูล/โอนเงินผ่านข้อความหรือเว็บปลอม เรียกว่าอะไร?", + "choices": [ + "ฟิชชิง (Phishing)", + "ดีบัก (Debug)", + "แบ็กอัป (Backup)", + "ฟอร์แมต (Format)" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "พยานหลักฐานที่ได้มาโดยมิชอบด้วยกฎหมาย โดยหลักแล้วศาลจะ?", + "choices": [ + "รับฟังได้เสมอ", + "อาจรับฟังไม่ได้ (ต้องห้าม)", + "ใช้ลงโทษได้ทันที", + "ส่งคืนเท่านั้น" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลัก 'ยกประโยชน์แห่งความสงสัยให้จำเลย' ใช้เมื่อใด?", + "choices": [ + "เมื่อพยานชัดเจน", + "เมื่อมีข้อสงสัยตามสมควร", + "เมื่อจำเลยรับสารภาพ", + "เมื่ออัยการร้องขอ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การร่วมกระทำผิดโดยแบ่งหน้าที่กันทำ ถือว่าเป็น?", + "choices": [ + "ตัวการร่วม", + "ผู้ใช้", + "ผู้สนับสนุน", + "ผู้รู้เห็น" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ความผิดที่ยอมความได้ หมายความว่า?", + "choices": [ + "ฟ้องไม่ได้", + "ผู้เสียหายถอนคำร้องทำให้คดีระงับได้", + "ศาลต้องตัดสินเสมอ", + "ไม่มีโทษ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การกระทำเพื่อป้องกันสิทธิของตนพอสมควรแก่เหตุเรียกว่า?", + "choices": [ + "บันดาลโทสะ", + "ป้องกันโดยชอบด้วยกฎหมาย", + "ความจำเป็น", + "ประมาท" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 6, + "th": "รับ coin เพิ่ม", + "en": "Fund", + "rarity": "legendary", + "imageUrl": "/Game/img/special-cards/card-6.png", + "txtImageUrl": "/Game/img/special-cards/card-6-txt.png" } }, "L5_C2": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "การลักทรัพย์โดยใช้กำลังประทุษร้ายหรือขู่เข็ญ เป็นความผิดฐานใด?", + "choices": [ + "ลักทรัพย์", + "ชิงทรัพย์/ปล้นทรัพย์", + "ยักยอกทรัพย์", + "ฉ้อโกง" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "ลายนิ้วมือที่พบในที่เกิดเหตุ จัดเป็นพยานหลักฐานประเภทใด?", + "choices": [ + "พยานบุคคล", + "พยานวัตถุ", + "พยานเอกสาร", + "คำรับสารภาพ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การปล้นทรัพย์ที่ร่วมกันตั้งแต่สามคนขึ้นไป กฎหมายกำหนดโทษอย่างไร?", + "choices": [ + "เบากว่าปกติ", + "เท่าเดิม", + "หนักขึ้น", + "ยกเว้นโทษ" + ], + "correctIndex": 2 + }, + { + "type": "mcq", + "text": "พยานหลักฐานที่ได้มาโดยมิชอบด้วยกฎหมาย โดยหลักแล้วศาลจะ?", + "choices": [ + "รับฟังได้เสมอ", + "อาจรับฟังไม่ได้ (ต้องห้าม)", + "ใช้ลงโทษได้ทันที", + "ส่งคืนเท่านั้น" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลัก 'ยกประโยชน์แห่งความสงสัยให้จำเลย' ใช้เมื่อใด?", + "choices": [ + "เมื่อพยานชัดเจน", + "เมื่อมีข้อสงสัยตามสมควร", + "เมื่อจำเลยรับสารภาพ", + "เมื่ออัยการร้องขอ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การร่วมกระทำผิดโดยแบ่งหน้าที่กันทำ ถือว่าเป็น?", + "choices": [ + "ตัวการร่วม", + "ผู้ใช้", + "ผู้สนับสนุน", + "ผู้รู้เห็น" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ความผิดที่ยอมความได้ หมายความว่า?", + "choices": [ + "ฟ้องไม่ได้", + "ผู้เสียหายถอนคำร้องทำให้คดีระงับได้", + "ศาลต้องตัดสินเสมอ", + "ไม่มีโทษ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การกระทำเพื่อป้องกันสิทธิของตนพอสมควรแก่เหตุเรียกว่า?", + "choices": [ + "บันดาลโทสะ", + "ป้องกันโดยชอบด้วยกฎหมาย", + "ความจำเป็น", + "ประมาท" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 7, + "th": "รับหลักฐานฟรี", + "en": "Free Evidence", + "rarity": "legendary", + "imageUrl": "/Game/img/special-cards/card-7.png", + "txtImageUrl": "/Game/img/special-cards/card--7txt.png" } }, "L5_C3": { - "questions": [], + "questions": [ + { + "type": "mcq", + "text": "ผู้ที่นำคดีอาญาขึ้นฟ้องต่อศาลแทนรัฐ เรียกว่าอะไร?", + "choices": [ + "ทนายจำเลย", + "พนักงานอัยการ", + "ผู้พิพากษา", + "เจ้าพนักงานบังคับคดี" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลักการ 'สันนิษฐานว่าบริสุทธิ์ไว้ก่อน' หมายความว่าอย่างไร?", + "choices": [ + "จำเลยผิดจนกว่าจะพิสูจน์ได้", + "จำเลยบริสุทธิ์จนกว่าจะพิสูจน์ว่าผิด", + "ศาลตัดสินทันที", + "ไม่ต้องมีพยาน" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การอุทธรณ์ หมายถึงอะไร?", + "choices": [ + "การยอมรับโทษ", + "การขอให้ศาลสูงพิจารณาคดีใหม่", + "การถอนฟ้อง", + "การประนีประนอม" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "พยานหลักฐานที่ได้มาโดยมิชอบด้วยกฎหมาย โดยหลักแล้วศาลจะ?", + "choices": [ + "รับฟังได้เสมอ", + "อาจรับฟังไม่ได้ (ต้องห้าม)", + "ใช้ลงโทษได้ทันที", + "ส่งคืนเท่านั้น" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "หลัก 'ยกประโยชน์แห่งความสงสัยให้จำเลย' ใช้เมื่อใด?", + "choices": [ + "เมื่อพยานชัดเจน", + "เมื่อมีข้อสงสัยตามสมควร", + "เมื่อจำเลยรับสารภาพ", + "เมื่ออัยการร้องขอ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การร่วมกระทำผิดโดยแบ่งหน้าที่กันทำ ถือว่าเป็น?", + "choices": [ + "ตัวการร่วม", + "ผู้ใช้", + "ผู้สนับสนุน", + "ผู้รู้เห็น" + ], + "correctIndex": 0 + }, + { + "type": "mcq", + "text": "ความผิดที่ยอมความได้ หมายความว่า?", + "choices": [ + "ฟ้องไม่ได้", + "ผู้เสียหายถอนคำร้องทำให้คดีระงับได้", + "ศาลต้องตัดสินเสมอ", + "ไม่มีโทษ" + ], + "correctIndex": 1 + }, + { + "type": "mcq", + "text": "การกระทำเพื่อป้องกันสิทธิของตนพอสมควรแก่เหตุเรียกว่า?", + "choices": [ + "บันดาลโทสะ", + "ป้องกันโดยชอบด้วยกฎหมาย", + "ความจำเป็น", + "ประมาท" + ], + "correctIndex": 1 + } + ], "specialCard": { - "th": "", - "en": "", - "rarity": "rare", - "imageUrl": "" + "cardId": 1, + "th": "เพิ่มเวลาเล่นมินิเกมทันที (+10 วินาที)", + "en": "Time Dilation", + "rarity": "legendary", + "imageUrl": "/Game/img/special-cards/card-1.png", + "txtImageUrl": "/Game/img/special-cards/card-1-txt.png" } } } diff --git a/www/html/Game/public/img/special-cards b/www/html/Game/public/img/special-cards new file mode 120000 index 0000000..432d102 --- /dev/null +++ b/www/html/Game/public/img/special-cards @@ -0,0 +1 @@ +../trycss/10-Game-1-result \ No newline at end of file diff --git a/www/html/Game/public/img/special-quiz/icon-lawyer.png b/www/html/Game/public/img/special-quiz/icon-lawyer.png new file mode 100644 index 0000000..de853c3 Binary files /dev/null and b/www/html/Game/public/img/special-quiz/icon-lawyer.png differ diff --git a/www/html/Game/public/img/special-quiz/icon-police.png b/www/html/Game/public/img/special-quiz/icon-police.png new file mode 100644 index 0000000..dba4260 Binary files /dev/null and b/www/html/Game/public/img/special-quiz/icon-police.png differ diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index 752c4e6..6127fd8 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -8806,6 +8806,8 @@ pl.appendChild(s); cardTextEl.appendChild(pl); } + /* การ์ดพิเศษจากชุดคำถาม (ได้มาจากการตอบคำถามพิเศษในฉาก) — แสดงเสมอถ้าได้รับ */ + if (specialQuizAwardedCard) renderSpecialCardInBonus(cardTextEl, specialQuizAwardedCard); } overlay.classList.remove('is-hidden'); const btn = document.getElementById('btn-quiz-carry-mission-done'); @@ -11309,25 +11311,31 @@ /** วาดการ์ดพิเศษลงในโซนโบนัสของสรุปผล (#gcm-bonus / .sm-bonus-right) */ function renderSpecialCardInBonus(bonusEl, card) { - if (!bonusEl || !card || !(card.th || card.en || card.imageUrl)) return; + if (!bonusEl || !card || !(card.th || card.en || card.imageUrl || card.cardId)) return; + const imgUrl = card.imageUrl || (card.cardId >= 1 && card.cardId <= 7 + ? '/Game/img/special-cards/card-' + card.cardId + '.png' : ''); + if (!imgUrl) return; + const isPresetAsset = imgUrl.indexOf('/img/special-cards/') >= 0 || imgUrl.indexOf('/trycss/10-Game-1-result/') >= 0; const wrap = document.createElement('div'); - wrap.className = 'gcm-special-card sm-bonus-special rarity-' + (card.rarity || 'rare'); - if (card.imageUrl) { - const img = document.createElement('img'); - img.className = 'gcm-special-card-img'; - img.src = card.imageUrl; - img.alt = card.th || card.en || ''; - wrap.appendChild(img); + wrap.className = isPresetAsset + ? 'sm-bonus-right gcm-special-card-preset' + : 'gcm-special-card sm-bonus-special rarity-' + (card.rarity || 'rare'); + const img = document.createElement('img'); + img.className = isPresetAsset ? 'bail-card gcm-special-card-img' : 'gcm-special-card-img'; + img.src = imgUrl; + img.alt = card.th || card.en || ''; + wrap.appendChild(img); + if (!isPresetAsset) { + const cap = document.createElement('div'); + cap.className = 'gcm-special-card-cap'; + const strong = document.createElement('strong'); + strong.textContent = card.th || ''; + const span = document.createElement('span'); + span.textContent = card.en || ''; + cap.appendChild(strong); + cap.appendChild(span); + wrap.appendChild(cap); } - const cap = document.createElement('div'); - cap.className = 'gcm-special-card-cap'; - const strong = document.createElement('strong'); - strong.textContent = card.th || ''; - const span = document.createElement('span'); - span.textContent = card.en || ''; - cap.appendChild(strong); - cap.appendChild(span); - wrap.appendChild(cap); bonusEl.appendChild(wrap); } @@ -11340,6 +11348,211 @@ renderCardInBonus: renderSpecialCardInBonus, }; + /* ===== Special Quiz ในเกม — ไอคอนในฉาก (เดินชน) + overlay คำถาม multiplayer ===== + * server เป็นผู้ตัดสิน: สุ่มไอคอน 1/3, เปิดคำถามให้ทุกคนพร้อมกัน, ตอบถูกทุกคนทุกข้อ -> ได้การ์ด + */ + const SPECIAL_QUIZ_CHOICE_LABELS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; + let specialQuizIcon = null; // { iconType, x, y } (พิกัด tile-center) + let specialQuizCollideSent = false; + let specialQuizAwardedCard = null; // การ์ดที่ได้รอบนี้ (จาก special-quiz-ended) + let specialQuizActiveQuestion = null; // { index, total, choices, endsAt, iconType } + let specialQuizAnswered = false; + let specialQuizTimerRaf = 0; + const specialQuizIconImgs = {}; + + function specialQuizIconUrl(type) { + return '/Game/img/special-quiz/icon-' + (type === 'police' ? 'police' : 'lawyer') + '.png'; + } + function specialQuizGetIconImg(type) { + const key = type === 'police' ? 'police' : 'lawyer'; + if (specialQuizIconImgs[key]) return specialQuizIconImgs[key]; + const img = new Image(); + img.src = specialQuizIconUrl(key); + specialQuizIconImgs[key] = img; + return img; + } + function setSpecialQuizIconFromServer(payload) { + if (!payload || payload.x == null || payload.y == null) { specialQuizIcon = null; return; } + specialQuizIcon = { + iconType: payload.iconType === 'police' ? 'police' : 'lawyer', + x: Number(payload.x), + y: Number(payload.y), + }; + specialQuizCollideSent = false; + specialQuizGetIconImg(specialQuizIcon.iconType); + } + function clearSpecialQuizIcon() { + specialQuizIcon = null; + specialQuizCollideSent = false; + } + + /** วาดไอคอนในฉาก — ใช้ worldToScreen + zDraw จาก draw() เพื่อให้ตรงทุกมินิเกม */ + function drawSpecialQuizIconWorld(w2s, zDraw) { + if (!specialQuizIcon || typeof w2s !== 'function') return; + const img = specialQuizGetIconImg(specialQuizIcon.iconType); + const sc = w2s(specialQuizIcon.x * tileSize, specialQuizIcon.y * tileSize); + const sx = sc[0]; + const sy = sc[1]; + if (!Number.isFinite(sx) || !Number.isFinite(sy)) return; + const size = Math.max(30, tileSize * zDraw * 1.75); + const bob = Math.sin(Date.now() / 320) * size * 0.06; + const cy = sy + bob; + ctx.save(); + const r = size * 0.62; + const grad = ctx.createRadialGradient(sx, cy, r * 0.18, sx, cy, r); + grad.addColorStop(0, 'rgba(122, 162, 247, 0.5)'); + grad.addColorStop(1, 'rgba(122, 162, 247, 0)'); + ctx.fillStyle = grad; + ctx.beginPath(); + ctx.arc(sx, cy, r, 0, Math.PI * 2); + ctx.fill(); + if (img && img.complete && img.naturalWidth > 0) { + ctx.drawImage(img, sx - size / 2, cy - size / 2, size, size); + } else { + ctx.fillStyle = '#7aa2f7'; + ctx.fillRect(sx - size / 2, cy - size / 2, size, size); + } + ctx.fillStyle = '#ffd166'; + ctx.font = '700 ' + Math.round(size * 0.42) + 'px system-ui, "Kanit", sans-serif'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText('?', sx + size * 0.4, cy - size * 0.38); + ctx.restore(); + } + + /** เช็คผู้เล่นเดินชนไอคอน — ใครชนก่อนเป็นคนเปิดคำถามให้ทั้งห้อง (server กันซ้ำ) */ + function checkSpecialQuizCollision() { + if (!specialQuizIcon || specialQuizCollideSent || specialQuizActiveQuestion) return; + if (!Number.isFinite(me.x) || !Number.isFinite(me.y)) return; + const dx = me.x - specialQuizIcon.x; + const dy = me.y - specialQuizIcon.y; + if (dx * dx + dy * dy <= 0.85 * 0.85) { + specialQuizCollideSent = true; + try { socket.emit('special-quiz-collide', {}, function () {}); } catch (e) { /* ignore */ } + } + } + + function specialQuizOverlayEl() { return document.getElementById('special-quiz-overlay'); } + + function openSpecialQuizQuestion(q) { + if (!q) return; + clearSpecialQuizIcon(); + specialQuizActiveQuestion = { + index: q.index | 0, + total: q.total | 0, + choices: Array.isArray(q.choices) ? q.choices : [], + endsAt: Number(q.endsAt) || 0, + iconType: q.iconType, + }; + specialQuizAnswered = false; + const ov = specialQuizOverlayEl(); + if (!ov) return; + const iconEl = document.getElementById('sq-ov-icon'); + if (iconEl) iconEl.src = specialQuizIconUrl(q.iconType); + const prog = document.getElementById('sq-ov-progress'); + if (prog) prog.textContent = 'ข้อ ' + ((q.index | 0) + 1) + ' / ' + (q.total | 0); + const qEl = document.getElementById('sq-ov-question'); + if (qEl) qEl.textContent = q.text || ''; + const result = document.getElementById('sq-ov-result'); + if (result) { result.classList.add('is-hidden'); result.textContent = ''; result.className = 'sq-ov-result is-hidden'; } + const status = document.getElementById('sq-ov-status'); + if (status) status.textContent = 'เลือกคำตอบ — ทุกคนต้องตอบถูกทุกข้อจึงได้การ์ดพิเศษ'; + const choicesEl = document.getElementById('sq-ov-choices'); + if (choicesEl) { + choicesEl.innerHTML = ''; + specialQuizActiveQuestion.choices.forEach(function (c, i) { + const b = document.createElement('button'); + b.type = 'button'; + b.className = 'sq-ov-choice'; + b.dataset.idx = String(i); + const k = document.createElement('span'); + k.className = 'sq-ov-choice-key'; + k.textContent = SPECIAL_QUIZ_CHOICE_LABELS[i] || String(i + 1); + const t = document.createElement('span'); + t.className = 'sq-ov-choice-text'; + t.textContent = c; + b.appendChild(k); + b.appendChild(t); + b.onclick = function () { submitSpecialQuizAnswer(i); }; + choicesEl.appendChild(b); + }); + } + ov.classList.remove('is-hidden'); + startSpecialQuizTimerLoop(); + } + + function submitSpecialQuizAnswer(i) { + if (specialQuizAnswered || !specialQuizActiveQuestion) return; + specialQuizAnswered = true; + const choicesEl = document.getElementById('sq-ov-choices'); + if (choicesEl) { + Array.prototype.forEach.call(choicesEl.children, function (b) { + b.classList.toggle('sq-ov-choice--picked', Number(b.dataset.idx) === i); + b.disabled = true; + }); + } + const status = document.getElementById('sq-ov-status'); + if (status) status.textContent = 'ส่งคำตอบแล้ว — รอผู้เล่นคนอื่น...'; + try { socket.emit('special-quiz-answer', { choiceIndex: i }, function () {}); } catch (e) { /* ignore */ } + } + + function startSpecialQuizTimerLoop() { + cancelAnimationFrame(specialQuizTimerRaf); + function tick() { + if (!specialQuizActiveQuestion) return; + const timerEl = document.getElementById('sq-ov-timer'); + const ms = specialQuizActiveQuestion.endsAt - Date.now(); + const s = Math.max(0, Math.ceil(ms / 1000)); + if (timerEl) { + timerEl.textContent = String(s); + timerEl.classList.toggle('sq-ov-timer--low', s <= 5); + } + if (ms > 0) specialQuizTimerRaf = requestAnimationFrame(tick); + } + tick(); + } + + function handleSpecialQuizResult(r) { + if (!r || !specialQuizActiveQuestion) return; + const choicesEl = document.getElementById('sq-ov-choices'); + if (choicesEl) { + Array.prototype.forEach.call(choicesEl.children, function (b) { + const idx = Number(b.dataset.idx); + b.disabled = true; + if (idx === r.correctIndex) b.classList.add('sq-ov-choice--correct'); + else if (b.classList.contains('sq-ov-choice--picked')) b.classList.add('sq-ov-choice--wrong'); + }); + } + const result = document.getElementById('sq-ov-result'); + if (result) { + result.classList.remove('is-hidden'); + result.textContent = r.allCorrect + ? 'ทุกคนตอบถูก!' + : (r.timeout ? 'หมดเวลา — ตอบไม่ครบทุกคน' : 'มีคนตอบผิด — ไม่ได้การ์ดพิเศษ'); + result.className = 'sq-ov-result ' + (r.allCorrect ? 'sq-ov-result--ok' : 'sq-ov-result--fail'); + } + cancelAnimationFrame(specialQuizTimerRaf); + } + + function endSpecialQuiz(data) { + cancelAnimationFrame(specialQuizTimerRaf); + specialQuizActiveQuestion = null; + specialQuizAnswered = false; + if (data && data.success && data.card) specialQuizAwardedCard = data.card; + const ov = specialQuizOverlayEl(); + const result = document.getElementById('sq-ov-result'); + if (result) { + result.classList.remove('is-hidden'); + result.textContent = (data && data.success) + ? 'ได้รับการ์ดพิเศษ! ดูในสรุปผลภารกิจ' + : 'รอบนี้ไม่ได้การ์ดพิเศษ'; + result.className = 'sq-ov-result ' + ((data && data.success) ? 'sq-ov-result--ok' : 'sq-ov-result--fail'); + } + const status = document.getElementById('sq-ov-status'); + if (status) status.textContent = ''; + if (ov) setTimeout(function () { ov.classList.add('is-hidden'); }, 1900); + } + function showGauntletCrownMissionOverlay(mission) { const ov = document.getElementById('gauntlet-crown-mission-overlay'); const rowEl = document.getElementById('gcm-rank-row'); @@ -11348,6 +11561,7 @@ const bonusEl = document.getElementById('gcm-bonus'); const btn = document.getElementById('btn-gcm-done'); if (!ov || !rowEl || !mission || !Array.isArray(mission.ranked)) return; + if (!mission.specialCardAwarded && specialQuizAwardedCard) mission.specialCardAwarded = specialQuizAwardedCard; let disp = gauntletCrownBuildDisplayMission(mission); if (mission.uiSkin === 'jumper') { disp = jumpSurviveMergePreviewBotsMission(disp) || disp; @@ -15254,6 +15468,7 @@ quizCarryPlaqueMapScale = Math.max(0.85, Math.min(2.5, joinSc)); } } + if (res.specialQuizIcon) setSpecialQuizIconFromServer(res.specialQuizIcon); if (previewMode && editorEmbedReturn && res.mapData && res.mapData.gameType === 'lobby') { const q = []; q.push('space=' + encodeURIComponent(spaceId)); @@ -15627,6 +15842,25 @@ } }); + /* ===== Special Quiz ในเกม — socket events ===== */ + socket.on('special-quiz-icon', (payload) => { + if (previewMode) return; + setSpecialQuizIconFromServer(payload); + }); + socket.on('special-quiz-icon-clear', () => { clearSpecialQuizIcon(); }); + socket.on('special-quiz-question', (q) => { + if (previewMode) return; + openSpecialQuizQuestion(q); + }); + socket.on('special-quiz-result', (r) => { + if (previewMode) return; + handleSpecialQuizResult(r); + }); + socket.on('special-quiz-ended', (data) => { + if (previewMode) return; + endSpecialQuiz(data); + }); + socket.on('gauntlet-sync', (data) => { if (!data || typeof data !== 'object') return; if (isMegaVirusMissionShellMapPlay()) { @@ -19005,6 +19239,11 @@ } }); } + /* ไอคอนคำถามพิเศษในฉาก (วาดทับ map/ผู้เล่น) + ตรวจการเดินชน */ + if (specialQuizIcon) { + drawSpecialQuizIconWorld(worldToScreen, zDraw); + checkSpecialQuizCollision(); + } if (isFrogger()) { ctx.fillStyle = '#7aa2f7'; ctx.font = 'bold 14px sans-serif'; diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index dd37e88..b9f84a2 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -3531,6 +3531,19 @@ object-fit: contain; filter: drop-shadow(0 0 10px rgba(91, 255, 141, 0.45)); } + /* การ์ดพิเศษจาก trycss/10-Game-1-result (mockup .bail-card) */ + #gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-bonus img.bail-card, + #gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-bonus img.gcm-special-card-img { + width: auto !important; + height: 258px !important; + max-width: 152px !important; + max-height: none !important; + filter: drop-shadow(0 0 14px rgba(120, 220, 255, 0.55)); + } + #gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-bonus .gcm-special-card-preset { + display: block; + margin-top: 8px; + } #gauntlet-crown-mission-overlay.gcm-quiz-tf-mock .gcm-plaque { display: none !important; } @@ -3857,10 +3870,57 @@ + + - +
v —
diff --git a/www/html/Game/server.js b/www/html/Game/server.js index e8d5693..2a146c1 100644 --- a/www/html/Game/server.js +++ b/www/html/Game/server.js @@ -287,6 +287,19 @@ const defaultMap = () => ({ const SPECIAL_QUIZ_LEVELS = [1, 2, 3, 4, 5]; const SPECIAL_QUIZ_CASES = [1, 2, 3]; const SPECIAL_QUIZ_CARD_RARITIES = ['common', 'rare', 'legendary']; +const SPECIAL_CARD_ASSET_BASE = '/Game/img/special-cards/'; + +function specialCardTxtFilename(id) { + return id === 7 ? 'card--7txt.png' : `card-${id}-txt.png`; +} + +function specialCardImageUrl(id) { + return `${SPECIAL_CARD_ASSET_BASE}card-${id}.png`; +} + +function specialCardTxtImageUrl(id) { + return `${SPECIAL_CARD_ASSET_BASE}${specialCardTxtFilename(id)}`; +} function specialQuizSetKey(level, caseId) { return 'L' + level + '_C' + caseId; @@ -318,11 +331,28 @@ function sanitizeSpecialQuizQuestion(q) { function sanitizeSpecialCard(c) { const src = (c && typeof c === 'object') ? c : {}; const rarity = SPECIAL_QUIZ_CARD_RARITIES.indexOf(src.rarity) >= 0 ? src.rarity : 'rare'; + let cardId = Number(src.cardId); + if (!(cardId >= 1 && cardId <= 7)) cardId = null; + let imageUrl = String(src.imageUrl == null ? '' : src.imageUrl).trim().slice(0, 600); + let txtImageUrl = String(src.txtImageUrl == null ? '' : src.txtImageUrl).trim().slice(0, 600); + if (cardId) { + if (!imageUrl) imageUrl = specialCardImageUrl(cardId); + if (!txtImageUrl) txtImageUrl = specialCardTxtImageUrl(cardId); + } else if (imageUrl) { + const m = imageUrl.match(/\/card-(\d)\.png/i); + if (m) { + cardId = Number(m[1]); + imageUrl = specialCardImageUrl(cardId); + if (!txtImageUrl) txtImageUrl = specialCardTxtImageUrl(cardId); + } + } return { + cardId: cardId || null, th: String(src.th == null ? '' : src.th).trim().slice(0, 160), en: String(src.en == null ? '' : src.en).trim().slice(0, 160), rarity, - imageUrl: String(src.imageUrl == null ? '' : src.imageUrl).trim().slice(0, 600), + imageUrl, + txtImageUrl, }; } @@ -349,6 +379,263 @@ function getSpecialQuizSet(settings, level, caseId) { return sets[specialQuizSetKey(L, C)] || sets[specialQuizSetKey(1, 1)] || { questions: [], specialCard: sanitizeSpecialCard({}) }; } +/* ===== Special Quiz ในเกม — ไอคอนสุ่มในฉาก + เซสชันถาม-ตอบ multiplayer (ทุกคนต้องตอบถูก) ===== */ +/** เฉพาะ 4 มินิเกมนี้ (ตอนสืบสวน) ที่ไอคอนพิเศษจะสุ่มโผล่ */ +const SPECIAL_QUIZ_ELIGIBLE_MAP_IDS = ['mng8a80o', 'mnorwqx1', 'mnptfts2', 'mnpz6rkp']; +const SPECIAL_QUIZ_ICON_TYPES = ['lawyer', 'police']; +/** โอกาสโผล่ = 1 ใน 3 ของการเล่นแต่ละรอบ */ +const SPECIAL_QUIZ_SPAWN_CHANCE = 1 / 3; +/** เวลาให้ตอบต่อข้อ (มิลลิวิ) — ค่าคงที่เริ่มต้น (ย้ายไป Admin ได้ภายหลัง) */ +const SPECIAL_QUIZ_ANSWER_MS = 20000; +/** หน่วงโชว์เฉลยก่อนข้อต่อไป */ +const SPECIAL_QUIZ_REVEAL_MS = 2600; + +/** แปลงคำถาม 1 ข้อให้อยู่ในรูป {text, choices[], correctIndex} เสมอ (tf -> จริง/เท็จ) */ +function specialQuizNormalizeQuestion(q) { + if (!q || typeof q !== 'object') return null; + const text = String(q.text == null ? '' : q.text).trim(); + if (!text) return null; + if (q.type === 'mcq') { + const choices = (Array.isArray(q.choices) ? q.choices : []).map((c) => String(c == null ? '' : c)).filter((c) => c.trim() !== ''); + if (choices.length < 2) return null; + let ci = Number(q.correctIndex); + if (!Number.isInteger(ci) || ci < 0 || ci >= choices.length) ci = 0; + return { text, choices, correctIndex: ci }; + } + return { text, choices: ['จริง', 'เท็จ'], correctIndex: q.answerTrue ? 0 : 1 }; +} + +function specialQuizShuffle(arr) { + const a = arr.slice(); + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [a[i], a[j]] = [a[j], a[i]]; + } + return a; +} + +function specialQuizTileWalkable(md, x, y) { + const w = md.width || 20; + const h = md.height || 15; + if (x < 0 || x >= w || y < 0 || y >= h) return false; + const row = md.objects && md.objects[y]; + return !!(row && row[x] !== 1); +} + +/** หลีกเลี่ยงโซน gameplay (คำตอบจริง/ผิด, hub, ตัวเลือกหยิบมาวาง) เพื่อไม่ชนกลไกเกม */ +function specialQuizTileInGameZone(md, x, y) { + if (quizCellOn(md.quizTrueArea || [], x, y)) return true; + if (quizCellOn(md.quizFalseArea || [], x, y)) return true; + if (quizCellOn(md.quizCarryHubArea || [], x, y)) return true; + const oa = md.quizCarryOptionArea; + if (oa && oa[y] && oa[y][x]) return true; + return false; +} + +/** หา tile ที่เดินถึงได้จาก spawn (BFS) และไม่อยู่ในโซน gameplay — คืนพิกัดกึ่งกลาง tile */ +function pickSpecialQuizSpawnTile(md) { + const sp = md.spawn || { x: 1, y: 1 }; + const sx = Math.floor(Number(sp.x)); + const sy = Math.floor(Number(sp.y)); + if (!specialQuizTileWalkable(md, sx, sy)) return null; + const q = [[sx, sy]]; + const seen = new Set([sx + ',' + sy]); + const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]]; + const cands = []; + while (q.length) { + const [cx, cy] = q.shift(); + for (let di = 0; di < dirs.length; di++) { + const nx = cx + dirs[di][0]; + const ny = cy + dirs[di][1]; + const k = nx + ',' + ny; + if (seen.has(k)) continue; + if (!specialQuizTileWalkable(md, nx, ny)) continue; + seen.add(k); + q.push([nx, ny]); + if (!specialQuizTileInGameZone(md, nx, ny)) { + const dist = Math.abs(nx - sx) + Math.abs(ny - sy); + if (dist >= 2) cands.push({ x: nx, y: ny }); + } + } + } + if (!cands.length) return null; + const pick = cands[Math.floor(Math.random() * cands.length)]; + return { x: pick.x + 0.5, y: pick.y + 0.5 }; +} + +function clearSpecialQuizTimers(space) { + if (Array.isArray(space.specialQuizTimers)) { + space.specialQuizTimers.forEach((t) => clearTimeout(t)); + } + space.specialQuizTimers = []; +} + +/** payload ไอคอนสำหรับ client (null = ไม่มี/ถูกทริกแล้ว) */ +function specialQuizIconPayload(space) { + const sq = space && space.specialQuiz; + if (!sq || sq.triggered || sq.done) return null; + return { iconType: sq.iconType, x: sq.x, y: sq.y }; +} + +/** เริ่มมินิเกมสืบสวน: สุ่ม 1/3 ว่ารอบนี้จะมีไอคอนพิเศษไหม + เลือกตำแหน่ง */ +function maybeSpawnSpecialQuizForRun(space) { + clearSpecialQuizTimers(space); + space.specialQuiz = null; + space.specialCardAwardedThisRun = null; + if (!space || !space.detectiveMinigameActive) return; + const mapId = String(space.mapId || '').trim(); + if (SPECIAL_QUIZ_ELIGIBLE_MAP_IDS.indexOf(mapId) < 0) return; + const level = Number(space.detectiveLobbyLevel) || 1; + const caseId = Number(space.detectiveLobbyCaseId) || 1; + const settings = loadQuizSettings(); + const set = getSpecialQuizSet(settings, level, caseId); + const questions = (set && Array.isArray(set.questions) ? set.questions : []) + .map(specialQuizNormalizeQuestion) + .filter(Boolean); + const card = set && set.specialCard; + const hasCard = !!(card && (card.cardId || card.imageUrl)); + if (!questions.length || !hasCard) return; + if (Math.random() > SPECIAL_QUIZ_SPAWN_CHANCE) return; + const md = (space.mapId && maps.get(space.mapId)) || space.mapData; + if (!md) return; + const tile = pickSpecialQuizSpawnTile(md); + if (!tile) return; + const iconType = SPECIAL_QUIZ_ICON_TYPES[Math.floor(Math.random() * SPECIAL_QUIZ_ICON_TYPES.length)]; + space.specialQuiz = { + iconType, + x: tile.x, + y: tile.y, + level, + caseId, + card, + triggered: false, + done: false, + session: null, + }; +} + +function emitSpecialQuizIcon(sid, space) { + const payload = specialQuizIconPayload(space); + if (payload) io.to(sid).emit('special-quiz-icon', payload); +} + +/** รายชื่อผู้เล่นจริงที่ยังอยู่ในห้องตอนนี้ (ใช้ตัดสิน "ทุกคนตอบถูก") */ +function specialQuizActiveParticipants(space) { + return [...space.peers.keys()]; +} + +function sendSpecialQuizQuestion(sid, space) { + const sq = space.specialQuiz; + const sess = sq && sq.session; + if (!sess) return; + const q = sess.questions[sess.qIndex]; + if (!q) { endSpecialQuizSession(sid, space, false); return; } + sess.answers = {}; + sess.phaseEndsAt = Date.now() + SPECIAL_QUIZ_ANSWER_MS; + io.to(sid).emit('special-quiz-question', { + index: sess.qIndex, + total: sess.questions.length, + text: q.text, + choices: q.choices, + endsAt: sess.phaseEndsAt, + iconType: sq.iconType, + }); + const t = setTimeout(() => { + const spNow = spaces.get(sid); + if (!spNow || spNow.specialQuiz !== sq || sq.session !== sess) return; + resolveSpecialQuizQuestion(sid, spNow, true); + }, SPECIAL_QUIZ_ANSWER_MS + 80); + space.specialQuizTimers.push(t); +} + +/** ตัดสินข้อปัจจุบัน: ต้องตอบครบทุกคน และถูกทุกคน จึงผ่าน */ +function resolveSpecialQuizQuestion(sid, space, fromTimeout) { + const sq = space.specialQuiz; + const sess = sq && sq.session; + if (!sess || sess.resolving) return; + sess.resolving = true; + clearSpecialQuizTimers(space); + const q = sess.questions[sess.qIndex]; + const participants = specialQuizActiveParticipants(space); + let allCorrect = participants.length > 0; + participants.forEach((pid) => { + const a = sess.answers[pid]; + if (!(Number.isInteger(a) && a === q.correctIndex)) allCorrect = false; + }); + io.to(sid).emit('special-quiz-result', { + index: sess.qIndex, + correctIndex: q.correctIndex, + answers: { ...sess.answers }, + allCorrect, + timeout: !!fromTimeout, + }); + if (!allCorrect) { + const t = setTimeout(() => { + const spNow = spaces.get(sid); + if (!spNow || spNow.specialQuiz !== sq) return; + endSpecialQuizSession(sid, spNow, false); + }, SPECIAL_QUIZ_REVEAL_MS); + space.specialQuizTimers.push(t); + return; + } + sess.qIndex++; + if (sess.qIndex >= sess.questions.length) { + const t = setTimeout(() => { + const spNow = spaces.get(sid); + if (!spNow || spNow.specialQuiz !== sq) return; + endSpecialQuizSession(sid, spNow, true); + }, SPECIAL_QUIZ_REVEAL_MS); + space.specialQuizTimers.push(t); + return; + } + const t = setTimeout(() => { + const spNow = spaces.get(sid); + if (!spNow || spNow.specialQuiz !== sq || sq.session !== sess) return; + sess.resolving = false; + sendSpecialQuizQuestion(sid, spNow); + }, SPECIAL_QUIZ_REVEAL_MS); + space.specialQuizTimers.push(t); +} + +function endSpecialQuizSession(sid, space, success) { + const sq = space.specialQuiz; + if (!sq) return; + clearSpecialQuizTimers(space); + sq.done = true; + sq.session = null; + let cardOut = null; + if (success && sq.card && (sq.card.cardId || sq.card.imageUrl)) { + space.specialCardAwardedThisRun = sq.card; + cardOut = sq.card; + } + io.to(sid).emit('special-quiz-ended', { success: !!success, card: cardOut }); +} + +function startSpecialQuizSession(sid, space) { + const sq = space.specialQuiz; + if (!sq || sq.triggered || sq.done) return false; + const settings = loadQuizSettings(); + const set = getSpecialQuizSet(settings, sq.level, sq.caseId); + const all = (set && Array.isArray(set.questions) ? set.questions : []) + .map(specialQuizNormalizeQuestion) + .filter(Boolean); + if (!all.length) return false; + const cap = clampQuizRoundQuestionCount(settings.quizRoundQuestionCount, 10); + const picked = specialQuizShuffle(all).slice(0, Math.max(1, Math.min(cap, all.length))); + sq.triggered = true; + sq.session = { + questions: picked, + qIndex: 0, + answers: {}, + phaseEndsAt: 0, + resolving: false, + }; + clearSpecialQuizTimers(space); + io.to(sid).emit('special-quiz-icon-clear', {}); + sendSpecialQuizQuestion(sid, space); + return true; +} + function defaultQuizSettings() { return { readMs: 10000, @@ -2013,6 +2300,9 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) { space.quizSession = null; space.mapId = mapId; space.mapData = md; + /* สุ่มไอคอนคำถามพิเศษสำหรับรอบนี้ (1 ใน 3) — ส่งจริงผ่าน joinCb เมื่อ client เข้า play.html */ + maybeSpawnSpecialQuizForRun(space); + emitSpecialQuizIcon(sid, space); let si = 0; space.peers.forEach((p) => { @@ -2098,6 +2388,8 @@ function beginDetectiveSuspectMinigame(sid, space, cardEntry, selectedIndex) { function returnDetectiveSpaceToLobbyB(sid, space, message, awardOptions) { ensurePostCaseLobbyMapLoaded(); clearSpaceQuizTimers(space); + clearSpecialQuizTimers(space); + space.specialQuiz = null; if (space.quizSession) space.quizSession.active = false; space.quizSession = null; space.detectiveMinigameActive = false; @@ -4877,6 +5169,9 @@ io.on('connection', (socket) => { if ((isBalloonBossMissionShellSpace(space) || isQuizQuestionMissionShellSpace(space) || isStackTowerMissionShellSpace(space) || isJumpSurviveMissionShellSpace(space) || isSpaceShooterMissionShellSpace(space)) && space.gauntletRun) { joinCb.gauntletCrownRunHeld = !!space.gauntletRun.crownRunHeld; } + /* ไอคอนคำถามพิเศษของรอบนี้ (ถ้ามีและยังไม่ถูกทริก) */ + const sqIcon = specialQuizIconPayload(space); + if (sqIcon) joinCb.specialQuizIcon = sqIcon; if (typeof cb === 'function') cb(joinCb); emitLobbyTintSync(spaceId, space); socket.to(spaceId).emit('user-joined', peer); @@ -4887,6 +5182,40 @@ io.on('connection', (socket) => { } }); + /** ผู้เล่นเดินชนไอคอนคำถามพิเศษ — ใครชนก่อนเป็นคนเปิดเซสชันให้ทั้งห้อง */ + socket.on('special-quiz-collide', (_data, cb) => { + const reply = typeof cb === 'function' ? cb : () => {}; + const sid = socket.data.spaceId; + const space = sid ? spaces.get(sid) : null; + if (!space || !space.peers.has(socket.id)) return reply({ ok: false }); + const sq = space.specialQuiz; + if (!sq || sq.triggered || sq.done) return reply({ ok: false }); + const started = startSpecialQuizSession(sid, space); + reply({ ok: !!started }); + }); + + /** ผู้เล่นส่งคำตอบของข้อปัจจุบัน */ + socket.on('special-quiz-answer', (data, cb) => { + const reply = typeof cb === 'function' ? cb : () => {}; + const sid = socket.data.spaceId; + const space = sid ? spaces.get(sid) : null; + if (!space || !space.peers.has(socket.id)) return reply({ ok: false }); + const sess = space.specialQuiz && space.specialQuiz.session; + if (!sess || sess.resolving) return reply({ ok: false }); + const choice = Number(data && data.choiceIndex); + const q = sess.questions[sess.qIndex]; + if (!q || !Number.isInteger(choice) || choice < 0 || choice >= q.choices.length) { + return reply({ ok: false }); + } + if (Number.isInteger(sess.answers[socket.id])) return reply({ ok: false, error: 'ตอบไปแล้ว' }); + sess.answers[socket.id] = choice; + reply({ ok: true }); + /* ตอบครบทุกคนแล้วตัดสินทันที ไม่ต้องรอหมดเวลา */ + const participants = specialQuizActiveParticipants(space); + const allAnswered = participants.length > 0 && participants.every((pid) => Number.isInteger(sess.answers[pid])); + if (allAnswered) resolveSpecialQuizQuestion(sid, space, false); + }); + /** เปลี่ยนสีเสื้อ/ผิวใน lobby — ห้ามเลือกสีที่คนอื่นใช้แล้ว */ socket.on('lobby-tint-update', (data, cb) => { const reply = typeof cb === 'function' ? cb : () => {}; @@ -5345,6 +5674,8 @@ io.on('connection', (socket) => { space.troublesomeResponseReceived = false; space.suspectPhaseActive = false; space.suspectPickIndex = 0; + space.detectiveLobbyLevel = Number(lobbyLevel) || 1; + space.detectiveLobbyCaseId = Number(caseId) || 1; if (space.troublesomeEligible) space.troublesomeEligible.clear(); else space.troublesomeEligible = new Set(); if (space.troublesomeDebTimer) clearTimeout(space.troublesomeDebTimer);