🎮ArcadeLab

Meme Merge Evolution+ | Full Menu & Progression

by CyberPanther37
762 lines34.5 KB🛠️ Phaser (2D game framework)
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <title>Meme Merge Evolution+ | Full Menu & Progression</title>

    <script src="https://cdn.jsdelivr.net/npm/phaser@3.70.0/dist/phaser.min.js"></script>
    <script src="https://yandex.ru/games/sdk/v2"></script>

    <style>
        * {
            -webkit-tap-highlight-color: transparent;
            user-select: none;
        }
        
        body {
            margin: 0;
            background: radial-gradient(circle at center, #08081a 0%, #000000 100%);
            overflow: hidden;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            font-family: 'Arial', sans-serif;
        }
        
        canvas {
            border-radius: 24px;
            box-shadow: 0 0 60px rgba(0, 242, 254, 0.15), 0 0 30px rgba(0,0,0,0.8);
        }
    </style>
</head>
<body>

<script>
// ================================
// YANDEX SDK
// ================================
let ysdk = null;
if (typeof YaGames !== 'undefined') {
    YaGames.init().then(sdk => { ysdk = sdk; }).catch(err => console.error(err));
}

// ================================
// WEB AUDIO SYNTHESIZER
// ================================
const SoundFX = {
    ctx: null,
    enabled: true,
    musicEnabled: false,

    init() {
        if (!this.ctx && this.enabled) {
            this.ctx = new (window.AudioContext || window.webkitAudioContext)();
            if (this.ctx.state === 'suspended') {
                this.ctx.resume();
            }
        }
    },

    playSpawn() {
        if (!this.enabled) return;
        this.init();
        if (!this.ctx) return;
        const ctx = this.ctx;
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(150, ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(80, ctx.currentTime + 0.1);
        gain.gain.setValueAtTime(0.15, ctx.currentTime);
        gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.1);
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.start();
        osc.stop(ctx.currentTime + 0.1);
    },

    playMerge(combo = 1) {
        if (!this.enabled) return;
        this.init();
        if (!this.ctx) return;
        const ctx = this.ctx;
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        const startFreq = 200 + (combo * 40);
        const endFreq = 500 + (combo * 80);
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(startFreq, ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(endFreq, ctx.currentTime + 0.2);
        gain.gain.setValueAtTime(0.2, ctx.currentTime);
        gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.2);
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.start();
        osc.stop(ctx.currentTime + 0.2);
    },

    playGameOver() {
        if (!this.enabled) return;
        this.init();
        if (!this.ctx) return;
        const ctx = this.ctx;
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(180, ctx.currentTime);
        osc.frequency.linearRampToValueAtTime(50, ctx.currentTime + 0.6);
        gain.gain.setValueAtTime(0.25, ctx.currentTime);
        gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.6);
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.start();
        osc.stop(ctx.currentTime + 0.6);
    },

    playUIClick() {
        if (!this.enabled) return;
        this.init();
        if (!this.ctx) return;
        const ctx = this.ctx;
        const osc = ctx.createOscillator();
        const gain = ctx.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(880, ctx.currentTime);
        gain.gain.setValueAtTime(0.1, ctx.currentTime);
        gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.1);
        osc.connect(gain);
        gain.connect(ctx.destination);
        osc.start();
        osc.stop(ctx.currentTime + 0.1);
    }
};

// ================================
// GLOBALS & STORAGE HELPERS
// ================================
function loadSettings() {
    return {
        soundEnabled: localStorage.getItem('soundEnabled') !== 'false',
        musicEnabled: localStorage.getItem('musicEnabled') === 'true',
        quality: localStorage.getItem('quality') === 'low' ? 'low' : 'high'
    };
}

function saveSettings(settings) {
    localStorage.setItem('soundEnabled', settings.soundEnabled);
    localStorage.setItem('musicEnabled', settings.musicEnabled);
    localStorage.setItem('quality', settings.quality);
    SoundFX.enabled = settings.soundEnabled;
}

function loadStats() {
    return {
        bestScore: parseInt(localStorage.getItem('bestScore')) || 0,
        bestCombo: parseInt(localStorage.getItem('bestCombo')) || 0,
        totalGames: parseInt(localStorage.getItem('totalGames')) || 0,
        lastDate: localStorage.getItem('lastDate') || '',
        bestToday: parseInt(localStorage.getItem('bestToday')) || 0
    };
}

