Welcome!

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

SignUp Now!

Нужен готовый index.js код для мафий

Янв
8
1
Пользователь
3.1. Заголовок темы должен отражать основную суть темы.
Ищу готовый код для мафий index js
 
Последнее редактирование модератором:
Осуждён за гениальность или проклят за талант?
Окт
142
434
Пользователь
game.js:
'use strict';

const {
  ROLE, ROLE_NAME, ROLE_DESC,
  PHASE, TIMER, LIMITS,
  MAFIA_ROLES, CIVILIAN_ROLES,
} = require('../config');
const Player = require('./Player');
const { buildTargetKeyboard, votingKeyboard } = require('../utils/keyboards');

class Game {
  constructor(chatId, hostId, vk, onEnd) {
    this.chatId = chatId;
    this.hostId = hostId;
    this.vk = vk;
    this.onEnd = onEnd;

    this.players = new Map();
    this.phase = PHASE.REGISTRATION;
    this.day = 0;

    this.nightActions = this._emptyNightActions();
    this.lastDoctorTarget = null;
    this.pendingNightDMs = new Map();

    this.dayVotes = new Map();

    this._timer = null;
  }

  addPlayer(userId, name) {
    if (this.phase !== PHASE.REGISTRATION) {
      return { ok: false, reason: 'Регистрация закрыта.' };
    }
    if (this.players.has(userId)) {
      return { ok: false, reason: 'Вы уже в игре.' };
    }
    if (this.players.size >= LIMITS.MAX_PLAYERS) {
      return { ok: false, reason: `Максимум ${LIMITS.MAX_PLAYERS} игроков.` };
    }
    this.players.set(userId, new Player(userId, name));
    return { ok: true };
  }

  removePlayer(userId) {
    if (this.phase !== PHASE.REGISTRATION) {
      return { ok: false, reason: 'Игра уже идёт.' };
    }
    if (!this.players.has(userId)) {
      return { ok: false, reason: 'Вы не в игре.' };
    }
    this.players.delete(userId);
    return { ok: true };
  }

  get playerList() {
    return [...this.players.values()].map((p, i) => `${i + 1}. ${p.mention}`).join('\n');
  }

  async start(initiatorId) {
    if (initiatorId !== this.hostId) {
      return { ok: false, reason: 'Только создатель может начать игру.' };
    }
    if (this.players.size < LIMITS.MIN_PLAYERS) {
      return { ok: false, reason: `Нужно минимум ${LIMITS.MIN_PLAYERS} игрока.` };
    }

    this._clearTimer();
    this._distributeRoles();
    await this._notifyRoles();

    await this._sendChat(
      `🎭 Игра начинается! Участников: ${this.players.size}\n` +
      `Роли розданы — проверьте личные сообщения бота.\n\n` +
      `Наступает первая ночь...`
    );

    await this._startNight();
    return { ok: true };
  }

  _distributeRoles() {
    const ids = [...this.players.keys()];
    this._shuffle(ids);

    const n = ids.length;
    const mafiaCount = Math.max(1, Math.floor(n / 3));
    const hasCommissioner = n >= 5;

    let idx = 0;

    if (mafiaCount >= 2) {
      this.players.get(ids[idx++]).role = ROLE.DON;
    }
    while (idx < mafiaCount) {
      this.players.get(ids[idx++]).role = ROLE.MAFIA;
    }
    this.players.get(ids[idx++]).role = ROLE.DOCTOR;
    if (hasCommissioner) {
      this.players.get(ids[idx++]).role = ROLE.COMMISSIONER;
    }
    while (idx < n) {
      this.players.get(ids[idx++]).role = ROLE.CITIZEN;
    }
  }

  async _notifyRoles() {
    const mafia = this._aliveMafia();
    const mafiaNames = mafia.map(p => p.mention).join(', ');

    const promises = [];
    for (const player of this.players.values()) {
      let text = `🎭 Ваша роль: ${ROLE_NAME[player.role]}\n${ROLE_DESC[player.role]}`;

      if (player.isMafia && mafia.length > 1) {
        const allies = mafia.filter(p => p.id !== player.id).map(p => p.name).join(', ');
        text += `\n\nВаши сообщники: ${allies}`;
      }
      promises.push(this._sendDM(player.id, text));
    }
    await Promise.allSettled(promises);
  }

