291 lines
8.9 KiB
JavaScript
291 lines
8.9 KiB
JavaScript
/* global Phaser, NetworkManager */
|
|
|
|
class MainMenuScene extends Phaser.Scene {
|
|
constructor() {
|
|
super({ key: 'MainMenuScene' });
|
|
}
|
|
|
|
create() {
|
|
const { width, height } = this.sys.game.config;
|
|
|
|
// Connect to server on scene create
|
|
NetworkManager.connect();
|
|
|
|
// Background
|
|
const bg = this.add.graphics();
|
|
bg.fillStyle(0x1a1a2e, 1);
|
|
bg.fillRect(0, 0, width, height);
|
|
bg.fillStyle(0x16213e, 1);
|
|
bg.fillRect(0, height * 0.5, width, height * 0.5);
|
|
|
|
// Title
|
|
this.add.text(width / 2, height * 0.22, 'iPuzzle', {
|
|
fontFamily: 'Georgia, serif',
|
|
fontSize: Math.round(height * 0.083) + 'px',
|
|
color: '#e0e0ff',
|
|
stroke: '#4444aa',
|
|
strokeThickness: 6
|
|
}).setOrigin(0.5);
|
|
|
|
this.add.text(width / 2, height * 0.36, 'Put the pieces together', {
|
|
fontFamily: 'Georgia, serif',
|
|
fontSize: Math.round(height * 0.026) + 'px',
|
|
color: '#8888bb'
|
|
}).setOrigin(0.5);
|
|
|
|
this._buildDomUI();
|
|
}
|
|
|
|
// ─── DOM UI ──────────────────────────────────────────────────────────
|
|
|
|
_buildDomUI() {
|
|
this._uiLayer = document.createElement('div');
|
|
Object.assign(this._uiLayer.style, {
|
|
position: 'fixed',
|
|
top: '50%',
|
|
left: '50%',
|
|
transform: 'translate(-50%, -50%)',
|
|
width: '100vw',
|
|
height: '56.25vw',
|
|
maxHeight: '100vh',
|
|
maxWidth: '177.78vh',
|
|
pointerEvents: 'none',
|
|
zIndex: '10',
|
|
});
|
|
document.body.appendChild(this._uiLayer);
|
|
|
|
const newBtn = this._makeDomBtn('New Puzzle', '#1a3366', '#2255aa', '#66aaff', () => {
|
|
this.scene.start('NewPuzzleScene');
|
|
});
|
|
Object.assign(newBtn.style, {
|
|
top: '55%',
|
|
left: '50%',
|
|
transform: 'translateX(-50%)',
|
|
width: '18%',
|
|
});
|
|
this._uiLayer.appendChild(newBtn);
|
|
|
|
const joinBtn = this._makeDomBtn('Join Puzzle', '#1a3322', '#225533', '#44aa66', () => {
|
|
this._showJoinDialog();
|
|
});
|
|
Object.assign(joinBtn.style, {
|
|
top: '70%',
|
|
left: '50%',
|
|
transform: 'translateX(-50%)',
|
|
width: '18%',
|
|
});
|
|
this._uiLayer.appendChild(joinBtn);
|
|
|
|
// Version credit — bottom right
|
|
const credit = document.createElement('div');
|
|
Object.assign(credit.style, {
|
|
position: 'absolute',
|
|
bottom: '1%',
|
|
right: '0.6%',
|
|
color: '#333355',
|
|
fontSize: '1.2vmin',
|
|
fontFamily: 'Arial, sans-serif',
|
|
});
|
|
credit.textContent = 'iPuzzle';
|
|
this._uiLayer.appendChild(credit);
|
|
|
|
this.events.once('shutdown', () => this._destroyDomUI());
|
|
}
|
|
|
|
_showJoinDialog() {
|
|
if (this._joinDialogEl) return;
|
|
|
|
const overlay = document.createElement('div');
|
|
Object.assign(overlay.style, {
|
|
position: 'absolute',
|
|
inset: '0',
|
|
background: 'rgba(0,0,0,0.6)',
|
|
pointerEvents: 'auto',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
});
|
|
|
|
const panel = document.createElement('div');
|
|
Object.assign(panel.style, {
|
|
background: '#1a1a3e',
|
|
border: '2px solid #4444aa',
|
|
borderRadius: '6px',
|
|
padding: '3vmin 4vmin',
|
|
textAlign: 'center',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '1.5vmin',
|
|
});
|
|
|
|
const title = document.createElement('div');
|
|
Object.assign(title.style, { color: '#aaccff', fontSize: '2.4vmin', fontFamily: 'Arial, sans-serif' });
|
|
title.textContent = 'Enter Room Code';
|
|
|
|
const input = document.createElement('input');
|
|
Object.assign(input.style, {
|
|
background: '#111133',
|
|
color: '#eeeeff',
|
|
border: '2px solid #4466aa',
|
|
borderRadius: '4px',
|
|
padding: '1vmin 1.5vmin',
|
|
fontSize: '2.8vmin',
|
|
fontFamily: 'monospace',
|
|
textAlign: 'center',
|
|
letterSpacing:'0.3em',
|
|
outline: 'none',
|
|
width: '14vmin',
|
|
});
|
|
input.maxLength = 4;
|
|
input.placeholder = 'ABCD';
|
|
input.autocomplete = 'off';
|
|
input.addEventListener('input', () => {
|
|
input.value = input.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
|
joinBtn.disabled = input.value.length !== 4;
|
|
joinBtn.style.opacity = input.value.length === 4 ? '1' : '0.4';
|
|
});
|
|
|
|
const errorMsg = document.createElement('div');
|
|
Object.assign(errorMsg.style, {
|
|
color: '#ff6666',
|
|
fontSize: '1.5vmin',
|
|
fontFamily: 'Arial, sans-serif',
|
|
display: 'none',
|
|
});
|
|
|
|
const btnRow = document.createElement('div');
|
|
Object.assign(btnRow.style, { display: 'flex', gap: '1.5vmin', justifyContent: 'center' });
|
|
|
|
const joinBtn = document.createElement('button');
|
|
joinBtn.textContent = 'Join';
|
|
Object.assign(joinBtn.style, {
|
|
padding: '0.8vmin 3vmin',
|
|
background: '#1a3322',
|
|
color: '#ddeeff',
|
|
border: '2px solid #44aa66',
|
|
borderRadius: '4px',
|
|
fontSize: '1.8vmin',
|
|
fontFamily: 'Arial, sans-serif',
|
|
cursor: 'pointer',
|
|
opacity: '0.4',
|
|
});
|
|
joinBtn.disabled = true;
|
|
|
|
const cancelBtn = document.createElement('button');
|
|
cancelBtn.textContent = 'Cancel';
|
|
Object.assign(cancelBtn.style, {
|
|
padding: '0.8vmin 3vmin',
|
|
background: '#332222',
|
|
color: '#ddeeff',
|
|
border: '2px solid #aa4444',
|
|
borderRadius: '4px',
|
|
fontSize: '1.8vmin',
|
|
fontFamily: 'Arial, sans-serif',
|
|
cursor: 'pointer',
|
|
});
|
|
|
|
btnRow.appendChild(joinBtn);
|
|
btnRow.appendChild(cancelBtn);
|
|
panel.appendChild(title);
|
|
panel.appendChild(input);
|
|
panel.appendChild(errorMsg);
|
|
panel.appendChild(btnRow);
|
|
overlay.appendChild(panel);
|
|
this._uiLayer.appendChild(overlay);
|
|
this._joinDialogEl = overlay;
|
|
|
|
// Focus the input
|
|
setTimeout(() => input.focus(), 50);
|
|
|
|
// Enter key submits
|
|
input.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' && input.value.length === 4) {
|
|
joinBtn.click();
|
|
}
|
|
if (e.key === 'Escape') {
|
|
cancelBtn.click();
|
|
}
|
|
});
|
|
|
|
cancelBtn.addEventListener('click', () => {
|
|
overlay.remove();
|
|
this._joinDialogEl = null;
|
|
});
|
|
|
|
// Clicking overlay background dismisses
|
|
overlay.addEventListener('click', (e) => {
|
|
if (e.target === overlay) {
|
|
overlay.remove();
|
|
this._joinDialogEl = null;
|
|
}
|
|
});
|
|
|
|
// Join logic
|
|
const onJoinedOk = (msg) => {
|
|
NetworkManager.off('room_joined', onJoinedOk);
|
|
NetworkManager.off('error', onJoinError);
|
|
// Start PuzzleScene with the state received from server
|
|
this.scene.start('PuzzleScene', {
|
|
imageKey: msg.state.imageKey,
|
|
imagePath: msg.state.imagePath,
|
|
pieceCount: msg.state.pieceCount,
|
|
roomCode: msg.state.roomCode,
|
|
_networkJoin: true,
|
|
_networkState: msg.state,
|
|
});
|
|
};
|
|
const onJoinError = (msg) => {
|
|
NetworkManager.off('room_joined', onJoinedOk);
|
|
NetworkManager.off('error', onJoinError);
|
|
errorMsg.textContent = msg.message || 'Room not found';
|
|
errorMsg.style.display = 'block';
|
|
joinBtn.disabled = false;
|
|
joinBtn.style.opacity = '1';
|
|
};
|
|
|
|
joinBtn.addEventListener('click', () => {
|
|
const code = input.value.trim().toUpperCase();
|
|
if (code.length !== 4) return;
|
|
errorMsg.style.display = 'none';
|
|
joinBtn.disabled = true;
|
|
joinBtn.style.opacity = '0.4';
|
|
|
|
NetworkManager.on('room_joined', onJoinedOk);
|
|
NetworkManager.on('error', onJoinError);
|
|
NetworkManager.joinRoom(code);
|
|
});
|
|
}
|
|
|
|
_makeDomBtn(label, bgNormal, bgHover, borderColor, onClick) {
|
|
const btn = document.createElement('button');
|
|
btn.textContent = label;
|
|
Object.assign(btn.style, {
|
|
position: 'absolute',
|
|
padding: '1.2vmin 2vmin',
|
|
background: bgNormal,
|
|
color: '#ddeeff',
|
|
border: `2px solid ${borderColor}`,
|
|
borderRadius: '4px',
|
|
fontSize: '2vmin',
|
|
fontFamily: 'Arial, sans-serif',
|
|
cursor: 'pointer',
|
|
pointerEvents: 'auto',
|
|
whiteSpace: 'nowrap',
|
|
textAlign: 'center',
|
|
});
|
|
btn.addEventListener('mouseenter', () => { btn.style.background = bgHover; });
|
|
btn.addEventListener('mouseleave', () => { btn.style.background = bgNormal; });
|
|
btn.addEventListener('mousedown', () => { btn.style.transform = (btn.style.transform || '') + ' scale(0.97)'; });
|
|
btn.addEventListener('mouseup', () => { btn.style.transform = btn.style.transform.replace(' scale(0.97)', ''); });
|
|
btn.addEventListener('click', onClick);
|
|
return btn;
|
|
}
|
|
|
|
_destroyDomUI() {
|
|
if (this._uiLayer && this._uiLayer.parentNode) {
|
|
this._uiLayer.parentNode.removeChild(this._uiLayer);
|
|
}
|
|
this._uiLayer = null;
|
|
}
|
|
}
|