Jelajahi Sumber

Add fast game realization

Tigran 4 tahun lalu
induk
melakukan
e4e437003e
32 mengubah file dengan 414 tambahan dan 67 penghapusan
  1. 1 0
      MafiaTelegramBot.sln.DotSettings.user
  2. 10 9
      MafiaTelegramBot/Controllers/MessageController.cs
  3. 94 19
      MafiaTelegramBot/Controllers/RoomController.cs
  4. 3 2
      MafiaTelegramBot/DataBase/EntityDao/UserDao.cs
  5. 127 0
      MafiaTelegramBot/Game/GameRooms/FastGameRoom.cs
  6. 13 23
      MafiaTelegramBot/Game/GameRooms/GameRoom.GameProcess.cs
  7. 13 0
      MafiaTelegramBot/Game/GameRooms/GameRoom.MessageChannels.cs
  8. 4 4
      MafiaTelegramBot/Game/GameRooms/GameRoom.Structure.cs
  9. 4 1
      MafiaTelegramBot/Game/GameRooms/GameRoom.Timer.cs
  10. 22 0
      MafiaTelegramBot/Logs.cs
  11. 6 4
      MafiaTelegramBot/Models/Bot.cs
  12. 4 0
      MafiaTelegramBot/Models/Commands/Command.cs
  13. 21 0
      MafiaTelegramBot/Models/Commands/FastGameCommand.cs
  14. 2 0
      MafiaTelegramBot/Models/Commands/KickPlayerCommand.cs
  15. 2 0
      MafiaTelegramBot/Models/Commands/LookPlayersListCommand.cs
  16. 2 0
      MafiaTelegramBot/Models/Commands/RoomSettingsCommand.cs
  17. 11 0
      MafiaTelegramBot/Models/Commands/StartCommand.cs
  18. 2 0
      MafiaTelegramBot/Models/Commands/StartGameCommand.cs
  19. 2 0
      MafiaTelegramBot/Models/Inlines/ApplyRolesChangeQuery.cs
  20. 2 0
      MafiaTelegramBot/Models/Inlines/ChangeRolesQuery.cs
  21. 2 0
      MafiaTelegramBot/Models/Inlines/PlayersCountSettingsQuery.cs
  22. 3 1
      MafiaTelegramBot/Models/Inlines/Query.cs
  23. 2 0
      MafiaTelegramBot/Models/Inlines/SetPlayersMaximumQuery.cs
  24. 2 0
      MafiaTelegramBot/Models/Inlines/StartGameQuery.cs
  25. 2 0
      MafiaTelegramBot/Models/Inlines/SwitchTimerQuery.cs
  26. 2 1
      MafiaTelegramBot/Resources/Callback.cs
  27. 9 3
      MafiaTelegramBot/Resources/Constants.cs
  28. 11 0
      MafiaTelegramBot/Resources/Keyboard.cs
  29. 6 0
      MafiaTelegramBot/Resources/keyboard.Designer.cs
  30. 3 0
      MafiaTelegramBot/Resources/keyboard.resx
  31. 18 0
      MafiaTelegramBot/Resources/strings.Designer.cs
  32. 9 0
      MafiaTelegramBot/Resources/strings.resx

+ 1 - 0
MafiaTelegramBot.sln.DotSettings.user

@@ -1,4 +1,5 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	
 	<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=0ea868fa_002D7cf8_002D466b_002Dbf25_002Dc540fc960a6b/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Killing_Player_In_Summing_Up_Phase #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
   &lt;TestAncestor&gt;
     &lt;TestId&gt;xUnit::BB1857E4-418E-4AE5-8437-43EBD1D903FB::net5.0::MafiaTelegramBotTests.Game.Tests.GameRooms.Tests.GameRoomTests&lt;/TestId&gt;

+ 10 - 9
MafiaTelegramBot/Controllers/MessageController.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Globalization;
 using System.Threading;
 using System.Threading.Tasks;
 using MafiaTelegramBot.Models;
@@ -10,6 +9,7 @@ using Telegram.Bot;
 using Telegram.Bot.Exceptions;
 using Telegram.Bot.Types;
 using Telegram.Bot.Types.Enums;
+using static MafiaTelegramBot.Logs;
 
 namespace MafiaTelegramBot.Controllers
 {
@@ -38,21 +38,22 @@ namespace MafiaTelegramBot.Controllers
 
         private static Task UnknownUpdateHandlerAsync(Update update)
         {
-            Console.WriteLine($"[{DateTime.Now.ToString()}] {update.Type}");
+            LogOut(update.Type.ToString());
             return Task.CompletedTask;
         }
 
         public static Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception,
             CancellationToken cancellationToken)
         {
-            var errorMessage = exception switch
+            switch (exception)
             {
-                ApiRequestException apiRequestException =>
-                    $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}",
-                _ => exception.ToString()
-            };
-
-            Console.WriteLine($"[{DateTime.Now.ToString()}] {errorMessage}");
+                case ApiRequestException apiRequestException:
+                    LogOutWarning($"API Error:[{apiRequestException.ErrorCode}] - {apiRequestException.Message}");
+                    break;
+                default:
+                    LogOutError(exception.ToString());
+                    break;
+            }
             return Task.CompletedTask;
         }
     }

+ 94 - 19
MafiaTelegramBot/Controllers/RoomController.cs

@@ -3,6 +3,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using MafiaTelegramBot.Game;
 using MafiaTelegramBot.Game.GameRooms;
+using MafiaTelegramBot.Models;
 using MafiaTelegramBot.Resources;
 
 namespace MafiaTelegramBot.Controllers
