Przeglądaj źródła

Room gameplay, don and cop roles, night voting, structure of night activity roles

Tigran 4 lat temu
rodzic
commit
1270d467b6

+ 27 - 6
MafiaTelegramBot/Game/GameRoles/CopRole.cs

@@ -1,5 +1,5 @@
+using System.Linq;
 using System.Threading.Tasks;
-using MafiaTelegramBot.Controllers;
 using MafiaTelegramBot.Game.GameRooms;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
@@ -10,15 +10,36 @@ namespace MafiaTelegramBot.Game.GameRoles
     public class CopRole : GameRoom.Role
     {
         public override Roles RoleKey => Roles.Cop;
-        
-        protected override async Task<Message> NightAction(Player player)
+
+        public override async Task NightAction()
         {
-            //TODO this is what user can do in night phase
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            var targets = Room.Players.Values.Except(KnownRoles).Where(p => p.IsAlive).ToList();
+            var message = await Bot.SendWithMarkdown2(Player.ChatId, strings.choose_player_to_check_role, 
+                Keyboards.NightChooseTargetKeyboard(targets, Player.Id));
+            MessageId = message.MessageId;
         }
 
-        public CopRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            if (NightTargetId == -1)
+                await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, strings.you_have_not_choosen_target);
+            else
+            {
+                var user = Room.Players[NightTargetId];
+                KnownRoles.Add(user);
+                var role = user.GetRole() is Roles.Don or Roles.Mafia
+                    ? roles.Mafia
+                    : roles.Villager;
+                await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.role_of_your_target} {user.NickName} - {role}");
+            }
         }
+
+        public override Task<Message> SetNightTarget(long userId)
+        {
+            NightTargetId = userId;
+            return Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
+        }
+
+        public CopRole(GameRoom room, Player player) : base(room, player) { }
     }
 }

+ 30 - 6
MafiaTelegramBot/Game/GameRoles/DoctorRole.cs

@@ -1,5 +1,5 @@
+using System.Linq;
 using System.Threading.Tasks;
-using MafiaTelegramBot.Controllers;
 using MafiaTelegramBot.Game.GameRooms;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
@@ -10,15 +10,39 @@ namespace MafiaTelegramBot.Game.GameRoles
     public class DoctorRole : GameRoom.Role
     {
         public override Roles RoleKey => Roles.Doctor;
-        
-        protected override async Task<Message> NightAction(Player player)
+
+        public override async Task NightAction()
         {
-            //TODO this is what user can do in night phase
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            var targets = Room.Players.Values.Where(p => p.IsAlive).ToList();
+            var message = await Bot.SendWithMarkdown2(Player.ChatId, strings.choose_player_to_heal, 
+                Keyboards.NightChooseTargetKeyboard(targets, Player.Id));
+            MessageId = message.MessageId;
         }
 
-        public DoctorRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            if (NightTargetId == -1)
+                await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, strings.you_have_not_choosen_target);
+            else
+            {
+                var user = Room.Players[NightTargetId];
+                var isTargetSaved = true; //TODO when doctor heals mafia target
+                if(isTargetSaved)
+                    await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_save_the_target} {user.NickName}");
+            }
         }
+
+        public override Task<Message> SetNightTarget(long userId)
+        {
+            var canHeal = true; //TODO why doctor cant heal
+            if (canHeal)
+            {
+                NightTargetId = userId;
+                return Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
+            }
+            return Bot.SendWithMarkdown2(Player.ChatId, strings.you_cant_heal_this_target);
+        }
+
+        public DoctorRole(GameRoom room, Player player) : base(room, player) { }
     }
 }

+ 28 - 5
MafiaTelegramBot/Game/GameRoles/DonRole.cs

@@ -1,4 +1,6 @@
+using System.Linq;
 using System.Threading.Tasks;
+using System.Timers;
 using MafiaTelegramBot.Game.GameRooms;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
