293 lines
8.5 KiB
TypeScript
293 lines
8.5 KiB
TypeScript
import Phaser from 'phaser';
|
||
import { Difficulty } from '../game/types';
|
||
import { GameSceneData, loadAudioPreferences } from '../game/preferences';
|
||
import { SettingsScene } from './SettingsScene';
|
||
|
||
type MenuButtonPalette = {
|
||
base: number;
|
||
hover: number;
|
||
};
|
||
|
||
type DifficultyOption = {
|
||
label: string;
|
||
subtitle: string;
|
||
value: Difficulty;
|
||
palette: MenuButtonPalette;
|
||
};
|
||
|
||
type RuleSection = {
|
||
heading: string;
|
||
lines: string[];
|
||
};
|
||
|
||
const TITLE_STYLE: Phaser.Types.GameObjects.Text.TextStyle = {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '52px',
|
||
color: '#ffd700',
|
||
stroke: '#000000',
|
||
strokeThickness: 4,
|
||
resolution: 2,
|
||
};
|
||
|
||
const PANEL_TITLE_STYLE: Phaser.Types.GameObjects.Text.TextStyle = {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '24px',
|
||
color: '#ffd700',
|
||
resolution: 2,
|
||
};
|
||
|
||
const BODY_STYLE: Phaser.Types.GameObjects.Text.TextStyle = {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '18px',
|
||
color: '#f8f5e6',
|
||
resolution: 2,
|
||
};
|
||
|
||
export class MenuScene extends Phaser.Scene {
|
||
constructor() {
|
||
super({ key: 'MenuScene' });
|
||
}
|
||
|
||
create(): void {
|
||
const width = this.scale.width;
|
||
const height = this.scale.height;
|
||
const audioPreferences = loadAudioPreferences();
|
||
|
||
this.drawBackground(width, height);
|
||
this.drawDecorativeCards(width, height);
|
||
this.ensureSettingsSceneAvailable();
|
||
|
||
this.add.text(width / 2, 92, 'Scopone Scientifico', TITLE_STYLE).setOrigin(0.5);
|
||
this.add.text(width / 2, 142, 'Due squadre da due, una mano intera e lettura del tavolo fino all’ultimo punto.', {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '21px',
|
||
color: '#d9f2d2',
|
||
resolution: 2,
|
||
}).setOrigin(0.5);
|
||
|
||
this.createRulesPanel(width, height);
|
||
this.createControlPanel(width, height, audioPreferences);
|
||
}
|
||
|
||
private drawBackground(width: number, height: number): void {
|
||
this.add.rectangle(0, 0, width, height, 0x123d22).setOrigin(0);
|
||
this.add.rectangle(width / 2, height / 2, width - 60, height - 60, 0x0b2916, 0.28)
|
||
.setStrokeStyle(2, 0xe8c25d, 0.35);
|
||
this.add.rectangle(width * 0.34, height * 0.58, width * 0.46, height * 0.50, 0x0d2215, 0.82)
|
||
.setStrokeStyle(2, 0xc8a445, 0.4);
|
||
this.add.rectangle(width * 0.77, height * 0.58, width * 0.24, height * 0.50, 0x10261b, 0.86)
|
||
.setStrokeStyle(2, 0xc8a445, 0.4);
|
||
}
|
||
|
||
private drawDecorativeCards(width: number, height: number): void {
|
||
const positions = [
|
||
[width * 0.08, height * 0.85],
|
||
[width * 0.14, height * 0.87],
|
||
[width * 0.92, height * 0.85],
|
||
[width * 0.86, height * 0.87],
|
||
];
|
||
|
||
positions.forEach(([x, y]) => {
|
||
this.add.image(x, y, 'retro').setScale(0.08).setAngle(Phaser.Math.Between(-15, 15)).setAlpha(0.9);
|
||
});
|
||
}
|
||
|
||
private createRulesPanel(width: number, height: number): void {
|
||
const panelX = width * 0.12;
|
||
const panelY = 206;
|
||
const sections: RuleSection[] = [
|
||
{
|
||
heading: 'Tavolo e squadre',
|
||
lines: [
|
||
'Si gioca in coppia: Sud e Nord contro Ovest ed Est.',
|
||
'Nel vero scopone si distribuiscono tutte le 40 carte, 10 a giocatore.',
|
||
],
|
||
},
|
||
{
|
||
heading: 'Come si prende',
|
||
lines: [
|
||
'Ogni carta cattura una carta dello stesso valore oppure una combinazione equivalente.',
|
||
'Se sul tavolo c’è una presa diretta dello stesso valore, quella ha sempre la precedenza.',
|
||
],
|
||
},
|
||
{
|
||
heading: 'Punteggio partita',
|
||
lines: [
|
||
'A fine mano contano carte, denari, settebello, primiera e scope.',
|
||
'La sfida prosegue mano dopo mano finché una squadra arriva ad almeno 11 punti.',
|
||
],
|
||
},
|
||
];
|
||
|
||
this.add.text(panelX, panelY, 'Regolamento essenziale', PANEL_TITLE_STYLE).setOrigin(0, 0.5);
|
||
|
||
let currentY = panelY + 44;
|
||
sections.forEach((section) => {
|
||
this.add.text(panelX, currentY, section.heading, {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '20px',
|
||
color: '#ffffff',
|
||
resolution: 2,
|
||
}).setOrigin(0, 0.5);
|
||
currentY += 30;
|
||
|
||
section.lines.forEach((line) => {
|
||
this.add.text(panelX, currentY, `• ${line}`, {
|
||
...BODY_STYLE,
|
||
wordWrap: { width: width * 0.40 },
|
||
lineSpacing: 5,
|
||
}).setOrigin(0, 0);
|
||
currentY += 54;
|
||
});
|
||
|
||
currentY += 6;
|
||
});
|
||
|
||
this.add.text(panelX, height - 92, 'Scegli la difficoltà quando vuoi iniziare: le preferenze audio vengono lette al momento della partita.', {
|
||
...BODY_STYLE,
|
||
fontSize: '16px',
|
||
color: '#cfe5cd',
|
||
wordWrap: { width: width * 0.41 },
|
||
}).setOrigin(0, 0.5);
|
||
}
|
||
|
||
private createControlPanel(width: number, height: number, audioPreferences: ReturnType<typeof loadAudioPreferences>): void {
|
||
const panelCenterX = width * 0.77;
|
||
const difficultyOptions: DifficultyOption[] = [
|
||
{
|
||
label: 'Principiante',
|
||
subtitle: 'AI prudente e leggibile',
|
||
value: 'beginner',
|
||
palette: { base: 0x2e7d32, hover: 0x43a047 },
|
||
},
|
||
{
|
||
label: 'Avanzato',
|
||
subtitle: 'Pressione costante sul tavolo',
|
||
value: 'advanced',
|
||
palette: { base: 0xd97706, hover: 0xf59e0b },
|
||
},
|
||
{
|
||
label: 'Maestro',
|
||
subtitle: 'Massima lettura e priorità alle prese forti',
|
||
value: 'master',
|
||
palette: { base: 0xb91c1c, hover: 0xdc2626 },
|
||
},
|
||
];
|
||
|
||
this.add.text(panelCenterX, 214, 'Inizia una partita', PANEL_TITLE_STYLE).setOrigin(0.5);
|
||
this.add.text(panelCenterX, 250, 'Ogni partita usa la difficoltà scelta qui sotto e le preferenze audio salvate.', {
|
||
...BODY_STYLE,
|
||
fontSize: '16px',
|
||
color: '#d7ead1',
|
||
align: 'center',
|
||
wordWrap: { width: 250 },
|
||
}).setOrigin(0.5, 0);
|
||
|
||
difficultyOptions.forEach((option, index) => {
|
||
const y = 340 + index * 96;
|
||
this.createButton(panelCenterX, y, 260, 64, option.label, option.subtitle, option.palette, () => {
|
||
this.startGame(option.value);
|
||
});
|
||
});
|
||
|
||
const musicLabel = audioPreferences.musicEnabled ? 'attiva' : 'disattivata';
|
||
const effectsLabel = audioPreferences.effectsEnabled ? 'attivi' : 'disattivati';
|
||
|
||
this.add.text(panelCenterX, height - 154, `Musica ${musicLabel} · Effetti ${effectsLabel}`, {
|
||
...BODY_STYLE,
|
||
fontSize: '16px',
|
||
color: '#cfe5cd',
|
||
align: 'center',
|
||
}).setOrigin(0.5);
|
||
|
||
this.createButton(
|
||
panelCenterX,
|
||
height - 100,
|
||
260,
|
||
58,
|
||
'Impostazioni audio',
|
||
'Modifica musica ed effetti in modo indipendente',
|
||
{ base: 0x1f6f78, hover: 0x2f8f99 },
|
||
() => {
|
||
this.openSettings();
|
||
},
|
||
);
|
||
}
|
||
|
||
private createButton(
|
||
x: number,
|
||
y: number,
|
||
width: number,
|
||
height: number,
|
||
label: string,
|
||
subtitle: string,
|
||
palette: MenuButtonPalette,
|
||
onClick: () => void,
|
||
): void {
|
||
const background = this.add.rectangle(x, y, width, height, palette.base, 1)
|
||
.setStrokeStyle(2, 0xf5e1a4, 0.4)
|
||
.setInteractive({ useHandCursor: true });
|
||
|
||
this.add.text(x, y - 10, label, {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '21px',
|
||
color: '#ffffff',
|
||
stroke: '#000000',
|
||
strokeThickness: 2,
|
||
resolution: 2,
|
||
}).setOrigin(0.5);
|
||
|
||
this.add.text(x, y + 14, subtitle, {
|
||
fontFamily: 'Georgia, serif',
|
||
fontSize: '13px',
|
||
color: '#f7f1d5',
|
||
resolution: 2,
|
||
align: 'center',
|
||
}).setOrigin(0.5);
|
||
|
||
background.on('pointerover', () => background.setFillStyle(palette.hover));
|
||
background.on('pointerout', () => background.setFillStyle(palette.base));
|
||
background.on('pointerdown', onClick);
|
||
}
|
||
|
||
private startGame(difficulty: Difficulty): void {
|
||
const gameData: GameSceneData = {
|
||
difficulty,
|
||
audioPreferences: loadAudioPreferences(),
|
||
};
|
||
|
||
this.cameras.main.fadeOut(300, 0, 30, 0);
|
||
this.cameras.main.once('camerafadeoutcomplete', () => {
|
||
this.scene.start('GameScene', gameData);
|
||
});
|
||
}
|
||
|
||
private openSettings(): void {
|
||
this.ensureSettingsSceneAvailable();
|
||
this.cameras.main.fadeOut(250, 0, 30, 0);
|
||
this.cameras.main.once('camerafadeoutcomplete', () => {
|
||
this.scene.start('SettingsScene', { returnSceneKey: 'MenuScene' });
|
||
});
|
||
}
|
||
|
||
private ensureSettingsSceneAvailable(): void {
|
||
let existingScene: Phaser.Scene | null = null;
|
||
|
||
try {
|
||
existingScene = this.scene.get('SettingsScene');
|
||
} catch {
|
||
existingScene = null;
|
||
}
|
||
|
||
if (existingScene instanceof SettingsScene) {
|
||
return;
|
||
}
|
||
|
||
if (existingScene) {
|
||
this.scene.remove('SettingsScene');
|
||
}
|
||
|
||
this.scene.add('SettingsScene', SettingsScene, false);
|
||
}
|
||
}
|