@@ -10,6 +11,7 @@ namespace MafiaTelegramBot.Controllers
     public static class RoomController
     {
         private static readonly Dictionary<string, GameRoom> OpenedGames = new();
+        private static readonly Dictionary<string, FastGameRoom> FastGames = new();
 
         public static async Task<ResultCode> CreateNewGame(Player creator, string roomName, bool isExtended, bool isPrivate, bool isRanking)
         {
@@ -30,28 +32,95 @@ namespace MafiaTelegramBot.Controllers
             });
         }
 
+        public static async Task<ResultCode> ConnectToFastGame(Player player)
+        {
+            return await Task.Run(async () =>
+            {
+                async Task<FastGameRoom> GetMax(List<FastGameRoom> rooms)
+                {
+                    var maxIndex = 0;
+                    for (var i = 0; i < rooms.Count; ++i)
+                    {
+                        var first = await rooms[maxIndex].GetCapacity();
+                        var second = await rooms[i].GetCapacity();
+                        if (first < second && !rooms[i].IsRunning) maxIndex = i;
+                    }
+                    return rooms[maxIndex];
+                }
+                FastGameRoom room;
+                if (FastGames.Count == 0)
+                {
+                    room = new FastGameRoom { Owner = null, RoomName = "FastGame" + Utilities.Rnd.Next(10000) };
+                    room.SetTimer();
+                    FastGames.Add(RoomEncrypter.GetCode(room.RoomName), room);
+                }
+                else
+                {
+                    room = await GetMax(FastGames.Values.ToList());
+                    if (room.IsRunning)
+                    {
+                        room = new FastGameRoom { Owner = null, RoomName = "FastGame" + Utilities.Rnd.Next(10000) };
+                        room.SetTimer();
+                        FastGames.Add(RoomEncrypter.GetCode(room.RoomName), room);
+                    }
+                }
+                player.SetRoomName(room.RoomName);
+                room.Players.Add(player.Id, player);
+                if (room.Players.Count > Constants.MEMORY_CLEANER_PLAYERS_COUNT) room.StopTimer();
+                await room.PlayersMessageChannel.SendExcept(player.Id, $"{player.NickName} {strings.connected_to_game}");
+                room.PlayersMessageChannel.AddPerson(player.ChatId);
+                await Bot.SendWithMarkdown2(player.ChatId, strings.you_connect_to_fast_game, Keyboard.PlayerGameMenu);
+                await Bot.SendHyperLink(player.ChatId, $"<a href='https://t.me/{AppSettings.Name}?start=fast_game={RoomEncrypter.GetCode(room.RoomName)}'>{strings.link}</a>");
+                if (room.Players.Count == Constants.PLAYERS_TO_START_FAST_GAME)
+                {
+                    await room.Prepare();
+                    room.Start();
+                }
+                return ResultCode.CodeOk;
+            });
+        }
+
+        public static async Task<ResultCode> ConnectToFastGame(Player player, string roomKey)
+        {
+            return await Task.Run(async () =>
+            {
+                var result = await ConnectToGame(player, roomKey);
+                if (result != ResultCode.CodeOk) return result;
+                var room = GetRoom(roomKey);
+                await Bot.SendWithMarkdown2(player.ChatId, strings.you_connect_to_fast_game, Keyboard.PlayerGameMenu);
+                await Bot.SendHyperLink(player.ChatId, $"<a href='https://t.me/{AppSettings.Name}?start=fast_game={RoomEncrypter.GetCode(room.RoomName)}'>{strings.link}</a>");
+                if (room.Players.Count == Constants.PLAYERS_TO_START_FAST_GAME)
+                {
+                    await room.Prepare();
+                    room.Start();
+                }
+                return ResultCode.CodeOk;
+            });
+        }
+
         public static async Task<ResultCode> ConnectToGame(Player player, string roomKey)
         {
             return await Task.Run(async () =>
             {
-                if (!OpenedGames.ContainsKey(roomKey)) return ResultCode.RoomDoesNotExist;
+                var room = GetRoom(roomKey);
+                if (room == null) return ResultCode.RoomDoesNotExist;
                 var roomName = RoomEncrypter.GetName(roomKey);
-                if (OpenedGames[roomKey].IsRunning) return ResultCode.GameAlreadyRunning;
-                if (OpenedGames[roomKey].IsFilled()) return ResultCode.RoomIsFilled;
+                if (room.IsRunning) return ResultCode.GameAlreadyRunning;
+                if (room.IsFilled()) return ResultCode.RoomIsFilled;
                 if (!player.SetRoomName(roomName)) return ResultCode.UserAlreadyInGame;
-                OpenedGames[roomKey].Players.Add(player.Id, player);
+                room.Players.Add(player.Id, player);
 
-                if (OpenedGames[roomKey].Players.Count > Constants.MEMORY_CLEANER_PLAYERS_COUNT) OpenedGames[roomKey].StopTimer();
+                if (room.Players.Count > Constants.MEMORY_CLEANER_PLAYERS_COUNT) room.StopTimer();
                 
-                await OpenedGames[roomKey].PlayersMessageChannel.SendExcept(player.Id, $"{player.NickName} {strings.connected_to_game}");
-                OpenedGames[roomKey].PlayersMessageChannel.AddPerson(player.ChatId);
+                await room.PlayersMessageChannel.SendExcept(player.Id, $"{player.NickName} {strings.connected_to_game}");
+                room.PlayersMessageChannel.AddPerson(player.ChatId);
                 return ResultCode.CodeOk;
             });
         }
 
         public static List<Player> GetPlayers(string roomKey)
         {
-                return OpenedGames[roomKey].Players.Values.ToList();
+                return GetRoom(roomKey).Players.Values.ToList();
         }
         
         public static async Task<ResultCode> LeaveFromGame(Player player)
