Browse Source

feat: 🎨 add PlaybackServer and fix HttpSender

tags/0.1.0
gsy1519 2 years ago
parent
commit
0751d2534c
9 changed files with 354 additions and 41 deletions
  1. +1
    -0
      logic/GameClass/GameObj/Map/Chest.cs
  2. +2
    -2
      logic/Preparation/Utility/XY.cs
  3. +2
    -2
      logic/Server/CopyInfo.cs
  4. +1
    -1
      logic/Server/GameServer.cs
  5. +28
    -16
      logic/Server/HttpSender.cs
  6. +269
    -0
      logic/Server/PlaybackServer.cs
  7. +44
    -19
      logic/Server/Program.cs
  8. +1
    -1
      logic/Server/Properties/launchSettings.json
  9. +6
    -0
      logic/cmd/PlaybackServer.cmd

+ 1
- 0
logic/GameClass/GameObj/Map/Chest.cs View File

@@ -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)
{ {


+ 2
- 2
logic/Preparation/Utility/XY.cs View File

@@ -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();
} }
} }
} }

+ 2
- 2
logic/Server/CopyInfo.cs View File

@@ -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;
} }


+ 1
- 1
logic/Server/GameServer.cs View File

@@ -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);
} }
} }
} }


+ 28
- 16
logic/Server/HttpSender.cs View File

@@ -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;
}
} }

+ 269
- 0
logic/Server/PlaybackServer.cs View File

@@ -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];
}
}
}
}

+ 44
- 19
logic/Server/Program.cs View File

@@ -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;
} }


+ 1
- 1
logic/Server/Properties/launchSettings.json View File

@@ -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"
} }
} }
} }

+ 6
- 0
logic/cmd/PlaybackServer.cmd View File

@@ -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

Loading…
Cancel
Save