@@ -9,15 +11,36 @@ namespace MafiaTelegramBot.Game.GameRoles
     public class DonRole : GameRoom.Role
     {
         public override Roles RoleKey => Roles.Don;
-        
-        protected override async Task<Message> NightAction(Player player)
+
+        public override async Task NightAction()
         {
-            //TODO this is what user can do in night phase
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            var targets = Room.Players.Values.Except(KnownRoles).Where(p => p.IsAlive).ToList();
+            var message = await Bot.SendWithMarkdown2(Player.ChatId, strings.choose_player_to_check_role, 
+                Keyboards.NightChooseTargetKeyboard(targets, Player.Id));
+            MessageId = message.MessageId;
         }
 
-        public DonRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            if (NightTargetId == -1)
+                await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, strings.you_have_not_choosen_target);
+            else
+            {
+                var user = Room.Players[NightTargetId];
+                KnownRoles.Add(user);
+                var role = user.GetRole() is Roles.Cop
+                    ? roles.Cop
+                    : roles.Villager;
+                await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.role_of_your_target} {user.NickName} - {role}");
+            }
         }
+
+        public override async Task<Message> SetNightTarget(long userId)
+        {
+            NightTargetId = userId;
+            return await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
+        }
+
+        public DonRole(GameRoom room, Player player) : base(room, player) { }
     }
 }

+ 14 - 8
MafiaTelegramBot/Game/GameRoles/HookerRole.cs

@@ -1,5 +1,5 @@
 using System.Threading.Tasks;
-using MafiaTelegramBot.Controllers;
+using System.Timers;
 using MafiaTelegramBot.Game.GameRooms;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
@@ -11,17 +11,23 @@ namespace MafiaTelegramBot.Game.GameRoles
     public class HookerRole : GameRoom.Role
     {
         public override Roles RoleKey => Roles.Hooker;
-        
-        protected override async Task<Message> NightAction(Player player)
+
+        public override async Task NightAction()
         {
-            var roomKey = RoomEncrypter.GetCode(player.GetRoomName());
-            var room = RoomController.GetRoom(roomKey);
-            //TODO this is what user can do in night phase
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            
         }
 
-        public HookerRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            
         }
+
+        public override async Task<Message> SetNightTarget(long userId)
+        {
+            NightTargetId = userId;
+            return await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
+        }
+
+        public HookerRole(GameRoom room, Player player) : base(room, player) { }
     }
 }

+ 13 - 5
MafiaTelegramBot/Game/GameRoles/MafiaRole.cs

@@ -9,15 +9,23 @@ namespace MafiaTelegramBot.Game.GameRoles
     public class MafiaRole : GameRoom.Role
     {
         public override Roles RoleKey => Roles.Mafia;
-        
-        protected override async Task<Message> NightAction(Player player)
+
+        public override async Task NightAction()
         {
-            //TODO this is what user can do in night phase
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            
         }
 
-        public MafiaRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            
         }
+
+        public override async Task<Message> SetNightTarget(long userId)
+        {
+            NightTargetId = userId;
+            return await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
+        }
+
+        public MafiaRole(GameRoom room, Player player) : base(room, player) { }
     }
 }

+ 11 - 4
MafiaTelegramBot/Game/GameRoles/NoneRole.cs

@@ -1,4 +1,5 @@
 using System.Threading.Tasks;
+using System.Timers;
 using MafiaTelegramBot.Game.GameRooms;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
@@ -10,17 +11,23 @@ namespace MafiaTelegramBot.Game.GameRoles
     {
         public override Roles RoleKey => Roles.None;
 
-        protected override async Task<Message> NightAction(Player player)
+        public override async Task NightAction()
         {
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            
         }
 
-        public NoneRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            
         }
 
-        public NoneRole() : base(null)
+        public override async Task<Message> SetNightTarget(long userId)
         {
+            NightTargetId = userId;
+            return await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
         }
