| @@ -23,6 +23,7 @@ namespace GameClass.GameObj | |||||
| private int openStartTime = 0; | private int openStartTime = 0; | ||||
| public int OpenStartTime => openStartTime; | public int OpenStartTime => openStartTime; | ||||
| private Character? whoOpen = null; | private Character? whoOpen = null; | ||||
| public Character? WhoOpen => whoOpen; | public Character? WhoOpen => whoOpen; | ||||
| public void Open(int startTime, Character character) | public void Open(int startTime, Character character) | ||||
| { | { | ||||
| @@ -75,11 +75,11 @@ namespace Preparation.Utility | |||||
| return Math.Atan2(y, x); | 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() | public override int GetHashCode() | ||||
| { | { | ||||
| throw new NotImplementedException(); | |||||
| return this.x.GetHashCode() ^ this.y.GetHashCode(); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -153,7 +153,7 @@ namespace Server | |||||
| Y = bullet.Position.y, | Y = bullet.Position.y, | ||||
| FacingDirection = bullet.FacingDirection.Angle(), | FacingDirection = bullet.FacingDirection.Angle(), | ||||
| Guid = bullet.ID, | 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), | Place = Transformation.ToPlaceType((Preparation.Utility.PlaceType)bullet.Place), | ||||
| BombRange = bullet.BulletBombRange, | BombRange = bullet.BulletBombRange, | ||||
| Speed = bullet.Speed | Speed = bullet.Speed | ||||
| @@ -274,7 +274,7 @@ namespace Server | |||||
| Y = chest.Position.y | 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; | msg.ChestMessage.Progress = (progress > GameData.degreeOfOpenedChest) ? GameData.degreeOfOpenedChest : progress; | ||||
| return msg; | return msg; | ||||
| } | } | ||||
| @@ -338,7 +338,7 @@ namespace Server | |||||
| if (options.Url != DefaultArgumentOptions.Url && options.Token != DefaultArgumentOptions.Token) | 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 Newtonsoft.Json.Linq; | ||||
| using System; | using System; | ||||
| using System.Net; | using System.Net; | ||||
| using System.Net.Http.Json; | |||||
| using System.Text; | using System.Text; | ||||
| namespace Server | namespace Server | ||||
| @@ -9,29 +10,34 @@ namespace Server | |||||
| { | { | ||||
| private string url; | private string url; | ||||
| private string token; | private string token; | ||||
| private string method; | |||||
| public HttpSender(string url, string token, string method) | |||||
| public HttpSender(string url, string token) | |||||
| { | { | ||||
| this.url = url; | this.url = url; | ||||
| this.token = token; | this.token = token; | ||||
| this.method = method; | |||||
| } | } | ||||
| public void SendHttpRequest(JObject body) | |||||
| // void Test() | |||||
| // { | |||||
| // this.SendHttpRequest(new()).Wait(); | |||||
| // } | |||||
| public async Task SendHttpRequest(JObject body) | |||||
| { | { | ||||
| try | 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) | 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()); | 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; | return 0; | ||||
| } | } | ||||
| @@ -2,7 +2,7 @@ | |||||
| "profiles": { | "profiles": { | ||||
| "Server": { | "Server": { | ||||
| "commandName": "Project", | "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 | |||||