@@ -61,13 +130,14 @@ namespace MafiaTelegramBot.Controllers
                 var roomName = player.GetRoomName();
                 var roomKey = RoomEncrypter.GetCode(roomName);
                 if (!player.RemoveGame()) return ResultCode.UserNotInGame;
-                await OpenedGames[roomKey].Leave(player);
-                if (!OpenedGames[roomKey].IsRunning &&
-                    OpenedGames[roomKey].Players.Count <= Constants.MEMORY_CLEANER_INTERVAL)
-                    OpenedGames[roomKey].StartTimer();
-                if (OpenedGames[roomKey].Players.Count >= 0) return ResultCode.CodeOk;
+                var room = GetRoom(roomKey);
+                if (room == null) return ResultCode.CodeOk;
+                await room.Leave(player);
+                if (!room.IsRunning && room.Players.Count <= Constants.MEMORY_CLEANER_INTERVAL) room.StartTimer();
+                if (room.Players.Count >= 0) return ResultCode.CodeOk;
                 RoomEncrypter.RemoveCode(roomName);
                 OpenedGames.Remove(roomKey);
+                FastGames.Remove(roomKey);
                 return ResultCode.CodeOk;
             });
         }
@@ -76,18 +146,20 @@ namespace MafiaTelegramBot.Controllers
         {
             await Task.Run(async () =>
             {
-                if (OpenedGames.ContainsKey(roomKey))
+                var room = GetRoom(roomKey);
+                if (room != null)
                 {
-                    foreach (var player in OpenedGames[roomKey].Players.Values)
+                    foreach (var player in room.Players.Values)
                     {
-                        await OpenedGames[roomKey].PlayersMessageChannel.SendTo(player.Id, strings.room_dissolved,
+                        await room.PlayersMessageChannel.SendTo(player.Id, strings.room_dissolved,
                             player.IsAdmin ? Keyboard.AdminMainMenu : Keyboard.MainMenu);
                         player.RemoveGame();
                         player.ResetState();
                     }
-                    RoomEncrypter.RemoveCode(OpenedGames[roomKey].RoomName);
-                    OpenedGames[roomKey].DeleteTimer();
+                    RoomEncrypter.RemoveCode(room.RoomName);
+                    room.DeleteTimer();
                     OpenedGames.Remove(roomKey);
+                    FastGames.Remove(roomKey);
                 }
             });
         }
@@ -138,7 +210,10 @@ namespace MafiaTelegramBot.Controllers
         
         public static GameRoom GetRoom(string roomKey)
         {
-            return OpenedGames[roomKey];
+            return OpenedGames.ContainsKey(roomKey)
+                ? OpenedGames[roomKey]
+                : FastGames.ContainsKey(roomKey)
+                    ? FastGames[roomKey] : null;
         }
     }
 }

+ 3 - 2
MafiaTelegramBot/DataBase/EntityDao/UserDao.cs

@@ -7,6 +7,7 @@ using MafiaTelegramBot.DataBase.Entity;
 using MafiaTelegramBot.Game;
 using MafiaTelegramBot.Resources;
 using Microsoft.EntityFrameworkCore;
