Initial commit

This commit is contained in:
root 2026-02-21 19:28:29 +00:00
commit b37727ae66
25 changed files with 29414 additions and 0 deletions

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM nginx:alpine
# Копируем файлы игры в директорию nginx
COPY index.html /usr/share/nginx/html/index.html
COPY style.css /usr/share/nginx/html/style.css
COPY game.js /usr/share/nginx/html/game.js
# Используем конфигурацию nginx по умолчанию
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

2689
Minegrechka.html Normal file

File diff suppressed because it is too large Load Diff

2623
Minegrechka_Telegram.html Normal file

File diff suppressed because it is too large Load Diff

215
README.md Normal file
View File

@ -0,0 +1,215 @@
# GrechkaCraft: Multiplayer
2D песочница в стиле Minecraft с мультиплеером. Исследуйте, стройте, добывайте ресурсы и сражайтесь с мобами!
## 🎮 Управление
### Клавиатура (ПК)
- **A / ←** - движение влево
- **D / →** - движение вправо
- **W / ↑ / Пробел** - прыжок
- **S / ↓** - спуск вниз (на лестницах)
- **ЛКМ** - взаимодействие (добыча/строительство/атака)
### Сенсорное управление (мобильные устройства)
На мобильных устройствах (экраны шириной до 768px) отображается панель управления внизу экрана:
- **⬅️** - движение влево
- **⬆️** - прыжок
- **⬇️** - спуск вниз
- **➡️** - движение вправо
## 🎯 Режимы игры
Переключайте режимы кнопкой **🏃** в правом верхнем углу:
1. **🏃 Движение** - стандартный режим для передвижения
2. **⛏️ Добыча** - ломайте блоки и атакуйте мобов
3. **🧱 Строительство** - ставьте блоки
## 📦 Инвентарь и крафт
### Инвентарь
- Откройте инвентарь кнопкой **📦** в правом верхнем углу
- Выберите предмет для использования
- Инвентарь отображается поверх кнопок управления на мобильных устройствах
### Крафт
- Откройте панель крафта кнопкой **🔨** в правом верхнем углу
- Выбирайте рецепты из списка
- Кнопка "Создать" активна только если достаточно ресурсов
## 🧱 Блоки
### Базовые блоки
- **Трава** - основной блок поверхности
- **Грязь** - подповерхностный слой
- **Камень** - прочный блок для строительства
- **Песок** - падает при отсутствии опоры
- **Гравий** - падает при отсутствии опоры
- **Глина** - добывается в песчаных берегах
### Деревянные блоки
- **Дерево** - добывается из деревьев
- **Доски** - создаются из дерева
- **Лестница** - позволяет подниматься и спускаться
### Руды
- **Уголь** - топливо для костров
- **Медь** - редкая руда
- **Железо** - средняя редкость
- **Золото** - редкая руда
- **Алмаз** - очень редкая руда
### Декоративные и специальные блоки
- **Листва** - декоративный блок
- **Стекло** - прозрачный блок
- **Вода** - жидкость, можно плавать
- **Кирпич** - прочный строительный блок
- **Цветок** - декоративный элемент
- **Факел** - освещение ночью
- **Костёр** - освещение + жарка мяса
- **TNT** - взрывчатка
- **Кровать** - позволяет спать и пропускать ночь
- **Лодка** - для передвижения по воде
## 🍖 Предметы и инструменты
### Еда
- **Сырое мясо** - добывается из животных, восстанавливает 15 голода и 15 HP
- **Жареное мясо** - жарится на костре, восстанавливает 45 голода и 15 HP
### Инструменты
- **Деревянная кирка** - 60 прочности, mining power 1
- **Каменная кирка** - 130 прочности, mining power 2
- **Железная кирка** - 250 прочности, mining power 3
- **Деревянный меч** - 40 прочности, 5 урона
- **Каменный меч** - 100 прочности, 8 урона
- **Железный меч** - 200 прочности, 12 урона
## 👾 Мобы
### Дружественные (днём)
- **Свинья** - даёт 2 сырого мяса
- **Курица** - даёт 1 сырое мясо
### Враждебные (ночью)
- **Зомби** - атакует игрока, наносит 15 урона
- **Крипер** - взрывается рядом с игроком
- **Скелет** - стреляет стрелами издалека
## 🌍 Мир
### Генерация
- Детерминированная генерация на основе seed
- Автоматическая генерация при движении
- Разнообразные биомы: горы, равнины, пляжи
### День и ночь
- Автоматический цикл дня и ночи
- Ночью появляются враждебные мобы
- Нажмите на часы в UI для переключения на ночь
- Спите на кровати для пропуска ночи
### Физика
- Гравитация и коллизии
- Плавание в воде
- Лестницы для вертикального движения
- Урон от падения
## 💾 Сохранение
### Автосохранение
- Игра сохраняется автоматически при скрытии страницы
- Игра сохраняется перед закрытием страницы
- Игра сохраняется при отходе ко сну
### Ручное сохранение
- Кнопка **💾** в правом верхнем углу (только в одиночном режиме)
- Сохраняется в localStorage браузера
### Сброс игры
- Кнопка **🔄** удаляет сохранение и начинает новую игру
- Генерируется новый worldId
## 🌐 Мультиплеер
### Подключение
- Автоматическое подключение к серверу при запуске
- World ID отображается в левом верхнем углу
- Кликните на World ID для копирования ссылки
### Совместная игра
- Видите других игроков в реальном времени
- Общие изменения блоков
- Общий день/ночь
- Чат (временно скрыт)
### Синхронизация
- Позиции игроков синхронизируются 20 раз в секунду
- Изменения блоков мгновенно передаются всем игрокам
## 🛠️ Развертывание
### Локальный запуск
```bash
# Клонирование репозитория
git clone <repository-url>
cd grechka-game
# Запуск через Docker Compose
docker-compose up -d
# Игра доступна на порту 80
```
### Docker
```bash
# Сборка образа
docker build -t grechka-game .
# Запуск контейнера
docker run -p 80:80 grechka-game
```
### Структура файлов
- `index.html` - основной HTML файл
- `game.js` - игровой движок
- `style.css` - стили интерфейса
- `Dockerfile` - конфигурация Docker образа
- `docker-compose.yml` - конфигурация Docker Compose
## 📝 Недавние изменения
### Версия 1.3 (2026-01-03)
- ✅ Добавлена кнопка "вниз" для мобильных устройств
- ✅ Исправлен z-index для инвентаря и крафта (теперь поверх кнопок управления)
- ✅ Скрыт чат из интерфейса
- ✅ Добавлены проверки на null для предотвращения крашей JavaScript
### Предыдущие версии
- Мультиплеер через Socket.IO
- Система сохранения в localStorage
- Автоматический цикл дня и ночи
- Система крафта и инструментов
- Разнообразные мобы и блоки
## 🐛 Известные проблемы
- Чат временно скрыт
- На мобильных устройствах кнопки управления могут перекрывать hotbar
## 📄 Лицензия
Проект распространяется как есть.
## 🤝 Участие
Для внесения изменений:
1. Форкните репозиторий
2. Создайте ветку для новой функции
3. Внесите изменения
4. Отправьте pull request
## 📞 Контакты
По вопросам и предложениям обращайтесь к разработчику.

