iPuzzle/js/scenes/NewPuzzleScene.js

342 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* global Phaser, generateRoomCode */
const DIFFICULTIES = [
{ pieces: 20, label: '20 Pieces' },
{ pieces: 40, label: '40 Pieces' },
{ pieces: 60, label: '60 Pieces' },
{ pieces: 100, label: '100 Pieces' }
];
class NewPuzzleScene extends Phaser.Scene {
constructor() {
super({ key: 'NewPuzzleScene' });
}
preload() {
// Load the puzzle list JSON; images are loaded dynamically in create()
this.load.json('puzzle_list', 'assets/puzzles.json');
}
create() {
this.selectedImageIdx = null;
this.selectedPieces = null;
const W = this.sys.game.config.width;
const H = this.sys.game.config.height;
// Background
this.add.graphics().fillStyle(0x1a1a2e, 1).fillRect(0, 0, W, H);
// Title — fixed position (canvas is always 1920×1080)
this.add.text(960, -30, 'Choose Your Puzzle', {
fontFamily: 'Georgia, serif',
fontSize: '58px',
color: '#e8f4ff',
stroke: '#1a4a88',
strokeThickness: 6,
shadow: { offsetX: 0, offsetY: 0, color: '#3388ff', blur: 28, fill: true, stroke: true }
}).setOrigin(0.5, 0.5);
// Decorative separator below the title
const sep = this.add.graphics();
// Faint full-width rule
sep.lineStyle(1, 0x223355, 0.7);
sep.lineBetween(77, -81, 1843, -81);
// Bright centre segment
sep.lineStyle(2, 0x3377bb, 1);
sep.lineBetween(653, -81, 1267, -81);
// Diamond accent at the midpoint
sep.fillStyle(0x66aaff, 1);
sep.fillTriangle(960, 100, 966, 105, 960, 110);
sep.fillTriangle(960, 100, 954, 105, 960, 110);
// Read puzzle list from cache, then load any images not yet in the texture cache
this._puzzleImages = this.cache.json.get('puzzle_list');
const toLoad = this._puzzleImages.filter(img => !this.textures.exists(img.key));
if (toLoad.length > 0) {
toLoad.forEach(img => this.load.image(img.key, img.path));
this.load.once('complete', () => this._buildDomUI());
this.load.start();
} else {
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);
this._buildPuzzleGrid(this._uiLayer);
this._buildControlsBar(this._uiLayer);
this.events.once('shutdown', () => this._destroyDomUI());
}
_buildPuzzleGrid(uiLayer) {
// Scrollable area: sits below the Phaser title (top 10%) and above the controls bar (bottom 28%)
const scrollArea = document.createElement('div');
Object.assign(scrollArea.style, {
position: 'absolute',
top: '10%',
left: '5%',
width: '90%',
height: '62%',
overflowY: 'auto',
overflowX: 'hidden',
pointerEvents: 'auto',
});
const grid = document.createElement('div');
Object.assign(grid.style, {
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '1.5vmin',
padding: '1vmin',
});
this._cardEls = this._puzzleImages.map((img, i) => {
const card = document.createElement('div');
Object.assign(card.style, {
cursor: 'pointer',
border: '2px solid transparent',
borderRadius: '6px',
overflow: 'hidden',
transition: 'border-color 0.15s, box-shadow 0.15s',
background: '#111128',
});
const thumb = document.createElement('img');
thumb.src = img.path;
Object.assign(thumb.style, {
display: 'block',
width: '100%',
aspectRatio: '16 / 9',
objectFit: 'cover',
});
const label = document.createElement('div');
Object.assign(label.style, {
padding: '0.5vmin 0.8vmin',
color: '#8899cc',
fontSize: '1.5vmin',
fontFamily: 'Arial, sans-serif',
textAlign: 'center',
});
label.textContent = img.label;
card.appendChild(thumb);
card.appendChild(label);
card.addEventListener('mouseenter', () => {
if (this.selectedImageIdx !== i) {
card.style.borderColor = '#334466';
}
});
card.addEventListener('mouseleave', () => {
if (this.selectedImageIdx !== i) {
card.style.borderColor = 'transparent';
card.style.boxShadow = '';
}
});
card.addEventListener('click', () => this._selectImage(i));
grid.appendChild(card);
return card;
});
scrollArea.appendChild(grid);
uiLayer.appendChild(scrollArea);
}
_buildControlsBar(uiLayer) {
const bar = document.createElement('div');
Object.assign(bar.style, {
position: 'absolute',
bottom: '0',
left: '0',
width: '100%',
height: '28%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '1.5vmin',
borderTop: '1px solid #222244',
background: 'rgba(10, 10, 25, 0.7)',
pointerEvents: 'auto',
});
// "Difficulty" label
const diffLabel = document.createElement('div');
Object.assign(diffLabel.style, {
color: '#8899cc',
fontSize: '1.8vmin',
fontFamily: 'Arial, sans-serif',
});
diffLabel.textContent = 'Difficulty';
// Difficulty buttons row
const diffRow = document.createElement('div');
Object.assign(diffRow.style, {
display: 'flex',
gap: '2vmin',
});
this._diffBtnEls = DIFFICULTIES.map((diff, i) => {
const btn = document.createElement('button');
btn.textContent = diff.label;
Object.assign(btn.style, {
padding: '0.7vmin 2.5vmin',
background: '#111133',
color: '#ccddff',
border: '1px solid #222255',
borderRadius: '4px',
fontSize: '1.6vmin',
fontFamily: 'Arial, sans-serif',
cursor: 'pointer',
transition: 'background 0.1s, border-color 0.1s',
});
btn.addEventListener('mouseenter', () => {
if (this.selectedPieces !== diff.pieces) btn.style.background = '#1a3366';
});
btn.addEventListener('mouseleave', () => {
if (this.selectedPieces !== diff.pieces) btn.style.background = '#111133';
});
btn.addEventListener('click', () => this._selectDifficulty(i, diff.pieces));
diffRow.appendChild(btn);
return { el: btn, pieces: diff.pieces };
});
// Action buttons row (Back left, Start right)
const actionRow = document.createElement('div');
Object.assign(actionRow.style, {
display: 'flex',
justifyContent: 'center',
gap: '4vmin',
width: '55%',
});
const backBtn = this._makeDomBtn('← Back', '#221133', '#332244', '#443366');
backBtn.style.color = '#8899cc';
backBtn.style.flex = '1';
backBtn.addEventListener('click', () => this.scene.start('MainMenuScene'));
this._startBtnEl = this._makeDomBtn('Start Puzzle →', '#111111', '#111111', '#222222');
Object.assign(this._startBtnEl.style, {
flex: '1',
color: '#445544',
cursor: 'not-allowed',
});
this._startBtnEl.disabled = true;
this._startBtnEl._bgNormal = '#111111';
this._startBtnEl._bgHover = '#111111';
this._startBtnEl.addEventListener('click', () => this._startPuzzle());
actionRow.appendChild(backBtn);
actionRow.appendChild(this._startBtnEl);
bar.appendChild(diffLabel);
bar.appendChild(diffRow);
bar.appendChild(actionRow);
uiLayer.appendChild(bar);
}
// ─── Selection ───────────────────────────────────────────────────────
_selectImage(idx) {
this.selectedImageIdx = idx;
this._cardEls.forEach((card, i) => {
const sel = i === idx;
card.style.borderColor = sel ? '#44aaff' : 'transparent';
card.style.boxShadow = sel ? '0 0 10px #44aaff88' : '';
});
this._refreshStartButton();
}
_selectDifficulty(idx, pieces) {
this.selectedPieces = pieces;
this._diffBtnEls.forEach(({ el, pieces: p }) => {
const sel = p === pieces;
el.style.background = sel ? '#224488' : '#111133';
el.style.borderColor = sel ? '#44aaff' : '#222255';
});
this._refreshStartButton();
}
_refreshStartButton() {
if (!this._startBtnEl) return;
const ready = this.selectedImageIdx !== null && this.selectedPieces !== null;
this._startBtnEl.disabled = !ready;
this._startBtnEl._bgNormal = ready ? '#115511' : '#111111';
this._startBtnEl._bgHover = ready ? '#227722' : '#111111';
Object.assign(this._startBtnEl.style, {
background: this._startBtnEl._bgNormal,
borderColor: ready ? '#44cc44' : '#222222',
color: ready ? '#aaffaa' : '#445544',
cursor: ready ? 'pointer' : 'not-allowed',
});
}
// ─── Helpers ─────────────────────────────────────────────────────────
_makeDomBtn(label, bgNormal, bgHover, borderColor) {
const btn = document.createElement('button');
btn.textContent = label;
btn._bgNormal = bgNormal;
btn._bgHover = bgHover;
Object.assign(btn.style, {
padding: '0.8vmin 0',
background: bgNormal,
color: '#ddeeff',
border: `1px solid ${borderColor}`,
borderRadius: '4px',
fontSize: '1.7vmin',
fontFamily: 'Arial, sans-serif',
cursor: 'pointer',
whiteSpace: 'nowrap',
textAlign: 'center',
});
btn.addEventListener('mouseenter', () => { if (!btn.disabled) btn.style.background = btn._bgHover; });
btn.addEventListener('mouseleave', () => { if (!btn.disabled) btn.style.background = btn._bgNormal; });
return btn;
}
_destroyDomUI() {
if (this._uiLayer && this._uiLayer.parentNode) {
this._uiLayer.parentNode.removeChild(this._uiLayer);
}
this._uiLayer = null;
}
// ─── Start puzzle ────────────────────────────────────────────────────
_startPuzzle() {
if (this.selectedImageIdx === null || this.selectedPieces === null) return;
const img = this._puzzleImages[this.selectedImageIdx];
this.scene.start('PuzzleScene', {
imageKey: img.key,
imagePath: img.path,
pieceCount: this.selectedPieces,
roomCode: generateRoomCode()
});
}
}