diff --git a/www/html/Admin/admin.css b/www/html/Admin/admin.css index 5ec7d1c..355bd35 100644 --- a/www/html/Admin/admin.css +++ b/www/html/Admin/admin.css @@ -572,6 +572,182 @@ legend { color: var(--accent); } +/* —— Tab group dividers (จัดหมวดเมนูให้สแกนง่าย) —— */ +.admin-tab-sep { + flex: 1 1 100%; + display: flex; + align-items: center; + gap: 0.55rem; + margin: 0.35rem 0.15rem 0; + padding: 0 0.15rem; + user-select: none; +} +.admin-tab-sep:first-child { + margin-top: 0; +} +.admin-tab-sep span { + font-size: 0.66rem; + font-weight: 700; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--muted); + white-space: nowrap; +} +.admin-tab-sep::after { + content: ''; + flex: 1; + height: 1px; + background: linear-gradient(90deg, var(--border), transparent); +} +@media (max-width: 899px) { + /* มือถือ: แถบเลื่อนแนวนอน — ไม่ต้องแสดงหัวข้อกลุ่ม */ + .admin-tab-sep { display: none; } +} + +/* —— ชุดคำถามพิเศษ (Special Quiz panel) —— */ +.sq-title { margin: 0 0 0.35rem; } +.sq-steps { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin: 0.9rem 0 1.1rem; + padding: 0; +} +.sq-steps li { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1 1 220px; + padding: 0.55rem 0.8rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.02); + font-size: 0.88rem; +} +.sq-step-no { + flex: 0 0 auto; + width: 1.65rem; + height: 1.65rem; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: var(--accent-dim); + color: var(--accent); + font-weight: 700; + font-size: 0.9rem; +} +.sq-fieldset { + margin: 0 0 1.1rem; + padding: 0.95rem 1.1rem 1.1rem; + border: 1px solid var(--border); + border-radius: var(--radius-lg); + background: var(--bg-elevated, rgba(255, 255, 255, 0.02)); +} +.sq-fieldset > legend { + font-size: 0.95rem; + font-weight: 700; + padding: 0 0.45rem; + color: var(--text); +} +.sq-pick-row { + display: flex; + flex-wrap: wrap; + gap: 1rem; + align-items: flex-end; +} +.sq-select { min-width: 200px; } +.sq-set-tag { + margin-left: auto; + align-self: center; + font-size: 0.8rem; + font-weight: 600; + color: var(--accent); + background: var(--accent-dim); + padding: 0.3rem 0.7rem; + border-radius: 999px; + white-space: nowrap; +} +.sq-card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.85rem; +} +.sq-card-grid .sq-card-url { grid-column: 1 / -1; } +.sq-card-grid input, +.sq-card-grid select { width: 100%; } +.sq-count { + font-weight: 400; + font-size: 0.8rem; + color: var(--muted); + margin-left: 0.3rem; +} + +/* —— คำถามตัวเลือก A/B/C/D (Special Quiz MCQ rows) —— */ +.sq-mcq-row { + display: flex; + flex-direction: column; + gap: 0.7rem; + padding: 0.85rem 0.95rem; + margin-bottom: 0.85rem; + border: 1px solid var(--border); + border-radius: var(--radius); + background: rgba(255, 255, 255, 0.02); +} +.sq-mcq-q { + display: flex; + flex-direction: column; + gap: 0.3rem; + font-size: 0.82rem; + font-weight: 600; + color: var(--muted); +} +.sq-mcq-text { width: 100%; } +.sq-mcq-choices { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.55rem; +} +.sq-mcq-choice { + display: flex; + align-items: center; + gap: 0.5rem; +} +.sq-mcq-letter { + flex: 0 0 auto; + width: 1.7rem; + height: 1.7rem; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + background: var(--accent-dim); + color: var(--accent); + font-weight: 700; + font-size: 0.85rem; +} +.sq-mcq-choice-input { flex: 1 1 auto; min-width: 0; } +.sq-mcq-foot { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + flex-wrap: wrap; +} +.sq-mcq-ans { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.82rem; + font-weight: 600; + color: var(--text); +} +.sq-mcq-correct { min-width: 5rem; } +@media (max-width: 620px) { + .sq-mcq-choices { grid-template-columns: 1fr; } +} + .tab-panel[hidden] { display: none !important; } diff --git a/www/html/Admin/admin.js b/www/html/Admin/admin.js index dc4455c..715309a 100644 --- a/www/html/Admin/admin.js +++ b/www/html/Admin/admin.js @@ -246,6 +246,189 @@ }); } + /* ===== ชุดคำถามพิเศษ (Level×Case = 15 ชุด) รับการ์ดพิเศษ ===== */ + var SPECIAL_CARD_RARITIES = ['common', 'rare', 'legendary']; + var specialQuizSetsState = {}; + var specialQuizCurrentKey = 'L1_C1'; + + function specialQuizSelectorKey() { + var L = el('special-quiz-level'); + var C = el('special-quiz-case'); + return 'L' + ((L && L.value) || '1') + '_C' + ((C && C.value) || '1'); + } + + function updateSpecialQuizCount() { + var c = el('special-quiz-count'); + if (!c) return; + var n = document.querySelectorAll('#special-quiz-questions-list .quiz-admin-q-row').length; + c.textContent = '(' + n + ' ข้อ)'; + } + + var SPECIAL_QUIZ_CHOICE_COUNT = 4; + var SPECIAL_QUIZ_LETTERS = ['A', 'B', 'C', 'D', 'E', 'F']; + + /** คำถามตัวเลือก A/B/C/D — เลือกคำตอบถูก 1 ข้อ */ + function addSpecialQuizRow(q) { + q = q || {}; + var list = el('special-quiz-questions-list'); + if (!list) return; + var choices = Array.isArray(q.choices) ? q.choices : []; + var correctIndex = Number(q.correctIndex); + if (!(correctIndex >= 0 && correctIndex < SPECIAL_QUIZ_CHOICE_COUNT)) correctIndex = 0; + + var row = document.createElement('div'); + row.className = 'quiz-admin-q-row sq-mcq-row'; + + var qLab = document.createElement('label'); + qLab.className = 'sq-mcq-q'; + qLab.appendChild(document.createTextNode('คำถาม')); + var qInp = document.createElement('input'); + qInp.type = 'text'; + qInp.className = 'sq-mcq-text'; + qInp.maxLength = 500; + qInp.placeholder = 'พิมพ์คำถาม...'; + qInp.value = q.text || ''; + qLab.appendChild(qInp); + + var grid = document.createElement('div'); + grid.className = 'sq-mcq-choices'; + for (var i = 0; i < SPECIAL_QUIZ_CHOICE_COUNT; i++) { + var cl = document.createElement('label'); + cl.className = 'sq-mcq-choice'; + var badge = document.createElement('span'); + badge.className = 'sq-mcq-letter'; + badge.textContent = SPECIAL_QUIZ_LETTERS[i]; + var ci = document.createElement('input'); + ci.type = 'text'; + ci.className = 'sq-mcq-choice-input'; + ci.maxLength = 200; + ci.placeholder = 'ตัวเลือก ' + SPECIAL_QUIZ_LETTERS[i]; + ci.value = choices[i] || ''; + ci.setAttribute('data-choice-index', String(i)); + cl.appendChild(badge); + cl.appendChild(ci); + grid.appendChild(cl); + } + + var foot = document.createElement('div'); + foot.className = 'sq-mcq-foot'; + var ansLab = document.createElement('label'); + ansLab.className = 'sq-mcq-ans'; + ansLab.appendChild(document.createTextNode('คำตอบที่ถูก ')); + var ansSel = document.createElement('select'); + ansSel.className = 'sq-mcq-correct'; + for (var j = 0; j < SPECIAL_QUIZ_CHOICE_COUNT; j++) { + var o = document.createElement('option'); + o.value = String(j); + o.textContent = SPECIAL_QUIZ_LETTERS[j]; + if (j === correctIndex) o.selected = true; + ansSel.appendChild(o); + } + ansLab.appendChild(ansSel); + + var rm = document.createElement('button'); + rm.type = 'button'; + rm.className = 'btn btn-danger btn-sm quiz-admin-q-remove'; + rm.setAttribute('aria-label', 'ลบข้อนี้'); + rm.textContent = 'ลบ'; + rm.addEventListener('click', function () { + row.remove(); + updateSpecialQuizCount(); + }); + foot.appendChild(ansLab); + foot.appendChild(rm); + + row.appendChild(qLab); + row.appendChild(grid); + row.appendChild(foot); + list.appendChild(row); + } + + function renderSpecialQuizSet(key) { + 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 || ''; + 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 || ''; + var list = el('special-quiz-questions-list'); + if (list) { + list.innerHTML = ''; + var qs = (set.questions && set.questions.length) + ? set.questions + : [{ type: 'mcq', text: '', choices: ['', '', '', ''], correctIndex: 0 }]; + qs.forEach(addSpecialQuizRow); + } + var tag = el('special-quiz-set-tag'); + if (tag) tag.textContent = 'ชุดที่แก้: ' + key; + updateSpecialQuizCount(); + } + + function captureSpecialQuizForm(key) { + var questions = []; + document.querySelectorAll('#special-quiz-questions-list .sq-mcq-row').forEach(function (row) { + var inp = row.querySelector('.sq-mcq-text'); + var t = inp && inp.value ? String(inp.value).trim() : ''; + if (!t) return; + var choices = []; + row.querySelectorAll('.sq-mcq-choice-input').forEach(function (ci) { + choices.push(String(ci.value || '').trim()); + }); + if (choices.filter(function (c) { return c; }).length < 2) return; + var sel = row.querySelector('.sq-mcq-correct'); + var correctIndex = sel ? parseInt(sel.value, 10) : 0; + if (!(correctIndex >= 0 && correctIndex < choices.length) || !choices[correctIndex]) { + correctIndex = choices.findIndex(function (c) { return c; }); + if (correctIndex < 0) correctIndex = 0; + } + questions.push({ type: 'mcq', text: t, choices: choices, correctIndex: correctIndex }); + }); + var rar = el('special-card-rarity') ? el('special-card-rarity').value : 'rare'; + specialQuizSetsState[key] = { + questions: questions, + specialCard: { + 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() : '', + }, + }; + } + + function onSpecialQuizSelectorChange() { + captureSpecialQuizForm(specialQuizCurrentKey); + specialQuizCurrentKey = specialQuizSelectorKey(); + renderSpecialQuizSet(specialQuizCurrentKey); + } + + function loadSpecialQuizPanel() { + setMsg('special-quiz-msg', 'กำลังโหลด...', ''); + gameQuizFetch('GET').then(function (data) { + specialQuizSetsState = (data.specialQuizSets && typeof data.specialQuizSets === 'object') ? data.specialQuizSets : {}; + specialQuizCurrentKey = specialQuizSelectorKey(); + renderSpecialQuizSet(specialQuizCurrentKey); + setMsg('special-quiz-msg', '', ''); + }).catch(function (e) { + setMsg('special-quiz-msg', e.message || 'โหลดไม่ได้', 'error'); + }); + } + + function saveSpecialQuizPanel() { + captureSpecialQuizForm(specialQuizCurrentKey); + setMsg('special-quiz-msg', 'กำลังบันทึก...', ''); + gameQuizFetch('PUT', { specialQuizSets: specialQuizSetsState }).then(function (res) { + if (res && res.specialQuizSets && typeof res.specialQuizSets === 'object') { + specialQuizSetsState = res.specialQuizSets; + } + setMsg('special-quiz-msg', 'บันทึกแล้ว (15 ชุด)', 'ok'); + renderSpecialQuizSet(specialQuizCurrentKey); + }).catch(function (e) { + setMsg('special-quiz-msg', e.message || 'บันทึกไม่ได้', 'error'); + }); + } + function quizTfThemeHiddenId(kind) { if (kind === 'bg') return 'quiz-tf-theme-bg'; if (kind === 'border') return 'quiz-tf-theme-border'; @@ -3740,6 +3923,7 @@ } }); if (name === 'quiz') loadQuizSettingsPanel(); + if (name === 'special-quiz') loadSpecialQuizPanel(); if (name === 'quiz-carry') loadQuizCarryPanel(); if (name === 'quiz-battle') loadQbBattlePanel(); if (name === 'jump-survive') loadJumpSurviveTimingPanel(); @@ -3935,6 +4119,20 @@ } if (btnQuizSave) btnQuizSave.addEventListener('click', saveQuizSettingsPanel); + var btnSpecialQuizAdd = el('btn-special-quiz-add'); + var btnSpecialQuizSave = el('btn-special-quiz-save'); + if (btnSpecialQuizAdd) { + btnSpecialQuizAdd.addEventListener('click', function () { + addSpecialQuizRow({ type: 'tf', text: '', answerTrue: true }); + updateSpecialQuizCount(); + }); + } + if (btnSpecialQuizSave) btnSpecialQuizSave.addEventListener('click', saveSpecialQuizPanel); + var specialQuizLevelSel = el('special-quiz-level'); + var specialQuizCaseSel = el('special-quiz-case'); + if (specialQuizLevelSel) specialQuizLevelSel.addEventListener('change', onSpecialQuizSelectorChange); + if (specialQuizCaseSel) specialQuizCaseSel.addEventListener('change', onSpecialQuizSelectorChange); + var btnQuizCarryAdd = el('btn-quiz-carry-add'); var btnQuizCarrySave = el('btn-quiz-carry-save'); if (btnQuizCarryAdd) { @@ -4228,7 +4426,7 @@ try { want = new URL(window.location.href).searchParams.get('tab'); } catch (e) { /* ignore */ } - var allowed = { 'change-password': true, oauth: true, 'map-editor': true, quiz: true, 'quiz-carry': true, 'quiz-battle': true, 'jump-survive': true, 'space-shooter': true, 'mega-virus': true, 'game-timing': true, 'stack-game': true, characters: true, accounts: true, admins: true }; + var allowed = { 'change-password': true, oauth: true, 'map-editor': true, quiz: true, 'special-quiz': true, 'quiz-carry': true, 'quiz-battle': true, 'jump-survive': true, 'space-shooter': true, 'mega-virus': true, 'game-timing': true, 'stack-game': true, characters: true, accounts: true, admins: true }; if (!want || !allowed[want]) return 'oauth'; if (want === 'admins' && (!admin || admin.role !== 'super')) return 'oauth'; return want; diff --git a/www/html/Admin/api/game-quiz-settings.php b/www/html/Admin/api/game-quiz-settings.php index e5541f1..ffa98fb 100644 --- a/www/html/Admin/api/game-quiz-settings.php +++ b/www/html/Admin/api/game-quiz-settings.php @@ -52,7 +52,7 @@ function merge_quiz_settings_disk_from_put(string $rawBody): bool if ($hadFileButInvalidJson) { return false; } - $mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryMapPanelTheme', 'quizMapPanelTheme', 'carryEmbedCountdownTheme', 'carryChoicePlaqueTheme', 'carryChoicePlaqueThemes', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount', 'questions', 'carryQuestions', 'battleQuizMcq']; + $mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryMapPanelTheme', 'quizMapPanelTheme', 'carryEmbedCountdownTheme', 'carryChoicePlaqueTheme', 'carryChoicePlaqueThemes', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount', 'questions', 'carryQuestions', 'battleQuizMcq', 'specialQuizSets']; foreach ($mergeKeys as $k) { if (array_key_exists($k, $patch)) { $base[$k] = $patch[$k]; @@ -96,6 +96,9 @@ function overlay_quiz_settings_from_disk(string $nodeBody): string if (array_key_exists('battleQuizMcq', $disk)) { $j['battleQuizMcq'] = $disk['battleQuizMcq']; } + if (array_key_exists('specialQuizSets', $disk) && is_array($disk['specialQuizSets'])) { + $j['specialQuizSets'] = $disk['specialQuizSets']; + } if (array_key_exists('carryMapPanelTheme', $disk) && is_array($disk['carryMapPanelTheme'])) { $j['carryMapPanelTheme'] = $disk['carryMapPanelTheme']; } diff --git a/www/html/Admin/index.html b/www/html/Admin/index.html index d94bbed..6478626 100644 --- a/www/html/Admin/index.html +++ b/www/html/Admin/index.html @@ -10,7 +10,7 @@ - + @@ -66,9 +66,12 @@ - + diff --git a/www/html/Game/data/quiz-settings.json b/www/html/Game/data/quiz-settings.json index ae1ba70..42f9d9a 100644 --- a/www/html/Game/data/quiz-settings.json +++ b/www/html/Game/data/quiz-settings.json @@ -1,291 +1,428 @@ { - "readMs": 15000, - "answerMs": 5000, - "betweenMs": 3000, - "carryReadMs": 3000, - "carryAnswerMs": 15000, - "carrySessionLength": 2, - "carryMapPanelTheme": { - "panelBg": "rgba(255, 0, 0, 0)", - "panelBorder": "rgba(255, 255, 255, 0)", - "textColor": "rgba(241, 245, 249, 1)", - "borderWidthPx": 0, - "questionFontMinPx": 10, - "questionFontMaxPx": 24 + "readMs": 15000, + "answerMs": 5000, + "betweenMs": 3000, + "carryReadMs": 3000, + "carryAnswerMs": 15000, + "carrySessionLength": 2, + "carryMapPanelTheme": { + "panelBg": "rgba(255, 0, 0, 0)", + "panelBorder": "rgba(255, 255, 255, 0)", + "borderWidthPx": 0, + "textColor": "rgba(241, 245, 249, 1)", + "questionFontMinPx": 10, + "questionFontMaxPx": 24 + }, + "quizMapPanelTheme": { + "panelBg": "rgba(12, 14, 28, 0)", + "panelBorder": "rgba(255, 214, 102, 0)", + "borderWidthPx": 2, + "textColor": "rgba(255, 224, 102, 1)", + "questionFontMinPx": 10, + "questionFontMaxPx": 28 + }, + "carryEmbedCountdownTheme": { + "overlayBackdrop": "rgba(8, 10, 20, 0)", + "innerBg": "rgba(12, 14, 28, 0)", + "innerBorder": "rgba(122, 162, 247, 0)", + "innerBorderWpx": 1, + "innerRadiusPx": 12, + "digitColor": "#f2f2f2", + "mapDigitCqmin": 78, + "mapDigitCqh": 82, + "mapDigitMaxPx": 200, + "screenDigitVw": 28, + "screenDigitMaxPx": 132 + }, + "carryChoicePlaqueThemes": [ + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-2.png" }, - "quizMapPanelTheme": { - "panelBg": "rgba(12, 14, 28, 0)", - "panelBorder": "rgba(255, 214, 102, 0)", - "borderWidthPx": 2, - "textColor": "rgba(255, 224, 102, 1)", - "questionFontMinPx": 10, - "questionFontMaxPx": 28 + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-2.png" }, - "carryEmbedCountdownTheme": { - "overlayBackdrop": "rgba(8, 10, 20, 0)", - "innerBg": "rgba(12, 14, 28, 0)", - "innerBorder": "rgba(122, 162, 247, 0)", - "innerBorderWpx": 1, - "innerRadiusPx": 12, - "digitColor": "#f2f2f2", - "mapDigitCqmin": 78, - "mapDigitCqh": 82, - "mapDigitMaxPx": 200, - "screenDigitVw": 28, - "screenDigitMaxPx": 132 + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-1.png" }, - "carryChoicePlaqueThemes": [ - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-2.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-2.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-1.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-1.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-1.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-3.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-3.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-4.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-4.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-5.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-5.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-5.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-6.png" - }, - { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-6.png" - }, - { - "borderMode": "neon", - "fixedBorder": "rgba(122, 200, 255, 0.9)", - "fillBg": "rgba(12, 10, 20, 0.88)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 2.5, - "plaqueImageUrl": "" - }, - { - "borderMode": "neon", - "fixedBorder": "rgba(122, 200, 255, 0.9)", - "fillBg": "rgba(12, 10, 20, 0.88)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 2.5, - "plaqueImageUrl": "" - } - ], - "carryChoicePlaqueTheme": { - "borderMode": "fixed", - "fixedBorder": "rgba(122, 200, 255, 0)", - "fillBg": "rgba(12, 10, 20, 0)", - "textColor": "rgba(248, 249, 255, 1)", - "borderWidthPx": 0, - "plaqueImageUrl": "https:\/\/srv1361159.hstgr.cloud\/Game\/img\/quiz-carry\/board-2.png" + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-1.png" }, - "carryChoicePlaqueMapScale": 1.9, - "carryWalkSpeedMultForMapId": "", - "carryWalkSpeedMult": null, - "quizRoundQuestionCount": 3, - "questions": [ - { - "text": "test1", - "answerTrue": true - }, - { - "text": "test2", - "answerTrue": true - }, - { - "text": "test3", - "answerTrue": true - }, - { - "text": "test4", - "answerTrue": true - }, - { - "text": "test5", - "answerTrue": true - }, - { - "text": "test6", - "answerTrue": true - }, - { - "text": "test7", - "answerTrue": true - }, - { - "text": "test8", - "answerTrue": true - }, - { - "text": "test9", - "answerTrue": true - }, - { - "text": "test10", - "answerTrue": true - }, - { - "text": "test11", - "answerTrue": true - }, - { - "text": "test12", - "answerTrue": true - }, - { - "text": "test13", - "answerTrue": true - }, - { - "text": "test14", - "answerTrue": true - } - ], - "carryQuestions": [ - { - "text": "ผู้มีอํานาจในการพิจารณาพิพากษาตัดสินคดีความในศาล", - "choices": [ - "จําเลย", - "ผู้เสียหาย", - "อัยการ", - "ทนายความ", - "โจทก์", - "ตํารวจ", - "ผู้ไกล่เกลี่ย", - "ผู้พิพากษา", - "นิติกร", - "พยานบุคคล", - "หมายค้น", - "หมายจับ", - "หมายเรียก", - "พยานหลักฐาน" - ], - "correctIndex": 7 - }, - { - "text": "ผู้ที่ถูกกล่าวหาว่ากระทําความผิดทางอาญา และถูกฟ้องต่อศาลแล้ว", - "choices": [ - "ผู้พิพากษา", - "โจทก์", - "พยานบุคคล", - "การประกันตัว", - "นิติกร", - "ผู้เสียหาย", - "สัญญา", - "หมายจับ", - "ตํารวจ", - "ผู้ไกล่เกลี่ย", - "อัยการ", - "ทนายความ", - "จําเลย", - "พยานหลักฐาน" - ], - "correctIndex": 12 - } - ], - "battleQuizMcq": [ - { - "categoryId": "cybercrime", - "text": "ฟหกฟหก", - "choices": [ - "ๆไำ", - "ฟหฟหด", - "ฟหกไๆไก" - ], - "correctIndex": 0 - } - ] + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-1.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-3.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-3.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-4.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-4.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-5.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-5.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-5.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-6.png" + }, + { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-6.png" + }, + { + "borderMode": "neon", + "fixedBorder": "rgba(122, 200, 255, 0.9)", + "fillBg": "rgba(12, 10, 20, 0.88)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 2.5, + "plaqueImageUrl": "" + }, + { + "borderMode": "neon", + "fixedBorder": "rgba(122, 200, 255, 0.9)", + "fillBg": "rgba(12, 10, 20, 0.88)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 2.5, + "plaqueImageUrl": "" + } + ], + "carryChoicePlaqueTheme": { + "borderMode": "fixed", + "fixedBorder": "rgba(122, 200, 255, 0)", + "fillBg": "rgba(12, 10, 20, 0)", + "textColor": "rgba(248, 249, 255, 1)", + "borderWidthPx": 0, + "plaqueImageUrl": "https://srv1361159.hstgr.cloud/Game/img/quiz-carry/board-2.png" + }, + "carryChoicePlaqueMapScale": 1.9, + "carryWalkSpeedMultForMapId": "", + "carryWalkSpeedMult": null, + "quizRoundQuestionCount": 3, + "questions": [ + { + "text": "test1", + "answerTrue": true + }, + { + "text": "test2", + "answerTrue": true + }, + { + "text": "test3", + "answerTrue": true + }, + { + "text": "test4", + "answerTrue": true + }, + { + "text": "test5", + "answerTrue": true + }, + { + "text": "test6", + "answerTrue": true + }, + { + "text": "test7", + "answerTrue": true + }, + { + "text": "test8", + "answerTrue": true + }, + { + "text": "test9", + "answerTrue": true + }, + { + "text": "test10", + "answerTrue": true + }, + { + "text": "test11", + "answerTrue": true + }, + { + "text": "test12", + "answerTrue": true + }, + { + "text": "test13", + "answerTrue": true + }, + { + "text": "test14", + "answerTrue": true + } + ], + "carryQuestions": [ + { + "text": "ผู้มีอํานาจในการพิจารณาพิพากษาตัดสินคดีความในศาล", + "choices": [ + "จําเลย", + "ผู้เสียหาย", + "อัยการ", + "ทนายความ", + "โจทก์", + "ตํารวจ", + "ผู้ไกล่เกลี่ย", + "ผู้พิพากษา", + "นิติกร", + "พยานบุคคล", + "หมายค้น", + "หมายจับ", + "หมายเรียก", + "พยานหลักฐาน" + ], + "correctIndex": 7 + }, + { + "text": "ผู้ที่ถูกกล่าวหาว่ากระทําความผิดทางอาญา และถูกฟ้องต่อศาลแล้ว", + "choices": [ + "ผู้พิพากษา", + "โจทก์", + "พยานบุคคล", + "การประกันตัว", + "นิติกร", + "ผู้เสียหาย", + "สัญญา", + "หมายจับ", + "ตํารวจ", + "ผู้ไกล่เกลี่ย", + "อัยการ", + "ทนายความ", + "จําเลย", + "พยานหลักฐาน" + ], + "correctIndex": 12 + } + ], + "battleQuizMcq": [ + { + "categoryId": "cybercrime", + "text": "ฟหกฟหก", + "choices": [ + "ๆไำ", + "ฟหฟหด", + "ฟหกไๆไก" + ], + "correctIndex": 0 + } + ], + "specialQuizSets": { + "L1_C1": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L1_C2": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L1_C3": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L2_C1": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L2_C2": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L2_C3": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L3_C1": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L3_C2": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L3_C3": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L4_C1": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L4_C2": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L4_C3": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L5_C1": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L5_C2": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + }, + "L5_C3": { + "questions": [], + "specialCard": { + "th": "", + "en": "", + "rarity": "rare", + "imageUrl": "" + } + } + } } \ No newline at end of file diff --git a/www/html/Game/public/js/play.js b/www/html/Game/public/js/play.js index cdf1efb..752c4e6 100644 --- a/www/html/Game/public/js/play.js +++ b/www/html/Game/public/js/play.js @@ -11243,6 +11243,103 @@ }; } + /* ===== ชุดคำถามพิเศษ (รับการ์ดพิเศษ) — โมดูลกลาง ใช้ได้หลายเกม ===== + * จุดต่อเข้าเกมจริง (trigger) จะ wire ภายหลังตามที่ผู้ใช้ระบุ + * วิธีใช้: SpecialQuiz.fetchSettings() -> getSet(level,caseId) -> pickQuestions(set,n) + * เล่นจบแล้ว awardCard(set, correct, total) -> set mission.specialCardAwarded = card + */ + let __specialQuizSettingsCache = null; + let __specialQuizFetchPromise = null; + + function specialQuizFetchSettings() { + if (__specialQuizSettingsCache) return Promise.resolve(__specialQuizSettingsCache); + if (__specialQuizFetchPromise) return __specialQuizFetchPromise; + __specialQuizFetchPromise = fetch(BASE + '/api/quiz-settings', { cache: 'no-store' }) + .then(function (r) { return r.json(); }) + .then(function (j) { __specialQuizSettingsCache = j || {}; return __specialQuizSettingsCache; }) + .catch(function () { __specialQuizFetchPromise = null; return {}; }); + return __specialQuizFetchPromise; + } + + function specialQuizCurrentLevelCase() { + const meta = window.__detectiveLobbyMeta || {}; + let level = Number(meta.level); + let caseId = Number(meta.caseId); + if (!(level >= 1 && level <= 5)) { + const lp = Number(params.get('lobbyLevel')); + if (lp >= 1 && lp <= 5) level = lp; + } + if (!(caseId >= 1 && caseId <= 3)) { + const cp = Number(params.get('case') || params.get('caseId')); + if (cp >= 1 && cp <= 3) caseId = cp; + } + return { + level: (level >= 1 && level <= 5) ? level : 1, + caseId: (caseId >= 1 && caseId <= 3) ? caseId : 1, + }; + } + + function specialQuizGetSet(settings, level, caseId) { + const sets = (settings && settings.specialQuizSets) || {}; + const key = 'L' + level + '_C' + caseId; + return sets[key] || sets.L1_C1 || { questions: [], specialCard: {} }; + } + + function specialQuizPickQuestions(set, count) { + const qs = (set && Array.isArray(set.questions)) ? set.questions.slice() : []; + for (let i = qs.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + const t = qs[i]; qs[i] = qs[j]; qs[j] = t; + } + let n = Number(count); + if (!(n >= 1)) n = qs.length; + return qs.slice(0, Math.min(n, qs.length)); + } + + /** กติกาได้การ์ด: ตอบถูก >= ครึ่งหนึ่ง และชุดนั้นตั้งการ์ดไว้ — คืน card หรือ null */ + function specialQuizAwardCard(set, correctCount, totalCount) { + const card = (set && set.specialCard) || null; + if (!card || !(card.th || card.en || card.imageUrl)) return null; + const total = Number(totalCount) || 0; + const correct = Number(correctCount) || 0; + const passNeed = total > 0 ? Math.ceil(total / 2) : 1; + if (correct < passNeed) return null; + return card; + } + + /** วาดการ์ดพิเศษลงในโซนโบนัสของสรุปผล (#gcm-bonus / .sm-bonus-right) */ + function renderSpecialCardInBonus(bonusEl, card) { + if (!bonusEl || !card || !(card.th || card.en || card.imageUrl)) return; + 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); + } + 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); + } + + window.SpecialQuiz = { + fetchSettings: specialQuizFetchSettings, + currentLevelCase: specialQuizCurrentLevelCase, + getSet: specialQuizGetSet, + pickQuestions: specialQuizPickQuestions, + awardCard: specialQuizAwardCard, + renderCardInBonus: renderSpecialCardInBonus, + }; + function showGauntletCrownMissionOverlay(mission) { const ov = document.getElementById('gauntlet-crown-mission-overlay'); const rowEl = document.getElementById('gcm-rank-row'); @@ -11495,6 +11592,10 @@ pl.innerHTML = 'ไม่ได้รับการ์ดเกรด ' + gLetter + ' · ภารกิจไม่ผ่านเกณฑ์'; bonusEl.appendChild(pl); } + /* การ์ดพิเศษจากชุดคำถาม (ถ้ารอบนี้ได้รับ) — wire จุด trigger ภายหลัง */ + if (mission && mission.specialCardAwarded) { + renderSpecialCardInBonus(bonusEl, mission.specialCardAwarded); + } } const gcmHead = document.getElementById('gcm-heading'); if (gcmHead) { diff --git a/www/html/Game/public/play.html b/www/html/Game/public/play.html index dcf74d7..dd37e88 100644 --- a/www/html/Game/public/play.html +++ b/www/html/Game/public/play.html @@ -1608,6 +1608,24 @@ } .gcm-plaque strong { color: #f1f5f9; display: block; } .gcm-plaque span { color: #7af8ff; font-size: 0.92em; } + .gcm-special-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + margin-top: 8px; + padding: 8px 10px; + border-radius: 12px; + border: 1px solid rgba(255, 214, 102, 0.6); + background: linear-gradient(165deg, rgba(40, 30, 10, 0.96), rgba(20, 14, 6, 0.98)); + max-width: 200px; + } + .gcm-special-card.rarity-rare { border-color: rgba(122, 162, 247, 0.75); } + .gcm-special-card.rarity-legendary { border-color: rgba(255, 184, 77, 0.9); box-shadow: 0 0 12px rgba(255, 184, 77, 0.45); } + .gcm-special-card-img { width: 100%; max-width: 120px; height: auto; border-radius: 8px; display: block; } + .gcm-special-card-cap { text-align: center; line-height: 1.25; } + .gcm-special-card-cap strong { display: block; color: #ffe066; font-size: 0.95em; } + .gcm-special-card-cap span { color: #cbd5e1; font-size: 0.82em; } .btn-gcm-done { display: block; margin: 4px auto 0; @@ -3842,7 +3860,7 @@ - +
v —
diff --git a/www/html/Game/public/trycss/10-Game-1-result/btn-continue-play.png b/www/html/Game/public/trycss/10-Game-1-result/btn-continue-play.png new file mode 100644 index 0000000..7dddec7 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/btn-continue-play.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card--7txt.png b/www/html/Game/public/trycss/10-Game-1-result/card--7txt.png new file mode 100644 index 0000000..2e6f584 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card--7txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-1-txt.png b/www/html/Game/public/trycss/10-Game-1-result/card-1-txt.png new file mode 100644 index 0000000..8e3dcf8 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-1-txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-1.png b/www/html/Game/public/trycss/10-Game-1-result/card-1.png new file mode 100644 index 0000000..3e17ebc Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-1.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-2-txt.png b/www/html/Game/public/trycss/10-Game-1-result/card-2-txt.png new file mode 100644 index 0000000..2cecac7 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-2-txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-2.png b/www/html/Game/public/trycss/10-Game-1-result/card-2.png new file mode 100644 index 0000000..0b9ba05 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-2.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-3-txt.png b/www/html/Game/public/trycss/10-Game-1-result/card-3-txt.png new file mode 100644 index 0000000..3d4b472 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-3-txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-3.png b/www/html/Game/public/trycss/10-Game-1-result/card-3.png new file mode 100644 index 0000000..c126d4d Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-3.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-4-txt.png b/www/html/Game/public/trycss/10-Game-1-result/card-4-txt.png new file mode 100644 index 0000000..b68fa69 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-4-txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-4.png b/www/html/Game/public/trycss/10-Game-1-result/card-4.png new file mode 100644 index 0000000..b0bc320 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-4.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-5-txt.png b/www/html/Game/public/trycss/10-Game-1-result/card-5-txt.png new file mode 100644 index 0000000..38e7094 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-5-txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-5.png b/www/html/Game/public/trycss/10-Game-1-result/card-5.png new file mode 100644 index 0000000..2ed17db Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-5.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-6-txt.png b/www/html/Game/public/trycss/10-Game-1-result/card-6-txt.png new file mode 100644 index 0000000..6d8a591 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-6-txt.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-6.png b/www/html/Game/public/trycss/10-Game-1-result/card-6.png new file mode 100644 index 0000000..d7701d2 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-6.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-7.png b/www/html/Game/public/trycss/10-Game-1-result/card-7.png new file mode 100644 index 0000000..0d3a953 Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-7.png differ diff --git a/www/html/Game/public/trycss/10-Game-1-result/card-effect.png b/www/html/Game/public/trycss/10-Game-1-result/card-effect.png new file mode 100644 index 0000000..970ee7e Binary files /dev/null and b/www/html/Game/public/trycss/10-Game-1-result/card-effect.png differ diff --git a/www/html/Game/server.js b/www/html/Game/server.js index 9cc1c04..e8d5693 100644 --- a/www/html/Game/server.js +++ b/www/html/Game/server.js @@ -283,6 +283,72 @@ const defaultMap = () => ({ jumpSurviveMovePxPerSec: 200, }); +/* ===== Special Quiz Sets (Level 1-5 × Case 1-3 = 15 ชุด) — คำถามรับการ์ดพิเศษ ใช้ได้หลายเกม ===== */ +const SPECIAL_QUIZ_LEVELS = [1, 2, 3, 4, 5]; +const SPECIAL_QUIZ_CASES = [1, 2, 3]; +const SPECIAL_QUIZ_CARD_RARITIES = ['common', 'rare', 'legendary']; + +function specialQuizSetKey(level, caseId) { + return 'L' + level + '_C' + caseId; +} + +/** คำถาม 1 ข้อ — รองรับทั้งจริง/เท็จ (tf) และตัวเลือก A/B/C (mcq) เพื่อขยายภายหลัง */ +function sanitizeSpecialQuizQuestion(q) { + if (!q || typeof q !== 'object') return null; + const text = String(q.text == null ? '' : q.text).trim(); + if (!text) return null; + const type = q.type === 'mcq' ? 'mcq' : 'tf'; + if (type === 'mcq') { + /* รักษาตำแหน่งตัวเลือก (A=0,B=1,...) — ตัดเฉพาะช่องว่างท้ายสุด ไม่ขยับ index คำตอบ */ + const choices = (Array.isArray(q.choices) ? q.choices : []) + .map((c) => String(c == null ? '' : c).trim()) + .slice(0, 8); + while (choices.length && choices[choices.length - 1] === '') choices.pop(); + if (choices.filter((c) => c).length < 2) return null; + let ci = Number(q.correctIndex); + if (!Number.isInteger(ci) || ci < 0 || ci >= choices.length || choices[ci] === '') { + ci = choices.findIndex((c) => c); + if (ci < 0) ci = 0; + } + return { type: 'mcq', text: text.slice(0, 500), choices, correctIndex: ci }; + } + return { type: 'tf', text: text.slice(0, 500), answerTrue: !!q.answerTrue }; +} + +function sanitizeSpecialCard(c) { + const src = (c && typeof c === 'object') ? c : {}; + const rarity = SPECIAL_QUIZ_CARD_RARITIES.indexOf(src.rarity) >= 0 ? src.rarity : 'rare'; + return { + 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), + }; +} + +/** คืน object 15 ชุดเสมอ (ชุดที่ยังไม่ตั้ง = ว่าง) */ +function sanitizeSpecialQuizSets(obj) { + const out = {}; + SPECIAL_QUIZ_LEVELS.forEach((L) => SPECIAL_QUIZ_CASES.forEach((C) => { + const key = specialQuizSetKey(L, C); + const src = (obj && typeof obj === 'object' && obj[key] && typeof obj[key] === 'object') ? obj[key] : {}; + const questions = (Array.isArray(src.questions) ? src.questions : []) + .map(sanitizeSpecialQuizQuestion) + .filter(Boolean) + .slice(0, 50); + out[key] = { questions, specialCard: sanitizeSpecialCard(src.specialCard) }; + })); + return out; +} + +/** เลือกชุดคำถามตาม level+case (fallback ชุด L1_C1) */ +function getSpecialQuizSet(settings, level, caseId) { + const sets = (settings && settings.specialQuizSets) || sanitizeSpecialQuizSets({}); + const L = SPECIAL_QUIZ_LEVELS.indexOf(Number(level)) >= 0 ? Number(level) : 1; + const C = SPECIAL_QUIZ_CASES.indexOf(Number(caseId)) >= 0 ? Number(caseId) : 1; + return sets[specialQuizSetKey(L, C)] || sets[specialQuizSetKey(1, 1)] || { questions: [], specialCard: sanitizeSpecialCard({}) }; +} + function defaultQuizSettings() { return { readMs: 10000, @@ -314,6 +380,8 @@ function defaultQuizSettings() { questions: [], carryQuestions: [], battleQuizMcq: [], + /** 15 ชุดคำถามพิเศษ (Level×Case) สำหรับรับการ์ดพิเศษ */ + specialQuizSets: sanitizeSpecialQuizSets({}), }; } @@ -684,6 +752,7 @@ function loadQuizSettings() { questions, carryQuestions, battleQuizMcq, + specialQuizSets: sanitizeSpecialQuizSets(j.specialQuizSets), }; return mergeQuizCarryFromMirrorIfSet(out); } @@ -1489,6 +1558,9 @@ function saveQuizSettings(d) { const quizRoundQuestionCount = d.quizRoundQuestionCount != null ? clampQuizRoundQuestionCount(d.quizRoundQuestionCount, prev.quizRoundQuestionCount) : prev.quizRoundQuestionCount; + const specialQuizSets = d.specialQuizSets != null + ? sanitizeSpecialQuizSets(d.specialQuizSets) + : sanitizeSpecialQuizSets(prev.specialQuizSets); const out = { readMs, answerMs, @@ -1508,12 +1580,14 @@ function saveQuizSettings(d) { questions, carryQuestions, battleQuizMcq, + specialQuizSets, }; fs.writeFileSync(QUIZ_SETTINGS_PATH, JSON.stringify(out, null, 2), 'utf8'); return { ok: true, battleQuizMcqSaved: (out.battleQuizMcq || []).length, carryQuestionsSaved: (out.carryQuestions || []).length, + specialQuizSets: out.specialQuizSets, carryMapPanelTheme: out.carryMapPanelTheme, quizMapPanelTheme: out.quizMapPanelTheme, carryEmbedCountdownTheme: out.carryEmbedCountdownTheme, @@ -2979,6 +3053,7 @@ const server = http.createServer((req, res) => { ok: true, battleQuizMcqSaved: saved.battleQuizMcqSaved, carryQuestionsSaved: saved.carryQuestionsSaved, + specialQuizSets: saved.specialQuizSets, carryMapPanelTheme: saved.carryMapPanelTheme, carryEmbedCountdownTheme: saved.carryEmbedCountdownTheme, carryChoicePlaqueThemes: saved.carryChoicePlaqueThemes,