function updateStats(score, combo) {
    let stats = loadStats();
    stats.totalGames++;
    if (score > stats.bestScore) stats.bestScore = score;
    if (combo > stats.bestCombo) stats.bestCombo = combo;
    
    const today = new Date().toDateString();
    if (stats.lastDate !== today) {
        stats.bestToday = score;
        stats.lastDate = today;
    } else {
        if (score > stats.bestToday) stats.bestToday = score;
    }
    
    localStorage.setItem('bestScore', stats.bestScore);
    localStorage.setItem('bestCombo', stats.bestCombo);
    localStorage.setItem('totalGames', stats.totalGames);
    localStorage.setItem('bestToday', stats.bestToday);
    localStorage.setItem('lastDate', stats.lastDate);
    return stats;
}

// ================================
// DYNAMIC RESOLUTION
// ================================
function getGameDimensions() {
    let maxWidth = Math.min(window.innerWidth, 500);
    let maxHeight = Math.min(window.innerHeight, 850);
    let ratio = maxHeight / maxWidth;
    if (ratio > 1.8) {
        maxWidth = Math.floor(maxHeight / 1.78);
    } else if (ratio < 1.5) {
        maxHeight = Math.floor(maxWidth * 1.78);
    }
    return { width: maxWidth, height: maxHeight };
}

let GAME_WIDTH = 420;
let GAME_HEIGHT = 760;
const TOP_LIMIT = 140;
const MAX_LEVEL = 8;
let BASE_GRAVITY = 1250;

// ================================
// LEVEL COLORS
// ================================
const LEVEL_COLORS = [
    0x00f2fe, 0x00ff87, 0xff007f, 0xff9f43, 0x7000ff, 0xfeff33, 0x00ffff, 0xffffff
];
const LEVEL_GLOW = [
    0x80f9ff, 0x80ffc3, 0xff80bf, 0xffcf9e, 0xb880ff, 0xfffd96, 0x96ffff, 0xffffff
];

// ================================
// BOOT SCENE (SPLASH)
// ================================
class BootScene extends Phaser.Scene {
    constructor() { super({ key: 'BootScene' }); }
    preload() { }
    create() {
        let dims = getGameDimensions();
        window.GAME_WIDTH = dims.width;
        window.GAME_HEIGHT = dims.height;
        
        this.cameras.main.setBackgroundColor('#000000');
        let logo = this.add.text(dims.width/2, dims.height/2, 'MERGE EVOLUTION', { 
            fontSize: '32px', 
            fontFamily: 'Arial Black', 
            color: '#ffffff', 
            stroke: '#ff00ff', 
            strokeThickness: 4 
        }).setOrigin(0.5);
        
        this.tweens.add({ 
            targets: logo, 
            scale: 1.2, 
            alpha: 0.5, 
            duration: 800, 
            yoyo: true, 
            repeat: 1, 
            onComplete: () => this.scene.start('MenuScene') 
        });
        
        SoundFX.enabled = loadSettings().soundEnabled;
    }
}

// ================================
// MENU SCENE
// ================================
class MenuScene extends Phaser.Scene {
    constructor() {
        super({ key: 'MenuScene' });
    }