+using static MafiaTelegramBot.Logs;
 
 namespace MafiaTelegramBot.DataBase.EntityDao
 {
@@ -61,7 +62,7 @@ namespace MafiaTelegramBot.DataBase.EntityDao
             try
             {
                 await DataBase.SaveChangesAsync();
-                Console.WriteLine($"[{DateTime.Now.ToString()}] Saving");
+                LogOut("Saving changes");
                 foreach (var (id, player) in ActiveUsers)
                 {
                     if (player.GetLastActivityInterval()
@@ -72,7 +73,7 @@ namespace MafiaTelegramBot.DataBase.EntityDao
             }
             catch (Exception e)
             {
-                Console.WriteLine($"[{DateTime.Now.ToString()}] {e.Message}");
+                LogOutWarning(e.Message);
             }
         }
 

+ 127 - 0
MafiaTelegramBot/Game/GameRooms/FastGameRoom.cs

@@ -0,0 +1,127 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Timers;
+using MafiaTelegramBot.Controllers;
+using MafiaTelegramBot.CustomCollections.Extensions;
+using MafiaTelegramBot.Models;
+using MafiaTelegramBot.Resources;
+
+namespace MafiaTelegramBot.Game.GameRooms
+{
+    public class FastGameRoom : GameRoom
+    {
+        public List<Player> ContinuePlayers = new ();
+
+        public async Task RunContinueVote()
+        {
+            var result = await PlayersMessageChannel.Send(strings.press_button_to_continue_game, Keyboard.ContinueButton);
+            var timer = new Timer { Interval = Constants.TIME_TO_DECIDE, AutoReset = false, Enabled = true};
+            timer.Elapsed += async (_, _) =>
+            {
+                foreach (var (k, v) in result)
+                    await Bot.Get().DeleteMessageAsync(k, v);
+                await ApplyResults();
+            };
+        }
+
+        private async Task ApplyResults()
+        {
+            foreach (var (k, v) in Players)
+                if (!ContinuePlayers.Contains(v))
+                {
+                    var result = await RoomController.LeaveFromGame(v);
+                    if (result == ResultCode.CodeOk)
+                        await Bot.SendWithMarkdown2(k, $"{strings.you_leave_from_game} _*{RoomName}*_",
+                            v.IsAdmin ? Keyboard.AdminMainMenu : Keyboard.MainMenu);
+                    else await Utilities.GetResultCodeMessage(result, k);
+                }
+            ContinuePlayers.Clear();
+            if (Players.Values.Count == Constants.PLAYERS_TO_START_FAST_GAME)
+            {
+                await Prepare();
+                Start();
+            }
+            else if (Players.Values.Count == 0) await RoomController.DissolveRoom(RoomEncrypter.GetCode(RoomName));
+        }
+        protected override async Task<ResultCode> ReadSettings()
+        {
+            return await Task.Run(() =>
+            {
+                List<Roles> openedRoles = new();
+                foreach (var player in Players.Values)
+                    openedRoles.ConcatenateUnique(player.OpenedRoles.ToList());
+                var mafiaList = new List<Roles>();
+                if(openedRoles.Remove(Roles.Don)) mafiaList.Add(Roles.Don);
+                if(openedRoles.Remove(Roles.Dame)) mafiaList.Add(Roles.Dame);
+                if(openedRoles.Remove(Roles.Werewolf)) mafiaList.Add(Roles.Werewolf);
+                if(openedRoles.Remove(Roles.Lawyer)) mafiaList.Add(Roles.Lawyer);
+                for (var i = 0; i < Players.Count / 3; ++i)
+                {
+                    var chance = Utilities.Rnd.Next(100);
+                    if (chance < Constants.ACTIVE_MAFIA_CHANCE)
+                    {
+                        var role = mafiaList.GetAndRemove(Utilities.Rnd.Next(mafiaList.Count));
+                        switch (role)
+                        {
+                            case Roles.Don or Roles.Dame:
+                                mafiaList.Remove(Roles.Dame);
+                                mafiaList.Remove(Roles.Don);
+                                break;
+                        }
+                        Settings.Add(role, 1);
+                    }
+                    else if (Settings.ContainsKey(Roles.Mafia)) Settings[Roles.Mafia]++;
+                    else Settings.Add(Roles.Mafia, 1);
+                }
+                for (var i = 0; i < Players.Count - Players.Count / 3; ++i)
+                {
+                    var chance = Utilities.Rnd.Next(100);
+                    if (chance < Constants.ACTIVE_VILLAGER_CHANCE)
+                    {
+                        var role = openedRoles.GetAndRemove(Utilities.Rnd.Next(openedRoles.Count));
+                        switch (role)
+                        {
+                            case Roles.Doctor or Roles.Bodyguard or Roles.Necromancer:
+                                openedRoles.Remove(Roles.Doctor);
+                                openedRoles.Remove(Roles.Bodyguard);
+                                openedRoles.Remove(Roles.Necromancer);
+                                break;
+                            case Roles.Cop or Roles.Detective or Roles.Journalist:
+                                openedRoles.Remove(Roles.Cop);
+                                openedRoles.Remove(Roles.Detective);
+                                openedRoles.Remove(Roles.Journalist);
+                                break;
+                        }
+                        Settings.Add(role, 1);
+                    }
+                    else if (Settings.ContainsKey(Roles.Villager)) Settings[Roles.Villager]++;
+                    else Settings.Add(Roles.Villager, 1);
+                }
+                return ResultCode.CodeOk;
+            });
+        }
+
+        public override bool IsExtended { get; init; } = true;
+        public override Dictionary<Roles, List<Player>> PlayersRole { get; set; } = new()
+        {
+            //passive roles
+            [Roles.Elder] = new List<Player>(),
+            [Roles.Fool] = new List<Player>(),
+            [Roles.Villager] = new List<Player>(),
+            //Active roles
+            [Roles.Detective] = new List<Player>(),
+            [Roles.Hooker] = new List<Player>(),
+            [Roles.Mafia] = new List<Player>(),
+            [Roles.Don] = new List<Player>(),
+            [Roles.Dame] = new List<Player>(),
+            [Roles.Bodyguard] = new List<Player>(),
+            [Roles.Doctor] = new List<Player>(),
+            [Roles.Necromancer] = new List<Player>(),
+            [Roles.Cop] = new List<Player>(),
+            [Roles.Journalist] = new List<Player>(),
+            [Roles.Lawyer] = new List<Player>(),
+            [Roles.Werewolf] = new List<Player>(),
+            [Roles.Parasite] = new List<Player>(),
+        };
+    }
+}

+ 13 - 23
MafiaTelegramBot/Game/GameRooms/GameRoom.GameProcess.cs

@@ -56,17 +56,10 @@ namespace MafiaTelegramBot.Game.GameRooms
             IsDay = false;
             await Task.Run(async () =>
             {
-                try
-                {
-                    foreach (var player in Players.Values)
-                    {
-                        player.Statistics[player.CurrentRole.RoleKey].Games++;
-                        player.Statistics[Roles.All].Games++;
-                    }
-                }
-                catch (Exception e)
+                foreach (var player in Players.Values)
                 {
-                    await Console.Out.WriteLineAsync($"[{DateTime.Now.ToString()}] {e.Message}");
+                    player.Statistics[player.CurrentRole.RoleKey].Games++;
+                    player.Statistics[Roles.All].Games++;
                 }
 
                 await PlayersMessageChannel.SendSticker(Stickers.Sticker["Night"]);
@@ -89,15 +82,6 @@ namespace MafiaTelegramBot.Game.GameRooms
                 timer.Elapsed += (_, _) => resetEvent.Set();
                 timer.Start();
 
-                try
-                {
-                    await UserDao.DataBase.SaveChangesAsync();
-                }
-                catch (Exception e)
-                {
-                    await Console.Out.WriteLineAsync($"[{DateTime.Now.ToString()}] {e.Message}");
-                }
-
                 foreach (var player in Players.Values)
                 {
                     if (player.GetRole() is not (Roles.Don or Roles.Mafia or Roles.Dame))
@@ -163,13 +147,14 @@ namespace MafiaTelegramBot.Game.GameRooms
             {
                 var player = _turnOrder.Dequeue();
                 if (!Players.ContainsKey(player.Id)) continue;
-                if (!player.IsBlocked && player.IsAlive)
+                if (!player.IsBlocked && player.IsAlive && Owner != null)
                 {
                     await PlayersMessageChannel.Send($"{strings.now_turn} ({player.TurnOrder}) {player.NickName}");
                     if (TimerEnabled) await Bot.SendWithMarkdown2(player.ChatId, strings.you_turn_say);
                     await player.CurrentRole.SpeakAction(Constants.DAY_SPEAK_INTERVAL);
                     if (Players.ContainsKey(player.Id)) await player.CurrentRole.VotingAction(VoteUpList.Count == 0);
                 }
+                else if (Owner == null) await player.CurrentRole.VotingAction(VoteUpList.Count == 0);
 
                 if (toEndQueue != null) _turnOrder.Enqueue(player);
                 else toEndQueue = player;
@@ -452,15 +437,20 @@ namespace MafiaTelegramBot.Game.GameRooms
                 _turnOrder.Clear();
                 Settings.Clear();
                 foreach (var (_, list) in PlayersRole) list.Clear();
-                if (!Players.ContainsKey(Owner.Id)) await RoomController.DissolveRoom(RoomEncrypter.GetCode(RoomName));
+                if(this is FastGameRoom r)
+                {
+                    await PlayersMessageChannel.Send(strings.thanks_for_game);
+                    await r.RunContinueVote();
+                }
+                else if (!Players.ContainsKey(Owner.Id))
+                    await RoomController.DissolveRoom(RoomEncrypter.GetCode(RoomName));
                 else
                 {
                     await PlayersMessageChannel.SendTo(Owner.ChatId, strings.thanks_for_game, Keyboard.OwnerGameMenu);
                     await PlayersMessageChannel.SendExcept(Owner.Id, strings.thanks_for_game, Keyboard.PlayerGameMenu);
+                    if (Players.Count <= Constants.MEMORY_CLEANER_PLAYERS_COUNT) StartTimer();
                 }
-
                 await UserDao.DataBase.SaveChangesAsync();
-                if (Players.Count <= Constants.MEMORY_CLEANER_PLAYERS_COUNT) StartTimer();
             });
         }
 

+ 13 - 0
MafiaTelegramBot/Game/GameRooms/GameRoom.MessageChannels.cs

@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
@@ -31,6 +32,18 @@ namespace MafiaTelegramBot.Game.GameRooms
                 foreach (var id in Players)
                     await Bot.SendWithMarkdown2(id, message, replyMarkup);
             }
+            
+            public async Task<Dictionary<long, int>> Send(string message, Func<long, IReplyMarkup> keyboard)
+            {
+                var messages = new Dictionary<long, int>();
+                foreach (var id in Players)
+                {
+                    var result = await Bot.SendWithMarkdown2(id, message, keyboard(id));
+                    messages.Add(id, result.MessageId);
+                }
+
+                return messages;
+            }
 
             public async Task SendExcept(long playerId, string message, IReplyMarkup replyMarkup = null)
             {

+ 4 - 4
MafiaTelegramBot/Game/GameRooms/GameRoom.Structure.cs

@@ -15,9 +15,9 @@ namespace MafiaTelegramBot.Game.GameRooms
         public bool IsDay;
         public int MaxPlayers = 10;
         private int _minPlayers = Constants.PLAYER_LIMITS_MIN;
-        public bool IsPrivate { get; init; }
+        public bool IsPrivate { get; init; } = true;
         public abstract bool IsExtended { get; init; }
-        public bool IsRanking { get; init; }
+        public bool IsRanking { get; init; } = false;
         public bool TimerEnabled { get; set; } = true;
         public string RoomName { get; init; } = "NoNameRoom";
         public Player Owner { get; init; } = new();
@@ -53,7 +53,7 @@ namespace MafiaTelegramBot.Game.GameRooms
             return await Task.Run(() =>
             {
                 var players = Players.Values.ToList();
-                players.Remove(Owner);
+                if (Owner != null) players.Remove(Owner);
                 return players;
             });
         }
@@ -68,7 +68,7 @@ namespace MafiaTelegramBot.Game.GameRooms
             Players.Remove(player.Id);
             PlayersMessageChannel.Remove(player.Id);
             MafiaMessageChannel.Remove(player.Id);
-            var message = Owner.Id == player.Id
+            var message = Owner != null && Owner.Id == player.Id
                 ? $"{player.NickName} ({strings.room_owner}) {strings.leave_from_game}"
                 : $"{player.NickName} {strings.leave_from_game}";
             await PlayersMessageChannel.Send(message);

+ 4 - 1
MafiaTelegramBot/Game/GameRooms/GameRoom.Timer.cs

@@ -35,7 +35,10 @@ namespace MafiaTelegramBot.Game.GameRooms
                 switch (_minutes)
                 {
                     case Constants.MINUTES_UNTIL_DISSOLVE - 2:
-                        await Bot.SendWithMarkdown2(Owner.ChatId, strings.dissolve_warning);
+                        if (Owner != null)
+                            await Bot.SendWithMarkdown2(Owner.ChatId, strings.dissolve_warning);
+                        else
+                            await PlayersMessageChannel.Send(strings.dissolve_warning);
                         _minutes++;
                         break;
                     case Constants.MINUTES_UNTIL_DISSOLVE:

+ 22 - 0
MafiaTelegramBot/Logs.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+// ReSharper disable LocalizableElement
+
+namespace MafiaTelegramBot
+{
+    public static class Logs
+    {
+        public static void LogOut(string message)
+        {
+            Console.WriteLine($"[INFO] [{DateTime.Now.ToString(CultureInfo.CurrentCulture)}] {message}");
+        }
+        public static void LogOutWarning(string message)
+        {
+            Console.WriteLine($"[WARNING] [{DateTime.Now.ToString(CultureInfo.CurrentCulture)}] {message}");
+        }
+        public static void LogOutError(string message)
+        {
+            Console.WriteLine($"[ERROR] [{DateTime.Now.ToString(CultureInfo.CurrentCulture)}] {message}");
+        }
+    }
+}

+ 6 - 4
MafiaTelegramBot/Models/Bot.cs

@@ -12,6 +12,7 @@ using Telegram.Bot;
 using Telegram.Bot.Types;
 using Telegram.Bot.Types.Enums;
 using Telegram.Bot.Types.ReplyMarkups;
+using static MafiaTelegramBot.Logs;
 
 namespace MafiaTelegramBot.Models
 {
@@ -56,6 +57,7 @@ namespace MafiaTelegramBot.Models
                 new DissolveRoomCommand(),
                 new StartGameCommand(),
                 new AdminPanelCommand(),
+                new FastGameCommand(),
             };
         }
         
@@ -108,7 +110,7 @@ namespace MafiaTelegramBot.Models
             }
             catch (Exception e)
             {
-                Console.WriteLine($"[{DateTime.Now.ToString()}] {e.Message}");
+                LogOutError(e.Message);
                 return new Message();
             }
         }