  async _startNight() {
    this.day++;
    this.phase = PHASE.NIGHT;
    this.nightActions = this._emptyNightActions();
    this.pendingNightDMs.clear();

    await this._sendChat(
      `🌙 Ночь ${this.day}. Город засыпает...\n` +
      `Активные роли — проверьте ЛС бота.`
    );

    await this._sendNightPrompts();
    this._startTimer(TIMER.NIGHT_ACTION, () => this._resolveNight());
  }

  async _sendNightPrompts() {
    const alive = this._alivePlayers();
    const nonMafia = alive.filter(p => !p.isMafia);
    const promises = [];

    for (const m of this._aliveMafia()) {
      const targets = nonMafia.map(p => ({ id: p.id, label: p.name }));
      if (targets.length === 0) continue;
      const kb = buildTargetKeyboard(targets, 'mafia_vote');
      promises.push(
        this._sendDM(m.id, '🔪 Выберите жертву:', kb)
          .then(ok => { if (ok) this.pendingNightDMs.set(m.id, 'mafia'); })
      );
    }

    const don = this._findAliveByRole(ROLE.DON);
    if (don) {
      const targets = alive.filter(p => p.id !== don.id).map(p => ({ id: p.id, label: p.name }));
      if (targets.length > 0) {
        const kb = buildTargetKeyboard(targets, 'don_check');
        promises.push(
          this._sendDM(don.id, '🎩 Кого проверить на Комиссара?', kb)
            .then(ok => { if (ok) this.pendingNightDMs.set(`don_${don.id}`, 'don_check'); })
        );
      }
    }

    const doc = this._findAliveByRole(ROLE.DOCTOR);
    if (doc) {
      const targets = alive
        .filter(p => p.id !== this.lastDoctorTarget)
        .map(p => ({ id: p.id, label: p.name }));
      if (targets.length > 0) {
        const kb = buildTargetKeyboard(targets, 'doctor_heal');
        promises.push(
          this._sendDM(doc.id, '💊 Кого вылечить этой ночью?', kb)
            .then(ok => { if (ok) this.pendingNightDMs.set(doc.id, 'doctor'); })
        );
      }
    }

    const com = this._findAliveByRole(ROLE.COMMISSIONER);
    if (com) {
      const targets = alive.filter(p => p.id !== com.id).map(p => ({ id: p.id, label: p.name }));
      if (targets.length > 0) {
        const kb = buildTargetKeyboard(targets, 'commissioner_check');
        promises.push(
          this._sendDM(com.id, '🔍 Кого проверить этой ночью?', kb)
            .then(ok => { if (ok) this.pendingNightDMs.set(com.id, 'commissioner'); })
        );
      }
    }

    await Promise.allSettled(promises);
  }

  handleMafiaVote(mafiaId, targetId) {
    if (this.phase !== PHASE.NIGHT) return null;
    const mafia = this.players.get(mafiaId);
    if (!mafia || !mafia.alive || !mafia.isMafia) return null;
    if (this.nightActions.mafiaVotes.has(mafiaId)) return null;

    const target = this.players.get(targetId);
    if (!target || !target.alive || target.isMafia) return null;

    this.nightActions.mafiaVotes.set(mafiaId, targetId);
    this._checkNightComplete();
    return target.name;
  }

  handleDonCheck(donId, targetId) {
    if (this.phase !== PHASE.NIGHT) return null;
    const don = this.players.get(donId);
    if (!don || !don.alive || don.role !== ROLE.DON) return null;
    if (this.nightActions.donCheckTarget !== null) return null;

    const target = this.players.get(targetId);
    if (!target || !target.alive) return null;

    this.nightActions.donCheckTarget = targetId;
    const isCommissioner = target.role === ROLE.COMMISSIONER;
    this._checkNightComplete();
    return isCommissioner;
  }

  handleDoctorHeal(docId, targetId) {
    if (this.phase !== PHASE.NIGHT) return null;
    const doc = this.players.get(docId);
    if (!doc || !doc.alive || doc.role !== ROLE.DOCTOR) return null;
    if (this.nightActions.doctorTarget !== null) return null;

    const target = this.players.get(targetId);
    if (!target || !target.alive) return null;
    if (targetId === this.lastDoctorTarget) return null;

    this.nightActions.doctorTarget = targetId;
    this._checkNightComplete();
    return target.name;
  }

  handleCommissionerCheck(comId, targetId) {
    if (this.phase !== PHASE.NIGHT) return null;
    const com = this.players.get(comId);
    if (!com || !com.alive || com.role !== ROLE.COMMISSIONER) return null;
    if (this.nightActions.commissionerTarget !== null) return null;

    const target = this.players.get(targetId);
    if (!target || !target.alive) return null;

    this.nightActions.commissionerTarget = targetId;
    const isMafia = target.isMafia;
    this._checkNightComplete();
    return isMafia;
  }

