This commit is contained in:
2026-02-23 14:22:24 +07:00
parent 23e91a3790
commit 43281f7615
8 changed files with 201 additions and 65 deletions

View File

@@ -0,0 +1,44 @@
import os
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
ADMIN_ID = os.getenv("ADMIN_ID")
# Стартовые значения
INITIAL_BALANCE = 10_000
INITIAL_SERVERS = 1
INITIAL_COVERAGE_CUSTOMERS = 500
INITIAL_MAX_CUSTOMERS = 500
INITIAL_SPEED = 10
INITIAL_CHANNEL_LEVEL = 1
INITIAL_POWER_CAPACITY = 5.0
INITIAL_NETWORK_CHANNEL_CAPACITY = 10 # Мбит/с (база)
# Параметры инфраструктуры
SERVER_POWER = 2.0 # кВт/сервер
SERVER_COST = 3_000
SERVER_CAPACITY_ADD = 500
CABLE_COST = 800
CABLE_COVERAGE_ADD = 500
BACKUP_CHANNEL_COST = 2_500
BACKUP_CHANNEL_POWER_ADD = 100
# Тарифы (в ₡/день/клиент)
TARIFFS = {
"home": 10,
"office": 30,
"gamer": 50,
"botfarm": 15
}
# Уровни канала
CHANNEL_LEVELS = {
1: 10,
2: 50,
3: 100,
4: 250,
5: 500,
6: 1000
}

View File

@@ -1,8 +1,10 @@
# database.py — работа с SQLite
import sqlite3
from typing import Optional, Dict, Any
DB_PATH = "data/db.sqlite3"
def init_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
@@ -32,14 +34,17 @@ def init_db():
conn.commit()
conn.close()
def get_player(user_id: int) -> Optional[Dict[str, Any]]:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT * FROM players WHERE user_id = ?", (user_id,))
row = cur.fetchone()
conn.close()
if not row:
return None
keys = [
"user_id", "balance", "servers", "cables", "coverage_customers",
"max_customers", "speed", "rating", "rating_threshold",
@@ -49,6 +54,7 @@ def get_player(user_id: int) -> Optional[Dict[str, Any]]:
]
return dict(zip(keys, row))
def update_player(user_id: int, updates: Dict[str, Any]):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
@@ -58,15 +64,30 @@ def update_player(user_id: int, updates: Dict[str, Any]):
conn.commit()
conn.close()
def create_player_if_not_exists(user_id: int):
player = get_player(user_id)
if not player:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO players (user_id, balance, servers, cables, coverage_customers, max_customers, speed, power_capacity, channel_capacity)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (user_id, INITIAL_BALANCE, INITIAL_SERVERS, 0, INITIAL_COVERAGE_CUSTOMERS,
INITIAL_MAX_CUSTOMERS, INITIAL_SPEED, INITIAL_POWER_CAPACITY, INITIAL_NETWORK_CHANNEL_CAPACITY))
conn.commit()
conn.close()
if player:
return
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO players
(user_id, balance, servers, cables, coverage_customers, max_customers, speed,
power_capacity, network_channel_level, channel_capacity)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
user_id,
10_000, # balance
1, # servers
0, # cables
500, # coverage_customers
500, # max_customers
10, # speed
5.0, # power_capacity
1, # network_channel_level
10 # channel_capacity
))
conn.commit()
conn.close()

View File

@@ -0,0 +1,74 @@
from aiogram import types, Router
from database import get_player, update_player
from economy import sync_power
from config import CABLE_COST, SERVER_COST, BACKUP_CHANNEL_COST, BACKUP_CHANNEL_POWER_ADD
router = Router()
@router.callback_query(lambda c: c.data == "build_cable")
async def build_cable(callback: types.CallbackQuery):
user_id = callback.from_user.id
player = get_player(user_id)
if player["balance"] < CABLE_COST:
await callback.answer("Не хватает средств!", show_alert=True)
return
new_power = player["power_capacity"] + 2
update_player(user_id, {
"balance": player["balance"] - CABLE_COST,
"power_capacity": new_power,
"cables": player["cables"] + 1
})
await callback.message.edit_text(
f"✅ Кабель проложен!\n"
f"⚡ Лимит питания: {new_power} кВт\n"
f"💰 Остаток: {player['balance'] - CABLE_COST}"
)
await callback.answer("Кабель установлен 🛠️")
@router.callback_query(lambda c: c.data == "buy_server")
async def buy_server(callback: types.CallbackQuery):
user_id = callback.from_user.id
player = get_player(user_id)
if player["balance"] < SERVER_COST:
await callback.answer("Не хватает средств!", show_alert=True)
return
new_servers = player["servers"] + 1
new_power = new_servers * 2.0 # SERVER_POWER = 2.0 из config.py
update_player(user_id, {
"balance": player["balance"] - SERVER_COST,
"servers": new_servers,
"max_customers": player["max_customers"] + 500,
"power_capacity": new_power, # обновляем лимит
"power_usage": player["servers"] * 2.0 # обновляем нагрузку (была на старом количестве)
})
await callback.message.edit_text(
f"✅ Сервер установлен!\n"
f"🖥 Теперь: {new_servers} шт.\n"
f"⚡ Потребление: {new_power:.1f}/{new_power:.1f} кВт"
)
await callback.answer("Сервер включён!")
@router.callback_query(lambda c: c.data == "buy_backup_channel")
async def buy_backup_channel(callback: types.CallbackQuery):
user_id = callback.from_user.id
player = get_player(user_id)
if player["balance"] < BACKUP_CHANNEL_COST:
await callback.answer("Не хватает средств!", show_alert=True)
return
new_power = player["power_capacity"] + BACKUP_CHANNEL_POWER_ADD
update_player(user_id, {
"balance": player["balance"] - BACKUP_CHANNEL_COST,
"power_capacity": new_power,
"backup_channel_active": 0
})
await callback.message.edit_text(
f"⚡ Резервный канал построен! ✅\n"
f"Лимит питания: {new_power} кВт\n"
f"💰 Остаток: {player['balance'] - BACKUP_CHANNEL_COST}"
)
await callback.answer("Резерв готов!")