@@ -121,7 +123,7 @@ namespace MafiaTelegramBot.Models
             }
             catch (Exception e)
             {
-                Console.WriteLine($"[{DateTime.Now.ToString()}] {e.Message}");
+                LogOutError(e.Message);
                 return new Message();
             }
         }
@@ -136,7 +138,7 @@ namespace MafiaTelegramBot.Models
             }
             catch (Exception e)
             {
-                Console.WriteLine($"[{DateTime.Now.ToString()}] {e.Message}");
+                LogOutError(e.Message);
                 return new Message();
             }
         }
@@ -150,7 +152,7 @@ namespace MafiaTelegramBot.Models
             }
             catch (Exception e)
             {
-                Console.WriteLine($"[{DateTime.Now.ToString()}] {e.Message}");
+                LogOutError(e.Message);
                 return new Message();
             }
         }

+ 4 - 0
MafiaTelegramBot/Models/Commands/Command.cs

@@ -42,6 +42,8 @@ namespace MafiaTelegramBot.Models.Commands
             {
                 var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
                 var room = RoomController.GetRoom(roomKey);
+                if(room == null)
+                    return await Bot.SendWithMarkdown2(chatId, strings.room_does_not_exists);
                 return await room.MHandler.Handle(update);
             }
             
@@ -60,6 +62,8 @@ namespace MafiaTelegramBot.Models.Commands
             {
                 var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
                 var room = RoomController.GetRoom(roomKey);
+                if(room == null)
+                    return await Bot.SendWithMarkdown2(chatId, strings.room_does_not_exists);
                 return await room.MHandler.Handle(update);
             }
         }