  _checkNightComplete() {
    const expected = this._expectedNightActions();
    const received = this._receivedNightActions();
    if (received >= expected) {
      this._clearTimer();
      setTimeout(() => this._resolveNight(), 1500);
    }
  }

  _expectedNightActions() {
    let count = this._aliveMafia().length;
    if (this._findAliveByRole(ROLE.DON)) count++;
    if (this._findAliveByRole(ROLE.DOCTOR)) count++;
    if (this._findAliveByRole(ROLE.COMMISSIONER)) count++;
    return count;
  }

  _receivedNightActions() {
    let count = this.nightActions.mafiaVotes.size;
    if (this.nightActions.donCheckTarget !== null) count++;
    if (this.nightActions.doctorTarget !== null) count++;
    if (this.nightActions.commissionerTarget !== null) count++;
    return count;
  }

  async _resolveNight() {
    if (this.phase !== PHASE.NIGHT) return;
    this.phase = PHASE.DAY_ANNOUNCE;
    this._clearTimer();

    const mafiaTarget = this._resolveMafiaTarget();

    const healed = mafiaTarget !== null &&
                   this.nightActions.doctorTarget === mafiaTarget;

    this.lastDoctorTarget = this.nightActions.doctorTarget;

    await this._sendCommissionerResult();
    await this._sendDonCheckResult();

    let killedPlayer = null;
    if (mafiaTarget !== null && !healed) {
      killedPlayer = this.players.get(mafiaTarget);
      killedPlayer.kill();
    }

    await this._announceNightResults(killedPlayer, healed);

    const winner = this._checkWinCondition();
    if (winner) {
      await this._endGame(winner);
      return;
    }

    await this._startDiscussion();
  }

  _resolveMafiaTarget() {
    const votes = this.nightActions.mafiaVotes;
    if (votes.size === 0) {
      const nonMafia = this._alivePlayers().filter(p => !p.isMafia);
      if (nonMafia.length === 0) return null;
      return nonMafia[Math.floor(Math.random() * nonMafia.length)].id;
    }

    const tally = new Map();
    for (const targetId of votes.values()) {
      tally.set(targetId, (tally.get(targetId) || 0) + 1);
    }

    let maxVotes = 0;
    for (const v of tally.values()) {
      if (v > maxVotes) maxVotes = v;
    }
    const topTargets = [...tally.entries()]
      .filter(([, v]) => v === maxVotes)
      .map(([id]) => id);

    return topTargets[Math.floor(Math.random() * topTargets.length)];
  }

  async _sendCommissionerResult() {
    const com = this._findAliveByRole(ROLE.COMMISSIONER);
    const targetId = this.nightActions.commissionerTarget;
    if (!com || targetId === null) return;

    const target = this.players.get(targetId);
    if (!target) return;

    const result = target.isMafia
      ? `🔴 ${target.name} — МАФИЯ!`
      : `🟢 ${target.name} — мирный житель.`;

    await this._sendDM(com.id, `🔍 Результат проверки:\n${result}`);
  }

  async _sendDonCheckResult() {
    const don = this._findAliveByRole(ROLE.DON);
    const targetId = this.nightActions.donCheckTarget;
    if (!don || targetId === null) return;

    const target = this.players.get(targetId);
    if (!target) return;

    const isCom = target.role === ROLE.COMMISSIONER;
    const result = isCom
      ? `🔴 ${target.name} — КОМИССАР!`
      : `🟢 ${target.name} — не комиссар.`;

    await this._sendDM(don.id, `🎩 Результат проверки:\n${result}`);
  }

  async _announceNightResults(killedPlayer, healed) {
    let text = `☀️ Наступает день ${this.day}. Город просыпается...\n\n`;

    if (killedPlayer) {
      text += `💀 Этой ночью был убит: ${killedPlayer.mention}\n`;
      text += `Роль: ${ROLE_NAME[killedPlayer.role]}`;
    } else if (healed) {
      text += `💊 Доктор спас жизнь этой ночью! Никто не погиб.`;
    } else {
      text += `✨ Этой ночью никто не погиб.`;
    }

    text += `\n\nОсталось в живых: ${this._alivePlayers().length}`;
    await this._sendChat(text);
  }

