Selaa lähdekoodia

Day action for roles, voting and timer based turns

Tigran 4 vuotta sitten
vanhempi
commit
558b8cf2c2

+ 6 - 0
MafiaTelegramBot/CustomCollections/Extensions/ListExtension.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 
 namespace MafiaTelegramBot.CustomCollections.Extensions
 {
@@ -20,5 +21,10 @@ namespace MafiaTelegramBot.CustomCollections.Extensions
         {
             return new(list);
         }
+
+        public static void AddUnique<T>(this List<T> list, T item)
+        {
+            if(!list.Contains(item)) list.Add(item);
+        }
     }
 }

+ 64 - 9
MafiaTelegramBot/Game/GameRooms/GameRoom.GameProcess.cs

@@ -2,8 +2,11 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using MafiaTelegramBot.Controllers;
+using MafiaTelegramBot.CustomCollections.Extensions;
+using MafiaTelegramBot.DataBase.EntityDao;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
+using Telegram.Bot.Types;
 
 namespace MafiaTelegramBot.Game.GameRooms
 {
@@ -27,29 +30,25 @@ namespace MafiaTelegramBot.Game.GameRooms
             await PlayersCh.Send(strings.first_day_message);
             await Task.Run(async() =>
             {
-                var turnsCount = Players.Count;
+                var turnsCount = _turnOrder.Count;
                 Player firstPlayer = null;
                 for (var i = 0; i < turnsCount; ++i)
                 {
                     var player = _turnOrder.Dequeue();
                     if(!player.IsPlaying) continue;
-                    await PlayersCh.SendExcept(player.Id, $"{strings.now_turn} {player.NickName}");
-                    if(player.IsPlaying) await Bot.SendWithMarkdown2(player.ChatId, strings.your_turn);
-                    player.IsSpeaker = true;
-                    Thread.Sleep(60*1000);
-                    player.IsSpeaker = false;
-                    if(player.IsPlaying) await Bot.SendWithMarkdown2(player.ChatId, strings.your_turn_ended);
+                    await player.CurrentRole.DayAction(player);
                     if (firstPlayer != null && player.IsPlaying) _turnOrder.Enqueue(player);
                     else firstPlayer = player;
                 }
                 _turnOrder.Enqueue(firstPlayer);
             });
+            IsFirstCycle = false;
             await PlayersCh.Send(strings.city_falls_asleep);
         }
 
         private async Task FirstNight()
         {
-            IsFirstNight = true;
+            IsFirstCycle = true;
             await Task.Run(async() =>
             {
                 await PlayersCh.Send(strings.city_falls_asleep);
@@ -61,10 +60,52 @@ namespace MafiaTelegramBot.Game.GameRooms
                 await MafiaCh.Send(message);
                 Thread.Sleep(10*1000);
             });
-            IsFirstNight = false;
         }
 
         private async Task GameCycle()
+        {
+            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();
+                while (alivePlayers.Count > 2 * aliveMafia.Count && aliveMafia.Count > 0)
+                {
+                    IsDay = true;
+                    await DayPhase();
+                    IsDay = false;
+                    await NightPhase();
+                    IsDay = true;
+                    await SummingUpPhase();
+                }
+            });
+        }
+
+        private async Task DayPhase()
+        {
+            var turnsCount = _turnOrder.Count;
+            Player firstPlayer = null;
+            for (var i = 0; i < turnsCount; ++i)
+            {
+                var player = _turnOrder.Dequeue();
+                if(!player.IsPlaying) continue;
+                if (firstPlayer == null) player.IsFirst = true;
+                await player.CurrentRole.DayAction(player);
+                if (player.IsFirst) firstPlayer = player;
+                else _turnOrder.Enqueue(player);
+            }
+            firstPlayer!.IsFirst = false;
+            _turnOrder.Enqueue(firstPlayer);
+        }
+
+        private async Task NightPhase()
+        {
+            await Task.Run(() =>
+            {
+                
+            });
+        }
+
+        private async Task SummingUpPhase()
         {
             await Task.Run(() =>
             {
@@ -72,6 +113,20 @@ namespace MafiaTelegramBot.Game.GameRooms
             });
         }
 