+
+        public NoneRole(GameRoom room, Player player) : base(room, player) { }
+        public NoneRole() : base(null, null) { }
     }
 }

+ 14 - 5
MafiaTelegramBot/Game/GameRoles/VillagerRole.cs

@@ -1,4 +1,5 @@
 using System.Threading.Tasks;
+using System.Timers;
 using MafiaTelegramBot.Game.GameRooms;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
@@ -9,15 +10,23 @@ namespace MafiaTelegramBot.Game.GameRoles
     public class VillagerRole : GameRoom.Role
     {
         public override Roles RoleKey => Roles.Villager;
-        
-        protected override async Task<Message> NightAction(Player player)
+
+        public override async Task NightAction()
         {
-            //TODO this is what user can do in night phase
-            return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+            
         }
 
-        public VillagerRole(GameRoom room) : base(room)
+        public override async Task ApplyNightActionResult()
         {
+            
         }
+
+        public override async Task<Message> SetNightTarget(long userId)
+        {
+            NightTargetId = userId;
+            return await Bot.Get().EditMessageTextAsync(Player.ChatId, MessageId, $"{strings.you_choose_target} {Room.Players[userId].NickName}");
+        }
+
+        public VillagerRole(GameRoom room, Player player) : base(room, player) { }
     }
 }

+ 62 - 14
MafiaTelegramBot/Game/GameRooms/GameRoom.GameProcess.cs

@@ -1,12 +1,16 @@
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using MafiaTelegramBot.Controllers;
 using MafiaTelegramBot.CustomCollections.Extensions;
 using MafiaTelegramBot.DataBase.EntityDao;
+using MafiaTelegramBot.Game.GameRoles;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
+using Org.BouncyCastle.Utilities;
 using Telegram.Bot.Types;
+using Timer = System.Timers.Timer;
 
 namespace MafiaTelegramBot.Game.GameRooms
 {
@@ -36,7 +40,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                 {
                     var player = _turnOrder.Dequeue();
                     if(!player.IsPlaying) continue;
-                    await player.CurrentRole.DayAction(player);
+                    await player.CurrentRole.DayAction();
                     if (firstPlayer != null && player.IsPlaying) _turnOrder.Enqueue(player);
                     else firstPlayer = player;
                 }
@@ -52,13 +56,28 @@ namespace MafiaTelegramBot.Game.GameRooms
             await Task.Run(async() =>
             {
                 await PlayersCh.Send(strings.city_falls_asleep);
-                var mafia = Players.Values.Where(player => player.CurrentRole.RoleKey is Roles.Mafia).ToArray();
-                var don = Players.Values.FirstOrDefault(player => player.CurrentRole.RoleKey is Roles.Don);
+                var mafia = Players.Values.Where(player => player.GetRole() is Roles.Mafia).ToArray();
+                var don = Players.Values.FirstOrDefault(player => player.GetRole() is Roles.Don);
                 var message = strings.your_teammates;
                 if (don != null) message += $" \\({don.TurnOrder}\\) {don.NickName} - {roles.Don}";
                 message = mafia.Aggregate(message, (current, player) => current + $" \\({player.TurnOrder}\\) {player.NickName}");
                 await MafiaCh.Send(message);
-                Thread.Sleep(10*1000);
+                var resetEvent = new ManualResetEvent(false);
+                var timer = new Timer
+                {
+                    AutoReset = false,
+                    Interval = 10 * 1000
+                };
+                timer.Elapsed += (_, _) => resetEvent.Set();
+                timer.Start();
+                foreach (var player in Players.Values)
+                {
+                    player.CurrentRole.KnownRoles.Add(player);
+                    if(player.GetRole() is Roles.Don or Roles.Mafia) player.CurrentRole.KnownRoles.AddRange(
+                        Players.Values.Where(p=> player.Id != p.Id && p.GetRole() is Roles.Mafia or Roles.Don)
+                    );
+                }
+                resetEvent.WaitOne();
             });
         }
 
