ppl / index.html
ZMaxAIru's picture
Add 1 files
fde372b verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Пиксельный майнинг</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--bg-color: #1a1a2e;
--panel-bg: #16213e;
--text-color: #e6e6e6;
--accent-color: #4cc9f0;
--rare-silver: #c0c0c0;
--rare-gold: #ffd700;
--rare-diamond: #b9f2ff;
--pixel-size: 16px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
min-height: 100vh;
overflow-x: hidden;
}
.container {
max-width: 100%;
padding: 12px;
margin: 0 auto;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 12px;
}
.logo {
font-weight: bold;
font-size: 1.2rem;
color: var(--accent-color);
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: var(--panel-bg);
}
.mining-section {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 20px;
}
.canvas-container {
position: relative;
margin: 0 auto;
border: 2px solid var(--panel-bg);
border-radius: 8px;
overflow: hidden;
background-color: #111;
}
.pixel-canvas {
display: grid;
grid-template-columns: repeat(var(--cols), var(--pixel-size));
grid-template-rows: repeat(var(--rows), var(--pixel-size));
}
.pixel {
background-color: #222;
border: 1px solid #111;
cursor: pointer;
transition: background-color 0.2s;
}
.pixel:hover {
opacity: 0.9;
}
.pixel-mined {
background-color: #444;
}
.pixel-special {
position: relative;
}
.pixel-silver {
background-color: var(--rare-silver);
}
.pixel-gold {
background-color: var(--rare-gold);
}
.pixel-diamond {
background-color: var(--rare-diamond);
}
.mining-info {
background-color: var(--panel-bg);
border-radius: 8px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.block-info {
display: flex;
justify-content: space-between;
}
.progress-container {
background-color: #333;
border-radius: 999px;
height: 8px;
margin-top: 4px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: var(--accent-color);
border-radius: 999px;
transition: width 0.3s;
}
.mining-button {
background-color: var(--accent-color);
color: #111;
border: none;
border-radius: 8px;
padding: 12px;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
transition: transform 0.2s, background-color 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.mining-button:active {
transform: scale(0.98);
background-color: #3aa8d8;
}
.mining-button i {
font-size: 1.2rem;
}
.stats-section {
background-color: var(--panel-bg);
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 12px;
}
.stat-item {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 6px;
padding: 8px;
text-align: center;
}
.stat-value {
font-size: 1.2rem;
font-weight: bold;
margin-top: 4px;
color: var(--accent-color);
}
.tab-container {
display: flex;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 12px;
}
.tab {
padding: 8px 12px;
cursor: pointer;
position: relative;
}
.tab.active {
color: var(--accent-color);
}
.tab.active::after {
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 2px;
background-color: var(--accent-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.leaderboard {
width: 100%;
border-collapse: collapse;
}
.leaderboard th {
text-align: left;
padding: 8px 12px;
background-color: rgba(0, 0, 0, 0.2);
}
.leaderboard td {
padding: 8px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.leaderboard tr:last-child td {
border-bottom: none;
}
.medal {
width: 18px;
height: 18px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 6px;
}
.medal-gold {
background-color: var(--rare-gold);
color: #333;
}
.medal-silver {
background-color: var(--rare-silver);
color: #333;
}
.medal-bronze {
background-color: #cd7f32;
color: #333;
}
.history-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.history-item:last-child {
border-bottom: none;
}
.rare-icon {
font-size: 0.8rem;
margin-left: 4px;
}
.pixel-tooltip {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.8);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
white-space: nowrap;
z-index: 10;
opacity: 0;
transition: opacity 0.2s;
}
.pixel:hover .pixel-tooltip {
opacity: 1;
}
.avatar-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 12px;
}
.avatar-item {
aspect-ratio: 1/1;
background-color: #333;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s;
overflow: hidden;
}
.avatar-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-item:active {
transform: scale(0.95);
}
.avatar-item.selected {
border: 2px solid var(--accent-color);
}
@media (min-width: 500px) {
:root {
--pixel-size: 20px;
}
.container {
max-width: 500px;
}
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo">PixelMiner</div>
<div class="user-info">
<span id="user-coins">0 PX</span>
<div class="user-avatar" id="user-avatar"></div>
</div>
</header>
<div class="mining-section">
<div class="canvas-container">
<div class="pixel-canvas" id="pixel-canvas"></div>
</div>
<div class="mining-info">
<div class="block-info">
<div>
<div>Блок #<span id="current-block">1</span></div>
<div class="progress-container">
<div class="progress-bar" id="block-progress" style="width: 0%"></div>
</div>
</div>
<div style="text-align: right;">
<div>Пикселей: <span id="mined-pixels">0</span>/<span id="total-pixels">0</span></div>
<div>Игроков: <span id="online-players">0</span></div>
</div>
</div>
<button class="mining-button" id="mine-button">
<i class="fas fa-hammer"></i>
<span>Добыть пиксель</span>
</button>
</div>
</div>
<div class="tab-container">
<div class="tab active" data-tab="stats">Статистика</div>
<div class="tab" data-tab="leaderboard">Лидеры</div>
<div class="tab" data-tab="history">История</div>
<div class="tab" data-tab="profile">Профиль</div>
</div>
<div class="tab-content active" id="stats-content">
<div class="stats-section">
<div class="stats-grid">
<div class="stat-item">
<div>Ваши очки</div>
<div class="stat-value" id="user-score">0</div>
</div>
<div class="stat-item">
<div>Добыто</div>
<div class="stat-value" id="user-mined">0</div>
</div>
<div class="stat-item">
<div>Серебряных</div>
<div class="stat-value" id="user-silver">0</div>
</div>
<div class="stat-item">
<div>Золотых</div>
<div class="stat-value" id="user-gold">0</div>
</div>
<div class="stat-item">
<div>Бриллиантов</div>
<div class="stat-value" id="user-diamond">0</div>
</div>
<div class="stat-item">
<div>Создано блоков</div>
<div class="stat-value" id="user-created">0</div>
</div>
<div class="stat-item">
<div>Место в топе</div>
<div class="stat-value" id="user-rank">-</div>
</div>
</div>
</div>
</div>
<div class="tab-content" id="leaderboard-content">
<div class="stats-section">
<table class="leaderboard">
<thead>
<tr>
<th>#</th>
<th>Имя</th>
<th>Очки</th>
</tr>
</thead>
<tbody id="leaderboard-body">
<!-- заполняется JS -->
</tbody>
</table>
</div>
</div>
<div class="tab-content" id="history-content">
<div class="stats-section">
<div id="history-list">
<!-- заполняется JS -->
</div>
</div>
</div>
<div class="tab-content" id="profile-content">
<div class="stats-section">
<div>
<div style="text-align: center; margin-bottom: 12px;">
<div class="user-avatar" style="width: 80px; height: 80px; margin: 0 auto 8px;" id="profile-avatar"></div>
<div id="profile-name">Имя пользователя</div>
<div style="font-size: 0.9rem; color: var(--accent-color);" id="profile-pix">0 PX</div>
</div>
<div style="margin-top: 16px;">Созданные блоки:</div>
<div class="avatar-grid" id="avatar-grid">
<!-- заполняется JS -->
</div>
</div>
</div>
</div>
</div>
<script>
// Инициализация игры
document.addEventListener('DOMContentLoaded', function() {
// Конфигурация
const config = {
cols: 16,
rows: 16,
blockSizeIncrease: 2,
initialBlockSize: 16*16,
baseScore: 1,
rareChances: {
silver: 0.1,
gold: 0.03,
diamond: 0.01
},
rareScores: {
silver: 5,
gold: 15,
diamond: 50
},
updateInterval: 2000,
fakePlayers: 25
};
// Состояние игры
const state = {
currentBlock: 1,
canvasSize: config.initialBlockSize,
minedPixels: 0,
onlinePlayers: 0,
playerData: {
score: 0,
mined: 0,
silver: 0,
gold: 0,
diamond: 0,
createdBlocks: 0,
pix: 0,
avatar: '',
name: 'Игрок'
},
leaderboard: [],
blockHistory: [],
createdBlocks: []
};
// DOM элементы
const elements = {
canvas: document.getElementById('pixel-canvas'),
currentBlock: document.getElementById('current-block'),
minedPixels: document.getElementById('mined-pixels'),
totalPixels: document.getElementById('total-pixels'),
onlinePlayers: document.getElementById('online-players'),
mineButton: document.getElementById('mine-button'),
blockProgress: document.getElementById('block-progress'),
userScore: document.getElementById('user-score'),
userMined: document.getElementById('user-mined'),
userSilver: document.getElementById('user-silver'),
userGold: document.getElementById('user-gold'),
userDiamond: document.getElementById('user-diamond'),
userCreated: document.getElementById('user-created'),
userRank: document.getElementById('user-rank'),
userCoins: document.getElementById('user-coins'),
userAvatar: document.getElementById('user-avatar'),
profileAvatar: document.getElementById('profile-avatar'),
profileName: document.getElementById('profile-name'),
profilePix: document.getElementById('profile-pix'),
leaderboardBody: document.getElementById('leaderboard-body'),
historyList: document.getElementById('history-list'),
avatarGrid: document.getElementById('avatar-grid'),
tabs: document.querySelectorAll('.tab'),
tabContents: document.querySelectorAll('.tab-content')
};
// Симуляция WebSocket соединения
let socket = {
connect: () => {
console.log('Connecting to WebSocket server...');
// Здесь будет реальное подключение через WebSocket
// Пока используем setTimeout для имитации сетевых событий
// Имитация получения данных от сервера
setInterval(() => {
updateOnlinePlayers();
updateBlockProgress();
updateLeaderboard();
}, config.updateInterval);
// Для демонстрации - имитация других игроков
setInterval(() => {
simulateOtherPlayers();
}, 1000);
},
send: (data) => {
console.log('Sending data:', data);
// Здесь будет отправка данных на сервер через WebSocket
}
};
// Инициализация канваса
function initCanvas() {
elements.canvas.style.setProperty('--cols', config.cols);
elements.canvas.style.setProperty('--rows', config.rows);
elements.totalPixels.textContent = config.cols * config.rows;
// Очищаем канвас
elements.canvas.innerHTML = '';
// Создаем пиксели
for (let i = 0; i < config.cols * config.rows; i++) {
const pixel = document.createElement('div');
pixel.className = 'pixel';
pixel.dataset.index = i;
// Добавляем tooltip
const tooltip = document.createElement('div');
tooltip.className = 'pixel-tooltip';
tooltip.textContent = 'Не добыт';
pixel.appendChild(tooltip);
elements.canvas.appendChild(pixel);
}
}
// Обновление счетчиков
function updateUI() {
elements.currentBlock.textContent = state.currentBlock;
elements.minedPixels.textContent = state.minedPixels;
elements.totalPixels.textContent = config.cols * config.rows;
elements.onlinePlayers.textContent = state.onlinePlayers;
elements.userScore.textContent = state.playerData.score;
elements.userMined.textContent = state.playerData.mined;
elements.userSilver.textContent = state.playerData.silver;
elements.userGold.textContent = state.playerData.gold;
elements.userDiamond.textContent = state.playerData.diamond;
elements.userCreated.textContent = state.playerData.createdBlocks;
elements.userCoins.textContent = state.playerData.pix + ' PX';
elements.profilePix.textContent = state.playerData.pix + ' PX';
// Обновление прогресса блока
const progress = (state.minedPixels / (config.cols * config.rows)) * 100;
elements.blockProgress.style.width = progress + '%';
}
// Добыча пикселя
function minePixel(index = null) {
// Если индекс не указан, выбираем случайный неподобранный пиксель
if (index === null) {
const unminedPixels = Array.from(document.querySelectorAll('.pixel:not(.pixel-mined)'));
if (unminedPixels.length === 0) return false;
const randomIndex = Math.floor(Math.random() * unminedPixels.length);
index = parseInt(unminedPixels[randomIndex].dataset.index);
}
const pixel = elements.canvas.children[index];
if (!pixel || pixel.classList.contains('pixel-mined')) return false;
// Определяем, является ли пиксель редким
const random = Math.random();
let pixelType = 'normal';
let score = config.baseScore;
if (random < config.rareChances.diamond) {
pixelType = 'diamond';
score = config.rareScores.diamond;
state.playerData.diamond++;
} else if (random < config.rareChances.gold) {
pixelType = 'gold';
score = config.rareScores.gold;
state.playerData.gold++;
} else if (random < config.rareChances.silver) {
pixelType = 'silver';
score = config.rareScores.silver;
state.playerData.silver++;
}
// Обновляем состояние
state.minedPixels++;
state.playerData.mined++;
state.playerData.score += score;
state.playerData.pix += score;
// Обновляем пиксель
pixel.classList.add('pixel-mined');
if (pixelType !== 'normal') {
pixel.classList.add('pixel-special', `pixel-${pixelType}`);
pixel.querySelector('.pixel-tooltip').innerHTML =
pixelType === 'silver' ? 'Серебряный ✨' :
pixelType === 'gold' ? 'Золотой ⭐' : 'Бриллиантовый 💎';
} else {
pixel.querySelector('.pixel-tooltip').textContent = 'Добыт';
}
// Проверяем, завершен ли блок
if (state.minedPixels >= config.cols * config.rows) {
completeBlock();
}
updateUI();
return true;
}
// Завершение блока
function completeBlock() {
// Находим игрока с максимальным количеством очков
// В реальном приложении это будет определяться сервером
state.playerData.createdBlocks++;
// Добавляем блок в историю
const blockData = {
id: state.currentBlock,
creator: state.playerData.name,
score: state.playerData.score,
pixels: state.minedPixels,
silver: state.playerData.silver,
gold: state.playerData.gold,
diamond: state.playerData.diamond,
completedAt: new Date()
};
state.blockHistory.unshift(blockData);
state.createdBlocks.push({
id: state.currentBlock,
// В реальном приложении здесь будет ссылка на изображение холста
image: generateBlockImage()
});
// Переходим к следующему блоку
state.currentBlock++;
state.minedPixels = 0;
// Увеличиваем размер холста для следующего блока
config.cols += config.blockSizeIncrease;
config.rows += config.blockSizeIncrease;
// Инициализируем новый холст
initCanvas();
// Показываем уведомление
showBlockCompleteNotification(blockData);
}
// Генерация изображения блока (для демонстрации)
function generateBlockImage() {
// В реальном приложении это будет canvas.toDataURL()
return `https://picsum.photos/200/200?random=${state.currentBlock}`;
}
// Показать уведомление о завершении блока
function showBlockCompleteNotification(blockData) {
const notification = document.createElement('div');
notification.style.position = 'fixed';
notification.style.bottom = '20px';
notification.style.left = '50%';
notification.style.transform = 'translateX(-50%)';
notification.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
notification.style.color = 'white';
notification.style.padding = '12px 20px';
notification.style.borderRadius = '8px';
notification.style.zIndex = '1000';
notification.style.display = 'flex';
notification.style.alignItems = 'center';
notification.style.gap = '8px';
notification.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)';
notification.innerHTML = `
<div style="font-weight: bold; color: var(--accent-color)">Блок #${blockData.id} завершен!</div>
<div style="font-size: 0.9rem;">Создатель: ${blockData.creator}</div>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.transition = 'opacity 0.5s';
notification.style.opacity = '0';
setTimeout(() => notification.remove(), 500);
}, 3000);
}
// Обновление списка игроков онлайн
function updateOnlinePlayers() {
// В реальном приложении это будет получаться с сервера
state.onlinePlayers = Math.floor(Math.random() * 50) + config.fakePlayers;
updateUI();
}
// Обновление прогресса блока
function updateBlockProgress() {
// В реальном приложении это будет получаться с сервера
if (state.minedPixels < config.cols * config.rows) {
state.minedPixels += Math.floor(Math.random() * 5) + 1;
// Ограничиваем максимумом
if (state.minedPixels > config.cols * config.rows) {
state.minedPixels = config.cols * config.rows;
}
updateUI();
}
}
// Обновление таблицы лидеров
function updateLeaderboard() {
// В реальном приложении это будет получаться с сервера
const fakeData = Array.from({length: 10}, (_, i) => ({
id: i+1,
name: ['Игрок1', 'Игрок2', 'Игрок3', 'Игрок4', 'Игрок5', 'Игрок6', 'Игрок7', 'Игрок8'][i] || `Игрок${i+1}`,
score: Math.floor(Math.random() * 10000) + 1000,
avatar: `https://picsum.photos/200/200?random=${i+100}`
}));
// Добавляем текущего игрока, если его нет в топе
if (!fakeData.some(p => p.id === 999)) {
fakeData.push({
id: 999,
name: state.playerData.name,
score: state.playerData.score,
avatar: state.playerData.avatar
});
}
// Сортируем по очкам
fakeData.sort((a, b) => b.score - a.score);
state.leaderboard = fakeData.slice(0, 10);
// Обновляем UI
elements.leaderboardBody.innerHTML = '';
state.leaderboard.forEach((player, index) => {
const isCurrentUser = player.id === 999;
const row = document.createElement('tr');
row.style.background = isCurrentUser ? 'rgba(76, 201, 240, 0.2)' : 'transparent';
row.innerHTML = `
<td>
${index < 3 ?
`<span class="medal ${index === 0 ? 'medal-gold' : index === 1 ? 'medal-silver' : 'medal-bronze'}">
${index + 1}
</span>` :
index + 1
}
</td>
<td>
<div style="display: flex; align-items: center; gap: 8px;">
<img src="${player.avatar}" style="width: 24px; height: 24px; border-radius: 50%;">
${player.name}
</div>
</td>
<td>${player.score.toLocaleString()}</td>
`;
elements.leaderboardBody.appendChild(row);
// Обновляем место текущего игрока
if (isCurrentUser) {
const rank = index + 1;
elements.userRank.textContent = rank;
}
});
// Если текущего игрока нет в топе, показываем его место
if (!state.leaderboard.some(p => p.id === 999)) {
elements.userRank.textContent = '>10';
}
}
// Имитация других игроков
function simulateOtherPlayers() {
const unminedPixels = Array.from(document.querySelectorAll('.pixel:not(.pixel-mined)'));
if (unminedPixels.length === 0) return;
const simultaneousMining = Math.floor(Math.random() * 5);
for (let i = 0; i < simultaneousMining; i++) {
if (Math.random() < 0.7) { // 70% шанс, что другой игрок добудет пиксель
const randomIndex = Math.floor(Math.random() * unminedPixels.length);
minePixel(parseInt(unminedPixels[randomIndex].dataset.index));
// Обновляем список неподобранных пикселей
unminedPixels.splice(randomIndex, 1);
if (unminedPixels.length === 0) break;
}
}
}
// Обновление истории блоков
function updateHistory() {
elements.historyList.innerHTML = '';
if (state.blockHistory.length === 0) {
elements.historyList.innerHTML = '<div style="text-align: center; padding: 12px; color: #666;">История блоков пуста</div>';
return;
}
state.blockHistory.forEach(block => {
const item = document.createElement('div');
item.className = 'history-item';
item.innerHTML = `
<div>
<div style="font-weight: bold;">Блок #${block.id}</div>
<div style="font-size: 0.8rem; color: #aaa;">${block.completedAt.toLocaleTimeString()}</div>
</div>
<div style="text-align: right;">
<div>${block.creator}</div>
<div style="font-size: 0.8rem;">
<span>${block.score} PX</span>
${block.silver > 0 ? `<span class="rare-icon">✨${block.silver}</span>` : ''}
${block.gold > 0 ? `<span class="rare-icon">⭐${block.gold}</span>` : ''}
${block.diamond > 0 ? `<span class="rare-icon">💎${block.diamond}</span>` : ''}
</div>
</div>
`;
elements.historyList.appendChild(item);
});
}
// Обновление профиля
function updateProfile() {
elements.profileName.textContent = state.playerData.name;
if (state.playerData.avatar) {
elements.userAvatar.style.backgroundImage = `url(${state.playerData.avatar})`;
elements.userAvatar.style.backgroundSize = 'cover';
elements.profileAvatar.style.backgroundImage = `url(${state.playerData.avatar})`;
elements.profileAvatar.style.backgroundSize = 'cover';
}
// Обновляем сетку созданных блоков
elements.avatarGrid.innerHTML = '';
if (state.createdBlocks.length === 0) {
elements.avatarGrid.innerHTML = '<div style="grid-column: 1 / -1; text-align: center; padding: 12px;">Нет созданных блоков</div>';
return;
}
state.createdBlocks.forEach(block => {
const item = document.createElement('div');
item.className = 'avatar-item';
item.dataset.blockId = block.id;
const img = document.createElement('img');
img.src = block.image;
img.alt = `Блок ${block.id}`;
item.appendChild(img);
elements.avatarGrid.appendChild(item);
});
}
// Обработчики событий
function setupEventListeners() {
// Кнопка добычи пикселя
elements.mineButton.addEventListener('click', () => {
minePixel();
});
// Переключение вкладок
elements.tabs.forEach(tab => {
tab.addEventListener('click', () => {
elements.tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const tabName = tab.dataset.tab;
elements.tabContents.forEach(content => {
content.classList.remove('active');
if (content.id === `${tabName}-content`) {
content.classList.add('active');
// Обновляем данные при открытии вкладки
if (tabName === 'leaderboard') {
updateLeaderboard();
} else if (tabName === 'history') {
updateHistory();
} else if (tabName === 'profile') {
updateProfile();
}
}
});
});
});
// Клик по пикселю
elements.canvas.addEventListener('click', (e) => {
if (e.target.classList.contains('pixel')) {
const index = parseInt(e.target.dataset.index);
if (!e.target.classList.contains('pixel-mined')) {
if (Math.random() < 0.5) { // 50% шанс успешной добычи при клике
minePixel(index);
}
}
}
});
}
// Инициализация игры
function initGame() {
// Устанавливаем имя игрока из Telegram WebApp.initData
// В демо-версии используем случайное имя
const randomName = `Игрок${Math.floor(Math.random() * 1000)}`;
state.playerData.name = randomName;
state.playerData.avatar = `https://picsum.photos/200/200?random=${Math.floor(Math.random() * 1000)}`;
initCanvas();
updateUI();
setupEventListeners();
socket.connect();
// Инициализация демо-данных
setTimeout(() => {
updateLeaderboard();
updateHistory();
updateProfile();
}, 500);
}
// Запуск игры
initGame();
});
</script>
</body>
</html>