+        public async Task<Message> PutUpVote(long playerId, long targetId)
+        {
+            var player = await UserDao.GetPlayerById(playerId);
+            VoteUpList.AddUnique(Players[targetId]);
+            if (playerId != targetId)
+            {
+                var target = await UserDao.GetPlayerById(targetId);
+                await PlayersCh.SendExcept(player.ChatId, $"{player.NickName} {strings.vote_to} {target.NickName}");
+                return await Bot.SendWithMarkdown2(player.ChatId, $"{strings.you_vote_player} {target.NickName}");
+            }
+            await PlayersCh.SendExcept(player.ChatId, $"{player.NickName} {strings.vote_to_self}");
+            return await Bot.SendWithMarkdown2(player.ChatId, strings.you_vote_to_self);
+        }
+
         private async Task EndOfGame()
         {
             await Task.Run(async () =>

+ 7 - 8
MafiaTelegramBot/Game/GameRooms/GameRoom.MessageChannels.cs

@@ -18,16 +18,16 @@ namespace MafiaTelegramBot.Game.GameRooms
             }
 
             public abstract Task Send(string message, IReplyMarkup replyMarkup = null,
-                bool exceptDied = false);
+                bool exceptDied = true);
 
             public abstract Task SendExcept(long playerId, string message, IReplyMarkup replyMarkup = null,
-                bool exceptDied = false);
+                bool exceptDied = true);
         }
 
         public class PlayersChannel : Channel
         {
             public override async Task Send(string message, IReplyMarkup replyMarkup = null,
-                bool exceptDied = false)
+                bool exceptDied = true)
             {
                 var receivers = exceptDied
                     ? Room.Players.Values
@@ -36,10 +36,9 @@ namespace MafiaTelegramBot.Game.GameRooms
                     await Bot.SendWithMarkdown2(player.ChatId, message, replyMarkup);
             }
 
-            public override async Task SendExcept(long playerId, string message, IReplyMarkup replyMarkup = null,
-                bool exceptDied = false)
+            public override async Task SendExcept(long playerId, string message, IReplyMarkup replyMarkup = null, bool exceptDied = true)
             {
-                var receivers = exceptDied ? Room.Players.Values
+                var receivers = exceptDied ? Room.Players.Values.Where(p => p.Id != playerId)
                     : Room.Players.Values.Where(p => p.IsAlive && p.Id != playerId);
                 foreach (var player in receivers)
                     await Bot.SendWithMarkdown2(player.ChatId, message, replyMarkup);
@@ -52,7 +51,7 @@ namespace MafiaTelegramBot.Game.GameRooms
 
         public class MafiaChannel : Channel
         {
-            public override async Task Send(string message, IReplyMarkup replyMarkup = null, bool exceptDied = false)
+            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 receivers = exceptDied ? mafia : mafia.Where(p => p.IsAlive);
@@ -61,7 +60,7 @@ namespace MafiaTelegramBot.Game.GameRooms
             }
 
             public override async Task SendExcept(long playerId, string message, IReplyMarkup replyMarkup = null,
-                bool exceptDied = false)
+                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);

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

@@ -45,7 +45,7 @@ namespace MafiaTelegramBot.Game.GameRooms
                                 await _room.MafiaCh.SendExcept(userId, $"\\({player.TurnOrder}\\) {player.NickName}: {update.Message.Text}");
                             else
                             {
-                                if(_room.IsFirstNight && player.CurrentRole.RoleKey is Roles.Mafia or Roles.Don)
+                                if(_room.IsFirstCycle && player.CurrentRole.RoleKey is Roles.Mafia or Roles.Don)
                                     await Bot.SendWithMarkdown2(chatId, strings.mafia_get_mail);
                                 else await Bot.SendWithMarkdown2(chatId, strings.villagers_are_sleep);
                             }

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