  async _startDiscussion() {
    this.phase = PHASE.DISCUSSION;

    const alive = this._alivePlayers();
    const list = alive.map((p, i) => `${i + 1}. ${p.mention}`).join('\n');

    await this._sendChat(
      `💬 Обсуждение (${TIMER.DISCUSSION} сек).\n` +
      `Обсудите, кто может быть мафией.\n\n` +
      `Живые игроки:\n${list}`
    );

    this._startTimer(TIMER.DISCUSSION, () => this._startVoting());
  }

  async _startVoting() {
    this.phase = PHASE.VOTING;
    this.dayVotes.clear();

    const alive = this._alivePlayers();

    await this._sendChat(
      `🗳 Голосование! (${TIMER.VOTING} сек)\n` +
      `Нажмите кнопку в ЛС бота, чтобы проголосовать.`
    );

    const promises = alive.map(p => {
      const kb = votingKeyboard(alive, p.id);
      return this._sendDM(p.id, '🗳 Голосуйте! Кого исключить из города?', kb);
    });
    await Promise.allSettled(promises);

    this._startTimer(TIMER.VOTING, () => this._resolveVoting());
  }

  handleDayVote(voterId, targetId) {
    if (this.phase !== PHASE.VOTING) return null;
    const voter = this.players.get(voterId);
    if (!voter || !voter.alive) return null;
    if (this.dayVotes.has(voterId)) return null;

    if (targetId !== 'skip') {
      const target = this.players.get(targetId);
      if (!target || !target.alive) return null;
    }

    this.dayVotes.set(voterId, targetId);

    const aliveCount = this._alivePlayers().length;
    if (this.dayVotes.size >= aliveCount) {
      this._clearTimer();
      setTimeout(() => this._resolveVoting(), 1000);
    }

    if (targetId === 'skip') return 'Пропустить';
    return this.players.get(targetId)?.name ?? null;
  }

  async _resolveVoting() {
    if (this.phase !== PHASE.VOTING) return;
    this.phase = PHASE.LAST_WORD;
    this._clearTimer();

    const tally = new Map();
    let skipVotes = 0;

    for (const targetId of this.dayVotes.values()) {
      if (targetId === 'skip') {
        skipVotes++;
      } else {
        tally.set(targetId, (tally.get(targetId) || 0) + 1);
      }
    }

    let resultText = '📊 Результаты голосования:\n';
    const sortedTally = [...tally.entries()].sort((a, b) => b[1] - a[1]);
    for (const [id, count] of sortedTally) {
      const p = this.players.get(id);
      resultText += `  ${p.mention} — ${count} гол.\n`;
    }
    if (skipVotes > 0) {
      resultText += `  Пропустить — ${skipVotes} гол.\n`;
    }
    if (this.dayVotes.size === 0) {
      resultText += '  Никто не голосовал.\n';
    }

    let maxVotes = skipVotes;
    let eliminatedId = null;

    for (const [id, count] of tally) {
      if (count > maxVotes) {
        maxVotes = count;
        eliminatedId = id;
      } else if (count === maxVotes) {
        eliminatedId = null;
      }
    }

    if (eliminatedId) {
      const victim = this.players.get(eliminatedId);
      victim.kill();
      resultText += `\n⚰️ Город решил казнить: ${victim.mention}\n`;
      resultText += `Роль: ${ROLE_NAME[victim.role]}`;

      await this._sendChat(resultText);

      const winner = this._checkWinCondition();
      if (winner) {
        await this._endGame(winner);
        return;
      }

      await this._sendChat(
        `🗣 ${victim.mention}, у вас есть ${TIMER.LAST_WORD} секунд на последнее слово.`
      );
      this._startTimer(TIMER.LAST_WORD, () => this._startNight());
    } else {
      resultText += '\n🤝 Ничья — никто не исключён.';
      await this._sendChat(resultText);

      const winner = this._checkWinCondition();
      if (winner) {
        await this._endGame(winner);
        return;
      }

      await this._startNight();
    }
  }

  _checkWinCondition() {
    const aliveMafia = this._aliveMafia().length;
    const aliveCivil = this._alivePlayers().length - aliveMafia;

    if (aliveMafia === 0) return 'civilians';
    if (aliveMafia >= aliveCivil) return 'mafia';
    return null;
  }

  async _endGame(winner) {
    this.phase = PHASE.GAME_OVER;
    this._clearTimer();

    const isMafiaWin = winner === 'mafia';

    let text = isMafiaWin
      ? '🔴 Мафия победила! Город пал.\n\n'
      : '🟢 Мирные жители победили! Мафия уничтожена.\n\n';

    text += '📋 Роли игроков:\n';
    for (const p of this.players.values()) {
      const status = p.alive ? '✅' : '💀';
      text += `${status} ${p.mention} — ${ROLE_NAME[p.role]}\n`;
    }

    text += '\n📊 Статистика:\n';
    text += `  Дней прожито: ${this.day}\n`;
    text += `  Игроков: ${this.players.size}\n`;
    text += `  Выживших: ${this._alivePlayers().length}`;

    await this._sendChat(text);
    this.onEnd(this.chatId);
  }

