Hexagonic
by NeonFlare132536 lines100.2 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Hexagonic</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
body {
background: #0a0a1a;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Segoe UI', Arial, sans-serif;
overflow: hidden;
position: relative;
}
.game-container {
background: #141428;
padding: 15px;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0,0,0,0.9);
display: flex;
flex-direction: column;
align-items: center;
max-width: 98vw;
position: relative;
z-index: 1;
}
h1 { color: #ff6b6b; font-size: 20px; margin-bottom: 6px; text-shadow: 0 0 30px rgba(255,107,107,0.3); letter-spacing: 3px; }
.top-bar { display: flex; justify-content: space-between; width: 100%; margin-bottom: 8px; gap: 8px; flex-wrap: wrap; align-items: center; }
.stats { display: flex; gap: 12px; background: rgba(255,255,255,0.05); padding: 5px 12px; border-radius: 10px; flex-wrap: wrap; }
.stat { color: #ccc; font-size: 12px; }
.stat span { font-weight: bold; }
.stat .color-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; vertical-align: middle; margin-right: 3px; border: 2px solid rgba(255,255,255,0.2); }
.controls { display: flex; gap: 6px; flex-wrap: wrap; }
.btn {
border: none;
color: white;
padding: 5px 16px;
border-radius: 30px;
font-size: 12px;
font-weight: 700;
cursor: pointer;
transition: all 0.25s ease;
letter-spacing: 0.5px;
background: rgba(255,255,255,0.06);
backdrop-filter: blur(4px);
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
position: relative;
overflow: hidden;
}
.btn::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle at center, rgba(255,255,255,0.05) 0%, transparent 70%);
opacity: 0;
transition: opacity 0.4s;
pointer-events: none;
}
.btn:hover:not(:disabled)::after { opacity: 1; }
.btn:hover:not(:disabled) { transform: translateY(-2px) scale(1.02); box-shadow: 0 8px 20px rgba(0,0,0,0.3); }
.btn:active:not(:disabled) { transform: scale(0.96); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none !important; box-shadow: none !important; }
.btn-primary { background: linear-gradient(145deg, #ff6b6b, #e55555); border-color: #ff6b6b; }
.btn-primary:hover:not(:disabled) { background: linear-gradient(145deg, #ff7a7a, #f55a5a); }
.btn-secondary { background: rgba(45, 64, 89, 0.7); border-color: rgba(255,255,255,0.1); }
.btn-secondary:hover:not(:disabled) { background: rgba(60, 85, 115, 0.9); }
.btn-green { background: linear-gradient(145deg, #4ecca3, #3db892); border-color: #4ecca3; }
.btn-green:hover:not(:disabled) { background: linear-gradient(145deg, #5edbb3, #4ac89e); }
.btn-gold { background: linear-gradient(145deg, #ffd700, #f0c000); color: #1a1a3e; border-color: #ffd700; }
.btn-gold:hover:not(:disabled) { background: linear-gradient(145deg, #ffe44d, #f5cc00); }
.btn-outline { background: transparent; border: 1.5px solid rgba(255,255,255,0.15); }
.btn-outline:hover:not(:disabled) { background: rgba(255,255,255,0.05); border-color: rgba(255,255,255,0.3); }
.turn-indicator { color: #ff6b6b; font-size: 14px; font-weight: bold; padding: 3px 12px; border-radius: 30px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06); backdrop-filter: blur(4px); }
.message { color: #ffd700; font-size: 12px; min-height: 18px; text-align: center; margin-top: 3px; opacity: 0.9; }
canvas { border-radius: 12px; background: #0f1a2e; display: block; touch-action: none; cursor: pointer; max-width: 100%; height: auto; border: 2px solid rgba(255,255,255,0.06); box-shadow: inset 0 0 60px rgba(0,0,0,0.4); }
/* --- МЕНЮ --- */
.menu-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.85);
z-index: 9999;
justify-content: center;
align-items: center;
backdrop-filter: blur(16px);
}
.menu-overlay.active { display: flex; }
.main-menu {
display: none;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 45px 50px 35px;
background: linear-gradient(160deg, #1a1a3e, #0f0f2a);
border-radius: 40px;
min-width: 420px;
max-width: 94vw;
border: 1px solid rgba(255, 107, 107, 0.15);
box-shadow: 0 50px 120px rgba(0,0,0,0.9), inset 0 0 80px rgba(255,107,107,0.02);
position: relative;
overflow: hidden;
max-height: 90vh;
overflow-y: auto;
}
.main-menu::-webkit-scrollbar { width: 4px; }
.main-menu::-webkit-scrollbar-track { background: transparent; }
.main-menu::-webkit-scrollbar-thumb { background: rgba(255,107,107,0.3); border-radius: 10px; }
.main-menu::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(ellipse at 30% 20%, rgba(255,107,107,0.03) 0%, transparent 60%);
pointer-events: none;
}
.main-menu.active { display: flex; }
.main-menu .menu-header {
text-align: center;
position: relative;
z-index: 1;
}
.main-menu h2 {
color: #ff6b6b;
font-size: 38px;
font-weight: 300;
letter-spacing: 8px;
text-shadow: 0 0 60px rgba(255,107,107,0.15);
margin-bottom: 2px;
}
.main-menu .menu-subtitle {
color: #888;
font-size: 13px;
letter-spacing: 4px;
opacity: 0.6;
font-weight: 300;
}
.main-menu .menu-divider {
width: 60%;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255,107,107,0.2), transparent);
margin: 2px 0 8px 0;
position: relative;
z-index: 1;
}
.menu-section-title {
color: #999;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 5px;
opacity: 0.5;
margin-top: 2px;
position: relative;
z-index: 1;
}
.menu-main-buttons {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
position: relative;
z-index: 1;
padding: 10px 0;
}
.menu-main-btn {
padding: 18px 30px;
border-radius: 20px;
border: 1.5px solid rgba(255,255,255,0.06);
background: rgba(255,255,255,0.02);
color: white;
font-size: 20px;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
letter-spacing: 2px;
backdrop-filter: blur(4px);
position: relative;
overflow: hidden;
}
.menu-main-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.03) 0%, transparent 60%);
pointer-events: none;
}
.menu-main-btn::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--btn-color, #ff6b6b), transparent);
opacity: 0;
transition: opacity 0.4s;
}
.menu-main-btn:hover {
transform: translateY(-3px) scale(1.01);
box-shadow: 0 20px 50px rgba(0,0,0,0.3);
border-color: var(--btn-color, rgba(255,255,255,0.1));
}
.menu-main-btn:hover::after { opacity: 1; }
.menu-main-btn:active { transform: scale(0.97); }
.menu-main-btn.play-btn { --btn-color: #4ecca3; }
.menu-main-btn.play-btn:hover { background: rgba(78, 204, 163, 0.06); border-color: rgba(78, 204, 163, 0.3); }
.menu-main-btn.tutorial-btn { --btn-color: #ffd700; }
.menu-main-btn.tutorial-btn:hover { background: rgba(255, 215, 0, 0.06); border-color: rgba(255, 215, 0, 0.3); }
.menu-main-btn.settings-btn { --btn-color: #888; }
.menu-main-btn.settings-btn:hover { background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.15); }
.menu-main-btn .btn-icon { font-size: 24px; margin-right: 12px; vertical-align: middle; }
.menu-main-btn .btn-label { vertical-align: middle; }
.menu-sub {
display: none;
flex-direction: column;
align-items: center;
gap: 16px;
width: 100%;
position: relative;
z-index: 1;
animation: fadeSlideIn 0.3s ease;
}
.menu-sub.active { display: flex; }
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.menu-sub-back {
align-self: flex-start;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 30px;
color: #888;
padding: 8px 20px;
font-size: 13px;
cursor: pointer;
transition: all 0.3s;
font-weight: 600;
letter-spacing: 1px;
}
.menu-sub-back:hover {
background: rgba(255,255,255,0.06);
color: white;
border-color: rgba(255,255,255,0.15);
}
/* --- ОБУЧЕНИЕ --- */
.tutorial-container {
width: 100%;
max-width: 520px;
position: relative;
z-index: 1;
}
.tutorial-slide {
display: none;
flex-direction: column;
gap: 12px;
padding: 16px 20px 20px;
background: rgba(255,255,255,0.02);
border-radius: 18px;
border: 1px solid rgba(255,255,255,0.06);
animation: fadeSlideIn 0.35s ease;
}
.tutorial-slide.active { display: flex; }
.tutorial-slide .step-badge {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 2px;
color: #666;
margin-bottom: -4px;
}
.tutorial-slide .step-title {
font-size: 18px;
font-weight: 700;
color: #fff;
display: flex;
align-items: center;
gap: 10px;
}
.tutorial-slide .step-title .emoji { font-size: 24px; }
.tutorial-slide .step-desc {
color: #aaa;
font-size: 14px;
line-height: 1.6;
padding: 0 2px;
}
.tutorial-slide .step-desc strong { color: #fff; font-weight: 600; }
.tutorial-slide .step-desc .highlight-red { color: #ff6b6b; font-weight: 600; }
.tutorial-slide .step-desc .highlight-green { color: #4ecca3; font-weight: 600; }
.tutorial-slide .step-desc .highlight-gold { color: #ffd700; font-weight: 600; }
.tutorial-slide .example-box {
background: rgba(255,255,255,0.03);
border-radius: 12px;
padding: 10px 14px;
border-left: 3px solid var(--ex-color, #ff6b6b);
margin: 2px 0;
}
.tutorial-slide .example-box .ex-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: #666;
margin-bottom: 3px;
}
.tutorial-slide .example-box .ex-text {
color: #bbb;
font-size: 13px;
line-height: 1.5;
}
.tutorial-nav {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
width: 100%;
margin-top: 2px;
}
.tutorial-nav .dots {
display: flex;
gap: 6px;
}
.tutorial-nav .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: rgba(255,255,255,0.1);
transition: all 0.3s;
cursor: pointer;
}
.tutorial-nav .dot.active {
background: #ff6b6b;
box-shadow: 0 0 12px rgba(255,107,107,0.3);
width: 18px;
border-radius: 10px;
}
.tutorial-nav .dot:hover:not(.active) {
background: rgba(255,255,255,0.2);
}
.tutorial-nav .tut-btn {
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.06);
color: #aaa;
padding: 6px 18px;
border-radius: 30px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
font-family: inherit;
}
.tutorial-nav .tut-btn:hover:not(:disabled) {
background: rgba(255,255,255,0.08);
color: white;
border-color: rgba(255,255,255,0.15);
}
.tutorial-nav .tut-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.tutorial-nav .tut-btn.primary {
background: rgba(78, 204, 163, 0.15);
border-color: rgba(78, 204, 163, 0.2);
color: #4ecca3;
}
.tutorial-nav .tut-btn.primary:hover:not(:disabled) {
background: rgba(78, 204, 163, 0.25);
border-color: rgba(78, 204, 163, 0.4);
}
/* Выбор сложности и карты */
.game-options {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
width: 100%;
}
.difficulty-list {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
min-width: 280px;
position: relative;
z-index: 1;
}
.diff-card {
padding: 16px 22px;
border-radius: 18px;
background: rgba(255,255,255,0.02);
border: 1.5px solid rgba(255,255,255,0.06);
cursor: pointer;
transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
text-align: left;
color: white;
font-weight: 600;
position: relative;
overflow: hidden;
backdrop-filter: blur(4px);
display: flex;
align-items: center;
gap: 16px;
}
.diff-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.03) 0%, transparent 60%);
pointer-events: none;
}
.diff-card::after {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--card-color, #ff6b6b);
opacity: 0;
transition: opacity 0.4s;
border-radius: 0 3px 3px 0;
}
.diff-card:hover:not(:disabled) {
transform: translateX(6px) scale(1.01);
box-shadow: 0 10px 40px rgba(0,0,0,0.3);
border-color: var(--card-color, rgba(255,255,255,0.1));
}
.diff-card:hover:not(:disabled)::after {
opacity: 1;
}
.diff-card:active:not(:disabled) {
transform: scale(0.97) translateX(3px);
}
.diff-card .diff-icon {
font-size: 28px;
flex-shrink: 0;
filter: drop-shadow(0 0 20px rgba(255,255,255,0.05));
}
.diff-card .diff-info {
flex: 1;
}
.diff-card .diff-label {
font-size: 17px;
font-weight: 700;
display: block;
letter-spacing: 0.5px;
}
.diff-card .diff-desc {
font-size: 11px;
opacity: 0.4;
font-weight: 400;
display: block;
line-height: 1.3;
}
.diff-card .diff-badge {
display: inline-block;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
padding: 2px 12px;
border-radius: 20px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.06);
opacity: 0.4;
flex-shrink: 0;
}
.diff-card.easy { --card-color: #4ecca3; }
.diff-card.easy:hover { background: rgba(78, 204, 163, 0.06); border-color: rgba(78, 204, 163, 0.3); }
.diff-card.easy .diff-badge { color: #4ecca3; border-color: rgba(78, 204, 163, 0.2); }
.diff-card.medium { --card-color: #ffd700; }
.diff-card.medium:hover { background: rgba(255, 215, 0, 0.06); border-color: rgba(255, 215, 0, 0.3); }
.diff-card.medium .diff-badge { color: #ffd700; border-color: rgba(255, 215, 0, 0.2); }
.diff-card.hard { --card-color: #ff6b6b; }
.diff-card.hard:hover { background: rgba(255, 107, 107, 0.06); border-color: rgba(255, 107, 107, 0.3); }
.diff-card.hard .diff-badge { color: #ff6b6b; border-color: rgba(255, 107, 107, 0.2); }
.diff-card.impossible { --card-color: #ff0040; }
.diff-card.impossible { background: rgba(255, 0, 64, 0.04); border-color: rgba(255, 0, 64, 0.15); }
.diff-card.impossible:hover { background: rgba(255, 0, 64, 0.1); border-color: rgba(255, 0, 64, 0.4); }
.diff-card.impossible .diff-badge { color: #ff0040; border-color: rgba(255, 0, 64, 0.2); }
.map-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
width: 100%;
position: relative;
z-index: 1;
}
.map-btn {
padding: 14px 16px;
border-radius: 16px;
background: rgba(255,255,255,0.02);
border: 1.5px solid rgba(255,255,255,0.06);
color: #888;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
letter-spacing: 0.5px;
backdrop-filter: blur(4px);
font-family: inherit;
}
.map-btn:hover {
background: rgba(255,255,255,0.04);
border-color: rgba(255,255,255,0.12);
color: #ccc;
}
.map-btn.active {
border-color: #ff6b6b;
background: rgba(255, 107, 107, 0.06);
color: white;
box-shadow: 0 0 30px rgba(255, 107, 107, 0.04);
}
.map-btn.active:hover {
background: rgba(255, 107, 107, 0.1);
}
.map-btn .map-icon {
font-size: 24px;
display: block;
margin-bottom: 4px;
}
.map-btn .map-name {
display: block;
font-size: 12px;
}
.map-btn .map-desc {
display: block;
font-size: 9px;
opacity: 0.4;
margin-top: 2px;
}
.settings-placeholder {
color: #666;
font-size: 16px;
text-align: center;
padding: 30px 20px;
letter-spacing: 2px;
opacity: 0.5;
font-weight: 300;
width: 100%;
}
.settings-placeholder .settings-icon {
font-size: 48px;
display: block;
margin-bottom: 16px;
opacity: 0.3;
}
.menu-footer {
display: none;
}
@media (max-width: 600px) {
.game-container { padding: 8px; }
h1 { font-size: 16px; }
.stat { font-size: 10px; }
.btn { padding: 4px 12px; font-size: 10px; }
.top-bar { flex-direction: column; align-items: center; }
.main-menu {
min-width: unset;
width: 92vw;
padding: 28px 16px 24px;
border-radius: 28px;
gap: 16px;
}
.main-menu h2 { font-size: 28px; letter-spacing: 5px; }
.menu-main-btn { padding: 14px 20px; font-size: 17px; }
.menu-main-btn .btn-icon { font-size: 20px; margin-right: 8px; }
.difficulty-list { min-width: unset; }
.diff-card { padding: 14px 16px; gap: 12px; }
.diff-card .diff-icon { font-size: 22px; }
.diff-card .diff-label { font-size: 15px; }
.diff-card .diff-desc { font-size: 10px; }
.diff-card .diff-badge { font-size: 8px; padding: 1px 10px; }
.map-grid { grid-template-columns: 1fr 1fr; gap: 8px; }
.map-btn { padding: 10px 12px; font-size: 11px; }
.map-btn .map-icon { font-size: 18px; }
.map-btn .map-name { font-size: 10px; }
.tutorial-slide { padding: 12px 14px 16px; }
.tutorial-slide .step-title { font-size: 16px; }
.tutorial-slide .step-desc { font-size: 13px; }
.tutorial-nav .tut-btn { padding: 4px 14px; font-size: 11px; }
}
@media (max-width: 420px) {
.diff-card .diff-icon { font-size: 18px; }
.diff-card .diff-label { font-size: 13px; }
.diff-card .diff-desc { font-size: 9px; }
.diff-card { padding: 10px 12px; gap: 8px; }
.map-grid { grid-template-columns: 1fr 1fr; gap: 6px; }
.map-btn { padding: 8px 8px; font-size: 10px; }
.map-btn .map-icon { font-size: 16px; }
}
</style>
</head>
<body>
<div class="menu-overlay active" id="menuOverlay">
<div class="main-menu active" id="mainMenu">
<div class="menu-header">
<h2>✦ HEXAGONIC ✦</h2>
<div class="menu-subtitle">Гексагональная стратегия</div>
</div>
<div class="menu-divider"></div>
<div class="menu-main-buttons" id="menuMain">
<button class="menu-main-btn play-btn" onclick="showSubMenu('play')">
<span class="btn-icon">▶</span>
<span class="btn-label">Играть</span>
</button>
<button class="menu-main-btn tutorial-btn" onclick="showSubMenu('tutorial')">
<span class="btn-icon">📖</span>
<span class="btn-label">Обучение</span>
</button>
<button class="menu-main-btn settings-btn" onclick="showSubMenu('settings')">
<span class="btn-icon">⚙</span>
<span class="btn-label">Настройки</span>
</button>
</div>
<div class="menu-sub" id="menuSubPlay">
<button class="menu-sub-back" onclick="showMainMenu()">← Назад</button>
<!-- Выбор сложности и карты -->
<div class="game-options" id="gameOptions">
<div class="menu-divider"></div>
<div class="menu-section-title">⚔️ Выберите сложность</div>
<div class="difficulty-list">
<button class="diff-card easy" onclick="startGame(1)">
<span class="diff-icon">🌱</span>
<div class="diff-info">
<span class="diff-label">Лёгкий</span>
<span class="diff-desc">ИИ редко атакует</span>
</div>
<span class="diff-badge">спокойный</span>
</button>
<button class="diff-card medium" onclick="startGame(2)">
<span class="diff-icon">⚡</span>
<div class="diff-info">
<span class="diff-label">Средний</span>
<span class="diff-desc">ИИ атакует умеренно</span>
</div>
<span class="diff-badge">сбалансированный</span>
</button>
<button class="diff-card hard" onclick="startGame(3)">
<span class="diff-icon">🔥</span>
<div class="diff-info">
<span class="diff-label">Сложный</span>
<span class="diff-desc">ИИ умный и сильный</span>
</div>
<span class="diff-badge">вызов</span>
</button>
<button class="diff-card impossible" onclick="startGame(4)">
<span class="diff-icon">💀</span>
<div class="diff-info">
<span class="diff-label">Невозможный</span>
<span class="diff-desc">Супер-агрессивный ИИ</span>
</div>
<span class="diff-badge">экстрим</span>
</button>
</div>
<div class="menu-divider"></div>
<div class="menu-section-title">🗺️ Выберите карту</div>
<div class="map-grid">
<button class="map-btn active" data-map="1" onclick="selectMap(1)">
<span class="map-icon">🏝️</span>
<span class="map-name">Острова</span>
<span class="map-desc">с водой, по углам</span>
</button>
<button class="map-btn" data-map="2" onclick="selectMap(2)">
<span class="map-icon">🌿</span>
<span class="map-name">Долина</span>
<span class="map-desc">с водой, по бокам</span>
</button>
<button class="map-btn" data-map="3" onclick="selectMap(3)">
<span class="map-icon">🏜️</span>
<span class="map-name">Пустыня</span>
<span class="map-desc">без воды, по углам</span>
</button>
<button class="map-btn" data-map="4" onclick="selectMap(4)">
<span class="map-icon">⛰️</span>
<span class="map-name">Нагорье</span>
<span class="map-desc">без воды, центр-край</span>
</button>
</div>
</div>
</div>
<!-- Отдельное подменю для обучения -->
<div class="menu-sub" id="menuSubTutorial">
<button class="menu-sub-back" onclick="showMainMenu()">← Назад</button>
<div class="tutorial-container" id="tutorialContainer2">
<div class="tutorial-slide" data-step="0">
<div class="step-badge">📖 Шаг 1 из 6</div>
<div class="step-title"><span class="emoji">🎯</span> Цель игры</div>
<div class="step-desc">
Захватите все клетки на поле! <strong>Побеждает тот</strong>, у кого в конце останется больше клеток.
<br><br>
У вас <span class="highlight-red">красные</span> клетки, у ИИ — <span class="highlight-green">зелёные</span>.
</div>
<div class="example-box" style="--ex-color: #ff6b6b;">
<div class="ex-label">💡 Совет</div>
<div class="ex-text">Стартуете с двумя клетками. Захватывайте нейтральные (серые) и вражеские клетки.</div>
</div>
</div>
<div class="tutorial-slide" data-step="1">
<div class="step-badge">📖 Шаг 2 из 6</div>
<div class="step-title"><span class="emoji">⚔️</span> Как атаковать</div>
<div class="step-desc">
<strong>Кликните</strong> по своей клетке с силой <span class="highlight-gold">> 1</span>, затем по соседней вражеской или нейтральной.
<br><br>
Сила атакующей клетки <strong>сравнивается</strong> с силой цели. Если ваша сила больше — вы захватываете клетку!
</div>
<div class="example-box" style="--ex-color: #4ecca3;">
<div class="ex-label">📊 Пример</div>
<div class="ex-text">Ваша клетка <strong>сила 5</strong> атакует клетку с <strong>силой 3</strong> → захват! Ваша клетка становится силой <strong>2</strong> (5−3).</div>
</div>
</div>
<div class="tutorial-slide" data-step="2">
<div class="step-badge">📖 Шаг 3 из 6</div>
<div class="step-title"><span class="emoji">⚡</span> Прокачка (главное!)</div>
<div class="step-desc">
В начале каждого хода вы получаете <strong>очки прокачки</strong> = количеству ваших клеток.
<br><br>
Чтобы потратить очки:
<br>1️⃣ Нажмите <span class="highlight-gold">«Пропустить»</span>
<br>2️⃣ Кликните по <strong>своей</strong> клетке, чтобы увеличить её силу на +1
<br>3️⃣ Или нажмите <span class="highlight-gold">«Распределить»</span> — очки сами пойдут в самые слабые клетки
</div>
<div class="example-box" style="--ex-color: #ffd700;">
<div class="ex-label">⭐ MAX-режим</div>
<div class="ex-text">Включите <strong>MAX</strong> — тогда один клик вкинет <strong>все</strong> очки в одну клетку (до силы 20).</div>
</div>
</div>
<div class="tutorial-slide" data-step="3">
<div class="step-badge">📖 Шаг 4 из 6</div>
<div class="step-title"><span class="emoji">🔄</span> Очерёдность ходов</div>
<div class="step-desc">
1. <span class="highlight-red">Ваш ход</span> — вы прокачиваетесь, затем атакуете.<br>
2. Нажимаете <span class="highlight-gold">«Пропустить»</span>, чтобы завершить прокачку.<br>
3. После прокачки вы можете <strong>атаковать</strong> столько раз, сколько хотите.<br>
4. Когда закончите — нажмите <span class="highlight-gold">«Пропустить»</span> ещё раз, чтобы передать ход ИИ.
</div>
<div class="example-box" style="--ex-color: #888;">
<div class="ex-label">🔄 Важно</div>
<div class="ex-text">Если у вас <strong>нет</strong> очков прокачки — ход передаётся сразу, без «Пропустить».</div>
</div>
</div>
<div class="tutorial-slide" data-step="4">
<div class="step-badge">📖 Шаг 5 из 6</div>
<div class="step-title"><span class="emoji">🤖</span> Как играет ИИ</div>
<div class="step-desc">
ИИ тоже прокачивается и атакует. Его агрессивность зависит от выбранной <strong>сложности</strong>:
<br><br>
<span style="color: #4ecca3;">🌱 Лёгкий</span> — редко атакует, прокачивается слабо.<br>
<span style="color: #ffd700;">⚡ Средний</span> — атакует умеренно, сбалансирован.<br>
<span style="color: #ff6b6b;">🔥 Сложный</span> — умный, агрессивный, сильный.<br>
<span style="color: #ff0040;">💀 Невозможный</span> — сверхагрессивный, с бонусной прокачкой.
</div>
<div class="example-box" style="--ex-color: #ff0040;">
<div class="ex-label">⚠️ Осторожно</div>
<div class="ex-text">На «Невозможном» ИИ получает <strong>+80%</strong> очков прокачки и атакует до 10 раз за ход!</div>
</div>
</div>
<div class="tutorial-slide" data-step="5">
<div class="step-badge">📖 Шаг 6 из 6</div>
<div class="step-title"><span class="emoji">🗺️</span> Карты и стратегия</div>
<div class="step-desc">
<strong>4 типа карт</strong> с уникальной генерацией:
<br><br>
🏝️ <strong>Острова</strong> — вода разбросана по карте, старт из углов.<br>
🌿 <strong>Долина</strong> — река с мостами, старт с боков.<br>
🏜️ <strong>Пустыня</strong> — стены-препятствия, старт из углов.<br>
⛰️ <strong>Нагорье</strong> — кольцевые стены, старт случайный.
</div>
<div class="example-box" style="--ex-color: #ff6b6b;">
<div class="ex-label">💡 Стратегия</div>
<div class="ex-text">Захватывайте нейтральные клетки, чтобы получить больше очков прокачки. Сильные клетки пробивают оборону ИИ!</div>
</div>
</div>
<div class="tutorial-nav">
<button class="tut-btn" id="tutPrev2" onclick="tutorialPrev2()" disabled>← Назад</button>
<div class="dots" id="tutDots2"></div>
<button class="tut-btn primary" id="tutNext2" onclick="tutorialNext2()">Далее →</button>
</div>
</div>
<div style="margin-top: 6px; color: #666; font-size: 12px; opacity:0.5;">Изучите механику перед игрой</div>
</div>
<div class="menu-sub" id="menuSubSettings">
<button class="menu-sub-back" onclick="showMainMenu()">← Назад</button>
<div class="settings-placeholder">
<span class="settings-icon">⚙</span>
Настройки игры<br>
<span style="font-size: 12px; opacity: 0.5;">(будут доступны в следующих обновлениях)</span>
</div>
</div>
</div>
</div>
<div class="game-container">
<h1>✦ HEXAGONIC ✦</h1>
<div class="top-bar">
<div class="stats">
<div class="stat"><span class="color-dot" style="background:#ff6b6b;"></span> Вы: <span id="playerScore">0</span></div>
<div class="stat"><span class="color-dot" style="background:#4ecca3;"></span> ИИ: <span id="aiScore">0</span></div>
<div class="stat">Ход: <span id="turnNum">1</span></div>
<div class="stat">⚡ <span id="upgradePoints">0</span></div>
<div class="stat">📊 <span id="usedUpgrades">0</span>/<span id="totalUpgrades">0</span></div>
</div>
<div class="controls">
<button class="btn btn-secondary" onclick="resetGame()">🔄 Новая</button>
<button class="btn btn-secondary" id="menuBtn" onclick="showMenu()">📋 Меню</button>
<button class="btn btn-green" id="distributeBtn" onclick="distributePoints()" disabled>Распределить</button>
<button class="btn btn-gold" id="maxBtn" onclick="toggleMaxMode()" disabled>MAX</button>
<button class="btn btn-outline" id="skipBtn" onclick="skipTurn()">⏭ Пропустить</button>
</div>
</div>
<div class="turn-indicator" id="turnIndicator">🔴 Ваш ход</div>
<canvas id="gameCanvas"></canvas>
<div class="message" id="message">Нажмите "Пропустить", чтобы начать прокачку</div>
</div>
<script>
// ============================================================
// HEXAGONIC — ГЕКСАГОНАЛЬНАЯ СТРАТЕГИЯ
// ============================================================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const COLS = 12;
const ROWS = 10;
const HEX_SIZE = 30;
const SPACING = HEX_SIZE * 1.75;
const W = COLS * SPACING + HEX_SIZE * 2;
const H = ROWS * SPACING + HEX_SIZE * 2;
canvas.width = W;
canvas.height = H;
const PLAYER = 0;
const AI = 1;
let grid = [];
let currentPlayer = PLAYER;
let turnNumber = 1;
let selectedCell = null;
let gameOver = false;
let aiThinking = false;
let upgradePoints = 0;
let usedUpgrades = 0;
let totalUpgrades = 0;
let currentMap = 1;
let skipPressed = false;
let canUpgradeAfterSkip = false;
let isFirstTurn = true;
let playerUpgradedThisTurn = false;
let difficulty = 2;
let gameStarted = false;
let maxModeActive = false;
let animations = [];
let animFrameId = null;
const COLORS = {
bg: '#0f1a2e',
player: '#ff6b6b',
playerGlow: 'rgba(255,107,107,0.3)',
ai: '#4ecca3',
aiGlow: 'rgba(78,204,163,0.3)',
neutral: '#3a5570',
neutralGlow: 'rgba(58,85,112,0.2)',
selected: '#ffd700',
selectedGlow: 'rgba(255,215,0,0.5)',
water: '#0a1525',
maxLevel: '#ffd700',
};
// ============================================================
// ГЕНЕРАЦИЯ КАРТ
// ============================================================
function getRandomStartPositions(type) {
const positions = {
corners: [
{ col: 2, row: 2 },
{ col: COLS-3, row: 2 },
{ col: 2, row: ROWS-3 },
{ col: COLS-3, row: ROWS-3 }
],
sides: [
{ col: 2, row: 3 },
{ col: COLS-3, row: 3 },
{ col: 2, row: ROWS-4 },
{ col: COLS-3, row: ROWS-4 }
],
diagonal: [
{ col: 2, row: 2 },
{ col: COLS-3, row: ROWS-3 },
{ col: COLS-3, row: 2 },
{ col: 2, row: ROWS-3 }
]
};
let pool;
switch(type) {
case 'corners': pool = positions.corners; break;
case 'sides': pool = positions.sides; break;
case 'diagonal': pool = positions.diagonal; break;
default: pool = positions.corners;
}
const idx1 = Math.floor(Math.random() * pool.length);
let idx2 = Math.floor(Math.random() * pool.length);
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * pool.length);
}
return {
playerStart: pool[idx1],
aiStart: pool[idx2]
};
}
function generateMapIslands() {
const map = [];
const waterDensity = 0.06 + Math.random() * 0.08;
for (let r = 0; r < ROWS; r++) {
map[r] = [];
for (let c = 0; c < COLS; c++) {
map[r][c] = true;
}
}
for (let r = 1; r < ROWS-1; r++) {
for (let c = 1; c < COLS-1; c++) {
const isNearStart = (r >= 1 && r <= 4 && c >= 1 && c <= 4) ||
(r >= ROWS-5 && r <= ROWS-2 && c >= COLS-5 && c <= COLS-2);
if (!isNearStart && Math.random() < waterDensity) {
map[r][c] = false;
}
}
}
for (let r = 4; r <= 5; r++) {
for (let c = 4; c <= 7; c++) {
map[r][c] = true;
}
}
const positions = getRandomStartPositions('corners');
return { map, ...positions };
}
function generateMapValley() {
const map = [];
const riverWidth = 1 + Math.floor(Math.random() * 2);
for (let r = 0; r < ROWS; r++) {
map[r] = [];
for (let c = 0; c < COLS; c++) {
map[r][c] = true;
}
}
for (let r = 2; r < ROWS-2; r++) {
const offset = Math.sin(r * 0.5) * 1.5;
const center = 5 + Math.floor(offset);
const w = riverWidth + Math.floor(Math.random() * 2);
for (let c = center - w; c <= center + w; c++) {
if (c > 1 && c < COLS-2) {
map[r][c] = false;
}
}
}
const numBridges = 2 + Math.floor(Math.random() * 3);
for (let i = 0; i < numBridges; i++) {
const bridgeRow = 3 + Math.floor(Math.random() * (ROWS - 6));
const bridgeCol = 4 + Math.floor(Math.random() * 4);
if (bridgeRow > 1 && bridgeRow < ROWS-2 && bridgeCol > 1 && bridgeCol < COLS-2) {
map[bridgeRow][bridgeCol] = true;
if (bridgeCol + 1 < COLS-2) map[bridgeRow][bridgeCol + 1] = true;
}
}
const positions = getRandomStartPositions('sides');
return { map, ...positions };
}
function generateMapDesert() {
const map = [];
const wallDensity = 0.08 + Math.random() * 0.10;
for (let r = 0; r < ROWS; r++) {
map[r] = [];
for (let c = 0; c < COLS; c++) {
map[r][c] = true;
}
}
const numWalls = Math.floor(ROWS * COLS * wallDensity);
for (let i = 0; i < numWalls; i++) {
const r = 1 + Math.floor(Math.random() * (ROWS - 2));
const c = 1 + Math.floor(Math.random() * (COLS - 2));
const isStartZone = (r <= 4 && c <= 4) || (r >= ROWS-5 && c >= COLS-5) ||
(r <= 4 && c >= COLS-5) || (r >= ROWS-5 && c <= 4);
if (!isStartZone) {
const clusterSize = 1 + Math.floor(Math.random() * 3);
for (let j = 0; j < clusterSize && r+j < ROWS-1 && c+j < COLS-1; j++) {
map[r+j][c+j] = false;
}
}
}
for (let r = 4; r <= 5; r++) {
for (let c = 5; c <= 6; c++) {
map[r][c] = true;
}
}
const positions = getRandomStartPositions('corners');
return { map, ...positions };
}
function generateMapHighland() {
const map = [];
for (let r = 0; r < ROWS; r++) {
map[r] = [];
for (let c = 0; c < COLS; c++) {
map[r][c] = true;
}
}
const centerR = 4 + Math.floor(Math.random() * 2);
const centerC = 5 + Math.floor(Math.random() * 2);
const radius = 2 + Math.floor(Math.random() * 2);
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const dist = Math.sqrt(Math.pow(r - centerR, 2) + Math.pow(c - centerC, 2));
if (dist > radius - 0.5 && dist < radius + 1.5) {
const angle = Math.atan2(r - centerR, c - centerC);
const isGate = Math.abs(Math.sin(angle * 2)) < 0.4 ||
Math.abs(Math.cos(angle * 1.5)) < 0.3;
if (!isGate) {
map[r][c] = false;
}
}
}
}
for (let i = 0; i < 5 + Math.floor(Math.random() * 8); i++) {
const r = 2 + Math.floor(Math.random() * (ROWS - 4));
const c = 2 + Math.floor(Math.random() * (COLS - 4));
const dist = Math.sqrt(Math.pow(r - centerR, 2) + Math.pow(c - centerC, 2));
if (dist > 1.5 && dist < 5) {
map[r][c] = false;
}
}
const startTypes = ['corners', 'sides', 'diagonal'];
const type = startTypes[Math.floor(Math.random() * startTypes.length)];
const positions = getRandomStartPositions(type);
return { map, ...positions };
}
// ============================================================
// ИНИЦИАЛИЗАЦИЯ
// ============================================================
function initGrid(mapType) {
let landMap, playerStart, aiStart;
switch(mapType) {
case 1:
const islands = generateMapIslands();
landMap = islands.map;
playerStart = islands.playerStart;
aiStart = islands.aiStart;
break;
case 2:
const valley = generateMapValley();
landMap = valley.map;
playerStart = valley.playerStart;
aiStart = valley.aiStart;
break;
case 3:
const desert = generateMapDesert();
landMap = desert.map;
playerStart = desert.playerStart;
aiStart = desert.aiStart;
break;
case 4:
const highland = generateMapHighland();
landMap = highland.map;
playerStart = highland.playerStart;
aiStart = highland.aiStart;
break;
default:
const def = generateMapIslands();
landMap = def.map;
playerStart = def.playerStart;
aiStart = def.aiStart;
}
grid = [];
for (let r = 0; r < ROWS; r++) {
grid[r] = [];
for (let c = 0; c < COLS; c++) {
if (landMap[r][c]) {
grid[r][c] = { owner: null, power: 1 };
} else {
grid[r][c] = null;
}
}
}
grid[playerStart.row][playerStart.col].owner = PLAYER;
grid[playerStart.row][playerStart.col].power = 2;
grid[aiStart.row][aiStart.col].owner = AI;
grid[aiStart.row][aiStart.col].power = 2;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === null) {
grid[r][c].power = 1;
}
}
}
}
// ============================================================
// СОСЕДИ
// ============================================================
function getNeighbors(col, row) {
const neighbors = [];
const directions = [];
if (row % 2 === 0) {
directions.push(
{ dc: -1, dr: 0 },
{ dc: 1, dr: 0 },
{ dc: 0, dr: -1 },
{ dc: 0, dr: 1 },
{ dc: -1, dr: -1 },
{ dc: -1, dr: 1 }
);
} else {
directions.push(
{ dc: -1, dr: 0 },
{ dc: 1, dr: 0 },
{ dc: 0, dr: -1 },
{ dc: 0, dr: 1 },
{ dc: 1, dr: -1 },
{ dc: 1, dr: 1 }
);
}
for (let dir of directions) {
const nc = col + dir.dc;
const nr = row + dir.dr;
if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS) {
if (grid[nr][nc] !== null) {
neighbors.push({ col: nc, row: nr });
}
}
}
return neighbors;
}
// ============================================================
// АНИМАЦИЯ
// ============================================================
function addCaptureAnimation(fromCol, fromRow, toCol, toRow, owner) {
const from = getHexCenter(fromCol, fromRow);
const to = getHexCenter(toCol, toRow);
const color = owner === PLAYER ? COLORS.player : COLORS.ai;
const glowColor = owner === PLAYER ? COLORS.playerGlow : COLORS.aiGlow;
animations.push({
fromX: from.x,
fromY: from.y,
toX: to.x,
toY: to.y,
progress: 0,
speed: 0.04 + Math.random() * 0.02,
color: color,
glowColor: glowColor,
particles: 8,
done: false
});
if (!animFrameId) {
animFrameId = requestAnimationFrame(animateLoop);
}
}
function animateLoop() {
let allDone = true;
for (let anim of animations) {
anim.progress += anim.speed;
if (anim.progress >= 1) {
anim.progress = 1;
anim.done = true;
}
allDone = allDone && anim.done;
}
draw();
if (!allDone) {
animFrameId = requestAnimationFrame(animateLoop);
} else {
animations = [];
animFrameId = null;
draw();
}
}
function drawAnimations() {
for (let anim of animations) {
if (anim.done) continue;
const cx = anim.fromX + (anim.toX - anim.fromX) * anim.progress;
const cy = anim.fromY + (anim.toY - anim.fromY) * anim.progress;
const size = 8 + 12 * anim.progress;
const alpha = 1 - anim.progress * 0.3;
const grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, size);
grad.addColorStop(0, anim.color);
grad.addColorStop(0.6, anim.color);
grad.addColorStop(1, 'rgba(255,255,255,0)');
ctx.globalAlpha = alpha * 0.8;
ctx.shadowColor = anim.glowColor;
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.arc(cx, cy, size, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
ctx.shadowBlur = 0;
for (let i = 0; i < anim.particles; i++) {
const angle = (i / anim.particles) * Math.PI * 2 + anim.progress * 3;
const dist = size * 0.5 + anim.progress * 15;
const px = cx + Math.cos(angle) * dist;
const py = cy + Math.sin(angle) * dist;
const pSize = 2 + 3 * (1 - anim.progress);
ctx.globalAlpha = alpha * 0.5 * (1 - anim.progress);
ctx.beginPath();
ctx.arc(px, py, pSize, 0, Math.PI * 2);
ctx.fillStyle = anim.color;
ctx.fill();
}
ctx.globalAlpha = 1;
if (anim.progress > 0.85) {
const flash = (anim.progress - 0.85) / 0.15;
ctx.globalAlpha = flash * 0.4;
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 40;
ctx.beginPath();
ctx.arc(anim.toX, anim.toY, 10 + flash * 20, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.shadowBlur = 0;
ctx.globalAlpha = 1;
}
}
}
// ============================================================
// ИГРОВАЯ МЕХАНИКА
// ============================================================
function growPlayerCells() {
if (playerUpgradedThisTurn) return;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === PLAYER) {
grid[r][c].power = Math.min(grid[r][c].power + 1, 20);
}
}
}
}
function executeAttack(fromCol, fromRow, toCol, toRow) {
const attacker = grid[fromRow][fromCol];
const defender = grid[toRow][toCol];
if (!attacker || !defender) return false;
if (attacker.owner === defender.owner) return false;
if (attacker.power <= 1) return false;
const neighbors = getNeighbors(fromCol, fromRow);
const isNeighbor = neighbors.some(n => n.col === toCol && n.row === toRow);
if (!isNeighbor) return false;
const diff = attacker.power - defender.power;
const attackerOwner = attacker.owner;
if (diff > 0) {
defender.owner = attacker.owner;
defender.power = Math.max(1, diff);
attacker.power = 1;
} else if (diff === 0) {
if (Math.random() < 0.5) {
defender.owner = attacker.owner;
defender.power = 1;
attacker.power = 1;
} else {
attacker.power = 1;
defender.power = 1;
}
} else {
attacker.power = 1;
defender.power = Math.max(1, Math.abs(diff));
}
attacker.power = Math.min(attacker.power, 20);
defender.power = Math.min(defender.power, 20);
if (defender.owner === attackerOwner) {
addCaptureAnimation(fromCol, fromRow, toCol, toRow, attackerOwner);
}
return true;
}
function upgradeCell(col, row) {
if (grid[row][col] === null) return false;
if (grid[row][col].owner !== currentPlayer) return false;
if (upgradePoints <= 0) return false;
if (grid[row][col].power >= 20) {
document.getElementById('message').textContent = '⚠️ Эта клетка уже имеет максимальную силу (20)!';
return false;
}
if (gameOver) return false;
if (aiThinking) return false;
if (!skipPressed || !canUpgradeAfterSkip) {
document.getElementById('message').textContent = '⚠️ Сначала нажмите "Пропустить"!';
return false;
}
let pointsToSpend = 1;
if (maxModeActive) {
const maxPossible = 20 - grid[row][col].power;
pointsToSpend = Math.min(upgradePoints, maxPossible);
}
grid[row][col].power = Math.min(grid[row][col].power + pointsToSpend, 20);
upgradePoints -= pointsToSpend;
usedUpgrades += pointsToSpend;
playerUpgradedThisTurn = true;
updateUI();
draw();
if (pointsToSpend > 1) {
document.getElementById('message').textContent = `⚡ MAX прокачка +${pointsToSpend}! Сила: ${grid[row][col].power} (осталось ${upgradePoints} очков)`;
} else {
document.getElementById('message').textContent = `⚡ Прокачка +1! Сила: ${grid[row][col].power} (осталось ${upgradePoints} очков)`;
}
if (maxModeActive && grid[row][col].power >= 20) {
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
document.getElementById('message').textContent += ' MAX режим отключён (клетка достигла 20)';
}
if (upgradePoints === 0) {
document.getElementById('distributeBtn').disabled = true;
skipPressed = false;
canUpgradeAfterSkip = false;
document.getElementById('skipBtn').disabled = true;
document.getElementById('maxBtn').disabled = true;
if (maxModeActive) {
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
}
setTimeout(() => {
if (!gameOver && currentPlayer === PLAYER && !aiThinking) {
endPlayerTurn();
}
}, 500);
}
return true;
}
// ============================================================
// РАСПРЕДЕЛЕНИЕ ОЧКОВ
// ============================================================
function distributePoints() {
if (currentPlayer !== PLAYER || gameOver || aiThinking) {
document.getElementById('message').textContent = '⏳ Нельзя сейчас!';
return;
}
if (upgradePoints <= 0) {
document.getElementById('message').textContent = '❌ Нет очков прокачки!';
return;
}
if (!skipPressed || !canUpgradeAfterSkip) {
document.getElementById('message').textContent = '⚠️ Сначала нажмите "Пропустить"!';
return;
}
if (maxModeActive) {
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
}
let playerCells = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === PLAYER) {
playerCells.push({ col: c, row: r, power: grid[r][c].power });
}
}
}
if (playerCells.length === 0) {
document.getElementById('message').textContent = '❌ Нет клеток для прокачки!';
return;
}
playerCells.sort((a, b) => a.power - b.power);
let points = upgradePoints;
let upgraded = 0;
let attempts = 0;
while (points > 0 && attempts < 100) {
attempts++;
let distributed = false;
for (let cell of playerCells) {
if (points <= 0) break;
if (grid[cell.row][cell.col] !== null && grid[cell.row][cell.col].owner === PLAYER) {
if (grid[cell.row][cell.col].power < 20) {
grid[cell.row][cell.col].power = Math.min(grid[cell.row][cell.col].power + 1, 20);
points--;
upgraded++;
usedUpgrades++;
playerUpgradedThisTurn = true;
distributed = true;
}
}
}
if (!distributed) break;
}
upgradePoints = points;
document.getElementById('message').textContent = `✅ Распределено: +${upgraded} силы (осталось ${upgradePoints} очков)`;
updateUI();
draw();
if (upgradePoints === 0) {
document.getElementById('distributeBtn').disabled = true;
skipPressed = false;
canUpgradeAfterSkip = false;
document.getElementById('skipBtn').disabled = true;
document.getElementById('maxBtn').disabled = true;
setTimeout(() => {
if (!gameOver && currentPlayer === PLAYER && !aiThinking) {
endPlayerTurn();
}
}, 500);
}
}
// ============================================================
// УПРАВЛЕНИЕ ХОДАМИ
// ============================================================
function startPlayerTurn() {
if (gameOver) return;
currentPlayer = PLAYER;
if (!isFirstTurn) {
growPlayerCells();
}
isFirstTurn = false;
playerUpgradedThisTurn = false;
let playerCount = 0;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === PLAYER) {
playerCount++;
}
}
}
totalUpgrades = playerCount;
upgradePoints = playerCount;
usedUpgrades = 0;
selectedCell = null;
skipPressed = false;
canUpgradeAfterSkip = false;
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
document.getElementById('maxBtn').disabled = true;
document.getElementById('distributeBtn').disabled = true;
document.getElementById('skipBtn').disabled = false;
document.getElementById('turnIndicator').textContent = `🔴 Ваш ход (${turnNumber})`;
document.getElementById('turnIndicator').style.color = '#ff6b6b';
document.getElementById('message').textContent = `⚡ У вас ${upgradePoints} очков прокачки. Нажмите "Пропустить", чтобы начать прокачку`;
updateUI();
draw();
}
function endPlayerTurn() {
if (gameOver) return;
skipPressed = false;
canUpgradeAfterSkip = false;
document.getElementById('distributeBtn').disabled = true;
document.getElementById('skipBtn').disabled = true;
document.getElementById('maxBtn').disabled = true;
if (maxModeActive) {
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
}
if (checkGameOver()) return;
setTimeout(() => {
startAITurn();
}, 300);
}
function startAITurn() {
if (gameOver) return;
currentPlayer = AI;
document.getElementById('turnIndicator').textContent = `🟢 Ход ИИ (${turnNumber})`;
document.getElementById('turnIndicator').style.color = '#4ecca3';
document.getElementById('skipBtn').disabled = true;
aiThinking = true;
setTimeout(() => {
aiTurn();
}, 400);
}
function endAITurn() {
if (gameOver) return;
aiThinking = false;
if (checkGameOver()) return;
turnNumber++;
setTimeout(() => {
startPlayerTurn();
}, 300);
}
// ============================================================
// КНОПКА MAX
// ============================================================
function toggleMaxMode() {
if (gameOver || aiThinking || currentPlayer !== PLAYER || !skipPressed || !canUpgradeAfterSkip) {
return;
}
if (upgradePoints <= 0) {
document.getElementById('message').textContent = '❌ Нет очков прокачки!';
return;
}
maxModeActive = !maxModeActive;
if (maxModeActive) {
document.getElementById('maxBtn').classList.add('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX ✓';
document.getElementById('message').textContent = `🔱 MAX режим включён! Кликните по клетке чтобы вкинуть все очки (до 20 силы)`;
} else {
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
document.getElementById('message').textContent = `📤 Режим прокачки! Кликайте по своим клеткам (${upgradePoints} очков) ИЛИ нажмите "Распределить"`;
}
}
// ============================================================
// КНОПКА ПРОПУСТИТЬ
// ============================================================
function skipTurn() {
if (gameOver) {
document.getElementById('message').textContent = '⏳ Игра уже окончена!';
return;
}
if (aiThinking) {
document.getElementById('message').textContent = '⏳ Подождите, ИИ думает...';
return;
}
if (currentPlayer !== PLAYER) {
document.getElementById('message').textContent = '⏳ Сейчас не ваш ход!';
return;
}
if (upgradePoints > 0) {
skipPressed = true;
canUpgradeAfterSkip = true;
document.getElementById('distributeBtn').disabled = false;
document.getElementById('maxBtn').disabled = false;
document.getElementById('message').textContent = `📤 Режим прокачки! Кликайте по своим клеткам (${upgradePoints} очков) ИЛИ нажмите "Распределить"`;
document.getElementById('skipBtn').disabled = true;
selectedCell = null;
draw();
return;
}
document.getElementById('message').textContent = '⏭ Ход пропущен (нет очков прокачки)';
endPlayerTurn();
}
// ============================================================
// ХОД ИИ
// ============================================================
function aiTurn() {
let aiCells = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === AI) {
aiCells.push({ col: c, row: r, power: grid[r][c].power });
}
}
}
let aiUpgradePoints = aiCells.length;
let pointsToDistribute = aiUpgradePoints;
if (difficulty === 1) {
pointsToDistribute = Math.floor(aiUpgradePoints * 0.4);
} else if (difficulty === 2) {
pointsToDistribute = aiUpgradePoints;
} else if (difficulty === 3) {
pointsToDistribute = aiUpgradePoints;
} else if (difficulty === 4) {
pointsToDistribute = aiUpgradePoints + Math.floor(aiUpgradePoints * 0.8);
}
let attempts = 0;
while (pointsToDistribute > 0 && attempts < 100) {
attempts++;
let distributed = false;
aiCells.sort((a, b) => a.power - b.power);
for (let cell of aiCells) {
if (pointsToDistribute <= 0) break;
if (grid[cell.row][cell.col] !== null && grid[cell.row][cell.col].owner === AI) {
if (grid[cell.row][cell.col].power < 20) {
grid[cell.row][cell.col].power++;
pointsToDistribute--;
distributed = true;
}
}
}
if (!distributed) break;
}
aiCells = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === AI) {
aiCells.push({ col: c, row: r, power: grid[r][c].power });
}
}
}
let totalAttacks = 0;
let maxAttacks = 0;
if (difficulty === 1) {
maxAttacks = 1;
} else if (difficulty === 2) {
maxAttacks = 3;
} else if (difficulty === 3) {
maxAttacks = 4;
} else if (difficulty === 4) {
maxAttacks = 10;
}
aiCells.sort((a, b) => b.power - a.power);
for (let attackRound = 0; attackRound < maxAttacks && totalAttacks < maxAttacks; attackRound++) {
let attackedThisRound = false;
for (let cell of aiCells) {
if (totalAttacks >= maxAttacks) break;
if (grid[cell.row][cell.col] === null || grid[cell.row][cell.col].owner !== AI) continue;
if (grid[cell.row][cell.col].power <= 1) continue;
if (difficulty === 1 && Math.random() > 0.35) continue;
const neighbors = getNeighbors(cell.col, cell.row);
let enemies = [];
let neutrals = [];
for (let n of neighbors) {
if (grid[n.row][n.col] !== null) {
if (grid[n.row][n.col].owner !== AI) {
enemies.push({
col: n.col,
row: n.row,
power: grid[n.row][n.col].power,
owner: grid[n.row][n.col].owner
});
} else if (grid[n.row][n.col].owner === null) {
neutrals.push({
col: n.col,
row: n.row,
power: grid[n.row][n.col].power
});
}
}
}
if (enemies.length > 0) {
enemies.sort((a, b) => a.power - b.power);
let target = null;
if (difficulty === 1) {
for (let enemy of enemies) {
const diff = cell.power - enemy.power;
if (diff > 2) {
target = enemy;
break;
}
}
} else if (difficulty === 2) {
for (let enemy of enemies) {
const diff = cell.power - enemy.power;
if (diff > -1) {
target = enemy;
break;
}
}
} else if (difficulty === 3) {
for (let enemy of enemies) {
const diff = cell.power - enemy.power;
if (diff > -2) {
target = enemy;
break;
}
}
if (!target && enemies.length > 0) {
target = enemies[0];
}
} else if (difficulty === 4) {
for (let enemy of enemies) {
const diff = cell.power - enemy.power;
if (diff > -4) {
target = enemy;
break;
}
}
if (!target && enemies.length > 0) {
target = enemies[0];
}
}
if (target) {
const result = executeAttack(cell.col, cell.row, target.col, target.row);
if (result) {
totalAttacks++;
attackedThisRound = true;
document.getElementById('message').textContent = `💥 ИИ атакует! (${totalAttacks}/${maxAttacks})`;
if (grid[cell.row][cell.col] !== null) {
cell.power = grid[cell.row][cell.col].power;
}
continue;
}
}
}
if (!attackedThisRound && difficulty >= 2 && neutrals.length > 0) {
for (let neutral of neutrals) {
if (totalAttacks >= maxAttacks) break;
if (grid[neutral.row][neutral.col] !== null && grid[neutral.row][neutral.col].owner === null) {
if (cell.power > 1) {
grid[neutral.row][neutral.col].owner = AI;
grid[neutral.row][neutral.col].power = Math.min(cell.power - 1, 20);
cell.power = 1;
totalAttacks++;
attackedThisRound = true;
document.getElementById('message').textContent = `🌱 ИИ захватывает клетку! (${totalAttacks}/${maxAttacks})`;
addCaptureAnimation(cell.col, cell.row, neutral.col, neutral.row, AI);
break;
}
}
}
}
}
if (!attackedThisRound) break;
aiCells = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === AI) {
aiCells.push({ col: c, row: r, power: grid[r][c].power });
}
}
}
aiCells.sort((a, b) => b.power - a.power);
}
if (difficulty === 4 && totalAttacks < maxAttacks) {
aiCells = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null && grid[r][c].owner === AI) {
aiCells.push({ col: c, row: r, power: grid[r][c].power });
}
}
}
aiCells.sort((a, b) => b.power - a.power);
for (let cell of aiCells) {
if (totalAttacks >= maxAttacks) break;
if (grid[cell.row][cell.col] === null || grid[cell.row][cell.col].owner !== AI) continue;
if (grid[cell.row][cell.col].power <= 2) continue;
const neighbors = getNeighbors(cell.col, cell.row);
for (let n of neighbors) {
if (totalAttacks >= maxAttacks) break;
if (grid[n.row][n.col] !== null && grid[n.row][n.col].owner !== AI) {
const diff = cell.power - grid[n.row][n.col].power;
if (diff > -3) {
const result = executeAttack(cell.col, cell.row, n.col, n.row);
if (result) {
totalAttacks++;
document.getElementById('message').textContent = `💥 ИИ продолжает атаку! (${totalAttacks}/${maxAttacks})`;
if (grid[cell.row][cell.col] !== null) {
cell.power = grid[cell.row][cell.col].power;
}
}
}
}
}
}
}
aiThinking = false;
if (totalAttacks === 0) {
document.getElementById('message').textContent = '🤖 ИИ пропускает атаку';
} else {
document.getElementById('message').textContent = `🤖 ИИ совершил ${totalAttacks} атак!`;
}
if (!gameOver) {
endAITurn();
}
}
// ============================================================
// ПРОВЕРКА ОКОНЧАНИЯ
// ============================================================
function checkGameOver() {
let playerCount = 0;
let aiCount = 0;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null) {
if (grid[r][c].owner === PLAYER) playerCount++;
else if (grid[r][c].owner === AI) aiCount++;
}
}
}
if (playerCount === 0 || aiCount === 0) {
gameOver = true;
let winner = '';
if (playerCount > aiCount) winner = '🏆 Вы победили!';
else if (aiCount > playerCount) winner = '💻 ИИ победил!';
else winner = '🤝 Ничья!';
document.getElementById('message').textContent = `Игра окончена! ${winner}`;
document.getElementById('turnIndicator').textContent = 'Игра завершена';
document.getElementById('distributeBtn').disabled = true;
document.getElementById('skipBtn').disabled = true;
document.getElementById('maxBtn').disabled = true;
if (maxModeActive) {
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
}
updateUI();
draw();
return true;
}
return false;
}
// ============================================================
// UI
// ============================================================
function updateUI() {
let playerCount = 0;
let aiCount = 0;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] !== null) {
if (grid[r][c].owner === PLAYER) playerCount++;
else if (grid[r][c].owner === AI) aiCount++;
}
}
}
document.getElementById('playerScore').textContent = playerCount;
document.getElementById('aiScore').textContent = aiCount;
document.getElementById('turnNum').textContent = turnNumber;
document.getElementById('upgradePoints').textContent = upgradePoints;
document.getElementById('usedUpgrades').textContent = usedUpgrades;
document.getElementById('totalUpgrades').textContent = totalUpgrades;
}
// ============================================================
// ОТРИСОВКА
// ============================================================
function draw() {
ctx.clearRect(0, 0, W, H);
const gradient = ctx.createRadialGradient(W/2, H/2, 0, W/2, H/2, W/2);
gradient.addColorStop(0, '#0f1a2e');
gradient.addColorStop(1, '#060a15');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, W, H);
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === null) {
const hex = getHexCenter(c, r);
drawHex(ctx, hex.x, hex.y, HEX_SIZE - 2, COLORS.water, null, false);
continue;
}
const hex = getHexCenter(c, r);
const cell = grid[r][c];
const isSelected = selectedCell && selectedCell.col === c && selectedCell.row === r;
const isMaxLevel = cell.power >= 20;
let color = COLORS.neutral;
let glow = COLORS.neutralGlow;
let powerColor = '#8aa';
let borderColor = 'rgba(255,255,255,0.05)';
if (cell.owner === PLAYER) {
if (isMaxLevel) {
color = '#8B0000';
glow = 'rgba(255,215,0,0.5)';
powerColor = '#ffd700';
borderColor = 'rgba(255,215,0,0.4)';
} else {
color = COLORS.player;
glow = COLORS.playerGlow;
powerColor = '#fff';
borderColor = 'rgba(255,107,107,0.25)';
}
} else if (cell.owner === AI) {
if (isMaxLevel) {
color = '#006400';
glow = 'rgba(255,215,0,0.5)';
powerColor = '#ffd700';
borderColor = 'rgba(255,215,0,0.4)';
} else {
color = COLORS.ai;
glow = COLORS.aiGlow;
powerColor = '#fff';
borderColor = 'rgba(78,204,163,0.25)';
}
}
ctx.shadowColor = isSelected ? COLORS.selectedGlow : glow;
ctx.shadowBlur = isSelected ? 30 : 16;
const grad = ctx.createRadialGradient(
hex.x - HEX_SIZE * 0.2, hex.y - HEX_SIZE * 0.2, 2,
hex.x, hex.y, HEX_SIZE * 0.9
);
grad.addColorStop(0, lightenColor(color, 30));
grad.addColorStop(1, color);
drawHex(ctx, hex.x, hex.y, HEX_SIZE - 2, grad, isSelected ? COLORS.selected : borderColor, false, true);
ctx.shadowBlur = 0;
const innerGlow = ctx.createRadialGradient(
hex.x - HEX_SIZE * 0.15, hex.y - HEX_SIZE * 0.15, 0,
hex.x, hex.y, HEX_SIZE * 0.5
);
innerGlow.addColorStop(0, 'rgba(255,255,255,0.08)');
innerGlow.addColorStop(1, 'rgba(255,255,255,0)');
drawHex(ctx, hex.x, hex.y, HEX_SIZE - 4, innerGlow, null, false);
ctx.fillStyle = powerColor;
ctx.font = 'bold 16px "Segoe UI", Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 8;
ctx.fillText(cell.power, hex.x + 1, hex.y + 1);
if (isMaxLevel) {
ctx.shadowBlur = 0;
ctx.strokeStyle = COLORS.maxLevel;
ctx.lineWidth = 2.5;
ctx.shadowColor = 'rgba(255,215,0,0.4)';
ctx.shadowBlur = 12;
drawHex(ctx, hex.x, hex.y, HEX_SIZE - 2, null, COLORS.maxLevel, false);
ctx.shadowBlur = 0;
}
if (isSelected) {
ctx.shadowBlur = 0;
ctx.strokeStyle = 'rgba(255,215,0,0.6)';
ctx.lineWidth = 2;
ctx.setLineDash([4, 4]);
drawHex(ctx, hex.x, hex.y, HEX_SIZE - 2, null, 'rgba(255,215,0,0.4)', false);
ctx.setLineDash([]);
}
}
}
if (selectedCell) {
const sel = selectedCell;
if (grid[sel.row][sel.col] && grid[sel.row][sel.col].owner === PLAYER && grid[sel.row][sel.col].power > 1) {
const neighbors = getNeighbors(sel.col, sel.row);
for (let n of neighbors) {
if (grid[n.row][n.col] !== null && grid[n.row][n.col].owner !== PLAYER) {
const pos = getHexCenter(n.col, n.row);
ctx.beginPath();
ctx.arc(pos.x, pos.y, 6, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255,68,68,0.25)';
ctx.fill();
ctx.strokeStyle = 'rgba(255,68,68,0.5)';
ctx.lineWidth = 2;
ctx.stroke();
}
}
}
}
drawAnimations();
}
function lightenColor(hex, percent) {
const num = parseInt(hex.replace('#', ''), 16);
const amt = Math.round(2.55 * percent);
const R = Math.min(255, (num >> 16) + amt);
const G = Math.min(255, ((num >> 8) & 0x00FF) + amt);
const B = Math.min(255, (num & 0x0000FF) + amt);
return `#${(1 << 24 | R << 16 | G << 8 | B).toString(16).slice(1)}`;
}
function getHexCenter(col, row) {
const x = HEX_SIZE + col * SPACING + (row % 2 === 0 ? 0 : SPACING / 2);
const y = HEX_SIZE + row * (HEX_SIZE * 1.5);
return { x, y };
}
function drawHex(ctx, x, y, size, fill, stroke, borderOnly = false, hasGradient = false) {
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = Math.PI / 180 * (60 * i - 30);
const px = x + size * Math.cos(angle);
const py = y + size * Math.sin(angle);
if (i === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.closePath();
if (!borderOnly) {
if (fill) {
ctx.fillStyle = fill;
ctx.fill();
}
if (stroke) {
ctx.strokeStyle = stroke;
ctx.lineWidth = 1.8;
ctx.stroke();
}
} else {
ctx.strokeStyle = 'rgba(255,255,255,0.04)';
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
// ============================================================
// ОБРАБОТКА КЛИКОВ
// ============================================================
canvas.addEventListener('click', handleClick);
canvas.addEventListener('touchstart', handleTouch);
function handleClick(e) {
if (!gameStarted) {
document.getElementById('message').textContent = '⏳ Сначала начните игру через меню!';
return;
}
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const mouseX = (e.clientX - rect.left) * scaleX;
const mouseY = (e.clientY - rect.top) * scaleY;
handleCellClick(mouseX, mouseY);
}
function handleTouch(e) {
if (!gameStarted) {
document.getElementById('message').textContent = '⏳ Сначала начните игру через меню!';
return;
}
e.preventDefault();
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const mouseX = (touch.clientX - rect.left) * scaleX;
const mouseY = (touch.clientY - rect.top) * scaleY;
handleCellClick(mouseX, mouseY);
}
function handleCellClick(mouseX, mouseY) {
let bestDist = Infinity;
let bestCell = null;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c] === null) continue;
const pos = getHexCenter(c, r);
const dist = Math.hypot(mouseX - pos.x, mouseY - pos.y);
if (dist < bestDist && dist < HEX_SIZE * 1.1) {
bestDist = dist;
bestCell = { col: c, row: r };
}
}
}
if (!bestCell) {
document.getElementById('message').textContent = '❌ Кликните по клетке!';
return;
}
const { col, row } = bestCell;
if (gameOver) {
document.getElementById('message').textContent = '⏳ Игра окончена!';
return;
}
if (aiThinking) {
document.getElementById('message').textContent = '⏳ Подождите, ИИ думает...';
return;
}
if (currentPlayer !== PLAYER) {
document.getElementById('message').textContent = '⏳ Сейчас не ваш ход!';
return;
}
const cell = grid[row][col];
if (cell === null) {
document.getElementById('message').textContent = '❌ Это вода!';
return;
}
if (skipPressed && canUpgradeAfterSkip) {
if (cell.owner === PLAYER) {
if (cell.power >= 20) {
document.getElementById('message').textContent = '⚠️ Эта клетка уже имеет максимальную силу (20)!';
return;
}
if (upgradePoints > 0) {
upgradeCell(col, row);
return;
} else {
document.getElementById('message').textContent = '❌ Нет очков прокачки!';
return;
}
} else {
document.getElementById('message').textContent = '⚠️ Сейчас можно только прокачивать свои клетки!';
return;
}
}
if (selectedCell) {
const sel = selectedCell;
const neighbors = getNeighbors(sel.col, sel.row);
const isNeighbor = neighbors.some(n => n.col === col && n.row === row);
if (isNeighbor && grid[sel.row][sel.col] !== null && grid[sel.row][sel.col].owner === PLAYER) {
if (cell.owner !== PLAYER && grid[sel.row][sel.col].power > 1) {
const result = executeAttack(sel.col, sel.row, col, row);
if (result) {
selectedCell = null;
document.getElementById('message').textContent = '💥 Атака выполнена!';
updateUI();
draw();
return;
}
} else if (cell.owner === PLAYER) {
selectedCell = { col, row };
draw();
return;
} else if (grid[sel.row][sel.col].power <= 1) {
document.getElementById('message').textContent = '⚠️ Нельзя атаковать с силой 1!';
selectedCell = null;
draw();
return;
}
}
if (cell.owner === PLAYER) {
selectedCell = { col, row };
draw();
return;
}
selectedCell = null;
draw();
return;
}
if (cell.owner === PLAYER && cell.power > 1) {
selectedCell = { col, row };
draw();
return;
} else if (cell.owner === PLAYER && cell.power <= 1) {
document.getElementById('message').textContent = '⚠️ Нельзя атаковать с силой 1! Нажмите "Пропустить", чтобы прокачаться';
return;
}
if (cell.owner !== PLAYER) {
const neighbors = getNeighbors(col, row);
let attackFrom = null;
for (let n of neighbors) {
if (grid[n.row][n.col] !== null && grid[n.row][n.col].owner === PLAYER && grid[n.row][n.col].power > 1) {
attackFrom = n;
break;
}
}
if (attackFrom) {
const result = executeAttack(attackFrom.col, attackFrom.row, col, row);
if (result) {
document.getElementById('message').textContent = '💥 Атака выполнена!';
updateUI();
draw();
return;
}
} else {
document.getElementById('message').textContent = '❌ Нет своей клетки с силой > 1 рядом!';
}
}
}
// ============================================================
// ОБУЧЕНИЕ (TUTORIAL) — только отдельная версия
// ============================================================
let tutorialStep2 = 0;
const totalTutorialSteps = 6;
function initTutorialDots() {
const container = document.getElementById('tutDots2');
if (!container) return;
container.innerHTML = '';
for (let i = 0; i < totalTutorialSteps; i++) {
const dot = document.createElement('span');
dot.className = 'dot' + (i === 0 ? ' active' : '');
dot.dataset.index = i;
dot.onclick = function() {
tutorialStep2 = i;
updateTutorialUI();
};
container.appendChild(dot);
}
}
function updateTutorialUI() {
const container = document.getElementById('tutorialContainer2');
if (!container) return;
// Находим все слайды внутри контейнера
const slides = container.querySelectorAll('.tutorial-slide');
slides.forEach((slide, i) => {
slide.classList.toggle('active', i === tutorialStep2);
});
// Обновляем точки
const dots = document.getElementById('tutDots2');
if (dots) {
const dotElements = dots.querySelectorAll('.dot');
dotElements.forEach((dot, i) => {
dot.classList.toggle('active', i === tutorialStep2);
});
}
// Обновляем кнопки
const prevBtn = document.getElementById('tutPrev2');
const nextBtn = document.getElementById('tutNext2');
if (prevBtn) prevBtn.disabled = tutorialStep2 === 0;
if (nextBtn) {
nextBtn.textContent = tutorialStep2 === totalTutorialSteps - 1 ? '✅ Завершить' : 'Далее →';
}
}
function tutorialNext2() {
if (tutorialStep2 < totalTutorialSteps - 1) {
tutorialStep2++;
updateTutorialUI();
} else {
// Завершаем обучение
document.getElementById('message').textContent = '📖 Обучение завершено! Теперь выберите сложность и карту в меню "Играть".';
showMainMenu();
}
}
function tutorialPrev2() {
if (tutorialStep2 > 0) {
tutorialStep2--;
updateTutorialUI();
}
}
// ============================================================
// НАВИГАЦИЯ ПО МЕНЮ
// ============================================================
function showMenu() {
document.getElementById('menuOverlay').classList.add('active');
document.getElementById('mainMenu').classList.add('active');
showMainMenu();
document.querySelectorAll('.map-btn').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.map) === currentMap);
});
}
function showMainMenu() {
document.getElementById('menuMain').style.display = 'flex';
document.getElementById('menuSubPlay').classList.remove('active');
document.getElementById('menuSubPlay').style.display = 'none';
document.getElementById('menuSubTutorial').classList.remove('active');
document.getElementById('menuSubTutorial').style.display = 'none';
document.getElementById('menuSubSettings').classList.remove('active');
document.getElementById('menuSubSettings').style.display = 'none';
// Сбрасываем обучение
tutorialStep2 = 0;
updateTutorialUI();
}
function showSubMenu(type) {
document.getElementById('menuMain').style.display = 'none';
if (type === 'play') {
const sub = document.getElementById('menuSubPlay');
sub.style.display = 'flex';
setTimeout(() => sub.classList.add('active'), 10);
document.querySelectorAll('.map-btn').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.map) === currentMap);
});
document.getElementById('message').textContent = '🎮 Выберите сложность и карту, чтобы начать!';
} else if (type === 'tutorial') {
const sub = document.getElementById('menuSubTutorial');
sub.style.display = 'flex';
setTimeout(() => sub.classList.add('active'), 10);
tutorialStep2 = 0;
initTutorialDots();
updateTutorialUI();
document.getElementById('message').textContent = '📖 Обучение — изучайте механику игры';
} else if (type === 'settings') {
const sub = document.getElementById('menuSubSettings');
sub.style.display = 'flex';
setTimeout(() => sub.classList.add('active'), 10);
}
}
function selectMap(mapType) {
currentMap = mapType;
document.querySelectorAll('.map-btn').forEach(btn => {
btn.classList.toggle('active', parseInt(btn.dataset.map) === mapType);
});
}
function startGame(diff) {
difficulty = diff;
document.getElementById('menuOverlay').classList.remove('active');
document.getElementById('mainMenu').classList.remove('active');
gameStarted = true;
resetGame();
const diffNames = ['', '🌱 Лёгкий', '⚡ Средний', '🔥 Сложный', '💀 Невозможный'];
const mapNames = ['', 'Острова', 'Долина', 'Пустыня', 'Нагорье'];
document.getElementById('message').textContent = `🎮 Игра начата! Сложность: ${diffNames[diff]}, Карта: ${mapNames[currentMap]}`;
}
// ============================================================
// СБРОС
// ============================================================
function resetGame() {
if (animFrameId) {
cancelAnimationFrame(animFrameId);
animFrameId = null;
}
animations = [];
initGrid(currentMap);
currentPlayer = PLAYER;
turnNumber = 1;
selectedCell = null;
gameOver = false;
aiThinking = false;
skipPressed = false;
canUpgradeAfterSkip = false;
usedUpgrades = 0;
isFirstTurn = true;
playerUpgradedThisTurn = false;
maxModeActive = false;
document.getElementById('maxBtn').classList.remove('btn-gold');
document.getElementById('maxBtn').textContent = 'MAX';
document.getElementById('maxBtn').disabled = true;
document.getElementById('distributeBtn').disabled = true;
document.getElementById('skipBtn').disabled = false;
document.getElementById('message').textContent = '🔄 Новая игра!';
document.getElementById('turnIndicator').textContent = '🔴 Ваш ход';
document.getElementById('turnIndicator').style.color = '#ff6b6b';
updateUI();
draw();
setTimeout(() => {
startPlayerTurn();
}, 200);
}
// ============================================================
// ЗАПУСК
// ============================================================
// Инициализируем сетку для отображения в меню
initGrid(1);
updateUI();
draw();
// Меню открыто по умолчанию
document.getElementById('menuOverlay').classList.add('active');
document.getElementById('mainMenu').classList.add('active');
showMainMenu();
document.querySelector('.map-btn[data-map="1"]').classList.add('active');
// Инициализируем обучение
initTutorialDots();
updateTutorialUI();
document.getElementById('message').textContent = '📖 Добро пожаловать!';
console.log('✦ HEXAGONIC — 4 УРОВНЯ СЛОЖНОСТИ, 4 КАРТЫ С РАЗНООБРАЗИЕМ');
console.log('📖 Обучение вынесено в отдельную кнопку.');
console.log('🎮 Игра начинается только после выбора сложности и карты в меню.');
</script>
</body>
</html>Game Source: Hexagonic
Creator: NeonFlare13
Libraries: none
Complexity: complex (2536 lines, 100.2 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: hexagonic-neonflare13" to link back to the original. Then publish at arcadelab.ai/publish.