// ============================================================ // 1. ИНИЦИАЛИЗАЦИЯ SDK // ============================================================ let ysdk = null; let player = null; let currentLevel = 0; let foundDifferences = 0; let totalDifferences = 5; let levelData = []; let hintsRemaining = 3; let isGameReady = false; // Определение языка через SDK let currentLang = 'ru'; // ============================================================ // 2. ГЕНЕРАЦИЯ УРОВНЕЙ (5 отличий на уровень, 50 уровней) // ============================================================ function generateLevels(count = 50) { const levels = []; for (let i = 0; i < count; i++) { const diffs = []; const used = new Set(); for (let j = 0; j < 5; j++) { let x, y, key; do { x = Math.floor(Math.random() * 180) + 10; y = Math.floor(Math.random() * 180) + 10; key = `${x},${y}`; } while (used.has(key)); used.add(key); diffs.push({ x: x, y: y, radius: 18, found: false }); } levels.push({ id: i, differences: diffs, imageSeed: i }); } return levels; } // ============================================================ // 3. ОТРИСОВКА КАНВАСОВ // ============================================================ function drawCanvas(canvas, seed, differences, isRight) { const ctx = canvas.getContext('2d'); const rect = canvas.parentElement.getBoundingClientRect(); const size = Math.min(rect.width * 0.42, 350); canvas.width = size; canvas.height = size; // Генерация "псевдо-картинки" на основе seed const rng = seedRandom(seed + (isRight ? 1000 : 0)); // Фон const gradient = ctx.createLinearGradient(0, 0, size, size); const hue1 = Math.floor(rng() * 360); const hue2 = (hue1 + 40) % 360; gradient.addColorStop(0, `hsl(${hue1}, 70%, 60%)`); gradient.addColorStop(1, `hsl(${hue2}, 70%, 40%)`); ctx.fillStyle = gradient; ctx.fillRect(0, 0, size, size); // Случайные геометрические фигуры const shapes = 12 + Math.floor(rng() * 8); for (let i = 0; i < shapes; i++) { const x = rng() * size; const y = rng() * size; const w = 15 + rng() * 40; const h = 15 + rng() * 40; const alpha = 0.3 + rng() * 0.5; ctx.globalAlpha = alpha; ctx.fillStyle = `hsl(${Math.floor(rng() * 360)}, 60%, ${40 + rng() * 40}%)`; if (rng() > 0.5) { ctx.fillRect(x, y, w, h); } else { ctx.beginPath(); ctx.arc(x + w/2, y + h/2, w/2, 0, Math.PI * 2); ctx.fill(); } } ctx.globalAlpha = 1; // Если это правая картинка — рисуем отличия (кружки) if (isRight) { for (const diff of differences) { if (!diff.found) { const cx = (diff.x / 200) * size; const cy = (diff.y / 200) * size; ctx.beginPath(); ctx.arc(cx, cy, diff.radius * (size / 200), 0, Math.PI * 2); ctx.strokeStyle = '#ff0000'; ctx.lineWidth = 3; ctx.stroke(); ctx.fillStyle = 'rgba(255, 0, 0, 0.15)'; ctx.fill(); } } } } // Простой генератор псевдослучайных чисел function seedRandom(seed) { let s = seed; return function() { s = (s * 9301 + 49297) % 233280; return s / 233280; }; } // ============================================================ // 4. ЛОГИКА ИГРЫ // ============================================================ function loadLevel(index) { if (index >= levelData.length) { showResultScreen(true); return; } currentLevel = index; const level = levelData[index]; foundDifferences = 0; totalDifferences = level.differences.length; hintsRemaining = 3; document.getElementById('level-num').textContent = index + 1; document.getElementById('found').textContent = '0'; document.getElementById('total').textContent = totalDifferences; const canvasLeft = document.getElementById('canvas-left'); const canvasRight = document.getElementById('canvas-right'); // Сбрасываем found у всех отличий for (const diff of level.differences) { diff.found = false; } drawCanvas(canvasLeft, level.imageSeed, level.differences, false); drawCanvas(canvasRight, level.imageSeed + 1000, level.differences, true); showScreen('game-screen'); // Сохраняем прогресс через SDK if (player) { player.setData({ currentLevel: index, levelData: JSON.stringify(levelData.map(l => l.differences.map(d => d.found))) }).then(() => { console.log('Progress saved'); }).catch(err => { console.warn('Save error:', err); }); } } function handleCanvasClick(e) { const canvas = e.target; const rect = canvas.getBoundingClientRect(); const x = (e.clientX || e.touches?.[0]?.clientX || 0) - rect.left; const y = (e.clientY || e.touches?.[0]?.clientY || 0) - rect.top; const size = canvas.width; const scaleX = size / rect.width; const scaleY = size / rect.height; const canvasX = x * scaleX; const canvasY = y * scaleY; const level = levelData[currentLevel]; if (!level) return; for (const diff of level.differences) { if (diff.found) continue; const dx = canvasX - (diff.x / 200) * size; const dy = canvasY - (diff.y / 200) * size; const radius = diff.radius * (size / 200); if (dx * dx + dy * dy < radius * radius) { diff.found = true; foundDifferences++; document.getElementById('found').textContent = foundDifferences; // Перерисовываем правый канвас const canvasRight = document.getElementById('canvas-right'); drawCanvas(canvasRight, level.imageSeed + 1000, level.differences, true); // Показываем rewarded ad после каждого найденного отличия (по желанию) // showRewardedAd(); if (foundDifferences >= totalDifferences) { showResultScreen(false); } return; } } } function showResultScreen(isComplete) { const screen = document.getElementById('result-screen'); if (isComplete) { document.querySelector('#result-screen h2').textContent = '🎉 Игра пройдена!'; document.querySelector('#result-screen p').innerHTML = 'Вы нашли все отличия!
Спасибо за игру!'; document.getElementById('next-btn').textContent = 'Сыграть ещё раз'; document.getElementById('next-btn').onclick = () => { levelData = generateLevels(50); loadLevel(0); }; } else { document.querySelector('#result-screen h2').textContent = '✅ Уровень пройден!'; document.querySelector('#result-screen p').innerHTML = `Найдено отличий: ${foundDifferences} из ${totalDifferences}`; document.getElementById('next-btn').textContent = 'Следующий уровень'; document.getElementById('next-btn').onclick = () => { // Показываем interstitial между уровнями showInterstitialAd(() => { loadLevel(currentLevel + 1); }); }; } showScreen('result-screen'); } function showHint() { if (hintsRemaining <= 0) { alert('Подсказки закончились!'); return; } const level = levelData[currentLevel]; if (!level) return; // Находим первое ненайденное отличие for (const diff of level.differences) { if (!diff.found) { const canvas = document.getElementById('canvas-right'); const size = canvas.width; const cx = (diff.x / 200) * size; const cy = (diff.y / 200) * size; const ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.arc(cx, cy, 25 * (size / 200), 0, Math.PI * 2); ctx.strokeStyle = '#00ff00'; ctx.lineWidth = 4; ctx.setLineDash([6, 6]); ctx.stroke(); ctx.setLineDash([]); hintsRemaining--; break; } } } // ============================================================ // 5. РЕКЛАМА (вызовы через SDK) // ============================================================ function showInterstitialAd(callback) { if (!ysdk) { if (callback) callback(); return; } ysdk.adv.showFullscreenAdv({ callbacks: { onClose: function() { console.log('Interstitial closed'); if (callback) callback(); }, onError: function(err) { console.warn('Interstitial error:', err); if (callback) callback(); } } }); } function showRewardedAd(callback) { if (!ysdk) { if (callback) callback(false); return; } ysdk.adv.showRewardedVideo({ callbacks: { onRewarded: function() { console.log('Rewarded video completed'); if (callback) callback(true); }, onClose: function() { console.log('Rewarded video closed'); if (callback) callback(false); }, onError: function(err) { console.warn('Rewarded video error:', err); if (callback) callback(false); } } }); } // ============================================================ // 6. УПРАВЛЕНИЕ ЭКРАНАМИ // ============================================================ function showScreen(id) { const screens = ['loading-screen', 'menu-screen', 'game-screen', 'result-screen']; for (const s of screens) { const el = document.getElementById(s); if (el) { el.style.display = (s === id) ? 'flex' : 'none'; if (s === 'game-screen') { el.style.display = (s === id) ? 'flex' : 'none'; } } } } // ============================================================ // 7. ТАБЛИЦА ЛИДЕРОВ // ============================================================ function showLeaderboard() { if (!ysdk) return; ysdk.leaderboards.showLeaderboard({ id: 'default' }).catch(err => { console.warn('Leaderboard error:', err); }); } // ============================================================ // 8. ЗВУК (остановка при потере фокуса) // ============================================================ document.addEventListener('visibilitychange', function() { const audioElements = document.querySelectorAll('audio'); if (document.hidden) { for (const audio of audioElements) { audio.pause(); } } }); // Отключение контекстного меню document.addEventListener('contextmenu', function(e) { e.preventDefault(); }); // ============================================================ // 9. ИНИЦИАЛИЗАЦИЯ SDK И ЗАПУСК // ============================================================ window.addEventListener('load', function() { // Инициализация SDK if (typeof YaGames !== 'undefined') { YaGames.init() .then(function(sdk) { ysdk = sdk; return ysdk.getPlayer(); }) .then(function(p) { player = p; // Определение языка if (ysdk.environment && ysdk.environment.i18n) { currentLang = ysdk.environment.i18n.lang || 'ru'; } // Загрузка сохранённого прогресса return player.getData(['currentLevel', 'levelData']); }) .then(function(data) { const savedLevel = data.currentLevel || 0; levelData = generateLevels(50); // Восстановление прогресса if (data.levelData) { try { const savedDiffs = JSON.parse(data.levelData); if (savedDiffs && savedDiffs.length === levelData.length) { for (let i = 0; i < levelData.length; i++) { if (savedDiffs[i] && savedDiffs[i].length === levelData[i].differences.length) { for (let j = 0; j < levelData[i].differences.length; j++) { levelData[i].differences[j].found = savedDiffs[i][j] || false; } } } } } catch (e) { console.warn('Failed to parse saved data'); } } // Game Ready — ОБЯЗАТЕЛЬНО! if (ysdk && ysdk.features && ysdk.features.LoadingAPI) { ysdk.features.LoadingAPI.ready(); isGameReady = true; console.log('Game Ready called'); } showScreen('menu-screen'); document.getElementById('loading-screen').style.display = 'none'; }) .catch(function(err) { console.error('SDK init error:', err); // Fallback — запускаем без SDK (для локальной разработки) levelData = generateLevels(50); showScreen('menu-screen'); document.getElementById('loading-screen').style.display = 'none'; }); } else { // SDK не загружен — fallback levelData = generateLevels(50); showScreen('menu-screen'); document.getElementById('loading-screen').style.display = 'none'; } // ============================================================ // 10. НАВЕШИВАНИЕ СОБЫТИЙ // ============================================================ document.getElementById('start-btn').addEventListener('click', function() { loadLevel(0); }); document.getElementById('leaderboard-btn').addEventListener('click', function() { showLeaderboard(); }); document.getElementById('hint-btn').addEventListener('click', function() { showHint(); }); document.getElementById('restart-btn').addEventListener('click', function() { loadLevel(currentLevel); }); // Клик по канвасам const canvasLeft = document.getElementById('canvas-left'); const canvasRight = document.getElementById('canvas-right'); canvasLeft.addEventListener('click', handleCanvasClick); canvasLeft.addEventListener('touchstart', function(e) { e.preventDefault(); handleCanvasClick(e); }); canvasRight.addEventListener('click', handleCanvasClick); canvasRight.addEventListener('touchstart', function(e) { e.preventDefault(); handleCanvasClick(e); }); });