  async handlePlayerLeave(userId) {
    const player = this.players.get(userId);
    if (!player || !player.alive) return;

    player.kill();
    await this._sendChat(
      `🚪 ${player.mention} покинул игру.\nРоль: ${ROLE_NAME[player.role]}`
    );

    const winner = this._checkWinCondition();
    if (winner) {
      await this._endGame(winner);
    }
  }

  async forceStop(userId) {
    if (userId !== this.hostId) return false;
    this._clearTimer();
    this.phase = PHASE.GAME_OVER;
    await this._sendChat('🛑 Игра принудительно остановлена создателем.');
    this.onEnd(this.chatId);
    return true;
  }

  _alivePlayers() {
    const result = [];
    for (const p of this.players.values()) {
      if (p.alive) result.push(p);
    }
    return result;
  }

  _aliveMafia() {
    const result = [];
    for (const p of this.players.values()) {
      if (p.alive && p.isMafia) result.push(p);
    }
    return result;
  }

  _findAliveByRole(role) {
    for (const p of this.players.values()) {
      if (p.alive && p.role === role) return p;
    }
    return null;
  }

  _emptyNightActions() {
    return {
      mafiaVotes: new Map(),
      donCheckTarget: null,
      doctorTarget: null,
      commissionerTarget: null,
    };
  }

  async _sendChat(text, keyboard = undefined) {
    try {
      await this.vk.api.messages.send({
        peer_id: this.chatId,
        message: text,
        keyboard: keyboard ? keyboard.toString() : undefined,
        random_id: Math.floor(Math.random() * 1e9),
      });
    } catch (err) {
      console.error(`[Game] Ошибка отправки в чат ${this.chatId}:`, err.message);
    }
  }

  async _sendDM(userId, text, keyboard = undefined) {
    try {
      await this.vk.api.messages.send({
        user_id: userId,
        message: text,
        keyboard: keyboard ? keyboard.toString() : undefined,
        random_id: Math.floor(Math.random() * 1e9),
      });
      return true;
    } catch (err) {
      console.error(`[Game] Не удалось отправить ЛС ${userId}:`, err.message);
      const player = this.players.get(userId);
      if (player) {
        await this._sendChat(
          `⚠️ Не удалось отправить сообщение ${player.mention}. ` +
          `Убедитесь, что ЛС бота открыты.`
        );
      }
      return false;
    }
  }

  _startTimer(seconds, callback) {
    this._clearTimer();
    this._timer = setTimeout(async () => {
      try {
        await callback.call(this);
      } catch (err) {
        console.error('[Game] Ошибка в таймере:', err);
      }
    }, seconds * 1000);
  }

  _clearTimer() {
    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
    }
  }

  _shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  }
}

module.exports = Game;


keyboards.js:
'use strict';

const { Keyboard } = require('vk-io');

function buildTargetKeyboard(targets, actionPrefix, columns = 2) {
  const builder = Keyboard.builder();
  let col = 0;

  for (const t of targets) {
    builder.callbackButton({
      label: t.label.slice(0, 40),
      payload: { action: actionPrefix, target: t.id },
      color: Keyboard.SECONDARY_COLOR,
    });
    col++;
    if (col >= columns) {
      builder.row();
      col = 0;
    }
  }

  return builder.inline();
}

function registrationKeyboard() {
  return Keyboard.builder()
    .callbackButton({
      label: 'Присоединиться',
      payload: { action: 'join' },
      color: Keyboard.POSITIVE_COLOR,
    })
    .callbackButton({
      label: 'Покинуть',
      payload: { action: 'leave' },
      color: Keyboard.NEGATIVE_COLOR,
    })
    .row()
    .callbackButton({
      label: 'Начать игру',
      payload: { action: 'start_game' },
      color: Keyboard.PRIMARY_COLOR,
    })
    .inline();
}