View File

@@ -1,7 +1,10 @@
from aiogram import types
from aiogram import types, Router
from database import get_player, update_player
from economy import calculate_channel_cost
router = Router() # ← ВАЖНО!
@router.callback_query(lambda c: c.data == "upgrade_channel")
async def upgrade_channel(callback: types.CallbackQuery):
user_id = callback.from_user.id
player = get_player(user_id)
@@ -17,7 +20,7 @@ async def upgrade_channel(callback: types.CallbackQuery):
return
next_level = current_level + 1
next_capacity = int(player["channel_capacity"] * 2.5) # +150% каждый уровень
next_capacity = int(player["channel_capacity"] * 2.5)
upgrade_hours = 4 * current_level
update_player(user_id, {
@@ -50,7 +53,11 @@ async def speedup_channel(callback: types.CallbackQuery):
await callback.answer("❌ Нет средств на ускорение", show_alert=True)
return
new_end = max(0, int(__import__("time").time()) + (player["next_build_end_ts"] - int(__import__("time").time())) // 2)
new_end = max(
0,
int(__import__("time").time()) +
(player["next_build_end_ts"] - int(__import__("time").time())) // 2
)
update_player(user_id, {
"balance": player["balance"] - 500,

View File

@@ -1,12 +1,15 @@
from aiogram import types
# handlers/map.py
from aiogram import types, Router
from database import get_player
router = Router() # ← ВАЖНО! Регистрируем роутер
@router.callback_query(lambda c: c.data == "map")
async def show_map(callback: types.CallbackQuery):
player = get_player(callback.from_user.id)
districts = [
("🏠 Гараж (ваша база)", "✅ Подключён"),
("🏢 Центральный район", "🟡 В процессе (15 мин)" if player["next_build_end_ts"] > 0 else "🔒 Недоступен"),
("🏢 Центральный район", "🟡 В процессе" if player.get("next_build_end_ts", 0) > 0 else "🔒 Недоступен"),
("🎮 Западный квартал", "🔒 Недоступен"),
]

View File

@@ -1,13 +1,10 @@
from aiogram import types, Router
from aiogram.filters import CommandStart
from aiogram.types import WebAppInfo
from database import create_player_if_not_exists, get_player
from economy import (
calculate_daily_offline_earnings, calculate_effective_bandwidth,
calculate_rating, calculate_daily_income
calculate_effective_bandwidth, calculate_rating, calculate_client_retention,
calculate_daily_income, calculate_daily_offline_earnings
)
from keyboards import get_main_menu
router = Router()
@@ -23,7 +20,6 @@ async def cmd_start(message: types.Message):
player["balance"] += offline_earn
player["total_income"] += offline_earn
player["last_login_ts"] = int(__import__("time").time())
get_player.cache_clear()
update_player(user_id, {
"balance": player["balance"],
"total_income": player["total_income"],
@@ -31,6 +27,8 @@ async def cmd_start(message: types.Message):
})
effective_bw = calculate_effective_bandwidth(player)
lost, churn_msg = calculate_client_retention(player)
daily_income = calculate_daily_income(player)
status = ""
if player["outage_end_ts"] > 0:
@@ -44,13 +42,14 @@ async def cmd_start(message: types.Message):
f"⚡ *Канал:* {player['channel_capacity']}{effective_bw} Мбит/с\n"
f"🔋 *Сеть:* {player['power_usage']:.1f}/{player['power_capacity']} кВт\n"
f"🖥 *Серверы:* {player['servers']} (ёмкость: {player['max_customers']} клиентов)\n"
f"📡 *Клиенты:* {player['coverage_customers']} (отток: {calculate_client_retention(player)[0]}/сутки)\n"
f"📡 *Клиенты:* {player['coverage_customers']} (отток: {lost}/сутки)\n"
f"💰 *Бюджет:* {player['balance']:,.0f}\n"
f"📊 *Доход/день:* {calculate_daily_income(player):,.0f}\n"
f"📊 *Доход/день:* {daily_income:,.0f}\n"
f"🌟 *Рейтинг:* {calculate_rating(player)}\n"
f"{status}"
f"{churn_msg}\n{status}"
)
from keyboards import get_main_menu
await message.answer(
text,
reply_markup=get_main_menu(player),

View File

@@ -1,18 +1,26 @@
from aiogram import types
# handlers/stats.py
from aiogram import types, Router
from database import get_player
from economy import calculate_rating, calculate_effective_bandwidth
router = Router()
@router.callback_query(lambda c: c.data == "stats")
async def stats(callback: types.CallbackQuery):
player = get_player(callback.from_user.id)
if not player:
await callback.answer("❌ Игрок не найден", show_alert=True)
return
bw = calculate_effective_bandwidth(player)
daily = calculate_daily_income(player)
text = (
f"📊 *Ваша статистика*\n\n"
f"🔹 Канал: {player['network_channel_level']}{bw} Мбит/с\n"
f"🔹 Серверы: {player['servers']} шт. (ёмкость: {player['max_customers']})\n"
f"🔹 Клиенты: {player['coverage_customers']} / {player['max_customers']}\n"
f"🔹 Доход/день: {calculate_daily_income(player):,.0f}\n"
f"🔹 Доход/день: {daily:,.0f}\n"
f"🔹 Общий доход: {player['total_income']:,.0f}\n"
f"🔹 Рейтинг: {calculate_rating(player)} 🌟"
)

58
main.py
View File

@@ -1,16 +1,12 @@
from aiogram import Bot, Dispatcher, types
from aiogram import Bot, Dispatcher
from aiogram.enums import ParseMode
from aiogram.filters import Command
from aiogram import Router
import asyncio
import logging
import time
from config import BOT_TOKEN
from database import init_db, get_player, update_player
from economy import calculate_effective_bandwidth, handle_outage, sync_power
from database import init_db
from handlers.start import router as start_router
from handlers.economy import router as economy_router
from handlers.build import router as build_router
from handlers.channel import router as channel_router
from handlers.stats import router as stats_router
@@ -19,53 +15,37 @@ from handlers.events import router as events_router
dp = Dispatcher()
dp.include_router(start_router)
dp.include_router(economy_router)
dp.include_router(build_router)
dp.include_router(channel_router)
dp.include_router(stats_router)
dp.include_router(map_router)
dp.include_router(events_router)
# Периодическая проверка состояний (каждые 5 минут)
@dp.message(Command("check"))
async def check_state(message: types.Message):
user_id = message.from_user.id
player = get_player(user_id)
if not player:
await message.answer("❌ Игрок не найден")
return
@dp.message()
async def echo(message: types.Message):
if message.text == "/check":
from database import get_player
from economy import calculate_effective_bandwidth, handle_outage, sync_power
player = get_player(message.from_user.id)
if not player:
await message.answer("❌ Игрок не найден")
return
# Оффлайн доход + апгрейды
player = handle_outage(player)
player = sync_power(player)
effective_bw = calculate_effective_bandwidth(player)
player = handle_outage(player)
player = sync_power(player)
# Обновление
update_player(user_id, {
"power_usage": player["power_usage"],
"coverage_customers": player["coverage_customers"],
"max_customers": player["max_customers"]
})
status = ""
if player["outage_end_ts"] > 0:
status = f"\n⚠️ АВАРИЯ: Отключение до {player['outage_end_ts'] - int(__import__('time').time()) // 3600}ч"
await message.answer(
f"⚡ Состояние проверено!\n"
f"Канал: {player['channel_capacity']}{effective_bw} Мбит/с\n"
f"Лимит: {player['power_usage']:.1f}/{player['power_capacity']} кВт\n"
f"Клиенты: {player['coverage_customers']} / {player['max_customers']}\n"
f"{status}",
parse_mode="Markdown"
)
await message.answer(
f"⚡ Состояние проверено!\n"
f"Канал: {player['channel_capacity']}{calculate_effective_bandwidth(player)} Мбит/с\n"
f"Лимит: {player['power_usage']:.1f}/{player['power_capacity']} кВт"
)
async def main():
init_db()
bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML)
print("🤖 ISP Tycoon bot started...")
await dp.start_polling(bot)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
bot = Bot(token=BOT_TOKEN, parse_mode=ParseMode.HTML)
asyncio.run(main())