diff --git a/SaladimQBot.Extensions/-Service/CommandWaiter.cs b/SaladimQBot.Extensions/-Service/CommandWaiter.cs index ba03f54..54f4367 100644 --- a/SaladimQBot.Extensions/-Service/CommandWaiter.cs +++ b/SaladimQBot.Extensions/-Service/CommandWaiter.cs @@ -11,7 +11,7 @@ public class CommandWaiter : EventWaiter public override EventWaiterChecker Checker { get; } - public CommandWaiter(SimCommandExecuter simCommandExecuter, Func checker) + public CommandWaiter(SimCommandExecuter simCommandExecuter, Func checker) { simCommandExecuter.OnCommandExecuted += this.SimCommandExecutor_OnCommandExecuted; this.checker = checker; @@ -40,6 +40,36 @@ public CommandWaiter(SimCommandExecuter simCommandExecuter, IUser executor, stri } + public CommandWaiter(SimCommandExecuter simCommandExecuter, IUser executor, string cmdName, Action argsReporter) + { + simCommandExecuter.OnCommandExecuted += this.SimCommandExecutor_OnCommandExecuted; + this.checker = (cmd, content, checkerArgs) => + { + if (content.Executor.IsSameUser(executor) && cmd.Name == cmdName) + { + argsReporter(checkerArgs); + return true; + } + return false; + }; + Checker = DefaultChecker; + } + + public CommandWaiter(SimCommandExecuter simCommandExecuter, IGroupUser executor, string cmdName, Action argsReporter) + { + simCommandExecuter.OnCommandExecuted += this.SimCommandExecutor_OnCommandExecuted; + this.checker = (cmd, content, checkerArgs) => + { + if (content.Executor is IGroupUser groupUser && groupUser.IsSameGroupUser(executor) && cmd.Name == cmdName) + { + argsReporter(checkerArgs); + return true; + } + return false; + }; + Checker = DefaultChecker; + } + protected bool DefaultChecker(IIClientEvent clientEvent) { if (curState) diff --git a/SaladimQBot.Extensions/-Service/CoroutineService.cs b/SaladimQBot.Extensions/-Service/CoroutineService.cs index d27c753..cbf9e68 100644 --- a/SaladimQBot.Extensions/-Service/CoroutineService.cs +++ b/SaladimQBot.Extensions/-Service/CoroutineService.cs @@ -27,6 +27,15 @@ public void AddNewCoroutine(IEnumerator enumerator) } } + /// + /// 移除一个协程 + /// + /// + public void RemoveCoroutine(IEnumerator enumerator) + { + coroutines.Remove(enumerator); + } + /// /// 使用一个事件推动该协程 /// 在遇到EventWaiter时会停止并且等待该事件 @@ -51,8 +60,8 @@ public void PushCoroutines(IIClientEvent clientEvent) { if (markedRemoves.Contains(coroutines[i])) { - coroutines.RemoveAt(i); markedRemoves.Remove(coroutines[i]); + coroutines.RemoveAt(i); } } } diff --git a/SaladimQBot.Extensions/-Service/ISession.cs b/SaladimQBot.Extensions/-Service/ISession.cs new file mode 100644 index 0000000..00518b6 --- /dev/null +++ b/SaladimQBot.Extensions/-Service/ISession.cs @@ -0,0 +1,6 @@ +namespace SaladimQBot.Extensions; + +public interface ISession +{ + SessionId SessionId { get; set; } +} \ No newline at end of file diff --git a/SaladimQBot.Extensions/-Service/ISessionService.cs b/SaladimQBot.Extensions/-Service/IStoreSessionService.cs similarity index 67% rename from SaladimQBot.Extensions/-Service/ISessionService.cs rename to SaladimQBot.Extensions/-Service/IStoreSessionService.cs index 6a16532..1e0c1d4 100644 --- a/SaladimQBot.Extensions/-Service/ISessionService.cs +++ b/SaladimQBot.Extensions/-Service/IStoreSessionService.cs @@ -2,7 +2,7 @@ namespace SaladimQBot.Extensions; -public interface ISessionService +public interface IStoreSessionService { TSession GetSession(SessionId sessionId) where TSession : class, ISession, new(); @@ -11,20 +11,15 @@ public interface ISessionService public static class ISessionServiceExtensions { - public static TSession GetUserSession(this ISessionService service, long userId) + public static TSession GetUserSession(this IStoreSessionService service, long userId) where TSession : class, ISession, new() => service.GetSession(new SessionId(userId)); - public static TSession GetGroupSession(this ISessionService service, long groupId) + public static TSession GetGroupSession(this IStoreSessionService service, long groupId) where TSession : class, ISession, new() => service.GetSession(new SessionId(0, groupId)); - public static TSession GetGroupUserSession(this ISessionService service, long groupId, long userId) + public static TSession GetGroupUserSession(this IStoreSessionService service, long groupId, long userId) where TSession : class, ISession, new() => service.GetSession(new SessionId(userId, groupId)); } - -public interface ISession -{ - SessionId SessionId { get; set; } -} \ No newline at end of file diff --git a/SaladimQBot.Extensions/-Service/MemorySessionService.cs b/SaladimQBot.Extensions/-Service/MemorySessionService.cs new file mode 100644 index 0000000..7da2819 --- /dev/null +++ b/SaladimQBot.Extensions/-Service/MemorySessionService.cs @@ -0,0 +1,71 @@ +using System; + +namespace SaladimQBot.Extensions; + +public class MemorySessionService +{ + protected Dictionary> allSessions; + + public MemorySessionService() + { + allSessions = new(); + } + + public TSession GetSession(SessionId sessionId) where TSession : class, ISession, new() + { + Type typeSession = typeof(TSession); + if (allSessions.TryGetValue(typeSession, out var sessions)) + { + if (sessions.TryGetValue(sessionId, out var session)) + { + return (TSession)session; + } + else + { + TSession newSession = new() + { + SessionId = sessionId + }; + sessions.Add(sessionId, newSession); + return newSession; + } + } + else + { + allSessions.Add(typeSession, new()); + return GetSession(sessionId); + } + } + + public bool TryRemoveSession(SessionId sessionId) where TSession : class, ISession, new() + { + Type typeSession = typeof(TSession); + if (allSessions.TryGetValue(typeSession, out var sessions)) + { + if (sessions.TryGetValue(sessionId, out var session)) + { + sessions.Remove(session.SessionId); + return true; + } + } + return false; + } + + public TSession GetUserSession(long userId) where TSession : class, ISession, new() + => GetSession(new SessionId(userId)); + + public TSession GetGroupSession(long groupId) where TSession : class, ISession, new() + => GetSession(new SessionId(0, groupId)); + + public TSession GetGroupUserSession(long groupId, long userId) where TSession : class, ISession, new() + => GetSession(new SessionId(userId, groupId)); + + public bool TryRemoveUserSession(long userId) where TSession : class, ISession, new() + => TryRemoveSession(new SessionId(userId)); + + public bool TryRemoveGroupSession(long groupId) where TSession : class, ISession, new() + => TryRemoveSession(new SessionId(0, groupId)); + + public bool TryRemoveGroupUserSession(long groupId, long userId) where TSession : class, ISession, new() + => TryRemoveSession(new SessionId(userId, groupId)); +} \ No newline at end of file diff --git a/SaladimQBot.Extensions/-Service/SessionSqliteService.cs b/SaladimQBot.Extensions/-Service/SessionSqliteService.cs index 4433535..f9b638b 100644 --- a/SaladimQBot.Extensions/-Service/SessionSqliteService.cs +++ b/SaladimQBot.Extensions/-Service/SessionSqliteService.cs @@ -3,7 +3,7 @@ namespace SaladimQBot.Extensions; -public class SessionSqliteService : ISessionService +public class SessionSqliteService : IStoreSessionService { protected SQLiteConnection sqliteConnection; @@ -16,7 +16,7 @@ public TSession GetSession(SessionId sessionId) where TSession : ISess { lock (sqliteConnection) { - if (!typeof(SqliteSession).IsAssignableFrom(typeof(TSession))) + if (!typeof(SqliteStoreSession).IsAssignableFrom(typeof(TSession))) throw new InvalidOperationException("The session type must subclass SqliteSession."); try { @@ -45,14 +45,14 @@ public void SaveSession(TSession session) sqliteConnection.InsertOrReplace(session); } - TSession ISessionService.GetSession(SessionId sessionId) + TSession IStoreSessionService.GetSession(SessionId sessionId) => this.GetSession(sessionId); - void ISessionService.SaveSession(TSession session) + void IStoreSessionService.SaveSession(TSession session) => this.SaveSession(session); } -public abstract class SqliteSession : ISession +public abstract class SqliteStoreSession : ISession { [PrimaryKey, Column("session_string")] public string SessionString diff --git a/SaladimQBot.Extensions/CommonTypeParsers.cs b/SaladimQBot.Extensions/CommonTypeParsers.cs index 07661d9..fa2b2e5 100644 --- a/SaladimQBot.Extensions/CommonTypeParsers.cs +++ b/SaladimQBot.Extensions/CommonTypeParsers.cs @@ -19,7 +19,7 @@ public static class CommonTypeParsers public static Vector2 Vector2(string s) { //以逗号分隔 - string[] ps = s.Split(','); + string[] ps = s.Split(',', ','); if (ps.Length != 2) throw new CommonTypeParseFailedException(); //允许括号 if (ps[0].StartsWith("(") && ps[1].EndsWith(")")) @@ -32,7 +32,7 @@ public static Vector2 Vector2(string s) public static Vector3 Vector3(string s) { //以逗号分隔 - string[] ps = s.Split(','); + string[] ps = s.Split(',', ','); if (ps.Length != 3) throw new CommonTypeParseFailedException(); //允许括号 if (ps[0].StartsWith("(") && ps[2].EndsWith(")")) @@ -78,12 +78,12 @@ public static Color Color(string s) } } //假设是以逗号分隔的形式 - else if (s.Contains(',')) + else if (s.Contains(',') || s.Contains(',')) { //不是小数的形式 if (!s.Contains('.')) { - string[] nums = s.Split(','); + string[] nums = s.Split(',', ','); if (nums.Length == 3) { int r = int.Parse(nums[0]); @@ -107,7 +107,7 @@ public static Color Color(string s) //含小数点, 是小数形式 else { - string[] nums = s.Split(','); + string[] nums = s.Split(',', ','); if (nums.Length == 3) { float r = float.Parse(nums[0]); diff --git a/SaladimWpf/App.xaml.cs b/SaladimWpf/App.xaml.cs index ce40ada..92d9609 100644 --- a/SaladimWpf/App.xaml.cs +++ b/SaladimWpf/App.xaml.cs @@ -29,13 +29,15 @@ public App() coll.AddSingleton(); coll.AddSingleton(); coll.AddSingleton(); - coll.AddSingleton(); + coll.AddSingleton(); #if DEBUG coll.AddSingleton(new SessionSqliteServiceConfig(new(@"D:\User\Desktop\SaladimWPF\data\debug.db"))); #elif !DEBUG coll.AddSingleton(new SessionSqliteServiceConfig(new(@"D:\User\Desktop\SaladimWPF\data\release.db"))); #endif coll.AddSingleton(); + coll.AddSingleton(); + coll.AddSingleton(); }) .Build(); serviceProvider = AppHost.Services; diff --git a/SaladimWpf/Properties/PublishProfiles/FolderProfile.pubxml.user b/SaladimWpf/Properties/PublishProfiles/FolderProfile.pubxml.user index fc07958..894ed24 100644 --- a/SaladimWpf/Properties/PublishProfiles/FolderProfile.pubxml.user +++ b/SaladimWpf/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - True|2023-01-05T08:55:14.3920225Z;True|2023-01-05T15:08:31.6491096+08:00;False|2023-01-05T15:08:19.9781972+08:00;False|2023-01-05T15:05:41.3082089+08:00;True|2023-01-05T11:17:02.6841461+08:00;True|2023-01-05T11:01:56.5159682+08:00;True|2023-01-05T11:01:16.6067244+08:00;True|2023-01-05T11:00:41.0894016+08:00;True|2023-01-04T21:55:42.5301452+08:00;True|2023-01-04T21:54:10.5989202+08:00;True|2023-01-03T21:30:00.6144582+08:00;True|2023-01-02T20:23:10.2277035+08:00;True|2023-01-02T20:15:03.4479581+08:00;True|2023-01-02T20:14:30.6578219+08:00;True|2023-01-02T20:13:35.9810001+08:00;True|2023-01-02T20:12:49.5190240+08:00;True|2023-01-02T18:41:02.6217807+08:00;True|2023-01-02T18:32:44.8093539+08:00;True|2023-01-02T18:11:08.6941887+08:00;True|2023-01-02T16:33:42.1850536+08:00;True|2023-01-02T16:32:26.4861060+08:00;True|2023-01-02T14:29:37.5313187+08:00;True|2023-01-02T14:29:05.0817055+08:00;True|2023-01-01T11:56:47.6765386+08:00;True|2022-12-31T20:47:29.3442163+08:00;True|2022-12-31T19:53:31.4897608+08:00;True|2022-12-31T19:09:27.5849193+08:00;True|2022-12-31T18:34:51.6835134+08:00;True|2022-12-31T18:33:07.6356083+08:00;True|2022-12-31T18:31:44.3594272+08:00;True|2022-12-31T16:30:33.5907953+08:00;False|2022-12-31T16:29:57.1706009+08:00;True|2022-12-30T22:23:38.8870651+08:00;True|2022-12-30T20:13:20.9119842+08:00;True|2022-12-30T20:08:30.3908541+08:00;True|2022-12-30T16:07:17.2492927+08:00;True|2022-12-30T11:02:36.4689010+08:00;True|2022-12-30T10:05:08.1779874+08:00;True|2022-12-30T10:03:14.7093706+08:00;True|2022-12-29T23:30:06.6475000+08:00;True|2022-12-29T23:26:41.2422368+08:00;True|2022-12-29T23:26:02.2951705+08:00;True|2022-12-29T22:36:46.4297516+08:00;True|2022-12-29T17:08:57.3370321+08:00;True|2022-12-29T17:00:01.7295036+08:00;True|2022-12-28T22:08:08.7670608+08:00;True|2022-12-28T17:27:29.8124543+08:00;True|2022-12-28T16:24:51.9861635+08:00;True|2022-12-28T16:23:44.1004953+08:00;True|2022-12-28T16:21:08.2353403+08:00;True|2022-12-28T16:20:56.6429409+08:00;True|2022-12-23T19:22:48.4392831+08:00;True|2022-12-21T22:23:35.9045939+08:00;True|2022-12-21T22:18:16.0962966+08:00;True|2022-12-21T20:54:02.6827032+08:00;True|2022-12-21T20:52:52.1827338+08:00;True|2022-12-21T20:52:06.8220342+08:00;True|2022-12-21T20:51:17.2555457+08:00;True|2022-12-18T21:48:23.3182037+08:00;False|2022-12-18T21:46:45.4066089+08:00;False|2022-12-18T21:46:32.1453050+08:00;True|2022-12-18T21:45:56.8222951+08:00;True|2022-12-18T19:21:18.8207927+08:00;True|2022-12-17T22:46:09.3784351+08:00;True|2022-12-17T21:59:08.8587742+08:00;True|2022-12-17T20:48:28.8245676+08:00;True|2022-12-17T20:34:01.3071499+08:00;True|2022-12-17T20:32:23.5134227+08:00;True|2022-12-17T18:31:00.3332961+08:00;True|2022-12-17T12:29:27.2257800+08:00;True|2022-12-17T11:06:37.8176899+08:00;True|2022-12-15T08:00:13.6842411+08:00;True|2022-12-13T12:10:12.2530920+08:00;True|2022-12-13T10:10:50.0512184+08:00;True|2022-12-13T10:08:30.0309694+08:00;True|2022-12-13T10:04:52.6876847+08:00;True|2022-12-12T20:14:30.0623921+08:00;True|2022-12-12T20:13:38.1238116+08:00;True|2022-12-12T20:12:07.0936964+08:00;True|2022-12-11T13:10:18.2140245+08:00;True|2022-12-11T13:08:51.2427844+08:00;True|2022-12-10T13:00:16.6605746+08:00;True|2022-12-09T23:07:24.8079525+08:00;True|2022-12-09T19:58:04.0989594+08:00;True|2022-12-09T19:56:42.7195927+08:00;True|2022-12-09T19:53:31.8804030+08:00;True|2022-12-09T19:45:39.8557456+08:00;True|2022-11-29T10:02:12.9644217+08:00;True|2022-11-28T20:07:05.4948835+08:00;True|2022-11-28T20:06:52.1724049+08:00;True|2022-11-28T12:58:30.0455236+08:00;True|2022-11-28T12:56:29.6927989+08:00;True|2022-11-28T12:55:41.4295095+08:00;True|2022-11-28T12:53:49.0933314+08:00;True|2022-11-28T12:51:28.8301278+08:00;True|2022-11-28T12:50:46.0058585+08:00;True|2022-11-28T12:50:01.8716847+08:00;True|2022-11-28T12:49:25.7150025+08:00; + True|2023-01-06T08:37:40.8079568Z;True|2023-01-06T16:31:24.6067714+08:00;True|2023-01-06T16:26:29.1946179+08:00;True|2023-01-06T14:14:35.1902279+08:00;True|2023-01-05T17:03:22.1394638+08:00;True|2023-01-05T16:55:14.3920225+08:00;True|2023-01-05T15:08:31.6491096+08:00;False|2023-01-05T15:08:19.9781972+08:00;False|2023-01-05T15:05:41.3082089+08:00;True|2023-01-05T11:17:02.6841461+08:00;True|2023-01-05T11:01:56.5159682+08:00;True|2023-01-05T11:01:16.6067244+08:00;True|2023-01-05T11:00:41.0894016+08:00;True|2023-01-04T21:55:42.5301452+08:00;True|2023-01-04T21:54:10.5989202+08:00;True|2023-01-03T21:30:00.6144582+08:00;True|2023-01-02T20:23:10.2277035+08:00;True|2023-01-02T20:15:03.4479581+08:00;True|2023-01-02T20:14:30.6578219+08:00;True|2023-01-02T20:13:35.9810001+08:00;True|2023-01-02T20:12:49.5190240+08:00;True|2023-01-02T18:41:02.6217807+08:00;True|2023-01-02T18:32:44.8093539+08:00;True|2023-01-02T18:11:08.6941887+08:00;True|2023-01-02T16:33:42.1850536+08:00;True|2023-01-02T16:32:26.4861060+08:00;True|2023-01-02T14:29:37.5313187+08:00;True|2023-01-02T14:29:05.0817055+08:00;True|2023-01-01T11:56:47.6765386+08:00;True|2022-12-31T20:47:29.3442163+08:00;True|2022-12-31T19:53:31.4897608+08:00;True|2022-12-31T19:09:27.5849193+08:00;True|2022-12-31T18:34:51.6835134+08:00;True|2022-12-31T18:33:07.6356083+08:00;True|2022-12-31T18:31:44.3594272+08:00;True|2022-12-31T16:30:33.5907953+08:00;False|2022-12-31T16:29:57.1706009+08:00;True|2022-12-30T22:23:38.8870651+08:00;True|2022-12-30T20:13:20.9119842+08:00;True|2022-12-30T20:08:30.3908541+08:00;True|2022-12-30T16:07:17.2492927+08:00;True|2022-12-30T11:02:36.4689010+08:00;True|2022-12-30T10:05:08.1779874+08:00;True|2022-12-30T10:03:14.7093706+08:00;True|2022-12-29T23:30:06.6475000+08:00;True|2022-12-29T23:26:41.2422368+08:00;True|2022-12-29T23:26:02.2951705+08:00;True|2022-12-29T22:36:46.4297516+08:00;True|2022-12-29T17:08:57.3370321+08:00;True|2022-12-29T17:00:01.7295036+08:00;True|2022-12-28T22:08:08.7670608+08:00;True|2022-12-28T17:27:29.8124543+08:00;True|2022-12-28T16:24:51.9861635+08:00;True|2022-12-28T16:23:44.1004953+08:00;True|2022-12-28T16:21:08.2353403+08:00;True|2022-12-28T16:20:56.6429409+08:00;True|2022-12-23T19:22:48.4392831+08:00;True|2022-12-21T22:23:35.9045939+08:00;True|2022-12-21T22:18:16.0962966+08:00;True|2022-12-21T20:54:02.6827032+08:00;True|2022-12-21T20:52:52.1827338+08:00;True|2022-12-21T20:52:06.8220342+08:00;True|2022-12-21T20:51:17.2555457+08:00;True|2022-12-18T21:48:23.3182037+08:00;False|2022-12-18T21:46:45.4066089+08:00;False|2022-12-18T21:46:32.1453050+08:00;True|2022-12-18T21:45:56.8222951+08:00;True|2022-12-18T19:21:18.8207927+08:00;True|2022-12-17T22:46:09.3784351+08:00;True|2022-12-17T21:59:08.8587742+08:00;True|2022-12-17T20:48:28.8245676+08:00;True|2022-12-17T20:34:01.3071499+08:00;True|2022-12-17T20:32:23.5134227+08:00;True|2022-12-17T18:31:00.3332961+08:00;True|2022-12-17T12:29:27.2257800+08:00;True|2022-12-17T11:06:37.8176899+08:00;True|2022-12-15T08:00:13.6842411+08:00;True|2022-12-13T12:10:12.2530920+08:00;True|2022-12-13T10:10:50.0512184+08:00;True|2022-12-13T10:08:30.0309694+08:00;True|2022-12-13T10:04:52.6876847+08:00;True|2022-12-12T20:14:30.0623921+08:00;True|2022-12-12T20:13:38.1238116+08:00;True|2022-12-12T20:12:07.0936964+08:00;True|2022-12-11T13:10:18.2140245+08:00;True|2022-12-11T13:08:51.2427844+08:00;True|2022-12-10T13:00:16.6605746+08:00;True|2022-12-09T23:07:24.8079525+08:00;True|2022-12-09T19:58:04.0989594+08:00;True|2022-12-09T19:56:42.7195927+08:00;True|2022-12-09T19:53:31.8804030+08:00;True|2022-12-09T19:45:39.8557456+08:00;True|2022-11-29T10:02:12.9644217+08:00;True|2022-11-28T20:07:05.4948835+08:00;True|2022-11-28T20:06:52.1724049+08:00;True|2022-11-28T12:58:30.0455236+08:00;True|2022-11-28T12:56:29.6927989+08:00;True|2022-11-28T12:55:41.4295095+08:00;True|2022-11-28T12:53:49.0933314+08:00;True|2022-11-28T12:51:28.8301278+08:00; \ No newline at end of file diff --git a/SaladimWpf/Services/ChessBoard.cs b/SaladimWpf/Services/ChessBoard.cs new file mode 100644 index 0000000..0c6902c --- /dev/null +++ b/SaladimWpf/Services/ChessBoard.cs @@ -0,0 +1,145 @@ +using SixLabors.Fonts; +using SixLabors.ImageSharp.Drawing.Processing; +using SixLabors.ImageSharp.Drawing; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp; +using System.Diagnostics.Metrics; + +namespace SaladimWpf.Services; + +public class ChessBoard +{ + /// + /// 棋盘情况, 0表示空, 1到其余表示对应棋子 + /// + private readonly int[,] board; + + public Func ChessPieceColorProvider { get; set; } + + public Color BgColor { get; set; } = new Color(new Rgba32(0xF1, 0xD6, 0xAB)); + + public Color LineColor { get; set; } = new Color(new Rgba32(0x9D, 0x7E, 0x4F)); + + public Color TextColor { get; set; } = Color.Gray; + + public int Rows { get; protected set; } + + public int Columns { get; protected set; } + + public int Width { get => Columns * GridSize; } + + public int Height { get => Rows * GridSize; } + + public int Count { get; protected set; } + + public int PlaceSucceedTimes { get; protected set; } + + public int GridSize { get; set; } = 64; + + public float ChessPieceSize { get; set; } = 0.8f * 64; + + public int HalfGridSize { get => GridSize / 2; } + + public ChessBoard(int rows, int columns, int count, Func chessPieceColorProvider) + { + Rows = rows; + Columns = columns; + ChessPieceColorProvider = chessPieceColorProvider; + Count = count; + board = new int[rows, columns]; + } + + public int GetAt(int row, int column) + => board[row, column]; + + public PointF GetPos(int row, int column) + => new(HalfGridSize + column * GridSize, HalfGridSize + row * GridSize); + + public int PlaceAt(int row, int column, int what) + { + int before = GetAt(row, column); + if (before == 0) + { + board[row, column] = what; + PlaceSucceedTimes += 1; + return 0; + } + else + { + return before; + } + } + + public Action GetImageGenerateAction(Font textFont) => op => + { + IPen linePen = new Pen(LineColor, 2); + for (int row = 0; row < Rows; row++) + { + op.DrawLines(linePen, GetPos(row, 0), GetPos(row, Columns - 1)); + } + for (int column = 0; column < Columns; column++) + { + op.DrawLines(linePen, GetPos(0, column), GetPos(Rows - 1, column)); + } + for (int row = 0; row < Rows; row++) + { + for (int column = 0; column < Columns; column++) + { + var curChess = GetAt(row, column); + if (curChess != 0) + { + IBrush brush = new SolidBrush(ChessPieceColorProvider(curChess)); + PathBuilder b = new(); + b.AddArc(GetPos(row, column), ChessPieceSize / 2, ChessPieceSize / 2, 0, 0, 360); + op.Fill(brush, b.Build()); + } + else + { + TextOptions o = new(textFont) + { + TextJustification = TextJustification.InterCharacter, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + TextAlignment = TextAlignment.Center, + Origin = GetPos(row, column), + Dpi = 14 + }; + op.DrawText(o, $"({row},{column})", TextColor); + } + } + } + }; + + public int CheckWinner() + { + int aim; + var addCount = Count - 1; + for (int r = 0; r < Rows; r++) + { + for (int c = 0; c < Columns; c++) + { + aim = GetAt(c, r); + if (aim == 0) continue; + var win1 = true; + var win2 = true; + var win3 = true; + var win4 = true; + for (int i = 0; i < Count; i++) + { + if (r + addCount >= Rows || GetAt(r + i, c) != aim) + win1 = false; + if (c + addCount >= Columns || GetAt(r, c + i) != aim) + win2 = false; + if (r + addCount >= Rows || c + addCount >= Columns || GetAt(r + i, c + i) != aim) + win3 = false; + if (r + addCount >= Rows || c - addCount < 0 || GetAt(r + i, c - i) != aim) + win4 = false; + } + if (win1 || win2 || win3 || win4) + return aim; + } + } + return 0; + } +} \ No newline at end of file diff --git a/SaladimWpf/Services/FiveInARowService.cs b/SaladimWpf/Services/FiveInARowService.cs new file mode 100644 index 0000000..14782f7 --- /dev/null +++ b/SaladimWpf/Services/FiveInARowService.cs @@ -0,0 +1,53 @@ +using SaladimQBot.Core; +using SaladimQBot.Extensions; +using SixLabors.Fonts; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SaladimWpf.Services; + +public class FiveInARowService +{ + protected List playingGames = new(); + protected readonly CoroutineService coroutineService; + + public IReadOnlyList PlayingGames { get => playingGames; } + + public Font ChessPieceTextFont { get; protected set; } + + public FiveInARowService(CoroutineService coroutineService) + { + FontCollection collection = new(); + collection.AddSystemFonts(); + FontFamily family = collection.Get("SimSun"); + ChessPieceTextFont = new(family, 96, FontStyle.Italic); + this.coroutineService = coroutineService; + } + + public bool IsPlayerPlaying(IGroupUser user) + => PlayingGames.FirstOrDefault(g => g.Users.Any(p => p.IsSameUser(user))) is not null; + + public FiveInARowRecord GetPlayerPlaying(IGroupUser user) + => PlayingGames.First(r => r.Users.Any(u => u.IsSameUser(user))); + + public FiveInARowRecord CreateAndAddNewGame(IGroupUser user1, IGroupUser user2, int rows, int columns, int count) + { + FiveInARowRecord r = new(new() { user1, user2 }, new(rows, columns, count, GetChessPieceColor)); + playingGames.Add(r); + return r; + } + + public void EndGame(FiveInARowRecord record) + { + playingGames.Remove(record); + } + + public static Color GetChessPieceColor(int i) => i switch + { + 1 => new Color(new Rgba32(0x2A, 0x30, 0x30)), + 2 => new Color(new Rgba32(0xEF, 0xEF, 0xEF)), + _ => Color.Black + }; +} + +public record FiveInARowRecord(List Users, ChessBoard ChessBoard); \ No newline at end of file diff --git a/SaladimWpf/Services/SaladimWpfService.cs b/SaladimWpf/Services/SaladimWpfService.cs index 93a2436..d78152d 100644 --- a/SaladimWpf/Services/SaladimWpfService.cs +++ b/SaladimWpf/Services/SaladimWpfService.cs @@ -58,7 +58,7 @@ private void ConfigurePipeline(Pipeline eventPipeline) { do { - if (e is ClientMessageReceivedEvent mre) + if (e is IClientMessageReceivedEvent mre) { #if DEBUG //DEBUG时只接受测试群消息 diff --git a/SaladimWpf/SimCmdModules/FiveInARowModule.cs b/SaladimWpf/SimCmdModules/FiveInARowModule.cs new file mode 100644 index 0000000..584d994 --- /dev/null +++ b/SaladimWpf/SimCmdModules/FiveInARowModule.cs @@ -0,0 +1,266 @@ +using System.Diagnostics.CodeAnalysis; +using System.Drawing; +using System.IO; +using System.Numerics; +using System.Text; +using SaladimQBot.Core; +using SaladimQBot.Extensions; +using SaladimQBot.Shared; +using SaladimWpf.Services; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SQLite; +using Point = System.Drawing.Point; + +namespace SaladimWpf.SimCmdModules; + +public class FiveInARowModule : CommandModule +{ + public const string TipMsgGroupOnly = "五子棋游戏只在群聊中进行哦~"; + public const string TipMsgAlreadyGaming = "你已经在进行游戏啦~"; + public const string TipMsgWaitingNextStart = "正在等待下一个开始五子棋的玩家... 等待中或游戏中可使用 /退出五子棋 来强制结束游戏."; + public const string TipMsgGameStarted = "五子棋已经开始啦, 输入 /下 1,5 这种格式的指令下第一子吧!"; + public const string TipMsgWaitingNextStartTwice = "正在等待下一个开始五子棋的玩家...请稍安勿躁qwq"; + public const string TipMsgPieceBlocked = "那里有人下过了哦, 再想想哦."; + public const string TipMsgNotPlaying = "您还未进入正在进行的游戏中哦~ 输入 /开始五子棋 等待加入吧!"; + public const string TipMsgOutOfRange = "位置越界了哦, 请输入一个正确的坐标."; + protected readonly FiveInARowService fiveInARowService; + protected readonly MemorySessionService memorySessionService; + protected readonly CoroutineService coroutineService; + protected readonly SessionSqliteService sessionSqliteService; + + public FiveInARowModule( + FiveInARowService fiveInARowService, + MemorySessionService memorySessionService, + CoroutineService coroutineService, + SessionSqliteService sessionSqliteService + ) + { + this.fiveInARowService = fiveInARowService; + this.memorySessionService = memorySessionService; + this.coroutineService = coroutineService; + this.sessionSqliteService = sessionSqliteService; + } + + [Command("开始五子棋")] + public void StartNew() + { + if (Content.Message is IGroupMessage groupMessage) + { + if (!fiveInARowService.IsPlayerPlaying(groupMessage.Sender)) + { + var s = memorySessionService.GetGroupSession(groupMessage.Group.GroupId); + lock (s) + { + if (s.CurrentWaitings.Count >= 1) + { + if (!s.CurrentWaitings[0].IsSameGroupUser(groupMessage.Sender)) + { + var gameRecord = fiveInARowService.CreateAndAddNewGame(s.CurrentWaitings[0], groupMessage.Sender, 17, 17, 5); + var co = GetGameCoroutine(gameRecord); + coroutineService.AddNewCoroutine(co); + foreach (var user in gameRecord.Users) + { + var gamingSession = memorySessionService.GetUserSession(user.UserId); + gamingSession.FiveInARowCorotine = co; + } + memorySessionService.TryRemoveGroupSession(groupMessage.Group.GroupId); + Content.MessageWindow.SendMessageAsync(TipMsgGameStarted); + } + else + { + Content.MessageWindow.SendMessageAsync(TipMsgWaitingNextStartTwice); + } + } + else + { + s.CurrentWaitings.Add(groupMessage.Sender); + Content.MessageWindow.SendMessageAsync(TipMsgWaitingNextStart); + } + } + } + else + { + FiveInARowRecord record = fiveInARowService.GetPlayerPlaying(groupMessage.Sender); + StringBuilder sb = new(); + sb.AppendLine(TipMsgAlreadyGaming); + sb.Append($"你是在这个群开始游戏的: {record.Users[0].Group.Name}"); + sb.Append($"({record.Users[0].Group.GroupId})"); + Content.MessageWindow.SendMessageAsync(Content.Client.CreateTextOnlyEntity(sb.ToString())); + } + } + else + { + Content.MessageWindow.SendMessageAsync(TipMsgGroupOnly); + } + } + + [Command("退出五子棋")] + public void EndNow() + { + if (Content.Message is IGroupMessage groupMessage) + { + if (!fiveInARowService.IsPlayerPlaying(groupMessage.Sender)) + { + var s = memorySessionService.GetGroupSession(groupMessage.Group.GroupId); + if (s.CurrentWaitings.Any(u => u.IsSameGroupUser((IGroupUser)Content.Executor))) + { + Content.MessageWindow.SendMessageAsync("已退出五子棋等待."); + memorySessionService.TryRemoveGroupSession(groupMessage.Group.GroupId); + } + else + { + Content.MessageWindow.SendMessageAsync("你既没有等待中也没有游玩中哦."); + } + } + else + { + var playingOne = fiveInARowService.GetPlayerPlaying(groupMessage.Sender); + if (playingOne.ChessBoard.PlaceSucceedTimes >= 5) + { + Content.MessageWindow.SendMessageAsync("游戏已结束, 但是已经下了5个子以上了, 认为你认输了哦qwq"); + var s = sessionSqliteService.GetUserSession(groupMessage.Sender.UserId); + s.LoseTimes += 1; + sessionSqliteService.SaveSession(s); + } + else + { + Content.MessageWindow.SendMessageAsync("游戏已结束, 还未下够5个子, 没有成绩记录了哦."); + } + fiveInARowService.EndGame(playingOne); + var session = memorySessionService.GetUserSession(Content.Executor.UserId); + var co = session.FiveInARowCorotine!; + coroutineService.RemoveCoroutine(co); + } + } + else + { + Content.MessageWindow.SendMessageAsync(TipMsgGroupOnly); + } + } + + public IEnumerator GetGameCoroutine(FiveInARowRecord record) + { + IMessageEntity startMsgEntity = Content.Client.CreateMessageBuilder() + .WithImage($"file:///{GenerateChessImage(record.ChessBoard)}") + .WithText("\n") + .WithAt(record.Users[0]) + .WithText("从你开始咯!") + .Build(); + Content.MessageWindow.SendMessageAsync(startMsgEntity); + int curPlayer = 0; + while (true) + { + object[] args = null!; + yield return new CommandWaiter(Content.SimCommandExecuter, record.Users[curPlayer], "下", report => args = report); + Vector2 pos = args[0].Cast(); + Point point = new((int)pos.X, (int)pos.Y); + if (point.X >= 0 && point.X < record.ChessBoard.Columns && point.Y >= 0 && point.Y < record.ChessBoard.Rows) + { + int at = record.ChessBoard.PlaceAt(point.X, point.Y, curPlayer + 1); + if (at != 0) + { + Content.MessageWindow.SendMessageAsync(TipMsgPieceBlocked); + } + else + { + int winner = record.ChessBoard.CheckWinner(); + if (winner == 0) + { + curPlayer += 1; + if (record.Users.Count == curPlayer) + curPlayer = 0; + IMessageEntity entity = Content.Client.CreateMessageBuilder() + .WithImage($"file:///{GenerateChessImage(record.ChessBoard)}") + .WithText("\n") + .WithAt(record.Users[curPlayer]) + .WithText("到你了!") + .Build(); + Content.MessageWindow.SendMessageAsync(entity); + } + else + { + //有人赢了 + IGroupUser winnerUser = record.Users[winner - 1]; + IMessageEntity entity = Content.Client.CreateMessageBuilder() + .WithImage($"file:///{GenerateChessImage(record.ChessBoard)}") + .WithText("\n恭喜") + .WithAt(winnerUser) + .WithText("赢得了本次游戏的胜利!") + .Build(); + Content.MessageWindow.SendMessageAsync(entity); + fiveInARowService.EndGame(record); + foreach (var u in record.Users) + { + var s = sessionSqliteService.GetUserSession(u.UserId); + if (u.IsSameUser(winnerUser)) + s.WinTimes += 1; + else + s.LoseTimes += 1; + sessionSqliteService.SaveSession(s); + } + yield break; + } + } + } + else + { + Content.MessageWindow.SendMessageAsync(TipMsgOutOfRange); + } + } + } + + protected string GenerateChessImage(ChessBoard chessBoard) + { + Image image = new(chessBoard.Width, chessBoard.Height, chessBoard.BgColor); + image.Mutate(chessBoard.GetImageGenerateAction(fiveInARowService.ChessPieceTextFont)); + string fileName = $@"tempImages\{DateTime.Now.Ticks}.png"; + if (!Directory.Exists("tempImages")) + Directory.CreateDirectory("tempImages"); + image.SaveAsPng(fileName); + return Path.GetFullPath(fileName); + } + + [Command("下")] + public void Drop(Vector2 pos) => GamePlayCheck(); + + public void GamePlayCheck() + { + if (Content.Executor is IGroupUser groupUser) + { + if (!fiveInARowService.IsPlayerPlaying(groupUser)) + { + Content.MessageWindow.SendMessageAsync(TipMsgNotPlaying); + } + } + else + { + Content.MessageWindow.SendMessageAsync(TipMsgGroupOnly); + } + } + + public class GamingSession : ISession + { + public SessionId SessionId { get; set; } + + public IEnumerator? FiveInARowCorotine { get; set; } + } + + public class LineUpSession : ISession + { + public SessionId SessionId { get; set; } + + public List CurrentWaitings { get; set; } = new(); + } + + [Table("fiveInARowWinner")] + public class FiveInARowWinnerSession : SqliteStoreSession + { + [Column("winTimes")] + public int WinTimes { get; set; } + + [Column("loseTimes")] + public int LoseTimes { get; set; } + } +} diff --git a/SaladimWpf/SimCmdModules/TextMisc.cs b/SaladimWpf/SimCmdModules/TextMiscModule.cs similarity index 97% rename from SaladimWpf/SimCmdModules/TextMisc.cs rename to SaladimWpf/SimCmdModules/TextMiscModule.cs index d83e621..35cf0e1 100644 --- a/SaladimWpf/SimCmdModules/TextMisc.cs +++ b/SaladimWpf/SimCmdModules/TextMiscModule.cs @@ -20,13 +20,13 @@ namespace SaladimWpf.SimCmdModules; -public partial class TextMisc : CommandModule +public partial class TextMiscModule : CommandModule { private readonly IServiceProvider serviceProvider; private readonly SalLoggerService salLoggerService; - private readonly ISessionService sessionService; + private readonly SessionSqliteService sessionService; - public TextMisc(IServiceProvider serviceProvider, SalLoggerService salLoggerService, ISessionService sessionService) + public TextMiscModule(IServiceProvider serviceProvider, SalLoggerService salLoggerService, SessionSqliteService sessionService) { this.serviceProvider = serviceProvider; this.salLoggerService = salLoggerService; @@ -34,7 +34,7 @@ public TextMisc(IServiceProvider serviceProvider, SalLoggerService salLoggerServ } [Table("test_session")] - public class TestSession : SqliteSession + public class TestSession : SqliteStoreSession { [Column("test_num")] public int Num { get; set; } @@ -275,8 +275,9 @@ public void Echo(params string[] s) } [Command("不定积分")] - public async void Integral(string s) + public async void Integral(params string[] strs) { + string s = string.Join(' ', strs); var service = serviceProvider.GetRequiredService(); var result = await service.IntegralOf(s).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(result))