iPuzzle/js/scenes/MainMenuScene.js

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;
}
}