+ 21 - 0
MafiaTelegramBot/Models/Commands/FastGameCommand.cs

@@ -0,0 +1,21 @@
+using System.Threading.Tasks;
+using MafiaTelegramBot.Controllers;
+using MafiaTelegramBot.DataBase.EntityDao;
+using MafiaTelegramBot.Resources;
+using Telegram.Bot.Types;
+
+namespace MafiaTelegramBot.Models.Commands
+{
+    public class FastGameCommand : Command
+    {
+        protected override string Name => keyboard.fast_game;
+        protected override async Task<Message> Execute(Update update)
+        {
+            var player = await UserDao.GetPlayerById(UserId);
+            if (player.GetRoomName() != "")
+                return await Bot.SendWithMarkdown2(ChatId, strings.user_already_in_game);
+            await RoomController.ConnectToFastGame(player);
+            return new Message();
+        }
+    }
+}

+ 2 - 0
MafiaTelegramBot/Models/Commands/KickPlayerCommand.cs

@@ -15,6 +15,8 @@ namespace MafiaTelegramBot.Models.Commands
             var user = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             var players = await room.GetPlayers();
             if (players.Count > 0)
                 return await Bot.SendWithMarkdown2(ChatId, strings.kick_user, Keyboard.KickKeyboard(players));

+ 2 - 0
MafiaTelegramBot/Models/Commands/LookPlayersListCommand.cs

@@ -17,6 +17,8 @@ namespace MafiaTelegramBot.Models.Commands
             var user = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             var users = RoomController.GetPlayers(roomKey);
             var list = users.Count > 1
                 ? users.Where(player => !player.Equals(user))

+ 2 - 0
MafiaTelegramBot/Models/Commands/RoomSettingsCommand.cs

@@ -16,6 +16,8 @@ namespace MafiaTelegramBot.Models.Commands
             var user = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             var status = room.TimerEnabled ? strings.enabled : strings.disabled;
             return await Bot.SendWithMarkdown2(ChatId, strings.what_settings, 
                 Keyboard.SettingsRoomKeyboard(UserId, $"{strings.timer}: {status}"));

+ 11 - 0
MafiaTelegramBot/Models/Commands/StartCommand.cs