@@ -0,0 +1,49 @@
+using System;
+using System.Threading.Tasks;
+using MafiaTelegramBot.DataBase.EntityDao;
+using MafiaTelegramBot.Models;
+using MafiaTelegramBot.Resources;
+using Telegram.Bot.Types;
+
+namespace MafiaTelegramBot.Game.GameRooms
+{
+    public partial class GameRoom
+    {
+        public class QueryHandler
+        {
+            private readonly GameRoom _room;
+
+            public QueryHandler(GameRoom room)
+            {
+                _room = room;
+            }
+            
+            public async Task<Message> Handle(Update update)
+            {
+                var data = update.CallbackQuery.Data.Split('|');
+                var userId = long.Parse(data[1]);
+                var player = await UserDao.GetPlayerById(userId);
+                var chatId = player.ChatId;
+                var command = Enum.Parse<Callback>(data[0]);
+                var message = command switch
+                {
+                    Callback.Vote => await VoteAsync(player, long.Parse(data[2])),
+                    Callback.Skip => await SkipAsync(player),
+                    _ => await Bot.SendWithMarkdown2(chatId, strings.not_supported_in_game)
+                };
+                return message;
+            }
+
+            private async Task<Message> VoteAsync(Player player, long targetId)
+            {
+                player.CurrentRole.DayActionComplete.Set();
+                return await _room.PutUpVote(player.Id, targetId);
+            }
+
+            private async Task<Message> SkipAsync(Player player)
+            {
+                return await Bot.SendWithMarkdown2(player.ChatId, strings.you_skip_vote);
+            }
+        }
+    }
+}

+ 43 - 6
MafiaTelegramBot/Game/GameRooms/GameRoom.Role.cs

@@ -1,9 +1,12 @@
 using System;
+using System.Linq;
+using System.Threading;
 using System.Threading.Tasks;
 using MafiaTelegramBot.Game.GameRoles;
 using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
 using Telegram.Bot.Types;
+using Timer = System.Timers.Timer;
 
 namespace MafiaTelegramBot.Game.GameRooms
 {
@@ -11,19 +14,53 @@ namespace MafiaTelegramBot.Game.GameRooms
     {
         public abstract class Role
         {
-            protected readonly GameRoom Room;
+            private readonly GameRoom _room;
 
             protected Role(GameRoom room)
             {
-                Room = room;
+                _room = room;
             }
             public abstract Roles RoleKey { get; }
 
-            protected async Task<Message> DayAction(Player player)
+            public readonly ManualResetEvent DayActionComplete = new(false);
+            public async Task DayAction(Player player)
             {
-                
-                //TODO user telling anything in one minute and vote
-                return await Bot.SendWithMarkdown2(player.ChatId, strings.unexpected_error);
+                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);
+                Message message = null;
+                timer.Elapsed += async (sender, args) =>
+                {
+                    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) =>
+                        {
+                            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}");
+                            DayActionComplete.Set();
+                        };
+                        await Bot.SendWithMarkdown2(player.Id, strings.you_have_one_minute);
+                        if(player.IsFirst) await Bot.SendWithMarkdown2(player.Id, strings.user_not_choose);
+                        voteTimer.Start();
+                    }
+                    DayActionComplete.Set();
+                };
+                timer.AutoReset = false;
+                timer.Start();
+                DayActionComplete.WaitOne();
+                DayActionComplete.Close();
+                timer.Stop();
+                voteTimer.Stop();
+                if(message!=null) await Bot.Get().DeleteMessageAsync(player.ChatId, message.MessageId);
             }
 
             protected abstract Task<Message> NightAction(Player player);

+ 9 - 5
MafiaTelegramBot/Game/GameRooms/GameRoom.Structure.cs