@@ -67,16 +86,17 @@ namespace MafiaTelegramBot.Game.GameRooms
             await Task.Run(async () =>
             {
                 var alivePlayers = Players.Values.Where(p => p.IsAlive).ToList();
-                var aliveMafia = alivePlayers.Where(p => p.CurrentRole.RoleKey is Roles.Don or Roles.Mafia).ToList();
-                var gameNotEnded = true;//alivePlayers.Count > 2 * aliveMafia.Count && aliveMafia.Count > 0;
+                var aliveMafia = alivePlayers.Where(p => p.GetRole() is Roles.Don or Roles.Mafia).ToList();
+                var gameNotEnded = alivePlayers.Count > 2 * aliveMafia.Count && aliveMafia.Count > 0;
                 while (gameNotEnded)
                 {
-                    IsDay = true;
-                    await DayPhase();
                     IsDay = false;
                     await NightPhase();
-                    IsDay = true;
                     await SummingUpPhase();
+                    IsDay = true;
+                    await DayPhase();
+                    await DefencePhase();
+                    await DispatchPhase();
                 }
             });
         }
@@ -90,7 +110,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                 var player = _turnOrder.Dequeue();
                 if(!player.IsPlaying) continue;
                 if (firstPlayer == null) player.IsFirst = true;
-                await player.CurrentRole.DayAction(player);
+                await player.CurrentRole.DayAction();
                 if (player.IsFirst) firstPlayer = player;
                 else _turnOrder.Enqueue(player);
             }