function votingKeyboard(alivePlayers, voterId) {
  const targets = alivePlayers
    .filter(p => p.id !== voterId)
    .map(p => ({ id: p.id, label: p.name }));

  const builder = Keyboard.builder();
  let col = 0;

  for (const t of targets) {
    builder.callbackButton({
      label: t.label.slice(0, 40),
      payload: { action: 'day_vote', target: t.id },
      color: Keyboard.SECONDARY_COLOR,
    });
    col++;
    if (col >= 2) {
      builder.row();
      col = 0;
    }
  }

  if (col !== 0) builder.row();
  builder.callbackButton({
    label: 'Пропустить',
    payload: { action: 'day_vote', target: 'skip' },
    color: Keyboard.NEGATIVE_COLOR,
  });

  return builder.inline();
}

module.exports = {
  buildTargetKeyboard,
  registrationKeyboard,
  votingKeyboard,
};


handler/index:
'use strict';

const { PHASE, LIMITS, ROLE_NAME, ROLE_DESC, ROLE } = require('../config');
const { registrationKeyboard } = require('../utils/keyboards');

function registerHandlers(vk, manager) {
  vk.updates.on('message_new', async (context, next) => {
    if (!context.text) return next();

    const text = context.text.toLowerCase().trim();
    const isChat = context.peerType === 'chat';
    const userId = context.senderId;
    const peerId = context.peerId;

    if (isChat) {
      if (text === '/мафия' || text === '/mafia') {
        return handleCreateGame(context, manager, peerId, userId);
      }

      if (text === '/стоп' || text === '/stop') {
        return handleStopGame(context, manager, peerId, userId);
      }

      if (text === '/правила' || text === '/rules') {
        return handleRules(context);
      }

      if (text === '/роли' || text === '/roles') {
        return handleRolesInfo(context);
      }

      if (text === '/статус' || text === '/status') {
        return handleStatus(context, manager, peerId);
      }

      if (text === '/выйти' || text === '/leave') {
        return handleLeaveCommand(context, manager, peerId, userId);
      }
    }

    return next();
  });

  vk.updates.on('message_event', async (context) => {
    const payload = context.eventPayload;
    const userId = context.userId;
    const peerId = context.peerId;

    if (!payload || !payload.action) return;

    try {
      switch (payload.action) {
        case 'join':
          await handleJoin(context, vk, manager, peerId, userId);
          break;

        case 'leave':
          await handleLeave(context, vk, manager, peerId, userId);
          break;

        case 'start_game':
          await handleStartGame(context, vk, manager, peerId, userId);
          break;

        case 'mafia_vote':
          await handleNightAction(context, manager, userId, 'mafia_vote', payload.target);
          break;

        case 'don_check':
          await handleNightAction(context, manager, userId, 'don_check', payload.target);
          break;

        case 'doctor_heal':
          await handleNightAction(context, manager, userId, 'doctor_heal', payload.target);
          break;

        case 'commissioner_check':
          await handleNightAction(context, manager, userId, 'commissioner_check', payload.target);
          break;

        case 'day_vote':
          await handleDayVoteAction(context, manager, userId, payload.target);
          break;

        default:
          await context.answer({ type: 'show_snackbar', text: 'Неизвестное действие.' });
      }
    } catch (err) {
      console.error('[Handler] Ошибка обработки callback:', err);
      try {
        await context.answer({ type: 'show_snackbar', text: 'Произошла ошибка.' });
      } catch {}
    }
  });
}

async function handleCreateGame(context, manager, peerId, userId) {
  const result = manager.createGame(peerId, userId);
  if (!result.ok) {
    return context.send(result.reason);
  }

  const name = await _getUserName(context.api, userId);
  result.game.addPlayer(userId, name);
  manager.registerPlayer(userId, peerId);

  await context.send({
    message:
      `🎭 Игра «Мафия» создана!\n` +
      `Организатор: [id${userId}|${name}]\n\n` +
      `Для участия нажмите «Присоединиться».\n` +
      `Минимум ${LIMITS.MIN_PLAYERS} игрока, максимум ${LIMITS.MAX_PLAYERS}.\n\n` +
      `Игроки:\n1. [id${userId}|${name}]`,
    keyboard: registrationKeyboard(),
  });
}

async function handleJoin(context, vk, manager, peerId, userId) {
  const game = manager.getGame(peerId);
  if (!game) {
    return context.answer({ type: 'show_snackbar', text: 'Игра не найдена.' });
  }

  const name = await _getUserName(vk.api, userId);
  const result = game.addPlayer(userId, name);

  if (!result.ok) {
    return context.answer({ type: 'show_snackbar', text: result.reason });
  }

  manager.registerPlayer(userId, peerId);

  await context.answer({ type: 'show_snackbar', text: '✅ Вы вступили в игру!' });

  await vk.api.messages.send({
    peer_id: peerId,
    message:
      `✅ [id${userId}|${name}] вступает в игру!\n\n` +
      `Игроки (${game.players.size}):\n${game.playerList}`,
    keyboard: registrationKeyboard(),
    random_id: Math.floor(Math.random() * 1e9),
  });
}

