|
<!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"> |
|
|
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
|
|
<div class="tab-content" id="history-content"> |
|
<div class="stats-section"> |
|
<div id="history-list"> |
|
|
|
</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"> |
|
|
|
</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: [] |
|
}; |
|
|
|
|
|
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') |
|
}; |
|
|
|
|
|
let socket = { |
|
connect: () => { |
|
console.log('Connecting to WebSocket server...'); |
|
|
|
|
|
|
|
|
|
setInterval(() => { |
|
updateOnlinePlayers(); |
|
updateBlockProgress(); |
|
updateLeaderboard(); |
|
}, config.updateInterval); |
|
|
|
|
|
setInterval(() => { |
|
simulateOtherPlayers(); |
|
}, 1000); |
|
}, |
|
send: (data) => { |
|
console.log('Sending data:', data); |
|
|
|
} |
|
}; |
|
|
|
|
|
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; |
|
|
|
|
|
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() { |
|
|
|
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); |
|
|
|
|
|
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) { |
|
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) { |
|
minePixel(index); |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
function initGame() { |
|
|
|
|
|
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> |