@@ -7,9 +7,9 @@ namespace MafiaTelegramBot.Game.GameRooms
 {
     public abstract partial class GameRoom
     {
-        protected bool IsRunning;
-        protected bool IsDay = false;
-        protected bool IsFirstNight = false;
+        public bool IsRunning;
+        public bool IsDay = false;
+        public bool IsFirstCycle = false;
         public int MaxPlayers = 10;
         private int _minPlayers = Constants.PLAYER_LIMITS_MIN;
         public bool IsPrivate { get; init; }
@@ -18,7 +18,10 @@ namespace MafiaTelegramBot.Game.GameRooms
         public string RoomName { get; init; } = "NoNameRoom";
         public Player Owner { get; init; } = new();
 
-        public readonly MessageHandler Handler;
+        protected readonly List<Player> VoteUpList = new();
+
+        public readonly MessageHandler MHandler;
+        public readonly QueryHandler QHandler;
         public readonly MafiaChannel MafiaCh;
         public readonly PlayersChannel PlayersCh;
 
@@ -38,7 +41,8 @@ namespace MafiaTelegramBot.Game.GameRooms
 
         public GameRoom()
         {
-            Handler = new MessageHandler(this);
+            MHandler = new MessageHandler(this);
+            QHandler = new QueryHandler(this);
             MafiaCh = new MafiaChannel(this);
             PlayersCh = new PlayersChannel(this);
         }

+ 11 - 0
MafiaTelegramBot/Game/Player.cs

@@ -21,6 +21,8 @@ namespace MafiaTelegramBot.Game
         public bool IsAlive = true;
         public bool IsSpeaker = false;
         public bool IsPlaying = false;
+        public bool IsVoting = false;
+        public bool IsFirst = false;
 
         public readonly StatisticsList Statistics = new();
 
@@ -81,5 +83,14 @@ namespace MafiaTelegramBot.Game
         {
             return roles.ResourceManager.GetString(CurrentRole.RoleKey.ToString())!;
         }
+
+        public void ResetState()
+        {
+            IsAlive = true;
+            IsSpeaker = false;
+            IsPlaying = false;
+            IsVoting = false;
+            IsFirst = false;
+        }
     }
 }

+ 1 - 1
MafiaTelegramBot/Models/Commands/Command.cs

@@ -39,7 +39,7 @@ namespace MafiaTelegramBot.Models.Commands
             {
                 var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
                 var room = RoomController.GetRoom(roomKey);
-                return await room.Handler.Handle(update);
+                return await room.MHandler.Handle(update);
             }
             var command = FirstOrDefault(commands, message);
             if(command != null) return await ((Command?) command.Clone(chatId, userId))!.Execute(update);

+ 10 - 0
MafiaTelegramBot/Models/Inlines/Query.cs

@@ -1,5 +1,8 @@
 #nullable enable
 using System.Threading.Tasks;
+using MafiaTelegramBot.Controllers;
+using MafiaTelegramBot.DataBase.EntityDao;
+using MafiaTelegramBot.Game;
 using MafiaTelegramBot.Resources;
 using Telegram.Bot.Types;
 using Telegram.Bot.Types.Enums;
@@ -20,6 +23,13 @@ namespace MafiaTelegramBot.Models.Inlines
             await Bot.Get().SendChatActionAsync(chatId, ChatAction.Typing);
             var data = update.CallbackQuery.Data;
             var userId = long.Parse(data.Split('|')[1]);
+            var user = await UserDao.GetPlayerById(userId);
+            if (user.IsPlaying)
+            {
+                var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
+                var room = RoomController.GetRoom(roomKey);
+                return await room.QHandler.Handle(update);
+            }
             var query = data.Split('|')[0];
             var queries = Bot.Queries;
             var command = FirstOrDefault(queries, query);

+ 2 - 0
MafiaTelegramBot/Resources/Callback.cs

@@ -17,5 +17,7 @@ namespace MafiaTelegramBot.Resources
         SetPlayersMaximum,
         KickUser,
         SwitchTimer,
+        Vote,
+        Skip
     }
 }

+ 16 - 0
MafiaTelegramBot/Resources/Keyboards.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Linq;
 using MafiaTelegramBot.DataBase;
 using MafiaTelegramBot.DataBase.Entity;
 using MafiaTelegramBot.Game;
@@ -132,6 +133,21 @@ namespace MafiaTelegramBot.Resources
             return new InlineKeyboardMarkup(inlineButtons);
         }
 