async function handleLeave(context, vk, manager, peerId, userId) {
  const game = manager.getGame(peerId);
  if (!game) {
    return context.answer({ type: 'show_snackbar', text: 'Игра не найдена.' });
  }

  if (game.phase !== PHASE.REGISTRATION) {
    await game.handlePlayerLeave(userId);
    manager.unregisterPlayer(userId);
    return context.answer({ type: 'show_snackbar', text: 'Вы вышли из игры.' });
  }

  const result = game.removePlayer(userId);
  if (!result.ok) {
    return context.answer({ type: 'show_snackbar', text: result.reason });
  }

  manager.unregisterPlayer(userId);
  await context.answer({ type: 'show_snackbar', text: 'Вы покинули игру.' });

  const player = `[id${userId}|Игрок]`;
  const count = game.players.size;

  if (count === 0) {
    manager._onGameEnd(peerId);
    await vk.api.messages.send({
      peer_id: peerId,
      message: `${player} покинул. Все вышли — игра отменена.`,
      random_id: Math.floor(Math.random() * 1e9),
    });
  } else {
    await vk.api.messages.send({
      peer_id: peerId,
      message:
        `❌ ${player} покидает игру.\n\n` +
        `Игроки (${count}):\n${game.playerList}`,
      keyboard: registrationKeyboard(),
      random_id: Math.floor(Math.random() * 1e9),
    });
  }
}

async function handleLeaveCommand(context, manager, peerId, userId) {
  const game = manager.getGame(peerId);
  if (!game) return;

  if (game.phase === PHASE.REGISTRATION) {
    const result = game.removePlayer(userId);
    if (result.ok) {
      manager.unregisterPlayer(userId);
      await context.send(`❌ Вы покинули игру.\n\nИгроки (${game.players.size}):\n${game.playerList}`);
    }
  } else {
    await game.handlePlayerLeave(userId);
    manager.unregisterPlayer(userId);
  }
}

async function handleStartGame(context, vk, manager, peerId, userId) {
  const game = manager.getGame(peerId);
  if (!game) {
    return context.answer({ type: 'show_snackbar', text: 'Игра не найдена.' });
  }

  const result = await game.start(userId);
  if (!result.ok) {
    return context.answer({ type: 'show_snackbar', text: result.reason });
  }

  await context.answer({ type: 'show_snackbar', text: '🎮 Игра запущена!' });
}

async function handleNightAction(context, manager, userId, action, targetId) {
  const game = manager.getGameByPlayer(userId);
  if (!game || game.phase !== PHASE.NIGHT) {
    return context.answer({ type: 'show_snackbar', text: 'Сейчас не время для этого.' });
  }

  let result = null;
  let responseText = '';

  switch (action) {
    case 'mafia_vote': {
      const name = game.handleMafiaVote(userId, targetId);
      if (name === null) {
        return context.answer({ type: 'show_snackbar', text: 'Действие недоступно.' });
      }
      responseText = `🔪 Вы голосуете за: ${name}`;
      break;
    }

    case 'don_check': {
      result = game.handleDonCheck(userId, targetId);
      if (result === null) {
        return context.answer({ type: 'show_snackbar', text: 'Действие недоступно.' });
      }
      const target = game.players.get(targetId);
      responseText = result
        ? `🔴 ${target.name} — КОМИССАР!`
        : `🟢 ${target.name} — не комиссар.`;
      break;
    }

    case 'doctor_heal': {
      const name = game.handleDoctorHeal(userId, targetId);
      if (name === null) {
        return context.answer({ type: 'show_snackbar', text: 'Действие недоступно.' });
      }
      responseText = `💊 Вы лечите: ${name}`;
      break;
    }

    case 'commissioner_check': {
      result = game.handleCommissionerCheck(userId, targetId);
      if (result === null) {
        return context.answer({ type: 'show_snackbar', text: 'Действие недоступно.' });
      }
      const target = game.players.get(targetId);
      responseText = result
        ? `🔴 ${target.name} — МАФИЯ!`
        : `🟢 ${target.name} — мирный.`;
      break;
    }
  }

  await context.answer({ type: 'show_snackbar', text: responseText });
}