    create() {
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        
        this.add.rectangle(0, 0, w, h, 0x050515).setOrigin(0);
        this.createBackgroundParticles(w, h);
        
        const title = this.add.text(w/2, h*0.2, '⚡ MERGE ARENA ⚡', {
            fontSize: '38px', fontFamily: 'Arial Black', color: '#ffffff', stroke: '#ff00ff', strokeThickness: 4,
            shadow: { offsetX: 0, offsetY: 0, color: '#ff00ff', blur: 15, fill: true }
        }).setOrigin(0.5);
        
        const btnStyle = { fontSize: '28px', fontFamily: 'Arial Black', color: '#ffffff', stroke: '#000', strokeThickness: 3 };
        const createBtn = (text, y, onClick) => {
            const btn = this.add.text(w/2, y, text, btnStyle).setOrigin(0.5).setInteractive({ useHandCursor: true });
            btn.setBackgroundColor('#222244');
            btn.setPadding(16, 12, 16, 12);
            btn.on('pointerover', () => btn.setColor('#ffff00'));
            btn.on('pointerout', () => btn.setColor('#ffffff'));
            btn.on('pointerdown', () => { 
                SoundFX.playUIClick(); 
                onClick(); 
            });
            return btn;
        };
        
        createBtn('🎮 ИГРАТЬ', h*0.4, () => {
            this.scene.start('GameScene');
        });
        createBtn('⚔️ ЧЕЛЛЕНДЖ ДНЯ', h*0.52, () => {
            this.showComingSoon();
        });
        createBtn('🔧 НАСТРОЙКИ', h*0.64, () => {
            this.scene.start('SettingsScene');
        });
        createBtn('🏆 РЕКОРДЫ', h*0.76, () => {
            this.scene.start('RecordsScene');
        });
        
        this.tweens.add({ targets: title, scale: 1.05, duration: 1500, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' });
    }
    
    createBackgroundParticles(w, h) {
        for (let i = 0; i < 40; i++) {
            const x = Phaser.Math.Between(0, w);
            const y = Phaser.Math.Between(0, h);
            const particle = this.add.circle(x, y, Phaser.Math.Between(2, 5), 0x00f2fe, 0.3);
            this.tweens.add({
                targets: particle,
                y: particle.y - Phaser.Math.Between(40, 120),
                x: particle.x + Phaser.Math.Between(-20, 20),
                alpha: 0,
                duration: Phaser.Math.Between(2000, 4000),
                onComplete: () => { if(particle.active) particle.destroy(); },
                repeat: -1
            });
        }
    }
    
    showComingSoon() {
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        const msg = this.add.text(w/2, h/2, 'СКОРО!', { fontSize: '44px', fontFamily: 'Arial Black', color: '#ffaa00', stroke: '#000', strokeThickness: 4 }).setOrigin(0.5);
        this.time.delayedCall(1000, () => { msg.destroy(); });
    }
}

// ================================
// SETTINGS SCENE
// ================================
class SettingsScene extends Phaser.Scene {
    constructor() {
        super({ key: 'SettingsScene' });
    }

    create() {
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        
        this.add.rectangle(0, 0, w, h, 0x050515).setOrigin(0);
        this.createFloatingParticles(w, h);
        
        const title = this.add.text(w/2, 60, 'НАСТРОЙКИ', { fontSize: '36px', fontFamily: 'Arial Black', color: '#00f2fe', stroke: '#000', strokeThickness: 3 }).setOrigin(0.5);
        
        let settings = loadSettings();
        this.createToggleButton(w/2, 160, 'Звук: ', settings.soundEnabled, (val) => { settings.soundEnabled = val; saveSettings(settings); SoundFX.enabled = val; });
        this.createToggleButton(w/2, 240, 'Музыка: ', settings.musicEnabled, (val) => { settings.musicEnabled = val; saveSettings(settings); });
        
        const qualityHigh = this.add.text(w/2 - 80, 320, 'ВЫСОКОЕ', { fontSize: '24px', fontFamily: 'Arial', color: settings.quality === 'high' ? '#00ff87' : '#888' }).setInteractive({ useHandCursor: true });
        const qualityLow = this.add.text(w/2 + 80, 320, 'НИЗКОЕ', { fontSize: '24px', fontFamily: 'Arial', color: settings.quality === 'low' ? '#00ff87' : '#888' }).setInteractive({ useHandCursor: true });
        
        qualityHigh.on('pointerdown', () => {
            settings.quality = 'high';
            saveSettings(settings);
            qualityHigh.setColor('#00ff87');
            qualityLow.setColor('#888');
            SoundFX.playUIClick();
        });
        qualityLow.on('pointerdown', () => {
            settings.quality = 'low';
            saveSettings(settings);
            qualityLow.setColor('#00ff87');
            qualityHigh.setColor('#888');
            SoundFX.playUIClick();
        });
        
        const backBtn = this.add.text(w/2, h - 80, '◀  НАЗАД', { fontSize: '32px', fontFamily: 'Arial Black', color: '#ffffff', backgroundColor: '#222244', padding: {x:20,y:10} }).setOrigin(0.5).setInteractive({ useHandCursor: true });
        backBtn.on('pointerdown', () => {
            SoundFX.playUIClick();
            this.scene.start('MenuScene');
        });
    }
    
    createToggleButton(x, y, label, initialValue, onChange) {
        const txt = this.add.text(x - 100, y, label + (initialValue ? 'ВКЛ' : 'ВЫКЛ'), { fontSize: '28px', fontFamily: 'Arial', color: '#ffffff' }).setOrigin(0, 0.5);
        const btn = this.add.rectangle(x + 60, y, 70, 40, initialValue ? 0x44ff44 : 0x444444).setInteractive({ useHandCursor: true });
        const circle = this.add.circle(btn.x + (initialValue ? 20 : -20), btn.y, 16, 0xffffff);
        
        btn.on('pointerdown', () => {
            const newVal = !initialValue;
            initialValue = newVal;
            txt.setText(label + (newVal ? 'ВКЛ' : 'ВЫКЛ'));
            btn.setFillStyle(newVal ? 0x44ff44 : 0x444444);
            circle.x = btn.x + (newVal ? 20 : -20);
            onChange(newVal);
            SoundFX.playUIClick();
        });
        return { txt, btn, circle };
    }
    
    createFloatingParticles(w, h) {
        for (let i = 0; i < 30; i++) {
            const x = Phaser.Math.Between(0, w);
            const y = Phaser.Math.Between(0, h);
            const particle = this.add.circle(x, y, Phaser.Math.Between(2, 4), 0x00ffff, 0.2);
            this.tweens.add({ targets: particle, y: y - 40, x: x + 20, alpha: 0, duration: 3000, repeat: -1, yoyo: true });
        }
    }
}

// ================================
// RECORDS SCENE
// ================================
class RecordsScene extends Phaser.Scene {
    constructor() {
        super({ key: 'RecordsScene' });
    }

    create() {
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        
        this.add.rectangle(0, 0, w, h, 0x050515).setOrigin(0);
        this.add.text(w/2, 50, '🏆 РЕКОРДЫ 🏆', { fontSize: '36px', fontFamily: 'Arial Black', color: '#ffd700', stroke: '#000', strokeThickness: 3 }).setOrigin(0.5);
        
        const stats = loadStats();
        const records = [
            { label: 'ЛУЧШИЙ СЧЁТ', value: stats.bestScore },
            { label: 'ЛУЧШЕЕ КОМБО', value: stats.bestCombo + 'x' },
            { label: 'РЕКОРД СЕГОДНЯ', value: stats.bestToday },
            { label: 'ВСЕГО ИГР', value: stats.totalGames }
        ];
        
        records.forEach((rec, i) => {
            this.add.text(w/2 - 120, 150 + i * 70, rec.label + ':', { fontSize: '24px', fontFamily: 'Arial', color: '#ccc' }).setOrigin(0, 0.5);
            this.add.text(w/2 + 80, 150 + i * 70, rec.value.toString(), { fontSize: '32px', fontFamily: 'Arial Black', color: '#00ff87' }).setOrigin(1, 0.5);
        });
        
        const backBtn = this.add.text(w/2, h - 80, '◀  ГЛАВНОЕ МЕНЮ', { fontSize: '28px', fontFamily: 'Arial Black', color: '#ffffff', backgroundColor: '#222244', padding: {x:20,y:10} }).setOrigin(0.5).setInteractive({ useHandCursor: true });
        backBtn.on('pointerdown', () => {
            SoundFX.playUIClick();
            this.scene.start('MenuScene');
        });
        
        this.createParticlesBg(w, h);
    }
    
    createParticlesBg(w, h) {
        for (let i = 0; i < 40; i++) {
            const star = this.add.circle(Phaser.Math.Between(0, w), Phaser.Math.Between(0, h), 2, 0xffaa44, 0.3);
            this.tweens.add({ targets: star, alpha: 0, duration: 1500, yoyo: true, repeat: -1 });
        }
    }
}

// ================================
// GAME SCENE WITH PROGRESSION
// ================================
class GameScene extends Phaser.Scene {
    constructor() {
        super({ key: 'GameScene' });
    }

    preload() {
        for (let i = 0; i < MAX_LEVEL; i++) {
            let size = 50 + i * 12;
            let radius = size / 2;
            let g = this.make.graphics({ x: 0, y: 0, add: false });
            for (let glow = 12; glow > 0; glow -= 2) g.fillStyle(LEVEL_GLOW[i], 0.12 - glow * 0.008).fillCircle(radius, radius, radius + glow);
            g.fillStyle(0x000000, 0.4).fillCircle(radius + 4, radius + 4, radius);
            g.fillStyle(LEVEL_COLORS[i], 1).fillCircle(radius, radius, radius);
            for (let j = 0; j < radius; j += 4) g.fillStyle(0xffffff, 0.15 * (1 - j / radius)).fillCircle(radius, radius - j, radius - j);
            g.fillStyle(0xffffff, 0.6).fillCircle(radius - 10, radius - 10, radius / 3.2);
            g.lineStyle(2.5, 0xffffff, 0.6).strokeCircle(radius, radius, radius - 2);
            g.lineStyle(2, LEVEL_GLOW[i], 0.7).strokeCircle(radius, radius, radius - 3);
            g.generateTexture('ball' + (i + 1), size, size);
        }
    }

    create() {
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        
        this.input.once('pointerdown', () => { SoundFX.init(); });
        
        // Difficulty progression vars
        this.difficulty = 1;
        this.maxBallsCreatedCount = 0; // счётчик созданных максимальных шаров (уровень 8)
        this.currentMaxBalls = 60;
        this.baseGravity = BASE_GRAVITY;
        this.updateDifficultyParams();
        
        this.score = 0;
        this.combo = 0;
        this.gameOver = false;
        this.canSpawn = true;
        this.nextLevel = 1;
        this.isGameOverTriggered = false;
        this.comboTimer = null;
        
        let savedBest = localStorage.getItem('bestScore');
        this.bestScore = savedBest ? parseInt(savedBest) : 0;
        
        this.physics.world.setBounds(0, 0, w, h);
        this.balls = this.physics.add.group();
        
        this.bgGlow = this.add.circle(w / 2, h / 2, 200, 0xffffff, 0).setBlendMode(Phaser.BlendModes.ADD);
        this.createStarBackground(w, h);
        
        // Danger line
        this.add.rectangle(w / 2, TOP_LIMIT, w, 3, 0xff0055, 0.9);
        this.add.rectangle(w / 2, TOP_LIMIT, w, 16, 0xff0055, 0.2);
        
        // UI
        this.scoreText = this.add.text(12, 12, '0', { fontSize: '46px', fontFamily: 'Arial Black', color: '#ffffff', shadow: { offsetX: 0, offsetY: 0, color: '#00f2fe', blur: 12, fill: true } });
        this.bestText = this.add.text(w - 12, 20, '🏆 ' + this.formatScore(this.bestScore), { fontSize: '18px', color: '#ffd700', fontFamily: 'Arial', fontStyle: 'bold', shadow: { offsetX: 2, offsetY: 2, color: '#000', blur: 4 } }).setOrigin(1, 0);
        this.comboText = this.add.text(w / 2, 105, '', { fontSize: '38px', color: '#ff00ff', fontStyle: 'bold', fontFamily: 'Arial Black', stroke: '#000', strokeThickness: 5 }).setOrigin(0.5).setAlpha(0);
        
        // Difficulty Bar
        this.diffBarBg = this.add.rectangle(w/2, 115, w - 40, 12, 0x333333).setOrigin(0.5);
        this.diffBarFill = this.add.rectangle(w/2 - (w-40)/2, 115, 0, 12, 0xffaa44).setOrigin(0, 0.5);
        this.diffText = this.add.text(w/2, 130, 'СЛОЖНОСТЬ 1', { fontSize: '14px', color: '#ffaa44', fontFamily: 'Arial', fontStyle: 'bold' }).setOrigin(0.5);
        
        // Счётчик максимальных шаров (для отладки на экране)
        this.counterText = this.add.text(w/2, 148, 'Белых шаров: 0', { fontSize: '12px', color: '#ffffff', fontFamily: 'Arial' }).setOrigin(0.5);
        
        // КНОПКА ДОМОЙ (выход в меню)
        this.homeBtn = this.add.text(w - 50, 12, '🏠', { fontSize: '36px', fontFamily: 'Arial', color: '#ffffff', backgroundColor: '#000000aa', padding: {x:8,y:4} })
            .setOrigin(0.5)
            .setInteractive({ useHandCursor: true });
        this.homeBtn.on('pointerover', () => this.homeBtn.setColor('#ffaa44'));
        this.homeBtn.on('pointerout', () => this.homeBtn.setColor('#ffffff'));
        this.homeBtn.on('pointerdown', () => {
            SoundFX.playUIClick();
            updateStats(this.score, this.combo);
            this.scene.start('MenuScene');
        });
        
        this.previewContainer = this.add.container(w - 45, 68);
        this.previewBg = this.add.circle(0, 0, 34, 0x000000, 0.6).setStrokeStyle(2.5, 0x00f2fe, 0.8);
        this.preview = this.add.image(0, 0, 'ball1').setScale(0.85);
        this.previewContainer.add([this.previewBg, this.preview]);
        this.tweens.add({ targets: this.previewContainer, angle: 360, duration: 8000, repeat: -1 });
        
        this.aimLine = this.add.graphics();
        
        this.input.on('pointermove', (pointer) => {
            if (this.gameOver) return;
            this.aimLine.clear();
            this.aimLine.lineStyle(2.5, 0x00f2fe, 0.5);
            this.aimLine.lineBetween(pointer.x, 110, pointer.x, h);
        });
        
        this.input.on('pointerdown', (pointer) => {
            if (this.gameOver) {
                this.addFlash(0xffffff, 0.6);
                this.scene.restart();
                return;
            }
            if (!this.canSpawn) return;
            if (this.balls.getLength() >= this.currentMaxBalls) {
                this.endGame();
                return;
            }
            SoundFX.playSpawn();
            this.createRipple(pointer.x, pointer.y);
            this.addFlash(LEVEL_COLORS[this.nextLevel - 1], 0.15);
            this.spawnBall(pointer.x, 90, this.nextLevel);
            this.canSpawn = false;
            this.tweens.add({ targets: this.previewContainer, scale: 0.7, duration: 100, yoyo: true });
            this.cameras.main.shake(30, 0.004);
            this.time.delayedCall(250, () => { if (!this.gameOver) this.canSpawn = true; });
            
            // Dynamic spawn probabilities based on difficulty
            let r = Math.random();
            let chance2 = Math.min(0.25 + this.difficulty * 0.01, 0.45);
            let chance3 = Math.min(0.1 + this.difficulty * 0.008, 0.25);
            if (r < chance3) this.nextLevel = 3;
            else if (r < chance2 + chance3) this.nextLevel = 2;
            else this.nextLevel = 1;
            if (this.nextLevel > MAX_LEVEL - 2) this.nextLevel = MAX_LEVEL - 2;
            this.preview.setTexture('ball' + this.nextLevel);
        });
        
        this.physics.add.collider(this.balls, this.balls, this.handleCollision, null, this);
        this.time.addEvent({ delay: 5000, callback: this.cleanupBalls, callbackScope: this, loop: true });
        this.showStartEffect(w, h);
        
        console.log('🎮 Игра запущена! Сложность повышается каждые 3 БЕЛЫХ шара (8 уровень)');
    }
    
    updateDifficultyParams() {
        const w = GAME_WIDTH;
        let gravityVal = this.baseGravity * (1 + (this.difficulty-1) * 0.05);
        this.physics.world.gravity.y = Math.min(gravityVal, 2800);
        this.currentMaxBalls = Math.max(40, 60 - Math.floor(this.difficulty/2));
        let fillPercent = this.difficulty / 20;
        if (this.diffBarFill) {
            this.diffBarFill.width = (w - 40) * Math.min(fillPercent, 1);
        }
        if (this.diffText) {
            this.diffText.setText(`СЛОЖНОСТЬ ${this.difficulty}`);
        }
        if (this.counterText) {
            let needed = 3 - (this.maxBallsCreatedCount % 3);
            if (needed === 0) needed = 3;
            this.counterText.setText(`⚪ Белых шаров: ${this.maxBallsCreatedCount} (до след. ур.: ${needed})`);
        }
        if (this.diffBarFill && this.diffBarFill.width > 0) this.diffBarFill.setVisible(true);
        
        console.log(`📊 Параметры сложности ${this.difficulty}: гравитация=${Math.round(gravityVal)}, макс.шаров=${this.currentMaxBalls}`);
    }
    
    increaseDifficulty() {
        if (this.difficulty >= 20) return;
        this.difficulty = Math.min(20, this.difficulty + 1);
        this.updateDifficultyParams();
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        const notif = this.add.text(w/2, h/2 - 100, `⚠️ СЛОЖНОСТЬ ${this.difficulty} ⚠️`, { fontSize: '28px', fontFamily: 'Arial Black', color: '#ff5500', stroke: '#000', strokeThickness: 4 }).setOrigin(0.5);
        this.tweens.add({ targets: notif, scale: 1.2, alpha: 0, duration: 800, onComplete: () => notif.destroy() });
        this.cameras.main.shake(200, 0.008);
        console.log(`🔥🔥🔥 СЛОЖНОСТЬ ПОВЫШЕНА ДО ${this.difficulty}! Создано белых шаров: ${this.maxBallsCreatedCount}`);
    }
    
    createStarBackground(w, h) {
        for (let i = 0; i < 60; i++) {
            const x = Phaser.Math.Between(0, w);
            const y = Phaser.Math.Between(0, h);
            const star = this.add.circle(x, y, Phaser.Math.Between(1, 2.5), 0xffffff, Phaser.Math.Between(0.2, 0.5));
            this.tweens.add({ targets: star, alpha: 0.05, duration: Phaser.Math.Between(1000, 3000), yoyo: true, repeat: -1 });
        }
    }
    
    showStartEffect(w, h) {
        this.addFlash(0x00f2fe, 0.5);
        const startText = this.add.text(w / 2, h / 2, '⚡ NEON MERGE ⚡', { fontSize: '42px', fontFamily: 'Arial Black', color: '#ffffff', stroke: '#ff00ff', strokeThickness: 4 }).setOrigin(0.5).setAlpha(0);
        this.tweens.add({ targets: startText, scale: 1.2, alpha: 1, duration: 400, yoyo: true, onComplete: () => startText.destroy() });
    }
    
    createRipple(x, y) { const ripple = this.add.circle(x, y, 5, 0x00ffff, 0.8); this.tweens.add({ targets: ripple, scale: 3, alpha: 0, duration: 200, onComplete: () => ripple.destroy() }); }
    addFlash(color, intensity) { const w = GAME_WIDTH; const h = GAME_HEIGHT; const flash = this.add.rectangle(w/2, h/2, w, h, color, intensity); this.tweens.add({ targets: flash, alpha: 0, duration: 150, onComplete: () => flash.destroy() }); }
    formatScore(score) { if (score >= 1000000) return (score/1000000).toFixed(1)+'M'; if (score>=1000) return (score/1000).toFixed(0)+'K'; return score.toString(); }

    spawnBall(x, y, level) {
        if (this.gameOver) return;
        const ball = this.balls.create(x, y, 'ball' + level);
        if (!ball) return;
        ball.setData('level', level);
        ball.setData('isMerging', false);
        ball.setData('dangerTime', null);
        ball.setBounce(0.2).setDrag(2).setMass(1 + level * 0.3).setCollideWorldBounds(true).setCircle(ball.width/2);
        ball.setScale(0);
        this.tweens.add({ targets: ball, scale: 1, duration: 200, ease: 'Back.Out' });
        
        // ОТСЛЕЖИВАНИЕ БЕЛЫХ ШАРОВ (уровень 8)
        if (level === MAX_LEVEL) {
            this.maxBallsCreatedCount++;
            console.log(`✨✨✨ СОЗДАН БЕЛЫЙ ШАР #${this.maxBallsCreatedCount} ✨✨✨`);
            
            // Обновляем счётчик на экране
            if (this.counterText) {
                let needed = 3 - (this.maxBallsCreatedCount % 3);
                if (needed === 0) needed = 3;
                this.counterText.setText(`⚪ Белых шаров: ${this.maxBallsCreatedCount} (до след. ур.: ${needed})`);
            }
            
            // Каждые 3 белых шара повышаем сложность
            if (this.maxBallsCreatedCount % 3 === 0 && this.difficulty < 20) {
                this.increaseDifficulty();
            }
        }
        
        for (let i=0;i<6;i++) { const angle=Math.random()*Math.PI*2; const spark=this.add.circle(x,y,2.5,LEVEL_COLORS[level-1],0.8); this.tweens.add({targets:spark, x:x+Math.cos(angle)*30, y:y+Math.sin(angle)*30, alpha:0, duration:250, onComplete:()=>spark.destroy()}); }
    }
    
    juiceSquash(ball,sx,sy){ if(ball&&ball.active&&!ball.getData('isMerging')) this.tweens.add({targets:ball,scaleX:sx,scaleY:sy,duration:70,yoyo:true});}
    
    handleCollision(ball1,ball2){
        if(!ball1||!ball2||!ball1.active||!ball2.active||this.gameOver) return;
        let l1=ball1.getData('level'), l2=ball2.getData('level');
        if(l1!==l2) { let v1=ball1.body.velocity, v2=ball2.body.velocity; if(Math.hypot(v1.x-v2.x,v1.y-v2.y)>100) { this.juiceSquash(ball1,1.18,0.82); this.juiceSquash(ball2,0.82,1.18); } return; }
        if(l1>=MAX_LEVEL||ball1.getData('isMerging')||ball2.getData('isMerging')) return;
        if(Phaser.Math.Distance.Between(ball1.x,ball1.y,ball2.x,ball2.y)>ball1.width/2+ball2.width/2+5) return;
        ball1.setData('isMerging',true); ball2.setData('isMerging',true);
        this.combo++; SoundFX.playMerge(this.combo);
        let x=(ball1.x+ball2.x)/2, y=(ball1.y+ball2.y)/2, nextLevel=l1+1;
        let scoreGain=Math.min(l1*25*Math.min(this.combo,10),5000);
        this.score=Math.min(this.score+scoreGain,999999);
        this.showScorePopup(x,y,scoreGain);
        this.bgGlow.setPosition(x,y).setFillStyle(LEVEL_COLORS[l1-1],0.35).setScale(0.1);
        this.tweens.add({targets:this.bgGlow,scale:3+l1*0.5,alpha:0,duration:450});
        if(this.score>this.bestScore){ this.bestScore=this.score; localStorage.setItem('bestScore',this.bestScore); this.bestText.setText('🏆 '+this.formatScore(this.bestScore)); this.addFlash(0xffd700,0.2);}
        this.scoreText.setText(this.formatScore(this.score)); this.tweens.add({targets:this.scoreText,scale:1.3,duration:90,yoyo:true});
        if(this.combo>=2){ this.comboText.setText(this.combo+'x COMBO!').setAlpha(1).setScale(0.2).setColor(this.combo>=5?'#ff00ff':'#00ffff'); this.tweens.add({targets:this.comboText,scale:1.4,alpha:0,duration:500}); if(this.combo>=5&&navigator.vibrate) navigator.vibrate(60); }
        this.cameras.main.shake(80,0.006+l1*0.002);
        this.createExplosion(x,y,LEVEL_COLORS[l1-1],l1);
        ball1.destroy(); ball2.destroy();
        
        // ВАЖНО: создаём новый шар следующего уровня
        this.time.delayedCall(40, () => {
            if (!this.gameOver) {
                this.spawnBall(x, y, nextLevel);
            }
        });
        
        if(this.comboTimer) this.comboTimer.remove();
        this.comboTimer=this.time.delayedCall(1800,()=>{ if(this.combo>0&&!this.gameOver){ this.combo=0; const lost=this.add.text(GAME_WIDTH/2,130,'COMBO LOST',{fontSize:'16px',color:'#ff3b3b'}).setOrigin(0.5); this.tweens.add({targets:lost,alpha:0,y:105,duration:500,onComplete:()=>lost.destroy()}); } });
    }
    
    showScorePopup(x,y,score){ const pop=this.add.text(x,y,'+'+score,{fontSize:'30px',fontFamily:'Arial Black',color:'#ffff00',stroke:'#000',strokeThickness:3}).setOrigin(0.5); this.tweens.add({targets:pop,y:y-65,alpha:0,scale:1.5,duration:400,onComplete:()=>pop.destroy()}); }
    createExplosion(x,y,color,level){ let quality = loadSettings().quality; let count = (quality === 'low') ? Math.min(12, 8 + level) : Math.min(32, 16 + level*2); for(let i=0;i<count;i++){ let angle=Math.random()*Math.PI*2, speed=Phaser.Math.Between(90,220), part=this.add.circle(x,y,Phaser.Math.Between(3,8),color,0.95); this.tweens.add({targets:part,x:x+Math.cos(angle)*speed,y:y+Math.sin(angle)*speed,alpha:0,scale:0,duration:350+Math.random()*200,onComplete:()=>part.destroy()}); } }
    cleanupBalls(){ if(this.gameOver) return; [...this.balls.getChildren()].forEach(b=>{if(b&&b.y>GAME_HEIGHT+150)b.destroy();}); }
    
    update(){
        if(this.gameOver) return;
        let anyDanger=false;
        [...this.balls.getChildren()].forEach(ball=>{
            if(!ball.body) return;
            if(ball.body.velocity.y>300&&Math.random()<0.35){ const trail=this.add.circle(ball.x,ball.y,ball.width/2.2,LEVEL_COLORS[ball.getData('level')-1],0.4); this.tweens.add({targets:trail,alpha:0,scale:0.5,duration:120,onComplete:()=>trail.destroy()}); }
            let stable=Math.abs(ball.body.velocity.y)<15&&Math.abs(ball.body.velocity.x)<8;
            if(ball.y+ball.height/2<TOP_LIMIT){
                if(stable){
                    if(!ball.getData('dangerTime')) ball.setData('dangerTime',this.time.now);
                    else if(this.time.now-ball.getData('dangerTime')>1500) anyDanger=true;
                } else ball.setData('dangerTime',null);
            } else ball.setData('dangerTime',null);
        });
        if(anyDanger&&!this.isGameOverTriggered) this.endGame();
    }
    
    endGame(){
        if(this.gameOver||this.isGameOverTriggered) return;
        this.gameOver=true; this.isGameOverTriggered=true;
        SoundFX.playGameOver();
        updateStats(this.score, this.combo);
        if(this.physics) this.physics.pause();
        this.aimLine.clear();
        this.addFlash(0xff0055,0.4);
        const w = GAME_WIDTH;
        const h = GAME_HEIGHT;
        const overlay=this.add.rectangle(w/2,h/2,w,h,0x02020a,0.9); this.tweens.add({targets:overlay,alpha:1,duration:400});
        this.add.text(w/2,200,'GAME OVER',{fontSize:'52px',color:'#ff0055',fontStyle:'bold',fontFamily:'Arial Black',stroke:'#000',strokeThickness:5}).setOrigin(0.5).setScale(1);
        this.add.text(w/2,310,this.formatScore(this.score),{fontSize:'64px',color:'#ffff00',fontStyle:'bold',fontFamily:'Arial Black',stroke:'#000',strokeThickness:4}).setOrigin(0.5);
        const tapText=this.add.text(w/2,510,'👆 TAP TO RESTART 👆',{fontSize:'22px',color:'#ffffff'}).setOrigin(0.5);
        this.tweens.add({targets:tapText,scale:1.05,duration:400,yoyo:true,repeat:-1});
        if(ysdk) ysdk.adv.showFullscreenAdv().catch(e=>console.error(e));
    }
}

// ================================
// CONFIG AND START
// ================================
let dims = getGameDimensions();
GAME_WIDTH = dims.width;
GAME_HEIGHT = dims.height;
BASE_GRAVITY = 1250;

const config = {
    type: Phaser.AUTO,
    width: GAME_WIDTH,
    height: GAME_HEIGHT,
    backgroundColor: '#050515',
    scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
    physics: { default: 'arcade', arcade: { gravity: { y: BASE_GRAVITY }, debug: false } },
    scene: [BootScene, MenuScene, SettingsScene, RecordsScene, GameScene],
    disableContextMenu: true
};

window.addEventListener('load', () => { new Phaser.Game(config); });
</script>
</body>
</html>

Game Source: Meme Merge Evolution+ | Full Menu & Progression

Creator: CyberPanther37

Libraries: phaser

Complexity: complex (762 lines, 34.5 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: meme-merge-evolution-full-menu-progressi-cyberpanther37" to link back to the original. Then publish at arcadelab.ai/publish.