+        public static InlineKeyboardMarkup VoteKeyboard(List<Player> players, long userId, bool withSkipButton)
+        {
+            var inlineButtons = new InlineKeyboardButton[withSkipButton ?players.Count :players.Count+1][];
+            for (var i = 0; i<players.Count; ++i)
+            {
+                inlineButtons[i] = new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(players[i].NickName, $"{Callback.Vote}|{userId}|{players[i].Id}")
+                };
+            }
+            if (withSkipButton) inlineButtons[players.Count] = new[]
+                    {InlineKeyboardButton.WithCallbackData(strings.skip, $"{Callback.Skip}|{userId}")};
+            return inlineButtons;
+        }
+
         public static InlineKeyboardMarkup TimerSwitchKeyboard(long userId, string text)
         {
             return new( new[]

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

@@ -506,5 +506,71 @@ namespace MafiaTelegramBot {
                 return ResourceManager.GetString("room_owner", resourceCulture);
             }
         }
+        
+        internal static string put_up_vote {
+            get {
+                return ResourceManager.GetString("put_up_vote", resourceCulture);
+            }
+        }
+        
+        internal static string didnt_put_anyone {
+            get {
+                return ResourceManager.GetString("didnt_put_anyone", resourceCulture);
+            }
+        }
+        
+        internal static string skip {
+            get {
+                return ResourceManager.GetString("skip", resourceCulture);
+            }
+        }
+        
+        internal static string you_have_one_minute {
+            get {
+                return ResourceManager.GetString("you_have_one_minute", resourceCulture);
+            }
+        }
+        
+        internal static string user_not_choose {
+            get {
+                return ResourceManager.GetString("user_not_choose", resourceCulture);
+            }
+        }
+        
+        internal static string not_supported_in_game {
+            get {
+                return ResourceManager.GetString("not_supported_in_game", resourceCulture);
+            }
+        }
+        
+        internal static string you_skip_vote {
+            get {
+                return ResourceManager.GetString("you_skip_vote", resourceCulture);
+            }
+        }
+        
+        internal static string you_vote_player {
+            get {
+                return ResourceManager.GetString("you_vote_player", resourceCulture);
+            }
+        }
+        
+        internal static string vote_to {
+            get {
+                return ResourceManager.GetString("vote_to", resourceCulture);
+            }
+        }
+        
+        internal static string vote_to_self {
+            get {
+                return ResourceManager.GetString("vote_to_self", resourceCulture);
+            }
+        }
+        
+        internal static string you_vote_to_self {
+            get {
+                return ResourceManager.GetString("you_vote_to_self", resourceCulture);
+            }
+        }
     }
 }

+ 33 - 0
MafiaTelegramBot/Resources/strings.resx

@@ -249,4 +249,37 @@
     <data name="room_owner" xml:space="preserve">
         <value>владелец комнаты</value>
     </data>
+    <data name="put_up_vote" xml:space="preserve">
+        <value>Кого вы хотите выставить на голосование?</value>
+    </data>
+    <data name="didnt_put_anyone" xml:space="preserve">
+        <value>никого не выставил на голосование</value>
+    </data>
+    <data name="skip" xml:space="preserve">
+        <value>Пропустить</value>
+    </data>
+    <data name="you_have_one_minute" xml:space="preserve">
+        <value>У вас есть одна минута, чтобы выставить игрока на голосование</value>
+    </data>
+    <data name="user_not_choose" xml:space="preserve">
+        <value>Иначе вы будете выставлены на голосование</value>
+    </data>
+    <data name="not_supported_in_game" xml:space="preserve">
+        <value>Данная команда не поддерживается во время игры</value>
+    </data>
+    <data name="you_skip_vote" xml:space="preserve">
+        <value>Вы пропустили голосование</value>
+    </data>
+    <data name="you_vote_player" xml:space="preserve">
+        <value>Вы выставили игрока</value>
+    </data>
+    <data name="vote_to" xml:space="preserve">
+        <value>выставил</value>
+    </data>
+    <data name="vote_to_self" xml:space="preserve">
+        <value>выставил самого себя.</value>
+    </data>
+    <data name="you_vote_to_self" xml:space="preserve">
+        <value>Вы выставили самого себя</value>
+    </data>
 </root>