minigame 2 add more design 2.5

This commit is contained in:
2026-05-06 16:58:31 +00:00
parent f887628322
commit 07d53ebe1c
3 changed files with 163 additions and 111 deletions
File diff suppressed because one or more lines are too long
+152 -109
View File
@@ -34,6 +34,9 @@
const QUIZ_QUESTION_MISSION_MAP_ID = 'mng8a80o';
/** Stack ซ้อนตึก — ฉากภารกิจ (HOWTO → นับถอยหลัง → เล่น → สรุป) รูปใน `/Game/img/TowerBlock` */
const STACK_TOWER_MISSION_MAP_ID = 'mnn93hpi';
/** Stack Tower: ความละเอียดบัฟเฟอร์วาดคงที่ — ย่อ/ขยายด้วย CSS เท่านั้น กัน BG กับตำแหน่งบล็อกเลื่อนตามขนาดหน้าต่าง */
const STACK_TOWER_FIXED_RENDER_W = 1280;
const STACK_TOWER_FIXED_RENDER_H = 720;
/** Mega Virus — balloon_boss ฉากภารกิจ (flow เดียวกับ crown / mno9kb07) รูปใน `/Game/img/MegaVirus` */
const BALLOON_BOSS_MISSION_MAP_ID = 'mnq1eml7';
/** ครบชั้นนี้ขึ้นไป — ยกจุดแกว่ง/เชือกขึ้น (world px) ป้องกันชนกองสูง (เฉพาะ Tower mission) */
@@ -987,6 +990,8 @@
let stackTowerScrollBgIntroImg = null;
let stackTowerScrollBgLoopImg = null;
let stackTowerScrollBgPx = 0;
/** ค่า zDraw ครั้งแรกที่วาด BG — ใช้ scale พื้นหลังให้ขยายตาม zoom เหมือนฉากโลก */
let stackTowerScrollBgZoomRefPlay = 0;
let stackTowerScrollBgSpeedPxPerSec = 40;
let stackTowerScrollBgOff = false;
let stackTowerScrollBgFlowDown = false;
@@ -1010,6 +1015,7 @@
stackTowerScrollBgIntroImg = null;
stackTowerScrollBgLoopImg = null;
stackTowerScrollBgPx = 0;
stackTowerScrollBgZoomRefPlay = 0;
if (!stackTowerScrollBgMapEligible()) return;
const raw = mapData.stackTowerBgScroll && typeof mapData.stackTowerBgScroll === 'object' ? mapData.stackTowerBgScroll : {};
stackTowerScrollBgOff = raw.enabled === false;
@@ -1045,7 +1051,8 @@
if (stackTowerScrollBgFlowDown) {
stackTowerScrollBgPx = 0;
} else {
stackTowerScrollBgPx = Math.max(0, drawHIntro - chR);
/* ขอบล่างของ intro ติดขอบล่างแคนวาสตอนเริ่ม — แม้ intro ต่ำกว่าจอก็ไม่ pin จากด้านบน */
stackTowerScrollBgPx = drawHIntro - chR;
}
}
@@ -1055,6 +1062,11 @@
const loop = stackTowerScrollBgLoopImg;
if (!intro || !intro.complete || !loop || !loop.complete) return;
const zPan = Number(zDrawPan) > 0 ? zDrawPan : zoom;
if (!(stackTowerScrollBgZoomRefPlay > 0) && zPan > 0.01 && Number.isFinite(zPan)) {
stackTowerScrollBgZoomRefPlay = zPan;
}
const zRef = stackTowerScrollBgZoomRefPlay > 0 ? stackTowerScrollBgZoomRefPlay : zPan;
const bgZoomMul = Math.max(0.28, Math.min(4.25, zPan / zRef));
const cwR = Math.max(1, Math.round(cw));
const chR = Math.max(1, Math.round(ch));
const scaleI = cwR / intro.naturalWidth;
@@ -1078,6 +1090,9 @@
ctx.beginPath();
ctx.rect(0, 0, cwR, chR);
ctx.clip();
ctx.translate(cwR * 0.5, chR * 0.5);
ctx.scale(bgZoomMul, bgZoomMul);
ctx.translate(-cwR * 0.5, -chR * 0.5);
if (vp0 < 0) {
let k = 1;
@@ -1123,7 +1138,8 @@
const scaleL = cwR / loop.naturalWidth;
const drawHLoop = Math.max(1, Math.round(loop.naturalHeight * scaleL));
const skyScreenH = Math.min(chR, Math.ceil(-worldMinY * zDraw) + 4);
const scroll = (stackTowerScrollBgPx + getStackTowerBgScrollHeightBoostPx() * zDraw) % drawHLoop;
const rawScroll = stackTowerScrollBgPx + getStackTowerBgScrollHeightBoostPx() * zDraw;
const scroll = ((rawScroll % drawHLoop) + drawHLoop) % drawHLoop;
let y = -scroll;
ctx.save();
ctx.imageSmoothingEnabled = false;
@@ -4151,12 +4167,19 @@
}
/** Last Light (mno9kb07) ใน iframe เอดิเตอร์: ซูมคงที่ — ผู้เล่นปรับเองไม่ได้ */
const PLAY_EMBED_LAST_LIGHT_FIXED_ZOOM_MUL = 1.49;
/** Stack Tower (mnn93hpi) ใน iframe เอดิเตอร์: ซูมคงที่ 1.22 — ล้อ/ปุ่มไม่ปรับได้ */
const PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL = 1.22;
function isLastLightEmbedZoomLockedPlay() {
return !!(previewMode && editorEmbedReturn && mapData && isGauntletCrownHeistMapPlay());
}
function isStackTowerEmbedZoomLockedPlay() {
return !!(previewMode && editorEmbedReturn && mapData && isStackTowerMissionUiMapPlay());
}
function applyPlayEmbedZoomForCurrentMapPlay() {
if (isLastLightEmbedZoomLockedPlay()) {
playEmbedUserZoomMul = PLAY_EMBED_LAST_LIGHT_FIXED_ZOOM_MUL;
} else if (isStackTowerEmbedZoomLockedPlay()) {
playEmbedUserZoomMul = PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL;
}
}
/**
@@ -4180,6 +4203,8 @@
}
if (isLastLightEmbedZoomLockedPlay()) {
playEmbedUserZoomMul = PLAY_EMBED_LAST_LIGHT_FIXED_ZOOM_MUL;
} else if (isStackTowerEmbedZoomLockedPlay()) {
playEmbedUserZoomMul = PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL;
}
if (previewMode && editorEmbedReturn && mapData && !isQuizQuestionMissionHudActivePlay()) {
zDraw *= playEmbedUserZoomMul;
@@ -4664,20 +4689,12 @@
ctx.globalAlpha = 1;
ctx.restore();
} else {
ctx.strokeStyle = 'rgba(90, 88, 86, 0.75)';
ctx.strokeStyle = 'rgba(90, 88, 86, 0.82)';
ctx.lineWidth = Math.max(2.2, zoom * 1.4);
ctx.beginPath();
ctx.moveTo(sx0, sy0);
ctx.lineTo(sx1, sy1);
ctx.stroke();
ctx.strokeStyle = 'rgba(125, 207, 255, 0.35)';
ctx.lineWidth = Math.max(1, zoom * 0.65);
ctx.setLineDash([3, 5]);
ctx.beginPath();
ctx.moveTo(sx0, sy0);
ctx.lineTo(sx1, sy1);
ctx.stroke();
ctx.setLineDash([]);
ctx.lineWidth = 1;
}
}
@@ -8650,6 +8667,7 @@
: autoW;
stackFall = null;
const towerMission = isStackTowerMissionUiMapPlay();
const towerLayerStripH = Math.max(14, tileSize * 0.31);
const missMax = Math.max(1, Math.min(20, Math.floor(Number(playStackTeamMissesMax) || 3)));
stackMini = {
topCenterX: towerCX,
@@ -8661,8 +8679,8 @@
phaseSpeed: playStackSwingHz,
floorWorldY,
swingWorldY,
layerWorldH: Math.max(14, tileSize * 0.3),
blockStripH: Math.max(16, tileSize * 0.32),
layerWorldH: towerMission ? towerLayerStripH : Math.max(14, tileSize * 0.3),
blockStripH: towerMission ? towerLayerStripH : Math.max(16, tileSize * 0.32),
layers: [],
lives: 3,
teamMissesLeft: towerMission ? missMax : null,
@@ -9171,20 +9189,6 @@
const floorY = m.floorWorldY;
const swingLiftDraw = getStackTowerSwingLiftWorldPx(m.layers.length, layerWorldH);
const swingDrawY = m.swingWorldY - swingLiftDraw;
const guideTopY = isStackTowerMissionUiMapPlay()
? (swingDrawY - tileSize * 6.5)
: Math.max(0, swingDrawY - tileSize * 6.5);
const [gtx, gty] = worldToScreen(m.topCenterX * tileSize, guideTopY);
const [gbx, gby] = worldToScreen(m.topCenterX * tileSize, floorY);
ctx.strokeStyle = 'rgba(255,255,255,0.22)';
ctx.lineWidth = Math.max(1, zoom * 0.85);
ctx.setLineDash([5, 7]);
ctx.beginPath();
ctx.moveTo(gtx, gty);
ctx.lineTo(gbx, gby);
ctx.stroke();
ctx.setLineDash([]);
ctx.lineWidth = 1;
const nLay = m.layers.length;
for (let i = 0; i < nLay; i++) {
@@ -14418,6 +14422,15 @@
});
function resizeCanvas() {
if (mapData && isStackTowerMissionUiMapPlay() && canvas) {
canvas.width = Math.max(320, STACK_TOWER_FIXED_RENDER_W);
canvas.height = Math.max(240, STACK_TOWER_FIXED_RENDER_H);
/* รีคำนวน BG scroll + zoom ref หลังขนาดบัฟเฟอร์คงที่ — กันรูปโหลดเร็วตอนแคนวาสยังไม่ล็อกแล้วไม่ซิงก์ใหม่ */
stackTowerScrollBgZoomRefPlay = 0;
stackTowerScrollBgSyncInitialScrollToBottom();
syncQuizCarryEmbedCountdownLayout();
return;
}
const vw = window.innerWidth || document.documentElement.clientWidth || 800;
const vh = window.innerHeight || document.documentElement.clientHeight || 600;
const header = document.querySelector('.game-header');
@@ -14465,6 +14478,7 @@
const wheelZoomHandler = (e) => {
if (!previewMode || !editorEmbedReturn || !mapData) return;
if (isLastLightEmbedZoomLockedPlay()) return;
if (isStackTowerEmbedZoomLockedPlay()) return;
if (isQuizQuestionMissionHudActivePlay()) return;
const t = e.target;
if (t && typeof t.closest === 'function') {
@@ -15260,76 +15274,78 @@
const headH = 38;
const boardH = headH + STACK_PREVIEW_TURN_COUNT * rowH + 10;
ctx.fillStyle = 'rgba(8, 12, 28, 0.9)';
roundRectPath(pad, 8, boardW, boardH, 8);
ctx.fill();
ctx.strokeStyle = 'rgba(0, 230, 255, 0.5)';
ctx.lineWidth = 1.5;
roundRectPath(pad, 8, boardW, boardH, 8);
ctx.stroke();
if (!towerMapHud) {
ctx.fillStyle = 'rgba(8, 12, 28, 0.9)';
roundRectPath(pad, 8, boardW, boardH, 8);
ctx.fill();
ctx.strokeStyle = 'rgba(0, 230, 255, 0.5)';
ctx.lineWidth = 1.5;
roundRectPath(pad, 8, boardW, boardH, 8);
ctx.stroke();
ctx.fillStyle = '#5cefff';
ctx.font = 'bold 12px ui-sans-serif, system-ui, sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('SCORE', pad + 14, 16);
ctx.fillStyle = 'rgba(94, 239, 255, 0.55)';
ctx.font = monoSm;
ctx.fillText('shared stack · P1P6', pad + 62, 18);
ctx.fillStyle = '#5cefff';
ctx.font = 'bold 12px ui-sans-serif, system-ui, sans-serif';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('SCORE', pad + 14, 16);
ctx.fillStyle = 'rgba(94, 239, 255, 0.55)';
ctx.font = monoSm;
ctx.fillText('shared stack · P1P6', pad + 62, 18);
let rowY = 8 + headH;
const drawRows = (entries) => {
entries.forEach((entry) => {
const isCurrent = entry.seat === currentSeat;
const v = resolveEntryVisual(entry);
let actionFlash = false;
if (entry.kind === 'human') {
actionFlash = lastStackPreviewActorId === '__human__' && now < lastStackPreviewActorUntil;
} else if (entry.botId) {
const ob = others.get(entry.botId);
actionFlash = !!(ob && ((now < (ob.stackBotGlowUntil || 0)) || (entry.botId === lastStackPreviewActorId && now < lastStackPreviewActorUntil)));
let rowY = 8 + headH;
const drawRows = (entries) => {
entries.forEach((entry) => {
const isCurrent = entry.seat === currentSeat;
const v = resolveEntryVisual(entry);
let actionFlash = false;
if (entry.kind === 'human') {
actionFlash = lastStackPreviewActorId === '__human__' && now < lastStackPreviewActorUntil;
} else if (entry.botId) {
const ob = others.get(entry.botId);
actionFlash = !!(ob && ((now < (ob.stackBotGlowUntil || 0)) || (entry.botId === lastStackPreviewActorId && now < lastStackPreviewActorUntil)));
}
if (isCurrent) {
ctx.fillStyle = 'rgba(255, 0, 180, 0.1)';
roundRectPath(pad + 6, rowY - 1, boardW - 12, rowH - 2, 4);
ctx.fill();
ctx.strokeStyle = 'rgba(0, 255, 220, 0.9)';
ctx.lineWidth = 2;
roundRectPath(pad + 6, rowY - 1, boardW - 12, rowH - 2, 4);
ctx.stroke();
} else if (actionFlash) {
ctx.strokeStyle = 'rgba(255, 214, 102, 0.65)';
ctx.lineWidth = 1.5;
roundRectPath(pad + 6, rowY - 1, boardW - 12, rowH - 2, 4);
ctx.stroke();
}
const avS = 34;
drawHudAvatar(pad + 10 + avS / 2, rowY + avS - 3, avS, v.characterId, v.playTint);
ctx.fillStyle = isCurrent ? '#e0f7ff' : '#89b4fa';
ctx.font = '600 10px ui-sans-serif, system-ui, sans-serif';
ctx.textBaseline = 'middle';
ctx.fillText('P' + entry.seat, pad + 14 + avS + 6, rowY + 12);
ctx.fillStyle = '#c0caf5';
ctx.font = 'bold 10px ' + mono;
ctx.fillText(v.name.slice(0, 11), pad + 14 + avS + 6, rowY + 28);
ctx.fillStyle = actionFlash ? '#ffe066' : '#7dfcff';
ctx.font = 'bold 13px ' + mono;
ctx.textAlign = 'right';
ctx.fillText(String(v.score), pad + boardW - 12, rowY + rowH / 2);
ctx.textAlign = 'left';
rowY += rowH;
});
};
if (order && order.length === STACK_PREVIEW_TURN_COUNT) {
drawRows(order);
} else {
const bots = [...others.keys()].filter(isPreviewBotId).sort();
const rows = [{ kind: 'human', seat: 1 }];
for (let i = 0; i < STACK_PREVIEW_TURN_COUNT - 1; i++) {
rows.push({ kind: 'bot', seat: i + 2, botId: bots[i] || null });
}
if (isCurrent) {
ctx.fillStyle = 'rgba(255, 0, 180, 0.1)';
roundRectPath(pad + 6, rowY - 1, boardW - 12, rowH - 2, 4);
ctx.fill();
ctx.strokeStyle = 'rgba(0, 255, 220, 0.9)';
ctx.lineWidth = 2;
roundRectPath(pad + 6, rowY - 1, boardW - 12, rowH - 2, 4);
ctx.stroke();
} else if (actionFlash) {
ctx.strokeStyle = 'rgba(255, 214, 102, 0.65)';
ctx.lineWidth = 1.5;
roundRectPath(pad + 6, rowY - 1, boardW - 12, rowH - 2, 4);
ctx.stroke();
}
const avS = 34;
drawHudAvatar(pad + 10 + avS / 2, rowY + avS - 3, avS, v.characterId, v.playTint);
ctx.fillStyle = isCurrent ? '#e0f7ff' : '#89b4fa';
ctx.font = '600 10px ui-sans-serif, system-ui, sans-serif';
ctx.textBaseline = 'middle';
ctx.fillText('P' + entry.seat, pad + 14 + avS + 6, rowY + 12);
ctx.fillStyle = '#c0caf5';
ctx.font = 'bold 10px ' + mono;
ctx.fillText(v.name.slice(0, 11), pad + 14 + avS + 6, rowY + 28);
ctx.fillStyle = actionFlash ? '#ffe066' : '#7dfcff';
ctx.font = 'bold 13px ' + mono;
ctx.textAlign = 'right';
ctx.fillText(String(v.score), pad + boardW - 12, rowY + rowH / 2);
ctx.textAlign = 'left';
rowY += rowH;
});
};
if (order && order.length === STACK_PREVIEW_TURN_COUNT) {
drawRows(order);
} else {
const bots = [...others.keys()].filter(isPreviewBotId).sort();
const rows = [{ kind: 'human', seat: 1 }];
for (let i = 0; i < STACK_PREVIEW_TURN_COUNT - 1; i++) {
rows.push({ kind: 'bot', seat: i + 2, botId: bots[i] || null });
drawRows(rows);
}
drawRows(rows);
}
const framePad = 10;
@@ -15339,7 +15355,8 @@
const rightW = Math.min(300, Math.max(236, Math.round(cw * 0.34)));
let rightX = cw - pad - rightW;
const topY = 8;
if (rightX < pad + boardW + 14) {
const leftReserve = towerMapHud ? (pad + 8) : (pad + boardW + 14);
if (rightX < leftReserve) {
rightX = Math.max(pad, cw - pad - rightW);
}
const integrityW = Math.max(integrityMinW, rightW - framePad * 2 - avBox - 10);
@@ -15348,8 +15365,8 @@
const integrityH = avBox + 2;
const avFrameX = integrityX + integrityW + 10;
const avFrameY = innerTop;
const footerH = 26;
const turnPanelH = framePad + integrityH + 10 + footerH + framePad;
const footerH = towerMapHud ? 0 : 26;
const turnPanelH = framePad + integrityH + (towerMapHud ? framePad : (10 + footerH + framePad));
ctx.fillStyle = 'rgba(8, 12, 28, 0.9)';
roundRectPath(rightX, topY, rightW, turnPanelH, 8);
@@ -15382,16 +15399,18 @@
ctx.stroke();
drawHudAvatar(avCx, avFrameY + avBox - 5, bigAv, curV.characterId, curV.playTint);
const footY = innerTop + integrityH + 12;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = livesRem <= 1 ? '#f7768e' : 'rgba(125, 252, 255, 0.88)';
ctx.font = monoSm;
ctx.fillText(
'P' + currentSeat + ' · ' + curV.name.slice(0, 9) + ' · TEAM ' + teamScore + ' · COMBO x' + combo,
rightX + rightW / 2,
footY,
);
if (!towerMapHud) {
const footY = innerTop + integrityH + 12;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillStyle = livesRem <= 1 ? '#f7768e' : 'rgba(125, 252, 255, 0.88)';
ctx.font = monoSm;
ctx.fillText(
'P' + currentSeat + ' · ' + curV.name.slice(0, 9) + ' · TEAM ' + teamScore + ' · COMBO x' + combo,
rightX + rightW / 2,
footY,
);
}
const termW = Math.min(380, Math.max(220, cw * 0.42));
const logLineH = 12;
@@ -15399,7 +15418,7 @@
const logBlockH = logMaxLines * logLineH;
/** หัวข้อ + บล็อกล็อก + ช่องว่าง + ป้าย TOWER_LINK + แถบ — อย่าให้ทับกัน (เดิม termH=86 แคบเกิน) */
const termH = Math.max(118, 22 + logBlockH + 10 + 14 + 8 + 10);
const termY = Math.max(boardH + 24, ch - termH - 52);
const termY = Math.max((towerMapHud ? 28 : boardH + 24), ch - termH - 52);
ctx.fillStyle = 'rgba(18, 8, 32, 0.88)';
roundRectPath(pad, termY, termW, termH, 6);
ctx.fill();
@@ -16179,7 +16198,13 @@
}
function draw() {
if (!mapData) return;
try {
if (!mapData) {
document.documentElement.classList.remove('play-stack-tower-pixel-canvas');
return;
}
document.documentElement.classList.toggle('play-stack-tower-pixel-canvas', isStackTowerMissionUiMapPlay());
} catch (e) { /* ignore */ }
if (isGauntletCrownHeistMapPlay()) ensureGauntletCrownScorePenaltyImgPlay();
ctx.setTransform(1, 0, 0, 1, 0, 0);
const w = mapData.width, h = mapData.height;
@@ -16208,6 +16233,21 @@
camX = gauntletGroupCam.px;
camY = gauntletGroupCam.py;
}
if (isStackTowerMissionUiMapPlay() && stackMini && canvas && zDraw > 0) {
const nLay0 = stackMini.layers ? stackMini.layers.length : 0;
if (nLay0 === 0) {
const rawFrac = mapData && mapData.stackTowerFloorScreenFrac;
const frac = Number(rawFrac);
/** ค่าน้อย = พื้นขึ้นบนจอ (จุดเริ่มตรงฐานศิลป์) — แมป override ได้ที่ stackTowerFloorScreenFrac */
const screenFrac = Number.isFinite(frac)
? Math.max(0.38, Math.min(0.82, frac))
: 0.56;
const floorY0 = stackMini.floorWorldY;
const curScreen = (floorY0 - camY) * zDraw + canvas.height * 0.5;
const desired = canvas.height * screenFrac;
camY += (curScreen - desired) / zDraw;
}
}
lastPlayZDrawForInput = zDraw;
const halfW = canvas.width / (2 * zDraw);
const halfH = canvas.height / (2 * zDraw);
@@ -17057,7 +17097,7 @@
document.addEventListener('keydown', (e) => {
if (isMovementKey(e.code) && isChatFocused()) return;
if (previewMode && editorEmbedReturn && mapData && !isChatFocused() && !isQuizQuestionMissionHudActivePlay() && !isLastLightEmbedZoomLockedPlay()) {
if (previewMode && editorEmbedReturn && mapData && !isChatFocused() && !isQuizQuestionMissionHudActivePlay() && !isLastLightEmbedZoomLockedPlay() && !isStackTowerEmbedZoomLockedPlay()) {
if (e.code === 'BracketLeft' || e.code === 'Minus' || e.code === 'NumpadSubtract') {
e.preventDefault();
playEmbedUserZoomMul = Math.max(PLAY_EMBED_USER_ZOOM_MIN, playEmbedUserZoomMul / PLAY_EMBED_ZOOM_STEP_KEY);
@@ -17192,6 +17232,9 @@
function tick() {
if (!mapData) { requestAnimationFrame(tick); return; }
if (isStack()) {
if (isStackTowerEmbedZoomLockedPlay()) {
playEmbedUserZoomMul = PLAY_EMBED_STACK_TOWER_FIXED_ZOOM_MUL;
}
const nowStBg = performance.now();
const dtStBg = Math.min(0.06, (nowStBg - lastStackTowerScrollBgTickMs) / 1000);
lastStackTowerScrollBgTickMs = nowStBg;
+10 -1
View File
@@ -127,6 +127,15 @@
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* Stack Tower (mnn93hpi) เท่านั้น — class บน html จาก play.js — แคนวาสคงที่ไม่ย่อเข้า stage (ไม่มีขอบเทารอบพิกเซล) */
html.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
max-width: none;
max-height: none;
}
html.play-preview-editor-embed.play-stack-tower-pixel-canvas #play-canvas-stage #game-canvas {
max-width: none !important;
max-height: none !important;
}
/* หลังกดรับหลักฐานในหน้าสรุป — BG ดำ alpha ทั้งจอ + timeup ติดโซนโต๊ะเหมือน #quiz-map-question-panel */
#quiz-carry-timeup-desk-layer {
position: absolute;
@@ -3167,7 +3176,7 @@
</div>
<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.0342"></script>
<script src="js/play.js?v=0.0355"></script>
<div class="version-tag">v —</div>
</body>
</html>