// ============================================================
// 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);
});
});