@@ -24,6 +24,7 @@ namespace MafiaTelegramBot.Models.Commands
                 {
                     "giveaway_id" => await GiveawayCommand(player, int.Parse(values[1])),
                     "room_key" => await ConnectToGameCommand(player, values[1]),
+                    "fast_game" => await ConnectToFastGameCommand(player, values[1]),
                     _ => new Message()
                 };
             }
@@ -62,5 +63,15 @@ namespace MafiaTelegramBot.Models.Commands
                 ? await Bot.SendWithMarkdown2(ChatId, strings.successful_entry_into_room, Keyboard.PlayerGameMenu)
                 : await Utilities.GetResultCodeMessage(code, ChatId);
         }
+
+        private async Task<Message> ConnectToFastGameCommand(Player player, string roomKey)
+        {
+            if (player.GetRoomName() != "")
+                return await Bot.SendWithMarkdown2(ChatId, $"{strings.prefer_leave_from_room} {player.GetRoomName()}");
+            var code = await RoomController.ConnectToFastGame(player, roomKey);
+            return code == ResultCode.CodeOk
+                ? new Message()
+                : await Utilities.GetResultCodeMessage(code, ChatId);
+        }
     }
 }

+ 2 - 0
MafiaTelegramBot/Models/Commands/StartGameCommand.cs

@@ -15,6 +15,8 @@ namespace MafiaTelegramBot.Models.Commands
             var owner = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(owner.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             if (room.IsExtended) return await Bot.SendWithMarkdown2(owner.ChatId, strings.continue_question, Keyboard.StartExtendedRoomKeyboard(UserId));
             var result = await Bot.SendWithMarkdown2(owner.ChatId, strings.game_process_started);
             var resultCode = await room.Prepare();

+ 2 - 0
MafiaTelegramBot/Models/Inlines/ApplyRolesChangeQuery.cs

@@ -17,6 +17,8 @@ namespace MafiaTelegramBot.Models.Inlines
             var data = update.CallbackQuery.Data.Split('|');
             var roomKey = data[2];
             _room = (ExtendedGameRoom) RoomController.GetRoom(roomKey);
+            if(_room == null)
+                await Bot.EditMessageAsync(ChatId, update.CallbackQuery.Message.MessageId, strings.room_does_not_exists);
             var roleQuery = Enum.Parse<Roles>(data[3]);
             if(roleQuery is Roles.Villager or Roles.Mafia) MafiaVillager(roleQuery, data[4]);
             else SwitchRole(roleQuery);

+ 2 - 0
MafiaTelegramBot/Models/Inlines/ChangeRolesQuery.cs

@@ -17,6 +17,8 @@ namespace MafiaTelegramBot.Models.Inlines
             var user = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
             var room = (ExtendedGameRoom) RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             if(room.CustomRoomSettings.Count == 0) await room.InitSettings();
             var message = $"{strings.current_enabled_roles}";
             foreach (var (role, count) in room.CustomRoomSettings)

+ 2 - 0
MafiaTelegramBot/Models/Inlines/PlayersCountSettingsQuery.cs

@@ -17,6 +17,8 @@ namespace MafiaTelegramBot.Models.Inlines
             var user = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             var roomMaxCapacity = room.MaxPlayers;
             return await Bot.SendWithMarkdown2(ChatId, $"{strings.max_capacity_message}: {roomMaxCapacity}", Keyboard.SetMaximumKeyboard(UserId));
         }

+ 3 - 1
MafiaTelegramBot/Models/Inlines/Query.cs

@@ -30,7 +30,9 @@ namespace MafiaTelegramBot.Models.Inlines
             {
                 var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
                 var room = RoomController.GetRoom(roomKey);
-                await room.QHandler.Handle(update);
+                if(room == null)
+                    await Bot.SendWithMarkdown2(chatId, strings.room_does_not_exists);
+                else await room.QHandler.Handle(update);
             }
             else
             {

+ 2 - 0
MafiaTelegramBot/Models/Inlines/SetPlayersMaximumQuery.cs

@@ -19,6 +19,8 @@ namespace MafiaTelegramBot.Models.Inlines
             var user = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(user.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             room.MaxPlayers = count;
             var message = $"{strings.maximum_was_set_to}: _*{count}*_ {strings.players}";
             return await Bot.SendWithMarkdown2(ChatId, message);

+ 2 - 0
MafiaTelegramBot/Models/Inlines/StartGameQuery.cs

@@ -16,6 +16,8 @@ namespace MafiaTelegramBot.Models.Inlines
             var owner = await UserDao.GetPlayerById(UserId);
             var roomKey = RoomEncrypter.GetCode(owner.GetRoomName());
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.SendWithMarkdown2(ChatId, strings.room_does_not_exists);
             var result = await Bot.SendWithMarkdown2(owner.ChatId, strings.game_process_started);
             var resultCode = await room.Prepare();
             if (resultCode != ResultCode.CodeOk) return await Utilities.GetResultCodeMessage(resultCode, ChatId);

+ 2 - 0
MafiaTelegramBot/Models/Inlines/SwitchTimerQuery.cs

@@ -16,6 +16,8 @@ namespace MafiaTelegramBot.Models.Inlines
             var roomName = user.GetRoomName();
             var roomKey = RoomEncrypter.GetCode(roomName);
             var room = RoomController.GetRoom(roomKey);
+            if(room == null)
+                return await Bot.EditMessageAsync(ChatId, update.CallbackQuery.Message.MessageId, strings.room_does_not_exists);
             room.TimerEnabled = !room.TimerEnabled;
             var status = room.TimerEnabled ? strings.enabled : strings.disabled;
             return await Bot.EditMessageAsync(ChatId, update.CallbackQuery.Message.MessageId, $"{strings.timer}: {status}");

+ 2 - 1
MafiaTelegramBot/Resources/Callback.cs

@@ -30,6 +30,7 @@ namespace MafiaTelegramBot.Resources
         ConnectToRanked,
         GiveToAll,
         GiveToSelected,
-        GiveRole
+        GiveRole,
+        Continue
     }
 }