View File

@ -0,0 +1,11 @@
FROM nginx:alpine
# Копируем файлы игры в директорию nginx
COPY index.html /usr/share/nginx/html/index.html
COPY style.css /usr/share/nginx/html/style.css
COPY game.js /usr/share/nginx/html/game.js
# Используем конфигурацию nginx по умолчанию
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
FROM nginx:alpine
# Копируем файлы игры в директорию nginx
COPY index.html /usr/share/nginx/html/index.html
COPY style.css /usr/share/nginx/html/style.css
COPY game.js /usr/share/nginx/html/game.js
# Используем конфигурацию nginx по умолчанию
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
name: grechka-game
services:
grechka-game:
build:
context: .
dockerfile: Dockerfile
container_name: grechka-game
restart: unless-stopped
networks:
- shared_network
labels:
# Включаем Traefik для этого сервиса
- "traefik.enable=true"
- "traefik.docker.network=shared_network"
# HTTPS роутер
- "traefik.http.routers.grechka-game.entrypoints=websecure"
- "traefik.http.routers.grechka-game.rule=Host(`grechka.mkn8n.ru`)"
- "traefik.http.routers.grechka-game.tls=true"
- "traefik.http.routers.grechka-game.tls.certresolver=mytlschallenge"
# Сервис
- "traefik.http.services.grechka-game.loadbalancer.server.port=80"
networks:
shared_network:
external: true

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>GrechkaCraft: Multiplayer</title>
<!-- Socket.io Client -->
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game">
<canvas id="c"></canvas>
<div class="ui">
<div id="stats">
<div class="row">❤️ <span id="hp">100</span> &nbsp; 🍗 <span id="food">100</span></div>
<div class="row">🫁 <span id="o2">100</span></div>
<div class="row">📍 X:<span id="sx">0</span> Y:<span id="sy">0</span></div>
<div class="row">🕒 <span id="tod">День</span></div>
<div class="row">🌐 <span id="worldId" style="cursor:pointer; text-decoration:underline;" title="Нажмите, чтобы скопировать ссылку">default</span></div>
<div class="row" id="multiplayerStatus" style="display:none;">👥 <span id="playerCount">0</span></div>
</div>
<div id="modeBtn" class="rbtn pe">🏃</div>
<div id="saveBtn" class="rbtn pe">💾</div>
<div id="craftBtn" class="rbtn pe">🔨</div>
<div id="resetBtn" class="rbtn pe">🔄</div>
<div id="craftPanel">
<div class="top">
<div style="font-weight:900;font-size:18px;">Крафт</div>
<button class="close" id="craftClose">Закрыть</button>
</div>
<div id="recipes"></div>
</div>
<div id="inventoryPanel">
<div class="top">
<div style="font-weight:900;font-size:18px;">Инвентарь</div>
<button class="close" id="inventoryClose">Закрыть</button>
</div>
<div id="inventoryGrid"></div>
</div>
<div id="death">
<div style="font-size:22px;font-weight:900;">ВЫ ПОГИБЛИ</div>
<button id="respawnBtn">ВОЗРОДИТЬСЯ</button>
</div>
<div id="hotbar" class="pe"></div>
<div id="invToggle" class="rbtn pe" style="bottom:10px; left:10px; background:#3498db;">🎒</div>
</div>
<!-- Chat UI -->
<div id="chatPanel" style="display:none; position:absolute; left:10px; right:10px; top:60px; bottom:160px; background:rgba(0,0,0,0.85); border:2px solid rgba(255,255,255,0.8); border-radius:12px; pointer-events:auto; z-index:50; padding:10px; overflow:hidden;">
<div style="display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px;">
<div style="font-weight:900;font-size:18px;">💬 Чат</div>
<button id="chatClose" style="background:#c0392b; border:none; color:#fff; font-weight:900; padding:6px 10px; border-radius:8px; cursor:pointer;"></button>
</div>
<div id="chatMessages" style="flex:1; overflow-y:auto; color:#fff; font-size:13px; margin-bottom:8px; max-height:calc(100% - 40px);"></div>
<div style="display:flex; gap:6px;">
<input id="chatInput" type="text" placeholder="Введите сообщение..." style="flex:1; padding:8px; border:2px solid rgba(255,255,255,0.5); border-radius:8px; background:rgba(255,255,255,0.1); color:#fff; font-size:13px;" />
<button id="chatSend" style="background:#2ecc71; border:none; color:#fff; font-weight:900; padding:8px 16px; border-radius:8px; cursor:pointer;"></button>
</div>
</div>
<div id="chatToggle" class="rbtn pe">💬</div>
</div>
<div id="controls">
<div id="left" class="cbtn"></div>
<div id="right" class="cbtn"></div>
<div id="jump" class="cbtn"></div>
</div>
<script src="game.js"></script>
</body>
</html>

