update 50 users
This commit is contained in:
@@ -2943,6 +2943,10 @@ code {
|
||||
gap: 1.1rem;
|
||||
align-items: start;
|
||||
}
|
||||
/* สำคัญ: id selector มี specificity สูงกว่า [hidden] ของเบราว์เซอร์ → ต้องบังคับซ่อนเมื่อยังไม่ login */
|
||||
#panel-app[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ---- Sidebar (เดิมเป็นแถบแท็บแนวนอน) ---- */
|
||||
#panel-app .admin-tabs {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="admin.css?v=39">
|
||||
<link rel="stylesheet" href="admin.css?v=40">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#admin-main">ข้ามไปเนื้อหา</a>
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
{
|
||||
"gauntletTickMs": 150,
|
||||
"gauntletJumpTicks": 5,
|
||||
"gauntletTimeLimitSec": 60,
|
||||
"gauntletLaneImageUrls": [
|
||||
"/Game/img/gauntlet-assets/gauntlet-6aa244ab734b12e4.png",
|
||||
"/Game/img/gauntlet-assets/gauntlet-c4d4482e2a60eac9.png",
|
||||
"/Game/img/gauntlet-assets/gauntlet-67a585790f67fe67.png",
|
||||
"/Game/img/gauntlet-assets/gauntlet-077c293962b8fe07.png"
|
||||
],
|
||||
"gauntletLaserTopUrl": "/Game/img/gauntlet-assets/gauntlet-dd51baed17270995.png",
|
||||
"gauntletLaserBottomUrl": "/Game/img/gauntlet-assets/gauntlet-dd51baed17270995.png",
|
||||
"gauntletLaserLineUrl": "/Game/img/gauntlet-assets/gauntlet-2a75a958cd5e5e8a.png",
|
||||
"gauntletLaserFillColor": "rgba(255,204,204,0)",
|
||||
"gauntletLaserStrokeColor": "rgba(255,220,255,0)",
|
||||
"gauntletLaserLineWidthPx": 0,
|
||||
"gauntletTickMs": 220,
|
||||
"gauntletJumpTicks": 16,
|
||||
"gauntletTimeLimitSec": 0,
|
||||
"gauntletLaneImageUrls": [],
|
||||
"gauntletLaserTopUrl": "",
|
||||
"gauntletLaserBottomUrl": "",
|
||||
"gauntletLaserLineUrl": "",
|
||||
"gauntletLaserFillColor": "rgba(140,230,255,0.42)",
|
||||
"gauntletLaserStrokeColor": "rgba(255,220,255,0.9)",
|
||||
"gauntletLaserLineWidthPx": 2,
|
||||
"stackSwingHz": 0.1,
|
||||
"stackBlockWidthTiles": 3.2,
|
||||
"stackTowerMissionTimeSec": 90,
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"qbroom1":[{"name":"ผู้เล่น8916","score":4,"characterId":"char-1777017632279","ts":1781074083031},{"name":"ผู้เล่น2821","score":4,"characterId":"char-1777017632279","ts":1781079179108},{"name":"Q","score":0,"characterId":"char-1777017632279","ts":1781082837883}],"qbroom2":[{"name":"ผู้เล่น2612","score":1,"characterId":"char-1777017632279","ts":1781079252376}]}
|
||||
{"qbroom1":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781171902962},{"name":"ผู้เล่น8916","score":4,"characterId":"char-1777017632279","ts":1781074083031},{"name":"ผู้เล่น2821","score":4,"characterId":"char-1777017632279","ts":1781079179108}],"qbroom2":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781175468983},{"name":"ผู้เล่น2612","score":1,"characterId":"char-1777017632279","ts":1781079252376}],"qbroom3":[{"name":"Q","score":9,"characterId":"char-1777017632279","ts":1781175654893}]}
|
||||
@@ -127,6 +127,7 @@
|
||||
<option value="quizBattlePath" id="draw-mode-option-quiz-battle-path" hidden>เส้นทางเดิน (ม่วง — วาดอย่างน้อย 1 ช่อง = จำกัดเดินเฉพาะเส้นทาง)</option>
|
||||
<option value="quizBattleDome" id="draw-mode-option-quiz-battle-dome" hidden>โดมคำถาม (ช่องกด E ในเกม)</option>
|
||||
<option value="quizBattlePathGate" id="draw-mode-option-quiz-battle-path-gate" hidden>จุดปลดล็อก / ประตู (แดง — วางบนเส้นทาง ต้องตอบสถานีก่อนผ่าน)</option>
|
||||
<option value="quizBattleFinish" id="draw-mode-option-quiz-battle-finish" hidden>จุดจบ / ปลายทาง (เขียว — ตอบครบทุกข้อแล้วผู้เล่นไปอยู่ตรงนี้)</option>
|
||||
</optgroup>
|
||||
<optgroup label="กระโดดให้รอด">
|
||||
<option value="jumpSurvivePlatform1" id="draw-mode-option-jump-platform-1" hidden>แพลตฟอร์ม 1 (ยืนได้ — รูปช่อง 1 จาก Admin)</option>
|
||||
@@ -164,7 +165,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<span id="quiz-battle-gate-wrap" class="editor-quiz-battle-gate-wrap" style="display:none;flex-wrap:wrap;align-items:center;gap:0.35rem 0.65rem;max-width:100%;" title="เลือกคำถามที่ต้องตอบถูกก่อนผ่าน — คลิกโดมเพื่อเลือก · คลิกเส้นทางม่วงวางประตู · ขวาลบ">
|
||||
<label style="display:flex;flex-direction:column;gap:0.15rem;min-width:12rem;flex:1 1 220px;">คำถามปลดล็อก
|
||||
<label style="display:flex;flex-direction:column;gap:0.15rem;min-width:12rem;flex:1 1 220px;">คำถาม (โดม / จุดปลดล็อก)
|
||||
<select id="quiz-battle-gate-question" style="max-width:100%;">
|
||||
<option value="1">กำลังโหลดคำถาม…</option>
|
||||
</select>
|
||||
@@ -425,7 +426,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/version.js?v=0.0258"></script>
|
||||
<script src="js/editor.js?v=0.0341"></script>
|
||||
<script src="js/editor.js?v=0.0344"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
|
||||
<!-- ===== QB overlay layout: map เต็มจอ + แถบเครื่องมือลอย ดัก/พับ/ปิดเปิดได้ (เพิ่มทับ ไม่แตะ editor.js) ===== -->
|
||||
|
||||
@@ -110,6 +110,10 @@
|
||||
let quizBattlePathArea = [];
|
||||
/** quiz_battle: กริด 0/สถานี — จุดประตูบนเส้นทาง (ต้องตอบสถานี N ก่อนผ่านช่องนี้และข้างหน้า) */
|
||||
let quizBattlePathGateArea = [];
|
||||
/** quiz_battle: กริด 0/ข้อ — บังคับว่าโดมช่องนี้ใช้คำถามข้อไหน (0 = อัตโนมัติตามลำดับเดิน) */
|
||||
let quizBattleDomeQuestionArea = [];
|
||||
/** quiz_battle: กริด 0/1 — จุดจบ/ปลายทาง · ตอบครบทุกข้อแล้วผู้เล่นย้ายไปอยู่กลางโซนนี้ */
|
||||
let quizBattleFinishArea = [];
|
||||
/** quiz_battle: หมวดคำถามของห้อง (topic1..topic10) — กรองจาก battleQuizMcq */
|
||||
let editorQuizBattleCategoryId = '';
|
||||
/** quiz_battle: รายการคำถามใน Editor [{ compId, text }] */
|
||||
@@ -1354,6 +1358,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
function ensureQuizBattleDomeQuestionArea() {
|
||||
if (!quizBattleDomeQuestionArea.length || quizBattleDomeQuestionArea.length !== height) {
|
||||
const existing = quizBattleDomeQuestionArea.slice().map(r => r && r.slice());
|
||||
quizBattleDomeQuestionArea = [];
|
||||
for (let y = 0; y < height; y++) {
|
||||
const row = existing[y] && existing[y].length === width ? existing[y].slice() : Array(width).fill(0);
|
||||
quizBattleDomeQuestionArea.push(row);
|
||||
}
|
||||
} else {
|
||||
for (let y = 0; y < height; y++) {
|
||||
if (!quizBattleDomeQuestionArea[y] || quizBattleDomeQuestionArea[y].length !== width) quizBattleDomeQuestionArea[y] = Array(width).fill(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readQuizBattleDomeQuestionSelect() {
|
||||
const sel = document.getElementById('quiz-battle-gate-question');
|
||||
let n = sel ? parseInt(sel.value, 10) : 0;
|
||||
if (!Number.isFinite(n) || n < 0) n = 0;
|
||||
return n; /* 0 = อัตโนมัติ */
|
||||
}
|
||||
|
||||
function ensureQuizBattleFinishArea() {
|
||||
if (!quizBattleFinishArea.length || quizBattleFinishArea.length !== height) {
|
||||
const existing = quizBattleFinishArea.slice().map(r => r && r.slice());
|
||||
quizBattleFinishArea = [];
|
||||
for (let y = 0; y < height; y++) {
|
||||
const row = existing[y] && existing[y].length === width ? existing[y].slice() : Array(width).fill(0);
|
||||
quizBattleFinishArea.push(row);
|
||||
}
|
||||
} else {
|
||||
for (let y = 0; y < height; y++) {
|
||||
if (!quizBattleFinishArea[y] || quizBattleFinishArea[y].length !== width) quizBattleFinishArea[y] = Array(width).fill(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function inferQuizBattleCategoryFromMapId(id) {
|
||||
const m = /^qbroom(\d+)$/i.exec(String(id || '').trim());
|
||||
if (!m) return '';
|
||||
@@ -1368,28 +1409,63 @@
|
||||
}
|
||||
const w = width, h = height;
|
||||
const dome = quizBattleDomeArea;
|
||||
const comp = Array(h).fill(0).map(() => Array(w).fill(0));
|
||||
const seen = Array(h).fill(0).map(() => Array(w).fill(false));
|
||||
let compId = 0;
|
||||
/* 1) flood-fill หากลุ่มโดม */
|
||||
const raw = Array(h).fill(0).map(() => Array(w).fill(0));
|
||||
const compsCells = [];
|
||||
let rawId = 0;
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
if (!dome[y] || dome[y][x] !== 1 || seen[y][x]) continue;
|
||||
compId++;
|
||||
if (!dome[y] || dome[y][x] !== 1 || raw[y][x]) continue;
|
||||
rawId++;
|
||||
const cells = [];
|
||||
const q = [[x, y]];
|
||||
seen[y][x] = true;
|
||||
raw[y][x] = rawId;
|
||||
while (q.length) {
|
||||
const [cx, cy] = q.shift();
|
||||
comp[cy][cx] = compId;
|
||||
cells.push([cx, cy]);
|
||||
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
||||
const nx = cx + dx, ny = cy + dy;
|
||||
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
|
||||
if (!dome[ny] || dome[ny][nx] !== 1 || seen[ny][nx]) continue;
|
||||
seen[ny][nx] = true;
|
||||
if (!dome[ny] || dome[ny][nx] !== 1 || raw[ny][nx]) continue;
|
||||
raw[ny][nx] = rawId;
|
||||
q.push([nx, ny]);
|
||||
}
|
||||
}
|
||||
compsCells.push(cells);
|
||||
}
|
||||
}
|
||||
/* 2) BFS ระยะจากจุดเกิด (เส้นทาง+โดม) — จัดเลข "เดินถึงก่อน = เลขน้อยกว่า" */
|
||||
const path = quizBattlePathArea;
|
||||
const dist = Array(h).fill(0).map(() => Array(w).fill(-1));
|
||||
const bq = [];
|
||||
const sx = Math.max(0, Math.min(w - 1, Math.floor(Number((spawn && spawn.x) || 0))));
|
||||
const sy = Math.max(0, Math.min(h - 1, Math.floor(Number((spawn && spawn.y) || 0))));
|
||||
const walkable = (tx, ty) => ((path && path[ty] && path[ty][tx] === 1) || (dome[ty] && dome[ty][tx] === 1));
|
||||
const enq = (tx, ty, d) => {
|
||||
if (tx < 0 || ty < 0 || tx >= w || ty >= h) return;
|
||||
if (dist[ty][tx] >= 0 || !walkable(tx, ty)) return;
|
||||
dist[ty][tx] = d; bq.push([tx, ty, d]);
|
||||
};
|
||||
enq(sx, sy, 0);
|
||||
if (dist[sy][sx] < 0) { for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) enq(sx + dx, sy + dy, 0); }
|
||||
if (spawnArea && Array.isArray(spawnArea)) {
|
||||
for (let ty = 0; ty < h; ty++) { const row = spawnArea[ty]; if (!row) continue; for (let tx = 0; tx < w; tx++) if (row[tx] === 1) enq(tx, ty, 0); }
|
||||
}
|
||||
let head = 0;
|
||||
while (head < bq.length) {
|
||||
const cur = bq[head++];
|
||||
enq(cur[0] + 1, cur[1], cur[2] + 1); enq(cur[0] - 1, cur[1], cur[2] + 1);
|
||||
enq(cur[0], cur[1] + 1, cur[2] + 1); enq(cur[0], cur[1] - 1, cur[2] + 1);
|
||||
}
|
||||
/* 3) เรียงกลุ่มตามระยะ → ให้เลข 1..N */
|
||||
const order = compsCells.map((cells, i) => {
|
||||
let mn = Infinity;
|
||||
for (const [cx, cy] of cells) { const dd = dist[cy][cx]; if (dd >= 0 && dd < mn) mn = dd; }
|
||||
return { idx: i, minDist: mn, fc: cells[0] };
|
||||
});
|
||||
order.sort((a, b) => (a.minDist - b.minDist) || (a.fc[1] - b.fc[1]) || (a.fc[0] - b.fc[0]));
|
||||
const comp = Array(h).fill(0).map(() => Array(w).fill(0));
|
||||
order.forEach((o, rank) => { for (const [cx, cy] of compsCells[o.idx]) comp[cy][cx] = rank + 1; });
|
||||
editorQuizBattleDomeComp = comp;
|
||||
}
|
||||
|
||||
@@ -1423,6 +1499,11 @@
|
||||
if (!sel) return;
|
||||
const prev = sel.value;
|
||||
sel.innerHTML = '';
|
||||
/* โหมดโดม: เลือก "อัตโนมัติ" = ใช้คำถามตามลำดับเดิน · เลือกข้อ N = บังคับโดมนั้นเป็นคำถามข้อ N */
|
||||
const autoOpt = document.createElement('option');
|
||||
autoOpt.value = '0';
|
||||
autoOpt.textContent = 'อัตโนมัติ (ตามลำดับเดิน)';
|
||||
sel.appendChild(autoOpt);
|
||||
if (!editorQuizBattleMcqList.length) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '1';
|
||||
@@ -2184,6 +2265,11 @@
|
||||
dqBattleGate.hidden = gt !== 'quiz_battle';
|
||||
if (gt !== 'quiz_battle' && drawModeEl && drawModeEl.value === 'quizBattlePathGate') drawModeEl.value = 'wall';
|
||||
}
|
||||
const dqBattleFinish = document.getElementById('draw-mode-option-quiz-battle-finish');
|
||||
if (dqBattleFinish) {
|
||||
dqBattleFinish.hidden = gt !== 'quiz_battle';
|
||||
if (gt !== 'quiz_battle' && drawModeEl && drawModeEl.value === 'quizBattleFinish') drawModeEl.value = 'wall';
|
||||
}
|
||||
if (hint) hint.style.removeProperty('display');
|
||||
if (gt === 'frogger') {
|
||||
froggerWrap.style.display = 'block';
|
||||
@@ -2403,7 +2489,7 @@
|
||||
if (drawModeEl && carryModes.indexOf(drawModeEl.value) >= 0 && gt !== 'quiz_carry') {
|
||||
drawModeEl.value = 'wall';
|
||||
}
|
||||
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath' || drawModeEl.value === 'quizBattlePathGate') && gt !== 'quiz_battle') {
|
||||
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath' || drawModeEl.value === 'quizBattlePathGate' || drawModeEl.value === 'quizBattleFinish') && gt !== 'quiz_battle') {
|
||||
drawModeEl.value = 'wall';
|
||||
}
|
||||
const jPlat1 = document.getElementById('draw-mode-option-jump-platform-1');
|
||||
@@ -2446,6 +2532,8 @@
|
||||
quizCarryOptionArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
carryEmbedCountdownArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattleDomeArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattleDomeQuestionArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattleFinishArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattlePathArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattlePathGateArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
stackReleaseArea = Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
@@ -2917,6 +3005,19 @@
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(tx + 1, ty + 1, tileSize - 2, tileSize - 2);
|
||||
}
|
||||
if (gtDraw === 'quiz_battle' && quizBattleFinishArea[y] && quizBattleFinishArea[y][x] === 1) {
|
||||
ctx.fillStyle = 'rgba(80, 230, 130, 0.42)';
|
||||
ctx.fillRect(tx + 1, ty + 1, tileSize - 2, tileSize - 2);
|
||||
ctx.strokeStyle = 'rgba(120, 255, 170, 0.95)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(tx + 1, ty + 1, tileSize - 2, tileSize - 2);
|
||||
ctx.lineWidth = 1;
|
||||
ctx.fillStyle = '#0a2a18';
|
||||
ctx.font = 'bold 8px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('จบ', tx + tileSize / 2, ty + tileSize / 2 + 3);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
if (gtDraw === 'quiz_battle' && quizBattleDomeArea[y] && quizBattleDomeArea[y][x] === 1) {
|
||||
const dComp = getEditorQuizBattleDomeCompAt(x, y);
|
||||
ctx.fillStyle = 'rgba(100, 200, 255, 0.42)';
|
||||
@@ -2928,7 +3029,10 @@
|
||||
ctx.fillStyle = '#1a1b26';
|
||||
ctx.font = 'bold 10px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(dComp > 0 ? ('E' + dComp) : 'E', tx + tileSize / 2, ty + tileSize / 2 + 3);
|
||||
const qov = (quizBattleDomeQuestionArea[y] && quizBattleDomeQuestionArea[y][x]) | 0;
|
||||
const domeLbl = dComp > 0 ? ('E' + dComp + (qov > 0 ? ('→Q' + qov) : '')) : 'E';
|
||||
ctx.font = domeLbl.length > 4 ? 'bold 8px sans-serif' : 'bold 10px sans-serif';
|
||||
ctx.fillText(domeLbl, tx + tileSize / 2, ty + tileSize / 2 + 3);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
if (gtDraw === 'quiz_battle' && quizBattlePathGateArea[y] && quizBattlePathGateArea[y][x] > 0) {
|
||||
@@ -3465,8 +3569,31 @@
|
||||
} else if (drawModeEl.value === 'quizBattleDome') {
|
||||
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_battle') return;
|
||||
ensureQuizBattleDomeArea();
|
||||
quizBattleDomeArea[y][x] = left ? 1 : 0;
|
||||
rebuildEditorQuizBattleDomeComp();
|
||||
ensureQuizBattleDomeQuestionArea();
|
||||
if (left) {
|
||||
quizBattleDomeArea[y][x] = 1;
|
||||
rebuildEditorQuizBattleDomeComp();
|
||||
/* กำหนดคำถามให้ทั้งกลุ่มโดมที่คลิก (เลือกข้อจากเมนู "คำถามที่โดน/ปลดล็อก") */
|
||||
const qsel = readQuizBattleDomeQuestionSelect();
|
||||
const comp = getEditorQuizBattleDomeCompAt(x, y);
|
||||
if (comp > 0 && editorQuizBattleDomeComp) {
|
||||
for (let yy = 0; yy < height; yy++) {
|
||||
for (let xx = 0; xx < width; xx++) {
|
||||
if (editorQuizBattleDomeComp[yy] && editorQuizBattleDomeComp[yy][xx] === comp) quizBattleDomeQuestionArea[yy][xx] = qsel;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (statusEl) {
|
||||
const snip = qsel > 0 ? quizBattleGateQuestionSnippet(qsel) : '';
|
||||
statusEl.textContent = qsel > 0
|
||||
? ('โดม E' + comp + ' = คำถามข้อ ' + qsel + (snip ? (' 「' + snip + '」') : ''))
|
||||
: ('โดม E' + comp + ' = คำถามอัตโนมัติ (ตามลำดับเดิน)');
|
||||
}
|
||||
} else {
|
||||
quizBattleDomeArea[y][x] = 0;
|
||||
quizBattleDomeQuestionArea[y][x] = 0;
|
||||
rebuildEditorQuizBattleDomeComp();
|
||||
}
|
||||
} else if (drawModeEl.value === 'quizBattlePathGate') {
|
||||
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_battle') return;
|
||||
ensureQuizBattlePathArea();
|
||||
@@ -3491,14 +3618,18 @@
|
||||
quizBattlePathGateArea[y][x] = st;
|
||||
const snip = quizBattleGateQuestionSnippet(st);
|
||||
if (statusEl) {
|
||||
statusEl.textContent = 'ประตูข้อ ' + st + (snip ? (' 「' + snip + '」') : '') + ' → (' + x + ',' + y + ')';
|
||||
statusEl.textContent = 'ประตูข้อ ' + st + (snip ? (' 「' + snip + '」') : '') + ' → (' + x + ',' + y + ') · เลือกข้ออื่นเองได้จากเมนูคำถาม';
|
||||
}
|
||||
const next = editorQuizBattleMcqList.find((q) => q.compId > st);
|
||||
if (next) setQuizBattleGateQuestionSelect(next.compId);
|
||||
/* ไม่เลื่อนคำถามอัตโนมัติ — ให้ผู้ใช้เลือกเองจากเมนู (ตามที่ผู้ใช้ต้องการ) */
|
||||
} else {
|
||||
quizBattlePathGateArea[y][x] = 0;
|
||||
if (statusEl) statusEl.textContent = 'ลบประตูช่อง (' + x + ',' + y + ')';
|
||||
}
|
||||
} else if (drawModeEl.value === 'quizBattleFinish') {
|
||||
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_battle') return;
|
||||
ensureQuizBattleFinishArea();
|
||||
quizBattleFinishArea[y][x] = left ? 1 : 0;
|
||||
if (statusEl) statusEl.textContent = left ? ('จุดจบ/ปลายทาง → (' + x + ',' + y + ') · ตอบครบทุกข้อแล้วผู้เล่นมาที่นี่') : ('ลบจุดจบช่อง (' + x + ',' + y + ')');
|
||||
} else if (drawModeEl.value === 'carryEmbedCountdown') {
|
||||
if ((gameTypeEl ? gameTypeEl.value : gameType) !== 'quiz_carry') return;
|
||||
ensureQuizCarryAreas();
|
||||
@@ -3967,7 +4098,7 @@
|
||||
if (drawModeEl && drawModeEl.value === 'shooterSpawnPaint' && gameType !== 'space_shooter' && gameType !== 'jump_survive') drawModeEl.value = 'wall';
|
||||
if (drawModeEl && drawModeEl.value === 'balloonBossPlayerPaint' && gameType !== 'balloon_boss') drawModeEl.value = 'wall';
|
||||
if (drawModeEl && drawModeEl.value === 'balloonBossBossPaint' && gameType !== 'balloon_boss') drawModeEl.value = 'wall';
|
||||
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath' || drawModeEl.value === 'quizBattlePathGate') && gameType !== 'quiz_battle') drawModeEl.value = 'wall';
|
||||
if (drawModeEl && (drawModeEl.value === 'quizBattleDome' || drawModeEl.value === 'quizBattlePath' || drawModeEl.value === 'quizBattlePathGate' || drawModeEl.value === 'quizBattleFinish') && gameType !== 'quiz_battle') drawModeEl.value = 'wall';
|
||||
if (drawModeEl && drawModeEl.value === 'lobbyPlayerSpawn' && !supportsLobbySpawnPaint(gameType)) drawModeEl.value = 'wall';
|
||||
toggleFroggerUI();
|
||||
syncEditorStackHudMock();
|
||||
@@ -4158,6 +4289,8 @@
|
||||
quizBattleDomeArea: gameType === 'quiz_battle' ? quizBattleDomeArea.map(r => r.slice()) : [],
|
||||
quizBattlePathArea: gameType === 'quiz_battle' ? quizBattlePathArea.map(r => r.slice()) : [],
|
||||
quizBattlePathGateArea: gameType === 'quiz_battle' ? quizBattlePathGateArea.map(r => r.slice()) : [],
|
||||
quizBattleDomeQuestionArea: gameType === 'quiz_battle' ? quizBattleDomeQuestionArea.map(r => r.slice()) : [],
|
||||
quizBattleFinishArea: gameType === 'quiz_battle' ? quizBattleFinishArea.map(r => r.slice()) : [],
|
||||
quizBattleCategoryId: gameType === 'quiz_battle' && editorQuizBattleCategoryId
|
||||
? String(editorQuizBattleCategoryId).trim()
|
||||
: undefined,
|
||||
@@ -4289,7 +4422,8 @@
|
||||
const gateWrap = document.getElementById('quiz-battle-gate-wrap');
|
||||
if (gateWrap) {
|
||||
const gt = gameTypeEl ? gameTypeEl.value : gameType;
|
||||
const showGate = drawModeEl.value === 'quizBattlePathGate' && gt === 'quiz_battle';
|
||||
/* โชว์เมนูเลือกคำถามทั้งโหมด "จุดปลดล็อก" และ "โดมคำถาม" (ใช้กำหนดคำถามต่อโดม) */
|
||||
const showGate = (drawModeEl.value === 'quizBattlePathGate' || drawModeEl.value === 'quizBattleDome') && gt === 'quiz_battle';
|
||||
gateWrap.style.display = showGate ? 'inline-flex' : 'none';
|
||||
if (showGate && !editorQuizBattleMcqList.length) loadEditorQuizBattleMcqList();
|
||||
}
|
||||
@@ -4490,6 +4624,8 @@
|
||||
quizBattleDomeArea = m.quizBattleDomeArea && m.quizBattleDomeArea.length ? m.quizBattleDomeArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattlePathArea = m.quizBattlePathArea && m.quizBattlePathArea.length ? m.quizBattlePathArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattlePathGateArea = m.quizBattlePathGateArea && m.quizBattlePathGateArea.length ? m.quizBattlePathGateArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattleDomeQuestionArea = m.quizBattleDomeQuestionArea && m.quizBattleDomeQuestionArea.length ? m.quizBattleDomeQuestionArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
quizBattleFinishArea = m.quizBattleFinishArea && m.quizBattleFinishArea.length ? m.quizBattleFinishArea.map(r => r && r.slice()) : Array(height).fill(0).map(() => Array(width).fill(0));
|
||||
editorQuizBattleCategoryId = m.quizBattleCategoryId
|
||||
? String(m.quizBattleCategoryId).trim()
|
||||
: inferQuizBattleCategoryFromMapId(mapId);
|
||||
|
||||
+139
-12
@@ -3164,6 +3164,8 @@
|
||||
let quizBattleChoiceLocked = false;
|
||||
/** โชว์ popup Completed แล้วหรือยัง (กันเด้งซ้ำเมื่อตอบครบทุกโดม) */
|
||||
let quizBattleCompletedShown = false;
|
||||
/** ถึงจุดจบแล้ว — ล็อกตำแหน่ง ไม่ให้ echo/restore เด้งกลับ start หรือจุดเดิม */
|
||||
let quizBattleFinishLock = false;
|
||||
/** โดมที่ "ผ่านแล้ว" (ตอบถูกหรือผิดก็ผ่าน) — ใช้ปลดล็อกเส้นทาง + จำว่าเคยตอบ ไม่ต้องตอบซ้ำ */
|
||||
let quizBattlePassedComps = new Set();
|
||||
/** ซูมที่ผู้เล่นปรับเองในฉาก quiz_battle (ล้อเมาส์ / ปุ่ม +−) */
|
||||
@@ -13320,29 +13322,74 @@
|
||||
grid.push(row);
|
||||
}
|
||||
md.quizBattleDomeArea = grid;
|
||||
const comp = Array(h).fill(0).map(() => Array(w).fill(0));
|
||||
let nextId = 0;
|
||||
/* 1) flood-fill หากลุ่มโดม (raw component = 1 สถานีคำถาม) */
|
||||
const raw = Array(h).fill(0).map(() => Array(w).fill(0));
|
||||
const compsCells = []; // compsCells[i] = [[x,y],...] ของกลุ่มที่ i (rawId = i+1)
|
||||
let rawId = 0;
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
if (grid[y][x] !== 1 || comp[y][x]) continue;
|
||||
nextId++;
|
||||
if (grid[y][x] !== 1 || raw[y][x]) continue;
|
||||
rawId++;
|
||||
const cells = [];
|
||||
const stack = [[x, y]];
|
||||
comp[y][x] = nextId;
|
||||
raw[y][x] = rawId;
|
||||
while (stack.length) {
|
||||
const c = stack.pop();
|
||||
const cx = c[0], cy = c[1];
|
||||
cells.push([cx, cy]);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const dx = (i === 0 ? 1 : i === 1 ? -1 : 0);
|
||||
const dy = (i === 2 ? 1 : i === 3 ? -1 : 0);
|
||||
const nx = cx + dx, ny = cy + dy;
|
||||
if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
|
||||
if (grid[ny][nx] !== 1 || comp[ny][nx]) continue;
|
||||
comp[ny][nx] = nextId;
|
||||
if (grid[ny][nx] !== 1 || raw[ny][nx]) continue;
|
||||
raw[ny][nx] = rawId;
|
||||
stack.push([nx, ny]);
|
||||
}
|
||||
}
|
||||
compsCells.push(cells);
|
||||
}
|
||||
}
|
||||
/* 2) BFS ระยะทางจากจุดเกิด (เดินบนเส้นทาง+โดม) เพื่อจัดเลขโดมตามลำดับ "เดินถึงก่อน = เลขน้อยกว่า" */
|
||||
const path = md.quizBattlePathArea;
|
||||
const dist = Array(h).fill(0).map(() => Array(w).fill(-1));
|
||||
const bq = [];
|
||||
const sp = md.spawn || { x: 1, y: 1 };
|
||||
const stx = Math.max(0, Math.min(w - 1, Math.floor(Number(sp.x)) || 0));
|
||||
const sty = Math.max(0, Math.min(h - 1, Math.floor(Number(sp.y)) || 0));
|
||||
const walkable = (tx, ty) => {
|
||||
const onPath = path && path[ty] && path[ty][tx] === 1;
|
||||
const onDome = grid[ty] && grid[ty][tx] === 1;
|
||||
return onPath || onDome;
|
||||
};
|
||||
const enq = (tx, ty, d) => {
|
||||
if (tx < 0 || ty < 0 || tx >= w || ty >= h) return;
|
||||
if (dist[ty][tx] >= 0 || !walkable(tx, ty)) return;
|
||||
dist[ty][tx] = d; bq.push([tx, ty, d]);
|
||||
};
|
||||
enq(stx, sty, 0);
|
||||
if (dist[sty][stx] < 0) {
|
||||
for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) enq(stx + dx, sty + dy, 0);
|
||||
}
|
||||
const sa = md.spawnArea;
|
||||
if (sa && Array.isArray(sa)) {
|
||||
for (let ty = 0; ty < h; ty++) { const row = sa[ty]; if (!row) continue; for (let tx = 0; tx < w; tx++) if (row[tx] === 1) enq(tx, ty, 0); }
|
||||
}
|
||||
let head = 0;
|
||||
while (head < bq.length) {
|
||||
const cur = bq[head++];
|
||||
const cx = cur[0], cy = cur[1], cd = cur[2];
|
||||
enq(cx + 1, cy, cd + 1); enq(cx - 1, cy, cd + 1); enq(cx, cy + 1, cd + 1); enq(cx, cy - 1, cd + 1);
|
||||
}
|
||||
/* 3) ระยะทางต่ำสุดของแต่ละกลุ่ม → เรียง → ให้เลข 1..N (กลุ่มที่ไปไม่ถึงไว้ท้าย) */
|
||||
const order = compsCells.map((cells, i) => {
|
||||
let md0 = Infinity;
|
||||
for (const [cx, cy] of cells) { const dd = dist[cy][cx]; if (dd >= 0 && dd < md0) md0 = dd; }
|
||||
return { idx: i, minDist: md0, firstCell: cells[0] };
|
||||
});
|
||||
order.sort((a, b) => (a.minDist - b.minDist) || (a.firstCell[1] - b.firstCell[1]) || (a.firstCell[0] - b.firstCell[0]));
|
||||
const comp = Array(h).fill(0).map(() => Array(w).fill(0));
|
||||
order.forEach((o, rank) => { for (const [cx, cy] of compsCells[o.idx]) comp[cy][cx] = rank + 1; });
|
||||
md.quizBattleDomeComp = comp;
|
||||
}
|
||||
|
||||
@@ -13529,10 +13576,32 @@
|
||||
return quizBattleFirstMissingGateCompPlay(md, px, py, solvedSet) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* คำถามของโดม compId → index ใน pool — เคารพ "คำถามที่กำหนดเองต่อโดม" (quizBattleDomeQuestionArea) จาก Editor
|
||||
* ถ้าไม่กำหนด (0) ใช้ตามลำดับเดิน (compId-1)%len เหมือนเดิม
|
||||
*/
|
||||
function quizBattleQuestionIndexForCompPlay(compId, poolLen) {
|
||||
if (!(poolLen > 0)) return 0;
|
||||
let override = 0;
|
||||
const md = mapData;
|
||||
if (md && Array.isArray(md.quizBattleDomeComp) && Array.isArray(md.quizBattleDomeQuestionArea)) {
|
||||
const comp = md.quizBattleDomeComp, qa = md.quizBattleDomeQuestionArea;
|
||||
for (let y = 0; y < comp.length && !override; y++) {
|
||||
const cr = comp[y], qr = qa[y];
|
||||
if (!cr || !qr) continue;
|
||||
for (let x = 0; x < cr.length; x++) {
|
||||
if (cr[x] === compId && (qr[x] | 0) > 0) { override = qr[x] | 0; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
const idx = (override > 0 ? override - 1 : compId - 1);
|
||||
return ((idx % poolLen) + poolLen) % poolLen;
|
||||
}
|
||||
|
||||
let quizBattleGateFlashAt = 0;
|
||||
function quizBattleGateQuestionSnippetPlay(compId) {
|
||||
if (!(compId > 0) || !quizBattleMcqPool.length) return '';
|
||||
const q = quizBattleMcqPool[(compId - 1) % quizBattleMcqPool.length];
|
||||
const q = quizBattleMcqPool[quizBattleQuestionIndexForCompPlay(compId, quizBattleMcqPool.length)];
|
||||
if (!q || !q.text) return '';
|
||||
const t = String(q.text).trim();
|
||||
return t.length > 42 ? t.slice(0, 42) + '…' : t;
|
||||
@@ -13595,10 +13664,25 @@
|
||||
}
|
||||
|
||||
/** ยืน/เดินได้บนเลนม่วง หรือทุกช่องสไปรต์อยู่ใน spawnArea (ฟ้า) — โซนรอก่อนลงเลน */
|
||||
/** footprint แตะโซนจุดจบ (quizBattleFinishArea) ไหม — ใช้อนุญาตให้ยืน/เด้งไปจุดจบได้โดยไม่ถูกสแน็ปกลับเลน */
|
||||
function quizBattleFootprintTouchesFinishPlay(md, px, py) {
|
||||
const fa = md && md.quizBattleFinishArea;
|
||||
if (!Array.isArray(fa)) return false;
|
||||
const tiles = quizBattleSpriteBoundsTilesPlay(md, px, py);
|
||||
for (const k of tiles) {
|
||||
const p = k.split(',');
|
||||
const tx = +p[0], ty = +p[1];
|
||||
if (fa[ty] && fa[ty][tx] === 1) return true;
|
||||
}
|
||||
const cx = Math.floor(px), cy = Math.floor(py);
|
||||
return !!(fa[cy] && fa[cy][cx] === 1);
|
||||
}
|
||||
|
||||
function quizBattlePositionAllowedPlay(md, px, py) {
|
||||
if (!md || !quizBattlePathModeActive(md)) return true;
|
||||
if (typeof px !== 'number' || typeof py !== 'number' || !Number.isFinite(px) || !Number.isFinite(py)) return false;
|
||||
if (quizBattleFootprintFullyOnPath(md, px, py)) return true;
|
||||
if (quizBattleFootprintTouchesFinishPlay(md, px, py)) return true; /* โซนจุดจบ — ยืนได้ */
|
||||
const sa = md.spawnArea;
|
||||
if (!sa || !Array.isArray(sa)) return false;
|
||||
const tiles = quizBattleSpriteBoundsTilesPlay(md, px, py);
|
||||
@@ -13852,12 +13936,13 @@
|
||||
hideQuizBattleMcqModal();
|
||||
return;
|
||||
}
|
||||
const q = pool[(compId - 1) % pool.length];
|
||||
const qIdx = quizBattleQuestionIndexForCompPlay(compId, pool.length);
|
||||
const q = pool[qIdx];
|
||||
if (!q) return;
|
||||
const ok = Number(q.correctIndex) === Number(idx);
|
||||
/* ตอบครั้งเดียวจบ — ผิดก็ "ผ่าน" และจำว่าผิด (ไม่ให้ตอบซ้ำ) · log ใช้ทำ MY RESULT (O/X) + accuracy */
|
||||
quizBattleAnswerLog.set(compId, {
|
||||
qIndex: (compId - 1) % pool.length,
|
||||
qIndex: qIdx,
|
||||
firstTry: ok,
|
||||
correct: ok,
|
||||
submittedIdx: idx,
|
||||
@@ -14009,6 +14094,7 @@
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (!isQuizBattle() || !me) return;
|
||||
if (quizBattleFinishLock) return; /* ถึงจุดจบแล้ว — อย่าเด้งกลับจุดเดิม */
|
||||
if (typeof canWalkLikeLobby === 'function' && !canWalkLikeLobby(px, py)) return;
|
||||
me.x = px; me.y = py;
|
||||
if (socket && socket.connected) socket.emit('move', { x: me.x, y: me.y, direction: me.direction });
|
||||
@@ -14016,6 +14102,17 @@
|
||||
} catch (e) { /* ignore */ }
|
||||
}, 350);
|
||||
}
|
||||
/* เข้ามาแล้วตอบครบทุกข้ออยู่แล้ว (resume) → ไปจุดจบเลย ไม่ต้องตอบใหม่ */
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (!isQuizBattle()) return;
|
||||
const total = quizBattleTotalCompsPlay();
|
||||
if (total > 0 && quizBattlePassedComps.size >= total) {
|
||||
quizBattleCompletedShown = true;
|
||||
moveToQuizBattleFinishPlay();
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}, 600);
|
||||
}
|
||||
|
||||
/* ===== Quiz Battle: MY RESULT + RANKING popups (Stage 2) ===== */
|
||||
@@ -14203,8 +14300,36 @@
|
||||
}
|
||||
|
||||
/** popup "Completed" (popup-result.png) — สรุป Correct / Time Taken / My Rank เมื่อตอบครบทุกโดม */
|
||||
/** สุ่มช่องในโซนจุดจบ (quizBattleFinishArea) เป็นพิกัด tile — ไม่มี = null (ให้ผู้เล่นกระจายตัว ไม่กองจุดเดียว) */
|
||||
function quizBattleFinishRandomCellPlay() {
|
||||
const md = mapData;
|
||||
const fa = md && md.quizBattleFinishArea;
|
||||
if (!Array.isArray(fa)) return null;
|
||||
const cells = [];
|
||||
for (let y = 0; y < fa.length; y++) {
|
||||
const row = fa[y]; if (!row) continue;
|
||||
for (let x = 0; x < row.length; x++) { if (row[x] === 1) cells.push([x, y]); }
|
||||
}
|
||||
if (!cells.length) return null;
|
||||
const pick = cells[Math.floor(Math.random() * cells.length)];
|
||||
/* สุ่มตำแหน่งย่อยในช่องด้วย เพื่อไม่ให้ซ้อนกันเป๊ะแม้สุ่มได้ช่องเดียวกัน */
|
||||
const jitter = () => 0.25 + Math.random() * 0.5;
|
||||
return { x: pick[0] + jitter(), y: pick[1] + jitter() };
|
||||
}
|
||||
/** ตอบครบทุกข้อ → ย้ายผู้เล่นไปจุดจบที่ตั้งไว้ใน Editor (สุ่มช่อง) */
|
||||
function moveToQuizBattleFinishPlay() {
|
||||
const c = quizBattleFinishRandomCellPlay();
|
||||
if (!c || !me) return;
|
||||
quizBattleFinishLock = true; /* ล็อก: หลังจากนี้ห้าม echo/restore เด้งกลับ */
|
||||
me.x = c.x; me.y = c.y;
|
||||
playPath = [];
|
||||
try { if (socket && socket.connected) socket.emit('move', { x: me.x, y: me.y, direction: me.direction }); } catch (e) { /* ignore */ }
|
||||
try { draw(); } catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function openQuizBattleCompleted() {
|
||||
if (!isQuizBattle()) return;
|
||||
moveToQuizBattleFinishPlay(); /* ย้ายไปจุดจบที่ตั้งไว้ (ถ้ามี) */
|
||||
const pop = document.getElementById('qb-completed-popup');
|
||||
if (!pop) return;
|
||||
const total = quizBattleTotalCompsPlay();
|
||||
@@ -14259,7 +14384,7 @@
|
||||
flashQuizBattleFeedback('ยังไม่มีคำถาม — บันทึกใน Admin → Quiz Battle แล้วรีเฟรช', false);
|
||||
return;
|
||||
}
|
||||
const q = pool[(compNow - 1) % pool.length];
|
||||
const q = pool[quizBattleQuestionIndexForCompPlay(compNow, pool.length)];
|
||||
if (!q) return;
|
||||
quizBattleModalCompId = compNow;
|
||||
showQuizBattleMcqModal(q);
|
||||
@@ -14337,7 +14462,7 @@
|
||||
flashQuizBattleFeedback('ยังไม่มีคำถามในระบบ (Admin → Quiz Battle)', false);
|
||||
return;
|
||||
}
|
||||
const q = pool[(compId - 1) % pool.length];
|
||||
const q = pool[quizBattleQuestionIndexForCompPlay(compId, pool.length)];
|
||||
if (!q) return;
|
||||
quizBattleModalCompId = compId;
|
||||
showQuizBattleMcqModal(q);
|
||||
@@ -14430,6 +14555,7 @@
|
||||
quizBattleModalCompId = null;
|
||||
quizBattleMcqPool = [];
|
||||
quizBattleCompletedShown = false;
|
||||
quizBattleFinishLock = false;
|
||||
hideQuizBattleMcqModal();
|
||||
playLiveQuizScores = {};
|
||||
if (myId != null) playLiveQuizScores[myId] = 0;
|
||||
@@ -17872,6 +17998,7 @@
|
||||
return;
|
||||
}
|
||||
if (mapData && mapData.gameType === 'quiz_battle') {
|
||||
if (quizBattleFinishLock) return; /* ถึงจุดจบแล้ว — เพิกเฉย echo ตำแหน่งของตัวเอง (กันเด้งกลับ) */
|
||||
if (data.x != null) {
|
||||
const x = Number(data.x);
|
||||
if (Number.isFinite(x)) me.x = x;
|
||||
|
||||
@@ -5152,7 +5152,7 @@
|
||||
<script src="/app-base.js?v=2"></script>
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0306"></script>
|
||||
<script src="js/play.js?v=0.00610200030"></script>
|
||||
<script src="js/play.js?v=0.00610200036"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5297,8 +5297,9 @@ const server = http.createServer((req, res) => {
|
||||
if (s.isPrivate || id === POST_CASE_LOBBY_SPACE_ID) continue;
|
||||
const n = s.peers ? s.peers.size : 0;
|
||||
if (n !== 0) continue;
|
||||
const created = s.createdAt;
|
||||
const stale = created == null || now - created > SPACE_EMPTY_TTL_MS;
|
||||
/* นับจาก "ว่างตั้งแต่เมื่อไหร่" (emptySince) ไม่ใช่อายุห้อง — ห้องที่อยู่มานานแล้วเพิ่งว่าง (เช่นกด refresh) จะไม่โดนลบทันที */
|
||||
const since = s.emptySince || s.createdAt;
|
||||
const stale = since == null || now - since > SPACE_EMPTY_TTL_MS;
|
||||
if (stale) spaces.delete(id);
|
||||
}
|
||||
const list = [...spaces]
|
||||
@@ -7682,10 +7683,14 @@ io.on('connection', (socket) => {
|
||||
stopGauntletTicker(space);
|
||||
clearSpaceQuizTimers(space);
|
||||
space.quizSession = null;
|
||||
space.emptySince = Date.now();
|
||||
/* ระหว่างมินิเกมสืบสวน / คดีล็อก — อย่าลบห้องเมื่อเปลี่ยนหน้า room-lobby → play.html (รอ join กลับ) */
|
||||
const keepCaseSpace = !!(space.detectiveMinigameActive
|
||||
|| (space.joinLocked && space.caseParticipantNicknames && space.caseParticipantNicknames.size > 0));
|
||||
if (keepCaseSpace) {
|
||||
/* ห้อง Quiz Battle — เก็บไว้เมื่อว่าง ให้ refresh กลับเข้าห้องเดิมได้ (TTL prune จะเก็บกวาดเมื่อว่างเกิน 45 วิ) */
|
||||
const spaceMd = (space.mapId && maps.get(space.mapId)) || space.mapData;
|
||||
const keepQuizBattleRoom = !!(spaceMd && spaceMd.gameType === 'quiz_battle');
|
||||
if (keepCaseSpace || keepQuizBattleRoom) {
|
||||
space.hostId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user