async function handleDayVoteAction(context, manager, userId, targetId) {
  const game = manager.getGameByPlayer(userId);
  if (!game || game.phase !== PHASE.VOTING) {
    return context.answer({ type: 'show_snackbar', text: 'Сейчас не время для голосования.' });
  }

  const result = game.handleDayVote(userId, targetId);
  if (result === null) {
    return context.answer({ type: 'show_snackbar', text: 'Действие недоступно.' });
  }

  await context.answer({ type: 'show_snackbar', text: `🗳 Ваш голос: ${result}` });
}

async function handleStopGame(context, manager, peerId, userId) {
  const game = manager.getGame(peerId);
  if (!game) {
    return context.send('Нет активной игры.');
  }
  const ok = await game.forceStop(userId);
  if (!ok) {
    await context.send('Только создатель игры может её остановить.');
  }
}

async function handleRules(context) {
  await context.send(
    `📖 Правила игры «Мафия»\n\n` +
    `🎭 Роли:\n` +
    `• Мафия — убивает каждую ночь\n` +
    `• Дон мафии — глава мафии + проверяет на комиссара\n` +
    `• Комиссар — проверяет игрока ночью (мафия/мирный)\n` +
    `• Доктор — лечит одного игрока за ночь\n` +
    `• Мирный житель — голосует днём\n\n` +
    `🌙 Ночь:\n` +
    `Мафия выбирает жертву. Доктор лечит. Комиссар проверяет.\n\n` +
    `☀️ День:\n` +
    `Обсуждение → Голосование. Игрок с большинством голосов исключён.\n\n` +
    `🏆 Победа:\n` +
    `• Мирные побеждают, когда вся мафия уничтожена\n` +
    `• Мафия побеждает, когда мафий >= мирных\n\n` +
    `⚙️ Нюансы:\n` +
    `• Доктор не может лечить одного и того же 2 ночи подряд\n` +
    `• При ничьей голосов днём — никого не исключают\n` +
    `• Если мафия не голосует — жертва случайная\n` +
    `• Дон голосует с мафией + проверяет на комиссара\n\n` +
    `📝 Команды:\n` +
    `/мафия — создать игру\n` +
    `/стоп — остановить игру (создатель)\n` +
    `/выйти — покинуть игру\n` +
    `/роли — распределение ролей\n` +
    `/статус — статус текущей игры\n` +
    `/правила — эта справка`
  );
}

async function handleRolesInfo(context) {
  let text = '🎭 Распределение ролей по количеству игроков:\n\n';
  for (let n = LIMITS.MIN_PLAYERS; n <= 12; n++) {
    const mafia = Math.max(1, Math.floor(n / 3));
    const hasDon = mafia >= 2;
    const hasCom = n >= 5;
    const special = 1 + (hasCom ? 1 : 0);
    const citizens = n - mafia - special;

    text += `👥 ${n} игроков: `;
    if (hasDon) text += `1 Дон + ${mafia - 1} Маф.`;
    else text += `${mafia} Маф.`;
    text += `, 1 Док.`;
    if (hasCom) text += `, 1 Ком.`;
    text += `, ${citizens} Мирн.\n`;
  }
  await context.send(text);
}

async function handleStatus(context, manager, peerId) {
  const game = manager.getGame(peerId);
  if (!game) {
    return context.send('Нет активной игры. Напишите /мафия для создания.');
  }

  const phaseName = {
    [PHASE.REGISTRATION]: '📝 Регистрация',
    [PHASE.NIGHT]:        '🌙 Ночь',
    [PHASE.DAY_ANNOUNCE]: '☀️ Утро',
    [PHASE.DISCUSSION]:   '💬 Обсуждение',
    [PHASE.VOTING]:       '🗳 Голосование',
    [PHASE.LAST_WORD]:    '🗣 Последнее слово',
    [PHASE.GAME_OVER]:    '🏁 Игра окончена',
  };

  const alive = game._alivePlayers();
  let text = `📊 Статус игры:\n`;
  text += `Фаза: ${phaseName[game.phase] || game.phase}\n`;
  text += `День: ${game.day}\n`;
  text += `Игроков: ${game.players.size} (живых: ${alive.length})\n\n`;
  text += `Живые:\n`;
  text += alive.map((p, i) => `${i + 1}. ${p.mention}`).join('\n');

  await context.send(text);
}

async function _getUserName(api, userId) {
  try {
    const [user] = await api.users.get({ user_ids: [userId] });
    return `${user.first_name} ${user.last_name}`;
  } catch {
    return `Игрок ${userId}`;
  }
}

module.exports = { registerHandlers };
 
Янв
8
1
Пользователь
Сверху