View File

@ -0,0 +1,72 @@
/* Minegrechka Game Styles - v1.2 */
html, body { margin:0; padding:0; width:100%; height:100%; overflow:hidden; background:#111; font-family: system-ui, sans-serif; user-select:none; -webkit-user-select:none; touch-action:none; }
#game { position:absolute; top:0; left:0; right:0; bottom:140px; background:#87CEEB; overflow:hidden; }
canvas { display:block; width:100%; height:100%; image-rendering:pixelated; }
#controls { position:absolute; left:0; right:0; bottom:0; height:140px; background:#222; border-top:4px solid #444; z-index:10; }
.ui { position:absolute; inset:0; pointer-events:none; z-index:20; }
.pe { pointer-events:auto; }
#stats { position:absolute; left:10px; top:10px; color:#fff; font-weight:800; font-size:14px;
background: rgba(0,0,0,0.55); padding:8px; border-radius:10px; text-shadow:1px 1px 0 #000; }
#stats .row{ display:flex; gap:10px; align-items:center; }
.rbtn { position:absolute; right:10px; width:52px; height:52px; border-radius:12px;
display:flex; align-items:center; justify-content:center; border:2px solid rgba(255,255,255,0.9);
font-size:24px; cursor:pointer; pointer-events:auto; box-shadow:0 4px 0 rgba(0,0,0,0.5); }
.rbtn:active { transform: translateY(4px); box-shadow:none; }
#modeBtn { top:10px; background:#f39c12; }
#saveBtn { top:10px; right:70px !important; background:#27ae60; }
#resetBtn { top:10px; right:130px !important; background:#e74c3c; }
#craftBtn { top:74px; right:10px !important; background:#9b59b6; }
#chatToggle { top:10px; right:190px !important; background:#9b59b6; }
#hotbar { position:absolute; left:50%; transform:translateX(-50%); bottom:10px; display:flex; gap:6px;
background: rgba(0,0,0,0.60); padding:6px; border-radius:12px; pointer-events:auto;
overflow-x: auto; overflow-y: hidden; max-width: 80%; }
.slot { width:38px; height:38px; border:2px solid rgba(255,255,255,0.22); border-radius:10px;
position:relative; overflow:hidden; cursor:pointer; background: rgba(255,255,255,0.07);
display:flex; align-items:center; justify-content:center; font-size:18px; }
.slot.sel { border-color:#f1c40f; box-shadow: 0 0 0 2px rgba(241,196,15,0.18) inset; }
.count { position:absolute; right:3px; bottom:1px; font-size:10px; color:#fff; font-weight:900; text-shadow:1px 1px 0 #000; }
/* Craft modal */
#craftPanel { display:none; position:absolute; left:14px; right:14px; top:14px; bottom:14px;
background: rgba(10,10,12,0.92); border:2px solid rgba(255,255,255,0.85); border-radius:14px;
pointer-events:auto; padding:12px; overflow:auto; }
/* Inventory modal */
#inventoryPanel { display:none; position:absolute; left:50%; top:50%; transform: translate(-50%, -50%);
width: 420px; max-width: 90%; background: rgba(10,10,12,0.92); border:2px solid rgba(255,255,255,0.85);
border-radius:14px; pointer-events:auto; padding:12px; z-index: 100; }
#inventoryPanel .top { display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px; }
#inventoryGrid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin-top: 10px; }
.inv-slot { width: 48px; height: 48px; border: 2px solid rgba(255,255,255,0.22); border-radius: 8px;
position: relative; overflow: hidden; cursor: pointer; background: rgba(255,255,255,0.07);
display: flex; align-items: center; justify-content: center; font-size: 24px; }
.inv-slot.sel { border-color: #f1c40f; box-shadow: 0 0 0 2px rgba(241,196,15,0.18) inset; }
.inv-count { position: absolute; right: 3px; bottom: 1px; font-size: 12px; color: #fff;
font-weight: 900; text-shadow: 1px 1px 0 #000; background: rgba(0,0,0,0.5); padding: 1px 3px; border-radius: 4px; }
#craftPanel .top { display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px; }
#craftPanel .close { background:#c0392b; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
.recipe { display:flex; align-items:center; gap:10px; padding:10px; border-radius:12px;
background: rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.10); margin-bottom:8px; }
.ricon { width:32px; height:32px; border-radius:8px; background-size:cover; image-rendering:pixelated; }
.rinfo { flex:1; }
.rname { color:#fff; font-weight:900; font-size:14px; }
.rcost { color:#bbb; font-size:11px; line-height:1.25; }
.rcraft { background:#2ecc71; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
.rcraft:disabled { background:#444; color:#888; cursor:not-allowed; }
/* Controls buttons */
.cbtn { position:absolute; top:50%; transform:translateY(-50%); width:74px; height:74px; border-radius:14px;
background:#333; border:3px solid #555; color:#fff; font-size:32px; display:flex; align-items:center; justify-content:center;
box-shadow:0 7px 0 #111; pointer-events:auto; }
.cbtn:active { transform:translateY(-46%); box-shadow:0 3px 0 #111; background:#444; }
#left { left:18px; }
#right { left:102px; }
#jump { right:18px; background:#d35400; border-color:#e67e22; }
#death { display:none; position:absolute; inset:0; background: rgba(60,0,0,0.88);
z-index:200; color:#fff; pointer-events:auto; align-items:center; justify-content:center; flex-direction:column; gap:12px; }
#death button { padding:12px 18px; font-size:18px; font-weight:900; border:none; border-radius:12px; cursor:pointer; }

View File

@ -0,0 +1,27 @@
name: grechka-game
services:
grechka-game:
build:
context: .
dockerfile: Dockerfile
container_name: grechka-game
restart: unless-stopped
networks:
- shared_network
labels:
# Включаем Traefik для этого сервиса
- "traefik.enable=true"
- "traefik.docker.network=shared_network"
# HTTPS роутер
- "traefik.http.routers.grechka-game.entrypoints=websecure"
- "traefik.http.routers.grechka-game.rule=Host(`grechka.mkn8n.ru`)"
- "traefik.http.routers.grechka-game.tls=true"
- "traefik.http.routers.grechka-game.tls.certresolver=mytlschallenge"
# Сервис
- "traefik.http.services.grechka-game.loadbalancer.server.port=80"
networks:
shared_network:
external: true

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>GrechkaCraft: Multiplayer</title>
<!-- Socket.io Client -->
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game">
<canvas id="c"></canvas>
<div class="ui">
<div id="stats">
<div class="row">❤️ <span id="hp">100</span> &nbsp; 🍗 <span id="food">100</span></div>
<div class="row">🫁 <span id="o2">100</span></div>
<div class="row">📍 X:<span id="sx">0</span> Y:<span id="sy">0</span></div>
<div class="row">🕒 <span id="tod">День</span></div>
<div class="row">🌐 <span id="worldId" style="cursor:pointer; text-decoration:underline;" title="Нажмите, чтобы скопировать ссылку">default</span></div>
<div class="row" id="multiplayerStatus" style="display:none;">👥 <span id="playerCount">0</span></div>
</div>
<div id="modeBtn" class="rbtn pe">🏃</div>
<div id="saveBtn" class="rbtn pe">💾</div>
<div id="craftBtn" class="rbtn pe">🔨</div>
<div id="resetBtn" class="rbtn pe">🔄</div>
<div id="craftPanel">
<div class="top">
<div style="font-weight:900;font-size:18px;">Крафт</div>
<button class="close" id="craftClose">Закрыть</button>
</div>
<div id="recipes"></div>
</div>
<div id="inventoryPanel">
<div class="top">
<div style="font-weight:900;font-size:18px;">Инвентарь</div>
<button class="close" id="inventoryClose">Закрыть</button>
</div>
<div id="inventoryGrid"></div>
</div>
<div id="death">
<div style="font-size:22px;font-weight:900;">ВЫ ПОГИБЛИ</div>
<button id="respawnBtn">ВОЗРОДИТЬСЯ</button>
</div>
<div id="hotbar" class="pe"></div>
<div id="invToggle" class="rbtn pe" style="bottom:10px; left:10px; background:#3498db;">🎒</div>
</div>
<!-- Chat UI -->
<div id="chatPanel" style="display:none; position:absolute; left:10px; right:10px; top:60px; bottom:160px; background:rgba(0,0,0,0.85); border:2px solid rgba(255,255,255,0.8); border-radius:12px; pointer-events:auto; z-index:50; padding:10px; overflow:hidden;">
<div style="display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px;">
<div style="font-weight:900;font-size:18px;">💬 Чат</div>
<button id="chatClose" style="background:#c0392b; border:none; color:#fff; font-weight:900; padding:6px 10px; border-radius:8px; cursor:pointer;"></button>
</div>
<div id="chatMessages" style="flex:1; overflow-y:auto; color:#fff; font-size:13px; margin-bottom:8px; max-height:calc(100% - 40px);"></div>
<div style="display:flex; gap:6px;">
<input id="chatInput" type="text" placeholder="Введите сообщение..." style="flex:1; padding:8px; border:2px solid rgba(255,255,255,0.5); border-radius:8px; background:rgba(255,255,255,0.1); color:#fff; font-size:13px;" />
<button id="chatSend" style="background:#2ecc71; border:none; color:#fff; font-weight:900; padding:8px 16px; border-radius:8px; cursor:pointer;"></button>
</div>
</div>
<div id="chatToggle" class="rbtn pe">💬</div>
</div>
<div id="controls">
<div id="left" class="cbtn"></div>
<div id="right" class="cbtn"></div>
<div id="jump" class="cbtn"></div>
</div>
<script src="game.js"></script>
</body>
</html>

View File

@ -0,0 +1,72 @@
/* Minegrechka Game Styles - v1.2 */
html, body { margin:0; padding:0; width:100%; height:100%; overflow:hidden; background:#111; font-family: system-ui, sans-serif; user-select:none; -webkit-user-select:none; touch-action:none; }
#game { position:absolute; top:0; left:0; right:0; bottom:140px; background:#87CEEB; overflow:hidden; }
canvas { display:block; width:100%; height:100%; image-rendering:pixelated; }
#controls { position:absolute; left:0; right:0; bottom:0; height:140px; background:#222; border-top:4px solid #444; z-index:10; }
.ui { position:absolute; inset:0; pointer-events:none; z-index:20; }
.pe { pointer-events:auto; }
#stats { position:absolute; left:10px; top:10px; color:#fff; font-weight:800; font-size:14px;
background: rgba(0,0,0,0.55); padding:8px; border-radius:10px; text-shadow:1px 1px 0 #000; }
#stats .row{ display:flex; gap:10px; align-items:center; }
.rbtn { position:absolute; right:10px; width:52px; height:52px; border-radius:12px;
display:flex; align-items:center; justify-content:center; border:2px solid rgba(255,255,255,0.9);
font-size:24px; cursor:pointer; pointer-events:auto; box-shadow:0 4px 0 rgba(0,0,0,0.5); }
.rbtn:active { transform: translateY(4px); box-shadow:none; }
#modeBtn { top:10px; background:#f39c12; }
#saveBtn { top:10px; right:70px !important; background:#27ae60; }
#resetBtn { top:10px; right:130px !important; background:#e74c3c; }
#craftBtn { top:74px; right:10px !important; background:#9b59b6; }
#chatToggle { top:10px; right:190px !important; background:#9b59b6; }
#hotbar { position:absolute; left:50%; transform:translateX(-50%); bottom:10px; display:flex; gap:6px;
background: rgba(0,0,0,0.60); padding:6px; border-radius:12px; pointer-events:auto;
overflow-x: auto; overflow-y: hidden; max-width: 80%; }
.slot { width:38px; height:38px; border:2px solid rgba(255,255,255,0.22); border-radius:10px;
position:relative; overflow:hidden; cursor:pointer; background: rgba(255,255,255,0.07);
display:flex; align-items:center; justify-content:center; font-size:18px; }
.slot.sel { border-color:#f1c40f; box-shadow: 0 0 0 2px rgba(241,196,15,0.18) inset; }
.count { position:absolute; right:3px; bottom:1px; font-size:10px; color:#fff; font-weight:900; text-shadow:1px 1px 0 #000; }
/* Craft modal */
#craftPanel { display:none; position:absolute; left:14px; right:14px; top:14px; bottom:14px;
background: rgba(10,10,12,0.92); border:2px solid rgba(255,255,255,0.85); border-radius:14px;
pointer-events:auto; padding:12px; overflow:auto; }
/* Inventory modal */
#inventoryPanel { display:none; position:absolute; left:50%; top:50%; transform: translate(-50%, -50%);
width: 420px; max-width: 90%; background: rgba(10,10,12,0.92); border:2px solid rgba(255,255,255,0.85);
border-radius:14px; pointer-events:auto; padding:12px; z-index: 100; }
#inventoryPanel .top { display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px; }
#inventoryGrid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin-top: 10px; }
.inv-slot { width: 48px; height: 48px; border: 2px solid rgba(255,255,255,0.22); border-radius: 8px;
position: relative; overflow: hidden; cursor: pointer; background: rgba(255,255,255,0.07);
display: flex; align-items: center; justify-content: center; font-size: 24px; }
.inv-slot.sel { border-color: #f1c40f; box-shadow: 0 0 0 2px rgba(241,196,15,0.18) inset; }
.inv-count { position: absolute; right: 3px; bottom: 1px; font-size: 12px; color: #fff;
font-weight: 900; text-shadow: 1px 1px 0 #000; background: rgba(0,0,0,0.5); padding: 1px 3px; border-radius: 4px; }
#craftPanel .top { display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px; }
#craftPanel .close { background:#c0392b; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
.recipe { display:flex; align-items:center; gap:10px; padding:10px; border-radius:12px;
background: rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.10); margin-bottom:8px; }
.ricon { width:32px; height:32px; border-radius:8px; background-size:cover; image-rendering:pixelated; }
.rinfo { flex:1; }
.rname { color:#fff; font-weight:900; font-size:14px; }
.rcost { color:#bbb; font-size:11px; line-height:1.25; }
.rcraft { background:#2ecc71; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
.rcraft:disabled { background:#444; color:#888; cursor:not-allowed; }
/* Controls buttons */
.cbtn { position:absolute; top:50%; transform:translateY(-50%); width:74px; height:74px; border-radius:14px;
background:#333; border:3px solid #555; color:#fff; font-size:32px; display:flex; align-items:center; justify-content:center;
box-shadow:0 7px 0 #111; pointer-events:auto; }
.cbtn:active { transform:translateY(-46%); box-shadow:0 3px 0 #111; background:#444; }
#left { left:18px; }
#right { left:102px; }
#jump { right:18px; background:#d35400; border-color:#e67e22; }
#death { display:none; position:absolute; inset:0; background: rgba(60,0,0,0.88);
z-index:200; color:#fff; pointer-events:auto; align-items:center; justify-content:center; flex-direction:column; gap:12px; }
#death button { padding:12px 18px; font-size:18px; font-weight:900; border:none; border-radius:12px; cursor:pointer; }

27
docker-compose.yml Normal file
View File

@ -0,0 +1,27 @@
name: grechka-game
services:
grechka-game:
build:
context: .
dockerfile: Dockerfile
container_name: grechka-game
restart: unless-stopped
networks:
- shared_network
labels:
# Включаем Traefik для этого сервиса
- "traefik.enable=true"
- "traefik.docker.network=shared_network"
# HTTPS роутер
- "traefik.http.routers.grechka-game.entrypoints=websecure"
- "traefik.http.routers.grechka-game.rule=Host(`grechka.mkn8n.ru`)"
- "traefik.http.routers.grechka-game.tls=true"
- "traefik.http.routers.grechka-game.tls.certresolver=mytlschallenge"
# Сервис
- "traefik.http.services.grechka-game.loadbalancer.server.port=80"
networks:
shared_network:
external: true

2734
game.js Normal file

File diff suppressed because it is too large Load Diff

BIN
imgs/grechka_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
imgs/grechka_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

82
index.html Normal file
View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>GrechkaCraft: Multiplayer</title>
<!-- Socket.io Client -->
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="game">
<canvas id="c"></canvas>
<div class="ui">
<div id="stats">
<div class="row">❤️ <span id="hp">100</span> &nbsp; 🍗 <span id="food">100</span></div>
<div class="row">🫁 <span id="o2">100</span></div>
<div class="row">📍 X:<span id="sx">0</span> Y:<span id="sy">0</span></div>
<div class="row">🕒 <span id="tod">День</span></div>
<div class="row">🌐 <span id="worldId" style="cursor:pointer; text-decoration:underline;" title="Нажмите, чтобы скопировать ссылку">default</span></div>
<div class="row" id="multiplayerStatus" style="display:none;">👥 <span id="playerCount">0</span></div>
</div>
<div id="modeBtn" class="rbtn pe">🏃</div>
<div id="saveBtn" class="rbtn pe">💾</div>
<div id="craftBtn" class="rbtn pe">🔨</div>
<div id="resetBtn" class="rbtn pe">🔄</div>
<div id="chatToggle" class="rbtn pe">💬</div>
<div id="invToggle" class="rbtn pe">📦</div>
<div id="hotbar" class="pe"></div>
</div>
<div id="controls">
<div id="left" class="btn pe">⬅️</div>
<div id="jump" class="btn pe">⬆️</div>
<div id="down" class="btn pe">⬇️</div>
<div id="right" class="btn pe">➡️</div>
</div>
<div id="craftPanel" class="panel" style="display:none;">
<div class="panel-header">
<span>Крафт</span>
<span id="craftClose" class="close" style="cursor:pointer;"></span>
</div>
<div id="recipes"></div>
</div>
<div id="inventoryPanel" class="panel" style="display:none;">
<div class="panel-header">
<span>Инвентарь</span>
<span id="inventoryClose" class="close" style="cursor:pointer;"></span>
</div>
<div id="inventoryGrid"></div>
</div>
<div id="chatPanel" class="panel" style="display:none;">
<div class="panel-header">
<span>Чат</span>
<span id="chatClose" class="close" style="cursor:pointer;"></span>
</div>
<div id="chatMessages"></div>
<div class="chat-input">
<input type="text" id="chatInput" placeholder="Введите сообщение...">
<button id="chatSend">Отправить</button>
</div>
</div>
<div id="death" class="death-screen" style="display:none;">
<div class="death-content">
<h1>💀 Вы погибли!</h1>
<button id="respawnBtn" class="respawn-btn">Возродиться</button>
</div>
</div>
</div>
<script src="game.js"></script>
</body>
</html>

4713
logs/server.log Normal file

File diff suppressed because it is too large Load Diff

92
style.css Normal file
View File

@ -0,0 +1,92 @@
/* Minegrechka Game Styles - v1.2 */
html, body { margin:0; padding:0; width:100%; height:100%; overflow:hidden; background:#111; font-family: system-ui, sans-serif; user-select:none; -webkit-user-select:none; touch-action:none; }
#game { position:absolute; top:0; left:0; right:0; bottom:0; background:#87CEEB; overflow:hidden; }
canvas { display:block; width:100%; height:100%; image-rendering:pixelated; }
#controls { position:absolute; left:0; right:0; bottom:0; height:140px; background:#222; border-top:4px solid #444; z-index:100; display:none; }
/* Стили для кнопок управления (.btn вместо .cbtn для совместимости с index.html) */
.btn { position:absolute; top:50%; transform:translateY(-50%); width:74px; height:74px; border-radius:14px;
background:#333; border:3px solid #555; color:#fff; font-size:32px; display:flex; align-items:center; justify-content:center;
box-shadow:0 7px 0 #111; pointer-events:auto; z-index:101; }
.btn:active { transform:translateY(-46%); box-shadow:0 3px 0 #111; background:#444; }
#left { left:18px; }
#right { left:102px; }
#down { left:186px; background:#2980b9; border-color:#3498db; }
#jump { right:18px; background:#d35400; border-color:#e67e22; }
/* Скрываем панель на десктопе (широкие экраны) */
@media (min-width: 769px) {
#controls { display: none !important; }
}
/* Показываем панель на мобильных устройствах (узкие экраны) */
@media (max-width: 768px) {
#controls { display: block !important; }
}
.ui { position:absolute; inset:0; pointer-events:none; z-index:20; }
.pe { pointer-events:auto; }
#stats { position:absolute; left:10px; top:10px; color:#fff; font-weight:800; font-size:14px;
background: rgba(0,0,0,0.55); padding:8px; border-radius:10px; text-shadow:1px 1px 0 #000; }
#stats .row{ display:flex; gap:10px; align-items:center; }
.rbtn { position:absolute; right:10px; width:52px; height:52px; border-radius:12px;
display:flex; align-items:center; justify-content:center; border:2px solid rgba(255,255,255,0.9);
font-size:24px; cursor:pointer; pointer-events:auto; box-shadow:0 4px 0 rgba(0,0,0,0.5); }
.rbtn:active { transform: translateY(4px); box-shadow:none; }
#modeBtn { top:10px; background:#f39c12; }
#saveBtn { top:10px; right:70px !important; background:#27ae60; }
#resetBtn { top:10px; right:130px !important; background:#e74c3c; }
#craftBtn { top:74px; right:10px !important; background:#9b59b6; }
#invToggle { top:74px; right:70px !important; background:#3498db; }
#chatToggle { display: none !important; }
#chatPanel { display: none !important; }
#hotbar { position:absolute; left:50%; transform:translateX(-50%); bottom:10px; display:flex; gap:6px;
background: rgba(0,0,0,0.60); padding:6px; border-radius:12px; pointer-events:auto;
overflow-x: auto; overflow-y: hidden; max-width: 80%; }
/* Поднимаем hotbar на тач-устройствах, чтобы не перекрывать кнопки */
body.touch-device #hotbar {
bottom: 150px;
}
.slot { width:38px; height:38px; border:2px solid rgba(255,255,255,0.22); border-radius:10px;
position:relative; overflow:hidden; cursor:pointer; background: rgba(255,255,255,0.07);
display:flex; align-items:center; justify-content:center; font-size:18px; }
.slot.sel { border-color:#f1c40f; box-shadow: 0 0 0 2px rgba(241,196,15,0.18) inset; }
.count { position:absolute; right:3px; bottom:1px; font-size:10px; color:#fff; font-weight:900; text-shadow:1px 1px 0 #000; }
/* Craft modal */
#craftPanel { display:none; position:absolute; left:14px; right:14px; top:14px; bottom:14px;
background: rgba(10,10,12,0.92); border:2px solid rgba(255,255,255,0.85); border-radius:14px;
pointer-events:auto; padding:12px; overflow:auto; z-index: 200; }
/* Inventory modal */
#inventoryPanel { display:none; position:absolute; left:50%; top:50%; transform: translate(-50%, -50%);
width: 420px; max-width: 90%; background: rgba(10,10,12,0.92); border:2px solid rgba(255,255,255,0.85);
border-radius:14px; pointer-events:auto; padding:12px; z-index: 200; }
#inventoryPanel .top { display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px; }
#inventoryGrid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin-top: 10px; }
.inv-slot { width: 48px; height: 48px; border: 2px solid rgba(255,255,255,0.22); border-radius: 8px;
position: relative; overflow: hidden; cursor: pointer; background: rgba(255,255,255,0.07);
display: flex; align-items: center; justify-content: center; font-size: 24px; }
.inv-slot.sel { border-color: #f1c40f; box-shadow: 0 0 0 2px rgba(241,196,15,0.18) inset; }
.inv-count { position: absolute; right: 3px; bottom: 1px; font-size: 12px; color: #fff;
font-weight: 900; text-shadow: 1px 1px 0 #000; background: rgba(0,0,0,0.5); padding: 1px 3px; border-radius: 4px; }
.equipped-indicator { position: absolute; top: 2px; right: 2px; font-size: 14px; color: #2ecc71; font-weight: 900; text-shadow: 1px 1px 0 #000; }
#craftPanel .top { display:flex; justify-content:space-between; align-items:center; color:#fff; margin-bottom:10px; }
#craftPanel .close { background:#c0392b; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
#inventoryPanel .close { background:#c0392b; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
.recipe { display:flex; align-items:center; gap:10px; padding:10px; border-radius:12px;
background: rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.10); margin-bottom:8px; }
.ricon { width:32px; height:32px; border-radius:8px; background-size:cover; image-rendering:pixelated; }
.rinfo { flex:1; }
.rname { color:#fff; font-weight:900; font-size:14px; }
.rcost { color:#bbb; font-size:11px; line-height:1.25; }
.rcraft { background:#2ecc71; border:none; color:#fff; font-weight:900; padding:8px 10px; border-radius:10px; cursor:pointer; }
.rcraft:disabled { background:#444; color:#888; cursor:not-allowed; }
#death { display:none; position:absolute; inset:0; background: rgba(60,0,0,0.88);
z-index:200; color:#fff; pointer-events:auto; align-items:center; justify-content:center; flex-direction:column; gap:12px; }
#death button { padding:12px 18px; font-size:18px; font-weight:900; border:none; border-radius:12px; cursor:pointer; }