@@ -100,15 +120,43 @@ namespace MafiaTelegramBot.Game.GameRooms
 
         private async Task NightPhase()
         {
-            await Task.Run(() =>
+            await Task.Run(async () =>
             {
-                
+                var resetEvent = new ManualResetEvent(false);
+                var timer = new Timer{ Interval = 60*1000, AutoReset = false };
+                foreach (var player in Players.Values.Where(p=>p.IsAlive))
+                {
+                    await player.CurrentRole.NightAction();
+                }
+                timer.Elapsed += (_, _) => resetEvent.Set();
+                timer.Start();
+                resetEvent.WaitOne();
             });
         }
 
         private async Task SummingUpPhase()
         {
-            await Task.Run(() =>
+            await Task.Run(async () =>
+            {
+                //TODO order of applying with priorities
+                foreach (var player in Players.Values.Where(p=>p.IsAlive))
+                {
+                    await player.CurrentRole.ApplyNightActionResult();
+                }
+            });
+        }
+
+        private async Task DefencePhase()
+        {
+            await Task.Run( () =>
+            {
+                
+            });
+        }
+
+        private async Task DispatchPhase()
+        {
+            await Task.Run( () =>
             {
                 
             });
@@ -134,7 +182,7 @@ namespace MafiaTelegramBot.Game.GameRooms
             {
                 foreach (var (_, player) in Players)
                 {
-                    player.CurrentRole = Role.GetNewRoleInstance(Roles.None, this);
+                    player.CurrentRole = new NoneRole();
                     player.IsPlaying = false;
                     player.IsSpeaker = false;
                     player.IsAlive = true;

+ 2 - 2
MafiaTelegramBot/Game/GameRooms/GameRoom.MessageChannels.cs

@@ -53,7 +53,7 @@ namespace MafiaTelegramBot.Game.GameRooms
         {
             public override async Task Send(string message, IReplyMarkup replyMarkup = null, bool exceptDied = true)
             {
-                var mafia = Room.Players.Values.Where(p => p.CurrentRole.RoleKey is Roles.Don or Roles.Mafia);
+                var mafia = Room.Players.Values.Where(p => p.GetRole() is Roles.Don or Roles.Mafia);
                 var receivers = exceptDied ? mafia : mafia.Where(p => p.IsAlive);
                 foreach (var player in receivers)
                     await Bot.SendWithMarkdown2(player.ChatId, message, replyMarkup);
@@ -63,7 +63,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                 bool exceptDied = true)
             {
                 var except = Room.Players.Values.Where(p => p.Id != playerId);
-                var mafia = except.Where(p => p.CurrentRole.RoleKey is Roles.Don or Roles.Mafia);
+                var mafia = except.Where(p => p.GetRole() is Roles.Don or Roles.Mafia);
                 var receivers = exceptDied ? mafia : mafia.Where(p => p.IsAlive);
                 foreach (var player in receivers)
                     await Bot.SendWithMarkdown2(player.ChatId, message, replyMarkup);

+ 29 - 5
MafiaTelegramBot/Game/GameRooms/GameRoom.MessageHandler.cs

@@ -1,4 +1,5 @@
 using System.Linq;
+using System.Security.AccessControl;
 using System.Threading.Tasks;
 using MafiaTelegramBot.Controllers;
 using MafiaTelegramBot.DataBase.EntityDao;
@@ -24,7 +25,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                 var text = update.Message.Text;
                 var userId = update.Message.From.Id;
                 var chatId = update.Message.Chat.Id;
-                if (text == keyboard.look_players_list) await LookPlayers(chatId);
+                if (text == keyboard.look_players_list) await LookPlayers(chatId, userId);
                 else if (text == keyboard.leave) await Leave(chatId, userId);
                 else
                 {
@@ -45,7 +46,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                                 await _room.MafiaCh.SendExcept(userId, $"\\({player.TurnOrder}\\) {player.NickName}: {update.Message.Text}");
                             else
                             {
-                                if(_room.IsFirstCycle && player.CurrentRole.RoleKey is Roles.Mafia or Roles.Don)
+                                if(_room.IsFirstCycle && player.GetRole() is Roles.Mafia or Roles.Don)
                                     await Bot.SendWithMarkdown2(chatId, strings.mafia_get_mail);
                                 else await Bot.SendWithMarkdown2(chatId, strings.villagers_are_sleep);
                             }
@@ -55,12 +56,35 @@ namespace MafiaTelegramBot.Game.GameRooms
                 return update.Message;
             }
 
-            private async Task LookPlayers(long chatId)
+            private async Task LookPlayers(long chatId, long userId)
             {
                 var players = _room.Players.Values.ToList();
+                var knownRoles = _room.Players[userId].CurrentRole.KnownRoles;
                 players.Sort((x, y) => x.TurnOrder - y.TurnOrder);
-                var message = players.Aggregate(strings.players_list,
-                    (current, player) => current + $"\n\\({player.TurnOrder}\\) {player.NickName} \\({(player.IsAlive ? strings.alive : strings.died)}\\)");
+                var message = strings.players_list;
+                var user = _room.Players[userId];
+                foreach (var player in players)
+                {
+                    var role = "";
+                    if (knownRoles.Contains(player))
+                    {
+                        role = user.GetRole() switch
+                        {
+                            Roles.Cop => player.GetRole() is Roles.Don or Roles.Mafia
+                                ? roles.Mafia
+                                : roles.Villager,
+                            Roles.Don => player.GetRole() is Roles.Cop
+                                ? roles.Cop
+                                : roles.Villager,
+                            _ => player.GetRoleName()
+                        };
+                    }
+                    message +=
+                        $"\n\\({player.TurnOrder}\\) " +
+                        $"{player.NickName} " +
+                        $"\\({(player.IsAlive ? strings.alive : strings.died)}\\) " +
+                        $"{role}";
+                }
                 await Bot.SendWithMarkdown2(chatId, message);
             }
 

+ 1 - 1
MafiaTelegramBot/Game/GameRooms/GameRoom.PrepareRoom.cs

@@ -39,7 +39,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                     var user = _turnOrder.Dequeue();
                     var value = random.Next(roles.Count);
                     var next = roles.GetAndRemove(value);
-                    user.CurrentRole = Role.GetNewRoleInstance(next, this);
+                    user.CurrentRole = Role.GetNewRoleInstance(next, this, user);
                     _turnOrder.Enqueue(user);
                 }
             });

+ 1 - 0
MafiaTelegramBot/Game/GameRooms/GameRoom.QueryHandler.cs

@@ -29,6 +29,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                 {
                     Callback.Vote => await VoteAsync(player, long.Parse(data[2])),
                     Callback.Skip => await SkipAsync(player),
+                    Callback.Target => await player.CurrentRole.SetNightTarget(long.Parse(data[2])),
                     _ => await Bot.SendWithMarkdown2(chatId, strings.not_supported_in_game)
                 };
                 return message;

+ 42 - 35
MafiaTelegramBot/Game/GameRooms/GameRoom.Role.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -14,42 +15,50 @@ namespace MafiaTelegramBot.Game.GameRooms
     {
         public abstract class Role
         {
-            private readonly GameRoom _room;
+            protected readonly GameRoom Room;
+            protected readonly Player Player;
+            public readonly List<Player> KnownRoles = new();
+            protected long NightTargetId = -1;
+            public readonly ManualResetEvent DayActionComplete = new(false);
+            protected int MessageId = -1;
+            public abstract Task NightAction();
+            public abstract Task ApplyNightActionResult();
+            public abstract Task<Message> SetNightTarget(long userId);
+            public abstract Roles RoleKey { get; }
 
-            protected Role(GameRoom room)
+            protected Role(GameRoom room, Player player)
             {
-                _room = room;
+                Room = room;
+                Player = player;
             }
-            public abstract Roles RoleKey { get; }
 
-            public readonly ManualResetEvent DayActionComplete = new(false);
-            public async Task DayAction(Player player)
+            public async Task DayAction()
             {
-                await _room.PlayersCh.SendExcept(player.Id, $"{strings.now_turn} {player.NickName}");
-                await Bot.SendWithMarkdown2(player.ChatId, strings.your_turn);
-                player.IsSpeaker = true;
+                await Room.PlayersCh.SendExcept(Player.Id, $"{strings.now_turn} {Player.NickName}");
+                await Bot.SendWithMarkdown2(Player.ChatId, strings.your_turn);
+                Player.IsSpeaker = true;
                 var timer = new Timer(60 * 1000);
-                var voteTimer = new Timer(60 * 1000);
+                var voteTimer = new Timer(10 * 1000);
                 Message message = null;
-                timer.Elapsed += async (sender, args) =>
+                timer.Elapsed += async (_, _) =>
                 {
-                    player.IsSpeaker = false;
-                    await Bot.SendWithMarkdown2(player.ChatId, strings.your_turn_ended);
-                    if (!_room.IsFirstCycle)
+                    Player.IsSpeaker = false;
+                    await Bot.SendWithMarkdown2(Player.ChatId, strings.your_turn_ended);
+                    if (!Room.IsFirstCycle)
                     {
-                        var alivePlayers = _room.Players.Values.Where(p => p.IsAlive).ToList();
-                        message = await Bot.SendWithMarkdown2(player.ChatId, strings.put_up_vote, Keyboards.VoteKeyboard(alivePlayers, player.Id, !player.IsFirst));
-                        voteTimer.Elapsed += async (o, eventArgs) =>
+                        var alivePlayers = Room.Players.Values.Where(p => p.IsAlive).ToList();
+                        message = await Bot.SendWithMarkdown2(Player.ChatId, strings.put_up_vote, Keyboards.VoteKeyboard(alivePlayers, Player.Id, !Player.IsFirst));
+                        voteTimer.Elapsed += async (_, _) =>
                         {
-                            await Bot.Get().EditMessageReplyMarkupAsync(player.ChatId, message.MessageId);
-                            if (player.IsFirst)
-                                await _room.PutUpVote(player.Id, player.Id);
+                            await Bot.Get().EditMessageReplyMarkupAsync(Player.ChatId, message.MessageId);
+                            if (Player.IsFirst)
+                                await Room.PutUpVote(Player.Id, Player.Id);
                             else
-                                await _room.PlayersCh.SendExcept(player.Id, $"{player.NickName} {strings.didnt_put_anyone}");
+                                await Room.PlayersCh.SendExcept(Player.Id, $"{Player.NickName} {strings.didnt_put_anyone}");
                             DayActionComplete.Set();
                         };
-                        await Bot.SendWithMarkdown2(player.Id, strings.you_have_one_minute);
-                        if(player.IsFirst) await Bot.SendWithMarkdown2(player.Id, strings.user_not_choose);
+                        await Bot.SendWithMarkdown2(Player.Id, strings.you_have_one_minute);
+                        if(Player.IsFirst) await Bot.SendWithMarkdown2(Player.Id, strings.user_not_choose);
                         voteTimer.Start();
                     }
                     if(!voteTimer.Enabled) DayActionComplete.Set();
@@ -60,23 +69,21 @@ namespace MafiaTelegramBot.Game.GameRooms
                 DayActionComplete.Reset();
                 timer.Stop();
                 voteTimer.Stop();
-                if(message!=null) await Bot.Get().DeleteMessageAsync(player.ChatId, message.MessageId);
+                if(message!=null) await Bot.Get().DeleteMessageAsync(Player.ChatId, message.MessageId);
             }
 
-            protected abstract Task<Message> NightAction(Player player);
-
-            public static Role GetNewRoleInstance(Roles roleKey, GameRoom room)
+            public static Role GetNewRoleInstance(Roles roleKey, GameRoom room, Player player)
             {
                 return roleKey switch
                 {
-                    Roles.All => new NoneRole(room),
-                    Roles.Doctor => new DoctorRole(room),
-                    Roles.Mafia => new MafiaRole(room),
-                    Roles.Don => new DonRole(room),
-                    Roles.Cop => new CopRole(room),
-                    Roles.Villager => new VillagerRole(room),
-                    Roles.Hooker => new HookerRole(room),
-                    Roles.None => new NoneRole(room),
+                    Roles.All => new NoneRole(),
+                    Roles.Doctor => new DoctorRole(room, player),
+                    Roles.Mafia => new MafiaRole(room, player),
+                    Roles.Don => new DonRole(room, player),
+                    Roles.Cop => new CopRole(room, player),
+                    Roles.Villager => new VillagerRole(room, player),
+                    Roles.Hooker => new HookerRole(room, player),
+                    Roles.None => new NoneRole(room, player),
                     _ => throw new ArgumentOutOfRangeException(nameof(roleKey), roleKey, null)
                 };
             }

+ 2 - 1
MafiaTelegramBot/Resources/Callback.cs

@@ -18,6 +18,7 @@ namespace MafiaTelegramBot.Resources
         KickUser,
         SwitchTimer,
         Vote,
-        Skip
+        Skip,
+        Target
     }
 }

+ 14 - 1
MafiaTelegramBot/Resources/Keyboards.cs

@@ -140,7 +140,7 @@ namespace MafiaTelegramBot.Resources
             {
                 inlineButtons[i] = new[]
                 {
-                    InlineKeyboardButton.WithCallbackData(players[i].NickName, $"{Callback.Vote}|{userId}|{players[i].Id}")
+                    InlineKeyboardButton.WithCallbackData($"\\({players[i].TurnOrder}\\) {players[i].NickName}", $"{Callback.Vote}|{userId}|{players[i].Id}")
                 };
             }
             if (withSkipButton) inlineButtons[players.Count] = new[]
@@ -148,6 +148,19 @@ namespace MafiaTelegramBot.Resources
             return inlineButtons;
         }
 
+        public static InlineKeyboardMarkup NightChooseTargetKeyboard(List<Player> players, long userId)
+        {
+            var inlineButtons = new InlineKeyboardButton[players.Count][];
+            for (var i = 0; i<players.Count; ++i)
+            {
+                inlineButtons[i] = new[]
+                {
+                    InlineKeyboardButton.WithCallbackData($"\\({players[i].TurnOrder}\\) {players[i].NickName}", $"{Callback.Target}|{userId}|{players[i].Id}")
+                };
+            }
+            return inlineButtons;
+        }
+
         public static InlineKeyboardMarkup TimerSwitchKeyboard(long userId, string text)
         {
             return new( new[]

+ 48 - 0
MafiaTelegramBot/Resources/strings.Designer.cs

@@ -572,5 +572,53 @@ namespace MafiaTelegramBot {
                 return ResourceManager.GetString("you_vote_to_self", resourceCulture);
             }
         }
+        
+        internal static string choose_target_to_known_role {
+            get {
+                return ResourceManager.GetString("choose_target_to_known_role", resourceCulture);
+            }
+        }
+        
+        internal static string choose_player_to_check_role {
+            get {
+                return ResourceManager.GetString("choose_player_to_check_role", resourceCulture);
+            }
+        }
+        
+        internal static string you_have_not_choosen_target {
+            get {
+                return ResourceManager.GetString("you_have_not_choosen_target", resourceCulture);
+            }
+        }
+        
+        internal static string you_choose_target {
+            get {
+                return ResourceManager.GetString("you_choose_target", resourceCulture);
+            }
+        }
+        
+        internal static string role_of_your_target {
+            get {
+                return ResourceManager.GetString("role_of_your_target", resourceCulture);
+            }
+        }
+        
+        internal static string choose_player_to_heal {
+            get {
+                return ResourceManager.GetString("choose_player_to_heal", resourceCulture);
+            }
+        }
+        
+        internal static string you_cant_heal_this_target {
+            get {
+                return ResourceManager.GetString("you_cant_heal_this_target", resourceCulture);
+            }
+        }
+        
+        internal static string you_save_the_target {
+            get {
+                return ResourceManager.GetString("you_save_the_target", resourceCulture);
+            }
+        }
     }
 }

+ 25 - 1
MafiaTelegramBot/Resources/strings.resx

@@ -259,7 +259,7 @@
         <value>Пропустить</value>
     </data>
     <data name="you_have_one_minute" xml:space="preserve">
-        <value>У вас есть одна минута, чтобы выставить игрока на голосование</value>
+        <value>У вас есть десять секунд, чтобы выставить игрока на голосование</value>
     </data>
     <data name="user_not_choose" xml:space="preserve">
         <value>Иначе вы будете выставлены на голосование</value>
@@ -282,4 +282,28 @@
     <data name="you_vote_to_self" xml:space="preserve">
         <value>Вы выставили самого себя</value>
     </data>
+    <data name="choose_target_to_known_role" xml:space="preserve">
+        <value>Выберите цель, роль которой хотите узнать</value>
+    </data>
+    <data name="choose_player_to_check_role" xml:space="preserve">
+        <value>Выберите игрока для проверки роли</value>
+    </data>
+    <data name="you_have_not_choosen_target" xml:space="preserve">
+        <value>Вы не выбрали цель</value>
+    </data>
+    <data name="you_choose_target" xml:space="preserve">
+        <value>Вы выбрали игрока</value>
+    </data>
+    <data name="role_of_your_target" xml:space="preserve">
+        <value>Роль игрока</value>
+    </data>
+    <data name="choose_player_to_heal" xml:space="preserve">
+        <value>Выберите игрока для лечения</value>
+    </data>
+    <data name="you_cant_heal_this_target" xml:space="preserve">
+        <value>Вы не можете вылечить данную цель</value>
+    </data>
+    <data name="you_save_the_target" xml:space="preserve">
+        <value>Вы спасли игрока</value>
+    </data>
 </root>