update all
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"openai_api_key": "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",
|
||||
"model": "gpt-4-turbo"
|
||||
"openai_api_key": "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",
|
||||
"model": "gpt-4-turbo",
|
||||
"intent": "",
|
||||
"rag_enabled": false,
|
||||
"rag_context": ""
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"qbroom1":[{"name":"Nam","score":18,"characterId":"char-1777017632279","ts":1782909257585},{"name":"ผู้เล่น9514","score":17,"characterId":"char-1777017632279","ts":1781850667717},{"name":"MONE","score":10,"characterId":"char-1777017632279","ts":1781850749437},{"name":"tomato","score":9,"characterId":"char-1777017632279","ts":1781850748552},{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781171902962},{"name":"ผู้เล่น1991","score":5,"characterId":"char-1777017632279","ts":1781850950078},{"name":"ผู้เล่น8916","score":4,"characterId":"char-1777017632279","ts":1781074083031},{"name":"ผู้เล่น2821","score":4,"characterId":"char-1777017632279","ts":1781079179108},{"name":"Weenwild","score":4,"characterId":"char-1777017632279","ts":1781849863580},{"name":"ผู้เล่น2359","score":0,"characterId":"char-1777017632279","ts":1783664802662},{"name":"ผู้เล่น1576","score":0,"characterId":"char-1777017632279","ts":1784258335514}],"qbroom2":[{"name":"ผู้เล่น5703","score":14,"characterId":"char-1777017632279","ts":1784258779407},{"name":"ผู้เล่น4472","score":13,"characterId":"char-1777017632279","ts":1781851033914},{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781175468983},{"name":"ผู้เล่น2612","score":1,"characterId":"char-1777017632279","ts":1781079252376},{"name":"ผู้เล่น1948","score":0,"characterId":"char-1777017632279","ts":1783664954451}],"qbroom3":[{"name":"Q","score":9,"characterId":"char-1777017632279","ts":1781175654893},{"name":"Weenwild","score":2,"characterId":"char-1777017632279","ts":1781849898452},{"name":"ผู้เล่น8622","score":0,"characterId":"char-1777017632279","ts":1783842317274}],"qbroom5":[{"name":"Q","score":6,"characterId":"char-1777017632279","ts":1781675299850}],"qbroom6":[{"name":"Q","score":3,"characterId":"char-1777017632279","ts":1781675383175},{"name":"ผู้เล่น1752","score":1,"characterId":"char-1777017632279","ts":1784273155119}],"qbroom9":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781696345322}],"qbroom4":[{"name":"ผู้เล่น9337","score":20,"characterId":"char-1777017632279","ts":1784270128376}],"qbroom7":[{"name":"ผู้เล่น4119","score":3,"characterId":"char-1777017632279","ts":1784270217530}]}
|
||||
{"qbroom1":[{"name":"Nam","score":18,"characterId":"char-1777017632279","ts":1782909257585},{"name":"ผู้เล่น9514","score":17,"characterId":"char-1777017632279","ts":1781850667717},{"name":"MONE","score":10,"characterId":"char-1777017632279","ts":1781850749437},{"name":"tomato","score":9,"characterId":"char-1777017632279","ts":1781850748552},{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781171902962},{"name":"ผู้เล่น1991","score":5,"characterId":"char-1777017632279","ts":1781850950078},{"name":"ผู้เล่น8916","score":4,"characterId":"char-1777017632279","ts":1781074083031},{"name":"ผู้เล่น2821","score":4,"characterId":"char-1777017632279","ts":1781079179108},{"name":"Weenwild","score":4,"characterId":"char-1777017632279","ts":1781849863580},{"name":"play1","score":1,"characterId":"char-1777017632279","ts":1785441076910},{"name":"ผู้เล่น2359","score":0,"characterId":"char-1777017632279","ts":1783664802662},{"name":"ผู้เล่น1576","score":0,"characterId":"char-1777017632279","ts":1784258335514}],"qbroom2":[{"name":"ผู้เล่น5703","score":14,"characterId":"char-1777017632279","ts":1784258779407},{"name":"ผู้เล่น4472","score":13,"characterId":"char-1777017632279","ts":1781851033914},{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781175468983},{"name":"ผู้เล่น2612","score":1,"characterId":"char-1777017632279","ts":1781079252376},{"name":"play2","score":1,"characterId":"char-1777017632279","ts":1785440817269},{"name":"ผู้เล่น1948","score":0,"characterId":"char-1777017632279","ts":1783664954451}],"qbroom3":[{"name":"Q","score":9,"characterId":"char-1777017632279","ts":1781175654893},{"name":"Weenwild","score":2,"characterId":"char-1777017632279","ts":1781849898452},{"name":"play3","score":1,"characterId":"char-1777017632279","ts":1785441122522},{"name":"ผู้เล่น8622","score":0,"characterId":"char-1777017632279","ts":1783842317274}],"qbroom5":[{"name":"Q","score":6,"characterId":"char-1777017632279","ts":1781675299850},{"name":"play5","score":1,"characterId":"char-1777017632279","ts":1785440912224}],"qbroom6":[{"name":"Q","score":3,"characterId":"char-1777017632279","ts":1781675383175},{"name":"ผู้เล่น1752","score":1,"characterId":"char-1777017632279","ts":1784273155119},{"name":"play6","score":1,"characterId":"char-1777017632279","ts":1785441196130}],"qbroom9":[{"name":"Q","score":7,"characterId":"char-1777017632279","ts":1781696345322},{"name":"play9","score":1,"characterId":"char-1777017632279","ts":1785441009369}],"qbroom4":[{"name":"ผู้เล่น9337","score":20,"characterId":"char-1777017632279","ts":1784270128376},{"name":"play4","score":1,"characterId":"char-1777017632279","ts":1785440881506}],"qbroom7":[{"name":"Q","score":4,"characterId":"char-1777017632279","ts":1784785097780},{"name":"ผู้เล่น4119","score":3,"characterId":"char-1777017632279","ts":1784270217530},{"name":"play7","score":1,"characterId":"char-1777017632279","ts":1785440972161}],"qbroom8":[{"name":"play8","score":1,"characterId":"char-1777017632279","ts":1785440990472}],"qbroom10":[{"name":"play10","score":1,"characterId":"char-1777017632279","ts":1785441027840}]}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"version": 1,
|
||||
"updatedAt": "2026-07-27 00:17",
|
||||
"updatedBy": "admin",
|
||||
"tz": "Asia/Bangkok",
|
||||
"modes": {
|
||||
"1": {
|
||||
"title": "กฎหมายใกล้ตัวสิ",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"2": {
|
||||
"title": "กฎหมายสิทธิพื้นฐาน",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"3": {
|
||||
"title": "กฎหมายจราจร",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"4": {
|
||||
"title": "คดีเกี่ยวกับทรัพย์",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"5": {
|
||||
"title": "คดีหมิ่นประมาท",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"6": {
|
||||
"title": "คดีทางเพศ",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"7": {
|
||||
"title": "คดีอาชญากรรม",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"8": {
|
||||
"title": "อาชญากรรมออนไลน์",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"9": {
|
||||
"title": "กระบวนการยุติธรรม",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
},
|
||||
"10": {
|
||||
"title": "งานบริการกระทรวงยุติธรรม",
|
||||
"enabled": true,
|
||||
"openUntil": "",
|
||||
"maxPlayers": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,9 +56,10 @@
|
||||
<div id="settings-tab" class="tab-content active">
|
||||
<h2 style="font-size:1.1rem;margin-bottom:1rem;color:#c0caf5;">การตั้งค่าระบบ</h2>
|
||||
<div class="form-group">
|
||||
<label for="openai-key">OpenAI API Key</label>
|
||||
<input type="password" id="openai-key" name="openai_api_key" placeholder="sk-..." autocomplete="off">
|
||||
<small>เว้นว่างถ้าไม่ต้องการเปลี่ยน</small>
|
||||
<label for="openai-key">API Key (OpenAI หรือ OpenRouter)</label>
|
||||
<input type="password" id="openai-key" name="openai_api_key" placeholder="sk-... หรือ sk-or-v1-..." autocomplete="off">
|
||||
<small id="key-status" style="color:#a9b1d6;">กำลังตรวจสอบ…</small>
|
||||
<small>เว้นว่างถ้าไม่ต้องการเปลี่ยน · คีย์ที่ขึ้นต้น <code>sk-or-</code> ระบบจะส่งไป OpenRouter ให้อัตโนมัติ</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="openai-model">Model</label>
|
||||
@@ -156,9 +157,24 @@
|
||||
document.getElementById('rag-enabled').checked = !!data.rag_enabled;
|
||||
document.getElementById('rag-context').value = data.rag_context != null ? data.rag_context : '';
|
||||
document.getElementById('rag-context-wrap').style.opacity = data.rag_enabled ? '1' : '0.7';
|
||||
showKeyStatus(data);
|
||||
});
|
||||
}
|
||||
|
||||
/* [2026-07-23] เดิมช่องคีย์ถูกล้างทุกครั้งหลังเซฟ และ hasKey ที่ API ส่งมาไม่เคยถูกแสดง
|
||||
→ ผู้ใช้ไม่มีทางรู้ว่าคีย์ถูกเก็บไว้จริงไหม = "กดเซฟแล้วมันไม่เซฟ" */
|
||||
function showKeyStatus(data) {
|
||||
var el = document.getElementById('key-status');
|
||||
if (!el || !data) return;
|
||||
if (data.hasKey) {
|
||||
el.style.color = '#9ece6a';
|
||||
el.textContent = '✓ มีคีย์บันทึกอยู่แล้ว: ' + (data.keyMasked || '') + ' → ใช้ ' + (data.provider || '?');
|
||||
} else {
|
||||
el.style.color = '#f7768e';
|
||||
el.textContent = '✗ ยังไม่มีคีย์ในระบบ';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('rag-enabled').addEventListener('change', function () {
|
||||
document.getElementById('rag-context-wrap').style.opacity = this.checked ? '1' : '0.7';
|
||||
});
|
||||
@@ -204,6 +220,7 @@
|
||||
msgEl.textContent = 'บันทึกแล้ว';
|
||||
msgEl.style.display = 'block';
|
||||
document.getElementById('openai-key').value = '';
|
||||
showKeyStatus(data); /* ยืนยันทันทีว่าคีย์ที่เก็บอยู่คือใบไหน/เจ้าไหน */
|
||||
} else {
|
||||
errEl.textContent = (data && data.error) || 'บันทึกไม่สำเร็จ';
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 416 KiB |
|
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 291 KiB After Width: | Height: | Size: 359 KiB |
|
Before Width: | Height: | Size: 322 KiB After Width: | Height: | Size: 430 KiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 533 KiB |
|
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 29 KiB |
@@ -4965,39 +4965,39 @@
|
||||
<div id="gauntlet-name-overlay" aria-hidden="true"></div>
|
||||
</div>
|
||||
<button type="button" id="quiz-carry-grab-btn" class="is-hidden" aria-label="หยิบหรือส่งคำตอบ (Grab)" title="หยิบ / ส่งคำตอบ — เหมือนกด F">
|
||||
<img src="/Game/img/quiz-carry/btn-grab.png" alt="" width="256" height="256" decoding="async" />
|
||||
<img src="/Game/img/quiz-carry/btn-grab.png" alt="" width="256" height="256" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<button type="button" id="gauntlet-crown-jump-btn" class="is-hidden" aria-label="กระโดด (Jump)" title="กระโดด — เหมือน Space / W / ↑" aria-hidden="true">
|
||||
<img src="/Game/img/gauntlet-assets/btn-jump.png" alt="" width="256" height="256" decoding="async" />
|
||||
<img src="/Game/img/gauntlet-assets/btn-jump.png" alt="" width="256" height="256" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<button type="button" id="stack-tower-drop-btn" class="is-hidden" aria-label="ปล่อยบล็อก (Drop)" title="ปล่อยบล็อก — เหมือน Space / Enter" aria-hidden="true">
|
||||
<img src="/Game/img/TowerBlock/btn-drop.png" alt="" width="256" height="256" decoding="async" />
|
||||
<img src="/Game/img/TowerBlock/btn-drop.png" alt="" width="256" height="256" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<div id="quiz-map-question-panel" class="is-hidden" aria-hidden="true">
|
||||
<img id="quiz-map-q-feedback-icon" class="quiz-map-q-feedback-icon is-hidden" alt="" width="64" height="64" decoding="async" />
|
||||
<img id="quiz-map-q-feedback-icon" class="quiz-map-q-feedback-icon is-hidden" alt="" width="64" height="64" decoding="async" / loading="lazy">
|
||||
<div id="quiz-map-question-kicker" class="quiz-map-question-kicker is-hidden" aria-hidden="true"></div>
|
||||
<p id="quiz-map-question-text"></p>
|
||||
<div id="quiz-map-question-subphase" class="quiz-map-question-subphase is-hidden" aria-hidden="true"></div>
|
||||
<div id="quiz-map-q-feedback-score-wrap" class="is-hidden" aria-hidden="true">
|
||||
<img id="quiz-map-q-feedback-score" alt="" width="120" height="48" decoding="async" />
|
||||
<img id="quiz-map-q-feedback-score" alt="" width="120" height="48" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div id="quiz-carry-timeup-desk-layer" class="is-hidden" aria-hidden="true">
|
||||
<div class="qc-timeup-dim" aria-hidden="true"></div>
|
||||
<div class="qc-timeup-desk-anchor">
|
||||
<img id="quiz-carry-timeup-txt-img" class="qc-timeup-txt-img" src="/Game/img/quiz-carry/timeup-txt.png" alt="" width="480" height="120" decoding="async" />
|
||||
<img id="quiz-carry-timeup-txt-img" class="qc-timeup-txt-img" src="/Game/img/quiz-carry/timeup-txt.png" alt="" width="480" height="120" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div id="quiz-carry-result-end-layer" class="is-hidden" aria-hidden="true" role="img" aria-label="ผลจบภารกิจ">
|
||||
<div class="qc-result-end-dim" aria-hidden="true"></div>
|
||||
<img id="quiz-carry-result-end-img" class="qc-result-end-img" src="" alt="" width="900" height="520" decoding="async" />
|
||||
<img id="quiz-carry-result-end-img" class="qc-result-end-img" src="" alt="" width="900" height="520" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div id="play-cyber-hud" class="play-cyber-hud is-hidden" aria-hidden="true">
|
||||
<div class="play-cyber-hud-mock-stage" aria-hidden="true">
|
||||
<aside class="play-cyber-scoreboard" aria-label="SCORE">
|
||||
<div id="play-cyber-crown-score-head" class="play-cyber-crown-score-head is-hidden" aria-hidden="true">
|
||||
<img id="play-cyber-crown-score-img" class="mg2-score-label" src="/Game/img/gauntlet-assets/score.png" alt="SCORE :" width="144" height="71" decoding="async" />
|
||||
<img id="play-cyber-crown-score-img" class="mg2-score-label" src="/Game/img/gauntlet-assets/score.png" alt="SCORE :" width="144" height="71" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
<div class="play-cyber-panel-title">SCORE</div>
|
||||
<ul id="play-cyber-score-list" class="play-cyber-score-list"></ul>
|
||||
@@ -5005,7 +5005,7 @@
|
||||
<div class="play-cyber-center-stack">
|
||||
<div class="play-cyber-time-block">
|
||||
<div class="play-cyber-time-head">
|
||||
<img id="play-cyber-time-plaque-img" class="play-cyber-time-plaque-img is-hidden" alt="TIME" decoding="async" aria-hidden="true" />
|
||||
<img id="play-cyber-time-plaque-img" class="play-cyber-time-plaque-img is-hidden" alt="TIME" decoding="async" aria-hidden="true" / loading="lazy">
|
||||
<div class="play-cyber-time-label">TIME</div>
|
||||
<span class="play-cyber-time-colon" aria-hidden="true">:</span>
|
||||
<div id="play-cyber-time-val" class="play-cyber-time-val">0</div>
|
||||
@@ -5014,7 +5014,7 @@
|
||||
<div id="play-cyber-embed-zoom-hint" class="play-cyber-embed-zoom-hint is-hidden" aria-hidden="true"></div>
|
||||
</div>
|
||||
<div id="play-cyber-quiz-mission-q-band" class="play-cyber-quiz-mission-q-band is-hidden" aria-hidden="true">
|
||||
<img id="play-cyber-quiz-mission-q-plaque" class="play-cyber-quiz-mission-q-plaque is-hidden" alt="" decoding="async" />
|
||||
<img id="play-cyber-quiz-mission-q-plaque" class="play-cyber-quiz-mission-q-plaque is-hidden" alt="" decoding="async" / loading="lazy">
|
||||
<p id="play-cyber-quiz-mission-q-text" class="play-cyber-quiz-mission-q-text"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5022,15 +5022,15 @@
|
||||
<div class="qb-room-profile-wrap play-cyber-profile-wrap" aria-hidden="true">
|
||||
<div class="qb-room-profile-frame play-cyber-profile-frame">
|
||||
<div class="qb-room-profile-avatar-viewport play-cyber-portrait-clip">
|
||||
<img id="play-cyber-portrait-img" class="qb-room-profile-avatar play-cyber-portrait-img" alt="" decoding="async" />
|
||||
<img id="play-cyber-portrait-img" class="qb-room-profile-avatar play-cyber-portrait-img" alt="" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
<img id="play-cyber-profile-frame-overlay" class="play-cyber-profile-frame-overlay" alt="" decoding="async" aria-hidden="true" />
|
||||
<img id="play-cyber-profile-frame-overlay" class="play-cyber-profile-frame-overlay" alt="" decoding="async" aria-hidden="true" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<img id="play-cyber-mic-img" class="play-cyber-mic-img is-hidden" alt="" decoding="async" aria-hidden="true" />
|
||||
<img id="play-cyber-mic-img" class="play-cyber-mic-img is-hidden" alt="" decoding="async" aria-hidden="true" / loading="lazy">
|
||||
<!-- ไมค์ในเกม (MG1-7) — WebRTC mesh เหมือนหน้า lobby; วางใต้ avatar ตามดีไซน์ (mg-mic). play.js เปิดแสดง (ซ่อน preview/Quiz Battle) -->
|
||||
<button type="button" id="btn-voice" class="play-voice-btn" title="เปิดไมค์" aria-label="เปิด/ปิดไมค์" style="display:none;">
|
||||
<img src="/Game/img/btn-mic-mute.png" alt="" id="btn-voice-icon-img" width="128" height="128" decoding="async" />
|
||||
<img src="/Game/img/btn-mic-mute.png" alt="" id="btn-voice-icon-img" width="128" height="128" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<div id="play-cyber-self-status" class="play-cyber-self-status"></div>
|
||||
<div id="play-cyber-op-widgets" class="play-cyber-op-widgets" aria-hidden="true">
|
||||
@@ -5049,9 +5049,9 @@
|
||||
<!-- ภารกิจคำถาม mng8a80o ช่วง live: virtual joystick (QUESTION/btn-joystick-*.png) -->
|
||||
<div id="quiz-question-mission-joystick" class="quiz-q-mission-joystick is-hidden" aria-hidden="true">
|
||||
<div class="quiz-q-mission-joystick-base" id="quiz-q-mission-joystick-base">
|
||||
<img id="quiz-q-mission-joystick-bg" class="quiz-q-mission-joystick-bg" src="" alt="" decoding="async" />
|
||||
<img id="quiz-q-mission-joystick-bg" class="quiz-q-mission-joystick-bg" src="" alt="" decoding="async" / loading="lazy">
|
||||
<div class="quiz-q-mission-joystick-knob" id="quiz-q-mission-joystick-knob">
|
||||
<img id="quiz-q-mission-joystick-knob-img" class="quiz-q-mission-joystick-knob-img" src="" alt="" decoding="async" />
|
||||
<img id="quiz-q-mission-joystick-knob-img" class="quiz-q-mission-joystick-knob-img" src="" alt="" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5059,54 +5059,54 @@
|
||||
<!-- MG3 Stack Tower (mnn93hpi) live HUD — DOM overlay (canvas object-fit:cover ครอป HUD ขอบจอ จึงย้ายมา DOM สเกลตาม stage) -->
|
||||
<div id="mg3-tower-hud" class="mg3-tower-hud is-hidden" aria-hidden="true">
|
||||
<div class="mg3-life">
|
||||
<img class="mg3-life-bar" src="" alt="" decoding="async" />
|
||||
<img class="mg3-life-bar" src="" alt="" decoding="async" / loading="lazy">
|
||||
<div class="mg3-life-hearts" id="mg3-life-hearts"></div>
|
||||
</div>
|
||||
<div class="mg3-host">
|
||||
<img class="mg3-host-frame" src="" alt="" decoding="async" />
|
||||
<img class="mg3-host-face" id="mg3-host-av" src="" alt="" decoding="async" />
|
||||
<img class="mg3-host-frame" src="" alt="" decoding="async" / loading="lazy">
|
||||
<img class="mg3-host-face" id="mg3-host-av" src="" alt="" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
<div class="mg3-terminal">
|
||||
<img class="mg3-terminal-bg" src="" alt="" decoding="async" />
|
||||
<img class="mg3-terminal-bg" src="" alt="" decoding="async" / loading="lazy">
|
||||
<div class="mg3-terminal-log" id="mg3-terminal-log"></div>
|
||||
</div>
|
||||
<div class="mg3-progress">
|
||||
<div class="mg3-progress-heading">
|
||||
<img class="mg3-progress-label" src="" alt="" decoding="async" />
|
||||
<img class="mg3-progress-label" src="" alt="" decoding="async" / loading="lazy">
|
||||
<span class="mg3-progress-pct" id="mg3-progress-pct">[ 0% ]</span>
|
||||
</div>
|
||||
<div class="mg3-progress-bar">
|
||||
<img class="mg3-progress-track" src="" alt="" decoding="async" />
|
||||
<img class="mg3-progress-fill" id="mg3-progress-fill" src="" alt="" decoding="async" />
|
||||
<img class="mg3-progress-track" src="" alt="" decoding="async" / loading="lazy">
|
||||
<img class="mg3-progress-fill" id="mg3-progress-fill" src="" alt="" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MG6 Space Shooter (mnpz6rkp) live life — DOM overlay สเกล design 1920×1080 เหมือน MG3 (ไม่พึ่ง cyber HUD flex) -->
|
||||
<div id="mg6-life-hud" class="mg6-life-hud is-hidden" aria-hidden="true">
|
||||
<div class="mg6-life">
|
||||
<img class="mg6-life-bar" src="" alt="" decoding="async" />
|
||||
<img class="mg6-life-bar" src="" alt="" decoding="async" / loading="lazy">
|
||||
<div class="mg6-life-hearts" id="mg6-life-hearts"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Quiz Battle (qbroom*) in-game HUD — DOM overlay สเกล design 1920×1080 (header หมวด, PLAYERS, SCORE, host, EXIT, MY RESULT, RANKING) -->
|
||||
<div id="qb-game-hud" class="qb-game-hud is-hidden" aria-hidden="true">
|
||||
<img class="qb-hud-tag" id="qb-hud-tag" src="" alt="" decoding="async" />
|
||||
<img class="qb-hud-tag" id="qb-hud-tag" src="" alt="" decoding="async" / loading="lazy">
|
||||
<div class="qb-hud-players">PLAYERS : <span id="qb-hud-players-val">1/50</span></div>
|
||||
<div class="qb-hud-score">SCORE : <span id="qb-hud-score-val">0</span></div>
|
||||
<div class="qb-hud-host">
|
||||
<div class="qb-hud-host-clip"><img class="qb-hud-host-av" id="qb-hud-host-av" src="" alt="" decoding="async" /></div>
|
||||
<div class="qb-hud-host-clip"><img class="qb-hud-host-av" id="qb-hud-host-av" src="" alt="" decoding="async" / loading="lazy"></div>
|
||||
</div>
|
||||
<button type="button" class="qb-hud-btn qb-hud-exit" id="qb-hud-exit" aria-label="ออกจากห้อง">
|
||||
<img src="/Game/img/qb-ui/btn-exit-room.png" alt="EXIT" decoding="async" />
|
||||
<img src="/Game/img/qb-ui/btn-exit-room.png" alt="EXIT" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<button type="button" class="qb-hud-btn qb-hud-myresult" id="qb-hud-myresult" aria-label="ผลของฉัน">
|
||||
<img src="/Game/img/qb-ui/btn-my-result.png" alt="MY RESULT" decoding="async" />
|
||||
<img src="/Game/img/qb-ui/btn-my-result.png" alt="MY RESULT" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<button type="button" class="qb-hud-btn qb-hud-ranking" id="qb-hud-ranking" aria-label="อันดับ">
|
||||
<img src="/Game/img/qb-ui/btn-ranking.png" alt="RANKING" decoding="async" />
|
||||
<img src="/Game/img/qb-ui/btn-ranking.png" alt="RANKING" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<button type="button" class="qb-hud-btn qb-hud-mic" id="qb-hud-mic" aria-label="ไมโครโฟน">
|
||||
<img id="qb-hud-mic-img" src="/Game/img/btn-mic-on.png" alt="mic" decoding="async" />
|
||||
<img id="qb-hud-mic-img" src="/Game/img/btn-mic-on.png" alt="mic" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<div class="qb-hud-zoom" id="qb-hud-zoom">
|
||||
<button type="button" class="qb-hud-zoom-btn" id="qb-hud-zoom-in" aria-label="ซูมเข้า">+</button>
|
||||
@@ -5180,7 +5180,7 @@
|
||||
<p id="quiz-carry-pregame-status" class="quiz-carry-pregame-status" aria-live="polite"></p>
|
||||
<div class="quiz-carry-pregame-actions">
|
||||
<button type="button" id="quiz-carry-pregame-primary" class="quiz-carry-pregame-primary-btn" aria-pressed="false" title="READY">
|
||||
<img id="quiz-carry-pregame-primary-img" src="/Game/img/quiz-carry/btn-ready.png" width="220" height="56" alt="READY" decoding="async" />
|
||||
<img id="quiz-carry-pregame-primary-img" src="/Game/img/quiz-carry/btn-ready.png" width="220" height="56" alt="READY" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5190,7 +5190,7 @@
|
||||
<div id="quiz-carry-embed-countdown-inner">
|
||||
<p id="quiz-carry-embed-countdown-kicker" class="quiz-carry-embed-countdown-kicker">คำถาม · Question</p>
|
||||
<p id="quiz-carry-embed-countdown-q" class="quiz-carry-embed-countdown-q"></p>
|
||||
<img id="quiz-carry-embed-countdown-num" src="/Game/img/QUESTION/3.png" width="200" height="200" alt="3" decoding="async" />
|
||||
<img id="quiz-carry-embed-countdown-num" src="/Game/img/QUESTION/3.png" width="200" height="200" alt="3" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div id="quiz-carry-embed-q-strip" class="is-hidden" role="status" aria-live="polite" aria-atomic="true" aria-hidden="true">
|
||||
@@ -5212,9 +5212,9 @@
|
||||
<button type="button" id="quiz-battle-mcq-close" aria-label="ปิด"></button>
|
||||
<p id="quiz-battle-mcq-text" class="quiz-battle-mcq-text"></p>
|
||||
<div class="quiz-battle-mcq-actions">
|
||||
<button type="button" class="quiz-battle-choice" data-idx="0"><span class="qbc-letter">A</span><span class="qbc-text"></span><img class="qbc-mark" alt="" decoding="async" /></button>
|
||||
<button type="button" class="quiz-battle-choice" data-idx="1"><span class="qbc-letter">B</span><span class="qbc-text"></span><img class="qbc-mark" alt="" decoding="async" /></button>
|
||||
<button type="button" class="quiz-battle-choice" data-idx="2"><span class="qbc-letter">C</span><span class="qbc-text"></span><img class="qbc-mark" alt="" decoding="async" /></button>
|
||||
<button type="button" class="quiz-battle-choice" data-idx="0"><span class="qbc-letter">A</span><span class="qbc-text"></span><img class="qbc-mark" alt="" decoding="async" / loading="lazy"></button>
|
||||
<button type="button" class="quiz-battle-choice" data-idx="1"><span class="qbc-letter">B</span><span class="qbc-text"></span><img class="qbc-mark" alt="" decoding="async" / loading="lazy"></button>
|
||||
<button type="button" class="quiz-battle-choice" data-idx="2"><span class="qbc-letter">C</span><span class="qbc-text"></span><img class="qbc-mark" alt="" decoding="async" / loading="lazy"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5222,7 +5222,7 @@
|
||||
<div class="gch-backdrop gch-legacy-backdrop" aria-hidden="true"></div>
|
||||
<div class="gch-shell">
|
||||
<!-- legacy (Mega Virus / แมปอื่น) -->
|
||||
<img class="gch-bg gch-legacy-only" src="/Game/img/gauntlet-assets/popup-Howto.png" alt="" width="920" height="520" decoding="async" />
|
||||
<img class="gch-bg gch-legacy-only" src="/Game/img/gauntlet-assets/popup-Howto.png" alt="" width="920" height="520" decoding="async" / loading="lazy">
|
||||
<div class="gch-inner gch-inner--art gch-legacy-only">
|
||||
<div class="gch-art-footer">
|
||||
<p id="gauntlet-crown-howto-status" class="gch-status is-hidden" aria-live="polite"></p>
|
||||
@@ -5234,13 +5234,13 @@
|
||||
<div class="gch-mg2-stage gch-mg2-only" aria-hidden="true">
|
||||
<div class="layer mg-overlay" aria-hidden="true"></div>
|
||||
<div class="layer howto-popup">
|
||||
<img class="gch-bg" src="/Game/img/gauntlet-assets/popup-Howto.png" alt="HOW TO PLAY" width="1500" height="880" decoding="async" />
|
||||
<img class="gch-bg" src="/Game/img/gauntlet-assets/popup-Howto.png" alt="HOW TO PLAY" width="1500" height="880" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
<div class="layer howto-status">
|
||||
<p id="gauntlet-crown-howto-status-mg2" class="gch-status is-hidden" aria-live="polite"></p>
|
||||
</div>
|
||||
<button type="button" class="layer howto-btn btn btn-gch-ready" id="btn-gch-ready-mg2" title="READY">
|
||||
<img id="btn-gch-ready-mg2-img" src="/Game/img/gauntlet-assets/btn-ready.png" alt="READY" width="299" height="120" decoding="async" />
|
||||
<img id="btn-gch-ready-mg2-img" src="/Game/img/gauntlet-assets/btn-ready.png" alt="READY" width="299" height="120" decoding="async" / loading="lazy">
|
||||
</button>
|
||||
<div id="gch-ready-avatars-mg2" class="gch-ready-avatars layer" aria-hidden="true"></div>
|
||||
</div>
|
||||
@@ -5248,18 +5248,18 @@
|
||||
</div>
|
||||
<div id="gauntlet-crown-countdown" class="is-hidden" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="gcc-panel">
|
||||
<img id="gauntlet-crown-countdown-num" class="gcc-num gcc-num--img" src="/Game/img/gauntlet-assets/3.png" width="240" height="240" alt="" decoding="async" />
|
||||
<img id="gauntlet-crown-countdown-num" class="gcc-num gcc-num--img" src="/Game/img/gauntlet-assets/3.png" width="240" height="240" alt="" decoding="async" / loading="lazy">
|
||||
<span id="gauntlet-crown-countdown-text" class="gcc-num gcc-num--text is-hidden" aria-hidden="true">3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="stack-tower-result-flash" class="is-hidden" role="dialog" aria-modal="true" aria-hidden="true" aria-label="ผลภารกิจ Tower">
|
||||
<div class="stack-tower-result-flash-backdrop" aria-hidden="true"></div>
|
||||
<img id="stack-tower-result-flash-img" src="" alt="" width="920" height="520" decoding="async" />
|
||||
<img id="stack-tower-result-flash-img" src="" alt="" width="920" height="520" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
<div id="gauntlet-crown-mission-overlay" class="is-hidden" role="dialog" aria-modal="true" aria-labelledby="gcm-heading">
|
||||
<div class="gcm-backdrop" aria-hidden="true"></div>
|
||||
<div class="gcm-shell">
|
||||
<img class="gcm-bg" src="/Game/img/gauntlet-assets/popup-result.png" alt="" width="920" height="560" decoding="async" />
|
||||
<img class="gcm-bg" src="/Game/img/gauntlet-assets/popup-result.png" alt="" width="920" height="560" decoding="async" / loading="lazy">
|
||||
<div class="gcm-inner">
|
||||
<div id="gcm-heading" class="gcm-header-tab">สรุปผลภารกิจ · Mission summary</div>
|
||||
<div id="gcm-rank-row" class="gcm-rank-row"></div>
|
||||
@@ -5270,7 +5270,7 @@
|
||||
<div id="gcm-grade" class="gcm-grade sm-grade" aria-live="polite">A</div>
|
||||
</div>
|
||||
<div id="gcm-vrule" class="gcm-vrule sm-vrule is-hidden" aria-hidden="true">
|
||||
<img class="gcm-line-2 sm-line-2" src="/Game/img/gauntlet-assets/popup-result-Line2.png" alt="" decoding="async" />
|
||||
<img class="gcm-line-2 sm-line-2" src="/Game/img/gauntlet-assets/popup-result-Line2.png" alt="" decoding="async" / loading="lazy">
|
||||
</div>
|
||||
<div class="gcm-col-bonus">
|
||||
<div class="gcm-h head">โบนัสพิเศษ</div>
|
||||
@@ -5428,7 +5428,7 @@
|
||||
<div class="sq-ov-panel">
|
||||
<div id="sq-ov-quiz-body">
|
||||
<div class="sq-ov-head">
|
||||
<img id="sq-ov-icon" class="sq-ov-icon" src="/Game/img/special-quiz/icon-lawyer.png" alt="" />
|
||||
<img id="sq-ov-icon" class="sq-ov-icon" src="/Game/img/special-quiz/icon-lawyer.png" alt="" / loading="lazy">
|
||||
<div class="sq-ov-head-text">
|
||||
<div id="sq-overlay-title" class="sq-ov-kicker">คำถามพิเศษ · Special Quiz</div>
|
||||
<div id="sq-ov-progress" class="sq-ov-progress">ข้อ 1 / 1</div>
|
||||
@@ -5445,10 +5445,10 @@
|
||||
</div>
|
||||
<div id="sq-ov-award" class="sq-ov-award is-hidden">
|
||||
<div class="sq-ov-award-stage">
|
||||
<img id="sq-ov-award-effect-img" class="sq-ov-award-effect" src="/Game/img/special-quiz/reward/card-effect.png" alt="" />
|
||||
<img id="sq-ov-award-img" class="sq-ov-award-img" alt="" />
|
||||
<img id="sq-ov-award-effect-img" class="sq-ov-award-effect" src="/Game/img/special-quiz/reward/card-effect.png" alt="" / loading="lazy">
|
||||
<img id="sq-ov-award-img" class="sq-ov-award-img" alt="" / loading="lazy">
|
||||
</div>
|
||||
<img id="sq-ov-award-txt" class="sq-ov-award-txt" alt="" />
|
||||
<img id="sq-ov-award-txt" class="sq-ov-award-txt" alt="" / loading="lazy">
|
||||
<div id="sq-ov-award-msg" class="sq-ov-award-msg"></div>
|
||||
<button type="button" id="sq-ov-award-continue" class="sq-ov-award-btn" aria-label="เล่นเกมต่อ">เล่นเกมต่อ</button>
|
||||
</div>
|
||||
@@ -5458,7 +5458,7 @@
|
||||
<script src="/Game/socket.io/socket.io.js"></script>
|
||||
<script src="js/version.js?v=0.0306"></script>
|
||||
<script src="js/sound.js?v=2"></script>
|
||||
<script src="js/play.js?v=0.006301183"></script>
|
||||
<script src="js/play.js?v=0.006301196"></script>
|
||||
<div class="version-tag">v —</div>
|
||||
<style id="qc-howto-mg2-fix">
|
||||
/* HOW TO PLAY ของ quiz_carry (Minigame-4) -> ให้เหมือน Minigame-2 (gch-mg2-mock)
|
||||
@@ -5665,7 +5665,23 @@
|
||||
if (!isMg) return; /* เฉพาะตอนเข้ามินิเกม (detective) เท่านั้น — ฉาก detective ปกติไม่เล่น */
|
||||
var ov = document.getElementById('jd-mg-transition'), vid = document.getElementById('jd-mg-vid');
|
||||
if (!ov || !vid) return;
|
||||
function done() { try { vid.pause(); } catch (e) {} ov.style.display = 'none'; } /* จบวิดีโอ → เอา overlay ออก (เกม/loading ที่โหลดข้างล่างโผล่มาเอง) */
|
||||
/* [2026-07-23] จบวิดีโอ → เอา overlay ออก **และปล่อยวิดีโอทิ้งด้วย**
|
||||
เดิมแค่ pause+ซ่อน → บัฟเฟอร์ ~3.1 MB ค้างทุกครั้งที่เข้ามินิเกม (เข้าหลายรอบ = สะสมไปเรื่อยๆ)
|
||||
บนมือถือแรมไม่พอแล้วโดนเบราว์เซอร์ฆ่าแท็บ */
|
||||
var released = false;
|
||||
function done() {
|
||||
if (released) return;
|
||||
released = true;
|
||||
try { vid.pause(); } catch (e) { /* ignore */ }
|
||||
try {
|
||||
vid.onended = null; vid.onerror = null;
|
||||
vid.removeAttribute('src');
|
||||
while (vid.firstChild) vid.removeChild(vid.firstChild);
|
||||
vid.load();
|
||||
} catch (e) { /* ignore */ }
|
||||
ov.style.display = 'none';
|
||||
try { if (ov.parentNode) ov.parentNode.removeChild(ov); } catch (e) { /* ignore */ }
|
||||
}
|
||||
vid.src = '/Game/video/minigame-transition.mp4';
|
||||
vid.onended = done; vid.onerror = done; /* วิดีโอหาย/พัง → ไม่บล็อกเกม */
|
||||
ov.style.display = 'flex'; /* ปิดจอระหว่างโหลด — ข้ามไม่ได้ (ไม่มีปุ่ม skip) */
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
<button class="tool active" data-tool="path"><span class="ic">🛣️</span>เส้นทาง</button>
|
||||
<button class="tool" data-tool="dome"><span class="ic">❓</span>จุดคำถาม</button>
|
||||
<button class="tool" data-tool="spawn"><span class="ic">🚩</span>จุดเกิด</button>
|
||||
<button class="tool" data-tool="fence"><span class="ic">🚧</span>รั้วกั้น</button>
|
||||
<button class="tool" data-tool="erase"><span class="ic">🧽</span>ยางลบ</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,12 +106,22 @@
|
||||
<select id="brush"><option value="1">1×1</option><option value="2" selected>2×2</option><option value="3">3×3</option></select>
|
||||
</div>
|
||||
|
||||
<div class="field" id="fence-field" style="display:none"><span>แบบรั้ว — คลิกวาง · ลากย้าย · คลิกขวาลบ</span>
|
||||
<select id="fence-kind">
|
||||
<option value="h1" selected>▬ แนวนอน สั้น (1 ช่อง)</option>
|
||||
<option value="h2">▬▬ แนวนอน ยาว (2 ช่อง)</option>
|
||||
<option value="v1">▮ แนวตั้ง สั้น (1 ช่อง)</option>
|
||||
<option value="v2">▮▮ แนวตั้ง ยาว (2 ช่อง)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="stat grow" id="stat">—</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn sec" id="btn-clear-path">ล้างเส้นทาง</button>
|
||||
<button class="btn sec" id="btn-clear-dome">ล้างจุดคำถาม</button>
|
||||
</div>
|
||||
<button class="btn sec grow" id="btn-clear-fence">ล้างรั้วกั้น</button>
|
||||
<button class="btn grow" id="btn-save">💾 บันทึก</button>
|
||||
<button class="btn sec grow" id="btn-test">▶ ทดสอบเล่น</button>
|
||||
<div class="toast" id="toast"></div>
|
||||
@@ -128,6 +139,14 @@
|
||||
var curId = qs.get('id') || 'qbroom1';
|
||||
|
||||
var map=null, W=24, H=49, path=[], dome=[], spawn={x:11,y:2};
|
||||
/* รั้วกั้น — [{x,y,w,h}] หน่วยเป็น "ช่อง" ทศนิยมได้ วางอิสระไม่ยึดกริด
|
||||
กริด 20x40 หยาบเกินจะกั้นตรงรั้วที่อาร์ตวาดกลางแถบได้ (รั้วอยู่กลางช่อง ไม่ใช่ขอบช่อง)
|
||||
เกมเช็คด้วยกล่องชนของสไปรต์ซ้อนกล่องรั้ว (server.js quizBattleFenceBlocksServer + play.js ตัวเดียวกัน)
|
||||
ในเกมมองไม่เห็น — อาร์ตวาดรั้วไว้ให้แล้ว ตรงนี้เป็นแค่ตัวกั้นการเดิน */
|
||||
var fences=[], dragFence=null;
|
||||
/* คลังคำถามของ Quiz Battle มี 20 ข้อต่อหัวข้อ (1 หัวข้อ = 1 ฉาก) → ฉากหนึ่งควรมีจุดคำถาม 20 ข้อพอดี
|
||||
ถ้าวางเกิน เลขข้อจะวนซ้ำ (quizBattleQuestionIndexForCompPlay ใช้ (idx % poolLen)) */
|
||||
var QUESTIONS_PER_MAP = 20;
|
||||
var bgImg=new Image(), tool='path', brush=2, painting=false, paintVal=1;
|
||||
|
||||
var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
|
||||
@@ -171,6 +190,9 @@
|
||||
document.querySelectorAll('.tool').forEach(function(x){x.classList.remove('active');});
|
||||
b.classList.add('active'); tool=b.getAttribute('data-tool');
|
||||
brushLabel.textContent = b.querySelector('.ic').textContent+' '+b.textContent.trim();
|
||||
/* ตัวเลือกแบบรั้วโผล่เฉพาะตอนใช้เครื่องมือรั้ว · ขนาดพู่กันไม่เกี่ยวกับรั้ว (รั้วไม่ยึดกริด) */
|
||||
var ff=document.getElementById('fence-field');
|
||||
if(ff) ff.style.display=(tool==='fence')?'':'none';
|
||||
});
|
||||
});
|
||||
document.getElementById('brush').addEventListener('change',function(e){ brush=parseInt(e.target.value,10)||1; });
|
||||
@@ -182,6 +204,9 @@
|
||||
.then(function(m){
|
||||
map=m; W=m.width||24; H=m.height||49;
|
||||
path=norm(m.quizBattlePathArea,W,H); dome=norm(m.quizBattleDomeArea,W,H);
|
||||
fences=(Array.isArray(m.quizBattleFences)?m.quizBattleFences:[])
|
||||
.map(function(f){ return f&&isFinite(f.x)&&isFinite(f.y)&&f.w>0&&f.h>0
|
||||
? { x:+f.x, y:+f.y, w:+f.w, h:+f.h } : null; }).filter(Boolean);
|
||||
spawn=(m.spawn&&typeof m.spawn.x==='number')?{x:m.spawn.x,y:m.spawn.y}:{x:11,y:2};
|
||||
if(m.backgroundImage){ bgImg=new Image(); bgImg.onload=draw; bgImg.onerror=draw; bgImg.src=m.backgroundImage+(m.backgroundImage.indexOf('?')<0?('?_='+Date.now()):''); }
|
||||
toast('โหลด "'+(m.name||curId)+'" แล้ว',true); layout();
|
||||
@@ -205,29 +230,98 @@
|
||||
for(var x=0;x<=W;x++){ ctx.beginPath(); ctx.moveTo(x*cell+.5,0); ctx.lineTo(x*cell+.5,h); ctx.stroke(); }
|
||||
for(var y=0;y<=H;y++){ ctx.beginPath(); ctx.moveTo(0,y*cell+.5); ctx.lineTo(w,y*cell+.5); ctx.stroke(); }
|
||||
ctx.fillStyle='rgba(122,162,247,.42)';
|
||||
for(var py=0;py<H;py++) for(var px=0;px<W;px++) if(path[py][px]) ctx.fillRect(px*cell,py*cell,cell,cell);
|
||||
var num=0;
|
||||
for(var dy2=0;dy2<H;dy2++) for(var dx2=0;dx2<W;dx2++) if(dome[dy2][dx2]){
|
||||
num++; var cx=dx2*cell+cell/2, cy=dy2*cell+cell/2, r=Math.max(8,cell*0.62);
|
||||
ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.fillStyle='rgba(255,209,102,.92)'; ctx.fill();
|
||||
ctx.lineWidth=2; ctx.strokeStyle='#7a5a10'; ctx.stroke();
|
||||
/* [2026-07-22] กัน TypeError ตอนเปิดหน้าครั้งแรก — layout() เรียก draw() ตั้งแต่ยังไม่ได้โหลดแผนที่
|
||||
ตอนนั้น path/dome ยังเป็น [] แต่ H=49 → path[py] undefined แล้ว throw ทั้ง draw() (กริด/ฉากไม่ขึ้นเลย) */
|
||||
for(var py=0;py<H;py++) for(var px=0;px<W;px++) if(path[py]&&path[py][px]) ctx.fillRect(px*cell,py*cell,cell,cell);
|
||||
/* [2026-07-30] "1 คำถาม" = "โดม 1 กลุ่มที่ติดกัน" ไม่ใช่ "1 ช่อง"
|
||||
server รวมช่องที่ติดกันเป็นกลุ่มเดียว (normalizeQuizBattleDomeCompOnMap) แล้วให้เลขข้อ 1 เลขต่อกลุ่ม
|
||||
ตั้งแต่ 07-30 โดมถูกวางเป็น "ทั้งคอลัมน์ของเลน" (2-3 ช่อง) เพราะเลนหนา 3 แถวแต่กล่องชนสูง 1.35 ช่อง
|
||||
ถ้าโดมเป็นช่องเดียวผู้เล่นจะมุดใต้ได้แล้วไปตันที่ประตูถัดไปแบบแก้ไม่ได้
|
||||
ของเดิมนับ/ใส่เลขทีละช่อง → แมป 20 ข้อโชว์เป็น 52-56 (user: "จริง ๆ ควรมีแค่ 20 คำถามต่อ map")
|
||||
ตรงนี้จึงจัดกลุ่มแบบเดียวกับ server แล้ววาดเลขครั้งเดียวต่อกลุ่ม (ที่ช่องบน-ซ้ายสุด เหมือน play.js) */
|
||||
var comp=[], nGroup=0;
|
||||
for(var gy=0;gy<H;gy++){ comp[gy]=[]; for(var gx=0;gx<W;gx++) comp[gy][gx]=0; }
|
||||
for(var qy=0;qy<H;qy++) for(var qx=0;qx<W;qx++){
|
||||
if(!(dome[qy]&&dome[qy][qx])||comp[qy][qx]) continue;
|
||||
nGroup++; var st=[[qx,qy]]; comp[qy][qx]=nGroup;
|
||||
while(st.length){
|
||||
var c=st.pop();
|
||||
var nb=[[c[0]+1,c[1]],[c[0]-1,c[1]],[c[0],c[1]+1],[c[0],c[1]-1]];
|
||||
for(var k=0;k<4;k++){
|
||||
var nx=nb[k][0], ny=nb[k][1];
|
||||
if(nx<0||ny<0||nx>=W||ny>=H) continue;
|
||||
if(!(dome[ny]&&dome[ny][nx])||comp[ny][nx]) continue;
|
||||
comp[ny][nx]=nGroup; st.push([nx,ny]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var dy2=0;dy2<H;dy2++) for(var dx2=0;dx2<W;dx2++) if(dome[dy2]&&dome[dy2][dx2]){
|
||||
var id=comp[dy2][dx2];
|
||||
var cx=dx2*cell+cell/2, cy=dy2*cell+cell/2, r=Math.max(8,cell*0.62);
|
||||
/* ช่องอื่นในกลุ่มเดียวกันวาดจาง ๆ ไม่ใส่เลข — ให้เห็นว่า "ก้อนนี้คือข้อเดียวกัน" */
|
||||
var head=!((comp[dy2-1]&&comp[dy2-1][dx2]===id)||(comp[dy2][dx2-1]===id));
|
||||
ctx.beginPath(); ctx.arc(cx,cy,head?r:r*0.5,0,Math.PI*2);
|
||||
ctx.fillStyle=head?'rgba(255,209,102,.92)':'rgba(255,209,102,.34)'; ctx.fill();
|
||||
ctx.lineWidth=head?2:1; ctx.strokeStyle=head?'#7a5a10':'rgba(122,90,16,.5)'; ctx.stroke();
|
||||
if(!head) continue;
|
||||
ctx.fillStyle='#3a2c00'; ctx.font='bold '+Math.max(10,cell*0.7)+'px Kanit,sans-serif';
|
||||
ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(String(num),cx,cy+1);
|
||||
ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(String(id),cx,cy+1);
|
||||
}
|
||||
/* รั้วกั้น — วาดตามพิกัดทศนิยมจริง (ไม่ snap เข้าช่อง) จะได้เห็นว่ามันอยู่ตรงไหนของภาพจริง ๆ */
|
||||
for(var fi=0;fi<fences.length;fi++){
|
||||
var fr=fences[fi];
|
||||
var rx=fr.x*cell, ry=fr.y*cell, rw=Math.max(2,fr.w*cell), rh=Math.max(2,fr.h*cell);
|
||||
ctx.fillStyle='rgba(255,99,132,.55)'; ctx.fillRect(rx,ry,rw,rh);
|
||||
ctx.lineWidth=2; ctx.strokeStyle='#ff4d6d'; ctx.strokeRect(rx+.5,ry+.5,rw-1,rh-1);
|
||||
/* ขีดทแยงให้ดูเป็น "รั้ว" ไม่ใช่แค่แถบสี */
|
||||
ctx.save(); ctx.beginPath(); ctx.rect(rx,ry,rw,rh); ctx.clip();
|
||||
ctx.strokeStyle='rgba(255,255,255,.5)'; ctx.lineWidth=1;
|
||||
for(var hx=rx-rh;hx<rx+rw;hx+=6){ ctx.beginPath(); ctx.moveTo(hx,ry+rh); ctx.lineTo(hx+rh,ry); ctx.stroke(); }
|
||||
ctx.restore();
|
||||
}
|
||||
var sx=spawn.x*cell, sy=spawn.y*cell;
|
||||
ctx.fillStyle='rgba(52,211,153,.9)'; ctx.fillRect(sx,sy,cell,cell);
|
||||
ctx.fillStyle='#04241a'; ctx.font='bold '+Math.max(10,cell*0.6)+'px Kanit,sans-serif';
|
||||
ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText('🚩',sx+cell/2,sy+cell/2);
|
||||
var np=0,nd=0; for(var a=0;a<H;a++) for(var b=0;b<W;b++){ if(path[a][b])np++; if(dome[a][b])nd++; }
|
||||
statEl.innerHTML='ฉาก: <b>'+(map&&map.name||curId)+'</b><br>เส้นทาง <b>'+np+'</b> ช่อง · จุดคำถาม <b>'+nd+'</b> · เกิด ('+spawn.x+','+spawn.y+')';
|
||||
/* กันเคสเดียวกับด้านบน — นับก่อนโหลดแผนที่ path[a] ยัง undefined */
|
||||
var np=0,nd=0; for(var a=0;a<H;a++) for(var b=0;b<W;b++){ if(path[a]&&path[a][b])np++; if(dome[a]&&dome[a][b])nd++; }
|
||||
/* จำนวนคำถามจริง = จำนวน "กลุ่ม" (ตรงกับที่เกมถาม) · จำนวนช่องบอกไว้ในวงเล็บเผื่อเช็คว่าโดมกั้นเลนครบไหม */
|
||||
var qWarn = (nGroup===QUESTIONS_PER_MAP) ? '' :
|
||||
' <span style="color:#ff9f6e">— ควรมี '+QUESTIONS_PER_MAP+' ข้อต่อฉาก</span>';
|
||||
statEl.innerHTML='ฉาก: <b>'+(map&&map.name||curId)+'</b><br>เส้นทาง <b>'+np+'</b> ช่อง · จุดคำถาม <b>'+nGroup+'</b> ข้อ <span style="opacity:.6">('+nd+' ช่อง)</span>'+qWarn
|
||||
+' · รั้ว <b>'+fences.length+'</b> · เกิด ('+spawn.x+','+spawn.y+')';
|
||||
}
|
||||
|
||||
function tileAt(ev){
|
||||
var p=pointAt(ev); return { x:Math.floor(p.x), y:Math.floor(p.y) };
|
||||
}
|
||||
/** พิกัดแบบทศนิยม (หน่วยช่อง) — รั้วใช้ตัวนี้ เพราะวางอิสระไม่ยึดกริด */
|
||||
function pointAt(ev){
|
||||
var rect=cv.getBoundingClientRect();
|
||||
var px=(ev.clientX!=null?ev.clientX:(ev.touches&&ev.touches[0].clientX))-rect.left;
|
||||
var py=(ev.clientY!=null?ev.clientY:(ev.touches&&ev.touches[0].clientY))-rect.top;
|
||||
var cell=cv._cell; return { x:Math.floor(px/cell), y:Math.floor(py/cell) };
|
||||
var cell=cv._cell; return { x:px/cell, y:py/cell };
|
||||
}
|
||||
function fenceSize(){
|
||||
var k=(document.getElementById('fence-kind')||{}).value||'h1';
|
||||
if(k==='h2') return { w:2.0, h:0.22 };
|
||||
if(k==='v1') return { w:0.22, h:1.0 };
|
||||
if(k==='v2') return { w:0.22, h:2.0 };
|
||||
return { w:1.0, h:0.22 };
|
||||
}
|
||||
function fenceAtPoint(p){
|
||||
for(var i=fences.length-1;i>=0;i--){
|
||||
var f=fences[i];
|
||||
if(p.x>=f.x&&p.x<=f.x+f.w&&p.y>=f.y&&p.y<=f.y+f.h) return i;
|
||||
}
|
||||
/* เผื่อรั้วบางมาก (0.22 ช่อง) จิ้มโดนยาก — ขยายพื้นที่จับอีก 0.18 ช่องรอบตัว */
|
||||
for(var j=fences.length-1;j>=0;j--){
|
||||
var g=fences[j], m=0.18;
|
||||
if(p.x>=g.x-m&&p.x<=g.x+g.w+m&&p.y>=g.y-m&&p.y<=g.y+g.h+m) return j;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
function round2(v){ return Math.round(v*100)/100; }
|
||||
function applyAt(tx,ty){
|
||||
if(tx<0||ty<0||tx>=W||ty>=H) return;
|
||||
if(tool==='spawn'){ spawn={x:tx,y:ty}; return; }
|
||||
@@ -240,14 +334,42 @@
|
||||
}
|
||||
}
|
||||
function onDown(ev){
|
||||
ev.preventDefault(); var t=tileAt(ev);
|
||||
ev.preventDefault();
|
||||
/* รั้ว: ทำงานด้วยพิกัดทศนิยมล้วน ไม่แตะกริดเลย — คลิกที่ว่าง = วางใหม่ (จุดที่คลิกคือ "กลาง" รั้ว)
|
||||
คลิกบนรั้วเดิม = เริ่มลากย้าย · ยางลบคลิกโดนรั้วก็ลบรั้วนั้น */
|
||||
var p=pointAt(ev);
|
||||
if(tool==='fence'||tool==='erase'){
|
||||
var hit=fenceAtPoint(p);
|
||||
if(tool==='erase'&&hit>=0){ fences.splice(hit,1); draw(); return; }
|
||||
if(tool==='fence'){
|
||||
if(hit>=0){ dragFence={ i:hit, dx:p.x-fences[hit].x, dy:p.y-fences[hit].y }; return; }
|
||||
var s=fenceSize();
|
||||
fences.push({ x:round2(p.x-s.w/2), y:round2(p.y-s.h/2), w:s.w, h:s.h });
|
||||
dragFence={ i:fences.length-1, dx:s.w/2, dy:s.h/2 };
|
||||
draw(); return;
|
||||
}
|
||||
}
|
||||
var t=tileAt(ev);
|
||||
if(tool==='path') paintVal=(path[t.y]&&path[t.y][t.x])?0:1;
|
||||
else if(tool==='dome') paintVal=(dome[t.y]&&dome[t.y][t.x])?0:1;
|
||||
else paintVal=1;
|
||||
painting=true; applyAt(t.x,t.y); draw();
|
||||
}
|
||||
function onMove(ev){ if(!painting) return; ev.preventDefault(); var t=tileAt(ev); applyAt(t.x,t.y); draw(); }
|
||||
function onUp(){ painting=false; }
|
||||
function onMove(ev){
|
||||
if(dragFence){
|
||||
ev.preventDefault(); var p=pointAt(ev), f=fences[dragFence.i];
|
||||
if(f){ f.x=round2(p.x-dragFence.dx); f.y=round2(p.y-dragFence.dy); draw(); }
|
||||
return;
|
||||
}
|
||||
if(!painting) return; ev.preventDefault(); var t=tileAt(ev); applyAt(t.x,t.y); draw();
|
||||
}
|
||||
function onUp(){ painting=false; dragFence=null; }
|
||||
/* คลิกขวาบนรั้ว = ลบ (ไม่ต้องสลับไปยางลบ) */
|
||||
cv.addEventListener('contextmenu',function(ev){
|
||||
var hit=fenceAtPoint(pointAt(ev));
|
||||
if(hit<0) return;
|
||||
ev.preventDefault(); fences.splice(hit,1); draw();
|
||||
});
|
||||
cv.addEventListener('mousedown',onDown);
|
||||
window.addEventListener('mousemove',onMove);
|
||||
window.addEventListener('mouseup',onUp);
|
||||
@@ -258,11 +380,12 @@
|
||||
|
||||
document.getElementById('btn-clear-path').addEventListener('click',function(){ path=emptyGrid(W,H); draw(); toast('ล้างเส้นทางแล้ว',true); });
|
||||
document.getElementById('btn-clear-dome').addEventListener('click',function(){ dome=emptyGrid(W,H); draw(); toast('ล้างจุดคำถามแล้ว',true); });
|
||||
document.getElementById('btn-clear-fence').addEventListener('click',function(){ fences=[]; draw(); toast('ล้างรั้วกั้นแล้ว',true); });
|
||||
|
||||
document.getElementById('btn-save').addEventListener('click',function(){
|
||||
var btn=this; btn.disabled=true; toast('กำลังบันทึก…');
|
||||
fetch(BASE+'/api/maps/'+encodeURIComponent(curId),{method:'PUT',headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({ quizBattlePathArea:path, quizBattleDomeArea:dome, spawn:spawn, width:W, height:H })})
|
||||
body:JSON.stringify({ quizBattlePathArea:path, quizBattleDomeArea:dome, quizBattleFences:fences, spawn:spawn, width:W, height:H })})
|
||||
.then(function(r){return r.json();}).then(function(res){ btn.disabled=false;
|
||||
if(res&&res.ok) toast('บันทึก "'+curId+'" สำเร็จ ✓',true); else toast((res&&res.error)||'บันทึกไม่สำเร็จ',false);
|
||||
}).catch(function(e){ btn.disabled=false; toast('บันทึกไม่สำเร็จ: '+e.message,false); });
|
||||
|
||||