minigame 1 question 1.2
This commit is contained in:
@@ -725,6 +725,10 @@ code {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.quiz-round-q-fieldset .quiz-round-q-label {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.quiz-admin-questions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -187,6 +187,13 @@
|
||||
el('quiz-answer-sec').value = String(Math.round((data.answerMs || 5000) / 1000));
|
||||
var bMs = data.betweenMs != null ? data.betweenMs : 3500;
|
||||
el('quiz-between-sec').value = String(Math.max(0, Math.round(bMs / 1000)));
|
||||
var rqIn = el('quiz-round-q-count');
|
||||
if (rqIn) {
|
||||
var rq = data.quizRoundQuestionCount != null ? parseInt(data.quizRoundQuestionCount, 10) : 10;
|
||||
if (Number.isNaN(rq) || rq < 1) rq = 10;
|
||||
rq = Math.min(50, rq);
|
||||
rqIn.value = String(rq);
|
||||
}
|
||||
renderQuizAdminQuestions(data.questions);
|
||||
if (data.quizMapPanelTheme && typeof data.quizMapPanelTheme === 'object') {
|
||||
quizTfFillFormFromTheme(data.quizMapPanelTheme);
|
||||
@@ -210,6 +217,11 @@
|
||||
ansS = Math.min(300, ansS);
|
||||
betS = Math.min(120, betS);
|
||||
|
||||
var roundQEl = el('quiz-round-q-count');
|
||||
var roundQ = roundQEl ? parseInt(roundQEl.value, 10) : 10;
|
||||
if (Number.isNaN(roundQ) || roundQ < 1) roundQ = 10;
|
||||
roundQ = Math.min(50, roundQ);
|
||||
|
||||
var questions = [];
|
||||
document.querySelectorAll('#quiz-admin-questions-list .quiz-admin-q-row').forEach(function (row) {
|
||||
var inp = row.querySelector('.quiz-admin-q-text');
|
||||
@@ -223,6 +235,7 @@
|
||||
readMs: readS * 1000,
|
||||
answerMs: ansS * 1000,
|
||||
betweenMs: betS * 1000,
|
||||
quizRoundQuestionCount: roundQ,
|
||||
questions: questions,
|
||||
quizMapPanelTheme: quizTfBuildThemeForSave(),
|
||||
}).then(function () {
|
||||
|
||||
@@ -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', 'questions', 'carryQuestions', 'battleQuizMcq'];
|
||||
$mergeKeys = ['readMs', 'answerMs', 'betweenMs', 'carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryMapPanelTheme', 'quizMapPanelTheme', 'carryEmbedCountdownTheme', 'carryChoicePlaqueTheme', 'carryChoicePlaqueThemes', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount', 'questions', 'carryQuestions', 'battleQuizMcq'];
|
||||
foreach ($mergeKeys as $k) {
|
||||
if (array_key_exists($k, $patch)) {
|
||||
$base[$k] = $patch[$k];
|
||||
@@ -106,7 +106,7 @@ function overlay_quiz_settings_from_disk(string $nodeBody): string
|
||||
$j['carryEmbedCountdownTheme'] = $disk['carryEmbedCountdownTheme'];
|
||||
}
|
||||
/* ไม่ทับ carryChoicePlaqueThemes / carryChoicePlaqueTheme จากดิสก์ — ใช้ค่าจาก Node (อ่านไฟล์เดียวกันกับ disk merge) เพื่อกันทับด้วย JSON เก่าที่ไม่มี plaqueImageUrl ทำให้ช่อง URL ใน Admin ว่างหลังรีเฟรช */
|
||||
foreach (['carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult'] as $ck) {
|
||||
foreach (['carryReadMs', 'carryAnswerMs', 'carrySessionLength', 'carryChoicePlaqueMapScale', 'carryWalkSpeedMultForMapId', 'carryWalkSpeedMult', 'quizRoundQuestionCount'] as $ck) {
|
||||
if (array_key_exists($ck, $disk)) {
|
||||
$j[$ck] = $disk[$ck];
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
<section id="tab-panel-quiz" class="tab-panel card" hidden role="tabpanel" aria-labelledby="tab-quiz">
|
||||
<h2>คำถามเกม (ถูก / ผิด)</h2>
|
||||
<p class="muted">ใช้กับฉากประเภท <strong>เกมตอบคำถาม</strong> ใน Editor — สุ่มสูงสุด <strong>10 ข้อ</strong> ต่อรอบ · เวลาเป็นวินาที (เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/quiz-settings.json</code>)</p>
|
||||
<p class="muted">ใช้กับฉากประเภท <strong>เกมตอบคำถาม</strong> ใน Editor — จำนวนข้อที่สุ่มต่อรอบตั้งได้ด้านล่าง (สูงสุด 50, ค่าเริ่ม 10) · เวลาเป็นวินาที (เก็บที่เซิร์ฟเวอร์เกม <code>/Game/data/quiz-settings.json</code>) · <em>English:</em> True/false quiz draws up to N shuffled questions per game session.</p>
|
||||
<fieldset class="quiz-timing-fieldset">
|
||||
<legend>เวลาในเกม</legend>
|
||||
<div class="form-grid form-inline quiz-timing-grid">
|
||||
@@ -154,6 +154,11 @@
|
||||
<label>พักระหว่างข้อ (วินาที) <input type="number" id="quiz-between-sec" min="0" max="120" step="1" value="3"></label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="quiz-timing-fieldset quiz-round-q-fieldset">
|
||||
<legend>จำนวนข้อต่อรอบ</legend>
|
||||
<p class="muted" style="margin:0 0 0.65rem;font-size:0.82rem;line-height:1.45">สุ่มจากชุดคำถามทั้งหมด (Admin + แมป) แล้วเล่นตามลำดับ — ไม่เกินจำนวนข้อที่มีจริง · เก็บที่ <code>quizRoundQuestionCount</code> ใน <code>quiz-settings.json</code> · <em>English:</em> How many shuffled true/false questions per game (1–50, default 10).</p>
|
||||
<label class="quiz-round-q-label" title="1–50 ข้อต่อรอบ">สุ่มสูงสุดกี่ข้อต่อรอบ <input type="number" id="quiz-round-q-count" min="1" max="50" step="1" value="10" aria-label="จำนวนข้อที่สุ่มต่อรอบ 1 ถึง 50"></label>
|
||||
</fieldset>
|
||||
<fieldset class="quiz-tf-theme-fieldset" style="margin:0.75rem 0 1rem;padding:0.75rem 1rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-elevated)">
|
||||
<legend class="quiz-tf-theme-legend" style="font-size:0.92rem;font-weight:600;padding:0 0.35rem">แผงคำถามบนแผนที่ (โซนทองใน Editor)</legend>
|
||||
<p class="muted" style="margin:0 0 0.65rem;font-size:0.82rem;line-height:1.45">ใช้กับ <code>#quiz-map-question-panel</code> ในโหมด <strong>เกมตอบคำถามถูก/ผิด</strong> · เก็บที่ <code>quizMapPanelTheme</code> ใน <code>quiz-settings.json</code> · <em>English:</em> Panel BG, border, text — same as carry panel theme pickers.</p>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"readMs": 30000,
|
||||
"readMs": 5000,
|
||||
"answerMs": 5000,
|
||||
"betweenMs": 3000,
|
||||
"carryReadMs": 3000,
|
||||
@@ -8,14 +8,14 @@
|
||||
"carryMapPanelTheme": {
|
||||
"panelBg": "rgba(255, 0, 0, 0)",
|
||||
"panelBorder": "rgba(255, 255, 255, 0)",
|
||||
"textColor": "rgba(241, 245, 249, 1)",
|
||||
"borderWidthPx": 0,
|
||||
"textColor": "rgba(241, 245, 249, 1)",
|
||||
"questionFontMinPx": 10,
|
||||
"questionFontMaxPx": 24
|
||||
},
|
||||
"quizMapPanelTheme": {
|
||||
"panelBg": "rgba(12, 14, 28, 0.88)",
|
||||
"panelBorder": "rgba(255, 214, 102, 0.7)",
|
||||
"panelBg": "rgba(12, 14, 28, 0)",
|
||||
"panelBorder": "rgba(255, 214, 102, 0)",
|
||||
"textColor": "rgba(255, 224, 102, 1)",
|
||||
"borderWidthPx": 2,
|
||||
"questionFontMinPx": 10,
|
||||
@@ -175,6 +175,7 @@
|
||||
"carryChoicePlaqueMapScale": 1.9,
|
||||
"carryWalkSpeedMultForMapId": "",
|
||||
"carryWalkSpeedMult": null,
|
||||
"quizRoundQuestionCount": 3,
|
||||
"questions": [
|
||||
{
|
||||
"text": "test1",
|
||||
|
||||
@@ -1003,6 +1003,8 @@
|
||||
let playQuizTimerInterval = null;
|
||||
/** Preview-only: real question pool + phased timer (matches server quiz-settings / map quizQuestions). */
|
||||
let previewQuizPool = [];
|
||||
/** ดัชนีข้อในรอบพรีวิว (0..len-1) — หลังสับแล้วตัดตาม quizRoundQuestionCount เหมือนเซิร์ฟเวอร์ */
|
||||
let previewQuizQIndex = 0;
|
||||
let previewQuizTiming = { readMs: 10000, answerMs: 5000, betweenMs: 3500 };
|
||||
let previewQuizStep = 'read';
|
||||
let previewQuizCurrent = null;
|
||||
@@ -2090,9 +2092,53 @@
|
||||
.map((q) => ({ text: String(q.text).trim(), answerTrue: !!q.answerTrue }));
|
||||
}
|
||||
|
||||
function pickRandomQuizFromPool(pool) {
|
||||
if (!pool || !pool.length) return null;
|
||||
return pool[Math.floor(Math.random() * pool.length)];
|
||||
function shufflePreviewQuizQuestionsPlay(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;
|
||||
}
|
||||
|
||||
/** ตรงกับ server clampQuizRoundQuestionCount — 1–50 ค่าเริ่ม 10 */
|
||||
function clampPreviewQuizRoundQuestionCount(settings) {
|
||||
const v = Number(settings && settings.quizRoundQuestionCount);
|
||||
if (!Number.isFinite(v)) return 10;
|
||||
return Math.max(1, Math.min(50, Math.floor(v)));
|
||||
}
|
||||
|
||||
function finishPreviewQuizSessionPlay() {
|
||||
previewQuizStep = 'done';
|
||||
playQuizPhaseLocal = null;
|
||||
previewQuizCurrent = null;
|
||||
playQuizPhaseEndsAt = 0;
|
||||
if (playQuizTimerInterval) {
|
||||
clearInterval(playQuizTimerInterval);
|
||||
playQuizTimerInterval = null;
|
||||
}
|
||||
const qov = document.getElementById('quiz-game-overlay');
|
||||
if (qov) qov.classList.add('is-hidden');
|
||||
const mapPanel = document.getElementById('quiz-map-question-panel');
|
||||
if (mapPanel) {
|
||||
mapPanel.classList.add('is-hidden');
|
||||
mapPanel.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
if (isQuizQuestionMissionUiMapPlay()) {
|
||||
quizQuestionMissionPhase = 'ended';
|
||||
applyQuizQuestionMissionPanelImages();
|
||||
const mission = quizQuestionMissionBuildPayload();
|
||||
showGauntletCrownMissionOverlay(mission);
|
||||
return;
|
||||
}
|
||||
playQuizText = 'ครบทุกข้อในรอบทดสอบแล้ว';
|
||||
if (qov) qov.classList.remove('is-hidden');
|
||||
const phaseEl = document.getElementById('quiz-game-phase-label');
|
||||
if (phaseEl) phaseEl.textContent = '[ทดสอบ] จบรอบ';
|
||||
const qEl = document.getElementById('quiz-game-question');
|
||||
if (qEl) qEl.textContent = playQuizText;
|
||||
const leg = document.getElementById('quiz-play-legend');
|
||||
if (leg) leg.textContent = 'จำนวนข้อต่อรอบใช้ค่าเดียวกับ Admin (quizRoundQuestionCount)';
|
||||
}
|
||||
|
||||
function clearPreviewBotAnswerPaths() {
|
||||
@@ -2123,7 +2169,9 @@
|
||||
playQuizText = q.text;
|
||||
playQuizPhaseEndsAt = Date.now() + previewQuizTiming.readMs;
|
||||
const phaseEl = document.getElementById('quiz-game-phase-label');
|
||||
if (phaseEl) phaseEl.textContent = '[ทดสอบ] อ่านคำถาม · สุ่มจากชุด ' + poolLen + ' ข้อ';
|
||||
if (phaseEl) {
|
||||
phaseEl.textContent = '[ทดสอบ] อ่านคำถาม · ข้อ ' + (previewQuizQIndex + 1) + ' / ' + poolLen;
|
||||
}
|
||||
const qEl = document.getElementById('quiz-game-question');
|
||||
if (qEl) qEl.textContent = playQuizText;
|
||||
const leg = document.getElementById('quiz-play-legend');
|
||||
@@ -2156,7 +2204,7 @@
|
||||
const qEl = document.getElementById('quiz-game-question');
|
||||
if (qEl) qEl.textContent = playQuizText;
|
||||
const leg = document.getElementById('quiz-play-legend');
|
||||
if (leg) leg.textContent = 'รอสักครู่แล้วจะสุ่มคำถามใหม่จากชุดเดิม';
|
||||
if (leg) leg.textContent = 'รอสักครู่แล้วไปข้อถัดไป (สับแล้วตามจำนวนข้อต่อรอบ)';
|
||||
}
|
||||
|
||||
function quizCellOnPlay(grid, x, y) {
|
||||
@@ -2691,6 +2739,7 @@
|
||||
|
||||
function advancePreviewQuizIfDue() {
|
||||
if (isQuizQuestionMissionUiMapPlay() && isQuizQuestionMissionPregameBlockingPlay()) return;
|
||||
if (previewQuizStep === 'done') return;
|
||||
if (!previewMode || !isQuiz() || !playQuizPhaseEndsAt || Date.now() < playQuizPhaseEndsAt) return;
|
||||
const pool = previewQuizPool;
|
||||
if (!pool || !pool.length) return;
|
||||
@@ -2704,7 +2753,12 @@
|
||||
return;
|
||||
}
|
||||
if (previewQuizStep === 'between') {
|
||||
const q = pickRandomQuizFromPool(pool);
|
||||
previewQuizQIndex += 1;
|
||||
if (previewQuizQIndex >= pool.length) {
|
||||
finishPreviewQuizSessionPlay();
|
||||
return;
|
||||
}
|
||||
const q = pool[previewQuizQIndex];
|
||||
if (q) applyPreviewReadPhase(q, pool.length);
|
||||
}
|
||||
}
|
||||
@@ -2723,7 +2777,10 @@
|
||||
.map((q) => ({ text: String(q.text).trim(), answerTrue: !!q.answerTrue }));
|
||||
}
|
||||
if (!pool.length && mapData) pool = buildQuizPoolFromMap(mapData);
|
||||
previewQuizPool = pool;
|
||||
const cap = clampPreviewQuizRoundQuestionCount(settings);
|
||||
const shuffled = shufflePreviewQuizQuestionsPlay(pool);
|
||||
previewQuizPool = shuffled.slice(0, Math.min(cap, shuffled.length));
|
||||
previewQuizQIndex = 0;
|
||||
const dRead = 10000;
|
||||
const dAns = 5000;
|
||||
const dBet = 3500;
|
||||
@@ -2748,8 +2805,9 @@
|
||||
startPlayQuizTimer();
|
||||
return;
|
||||
}
|
||||
const q = pickRandomQuizFromPool(pool);
|
||||
if (q) applyPreviewReadPhase(q, pool.length);
|
||||
const session = previewQuizPool;
|
||||
const q = session[0];
|
||||
if (q) applyPreviewReadPhase(q, session.length);
|
||||
initPlayLiveQuizScoresZeros();
|
||||
startPlayQuizTimer();
|
||||
}
|
||||
@@ -2782,6 +2840,7 @@
|
||||
playQuizText = '';
|
||||
playQuizPhaseEndsAt = 0;
|
||||
previewQuizPool = [];
|
||||
previewQuizQIndex = 0;
|
||||
previewQuizCurrent = null;
|
||||
previewQuizStep = 'read';
|
||||
playLiveQuizScores = {};
|
||||
@@ -2800,6 +2859,9 @@
|
||||
clearTimeout(quizQuestionMissionCountdownTimer);
|
||||
quizQuestionMissionCountdownTimer = null;
|
||||
}
|
||||
const gcmTeardown = document.getElementById('gauntlet-crown-mission-overlay');
|
||||
if (gcmTeardown) gcmTeardown.classList.add('is-hidden');
|
||||
cancelEmbedPreviewLobbyReturnTimer();
|
||||
const grabBtnTeardown = document.getElementById('quiz-carry-grab-btn');
|
||||
if (grabBtnTeardown) {
|
||||
grabBtnTeardown.classList.add('is-hidden');
|
||||
@@ -7095,7 +7157,14 @@
|
||||
btn.onclick = function () {
|
||||
if (previewMode && editorEmbedReturn) {
|
||||
ov.classList.add('is-hidden');
|
||||
showQuizCarryTimeupOnDeskLayer(function () { return gauntletCrownEmbedMissionAnyOk(disp); });
|
||||
if (mission && mission.uiSkin === 'question_mission') {
|
||||
cancelQuizCarryResultEndAfterTimeup();
|
||||
hideQuizCarryTimeupOnDeskLayer();
|
||||
hideQuizCarryResultEndLayer();
|
||||
scheduleEmbedPreviewReturnToLobbyAfterResultEnd();
|
||||
} else {
|
||||
showQuizCarryTimeupOnDeskLayer(function () { return gauntletCrownEmbedMissionAnyOk(disp); });
|
||||
}
|
||||
} else {
|
||||
goLobby();
|
||||
}
|
||||
|
||||
@@ -1966,7 +1966,7 @@
|
||||
</div>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0184"></script>
|
||||
<script src="js/play.js?v=0.238"></script>
|
||||
<script src="js/play.js?v=0.240"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+23
-1
@@ -196,6 +196,8 @@ function defaultQuizSettings() {
|
||||
/** quiz_carry: ถ้าใส่รหัสฉาก + คูณ — เดินเร็วเฉพาะแมปนั้น (เทียบกับค่าเริ่มในไคลเอนต์) */
|
||||
carryWalkSpeedMultForMapId: '',
|
||||
carryWalkSpeedMult: null,
|
||||
/** ถูก/ผิด: สุ่มกี่ข้อต่อรอบจากชุดคำถาม (สูงสุดเท่าที่มีในชุด) */
|
||||
quizRoundQuestionCount: 10,
|
||||
questions: [],
|
||||
carryQuestions: [],
|
||||
battleQuizMcq: [],
|
||||
@@ -445,6 +447,17 @@ function clampCarrySessionLength(n, def) {
|
||||
return Math.max(0, Math.min(500, Math.floor(v)));
|
||||
}
|
||||
|
||||
/** เกมตอบคำถามถูก/ผิด: จำนวนข้อที่สุ่มจากชุดต่อรอบ (เซสชัน) — 1–50 */
|
||||
function clampQuizRoundQuestionCount(n, def) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v)) {
|
||||
const d = Number(def);
|
||||
if (!Number.isFinite(d)) return 10;
|
||||
return Math.max(1, Math.min(50, Math.floor(d)));
|
||||
}
|
||||
return Math.max(1, Math.min(50, Math.floor(v)));
|
||||
}
|
||||
|
||||
function clampCarryChoicePlaqueMapScale(n, def) {
|
||||
const v = Number(n);
|
||||
if (Number.isNaN(v)) return def;
|
||||
@@ -509,6 +522,9 @@ function mergeQuizCarryFromMirrorIfSet(base) {
|
||||
if (Array.isArray(j.carryQuestions) && j.carryQuestions.length) {
|
||||
base.carryQuestions = sanitizeCarryQuestions(j.carryQuestions);
|
||||
}
|
||||
if (j.quizRoundQuestionCount != null) {
|
||||
base.quizRoundQuestionCount = clampQuizRoundQuestionCount(j.quizRoundQuestionCount, base.quizRoundQuestionCount);
|
||||
}
|
||||
return base;
|
||||
} catch (e) {
|
||||
console.error('mergeQuizCarryFromMirrorIfSet', e.message);
|
||||
@@ -551,6 +567,7 @@ function loadQuizSettings() {
|
||||
carryChoicePlaqueMapScale,
|
||||
carryWalkSpeedMultForMapId,
|
||||
carryWalkSpeedMult,
|
||||
quizRoundQuestionCount: clampQuizRoundQuestionCount(j.quizRoundQuestionCount, d.quizRoundQuestionCount),
|
||||
questions,
|
||||
carryQuestions,
|
||||
battleQuizMcq,
|
||||
@@ -1090,6 +1107,9 @@ function saveQuizSettings(d) {
|
||||
} else if (carryWalkSpeedMult == null) {
|
||||
carryWalkSpeedMult = clampCarryWalkSpeedMultSetting(1.42);
|
||||
}
|
||||
const quizRoundQuestionCount = d.quizRoundQuestionCount != null
|
||||
? clampQuizRoundQuestionCount(d.quizRoundQuestionCount, prev.quizRoundQuestionCount)
|
||||
: prev.quizRoundQuestionCount;
|
||||
const out = {
|
||||
readMs,
|
||||
answerMs,
|
||||
@@ -1105,6 +1125,7 @@ function saveQuizSettings(d) {
|
||||
carryChoicePlaqueMapScale,
|
||||
carryWalkSpeedMultForMapId,
|
||||
carryWalkSpeedMult,
|
||||
quizRoundQuestionCount,
|
||||
questions,
|
||||
carryQuestions,
|
||||
battleQuizMcq,
|
||||
@@ -1460,7 +1481,8 @@ function startQuizGame(sid, space, md) {
|
||||
const settings = loadQuizSettings();
|
||||
const pool = getQuizQuestionPool(md);
|
||||
const shuffled = shuffleQuizQuestions(pool);
|
||||
const picked = shuffled.slice(0, Math.min(10, shuffled.length));
|
||||
const cap = clampQuizRoundQuestionCount(settings.quizRoundQuestionCount, 10);
|
||||
const picked = shuffled.slice(0, Math.min(cap, shuffled.length));
|
||||
const players = {};
|
||||
space.peers.forEach((_, peerId) => {
|
||||
players[peerId] = { cannotTrue: false, cannotFalse: false, eliminated: false, score: 0 };
|
||||
|
||||
Reference in New Issue
Block a user