Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!

Что слить

Решение
фреймворк вк апи

код:
import json
import random
import threading
import time
from typing import Dict, Any, List, Optional

from vk_api.keyboard import VkKeyboard, VkKeyboardColor


ROLE_MAFIA = "мафия"
ROLE_DETECTIVE = "комиссар"
ROLE_DOCTOR = "доктор"
ROLE_CIVIL = "мирный"

PHASE_LOBBY = "lobby"
PHASE_NIGHT = "night"
PHASE_DAY = "day"
PHASE_VOTE = "vote"
PHASE_ENDED = "ended"


class MafiaModule:
    def __init__(
        self,
        vk,
        *,
        admin_id: int = 0,
        night_sec: int = 60,
        day_sec: int = 90,
        vote_sec: int = 45,
        min_players: int = 3,
        max_players: int = 999,
    ):
        self.vk = vk
        self.admin_id = admin_id
        self.night_sec = night_sec
        self.day_sec...
❤️Птиц❤️
Авг
432
503
Активный
Янв
243
539
Продавец
фреймворк вк апи

код:
import json
import random
import threading
import time
from typing import Dict, Any, List, Optional

from vk_api.keyboard import VkKeyboard, VkKeyboardColor


ROLE_MAFIA = "мафия"
ROLE_DETECTIVE = "комиссар"
ROLE_DOCTOR = "доктор"
ROLE_CIVIL = "мирный"

PHASE_LOBBY = "lobby"
PHASE_NIGHT = "night"
PHASE_DAY = "day"
PHASE_VOTE = "vote"
PHASE_ENDED = "ended"


