@@ -283,6 +343,431 @@
}
};
+ // API клиент
+ class GameAPI {
+ constructor(gameSlug) {
+ this.gameSlug = gameSlug;
+ this.tableName = 'game_save';
+ }
+
+ getCSRFToken() {
+ return document.querySelector('[name=csrfmiddlewaretoken]')?.value ||
+ document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1];
+ }
+
+ async requestPermission(operation, description = '') {
+ return new Promise((resolve) => {
+ const modal = document.createElement('div');
+ modal.className = 'permission-modal';
+ modal.innerHTML = `
+
+
+
+
+
Игра "${this.gameSlug}" хочет:
+
+ ${this.getOperationIcon(operation)}
+ ${this.getOperationName(operation)}
+
+ ${description ? `
${description}
` : ''}
+
+
+
+
+ `;
+
+ document.body.appendChild(modal);
+
+ const dontAskAgain = modal.querySelector('#dontAskAgain');
+ const allowBtn = modal.querySelector('#allowBtn');
+ const denyBtn = modal.querySelector('#denyBtn');
+ const allowAllBtn = modal.querySelector('#allowAllBtn');
+ const denyAllBtn = modal.querySelector('#denyAllBtn');
+
+ const handleResponse = async (allow, dontAsk, allowAll, denyAll) => {
+ await this.savePermission(operation, allow, dontAsk, allowAll, denyAll);
+ modal.remove();
+ resolve(allow);
+ };
+
+ allowBtn.onclick = () => handleResponse(true, dontAskAgain.checked, false, false);
+ denyBtn.onclick = () => handleResponse(false, dontAskAgain.checked, false, false);
+ allowAllBtn.onclick = () => handleResponse(true, true, true, false);
+ denyAllBtn.onclick = () => handleResponse(false, true, false, true);
+
+ modal.onclick = (e) => {
+ if (e.target === modal) {
+ handleResponse(false, false, false, false);
+ }
+ };
+ });
+ }
+
+ async getPermission(operation) {
+ try {
+ const response = await fetch('/api/user/permissions/', {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': this.getCSRFToken()
+ }
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ const permissions = data.permissions || {};
+ const gamePerms = permissions[this.gameSlug] || {};
+ return gamePerms[this.tableName]?.[operation] ?? null;
+ }
+ } catch (error) {
+ console.error('Ошибка загрузки разрешений:', error);
+ }
+ return null;
+ }
+
+ async savePermission(operation, allow, dontAskAgain, allowAll, denyAll) {
+ try {
+ await fetch('/api/user/permissions/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': this.getCSRFToken()
+ },
+ body: JSON.stringify({
+ game_slug: this.gameSlug,
+ table_name: this.tableName,
+ operation: operation,
+ allow: allow,
+ dont_ask_again: dontAskAgain,
+ allow_all: allowAll,
+ deny_all: denyAll
+ })
+ });
+ } catch (error) {
+ console.error('Ошибка сохранения разрешения:', error);
+ }
+ }
+
+ async checkPermission(operation, description = '') {
+ const cached = await this.getPermission(operation);
+ if (cached !== null) {
+ return cached;
+ }
+ return await this.requestPermission(operation, description);
+ }
+
+ async loadGame() {
+ const allowed = await this.checkPermission('select', 'Загрузить сохранение игры');
+ if (!allowed) {
+ throw new Error('Загрузка запрещена');
+ }
+
+ const response = await fetch('/api/game/select/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': this.getCSRFToken()
+ },
+ body: JSON.stringify({
+ game_slug: this.gameSlug,
+ table_name: this.tableName,
+ conditions: { 'key': 'game_state' }
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error('Ошибка загрузки');
+ }
+
+ const data = await response.json();
+ return data.data?.[0]?.value || null;
+ }
+
+ async saveGame(state) {
+ const allowed = await this.checkPermission('insert', 'Сохранить прогресс игры');
+ if (!allowed) {
+ throw new Error('Сохранение запрещено');
+ }
+
+ const response = await fetch('/api/game/insert/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': this.getCSRFToken()
+ },
+ body: JSON.stringify({
+ game_slug: this.gameSlug,
+ table_name: this.tableName,
+ data: {
+ 'key': 'game_state',
+ 'value': JSON.stringify(state),
+ 'updated_at': new Date().toISOString()
+ }
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error('Ошибка сохранения');
+ }
+
+ return await response.json();
+ }
+
+ async deleteGame() {
+ const allowed = await this.checkPermission('delete', 'Удалить сохранение игры');
+ if (!allowed) {
+ throw new Error('Удаление запрещено');
+ }
+
+ const response = await fetch('/api/game/delete/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': this.getCSRFToken()
+ },
+ body: JSON.stringify({
+ game_slug: this.gameSlug,
+ table_name: this.tableName,
+ conditions: { 'key': 'game_state' }
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error('Ошибка удаления');
+ }
+
+ return await response.json();
+ }
+
+ getOperationIcon(operation) {
+ const icons = {
+ 'select': 'download',
+ 'insert': 'save',
+ 'delete': 'delete'
+ };
+ return icons[operation] || 'help';
+ }
+
+ getOperationName(operation) {
+ const names = {
+ 'select': 'Загрузить данные',
+ 'insert': 'Сохранить данные',
+ 'delete': 'Удалить данные'
+ };
+ return names[operation] || operation;
+ }
+ }
+
+ // CSS для модального окна
+ const modalStyles = `
+
+ `;
+ document.head.insertAdjacentHTML('beforeend', modalStyles);
+
+ // Инициализация API
+ const gameAPI = new GameAPI('clicker-magnat');
+
// Описания улучшений
const upgradeDescriptions = {
clickPower: 'Увеличивает доход за клик на 1',
@@ -302,23 +787,81 @@
hyperAuto: '💎 Гипер автокликер'
};
- // Загрузка сохранения
- function loadGame() {
- const saved = localStorage.getItem('clickerGame');
- if (saved) {
- const savedState = JSON.parse(saved);
- gameState = { ...gameState, ...savedState };
- // Восстанавливаем стоимость улучшений
- for (let key in gameState.upgrades) {
- const upgrade = gameState.upgrades[key];
- upgrade.cost = Math.floor(upgrade.baseCost * Math.pow(1.15, upgrade.level));
+ // Показ индикатора сохранения
+ function showSaveIndicator(text, type = 'loading') {
+ const indicator = document.getElementById('saveIndicator');
+ const indicatorText = document.getElementById('saveIndicatorText');
+
+ indicator.className = 'save-indicator show';
+ if (type === 'success') {
+ indicator.classList.add('success');
+ indicator.querySelector('.spinner').style.display = 'none';
+ } else if (type === 'error') {
+ indicator.classList.add('error');
+ indicator.querySelector('.spinner').style.display = 'none';
+ } else {
+ indicator.querySelector('.spinner').style.display = 'block';
+ }
+
+ indicatorText.textContent = text;
+ }
+
+ function hideSaveIndicator() {
+ const indicator = document.getElementById('saveIndicator');
+ indicator.classList.remove('show');
+ }
+
+ // Загрузка сохранения через API
+ async function loadGame() {
+ try {
+ showSaveIndicator('Загрузка сохранения...');
+ const savedData = await gameAPI.loadGame();
+
+ if (savedData) {
+ const savedState = JSON.parse(savedData);
+ gameState = { ...gameState, ...savedState };
+
+ // Восстанавливаем стоимость улучшений
+ for (let key in gameState.upgrades) {
+ const upgrade = gameState.upgrades[key];
+ upgrade.cost = Math.floor(upgrade.baseCost * Math.pow(1.15, upgrade.level));
+ }
+ }
+
+ showSaveIndicator('Сохранение загружено', 'success');
+ setTimeout(hideSaveIndicator, 2000);
+ } catch (error) {
+ console.error('Ошибка загрузки:', error);
+ showSaveIndicator('Ошибка загрузки', 'error');
+ setTimeout(hideSaveIndicator, 2000);
+
+ // Fallback на localStorage
+ const saved = localStorage.getItem('clickerGame');
+ if (saved) {
+ const savedState = JSON.parse(saved);
+ gameState = { ...gameState, ...savedState };
+ for (let key in gameState.upgrades) {
+ const upgrade = gameState.upgrades[key];
+ upgrade.cost = Math.floor(upgrade.baseCost * Math.pow(1.15, upgrade.level));
+ }
}
}
}
- // Сохранение игры
- function saveGame() {
- localStorage.setItem('clickerGame', JSON.stringify(gameState));
+ // Сохранение игры через API
+ async function saveGame() {
+ try {
+ await gameAPI.saveGame(gameState);
+ showSaveIndicator('Сохранено', 'success');
+ setTimeout(hideSaveIndicator, 1500);
+ } catch (error) {
+ console.error('Ошибка сохранения:', error);
+ showSaveIndicator('Ошибка сохранения', 'error');
+ setTimeout(hideSaveIndicator, 2000);
+
+ // Fallback на localStorage
+ localStorage.setItem('clickerGame', JSON.stringify(gameState));
+ }
}
// Подсчет дохода за клик
@@ -405,38 +948,50 @@
setTimeout(() => animation.remove(), 1000);
updateUI();
- saveGame();
}
// Автокликер (каждую секунду)
function autoClick() {
const perSecond = getPerSecond();
if (perSecond > 0) {
- gameState.score += perSecond / 10; // Обновляем 10 раз в секунду для плавности
+ gameState.score += perSecond / 10;
updateUI();
}
}
// Сброс игры
- function resetGame() {
+ async function resetGame() {
if (confirm('Вы уверены, что хотите сбросить весь прогресс?')) {
+ try {
+ showSaveIndicator('Удаление сохранения...');
+ await gameAPI.deleteGame();
+ showSaveIndicator('Прогресс сброшен', 'success');
+ setTimeout(hideSaveIndicator, 1500);
+ } catch (error) {
+ console.error('Ошибка удаления:', error);
+ showSaveIndicator('Ошибка удаления', 'error');
+ setTimeout(hideSaveIndicator, 2000);
+ }
+
localStorage.removeItem('clickerGame');
location.reload();
}
}
// Инициализация
- loadGame();
- updateUI();
+ (async () => {
+ await loadGame();
+ updateUI();
- document.getElementById('clickButton').addEventListener('click', handleClick);
- document.getElementById('resetButton').addEventListener('click', resetGame);
+ document.getElementById('clickButton').addEventListener('click', handleClick);
+ document.getElementById('resetButton').addEventListener('click', resetGame);
- // Автокликер каждые 100мс
- setInterval(autoClick, 100);
+ // Автокликер каждые 100мс
+ setInterval(autoClick, 100);
- // Автосохранение каждые 10 секунд
- setInterval(saveGame, 10000);
+ // Автосохранение каждые 30 секунд
+ setInterval(saveGame, 30000);
+ })();