+ 9 - 3
MafiaTelegramBot/Resources/Constants.cs

@@ -1,23 +1,29 @@
 using System;
 using System.Collections.Generic;
 using Telegram.Bot.Types.Payments;
+// ReSharper disable UnreachableCode
+// ReSharper disable InconsistentNaming
 
 namespace MafiaTelegramBot.Resources
 {
     public static class Constants
     {
-        public const bool DEBUG = false;
+        public const bool DEBUG = true;
         
         public const int PLAYER_LIMITS_MIN = DEBUG ? 1 : 6;
         public const int MEMORY_CLEANER_INTERVAL = DEBUG ? 20 * 1000 : 30 * 60 * 1000;
         public const int MEMORY_CLEANER_PLAYERS_COUNT = DEBUG ? 1 : 6;
-        // ReSharper disable once InconsistentNaming
         public static readonly TimeSpan PLAYER_INACTIVE_INTERVAL = new(0, 40, 0);
         public const int MINUTES_UNTIL_DISSOLVE = 10;
         public const int MAX_SHOWING_ROOMS = 10;
+
+        public const int TIME_TO_DECIDE = DEBUG ? 10*1000 : 30 * 1000;
+        
+        public const int ACTIVE_MAFIA_CHANCE = 30;
+        public const int ACTIVE_VILLAGER_CHANCE = 50;
+        public const int PLAYERS_TO_START_FAST_GAME = DEBUG ? 1 : 10;
         
         public const string SHOP_CURRENCY = "RUB";
-        // ReSharper disable once InconsistentNaming
         public static List<LabeledPrice> RANDOM_ROLE_PRICE = new()
         {
             new LabeledPrice {Label = "100 рублей", Amount = 10000}

+ 11 - 0
MafiaTelegramBot/Resources/Keyboard.cs

@@ -12,6 +12,7 @@ namespace MafiaTelegramBot.Resources
     {
         public static readonly ReplyKeyboardMarkup MainMenu = new(new[]
             {
+                new KeyboardButton[] {keyboard.fast_game},
                 new KeyboardButton[] {keyboard.create_game},
                 new KeyboardButton[] {keyboard.connect_game, keyboard.show_profile}
             }, true
@@ -20,6 +21,7 @@ namespace MafiaTelegramBot.Resources
         public static readonly ReplyKeyboardMarkup AdminMainMenu = new(new[]
             {
                 new KeyboardButton[] {keyboard.admin_panel},
+                new KeyboardButton[] {keyboard.fast_game},
                 new KeyboardButton[] {keyboard.create_game},
                 new KeyboardButton[] {keyboard.connect_game, keyboard.show_profile},
             }, true
@@ -343,5 +345,14 @@ namespace MafiaTelegramBot.Resources
             }
             return new InlineKeyboardMarkup(keyboard);
         }
+
+        public static InlineKeyboardMarkup ContinueButton(long userId)
+        {
+            return new InlineKeyboardMarkup(new[]
+                {
+                    InlineKeyboardButton.WithCallbackData(strings._continue, $"{Callback.Continue}|{userId}")
+                }
+            );
+        }
     }
 }

+ 6 - 0
MafiaTelegramBot/Resources/keyboard.Designer.cs

@@ -110,5 +110,11 @@ namespace MafiaTelegramBot.Resources {
                 return ResourceManager.GetString("admin_panel", resourceCulture);
             }
         }
+        
+        internal static string fast_game {
+            get {
+                return ResourceManager.GetString("fast_game", resourceCulture);
+            }
+        }
     }
 }

+ 3 - 0
MafiaTelegramBot/Resources/keyboard.resx

@@ -51,4 +51,7 @@
     <data name="admin_panel" xml:space="preserve">
         <value>Панель администрирования</value>
     </data>
+    <data name="fast_game" xml:space="preserve">
+        <value>Быстрая игра</value>
+    </data>
 </root>

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

@@ -1394,5 +1394,23 @@ namespace MafiaTelegramBot {
                 return ResourceManager.GetString("not_cop", resourceCulture);
             }
         }
+        
+        internal static string you_connect_to_fast_game {
+            get {
+                return ResourceManager.GetString("you_connect_to_fast_game", resourceCulture);
+            }
+        }
+        
+        internal static string press_button_to_continue_game {
+            get {
+                return ResourceManager.GetString("press_button_to_continue_game", resourceCulture);
+            }
+        }
+        
+        internal static string _continue {
+            get {
+                return ResourceManager.GetString("continue", resourceCulture);
+            }
+        }
     }
 }

+ 9 - 0
MafiaTelegramBot/Resources/strings.resx

@@ -693,4 +693,13 @@
     <data name="not_cop" xml:space="preserve">
         <value>Не комиссар</value>
     </data>
+    <data name="you_connect_to_fast_game" xml:space="preserve">
+        <value>Вы подключились к комнате режима "Быстрая игра". Как только будет набрано 10 человек, игра будет начата.</value>
+    </data>
+    <data name="press_button_to_continue_game" xml:space="preserve">
+        <value>Нажмите на кнопку чтобы остаться в комнате</value>
+    </data>
+    <data name="continue" xml:space="preserve">
+        <value>Продолжить</value>
+    </data>
 </root>