| @@ -23,6 +23,7 @@ namespace GameClass.GameObj | |||
| private int openStartTime = 0; | |||
| public int OpenStartTime => openStartTime; | |||
| private Character? whoOpen = null; | |||
| public Character? WhoOpen => whoOpen; | |||
| public void Open(int startTime, Character character) | |||
| { | |||
| @@ -75,11 +75,11 @@ namespace Preparation.Utility | |||
| return Math.Atan2(y, x); | |||
| } | |||
| public override bool Equals(object obj) => throw new NotImplementedException(); | |||
| public override bool Equals(object? obj) => obj is null || obj is XY ? false : this == (XY)obj; | |||
| public override int GetHashCode() | |||
| { | |||
| throw new NotImplementedException(); | |||
| return this.x.GetHashCode() ^ this.y.GetHashCode(); | |||
| } | |||
| } | |||
| } | |||
| @@ -153,7 +153,7 @@ namespace Server | |||
| Y = bullet.Position.y, | |||
| FacingDirection = bullet.FacingDirection.Angle(), | |||
| Guid = bullet.ID, | |||
| Team = (bullet.Parent.IsGhost()) ? PlayerType.TrickerPlayer : PlayerType.StudentPlayer, | |||
| Team = (bullet.Parent!.IsGhost()) ? PlayerType.TrickerPlayer : PlayerType.StudentPlayer, | |||
| Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)bullet.Place), | |||
| BombRange = bullet.BulletBombRange, | |||
| Speed = bullet.Speed | |||
| @@ -274,7 +274,7 @@ namespace Server | |||
| Y = chest.Position.y | |||
| } | |||
| }; | |||
| int progress = (chest.OpenStartTime > 0) ? ((time - chest.OpenStartTime) * chest.WhoOpen.SpeedOfOpenChest) : 0; | |||
| int progress = (chest.OpenStartTime > 0) ? ((time - chest.OpenStartTime) * chest.WhoOpen!.SpeedOfOpenChest) : 0; | |||
| msg.ChestMessage.Progress = (progress > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : progress; | |||
| return msg; | |||
| } | |||
| @@ -338,7 +338,7 @@ namespace Server | |||
| if (options.Url != DefaultArgumentOptions.Url && options.Token != DefaultArgumentOptions.Token) | |||
| { | |||
| this.httpSender = new HttpSender(options.Url, options.Token, "PUT"); | |||
| this.httpSender = new HttpSender(options.Url, options.Token); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| using Newtonsoft.Json.Linq; | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Http.Json; | |||
| using System.Text; | |||
| namespace Server | |||
| @@ -9,29 +10,34 @@ namespace Server | |||
| { | |||
| private string url; | |||
| private string token; | |||
| private string method; | |||
| public HttpSender(string url, string token, string method) | |||
| public HttpSender(string url, string token) | |||
| { | |||
| this.url = url; | |||
| this.token = token; | |||
| this.method = method; | |||
| } | |||
| public void SendHttpRequest(JObject body) | |||
| // void Test() | |||
| // { | |||
| // this.SendHttpRequest(new()).Wait(); | |||
| // } | |||
| public async Task SendHttpRequest(JObject body) | |||
| { | |||
| try | |||
| { | |||
| var request = WebRequest.CreateHttp(url); | |||
| request.Method = method; | |||
| request.Headers.Add("Authorization", $"Bearer {token}"); | |||
| request.ContentType = "application/json"; | |||
| var raw = Encoding.UTF8.GetBytes(body.ToString()); | |||
| request.GetRequestStream().Write(raw, 0, raw.Length); | |||
| Console.WriteLine("Send to web successfully!"); | |||
| var response = request.GetResponse(); | |||
| Console.WriteLine($"Web response: {response}"); | |||
| var request = new HttpClient(); | |||
| request.DefaultRequestHeaders.Authorization = new("Bearer", token); | |||
| using (var response = await request.PutAsync(url, JsonContent.Create(new | |||
| { | |||
| result = new TeamScore[] | |||
| { | |||
| new TeamScore() { team_name = "Student", score = 0, }, | |||
| new TeamScore() { team_name = "Tricker", score = 0, }, | |||
| } | |||
| }))) | |||
| { | |||
| Console.WriteLine("Send to web successfully!"); | |||
| Console.WriteLine($"Web response: {await response.Content.ReadAsStringAsync()}"); | |||
| } | |||
| } | |||
| catch (Exception e) | |||
| { | |||
| @@ -40,4 +46,10 @@ namespace Server | |||
| } | |||
| } | |||
| } | |||
| internal class TeamScore | |||
| { | |||
| public string team_name { get; set; } = ""; | |||
| public int score { get; set; } = 0; | |||
| } | |||
| } | |||
| @@ -0,0 +1,269 @@ | |||
| using Protobuf; | |||
| using Playback; | |||
| using System; | |||
| using System.Threading; | |||
| using Timothy.FrameRateTask; | |||
| using Gaming; | |||
| using Grpc.Core; | |||
| namespace Server | |||
| { | |||
| class PlaybackServer : AvailableService.AvailableServiceBase | |||
| { | |||
| protected readonly ArgumentOptions options; | |||
| private int[,] teamScore; | |||
| private Dictionary<long, (SemaphoreSlim, SemaphoreSlim)> semaDict = new(); | |||
| private object semaDictLock = new(); | |||
| private MessageToClient? currentGameInfo = new(); | |||
| private uint spectatorMinPlayerID = 2023; | |||
| private List<uint> spectatorList = new List<uint>(); | |||
| public int TeamCount => options.TeamCount; | |||
| private MessageWriter? mwr = null; | |||
| private bool IsGaming { get; set; } | |||
| public PlaybackServer(ArgumentOptions options) | |||
| { | |||
| this.options = options; | |||
| IsGaming = true; | |||
| teamScore = new int[0, 0]; | |||
| } | |||
| public override async Task AddPlayer(PlayerMsg request, IServerStreamWriter<MessageToClient> responseStream, ServerCallContext context) | |||
| { | |||
| if (request.PlayerId >= spectatorMinPlayerID) | |||
| { | |||
| // 观战模式 | |||
| uint tp = (uint)request.PlayerId; | |||
| if (!spectatorList.Contains(tp)) | |||
| { | |||
| spectatorList.Add(tp); | |||
| Console.WriteLine("A new spectator comes to watch this game."); | |||
| var temp = (new SemaphoreSlim(0, 1), new SemaphoreSlim(0, 1)); | |||
| lock (semaDictLock) | |||
| { | |||
| semaDict.Add(request.PlayerId, temp); | |||
| } | |||
| } | |||
| do | |||
| { | |||
| semaDict[request.PlayerId].Item1.Wait(); | |||
| try | |||
| { | |||
| if (currentGameInfo != null) | |||
| { | |||
| await responseStream.WriteAsync(currentGameInfo); | |||
| //Console.WriteLine("Send!"); | |||
| } | |||
| } | |||
| catch (Exception) | |||
| { | |||
| //Console.WriteLine(ex); | |||
| } | |||
| finally | |||
| { | |||
| semaDict[request.PlayerId].Item2.Release(); | |||
| } | |||
| } while (IsGaming == true); | |||
| return; | |||
| } | |||
| } | |||
| public void ReportGame(MessageToClient? msg) | |||
| { | |||
| currentGameInfo = msg; | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item1.Release(); | |||
| } | |||
| foreach (var kvp in semaDict) | |||
| { | |||
| kvp.Value.Item2.Wait(); | |||
| } | |||
| } | |||
| public void WaitForGame() | |||
| { | |||
| try | |||
| { | |||
| if (options.ResultOnly) | |||
| { | |||
| using (MessageReader mr = new MessageReader(options.FileName)) | |||
| { | |||
| Console.WriteLine("Parsing playback file..."); | |||
| teamScore = new int[mr.teamCount, mr.playerCount]; | |||
| int infoNo = 0; | |||
| object cursorLock = new object(); | |||
| var initialTop = Console.CursorTop; | |||
| var initialLeft = Console.CursorLeft; | |||
| while (true) | |||
| { | |||
| MessageToClient? msg = null; | |||
| for (int i = 0; i < mr.teamCount; ++i) | |||
| { | |||
| for (int j = 0; j < mr.playerCount; ++j) | |||
| { | |||
| msg = mr.ReadOne(); | |||
| if (msg == null) | |||
| { | |||
| Console.WriteLine("The game doesn't come to an end because of timing up!"); | |||
| IsGaming = false; | |||
| goto endParse; | |||
| } | |||
| lock (cursorLock) | |||
| { | |||
| var curTop = Console.CursorTop; | |||
| var curLeft = Console.CursorLeft; | |||
| Console.SetCursorPosition(initialLeft, initialTop); | |||
| Console.WriteLine($"Parsing messages... Current message number: {infoNo}"); | |||
| Console.SetCursorPosition(curLeft, curTop); | |||
| } | |||
| if (msg != null) | |||
| { | |||
| //teamScore[i] = msg.TeamScore; | |||
| } | |||
| } | |||
| } | |||
| ++infoNo; | |||
| if (msg == null) | |||
| { | |||
| Console.WriteLine("No game information in this file!"); | |||
| goto endParse; | |||
| } | |||
| if (msg.GameState == GameState.GameEnd) | |||
| { | |||
| Console.WriteLine("Game over normally!"); | |||
| goto endParse; | |||
| } | |||
| } | |||
| endParse: | |||
| Console.WriteLine($"Successfully parsed {infoNo} informations!"); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| long timeInterval = GameServer.SendMessageToClientIntervalInMilliseconds; | |||
| if (options.PlaybackSpeed != 1.0) | |||
| { | |||
| options.PlaybackSpeed = Math.Max(0.25, Math.Min(4.0, options.PlaybackSpeed)); | |||
| timeInterval = (int)Math.Round(timeInterval / options.PlaybackSpeed); | |||
| } | |||
| using (MessageReader mr = new MessageReader(options.FileName)) | |||
| { | |||
| teamScore = new int[mr.teamCount, mr.playerCount]; | |||
| int infoNo = 0; | |||
| object cursorLock = new object(); | |||
| var msgCurTop = Console.CursorTop; | |||
| var msgCurLeft = Console.CursorLeft; | |||
| var frt = new FrameRateTaskExecutor<int> | |||
| ( | |||
| loopCondition: () => true, | |||
| loopToDo: () => | |||
| { | |||
| MessageToClient? msg = null; | |||
| msg = mr.ReadOne(); | |||
| if (msg == null) | |||
| { | |||
| Console.WriteLine("The game doesn't come to an end because of timing up!"); | |||
| IsGaming = false; | |||
| ReportGame(msg); | |||
| return false; | |||
| } | |||
| ReportGame(msg); | |||
| lock (cursorLock) | |||
| { | |||
| var curTop = Console.CursorTop; | |||
| var curLeft = Console.CursorLeft; | |||
| Console.SetCursorPosition(msgCurLeft, msgCurTop); | |||
| Console.WriteLine($"Sending messages... Current message number: {infoNo}."); | |||
| Console.SetCursorPosition(curLeft, curTop); | |||
| } | |||
| if (msg != null) | |||
| { | |||
| foreach (var item in msg.ObjMessage) | |||
| { | |||
| if (item.StudentMessage != null) | |||
| teamScore[0, item.StudentMessage.PlayerId] = item.StudentMessage.Score; | |||
| if (item.TrickerMessage != null) | |||
| teamScore[1, item.TrickerMessage.PlayerId - mr.playerCount] = item.TrickerMessage.Score; | |||
| } | |||
| } | |||
| ++infoNo; | |||
| if (msg == null) | |||
| { | |||
| Console.WriteLine("No game information in this file!"); | |||
| IsGaming = false; | |||
| ReportGame(msg); | |||
| return false; | |||
| } | |||
| if (msg.GameState == GameState.GameEnd) | |||
| { | |||
| Console.WriteLine("Game over normally!"); | |||
| IsGaming = false; | |||
| ReportGame(msg); | |||
| return false; | |||
| } | |||
| return true; | |||
| }, | |||
| timeInterval: timeInterval, | |||
| finallyReturn: () => 0 | |||
| ) | |||
| { AllowTimeExceed = true, MaxTolerantTimeExceedCount = 5 }; | |||
| Console.WriteLine("The server is well prepared! Please MAKE SURE that you have opened all the clients to watch the game!"); | |||
| Console.WriteLine("If ALL clients have opened, press any key to start."); | |||
| Console.ReadKey(); | |||
| new Thread | |||
| ( | |||
| () => | |||
| { | |||
| var rateCurTop = Console.CursorTop; | |||
| var rateCurLeft = Console.CursorLeft; | |||
| lock (cursorLock) | |||
| { | |||
| rateCurTop = Console.CursorTop; | |||
| rateCurLeft = Console.CursorLeft; | |||
| Console.WriteLine($"Send message to clients frame rate: {frt.FrameRate}"); | |||
| } | |||
| while (!frt.Finished) | |||
| { | |||
| lock (cursorLock) | |||
| { | |||
| var curTop = Console.CursorTop; | |||
| var curLeft = Console.CursorLeft; | |||
| Console.SetCursorPosition(rateCurLeft, rateCurTop); | |||
| Console.WriteLine($"Send message to clients frame rate: {frt.FrameRate}"); | |||
| Console.SetCursorPosition(curLeft, curTop); | |||
| } | |||
| Thread.Sleep(1000); | |||
| } | |||
| } | |||
| ) | |||
| { IsBackground = true }.Start(); | |||
| lock (cursorLock) | |||
| { | |||
| msgCurLeft = Console.CursorLeft; | |||
| msgCurTop = Console.CursorTop; | |||
| Console.WriteLine("Sending messages..."); | |||
| } | |||
| frt.Start(); | |||
| } | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| teamScore ??= new int[0, 0]; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -28,30 +28,55 @@ namespace Server | |||
| Console.WriteLine("Server begins to run: " + options.ServerPort.ToString()); | |||
| try | |||
| if (options.Playback) | |||
| { | |||
| GameServer? gameServer = new(options); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| try | |||
| { | |||
| Services = { AvailableService.BindService(gameServer) }, | |||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||
| }; | |||
| server.Start(); | |||
| PlaybackServer? playbackServer = new(options); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| Services = { AvailableService.BindService(playbackServer) }, | |||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||
| }; | |||
| server.Start(); | |||
| Console.WriteLine("Server begins to listen!"); | |||
| gameServer.WaitForEnd(); | |||
| Console.WriteLine("Server end!"); | |||
| server.ShutdownAsync().Wait(); | |||
| Thread.Sleep(50); | |||
| Console.WriteLine(""); | |||
| Console.WriteLine("=================== Final Score ===================="); | |||
| Console.WriteLine($"Studnet: {gameServer.GetScore()[0]}"); | |||
| Console.WriteLine($"Tricker: {gameServer.GetScore()[1]}"); | |||
| Console.WriteLine("Server begins to listen!"); | |||
| playbackServer.WaitForGame(); | |||
| Console.WriteLine("Server end!"); | |||
| server.ShutdownAsync().Wait(); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| Console.WriteLine(ex.ToString()); | |||
| } | |||
| } | |||
| catch (Exception ex) | |||
| else | |||
| { | |||
| Console.WriteLine(ex.ToString()); | |||
| try | |||
| { | |||
| GameServer? gameServer = new(options); | |||
| Grpc.Core.Server server = new Grpc.Core.Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) | |||
| { | |||
| Services = { AvailableService.BindService(gameServer) }, | |||
| Ports = { new ServerPort(options.ServerIP, options.ServerPort, ServerCredentials.Insecure) } | |||
| }; | |||
| server.Start(); | |||
| Console.WriteLine("Server begins to listen!"); | |||
| gameServer.WaitForEnd(); | |||
| Console.WriteLine("Server end!"); | |||
| server.ShutdownAsync().Wait(); | |||
| Thread.Sleep(50); | |||
| Console.WriteLine(""); | |||
| Console.WriteLine("=================== Final Score ===================="); | |||
| Console.WriteLine($"Studnet: {gameServer.GetScore()[0]}"); | |||
| Console.WriteLine($"Tricker: {gameServer.GetScore()[1]}"); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| Console.WriteLine(ex.ToString()); | |||
| } | |||
| } | |||
| return 0; | |||
| } | |||
| @@ -2,7 +2,7 @@ | |||
| "profiles": { | |||
| "Server": { | |||
| "commandName": "Project", | |||
| "commandLineArgs": "--ip 0.0.0.0 -p 8888 --characterID 2030" | |||
| "commandLineArgs": "--ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 30 --fileName E:\\GSY\\软件部\\THUAI6-dev\\THUAI6\\logic\\cmd\\test.thuaipb --playback true" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| @echo off | |||
| ::start cmd /k ..\Server\bin\Debug\net6.0\Server.exe --ip 0.0.0.0 --port 8888 --studentCount 4 --trickerCount 1 --gameTimeInSecond 600 --fileName test --playback true | |||
| ping -n 2 127.0.0.1 > NUL | |||
| start cmd /k ..\Client\bin\Debug\net6.0-windows\Client.exe --cl --port 8888 --characterID 2030 | |||