class MafiaModule:
    def __init__(
        self,
        vk,
        *,
        admin_id: int = 0,
        night_sec: int = 60,
        day_sec: int = 90,
        vote_sec: int = 45,
        min_players: int = 3,
        max_players: int = 999,
    ):
        self.vk = vk
        self.admin_id = admin_id
        self.night_sec = night_sec
        self.day_sec = day_sec
        self.vote_sec = vote_sec
        self.min_players = min_players
        self.max_players = max_players

        self.games: Dict[int, Dict[str, Any]] = {}
        self.lock = threading.Lock()
        self.name_cache: Dict[int, str] = {}

        t = threading.Thread(target=self._ticker_loop, daemon=True)
        t.start()

    def _ticker_loop(self):
        while True:
            try:
                self._tick()
            except Exception:
                pass
            time.sleep(1)

    def _tick(self):
        with self.lock:
            games = list(self.games.values())
        now = time.time()
        for game in games:
            if not game.get("phase_end"):
                continue
            if now < game["phase_end"]:
                continue
            phase = game.get("phase")
            if phase == PHASE_NIGHT:
                self._resolve_night(game)
            elif phase == PHASE_DAY:
                self._begin_vote(game)
            elif phase == PHASE_VOTE:
                self._resolve_vote(game)

    def _get_name(self, user_id: int) -> str:
        if user_id in self.name_cache:
            return self.name_cache[user_id]
        try:
            info = self.vk.users.get(user_ids=user_id)[0]
            name = info["first_name"]
        except Exception:
            name = str(user_id)
        self.name_cache[user_id] = name
        return name

    def _mention(self, user_id: int) -> str:
        return f"[id{user_id}|{self._get_name(user_id)}]"

    def _is_chat(self, peer_id: int) -> bool:
        return peer_id >= 2000000000

    def _peer_to_chat_id(self, peer_id: int) -> int:
        return peer_id - 2000000000

    def _send(self, peer_id: int, text: str, keyboard: Optional[VkKeyboard] = None):
        params = {"peer_id": peer_id, "random_id": random.randint(1, 10**9), "message": text}
        if keyboard is not None:
            params["keyboard"] = keyboard.get_keyboard()
        self.vk.messages.send(**params)

    def _new_game(self, chat_id: int) -> Dict[str, Any]:
        return {
            "chat_id": chat_id,
            "phase": PHASE_LOBBY,
            "players": {},
            "phase_end": 0,
            "night": {},
            "day_votes": {},
            "round": 0,
        }

    def _get_game(self, chat_id: int) -> Dict[str, Any]:
        with self.lock:
            game = self.games.get(chat_id)
            if not game:
                game = self._new_game(chat_id)
                self.games[chat_id] = game
        return game

    def _player(self, game: Dict[str, Any], user_id: int):
        return game["players"].get(str(user_id))

    def _list_players(self, game: Dict[str, Any], alive_only=False) -> List[int]:
        ids = []
        for uid_str, p in game["players"].items():
            if alive_only and not p.get("alive"):
                continue
            ids.append(int(uid_str))
        return ids

    def _add_player(self, game: Dict[str, Any], user_id: int):
        if str(user_id) in game["players"]:
            return
        if len(game["players"]) >= self.max_players:
            return
        game["players"][str(user_id)] = {
            "name": self._get_name(user_id),
            "alive": True,
            "role": None,
        }

    def _remove_player(self, game: Dict[str, Any], user_id: int):
        if str(user_id) in game["players"]:
            del game["players"][str(user_id)]

    def _assign_roles(self, game: Dict[str, Any]):
        ids = self._list_players(game)
        random.shuffle(ids)
        mafia_count = max(1, len(ids) // 3)
        mafia_ids = ids[:mafia_count]
        rest = ids[mafia_count:]
        detective = rest[0] if len(rest) > 0 else None
        doctor = rest[1] if len(rest) > 1 else None

        for uid in ids:
            role = ROLE_CIVIL
            if uid in mafia_ids:
                role = ROLE_MAFIA
            elif detective == uid:
                role = ROLE_DETECTIVE
            elif doctor == uid:
                role = ROLE_DOCTOR
            game["players"][str(uid)]["role"] = role

    def _send_roles(self, game: Dict[str, Any]):
        mafia_ids = [uid for uid in self._list_players(game) if self._player(game, uid)["role"] == ROLE_MAFIA]
        mafia_names = ", ".join(self._mention(uid) for uid in mafia_ids)
        for uid in self._list_players(game):
            role = self._player(game, uid)["role"]
            if role == ROLE_MAFIA:
                text = f"🕶 Роль: МАФИЯ\nСоюзники: {mafia_names}\nНочью выбирай жертву."
            elif role == ROLE_DETECTIVE:
                text = "🕵️ Роль: КОМИССАР. Ночью проверяй игроков."
            elif role == ROLE_DOCTOR:
                text = "🩺 Роль: ДОКТОР. Ночью выбирай, кого спасти."
            else:
                text = "🙂 Роль: МИРНЫЙ. Днём ищи мафию."
            self._send(uid, text)

    def _build_lobby_keyboard(self):
        kb = VkKeyboard(one_time=False)
        kb.add_button("✅ Войти", VkKeyboardColor.POSITIVE, payload={"action": "join"})
        kb.add_button("🚪 Выйти", VkKeyboardColor.NEGATIVE, payload={"action": "leave"})
        kb.add_line()
        kb.add_button("🚀 Старт", VkKeyboardColor.PRIMARY, payload={"action": "start"})
        kb.add_button("📊 Статус", VkKeyboardColor.SECONDARY, payload={"action": "status"})
        kb.add_line()
        kb.add_button("🛑 Стоп", VkKeyboardColor.NEGATIVE, payload={"action": "stop"})
        return kb

    def _build_targets_keyboard(self, targets: List[int], action: str, chat_id: int):
        if len(targets) == 0 or len(targets) > 10:
            return None
        kb = VkKeyboard(one_time=True, inline=True)
        for uid in targets:
            kb.add_button(
                self._get_name(uid),
                VkKeyboardColor.PRIMARY,
                payload={"action": action, "chat_id": chat_id, "target": uid},
            )
        return kb

    def _start_game(self, game: Dict[str, Any], peer_id: int):
        if game["phase"] != PHASE_LOBBY:
            self._send(peer_id, "Игра уже идёт.")
            return
        if len(game["players"]) < self.min_players:
            self._send(peer_id, f"Нужно минимум {self.min_players} игроков.")
            return
        self._assign_roles(game)
        self._send_roles(game)
        game["round"] = 1
        self._begin_night(game)
        self._send(peer_id, "🎬 Игра началась! Ночь наступает...")

    def _begin_night(self, game: Dict[str, Any]):
        game["phase"] = PHASE_NIGHT
        game["phase_end"] = time.time() + self.night_sec
        game["night"] = {"mafia_votes": {}, "doctor": None, "detective": None}

        chat_peer = game["chat_id"] + 2000000000
        self._send(chat_peer, "🌙 Ночь. Все засыпают...")

        alive = self._list_players(game, alive_only=True)
        mafia_ids = [uid for uid in alive if self._player(game, uid)["role"] == ROLE_MAFIA]
        targetable = [uid for uid in alive if self._player(game, uid)["role"] != ROLE_MAFIA]

        for uid in mafia_ids:
            kb = self._build_targets_keyboard(targetable, "kill", game["chat_id"])
            if kb:
                self._send(uid, "Выберите жертву:", keyboard=kb)
            else:
                self._send(uid, "Выберите жертву: kill ID")

        for uid in alive:
            role = self._player(game, uid)["role"]
            if role == ROLE_DOCTOR:
                kb = self._build_targets_keyboard(alive, "heal", game["chat_id"])
                self._send(uid, "Кого лечить?" if kb else "Кого лечить? heal ID", keyboard=kb)
            if role == ROLE_DETECTIVE:
                kb = self._build_targets_keyboard(alive, "check", game["chat_id"])
                self._send(uid, "Кого проверить?" if kb else "Кого проверить? check ID", keyboard=kb)

    def _resolve_night(self, game: Dict[str, Any]):
        alive = self._list_players(game, alive_only=True)
        mafia_votes = game["night"].get("mafia_votes", {})
        doctor_target = game["night"].get("doctor")
        detective_target = game["night"].get("detective")

        kill_target = None
        if mafia_votes:
            max_votes = max(mafia_votes.values())
            top = [int(uid) for uid, v in mafia_votes.items() if v == max_votes]
            kill_target = random.choice(top)

        killed = None
        if kill_target and kill_target in alive and kill_target != doctor_target:
            self._player(game, kill_target)["alive"] = False
            killed = kill_target

        if detective_target and detective_target in alive:
            role = self._player(game, detective_target)["role"]
            msg = "🔎 Проверка: МАФИЯ" if role == ROLE_MAFIA else "🔎 Проверка: НЕ мафия"
            det_id = next((u for u in alive if self._player(game, u)["role"] == ROLE_DETECTIVE), None)
            if det_id:
                self._send(det_id, msg)

        chat_peer = game["chat_id"] + 2000000000
        if killed:
            self._send(chat_peer, f"☀️ Утро. Ночью был убит: {self._mention(killed)}")
        else:
            self._send(chat_peer, "☀️ Утро. Ночь прошла без жертв.")

        if self._check_win(game):
            return

        game["phase"] = PHASE_DAY
        game["phase_end"] = time.time() + self.day_sec
        game["day_votes"] = {}
        self._send(chat_peer, "🗣 День. Обсуждаем. Скоро голосование.")

    def _begin_vote(self, game: Dict[str, Any]):
        game["phase"] = PHASE_VOTE
        game["phase_end"] = time.time() + self.vote_sec
        game["day_votes"] = {}

        chat_peer = game["chat_id"] + 2000000000
        alive = self._list_players(game, alive_only=True)
        names = [self._mention(uid) for uid in alive]
        self._send(chat_peer, "🗳 Голосование! Кого линчуем?\n" + "\n".join(names))

        kb = self._build_targets_keyboard(alive, "vote", game["chat_id"])
        if kb:
            self._send(chat_peer, "Выберите голос кнопкой:", keyboard=kb)
        else:
            self._send(chat_peer, "Напишите: vote ID")

    def _resolve_vote(self, game: Dict[str, Any]):
        votes = game.get("day_votes", {})
        alive = self._list_players(game, alive_only=True)
        counted = {}
        for voter, target in votes.items():
            if int(voter) not in alive:
                continue
            if target not in alive:
                continue
            counted[target] = counted.get(target, 0) + 1

        chat_peer = game["chat_id"] + 2000000000
        if not counted:
            self._send(chat_peer, "Никого не линчуем.")
        else:
            max_votes = max(counted.values())
            top = [uid for uid, v in counted.items() if v == max_votes]
            lynched = random.choice(top)
            self._player(game, lynched)["alive"] = False
            self._send(chat_peer, f"⚖️ Линчевали: {self._mention(lynched)}")

        if self._check_win(game):
            return
        game["round"] += 1
        self._begin_night(game)

    def _check_win(self, game: Dict[str, Any]) -> bool:
        alive = self._list_players(game, alive_only=True)
        mafia = [uid for uid in alive if self._player(game, uid)["role"] == ROLE_MAFIA]
        civ = [uid for uid in alive if self._player(game, uid)["role"] != ROLE_MAFIA]
        chat_peer = game["chat_id"] + 2000000000

        if len(mafia) == 0:
            self._send(chat_peer, "🏆 Победа мирных!")
            game["phase"] = PHASE_ENDED
            game["phase_end"] = 0
            return True
        if len(mafia) >= len(civ):
            self._send(chat_peer, "😈 Победа мафии!")
            game["phase"] = PHASE_ENDED
            game["phase_end"] = 0
            return True
        return False

    def _status_text(self, game: Dict[str, Any]) -> str:
        players = []
        for uid in self._list_players(game):
            p = self._player(game, uid)
            status = "✅" if p.get("alive") else "💀"
            players.append(f"{status} {self._mention(uid)}")
        phase_map = {
            PHASE_LOBBY: "лобби",
            PHASE_NIGHT: "ночь",
            PHASE_DAY: "день",
            PHASE_VOTE: "голосование",
            PHASE_ENDED: "завершена",
        }
        return (
            f"🎮 Мафия\nФаза: {phase_map.get(game['phase'], game['phase'])}\n"
            f"Раунд: {game.get('round', 0)}\n\n"
            f"Игроки ({len(game['players'])}):\n" + "\n".join(players)
        )

    def _handle_payload(self, peer_id: int, user_id: int, payload: Dict[str, Any]):
        action = payload.get("action")
        chat_id = payload.get("chat_id")
        if action in {"join", "leave", "start", "status", "stop"}:
            if not self._is_chat(peer_id):
                return
            chat_id = self._peer_to_chat_id(peer_id)
            game = self._get_game(chat_id)
            if action == "join":
                self._add_player(game, user_id)
                self._send(peer_id, f"{self._mention(user_id)} в игре!")
            elif action == "leave":
                if game["phase"] == PHASE_LOBBY:
                    self._remove_player(game, user_id)
                    self._send(peer_id, f"{self._mention(user_id)} вышел.")
                else:
                    self._player(game, user_id)["alive"] = False
                    self._send(peer_id, f"{self._mention(user_id)} покинул игру и считается мёртвым.")
                    self._check_win(game)
            elif action == "start":
                self._start_game(game, peer_id)
            elif action == "status":
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
            elif action == "stop":
                if self.admin_id and user_id != self.admin_id:
                    self._send(peer_id, "Только админ может стопнуть игру.")
                else:
                    game["phase"] = PHASE_ENDED
                    game["phase_end"] = 0
                    self._send(peer_id, "🛑 Игра остановлена.")
            return

        if action in {"kill", "heal", "check", "vote"}:
            if not chat_id:
                return
            game = self._get_game(int(chat_id))
            target = payload.get("target")
            if target is None:
                return
            self._handle_action(game, user_id, action, int(target))

    def _handle_action(self, game: Dict[str, Any], user_id: int, action: str, target: int):
        player = self._player(game, user_id)
        if not player or not player.get("alive"):
            return
        if game["phase"] == PHASE_NIGHT:
            if action == "kill" and player.get("role") == ROLE_MAFIA:
                game["night"]["mafia_votes"][str(target)] = game["night"]["mafia_votes"].get(str(target), 0) + 1
                self._send(user_id, "✅ Голос за убийство принят.")
            elif action == "heal" and player.get("role") == ROLE_DOCTOR:
                game["night"]["doctor"] = target
                self._send(user_id, "✅ Лечение принято.")
            elif action == "check" and player.get("role") == ROLE_DETECTIVE:
                game["night"]["detective"] = target
                self._send(user_id, "✅ Проверка принята.")
        elif game["phase"] == PHASE_VOTE and action == "vote":
            if not self._player(game, target) or not self._player(game, target).get("alive"):
                return
            game["day_votes"][str(user_id)] = target
            chat_peer = game["chat_id"] + 2000000000
            self._send(chat_peer, f"🗳 {self._mention(user_id)} голосует.")

    def _handle_text_action(self, peer_id: int, user_id: int, text: str):
        parts = text.strip().split()
        if len(parts) != 2:
            return
        cmd, target_str = parts[0].lower(), parts[1]
        if not target_str.isdigit():
            return
        target = int(target_str)
        if cmd not in {"kill", "heal", "check", "vote"}:
            return

        if self._is_chat(peer_id):
            chat_id = self._peer_to_chat_id(peer_id)
        else:
            chat_id = None
            for g in self.games.values():
                if str(user_id) in g.get("players", {}):
                    chat_id = g["chat_id"]
                    break
        if not chat_id:
            return
        game = self._get_game(chat_id)
        self._handle_action(game, user_id, cmd, target)

    def handle_message(self, event):
        msg = event.message
        peer_id = msg["peer_id"]
        user_id = msg["from_id"]
        text = (msg.get("text") or "").strip()

        payload_raw = msg.get("payload")
        if payload_raw:
            try:
                payload = json.loads(payload_raw)
                self._handle_payload(peer_id, user_id, payload)
                return
            except Exception:
                pass

        text_lower = text.lower()

        if self._is_chat(peer_id):
            chat_id = self._peer_to_chat_id(peer_id)
            game = self._get_game(chat_id)

            if text_lower in {"/mafia", "мафия", "игра"}:
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
                return
            if text_lower in {"/help", "помощь"}:
                self._send(peer_id, "Команды: мафия, войти, выйти, старт, статус, стоп")
                return
            if text_lower in {"войти", "join"}:
                self._add_player(game, user_id)
                self._send(peer_id, f"{self._mention(user_id)} в игре!")
                return
            if text_lower in {"выйти", "leave"}:
                if game["phase"] == PHASE_LOBBY:
                    self._remove_player(game, user_id)
                    self._send(peer_id, f"{self._mention(user_id)} вышел.")
                else:
                    self._player(game, user_id)["alive"] = False
                    self._send(peer_id, f"{self._mention(user_id)} покинул игру и считается мёртвым.")
                    self._check_win(game)
                return
            if text_lower in {"старт", "start"}:
                self._start_game(game, peer_id)
                return
            if text_lower in {"статус", "status"}:
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
                return
            if text_lower in {"стоп", "stop"}:
                if self.admin_id and user_id != self.admin_id:
                    self._send(peer_id, "Только админ может стопнуть игру.")
                else:
                    game["phase"] = PHASE_ENDED
                    game["phase_end"] = 0
                    self._send(peer_id, "🛑 Игра остановлена.")
                return

        self._handle_text_action(peer_id, user_id, text_lower)
 
Янв
71
51
Пользователь
фреймворк вк апи

код:
import json
import random
import threading
import time
from typing import Dict, Any, List, Optional

from vk_api.keyboard import VkKeyboard, VkKeyboardColor


ROLE_MAFIA = "мафия"
ROLE_DETECTIVE = "комиссар"
ROLE_DOCTOR = "доктор"
ROLE_CIVIL = "мирный"

PHASE_LOBBY = "lobby"
PHASE_NIGHT = "night"
PHASE_DAY = "day"
PHASE_VOTE = "vote"
PHASE_ENDED = "ended"


class MafiaModule:
    def __init__(
        self,
        vk,
        *,
        admin_id: int = 0,
        night_sec: int = 60,
        day_sec: int = 90,
        vote_sec: int = 45,
        min_players: int = 3,
        max_players: int = 999,
    ):
        self.vk = vk
        self.admin_id = admin_id
        self.night_sec = night_sec
        self.day_sec = day_sec
        self.vote_sec = vote_sec
        self.min_players = min_players
        self.max_players = max_players

        self.games: Dict[int, Dict[str, Any]] = {}
        self.lock = threading.Lock()
        self.name_cache: Dict[int, str] = {}

        t = threading.Thread(target=self._ticker_loop, daemon=True)
        t.start()

    def _ticker_loop(self):
        while True:
            try:
                self._tick()
            except Exception:
                pass
            time.sleep(1)

    def _tick(self):
        with self.lock:
            games = list(self.games.values())
        now = time.time()
        for game in games:
            if not game.get("phase_end"):
                continue
            if now < game["phase_end"]:
                continue
            phase = game.get("phase")
            if phase == PHASE_NIGHT:
                self._resolve_night(game)
            elif phase == PHASE_DAY:
                self._begin_vote(game)
            elif phase == PHASE_VOTE:
                self._resolve_vote(game)

    def _get_name(self, user_id: int) -> str:
        if user_id in self.name_cache:
            return self.name_cache[user_id]
        try:
            info = self.vk.users.get(user_ids=user_id)[0]
            name = info["first_name"]
        except Exception:
            name = str(user_id)
        self.name_cache[user_id] = name
        return name

    def _mention(self, user_id: int) -> str:
        return f"[id{user_id}|{self._get_name(user_id)}]"

    def _is_chat(self, peer_id: int) -> bool:
        return peer_id >= 2000000000

    def _peer_to_chat_id(self, peer_id: int) -> int:
        return peer_id - 2000000000

    def _send(self, peer_id: int, text: str, keyboard: Optional[VkKeyboard] = None):
        params = {"peer_id": peer_id, "random_id": random.randint(1, 10**9), "message": text}
        if keyboard is not None:
            params["keyboard"] = keyboard.get_keyboard()
        self.vk.messages.send(**params)

    def _new_game(self, chat_id: int) -> Dict[str, Any]:
        return {
            "chat_id": chat_id,
            "phase": PHASE_LOBBY,
            "players": {},
            "phase_end": 0,
            "night": {},
            "day_votes": {},
            "round": 0,
        }

    def _get_game(self, chat_id: int) -> Dict[str, Any]:
        with self.lock:
            game = self.games.get(chat_id)
            if not game:
                game = self._new_game(chat_id)
                self.games[chat_id] = game
        return game

    def _player(self, game: Dict[str, Any], user_id: int):
        return game["players"].get(str(user_id))

    def _list_players(self, game: Dict[str, Any], alive_only=False) -> List[int]:
        ids = []
        for uid_str, p in game["players"].items():
            if alive_only and not p.get("alive"):
                continue
            ids.append(int(uid_str))
        return ids

    def _add_player(self, game: Dict[str, Any], user_id: int):
        if str(user_id) in game["players"]:
            return
        if len(game["players"]) >= self.max_players:
            return
        game["players"][str(user_id)] = {
            "name": self._get_name(user_id),
            "alive": True,
            "role": None,
        }

    def _remove_player(self, game: Dict[str, Any], user_id: int):
        if str(user_id) in game["players"]:
            del game["players"][str(user_id)]

    def _assign_roles(self, game: Dict[str, Any]):
        ids = self._list_players(game)
        random.shuffle(ids)
        mafia_count = max(1, len(ids) // 3)
        mafia_ids = ids[:mafia_count]
        rest = ids[mafia_count:]
        detective = rest[0] if len(rest) > 0 else None
        doctor = rest[1] if len(rest) > 1 else None

        for uid in ids:
            role = ROLE_CIVIL
            if uid in mafia_ids:
                role = ROLE_MAFIA
            elif detective == uid:
                role = ROLE_DETECTIVE
            elif doctor == uid:
                role = ROLE_DOCTOR
            game["players"][str(uid)]["role"] = role

    def _send_roles(self, game: Dict[str, Any]):
        mafia_ids = [uid for uid in self._list_players(game) if self._player(game, uid)["role"] == ROLE_MAFIA]
        mafia_names = ", ".join(self._mention(uid) for uid in mafia_ids)
        for uid in self._list_players(game):
            role = self._player(game, uid)["role"]
            if role == ROLE_MAFIA:
                text = f"🕶 Роль: МАФИЯ\nСоюзники: {mafia_names}\nНочью выбирай жертву."
            elif role == ROLE_DETECTIVE:
                text = "🕵️ Роль: КОМИССАР. Ночью проверяй игроков."
            elif role == ROLE_DOCTOR:
                text = "🩺 Роль: ДОКТОР. Ночью выбирай, кого спасти."
            else:
                text = "🙂 Роль: МИРНЫЙ. Днём ищи мафию."
            self._send(uid, text)

    def _build_lobby_keyboard(self):
        kb = VkKeyboard(one_time=False)
        kb.add_button("✅ Войти", VkKeyboardColor.POSITIVE, payload={"action": "join"})
        kb.add_button("🚪 Выйти", VkKeyboardColor.NEGATIVE, payload={"action": "leave"})
        kb.add_line()
        kb.add_button("🚀 Старт", VkKeyboardColor.PRIMARY, payload={"action": "start"})
        kb.add_button("📊 Статус", VkKeyboardColor.SECONDARY, payload={"action": "status"})
        kb.add_line()
        kb.add_button("🛑 Стоп", VkKeyboardColor.NEGATIVE, payload={"action": "stop"})
        return kb

    def _build_targets_keyboard(self, targets: List[int], action: str, chat_id: int):
        if len(targets) == 0 or len(targets) > 10:
            return None
        kb = VkKeyboard(one_time=True, inline=True)
        for uid in targets:
            kb.add_button(
                self._get_name(uid),
                VkKeyboardColor.PRIMARY,
                payload={"action": action, "chat_id": chat_id, "target": uid},
            )
        return kb

    def _start_game(self, game: Dict[str, Any], peer_id: int):
        if game["phase"] != PHASE_LOBBY:
            self._send(peer_id, "Игра уже идёт.")
            return
        if len(game["players"]) < self.min_players:
            self._send(peer_id, f"Нужно минимум {self.min_players} игроков.")
            return
        self._assign_roles(game)
        self._send_roles(game)
        game["round"] = 1
        self._begin_night(game)
        self._send(peer_id, "🎬 Игра началась! Ночь наступает...")

    def _begin_night(self, game: Dict[str, Any]):
        game["phase"] = PHASE_NIGHT
        game["phase_end"] = time.time() + self.night_sec
        game["night"] = {"mafia_votes": {}, "doctor": None, "detective": None}

        chat_peer = game["chat_id"] + 2000000000
        self._send(chat_peer, "🌙 Ночь. Все засыпают...")

        alive = self._list_players(game, alive_only=True)
        mafia_ids = [uid for uid in alive if self._player(game, uid)["role"] == ROLE_MAFIA]
        targetable = [uid for uid in alive if self._player(game, uid)["role"] != ROLE_MAFIA]

        for uid in mafia_ids:
            kb = self._build_targets_keyboard(targetable, "kill", game["chat_id"])
            if kb:
                self._send(uid, "Выберите жертву:", keyboard=kb)
            else:
                self._send(uid, "Выберите жертву: kill ID")

        for uid in alive:
            role = self._player(game, uid)["role"]
            if role == ROLE_DOCTOR:
                kb = self._build_targets_keyboard(alive, "heal", game["chat_id"])
                self._send(uid, "Кого лечить?" if kb else "Кого лечить? heal ID", keyboard=kb)
            if role == ROLE_DETECTIVE:
                kb = self._build_targets_keyboard(alive, "check", game["chat_id"])
                self._send(uid, "Кого проверить?" if kb else "Кого проверить? check ID", keyboard=kb)

    def _resolve_night(self, game: Dict[str, Any]):
        alive = self._list_players(game, alive_only=True)
        mafia_votes = game["night"].get("mafia_votes", {})
        doctor_target = game["night"].get("doctor")
        detective_target = game["night"].get("detective")

        kill_target = None
        if mafia_votes:
            max_votes = max(mafia_votes.values())
            top = [int(uid) for uid, v in mafia_votes.items() if v == max_votes]
            kill_target = random.choice(top)

        killed = None
        if kill_target and kill_target in alive and kill_target != doctor_target:
            self._player(game, kill_target)["alive"] = False
            killed = kill_target

        if detective_target and detective_target in alive:
            role = self._player(game, detective_target)["role"]
            msg = "🔎 Проверка: МАФИЯ" if role == ROLE_MAFIA else "🔎 Проверка: НЕ мафия"
            det_id = next((u for u in alive if self._player(game, u)["role"] == ROLE_DETECTIVE), None)
            if det_id:
                self._send(det_id, msg)

        chat_peer = game["chat_id"] + 2000000000
        if killed:
            self._send(chat_peer, f"☀️ Утро. Ночью был убит: {self._mention(killed)}")
        else:
            self._send(chat_peer, "☀️ Утро. Ночь прошла без жертв.")

        if self._check_win(game):
            return

        game["phase"] = PHASE_DAY
        game["phase_end"] = time.time() + self.day_sec
        game["day_votes"] = {}
        self._send(chat_peer, "🗣 День. Обсуждаем. Скоро голосование.")

    def _begin_vote(self, game: Dict[str, Any]):
        game["phase"] = PHASE_VOTE
        game["phase_end"] = time.time() + self.vote_sec
        game["day_votes"] = {}

        chat_peer = game["chat_id"] + 2000000000
        alive = self._list_players(game, alive_only=True)
        names = [self._mention(uid) for uid in alive]
        self._send(chat_peer, "🗳 Голосование! Кого линчуем?\n" + "\n".join(names))

        kb = self._build_targets_keyboard(alive, "vote", game["chat_id"])
        if kb:
            self._send(chat_peer, "Выберите голос кнопкой:", keyboard=kb)
        else:
            self._send(chat_peer, "Напишите: vote ID")

    def _resolve_vote(self, game: Dict[str, Any]):
        votes = game.get("day_votes", {})
        alive = self._list_players(game, alive_only=True)
        counted = {}
        for voter, target in votes.items():
            if int(voter) not in alive:
                continue
            if target not in alive:
                continue
            counted[target] = counted.get(target, 0) + 1

        chat_peer = game["chat_id"] + 2000000000
        if not counted:
            self._send(chat_peer, "Никого не линчуем.")
        else:
            max_votes = max(counted.values())
            top = [uid for uid, v in counted.items() if v == max_votes]
            lynched = random.choice(top)
            self._player(game, lynched)["alive"] = False
            self._send(chat_peer, f"⚖️ Линчевали: {self._mention(lynched)}")

        if self._check_win(game):
            return
        game["round"] += 1
        self._begin_night(game)

    def _check_win(self, game: Dict[str, Any]) -> bool:
        alive = self._list_players(game, alive_only=True)
        mafia = [uid for uid in alive if self._player(game, uid)["role"] == ROLE_MAFIA]
        civ = [uid for uid in alive if self._player(game, uid)["role"] != ROLE_MAFIA]
        chat_peer = game["chat_id"] + 2000000000

        if len(mafia) == 0:
            self._send(chat_peer, "🏆 Победа мирных!")
            game["phase"] = PHASE_ENDED
            game["phase_end"] = 0
            return True
        if len(mafia) >= len(civ):
            self._send(chat_peer, "😈 Победа мафии!")
            game["phase"] = PHASE_ENDED
            game["phase_end"] = 0
            return True
        return False

    def _status_text(self, game: Dict[str, Any]) -> str:
        players = []
        for uid in self._list_players(game):
            p = self._player(game, uid)
            status = "✅" if p.get("alive") else "💀"
            players.append(f"{status} {self._mention(uid)}")
        phase_map = {
            PHASE_LOBBY: "лобби",
            PHASE_NIGHT: "ночь",
            PHASE_DAY: "день",
            PHASE_VOTE: "голосование",
            PHASE_ENDED: "завершена",
        }
        return (
            f"🎮 Мафия\nФаза: {phase_map.get(game['phase'], game['phase'])}\n"
            f"Раунд: {game.get('round', 0)}\n\n"
            f"Игроки ({len(game['players'])}):\n" + "\n".join(players)
        )

    def _handle_payload(self, peer_id: int, user_id: int, payload: Dict[str, Any]):
        action = payload.get("action")
        chat_id = payload.get("chat_id")
        if action in {"join", "leave", "start", "status", "stop"}:
            if not self._is_chat(peer_id):
                return
            chat_id = self._peer_to_chat_id(peer_id)
            game = self._get_game(chat_id)
            if action == "join":
                self._add_player(game, user_id)
                self._send(peer_id, f"{self._mention(user_id)} в игре!")
            elif action == "leave":
                if game["phase"] == PHASE_LOBBY:
                    self._remove_player(game, user_id)
                    self._send(peer_id, f"{self._mention(user_id)} вышел.")
                else:
                    self._player(game, user_id)["alive"] = False
                    self._send(peer_id, f"{self._mention(user_id)} покинул игру и считается мёртвым.")
                    self._check_win(game)
            elif action == "start":
                self._start_game(game, peer_id)
            elif action == "status":
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
            elif action == "stop":
                if self.admin_id and user_id != self.admin_id:
                    self._send(peer_id, "Только админ может стопнуть игру.")
                else:
                    game["phase"] = PHASE_ENDED
                    game["phase_end"] = 0
                    self._send(peer_id, "🛑 Игра остановлена.")
            return

        if action in {"kill", "heal", "check", "vote"}:
            if not chat_id:
                return
            game = self._get_game(int(chat_id))
            target = payload.get("target")
            if target is None:
                return
            self._handle_action(game, user_id, action, int(target))

    def _handle_action(self, game: Dict[str, Any], user_id: int, action: str, target: int):
        player = self._player(game, user_id)
        if not player or not player.get("alive"):
            return
        if game["phase"] == PHASE_NIGHT:
            if action == "kill" and player.get("role") == ROLE_MAFIA:
                game["night"]["mafia_votes"][str(target)] = game["night"]["mafia_votes"].get(str(target), 0) + 1
                self._send(user_id, "✅ Голос за убийство принят.")
            elif action == "heal" and player.get("role") == ROLE_DOCTOR:
                game["night"]["doctor"] = target
                self._send(user_id, "✅ Лечение принято.")
            elif action == "check" and player.get("role") == ROLE_DETECTIVE:
                game["night"]["detective"] = target
                self._send(user_id, "✅ Проверка принята.")
        elif game["phase"] == PHASE_VOTE and action == "vote":
            if not self._player(game, target) or not self._player(game, target).get("alive"):
                return
            game["day_votes"][str(user_id)] = target
            chat_peer = game["chat_id"] + 2000000000
            self._send(chat_peer, f"🗳 {self._mention(user_id)} голосует.")

    def _handle_text_action(self, peer_id: int, user_id: int, text: str):
        parts = text.strip().split()
        if len(parts) != 2:
            return
        cmd, target_str = parts[0].lower(), parts[1]
        if not target_str.isdigit():
            return
        target = int(target_str)
        if cmd not in {"kill", "heal", "check", "vote"}:
            return

        if self._is_chat(peer_id):
            chat_id = self._peer_to_chat_id(peer_id)
        else:
            chat_id = None
            for g in self.games.values():
                if str(user_id) in g.get("players", {}):
                    chat_id = g["chat_id"]
                    break
        if not chat_id:
            return
        game = self._get_game(chat_id)
        self._handle_action(game, user_id, cmd, target)

    def handle_message(self, event):
        msg = event.message
        peer_id = msg["peer_id"]
        user_id = msg["from_id"]
        text = (msg.get("text") or "").strip()

        payload_raw = msg.get("payload")
        if payload_raw:
            try:
                payload = json.loads(payload_raw)
                self._handle_payload(peer_id, user_id, payload)
                return
            except Exception:
                pass

        text_lower = text.lower()

        if self._is_chat(peer_id):
            chat_id = self._peer_to_chat_id(peer_id)
            game = self._get_game(chat_id)

            if text_lower in {"/mafia", "мафия", "игра"}:
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
                return
            if text_lower in {"/help", "помощь"}:
                self._send(peer_id, "Команды: мафия, войти, выйти, старт, статус, стоп")
                return
            if text_lower in {"войти", "join"}:
                self._add_player(game, user_id)
                self._send(peer_id, f"{self._mention(user_id)} в игре!")
                return
            if text_lower in {"выйти", "leave"}:
                if game["phase"] == PHASE_LOBBY:
                    self._remove_player(game, user_id)
                    self._send(peer_id, f"{self._mention(user_id)} вышел.")
                else:
                    self._player(game, user_id)["alive"] = False
                    self._send(peer_id, f"{self._mention(user_id)} покинул игру и считается мёртвым.")
                    self._check_win(game)
                return
            if text_lower in {"старт", "start"}:
                self._start_game(game, peer_id)
                return
            if text_lower in {"статус", "status"}:
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
                return
            if text_lower in {"стоп", "stop"}:
                if self.admin_id and user_id != self.admin_id:
                    self._send(peer_id, "Только админ может стопнуть игру.")
                else:
                    game["phase"] = PHASE_ENDED
                    game["phase_end"] = 0
                    self._send(peer_id, "🛑 Игра остановлена.")
                return

        self._handle_text_action(peer_id, user_id, text_lower)
js bot work будет?
 
Янв
71
51
Пользователь
фреймворк вк апи

код:
import json
import random
import threading
import time
from typing import Dict, Any, List, Optional

from vk_api.keyboard import VkKeyboard, VkKeyboardColor


ROLE_MAFIA = "мафия"
ROLE_DETECTIVE = "комиссар"
ROLE_DOCTOR = "доктор"
ROLE_CIVIL = "мирный"

PHASE_LOBBY = "lobby"
PHASE_NIGHT = "night"
PHASE_DAY = "day"
PHASE_VOTE = "vote"
PHASE_ENDED = "ended"


class MafiaModule:
    def __init__(
        self,
        vk,
        *,
        admin_id: int = 0,
        night_sec: int = 60,
        day_sec: int = 90,
        vote_sec: int = 45,
        min_players: int = 3,
        max_players: int = 999,
    ):
        self.vk = vk
        self.admin_id = admin_id
        self.night_sec = night_sec
        self.day_sec = day_sec
        self.vote_sec = vote_sec
        self.min_players = min_players
        self.max_players = max_players

        self.games: Dict[int, Dict[str, Any]] = {}
        self.lock = threading.Lock()
        self.name_cache: Dict[int, str] = {}

        t = threading.Thread(target=self._ticker_loop, daemon=True)
        t.start()

    def _ticker_loop(self):
        while True:
            try:
                self._tick()
            except Exception:
                pass
            time.sleep(1)

    def _tick(self):
        with self.lock:
            games = list(self.games.values())
        now = time.time()
        for game in games:
            if not game.get("phase_end"):
                continue
            if now < game["phase_end"]:
                continue
            phase = game.get("phase")
            if phase == PHASE_NIGHT:
                self._resolve_night(game)
            elif phase == PHASE_DAY:
                self._begin_vote(game)
            elif phase == PHASE_VOTE:
                self._resolve_vote(game)

    def _get_name(self, user_id: int) -> str:
        if user_id in self.name_cache:
            return self.name_cache[user_id]
        try:
            info = self.vk.users.get(user_ids=user_id)[0]
            name = info["first_name"]
        except Exception:
            name = str(user_id)
        self.name_cache[user_id] = name
        return name

    def _mention(self, user_id: int) -> str:
        return f"[id{user_id}|{self._get_name(user_id)}]"

    def _is_chat(self, peer_id: int) -> bool:
        return peer_id >= 2000000000

    def _peer_to_chat_id(self, peer_id: int) -> int:
        return peer_id - 2000000000

    def _send(self, peer_id: int, text: str, keyboard: Optional[VkKeyboard] = None):
        params = {"peer_id": peer_id, "random_id": random.randint(1, 10**9), "message": text}
        if keyboard is not None:
            params["keyboard"] = keyboard.get_keyboard()
        self.vk.messages.send(**params)

    def _new_game(self, chat_id: int) -> Dict[str, Any]:
        return {
            "chat_id": chat_id,
            "phase": PHASE_LOBBY,
            "players": {},
            "phase_end": 0,
            "night": {},
            "day_votes": {},
            "round": 0,
        }

    def _get_game(self, chat_id: int) -> Dict[str, Any]:
        with self.lock:
            game = self.games.get(chat_id)
            if not game:
                game = self._new_game(chat_id)
                self.games[chat_id] = game
        return game

    def _player(self, game: Dict[str, Any], user_id: int):
        return game["players"].get(str(user_id))

    def _list_players(self, game: Dict[str, Any], alive_only=False) -> List[int]:
        ids = []
        for uid_str, p in game["players"].items():
            if alive_only and not p.get("alive"):
                continue
            ids.append(int(uid_str))
        return ids

    def _add_player(self, game: Dict[str, Any], user_id: int):
        if str(user_id) in game["players"]:
            return
        if len(game["players"]) >= self.max_players:
            return
        game["players"][str(user_id)] = {
            "name": self._get_name(user_id),
            "alive": True,
            "role": None,
        }

    def _remove_player(self, game: Dict[str, Any], user_id: int):
        if str(user_id) in game["players"]:
            del game["players"][str(user_id)]

    def _assign_roles(self, game: Dict[str, Any]):
        ids = self._list_players(game)
        random.shuffle(ids)
        mafia_count = max(1, len(ids) // 3)
        mafia_ids = ids[:mafia_count]
        rest = ids[mafia_count:]
        detective = rest[0] if len(rest) > 0 else None
        doctor = rest[1] if len(rest) > 1 else None

        for uid in ids:
            role = ROLE_CIVIL
            if uid in mafia_ids:
                role = ROLE_MAFIA
            elif detective == uid:
                role = ROLE_DETECTIVE
            elif doctor == uid:
                role = ROLE_DOCTOR
            game["players"][str(uid)]["role"] = role

    def _send_roles(self, game: Dict[str, Any]):
        mafia_ids = [uid for uid in self._list_players(game) if self._player(game, uid)["role"] == ROLE_MAFIA]
        mafia_names = ", ".join(self._mention(uid) for uid in mafia_ids)
        for uid in self._list_players(game):
            role = self._player(game, uid)["role"]
            if role == ROLE_MAFIA:
                text = f"🕶 Роль: МАФИЯ\nСоюзники: {mafia_names}\nНочью выбирай жертву."
            elif role == ROLE_DETECTIVE:
                text = "🕵️ Роль: КОМИССАР. Ночью проверяй игроков."
            elif role == ROLE_DOCTOR:
                text = "🩺 Роль: ДОКТОР. Ночью выбирай, кого спасти."
            else:
                text = "🙂 Роль: МИРНЫЙ. Днём ищи мафию."
            self._send(uid, text)

    def _build_lobby_keyboard(self):
        kb = VkKeyboard(one_time=False)
        kb.add_button("✅ Войти", VkKeyboardColor.POSITIVE, payload={"action": "join"})
        kb.add_button("🚪 Выйти", VkKeyboardColor.NEGATIVE, payload={"action": "leave"})
        kb.add_line()
        kb.add_button("🚀 Старт", VkKeyboardColor.PRIMARY, payload={"action": "start"})
        kb.add_button("📊 Статус", VkKeyboardColor.SECONDARY, payload={"action": "status"})
        kb.add_line()
        kb.add_button("🛑 Стоп", VkKeyboardColor.NEGATIVE, payload={"action": "stop"})
        return kb

    def _build_targets_keyboard(self, targets: List[int], action: str, chat_id: int):
        if len(targets) == 0 or len(targets) > 10:
            return None
        kb = VkKeyboard(one_time=True, inline=True)
        for uid in targets:
            kb.add_button(
                self._get_name(uid),
                VkKeyboardColor.PRIMARY,
                payload={"action": action, "chat_id": chat_id, "target": uid},
            )
        return kb

    def _start_game(self, game: Dict[str, Any], peer_id: int):
        if game["phase"] != PHASE_LOBBY:
            self._send(peer_id, "Игра уже идёт.")
            return
        if len(game["players"]) < self.min_players:
            self._send(peer_id, f"Нужно минимум {self.min_players} игроков.")
            return
        self._assign_roles(game)
        self._send_roles(game)
        game["round"] = 1
        self._begin_night(game)
        self._send(peer_id, "🎬 Игра началась! Ночь наступает...")

    def _begin_night(self, game: Dict[str, Any]):
        game["phase"] = PHASE_NIGHT
        game["phase_end"] = time.time() + self.night_sec
        game["night"] = {"mafia_votes": {}, "doctor": None, "detective": None}

        chat_peer = game["chat_id"] + 2000000000
        self._send(chat_peer, "🌙 Ночь. Все засыпают...")

        alive = self._list_players(game, alive_only=True)
        mafia_ids = [uid for uid in alive if self._player(game, uid)["role"] == ROLE_MAFIA]
        targetable = [uid for uid in alive if self._player(game, uid)["role"] != ROLE_MAFIA]

        for uid in mafia_ids:
            kb = self._build_targets_keyboard(targetable, "kill", game["chat_id"])
            if kb:
                self._send(uid, "Выберите жертву:", keyboard=kb)
            else:
                self._send(uid, "Выберите жертву: kill ID")

        for uid in alive:
            role = self._player(game, uid)["role"]
            if role == ROLE_DOCTOR:
                kb = self._build_targets_keyboard(alive, "heal", game["chat_id"])
                self._send(uid, "Кого лечить?" if kb else "Кого лечить? heal ID", keyboard=kb)
            if role == ROLE_DETECTIVE:
                kb = self._build_targets_keyboard(alive, "check", game["chat_id"])
                self._send(uid, "Кого проверить?" if kb else "Кого проверить? check ID", keyboard=kb)

    def _resolve_night(self, game: Dict[str, Any]):
        alive = self._list_players(game, alive_only=True)
        mafia_votes = game["night"].get("mafia_votes", {})
        doctor_target = game["night"].get("doctor")
        detective_target = game["night"].get("detective")

        kill_target = None
        if mafia_votes:
            max_votes = max(mafia_votes.values())
            top = [int(uid) for uid, v in mafia_votes.items() if v == max_votes]
            kill_target = random.choice(top)

        killed = None
        if kill_target and kill_target in alive and kill_target != doctor_target:
            self._player(game, kill_target)["alive"] = False
            killed = kill_target

        if detective_target and detective_target in alive:
            role = self._player(game, detective_target)["role"]
            msg = "🔎 Проверка: МАФИЯ" if role == ROLE_MAFIA else "🔎 Проверка: НЕ мафия"
            det_id = next((u for u in alive if self._player(game, u)["role"] == ROLE_DETECTIVE), None)
            if det_id:
                self._send(det_id, msg)

        chat_peer = game["chat_id"] + 2000000000
        if killed:
            self._send(chat_peer, f"☀️ Утро. Ночью был убит: {self._mention(killed)}")
        else:
            self._send(chat_peer, "☀️ Утро. Ночь прошла без жертв.")

        if self._check_win(game):
            return

        game["phase"] = PHASE_DAY
        game["phase_end"] = time.time() + self.day_sec
        game["day_votes"] = {}
        self._send(chat_peer, "🗣 День. Обсуждаем. Скоро голосование.")

    def _begin_vote(self, game: Dict[str, Any]):
        game["phase"] = PHASE_VOTE
        game["phase_end"] = time.time() + self.vote_sec
        game["day_votes"] = {}

        chat_peer = game["chat_id"] + 2000000000
        alive = self._list_players(game, alive_only=True)
        names = [self._mention(uid) for uid in alive]
        self._send(chat_peer, "🗳 Голосование! Кого линчуем?\n" + "\n".join(names))

        kb = self._build_targets_keyboard(alive, "vote", game["chat_id"])
        if kb:
            self._send(chat_peer, "Выберите голос кнопкой:", keyboard=kb)
        else:
            self._send(chat_peer, "Напишите: vote ID")

    def _resolve_vote(self, game: Dict[str, Any]):
        votes = game.get("day_votes", {})
        alive = self._list_players(game, alive_only=True)
        counted = {}
        for voter, target in votes.items():
            if int(voter) not in alive:
                continue
            if target not in alive:
                continue
            counted[target] = counted.get(target, 0) + 1

        chat_peer = game["chat_id"] + 2000000000
        if not counted:
            self._send(chat_peer, "Никого не линчуем.")
        else:
            max_votes = max(counted.values())
            top = [uid for uid, v in counted.items() if v == max_votes]
            lynched = random.choice(top)
            self._player(game, lynched)["alive"] = False
            self._send(chat_peer, f"⚖️ Линчевали: {self._mention(lynched)}")

        if self._check_win(game):
            return
        game["round"] += 1
        self._begin_night(game)

    def _check_win(self, game: Dict[str, Any]) -> bool:
        alive = self._list_players(game, alive_only=True)
        mafia = [uid for uid in alive if self._player(game, uid)["role"] == ROLE_MAFIA]
        civ = [uid for uid in alive if self._player(game, uid)["role"] != ROLE_MAFIA]
        chat_peer = game["chat_id"] + 2000000000

        if len(mafia) == 0:
            self._send(chat_peer, "🏆 Победа мирных!")
            game["phase"] = PHASE_ENDED
            game["phase_end"] = 0
            return True
        if len(mafia) >= len(civ):
            self._send(chat_peer, "😈 Победа мафии!")
            game["phase"] = PHASE_ENDED
            game["phase_end"] = 0
            return True
        return False

    def _status_text(self, game: Dict[str, Any]) -> str:
        players = []
        for uid in self._list_players(game):
            p = self._player(game, uid)
            status = "✅" if p.get("alive") else "💀"
            players.append(f"{status} {self._mention(uid)}")
        phase_map = {
            PHASE_LOBBY: "лобби",
            PHASE_NIGHT: "ночь",
            PHASE_DAY: "день",
            PHASE_VOTE: "голосование",
            PHASE_ENDED: "завершена",
        }
        return (
            f"🎮 Мафия\nФаза: {phase_map.get(game['phase'], game['phase'])}\n"
            f"Раунд: {game.get('round', 0)}\n\n"
            f"Игроки ({len(game['players'])}):\n" + "\n".join(players)
        )

    def _handle_payload(self, peer_id: int, user_id: int, payload: Dict[str, Any]):
        action = payload.get("action")
        chat_id = payload.get("chat_id")
        if action in {"join", "leave", "start", "status", "stop"}:
            if not self._is_chat(peer_id):
                return
            chat_id = self._peer_to_chat_id(peer_id)
            game = self._get_game(chat_id)
            if action == "join":
                self._add_player(game, user_id)
                self._send(peer_id, f"{self._mention(user_id)} в игре!")
            elif action == "leave":
                if game["phase"] == PHASE_LOBBY:
                    self._remove_player(game, user_id)
                    self._send(peer_id, f"{self._mention(user_id)} вышел.")
                else:
                    self._player(game, user_id)["alive"] = False
                    self._send(peer_id, f"{self._mention(user_id)} покинул игру и считается мёртвым.")
                    self._check_win(game)
            elif action == "start":
                self._start_game(game, peer_id)
            elif action == "status":
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
            elif action == "stop":
                if self.admin_id and user_id != self.admin_id:
                    self._send(peer_id, "Только админ может стопнуть игру.")
                else:
                    game["phase"] = PHASE_ENDED
                    game["phase_end"] = 0
                    self._send(peer_id, "🛑 Игра остановлена.")
            return

        if action in {"kill", "heal", "check", "vote"}:
            if not chat_id:
                return
            game = self._get_game(int(chat_id))
            target = payload.get("target")
            if target is None:
                return
            self._handle_action(game, user_id, action, int(target))

    def _handle_action(self, game: Dict[str, Any], user_id: int, action: str, target: int):
        player = self._player(game, user_id)
        if not player or not player.get("alive"):
            return
        if game["phase"] == PHASE_NIGHT:
            if action == "kill" and player.get("role") == ROLE_MAFIA:
                game["night"]["mafia_votes"][str(target)] = game["night"]["mafia_votes"].get(str(target), 0) + 1
                self._send(user_id, "✅ Голос за убийство принят.")
            elif action == "heal" and player.get("role") == ROLE_DOCTOR:
                game["night"]["doctor"] = target
                self._send(user_id, "✅ Лечение принято.")
            elif action == "check" and player.get("role") == ROLE_DETECTIVE:
                game["night"]["detective"] = target
                self._send(user_id, "✅ Проверка принята.")
        elif game["phase"] == PHASE_VOTE and action == "vote":
            if not self._player(game, target) or not self._player(game, target).get("alive"):
                return
            game["day_votes"][str(user_id)] = target
            chat_peer = game["chat_id"] + 2000000000
            self._send(chat_peer, f"🗳 {self._mention(user_id)} голосует.")

    def _handle_text_action(self, peer_id: int, user_id: int, text: str):
        parts = text.strip().split()
        if len(parts) != 2:
            return
        cmd, target_str = parts[0].lower(), parts[1]
        if not target_str.isdigit():
            return
        target = int(target_str)
        if cmd not in {"kill", "heal", "check", "vote"}:
            return

        if self._is_chat(peer_id):
            chat_id = self._peer_to_chat_id(peer_id)
        else:
            chat_id = None
            for g in self.games.values():
                if str(user_id) in g.get("players", {}):
                    chat_id = g["chat_id"]
                    break
        if not chat_id:
            return
        game = self._get_game(chat_id)
        self._handle_action(game, user_id, cmd, target)

    def handle_message(self, event):
        msg = event.message
        peer_id = msg["peer_id"]
        user_id = msg["from_id"]
        text = (msg.get("text") or "").strip()

        payload_raw = msg.get("payload")
        if payload_raw:
            try:
                payload = json.loads(payload_raw)
                self._handle_payload(peer_id, user_id, payload)
                return
            except Exception:
                pass

        text_lower = text.lower()

        if self._is_chat(peer_id):
            chat_id = self._peer_to_chat_id(peer_id)
            game = self._get_game(chat_id)

            if text_lower in {"/mafia", "мафия", "игра"}:
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
                return
            if text_lower in {"/help", "помощь"}:
                self._send(peer_id, "Команды: мафия, войти, выйти, старт, статус, стоп")
                return
            if text_lower in {"войти", "join"}:
                self._add_player(game, user_id)
                self._send(peer_id, f"{self._mention(user_id)} в игре!")
                return
            if text_lower in {"выйти", "leave"}:
                if game["phase"] == PHASE_LOBBY:
                    self._remove_player(game, user_id)
                    self._send(peer_id, f"{self._mention(user_id)} вышел.")
                else:
                    self._player(game, user_id)["alive"] = False
                    self._send(peer_id, f"{self._mention(user_id)} покинул игру и считается мёртвым.")
                    self._check_win(game)
                return
            if text_lower in {"старт", "start"}:
                self._start_game(game, peer_id)
                return
            if text_lower in {"статус", "status"}:
                self._send(peer_id, self._status_text(game), keyboard=self._build_lobby_keyboard())
                return
            if text_lower in {"стоп", "stop"}:
                if self.admin_id and user_id != self.admin_id:
                    self._send(peer_id, "Только админ может стопнуть игру.")
                else:
                    game["phase"] = PHASE_ENDED
                    game["phase_end"] = 0
                    self._send(peer_id, "🛑 Игра остановлена.")
                return

        self._handle_text_action(peer_id, user_id, text_lower)
это же python, a можешь для js скинут пожалуйста ну или это js работат будет?
 
Янв
243
539
Продавец
JSON:
// mafia_module.js
const { Keyboard } = require("vk-io");

const ROLE_MAFIA = "мафия";
const ROLE_DETECTIVE = "комиссар";
const ROLE_DOCTOR = "доктор";
const ROLE_CIVIL = "мирный";

const PHASE_LOBBY = "lobby";
const PHASE_NIGHT = "night";
const PHASE_DAY = "day";
const PHASE_VOTE = "vote";
const PHASE_ENDED = "ended";

class MafiaModule {
  constructor(vk, {
    adminId = 0,
    nightSec = 60,
    daySec = 90,
    voteSec = 45,
    minPlayers = 3,
    maxPlayers = 999,
  } = {}) {
    this.vk = vk;
    this.adminId = adminId;
    this.nightSec = nightSec;
    this.daySec = daySec;
    this.voteSec = voteSec;
    this.minPlayers = minPlayers;
    this.maxPlayers = maxPlayers;

    this.games = new Map();
    this.nameCache = new Map();

    setInterval(() => this._tick(), 1000);
  }

  _isChat(peerId) {
    return peerId >= 2000000000;
  }

  _peerToChatId(peerId) {
    return peerId - 2000000000;
  }

  async _getName(userId) {
    if (this.nameCache.has(userId)) return this.nameCache.get(userId);
    try {
      const [info] = await this.vk.api.users.get({ user_ids: userId });
      const name = info.first_name || String(userId);
      this.nameCache.set(userId, name);
      return name;
    } catch {
      return String(userId);
    }
  }

  async _mention(userId) {
    const name = await this._getName(userId);
    return `[id${userId}|${name}]`;
  }

  async _send(peerId, text, keyboard = null) {
    const params = { peer_id: peerId, random_id: Date.now(), message: text };
    if (keyboard) params.keyboard = keyboard;
    await this.vk.api.messages.send(params);
  }

  _newGame(chatId) {
    return {
      chatId,
      phase: PHASE_LOBBY,
      players: {},
      phaseEnd: 0,
      night: {},
      dayVotes: {},
      round: 0,
    };
  }

  _getGame(chatId) {
    if (!this.games.has(chatId)) this.games.set(chatId, this._newGame(chatId));
    return this.games.get(chatId);
  }

  _player(game, userId) {
    return game.players[String(userId)];
  }

  _listPlayers(game, aliveOnly = false) {
    const ids = [];
    for (const [uid, p] of Object.entries(game.players)) {
      if (aliveOnly && !p.alive) continue;
      ids.push(Number(uid));
    }
    return ids;
  }

  async _addPlayer(game, userId) {
    if (game.players[String(userId)]) return;
    if (Object.keys(game.players).length >= this.maxPlayers) return;
    const name = await this._getName(userId);
    game.players[String(userId)] = { name, alive: true, role: null };
  }

  _removePlayer(game, userId) {
    delete game.players[String(userId)];
  }

  _assignRoles(game) {
    const ids = this._listPlayers(game);
    for (let i = ids.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [ids[i], ids[j]] = [ids[j], ids[i]];
    }
    const mafiaCount = Math.max(1, Math.floor(ids.length / 3));
    const mafiaIds = ids.slice(0, mafiaCount);
    const rest = ids.slice(mafiaCount);
    const detective = rest[0] || null;
    const doctor = rest[1] || null;

    for (const uid of ids) {
      let role = ROLE_CIVIL;
      if (mafiaIds.includes(uid)) role = ROLE_MAFIA;
      else if (uid === detective) role = ROLE_DETECTIVE;
      else if (uid === doctor) role = ROLE_DOCTOR;
      game.players[String(uid)].role = role;
    }
  }

  async _sendRoles(game) {
    const mafiaIds = this._listPlayers(game).filter(
      (uid) => this._player(game, uid).role === ROLE_MAFIA
    );
    const mafiaNames = (await Promise.all(mafiaIds.map((u) => this._mention(u)))).join(", ");
    for (const uid of this._listPlayers(game)) {
      const role = this._player(game, uid).role;
      let text = "🙂 Роль: МИРНЫЙ. Днём ищи мафию.";
      if (role === ROLE_MAFIA) text = `🕶 Роль: МАФИЯ\nСоюзники: ${mafiaNames}\nНочью выбирай жертву.`;
      if (role === ROLE_DETECTIVE) text = "🕵️ Роль: КОМИССАР. Ночью проверяй игроков.";
      if (role === ROLE_DOCTOR) text = "🩺 Роль: ДОКТОР. Ночью выбирай, кого спасти.";
      await this._send(uid, text);
    }
  }

  _buildLobbyKeyboard() {
    return Keyboard.builder()
      .textButton({ label: "✅ Войти", color: Keyboard.POSITIVE_COLOR, payload: { action: "join" } })
      .textButton({ label: "🚪 Выйти", color: Keyboard.NEGATIVE_COLOR, payload: { action: "leave" } })
      .row()
      .textButton({ label: "🚀 Старт", color: Keyboard.PRIMARY_COLOR, payload: { action: "start" } })
      .textButton({ label: "📊 Статус", color: Keyboard.SECONDARY_COLOR, payload: { action: "status" } })
      .row()
      .textButton({ label: "🛑 Стоп", color: Keyboard.NEGATIVE_COLOR, payload: { action: "stop" } })
      .inline(false)
      .oneTime(false)
      .build();
  }

  _buildTargetsKeyboard(targets, action, chatId) {
    if (targets.length === 0 || targets.length > 10) return null;
    const kb = Keyboard.builder().inline().oneTime();
    for (const uid of targets) {
      kb.textButton({
        label: String(uid),
        color: Keyboard.PRIMARY_COLOR,
        payload: { action, chatId, target: uid },
      });
    }
    return kb.build();
  }

  async _startGame(game, peerId) {
    if (game.phase !== PHASE_LOBBY) {
      await this._send(peerId, "Игра уже идёт.");
      return;
    }
    if (Object.keys(game.players).length < this.minPlayers) {
      await this._send(peerId, `Нужно минимум ${this.minPlayers} игроков.`);
      return;
    }
    this._assignRoles(game);
    await this._sendRoles(game);
    game.round = 1;
    await this._beginNight(game);
    await this._send(peerId, "🎬 Игра началась! Ночь наступает...");
  }

  async _beginNight(game) {
    game.phase = PHASE_NIGHT;
    game.phaseEnd = Date.now() + this.nightSec * 1000;
    game.night = { mafiaVotes: {}, doctor: null, detective: null };

    const chatPeer = game.chatId + 2000000000;
    await this._send(chatPeer, "🌙 Ночь. Все засыпают...");

    const alive = this._listPlayers(game, true);
    const mafiaIds = alive.filter((u) => this._player(game, u).role === ROLE_MAFIA);
    const targetable = alive.filter((u) => this._player(game, u).role !== ROLE_MAFIA);

    for (const uid of mafiaIds) {
      const kb = this._buildTargetsKeyboard(targetable, "kill", game.chatId);
      if (kb) await this._send(uid, "Выберите жертву:", kb);
      else await this._send(uid, "Выберите жертву: kill ID");
    }

    for (const uid of alive) {
      const role = this._player(game, uid).role;
      if (role === ROLE_DOCTOR) {
        const kb = this._buildTargetsKeyboard(alive, "heal", game.chatId);
        await this._send(uid, kb ? "Кого лечить?" : "Кого лечить? heal ID", kb);
      }
      if (role === ROLE_DETECTIVE) {
        const kb = this._buildTargetsKeyboard(alive, "check", game.chatId);
        await this._send(uid, kb ? "Кого проверить?" : "Кого проверить? check ID", kb);
      }
    }
  }

  async _resolveNight(game) {
    const alive = this._listPlayers(game, true);
    const mafiaVotes = game.night.mafiaVotes || {};
    const doctorTarget = game.night.doctor;
    const detectiveTarget = game.night.detective;

    let killTarget = null;
    const entries = Object.entries(mafiaVotes);
    if (entries.length > 0) {
      const maxVotes = Math.max(...entries.map(([, v]) => v));
      const top = entries.filter(([, v]) => v === maxVotes).map(([u]) => Number(u));
      killTarget = top[Math.floor(Math.random() * top.length)];
    }

    let killed = null;
    if (killTarget && alive.includes(killTarget) && killTarget !== doctorTarget) {
      this._player(game, killTarget).alive = false;
      killed = killTarget;
    }

    if (detectiveTarget && alive.includes(detectiveTarget)) {
      const role = this._player(game, detectiveTarget).role;
      const msg = role === ROLE_MAFIA ? "🔎 Проверка: МАФИЯ" : "🔎 Проверка: НЕ мафия";
      const detId = alive.find((u) => this._player(game, u).role === ROLE_DETECTIVE);
      if (detId) await this._send(detId, msg);
    }

    const chatPeer = game.chatId + 2000000000;
    if (killed) await this._send(chatPeer, `☀️ Утро. Ночью был убит: ${await this._mention(killed)}`);
    else await this._send(chatPeer, "☀️ Утро. Ночь прошла без жертв.");

    if (await this._checkWin(game)) return;

    game.phase = PHASE_DAY;
    game.phaseEnd = Date.now() + this.daySec * 1000;
    game.dayVotes = {};
    await this._send(chatPeer, "🗣 День. Обсуждаем. Скоро голосование.");
  }

  async _beginVote(game) {
    game.phase = PHASE_VOTE;
    game.phaseEnd = Date.now() + this.voteSec * 1000;
    game.dayVotes = {};

    const chatPeer = game.chatId + 2000000000;
    const alive = this._listPlayers(game, true);
    const names = await Promise.all(alive.map((u) => this._mention(u)));
    await this._send(chatPeer, "🗳 Голосование! Кого линчуем?\n" + names.join("\n"));

    const kb = this._buildTargetsKeyboard(alive, "vote", game.chatId);
    if (kb) await this._send(chatPeer, "Выберите голос кнопкой:", kb);
    else await this._send(chatPeer, "Напишите: vote ID");
  }

  async _resolveVote(game) {
    const votes = game.dayVotes || {};
    const alive = this._listPlayers(game, true);
    const counted = {};
    for (const [voter, target] of Object.entries(votes)) {
      if (!alive.includes(Number(voter))) continue;
      if (!alive.includes(target)) continue;
      counted[target] = (counted[target] || 0) + 1;
    }

    const chatPeer = game.chatId + 2000000000;
    const entries = Object.entries(counted);
    if (entries.length === 0) {
      await this._send(chatPeer, "Никого не линчуем.");
    } else {
      const maxVotes = Math.max(...entries.map(([, v]) => v));
      const top = entries.filter(([, v]) => v === maxVotes).map(([u]) => Number(u));
      const lynched = top[Math.floor(Math.random() * top.length)];
      this._player(game, lynched).alive = false;
      await this._send(chatPeer, `⚖️ Линчевали: ${await this._mention(lynched)}`);
    }

    if (await this._checkWin(game)) return;
    game.round += 1;
    await this._beginNight(game);
  }

  async _checkWin(game) {
    const alive = this._listPlayers(game, true);
    const mafia = alive.filter((u) => this._player(game, u).role === ROLE_MAFIA);
    const civ = alive.filter((u) => this._player(game, u).role !== ROLE_MAFIA);
    const chatPeer = game.chatId + 2000000000;

    if (mafia.length === 0) {
      await this._send(chatPeer, "🏆 Победа мирных!");
      game.phase = PHASE_ENDED;
      game.phaseEnd = 0;
      return true;
    }
    if (mafia.length >= civ.length) {
      await this._send(chatPeer, "😈 Победа мафии!");
      game.phase = PHASE_ENDED;
      game.phaseEnd = 0;
      return true;
    }
    return false;
  }

  async _handlePayload(peerId, userId, payload) {
    const action = payload?.action;
    const chatId = payload?.chatId;
    if (["join", "leave", "start", "status", "stop"].includes(action)) {
      if (!this._isChat(peerId)) return;
      const game = this._getGame(this._peerToChatId(peerId));
      if (action === "join") {
        await this._addPlayer(game, userId);
        await this._send(peerId, `${await this._mention(userId)} в игре!`);
      } else if (action === "leave") {
        if (game.phase === PHASE_LOBBY) {
          this._removePlayer(game, userId);
          await this._send(peerId, `${await this._mention(userId)} вышел.`);
        } else {
          this._player(game, userId).alive = false;
          await this._send(peerId, `${await this._mention(userId)} покинул игру и считается мёртвым.`);
          await this._checkWin(game);
        }
      } else if (action === "start") {
        await this._startGame(game, peerId);
      } else if (action === "status") {
        await this._send(peerId, await this._statusText(game), this._buildLobbyKeyboard());
      } else if (action === "stop") {
        if (this.adminId && userId !== this.adminId) {
          await this._send(peerId, "Только админ может стопнуть игру.");
        } else {
          game.phase = PHASE_ENDED;
          game.phaseEnd = 0;
          await this._send(peerId, "🛑 Игра остановлена.");
        }
      }
      return;
    }

    if (["kill", "heal", "check", "vote"].includes(action)) {
      if (!chatId) return;
      const game = this._getGame(Number(chatId));
      const target = payload?.target;
      if (target == null) return;
      await this._handleAction(game, userId, action, Number(target));
    }
  }

  async _handleAction(game, userId, action, target) {
    const player = this._player(game, userId);
    if (!player || !player.alive) return;

    if (game.phase === PHASE_NIGHT) {
      if (action === "kill" && player.role === ROLE_MAFIA) {
        game.night.mafiaVotes[String(target)] = (game.night.mafiaVotes[String(target)] || 0) + 1;
        await this._send(userId, "✅ Голос за убийство принят.");
      } else if (action === "heal" && player.role === ROLE_DOCTOR) {
        game.night.doctor = target;
        await this._send(userId, "✅ Лечение принято.");
      } else if (action === "check" && player.role === ROLE_DETECTIVE) {
        game.night.detective = target;
        await this._send(userId, "✅ Проверка принята.");
      }
    } else if (game.phase === PHASE_VOTE && action === "vote") {
      if (!this._player(game, target) || !this._player(game, target).alive) return;
      game.dayVotes[String(userId)] = target;
      const chatPeer = game.chatId + 2000000000;
      await this._send(chatPeer, `🗳 ${await this._mention(userId)} голосует.`);
    }
  }

  async _handleTextAction(peerId, userId, text) {
    const parts = text.trim().split(/\s+/);
    if (parts.length !== 2) return;
    const cmd = parts[0].toLowerCase();
    const targetStr = parts[1];
    if (!/^\d+$/.test(targetStr)) return;
    const target = Number(targetStr);
    if (!["kill", "heal", "check", "vote"].includes(cmd)) return;

    let chatId = null;
    if (this._isChat(peerId)) chatId = this._peerToChatId(peerId);
    else {
      for (const g of this.games.values()) {
        if (g.players[String(userId)]) {
          chatId = g.chatId;
          break;
        }
      }
    }
    if (!chatId) return;
    const game = this._getGame(chatId);
    await this._handleAction(game, userId, cmd, target);
  }

  async _statusText(game) {
    const players = [];
    for (const uid of this._listPlayers(game)) {
      const p = this._player(game, uid);
      const status = p.alive ? "✅" : "💀";
      players.push(`${status} ${await this._mention(uid)}`);
    }
    const phaseMap = {
      [PHASE_LOBBY]: "лобби",
      [PHASE_NIGHT]: "ночь",
      [PHASE_DAY]: "день",
      [PHASE_VOTE]: "голосование",
      [PHASE_ENDED]: "завершена",
    };
    return `🎮 Мафия\nФаза: ${phaseMap[game.phase] || game.phase}\nРаунд: ${game.round}\n\nИгроки (${Object.keys(game.players).length}):\n${players.join("\n")}`;
  }

  async _tick() {
    const now = Date.now();
    for (const game of this.games.values()) {
      if (!game.phaseEnd || now < game.phaseEnd) continue;
      if (game.phase === PHASE_NIGHT) await this._resolveNight(game);
      else if (game.phase === PHASE_DAY) await this._beginVote(game);
      else if (game.phase === PHASE_VOTE) await this._resolveVote(game);
    }
  }

  async handleMessage(ctx) {
    const peerId = ctx.peerId;
    const userId = ctx.senderId;
    const text = (ctx.text || "").trim();

    if (ctx.messagePayload) {
      await this._handlePayload(peerId, userId, ctx.messagePayload);
      return;
    }

    const t = text.toLowerCase();

    if (this._isChat(peerId)) {
      const game = this._getGame(this._peerToChatId(peerId));

      if (["/mafia", "мафия", "игра"].includes(t)) {
        await this._send(peerId, await this._statusText(game), this._buildLobbyKeyboard());
        return;
      }
      if (["/help", "помощь"].includes(t)) {
        await this._send(peerId, "Команды: мафия, войти, выйти, старт, статус, стоп");
        return;
      }
      if (["войти", "join"].includes(t)) {
        await this._addPlayer(game, userId);
        await this._send(peerId, `${await this._mention(userId)} в игре!`);
        return;
      }
      if (["выйти", "leave"].includes(t)) {
        if (game.phase === PHASE_LOBBY) {
          this._removePlayer(game, userId);
          await this._send(peerId, `${await this._mention(userId)} вышел.`);
        } else {
          this._player(game, userId).alive = false;
          await this._send(peerId, `${await this._mention(userId)} покинул игру и считается мёртвым.`);
          await this._checkWin(game);
        }
        return;
      }
      if (["старт", "start"].includes(t)) {
        await this._startGame(game, peerId);
        return;
      }
      if (["статус", "status"].includes(t)) {
        await this._send(peerId, await this._statusText(game), this._buildLobbyKeyboard());
        return;
      }
      if (["стоп", "stop"].includes(t)) {
        if (this.adminId && userId !== this.adminId) {
          await this._send(peerId, "Только админ может стопнуть игру.");
        } else {
          game.phase = PHASE_ENDED;
          game.phaseEnd = 0;
          await this._send(peerId, "🛑 Игра остановлена.");
        }
        return;
      }
    }

    await this._handleTextAction(peerId, userId, t);
  }
}

module.exports = { MafiaModule };
 
Янв
71
51
Пользователь
JSON:
// mafia_module.js
const { Keyboard } = require("vk-io");

const ROLE_MAFIA = "мафия";
const ROLE_DETECTIVE = "комиссар";
const ROLE_DOCTOR = "доктор";
const ROLE_CIVIL = "мирный";

const PHASE_LOBBY = "lobby";
const PHASE_NIGHT = "night";
const PHASE_DAY = "day";
const PHASE_VOTE = "vote";
const PHASE_ENDED = "ended";

class MafiaModule {
  constructor(vk, {
    adminId = 0,
    nightSec = 60,
    daySec = 90,
    voteSec = 45,
    minPlayers = 3,
    maxPlayers = 999,
  } = {}) {
    this.vk = vk;
    this.adminId = adminId;
    this.nightSec = nightSec;
    this.daySec = daySec;
    this.voteSec = voteSec;
    this.minPlayers = minPlayers;
    this.maxPlayers = maxPlayers;

    this.games = new Map();
    this.nameCache = new Map();

    setInterval(() => this._tick(), 1000);
  }

  _isChat(peerId) {
    return peerId >= 2000000000;
  }

  _peerToChatId(peerId) {
    return peerId - 2000000000;
  }

  async _getName(userId) {
    if (this.nameCache.has(userId)) return this.nameCache.get(userId);
    try {
      const [info] = await this.vk.api.users.get({ user_ids: userId });
      const name = info.first_name || String(userId);
      this.nameCache.set(userId, name);
      return name;
    } catch {
      return String(userId);
    }
  }

  async _mention(userId) {
    const name = await this._getName(userId);
    return `[id${userId}|${name}]`;
  }

  async _send(peerId, text, keyboard = null) {
    const params = { peer_id: peerId, random_id: Date.now(), message: text };
    if (keyboard) params.keyboard = keyboard;
    await this.vk.api.messages.send(params);
  }

  _newGame(chatId) {
    return {
      chatId,
      phase: PHASE_LOBBY,
      players: {},
      phaseEnd: 0,
      night: {},
      dayVotes: {},
      round: 0,
    };
  }

  _getGame(chatId) {
    if (!this.games.has(chatId)) this.games.set(chatId, this._newGame(chatId));
    return this.games.get(chatId);
  }

  _player(game, userId) {
    return game.players[String(userId)];
  }

  _listPlayers(game, aliveOnly = false) {
    const ids = [];
    for (const [uid, p] of Object.entries(game.players)) {
      if (aliveOnly && !p.alive) continue;
      ids.push(Number(uid));
    }
    return ids;
  }

  async _addPlayer(game, userId) {
    if (game.players[String(userId)]) return;
    if (Object.keys(game.players).length >= this.maxPlayers) return;
    const name = await this._getName(userId);
    game.players[String(userId)] = { name, alive: true, role: null };
  }

  _removePlayer(game, userId) {
    delete game.players[String(userId)];
  }

  _assignRoles(game) {
    const ids = this._listPlayers(game);
    for (let i = ids.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [ids[i], ids[j]] = [ids[j], ids[i]];
    }
    const mafiaCount = Math.max(1, Math.floor(ids.length / 3));
    const mafiaIds = ids.slice(0, mafiaCount);
    const rest = ids.slice(mafiaCount);
    const detective = rest[0] || null;
    const doctor = rest[1] || null;

    for (const uid of ids) {
      let role = ROLE_CIVIL;
      if (mafiaIds.includes(uid)) role = ROLE_MAFIA;
      else if (uid === detective) role = ROLE_DETECTIVE;
      else if (uid === doctor) role = ROLE_DOCTOR;
      game.players[String(uid)].role = role;
    }
  }

  async _sendRoles(game) {
    const mafiaIds = this._listPlayers(game).filter(
      (uid) => this._player(game, uid).role === ROLE_MAFIA
    );
    const mafiaNames = (await Promise.all(mafiaIds.map((u) => this._mention(u)))).join(", ");
    for (const uid of this._listPlayers(game)) {
      const role = this._player(game, uid).role;
      let text = "🙂 Роль: МИРНЫЙ. Днём ищи мафию.";
      if (role === ROLE_MAFIA) text = `🕶 Роль: МАФИЯ\nСоюзники: ${mafiaNames}\nНочью выбирай жертву.`;
      if (role === ROLE_DETECTIVE) text = "🕵️ Роль: КОМИССАР. Ночью проверяй игроков.";
      if (role === ROLE_DOCTOR) text = "🩺 Роль: ДОКТОР. Ночью выбирай, кого спасти.";
      await this._send(uid, text);
    }
  }

  _buildLobbyKeyboard() {
    return Keyboard.builder()
      .textButton({ label: "✅ Войти", color: Keyboard.POSITIVE_COLOR, payload: { action: "join" } })
      .textButton({ label: "🚪 Выйти", color: Keyboard.NEGATIVE_COLOR, payload: { action: "leave" } })
      .row()
      .textButton({ label: "🚀 Старт", color: Keyboard.PRIMARY_COLOR, payload: { action: "start" } })
      .textButton({ label: "📊 Статус", color: Keyboard.SECONDARY_COLOR, payload: { action: "status" } })
      .row()
      .textButton({ label: "🛑 Стоп", color: Keyboard.NEGATIVE_COLOR, payload: { action: "stop" } })
      .inline(false)
      .oneTime(false)
      .build();
  }

  _buildTargetsKeyboard(targets, action, chatId) {
    if (targets.length === 0 || targets.length > 10) return null;
    const kb = Keyboard.builder().inline().oneTime();
    for (const uid of targets) {
      kb.textButton({
        label: String(uid),
        color: Keyboard.PRIMARY_COLOR,
        payload: { action, chatId, target: uid },
      });
    }
    return kb.build();
  }

  async _startGame(game, peerId) {
    if (game.phase !== PHASE_LOBBY) {
      await this._send(peerId, "Игра уже идёт.");
      return;
    }
    if (Object.keys(game.players).length < this.minPlayers) {
      await this._send(peerId, `Нужно минимум ${this.minPlayers} игроков.`);
      return;
    }
    this._assignRoles(game);
    await this._sendRoles(game);
    game.round = 1;
    await this._beginNight(game);
    await this._send(peerId, "🎬 Игра началась! Ночь наступает...");
  }

  async _beginNight(game) {
    game.phase = PHASE_NIGHT;
    game.phaseEnd = Date.now() + this.nightSec * 1000;
    game.night = { mafiaVotes: {}, doctor: null, detective: null };

    const chatPeer = game.chatId + 2000000000;
    await this._send(chatPeer, "🌙 Ночь. Все засыпают...");

    const alive = this._listPlayers(game, true);
    const mafiaIds = alive.filter((u) => this._player(game, u).role === ROLE_MAFIA);
    const targetable = alive.filter((u) => this._player(game, u).role !== ROLE_MAFIA);

    for (const uid of mafiaIds) {
      const kb = this._buildTargetsKeyboard(targetable, "kill", game.chatId);
      if (kb) await this._send(uid, "Выберите жертву:", kb);
      else await this._send(uid, "Выберите жертву: kill ID");
    }

    for (const uid of alive) {
      const role = this._player(game, uid).role;
      if (role === ROLE_DOCTOR) {
        const kb = this._buildTargetsKeyboard(alive, "heal", game.chatId);
        await this._send(uid, kb ? "Кого лечить?" : "Кого лечить? heal ID", kb);
      }
      if (role === ROLE_DETECTIVE) {
        const kb = this._buildTargetsKeyboard(alive, "check", game.chatId);
        await this._send(uid, kb ? "Кого проверить?" : "Кого проверить? check ID", kb);
      }
    }
  }

  async _resolveNight(game) {
    const alive = this._listPlayers(game, true);
    const mafiaVotes = game.night.mafiaVotes || {};
    const doctorTarget = game.night.doctor;
    const detectiveTarget = game.night.detective;

    let killTarget = null;
    const entries = Object.entries(mafiaVotes);
    if (entries.length > 0) {
      const maxVotes = Math.max(...entries.map(([, v]) => v));
      const top = entries.filter(([, v]) => v === maxVotes).map(([u]) => Number(u));
      killTarget = top[Math.floor(Math.random() * top.length)];
    }

    let killed = null;
    if (killTarget && alive.includes(killTarget) && killTarget !== doctorTarget) {
      this._player(game, killTarget).alive = false;
      killed = killTarget;
    }

    if (detectiveTarget && alive.includes(detectiveTarget)) {
      const role = this._player(game, detectiveTarget).role;
      const msg = role === ROLE_MAFIA ? "🔎 Проверка: МАФИЯ" : "🔎 Проверка: НЕ мафия";
      const detId = alive.find((u) => this._player(game, u).role === ROLE_DETECTIVE);
      if (detId) await this._send(detId, msg);
    }

    const chatPeer = game.chatId + 2000000000;
    if (killed) await this._send(chatPeer, `☀️ Утро. Ночью был убит: ${await this._mention(killed)}`);
    else await this._send(chatPeer, "☀️ Утро. Ночь прошла без жертв.");

    if (await this._checkWin(game)) return;

    game.phase = PHASE_DAY;
    game.phaseEnd = Date.now() + this.daySec * 1000;
    game.dayVotes = {};
    await this._send(chatPeer, "🗣 День. Обсуждаем. Скоро голосование.");
  }

  async _beginVote(game) {
    game.phase = PHASE_VOTE;
    game.phaseEnd = Date.now() + this.voteSec * 1000;
    game.dayVotes = {};

    const chatPeer = game.chatId + 2000000000;
    const alive = this._listPlayers(game, true);
    const names = await Promise.all(alive.map((u) => this._mention(u)));
    await this._send(chatPeer, "🗳 Голосование! Кого линчуем?\n" + names.join("\n"));

    const kb = this._buildTargetsKeyboard(alive, "vote", game.chatId);
    if (kb) await this._send(chatPeer, "Выберите голос кнопкой:", kb);
    else await this._send(chatPeer, "Напишите: vote ID");
  }

  async _resolveVote(game) {
    const votes = game.dayVotes || {};
    const alive = this._listPlayers(game, true);
    const counted = {};
    for (const [voter, target] of Object.entries(votes)) {
      if (!alive.includes(Number(voter))) continue;
      if (!alive.includes(target)) continue;
      counted[target] = (counted[target] || 0) + 1;
    }

    const chatPeer = game.chatId + 2000000000;
    const entries = Object.entries(counted);
    if (entries.length === 0) {
      await this._send(chatPeer, "Никого не линчуем.");
    } else {
      const maxVotes = Math.max(...entries.map(([, v]) => v));
      const top = entries.filter(([, v]) => v === maxVotes).map(([u]) => Number(u));
      const lynched = top[Math.floor(Math.random() * top.length)];
      this._player(game, lynched).alive = false;
      await this._send(chatPeer, `⚖️ Линчевали: ${await this._mention(lynched)}`);
    }

    if (await this._checkWin(game)) return;
    game.round += 1;
    await this._beginNight(game);
  }

  async _checkWin(game) {
    const alive = this._listPlayers(game, true);
    const mafia = alive.filter((u) => this._player(game, u).role === ROLE_MAFIA);
    const civ = alive.filter((u) => this._player(game, u).role !== ROLE_MAFIA);
    const chatPeer = game.chatId + 2000000000;

    if (mafia.length === 0) {
      await this._send(chatPeer, "🏆 Победа мирных!");
      game.phase = PHASE_ENDED;
      game.phaseEnd = 0;
      return true;
    }
    if (mafia.length >= civ.length) {
      await this._send(chatPeer, "😈 Победа мафии!");
      game.phase = PHASE_ENDED;
      game.phaseEnd = 0;
      return true;
    }
    return false;
  }

  async _handlePayload(peerId, userId, payload) {
    const action = payload?.action;
    const chatId = payload?.chatId;
    if (["join", "leave", "start", "status", "stop"].includes(action)) {
      if (!this._isChat(peerId)) return;
      const game = this._getGame(this._peerToChatId(peerId));
      if (action === "join") {
        await this._addPlayer(game, userId);
        await this._send(peerId, `${await this._mention(userId)} в игре!`);
      } else if (action === "leave") {
        if (game.phase === PHASE_LOBBY) {
          this._removePlayer(game, userId);
          await this._send(peerId, `${await this._mention(userId)} вышел.`);
        } else {
          this._player(game, userId).alive = false;
          await this._send(peerId, `${await this._mention(userId)} покинул игру и считается мёртвым.`);
          await this._checkWin(game);
        }
      } else if (action === "start") {
        await this._startGame(game, peerId);
      } else if (action === "status") {
        await this._send(peerId, await this._statusText(game), this._buildLobbyKeyboard());
      } else if (action === "stop") {
        if (this.adminId && userId !== this.adminId) {
          await this._send(peerId, "Только админ может стопнуть игру.");
        } else {
          game.phase = PHASE_ENDED;
          game.phaseEnd = 0;
          await this._send(peerId, "🛑 Игра остановлена.");
        }
      }
      return;
    }

    if (["kill", "heal", "check", "vote"].includes(action)) {
      if (!chatId) return;
      const game = this._getGame(Number(chatId));
      const target = payload?.target;
      if (target == null) return;
      await this._handleAction(game, userId, action, Number(target));
    }
  }

  async _handleAction(game, userId, action, target) {
    const player = this._player(game, userId);
    if (!player || !player.alive) return;

    if (game.phase === PHASE_NIGHT) {
      if (action === "kill" && player.role === ROLE_MAFIA) {
        game.night.mafiaVotes[String(target)] = (game.night.mafiaVotes[String(target)] || 0) + 1;
        await this._send(userId, "✅ Голос за убийство принят.");
      } else if (action === "heal" && player.role === ROLE_DOCTOR) {
        game.night.doctor = target;
        await this._send(userId, "✅ Лечение принято.");
      } else if (action === "check" && player.role === ROLE_DETECTIVE) {
        game.night.detective = target;
        await this._send(userId, "✅ Проверка принята.");
      }
    } else if (game.phase === PHASE_VOTE && action === "vote") {
      if (!this._player(game, target) || !this._player(game, target).alive) return;
      game.dayVotes[String(userId)] = target;
      const chatPeer = game.chatId + 2000000000;
      await this._send(chatPeer, `🗳 ${await this._mention(userId)} голосует.`);
    }
  }

  async _handleTextAction(peerId, userId, text) {
    const parts = text.trim().split(/\s+/);
    if (parts.length !== 2) return;
    const cmd = parts[0].toLowerCase();
    const targetStr = parts[1];
    if (!/^\d+$/.test(targetStr)) return;
    const target = Number(targetStr);
    if (!["kill", "heal", "check", "vote"].includes(cmd)) return;

    let chatId = null;
    if (this._isChat(peerId)) chatId = this._peerToChatId(peerId);
    else {
      for (const g of this.games.values()) {
        if (g.players[String(userId)]) {
          chatId = g.chatId;
          break;
        }
      }
    }
    if (!chatId) return;
    const game = this._getGame(chatId);
    await this._handleAction(game, userId, cmd, target);
  }

  async _statusText(game) {
    const players = [];
    for (const uid of this._listPlayers(game)) {
      const p = this._player(game, uid);
      const status = p.alive ? "✅" : "💀";
      players.push(`${status} ${await this._mention(uid)}`);
    }
    const phaseMap = {
      [PHASE_LOBBY]: "лобби",
      [PHASE_NIGHT]: "ночь",
      [PHASE_DAY]: "день",
      [PHASE_VOTE]: "голосование",
      [PHASE_ENDED]: "завершена",
    };
    return `🎮 Мафия\nФаза: ${phaseMap[game.phase] || game.phase}\nРаунд: ${game.round}\n\nИгроки (${Object.keys(game.players).length}):\n${players.join("\n")}`;
  }

  async _tick() {
    const now = Date.now();
    for (const game of this.games.values()) {
      if (!game.phaseEnd || now < game.phaseEnd) continue;
      if (game.phase === PHASE_NIGHT) await this._resolveNight(game);
      else if (game.phase === PHASE_DAY) await this._beginVote(game);
      else if (game.phase === PHASE_VOTE) await this._resolveVote(game);
    }
  }

  async handleMessage(ctx) {
    const peerId = ctx.peerId;
    const userId = ctx.senderId;
    const text = (ctx.text || "").trim();

    if (ctx.messagePayload) {
      await this._handlePayload(peerId, userId, ctx.messagePayload);
      return;
    }

    const t = text.toLowerCase();

    if (this._isChat(peerId)) {
      const game = this._getGame(this._peerToChatId(peerId));

      if (["/mafia", "мафия", "игра"].includes(t)) {
        await this._send(peerId, await this._statusText(game), this._buildLobbyKeyboard());
        return;
      }
      if (["/help", "помощь"].includes(t)) {
        await this._send(peerId, "Команды: мафия, войти, выйти, старт, статус, стоп");
        return;
      }
      if (["войти", "join"].includes(t)) {
        await this._addPlayer(game, userId);
        await this._send(peerId, `${await this._mention(userId)} в игре!`);
        return;
      }
      if (["выйти", "leave"].includes(t)) {
        if (game.phase === PHASE_LOBBY) {
          this._removePlayer(game, userId);
          await this._send(peerId, `${await this._mention(userId)} вышел.`);
        } else {
          this._player(game, userId).alive = false;
          await this._send(peerId, `${await this._mention(userId)} покинул игру и считается мёртвым.`);
          await this._checkWin(game);
        }
        return;
      }
      if (["старт", "start"].includes(t)) {
        await this._startGame(game, peerId);
        return;
      }
      if (["статус", "status"].includes(t)) {
        await this._send(peerId, await this._statusText(game), this._buildLobbyKeyboard());
        return;
      }
      if (["стоп", "stop"].includes(t)) {
        if (this.adminId && userId !== this.adminId) {
          await this._send(peerId, "Только админ может стопнуть игру.");
        } else {
          game.phase = PHASE_ENDED;
          game.phaseEnd = 0;
          await this._send(peerId, "🛑 Игра остановлена.");
        }
        return;
      }
    }

    await this._handleTextAction(peerId, userId, t);
  }
}

module.exports = { MafiaModule };
Посмотри лс
 
Сверху