Compare commits
5 Commits
1254e32749
...
f728240db9
Author | SHA1 | Date | |
---|---|---|---|
f728240db9 | |||
16d001c084 | |||
f31d18bd0b | |||
a4e22a4c52 | |||
462e27c88a |
8
PongGame/Hubs/GameState.cs
Normal file
8
PongGame/Hubs/GameState.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace PongGame;
|
||||||
|
|
||||||
|
public enum GameStatus {
|
||||||
|
WaitingForPlayers,
|
||||||
|
InProgress,
|
||||||
|
Finished,
|
||||||
|
Paused,
|
||||||
|
}
|
7
PongGame/Hubs/IPongClient.cs
Normal file
7
PongGame/Hubs/IPongClient.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace PongGame.Hubs;
|
||||||
|
|
||||||
|
public interface IPongClient {
|
||||||
|
Task GameStateChanged(GameStatus state);
|
||||||
|
Task ReceiveGameState(PongGameState state);
|
||||||
|
Task UsernameChanged(string value);
|
||||||
|
}
|
110
PongGame/Hubs/PongGameState.cs
Normal file
110
PongGame/Hubs/PongGameState.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace PongGame.Hubs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pong game state saving the positions of the paddles and the ball.
|
||||||
|
/// The Pong board has aspect ratio 1:2 with height 500 and width 1000.
|
||||||
|
/// </summary>
|
||||||
|
public struct PongGameState {
|
||||||
|
public const float HEIGHT = 500.0f;
|
||||||
|
public const float WIDTH = 2 * HEIGHT;
|
||||||
|
public const float PADDLE1_OFFSET = WIDTH / 20;
|
||||||
|
public const float PADDLE2_OFFSET = WIDTH - PADDLE1_OFFSET;
|
||||||
|
|
||||||
|
public static readonly PongGameState Initial = new() {
|
||||||
|
BallState = PongBallState.Initial,
|
||||||
|
Paddle1 = PongPaddleState.Initial,
|
||||||
|
Paddle2 = PongPaddleState.Initial,
|
||||||
|
Status = GameStatus.WaitingForPlayers,
|
||||||
|
WinnerLeft = null
|
||||||
|
};
|
||||||
|
|
||||||
|
public PongPaddleState Paddle1;
|
||||||
|
public PongPaddleState Paddle2;
|
||||||
|
public PongBallState BallState;
|
||||||
|
public GameStatus Status;
|
||||||
|
public bool? WinnerLeft;
|
||||||
|
|
||||||
|
public static void Update(ref PongGameState state) {
|
||||||
|
if (state.Status is not GameStatus.InProgress) return;
|
||||||
|
|
||||||
|
PongPaddleState.Update(ref state.Paddle1);
|
||||||
|
PongPaddleState.Update(ref state.Paddle2);
|
||||||
|
PongBallState.Update(ref state.BallState);
|
||||||
|
var ballX = state.BallState.Pos.X;
|
||||||
|
if (ballX is < 0 or > WIDTH) {
|
||||||
|
state.WinnerLeft = ballX < 0;
|
||||||
|
state.Status = GameStatus.Finished;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collide(in state.Paddle1, ref state.BallState, true);
|
||||||
|
Collide(in state.Paddle2, ref state.BallState, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Collide(in PongPaddleState paddle, ref PongBallState ballState, bool left) {
|
||||||
|
var paddleX = left ? PADDLE1_OFFSET : PADDLE2_OFFSET;
|
||||||
|
var intersection = RectangleF.Intersect(paddle.GetCollider(paddleX), ballState.GetCollider());
|
||||||
|
if (intersection.IsEmpty) return;
|
||||||
|
|
||||||
|
// TODO: continuous collision
|
||||||
|
var ratio = (ballState.Pos.Y - paddle.Height + PongPaddleState.PADDLE_HALF_LENGTH) / PongPaddleState.PADDLE_LENGTH;
|
||||||
|
|
||||||
|
// TODO: lesser angles
|
||||||
|
var upAngle = left ? MathF.PI * 3 / 8 : MathF.PI * 5 / 8;
|
||||||
|
var downAngle = left ? -MathF.PI * 3 / 8 : MathF.PI * 11 / 8;
|
||||||
|
|
||||||
|
// TODO: reflect ball on surface instead of launching
|
||||||
|
ballState.BallAngle = ratio * downAngle + (1 - ratio) * upAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PongBallState {
|
||||||
|
public const float BALL_SPEED = 2 * BALL_RADIUS;
|
||||||
|
public const float BALL_RADIUS = HEIGHT / 125;
|
||||||
|
|
||||||
|
public static readonly PongBallState Initial = new() {
|
||||||
|
BallAngle = 0.0f,
|
||||||
|
Pos = new() {
|
||||||
|
X = WIDTH / 2,
|
||||||
|
Y = HEIGHT / 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public PointF Pos;
|
||||||
|
public float BallAngle;
|
||||||
|
|
||||||
|
public static void Update(ref PongBallState state) {
|
||||||
|
var (dy, dx) = MathF.SinCos(state.BallAngle);
|
||||||
|
state.Pos.X += BALL_SPEED * dx;
|
||||||
|
state.Pos.Y -= BALL_SPEED * dy;
|
||||||
|
|
||||||
|
if (state.Pos.Y < BALL_RADIUS
|
||||||
|
|| state.Pos.Y > HEIGHT - BALL_RADIUS)
|
||||||
|
state.BallAngle = 2 * MathF.PI - state.BallAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleF GetCollider() => new(Pos.X - BALL_RADIUS, Pos.Y - BALL_RADIUS, 2 * BALL_RADIUS, 2 * BALL_RADIUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct PongPaddleState {
|
||||||
|
public const float PADDLE_LENGTH = HEIGHT / 10;
|
||||||
|
public const float PADDLE_HALF_LENGTH = PADDLE_LENGTH / 2;
|
||||||
|
public const float PADDLE_WIDTH = PADDLE_LENGTH / 5;
|
||||||
|
public const float PADDLE_SPEED = 8;
|
||||||
|
|
||||||
|
public static readonly PongPaddleState Initial = new() {
|
||||||
|
Direction = PongPaddleDirection.Stop,
|
||||||
|
Height = HEIGHT / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
public float Height;
|
||||||
|
public PongPaddleDirection Direction;
|
||||||
|
|
||||||
|
public static void Update(ref PongPaddleState state) {
|
||||||
|
state.Height = Math.Clamp(state.Height - ((int)state.Direction) * PADDLE_SPEED, PADDLE_HALF_LENGTH, HEIGHT - PADDLE_HALF_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectangleF GetCollider(float x) => new(x - PADDLE_WIDTH / 2, Height - PADDLE_HALF_LENGTH, PADDLE_WIDTH, PADDLE_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
76
PongGame/Hubs/PongHub.cs
Normal file
76
PongGame/Hubs/PongHub.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
namespace PongGame.Hubs;
|
||||||
|
|
||||||
|
public class PongHub : Hub<IPongClient> {
|
||||||
|
private const string PLAYER_KEY = "PLAYER";
|
||||||
|
private readonly PongLobby Lobby;
|
||||||
|
private readonly ILogger<PongHub> Logger;
|
||||||
|
|
||||||
|
public PongHub(PongLobby lobby, ILogger<PongHub> logger) : base() {
|
||||||
|
Lobby = lobby;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PongPlayer Player {
|
||||||
|
get => (Context.Items.TryGetValue(PLAYER_KEY, out var player) ? player as PongPlayer : null)
|
||||||
|
?? throw new InvalidProgramException("Player was not assigned at connection start!");
|
||||||
|
set => Context.Items[PLAYER_KEY] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnConnectedAsync() {
|
||||||
|
Player = Lobby.CreatePlayer();
|
||||||
|
Player.Client = Clients.Client(Context.ConnectionId);
|
||||||
|
Player.Username = "Anon";
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssertNotInRoom() {
|
||||||
|
if (Player.ConnectedRoom is PongRoom currentRoom)
|
||||||
|
throw new HubException($"User is already connected to room [{currentRoom}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
private PongRoom AssertInRoom() {
|
||||||
|
if (Player.ConnectedRoom is not PongRoom currentRoom)
|
||||||
|
throw new HubException($"User is not in any room!");
|
||||||
|
return currentRoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> CreateRoom() {
|
||||||
|
AssertNotInRoom();
|
||||||
|
var room = Lobby.CreateRoom(Player);
|
||||||
|
return Task.FromResult(room.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> JoinRoom(string roomId) {
|
||||||
|
AssertNotInRoom();
|
||||||
|
var room = Lobby.JoinRoom(Player, roomId);
|
||||||
|
return Task.FromResult(room.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task MovePaddle(int dir) {
|
||||||
|
var room = AssertInRoom();
|
||||||
|
var direction = (PongPaddleDirection)dir;
|
||||||
|
if (!Enum.IsDefined(direction))
|
||||||
|
throw new HubException($"Invalid direction: {dir}!");
|
||||||
|
room.MovePaddle(Player, direction);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task LeaveRoom() {
|
||||||
|
Lobby.LeaveRoom(Player);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RequestUsernameChange(string username) {
|
||||||
|
// TOOD: check this
|
||||||
|
Logger.LogInformation($"Player {Player} requested username change to [{username}]");
|
||||||
|
Player.Username = username;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnDisconnectedAsync(Exception? exception) {
|
||||||
|
Lobby.RemovePlayer(Player);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
65
PongGame/Hubs/PongLobbyCollection.cs
Normal file
65
PongGame/Hubs/PongLobbyCollection.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
namespace PongGame.Hubs;
|
||||||
|
|
||||||
|
public class PongLobby {
|
||||||
|
private readonly HashSet<PongPlayer> connectedPlayers = new();
|
||||||
|
private readonly Dictionary<string, PongRoom> PongRooms = new();
|
||||||
|
|
||||||
|
public PongLobby(ILogger<PongLobby> logger)
|
||||||
|
=> Logger = logger;
|
||||||
|
|
||||||
|
public const int ROOM_ID_LENGTH = 4;
|
||||||
|
|
||||||
|
public PongPlayer CreatePlayer() {
|
||||||
|
var player = new PongPlayer();
|
||||||
|
lock (connectedPlayers)
|
||||||
|
connectedPlayers.Add(player);
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePlayer(PongPlayer player) {
|
||||||
|
if (player.ConnectedRoom is PongRoom room)
|
||||||
|
room.Leave(player);
|
||||||
|
lock (connectedPlayers)
|
||||||
|
_ = connectedPlayers.Remove(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PongRoom CreateRoom(PongPlayer player) {
|
||||||
|
PongRoom room;
|
||||||
|
lock (PongRooms) {
|
||||||
|
room = new(GenerateRoomId(), Logger);
|
||||||
|
PongRooms.Add(room.ID, room);
|
||||||
|
}
|
||||||
|
room.Join(player);
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PongRoom JoinRoom(PongPlayer player, string roomId) {
|
||||||
|
PongRoom? room;
|
||||||
|
lock (PongRooms) {
|
||||||
|
room = PongRooms.GetValueOrDefault(roomId);
|
||||||
|
}
|
||||||
|
if (room is null) throw new HubException($"Room [{roomId}] not found!");
|
||||||
|
room.Join(player);
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LeaveRoom(PongPlayer player) {
|
||||||
|
if (player.ConnectedRoom is PongRoom room)
|
||||||
|
room.Leave(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Random random = new();
|
||||||
|
private readonly ILogger<PongLobby> Logger;
|
||||||
|
private const string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
||||||
|
private string GenerateRoomId() {
|
||||||
|
string id;
|
||||||
|
do {
|
||||||
|
id = string.Concat(Enumerable.Range(0, ROOM_ID_LENGTH).Select(_ => ALPHABET[random.Next(ALPHABET.Length)]));
|
||||||
|
} while (PongRooms.ContainsKey(id));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
7
PongGame/Hubs/PongPaddleDirection.cs
Normal file
7
PongGame/Hubs/PongPaddleDirection.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace PongGame;
|
||||||
|
|
||||||
|
public enum PongPaddleDirection {
|
||||||
|
Up = -1,
|
||||||
|
Stop,
|
||||||
|
Down,
|
||||||
|
}
|
22
PongGame/Hubs/PongPlayer.cs
Normal file
22
PongGame/Hubs/PongPlayer.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace PongGame.Hubs;
|
||||||
|
|
||||||
|
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
|
||||||
|
public class PongPlayer {
|
||||||
|
private string username = default!;
|
||||||
|
|
||||||
|
public PongRoom? ConnectedRoom { get; internal set; }
|
||||||
|
public string Username {
|
||||||
|
get => username;
|
||||||
|
internal set {
|
||||||
|
if (username != value) {
|
||||||
|
username = value;
|
||||||
|
Task.Run(() => Client.UsernameChanged(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public IPongClient Client { get; internal set; } = default!;
|
||||||
|
|
||||||
|
public override string ToString() => $"[{Username}]";
|
||||||
|
}
|
99
PongGame/Hubs/PongRoom.cs
Normal file
99
PongGame/Hubs/PongRoom.cs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
namespace PongGame.Hubs;
|
||||||
|
|
||||||
|
public class PongRoom {
|
||||||
|
public string ID { get; }
|
||||||
|
private readonly ILogger Logger;
|
||||||
|
private const string JOIN_LOG_TEMPLATE = "[{ID}] {Player} joined pong room as player {Number}!";
|
||||||
|
private const string LEAVE_LOG_TEMPLATE = "[{ID}] Player {Number} {Player} left pong room!";
|
||||||
|
private const string DIRECTION_LOG_TEMPLATE = "[{ID}] Player {Number} {player} moves paddle in direction: {direction}!";
|
||||||
|
|
||||||
|
public PongRoom(string id, ILogger logger) {
|
||||||
|
ID = id;
|
||||||
|
Logger = logger;
|
||||||
|
gameWorker.DoWork += GameLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PongPlayer? Player1 { get; private set; }
|
||||||
|
public PongPlayer? Player2 { get; private set; }
|
||||||
|
private PongGameState State = PongGameState.Initial;
|
||||||
|
|
||||||
|
public void Join(PongPlayer player) {
|
||||||
|
// TODO: synchronize this
|
||||||
|
if (Player1 is null) {
|
||||||
|
Player1 = player;
|
||||||
|
Logger.LogInformation(JOIN_LOG_TEMPLATE, ID, player, 1);
|
||||||
|
} else if (Player2 is null) {
|
||||||
|
Player2 = player;
|
||||||
|
Logger.LogInformation(JOIN_LOG_TEMPLATE, ID, player, 1);
|
||||||
|
} else
|
||||||
|
throw new HubException($"Lobby [{ID}] is already full!");
|
||||||
|
_ = Task.Run(PlayersChanged);
|
||||||
|
player.ConnectedRoom = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Leave(PongPlayer player) {
|
||||||
|
if (Player1 == player) {
|
||||||
|
Player1 = null;
|
||||||
|
Logger.LogInformation(LEAVE_LOG_TEMPLATE, ID, 1, player);
|
||||||
|
} else if (Player2 == player) {
|
||||||
|
Player2 = null;
|
||||||
|
Logger.LogInformation(LEAVE_LOG_TEMPLATE, ID, 2, player);
|
||||||
|
}
|
||||||
|
player.ConnectedRoom = null;
|
||||||
|
_ = Task.Run(PlayersChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BackgroundWorker gameWorker = new() {
|
||||||
|
WorkerSupportsCancellation = true
|
||||||
|
};
|
||||||
|
|
||||||
|
private void GameLoop(object? sender, DoWorkEventArgs e) {
|
||||||
|
while (!gameWorker.CancellationPending
|
||||||
|
&& Player1 is PongPlayer p1
|
||||||
|
&& Player2 is PongPlayer p2
|
||||||
|
&& State.Status is GameStatus.InProgress) {
|
||||||
|
PongGameState.Update(ref State);
|
||||||
|
_ = Task.Run(() => Task.WhenAll(
|
||||||
|
p1.Client.ReceiveGameState(State),
|
||||||
|
p2.Client.ReceiveGameState(State)));
|
||||||
|
Thread.Sleep(1000 / 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PlayersChanged() {
|
||||||
|
if (Player1 is PongPlayer player1 && Player2 is PongPlayer player2) {
|
||||||
|
Logger.LogInformation("[{ID}] Pong game started: {player1} vs. {player2}", ID, player1, player2);
|
||||||
|
ResumeGame();
|
||||||
|
} else if (Player1 is null && Player2 is null) {
|
||||||
|
CloseRoom();
|
||||||
|
} else
|
||||||
|
PauseGame();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResumeGame() {
|
||||||
|
State.Status = GameStatus.InProgress;
|
||||||
|
if (!gameWorker.IsBusy) gameWorker.RunWorkerAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PauseGame() => State.Status = GameStatus.Paused;
|
||||||
|
private void CloseRoom() => State.Status = GameStatus.Finished;
|
||||||
|
|
||||||
|
public override string ToString() => $"[{ID}]";
|
||||||
|
|
||||||
|
public void MovePaddle(PongPlayer player, PongPaddleDirection direction) {
|
||||||
|
if (Player1 == player) {
|
||||||
|
State.Paddle1.Direction = direction;
|
||||||
|
Logger.LogInformation(DIRECTION_LOG_TEMPLATE, ID, 1, player, direction);
|
||||||
|
return;
|
||||||
|
} else if (Player2 == player) {
|
||||||
|
State.Paddle2.Direction = direction;
|
||||||
|
Logger.LogInformation(DIRECTION_LOG_TEMPLATE, ID, 2, player, direction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Player is not in this room, but moved! Assumably players room wasn't deleted.");
|
||||||
|
}
|
||||||
|
}
|
@ -6,4 +6,5 @@
|
|||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="display-4">Welcome</h1>
|
<h1 class="display-4">Welcome</h1>
|
||||||
|
<a asp-area="" asp-page="/Pong">Pong</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ public class IndexModel : PageModel {
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnGet() {
|
public IActionResult OnGet() {
|
||||||
|
return RedirectToPage("Pong");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
PongGame/Pages/Pong.cshtml
Normal file
30
PongGame/Pages/Pong.cshtml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
@page
|
||||||
|
@model PongModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Pong";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="display-4">Pong</h1>
|
||||||
|
<h3 id="connection">Connection Status</h3>
|
||||||
|
|
||||||
|
<button id="createlobby" class="btn btn-primary mb-3">Create</button>
|
||||||
|
<button id="leavelobby" class="btn btn-primary mb-3">Leave</button>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input id="roomid" type="text" class="form-control" placeholder="Room ID" aria-label="Room ID" aria-describedby="joinroom">
|
||||||
|
<button id="joinroom" class="btn btn-outline-secondary" type="button">Button</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input id="username" type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="setusername">
|
||||||
|
<button id="setusername" class="btn btn-outline-secondary" type="button">Set Username</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="canvas-container" class="mb-3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="~/lib/signalr/dist/browser/signalr.min.js"></script>
|
||||||
|
<script src="~/lib/signalr/dist/browser/signalr-protocol-msgpack.min.js"></script>
|
||||||
|
<script src="~/lib/pixi/dist/pixi.min.js"></script>
|
||||||
|
<script src="~/js/pong.js"></script>
|
15
PongGame/Pages/Pong.cshtml.cs
Normal file
15
PongGame/Pages/Pong.cshtml.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace PongGame.Pages;
|
||||||
|
public class PongModel : PageModel {
|
||||||
|
private readonly ILogger<PongModel> _logger;
|
||||||
|
|
||||||
|
public PongModel(ILogger<PongModel> logger) {
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGet() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
|
using MessagePack;
|
||||||
|
using PongGame.Hubs;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
|
builder.Services.AddSingleton<PongLobby>(services
|
||||||
|
=> new(services.GetRequiredService<ILogger<PongLobby>>()));
|
||||||
|
builder.Services.AddSignalR()
|
||||||
|
.AddMessagePackProtocol();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
@ -15,6 +22,9 @@ app.UseRouting();
|
|||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapRazorPages();
|
app.UseEndpoints(endpoints => {
|
||||||
|
endpoints.MapRazorPages();
|
||||||
|
endpoints.MapHub<PongHub>("/pong/hub");
|
||||||
|
});
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
37
PongGame/libman.json
Normal file
37
PongGame/libman.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"defaultProvider": "jsdelivr",
|
||||||
|
"libraries": [
|
||||||
|
{
|
||||||
|
"library": "@microsoft/signalr-protocol-msgpack@6.0.10",
|
||||||
|
"destination": "wwwroot/lib/signalr/",
|
||||||
|
"files": [
|
||||||
|
"dist/browser/signalr-protocol-msgpack.js",
|
||||||
|
"dist/browser/signalr-protocol-msgpack.js.map",
|
||||||
|
"dist/browser/signalr-protocol-msgpack.min.js",
|
||||||
|
"dist/browser/signalr-protocol-msgpack.min.js.map"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"library": "@microsoft/signalr@6.0.10",
|
||||||
|
"destination": "wwwroot/lib/signalr/",
|
||||||
|
"files": [
|
||||||
|
"dist/browser/signalr.js",
|
||||||
|
"dist/browser/signalr.js.map",
|
||||||
|
"dist/browser/signalr.min.js",
|
||||||
|
"dist/browser/signalr.min.js.map"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provider": "jsdelivr",
|
||||||
|
"library": "pixi.js@7.0.2",
|
||||||
|
"destination": "wwwroot/lib/pixi/",
|
||||||
|
"files": [
|
||||||
|
"dist/pixi.js",
|
||||||
|
"dist/pixi.js.map",
|
||||||
|
"dist/pixi.min.js.map",
|
||||||
|
"dist/pixi.min.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
169
PongGame/wwwroot/js/pong.js
Normal file
169
PongGame/wwwroot/js/pong.js
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
|
.withUrl("/pong/hub")
|
||||||
|
.withAutomaticReconnect()
|
||||||
|
.withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
function getElement(id) {
|
||||||
|
return document.getElementById(id) ?? console.error(`Element #${id} not found!`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionStatus = getElement("connection");
|
||||||
|
const createlobby = getElement("createlobby");
|
||||||
|
const roomidinput = getElement("roomid");
|
||||||
|
const joinroom = getElement("joinroom");
|
||||||
|
const usernameinput = getElement("username");
|
||||||
|
const setusername = getElement("setusername");
|
||||||
|
const leavelobby = getElement("leavelobby");
|
||||||
|
|
||||||
|
connection.onclose(function (error) {
|
||||||
|
if (error) {
|
||||||
|
connectionStatus.textContent = "Unexpected error!";
|
||||||
|
return console.error(`Connection aborted: ${error.message}`);
|
||||||
|
}
|
||||||
|
console.info("Disconnected!");
|
||||||
|
connectionStatus.textContent = "Closed!";
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.onreconnecting(function (error) {
|
||||||
|
if (error) {
|
||||||
|
connectionStatus.textContent = "Reconnecting!";
|
||||||
|
return console.error(`Connection reconnecting: ${error.message}`);
|
||||||
|
}
|
||||||
|
console.info("Reconnecting!");
|
||||||
|
connectionStatus.textContent = "Reconnecting!";
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.onreconnected(function (connectionId) {
|
||||||
|
console.info(`Connected as ${connectionId}!`);
|
||||||
|
connectionStatus.textContent = "Connected!";
|
||||||
|
});
|
||||||
|
|
||||||
|
createlobby.addEventListener("click", function (event) {
|
||||||
|
connection.invoke("CreateRoom").then(function (roomId) {
|
||||||
|
roomidinput.value = roomId;
|
||||||
|
console.info(`Joined room [${roomId}]`);
|
||||||
|
}).catch(function (err) {
|
||||||
|
return console.error(err.toString());
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on("GameStateChanged", function (state) {
|
||||||
|
console.info(`Game is now in state ${state}`);
|
||||||
|
});
|
||||||
|
connection.on("UsernameChanged", function (username) {
|
||||||
|
console.info(`Username is now ${username}`);
|
||||||
|
usernameinput.value = username;
|
||||||
|
});
|
||||||
|
|
||||||
|
joinroom.addEventListener("click", function (event) {
|
||||||
|
connection.invoke("JoinRoom", roomidinput.value).catch(function (err) {
|
||||||
|
return console.error(err.toString());
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
setusername.addEventListener("click", function (event) {
|
||||||
|
connection.invoke("RequestUsernameChange", usernameinput.value).catch(function (err) {
|
||||||
|
return console.error(err.toString());
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
leavelobby.addEventListener("click", function (event) {
|
||||||
|
connection.invoke("LeaveRoom").catch(function (err) {
|
||||||
|
return console.error(err.toString());
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
function movePaddle(direction) {
|
||||||
|
connection.invoke("MovePaddle", direction).catch(function (err) {
|
||||||
|
return console.error(err.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveUp() { return movePaddle(-1); }
|
||||||
|
function stopPaddle() { return movePaddle(0); }
|
||||||
|
function moveDown() { return movePaddle(1); }
|
||||||
|
|
||||||
|
// Create the application helper and add its render target to the page
|
||||||
|
let app = new PIXI.Application({ width: 1000, height: 500 });
|
||||||
|
getElement('canvas-container').appendChild(app.view);
|
||||||
|
|
||||||
|
let graphics = new PIXI.Graphics();
|
||||||
|
app.stage.addChild(graphics);
|
||||||
|
|
||||||
|
function renderPaddle(graphics, state, xMid) {
|
||||||
|
var xLeft = xMid - 5;
|
||||||
|
graphics.beginFill(0x00FFFF);
|
||||||
|
graphics.drawRect(xLeft, state.Height - 25, 10, 50);
|
||||||
|
graphics.endFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBall(graphics, state) {
|
||||||
|
graphics.beginFill(0xFF00FF);
|
||||||
|
graphics.drawCircle(state.Pos.X, state.Pos.Y, 4);
|
||||||
|
graphics.endFill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGameState(graphics, state) {
|
||||||
|
graphics.clear();
|
||||||
|
renderPaddle(graphics, state.Paddle1, 50);
|
||||||
|
renderPaddle(graphics, state.Paddle2, 1000 - 50);
|
||||||
|
renderBall(graphics, state.BallState);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.on("ReceiveGameState", function (state) {
|
||||||
|
renderGameState(graphics, state);
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyEvent = (function () {
|
||||||
|
var upPressed = false;
|
||||||
|
var downPressed = false;
|
||||||
|
|
||||||
|
function moveUpdated() {
|
||||||
|
if (upPressed == downPressed) stopPaddle();
|
||||||
|
else if (upPressed) moveUp();
|
||||||
|
else if (downPressed) moveDown();
|
||||||
|
else console.error("unknown move!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handler(event) {
|
||||||
|
if (event.repeat) return;
|
||||||
|
if (event.path.indexOf(document.body) != 0) return; // only use key if it was pressed on empty space
|
||||||
|
var pressed = event.type == "keyup";
|
||||||
|
// W Key is 87, Up arrow is 87
|
||||||
|
// S Key is 83, Down arrow is 40
|
||||||
|
switch (event.keyCode) {
|
||||||
|
case 87:
|
||||||
|
case 38:
|
||||||
|
upPressed = pressed;
|
||||||
|
break;
|
||||||
|
case 83:
|
||||||
|
case 40:
|
||||||
|
downPressed = pressed;
|
||||||
|
break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
moveUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
})();
|
||||||
|
|
||||||
|
document.addEventListener('keydown', keyEvent);
|
||||||
|
document.addEventListener('keyup', keyEvent);
|
||||||
|
|
||||||
|
connection.start().then(function () {
|
||||||
|
console.info(`Connected!`);
|
||||||
|
connectionStatus.textContent = "Connected!";
|
||||||
|
}).catch(function (err) {
|
||||||
|
connectionStatus.textContent = "Connection failed!";
|
||||||
|
return console.error(err.toString());
|
||||||
|
});
|
24183
PongGame/wwwroot/lib/pixi/dist/pixi.js
vendored
Normal file
24183
PongGame/wwwroot/lib/pixi/dist/pixi.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
PongGame/wwwroot/lib/pixi/dist/pixi.js.map
vendored
Normal file
1
PongGame/wwwroot/lib/pixi/dist/pixi.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1116
PongGame/wwwroot/lib/pixi/dist/pixi.min.js
vendored
Normal file
1116
PongGame/wwwroot/lib/pixi/dist/pixi.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PongGame/wwwroot/lib/pixi/dist/pixi.min.js.map
vendored
Normal file
1
PongGame/wwwroot/lib/pixi/dist/pixi.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2077
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.js
vendored
Normal file
2077
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.js.map
vendored
Normal file
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.min.js
vendored
Normal file
2
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.min.js.map
vendored
Normal file
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr-protocol-msgpack.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3115
PongGame/wwwroot/lib/signalr/dist/browser/signalr.js
vendored
Normal file
3115
PongGame/wwwroot/lib/signalr/dist/browser/signalr.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr.js.map
vendored
Normal file
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
PongGame/wwwroot/lib/signalr/dist/browser/signalr.min.js
vendored
Normal file
2
PongGame/wwwroot/lib/signalr/dist/browser/signalr.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr.min.js.map
vendored
Normal file
1
PongGame/wwwroot/lib/signalr/dist/browser/signalr.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
324
package-lock.json
generated
Normal file
324
package-lock.json
generated
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
{
|
||||||
|
"name": "PongGame",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/signalr": "^6.0.10",
|
||||||
|
"@microsoft/signalr-protocol-msgpack": "^6.0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@microsoft/signalr": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-ND9LiIYac+ZDgCgW2QzpNfe9BTiOtjc2AX/2GtFIhRGhEzx5CixcNANg2VGj27IAxycAPPnEoy7+QA31Eil7QQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"eventsource": "^1.0.7",
|
||||||
|
"fetch-cookie": "^0.11.0",
|
||||||
|
"node-fetch": "^2.6.7",
|
||||||
|
"ws": "^7.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@microsoft/signalr-protocol-msgpack": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-Xj3QuH/HMcLGtc+iZjE8BH/auQVn4FQY9adv7M/wsMpT9TEe1iQJKjD0Hlh+Y42ioNaO0MFVlfFXhkYUK7y5QQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/signalr": ">=6.0.10",
|
||||||
|
"@msgpack/msgpack": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@msgpack/msgpack": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"dependencies": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eventsource": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fetch-cookie": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA==",
|
||||||
|
"dependencies": {
|
||||||
|
"tough-cookie": "^2.3.3 || ^3.0.1 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/psl": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
|
||||||
|
},
|
||||||
|
"node_modules/punycode": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/querystringify": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
|
||||||
|
},
|
||||||
|
"node_modules/requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
||||||
|
},
|
||||||
|
"node_modules/tough-cookie": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"psl": "^1.1.33",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
|
"universalify": "^0.2.0",
|
||||||
|
"url-parse": "^1.5.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
|
"node_modules/universalify": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/url-parse": {
|
||||||
|
"version": "1.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||||
|
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"querystringify": "^2.1.1",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/signalr": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-ND9LiIYac+ZDgCgW2QzpNfe9BTiOtjc2AX/2GtFIhRGhEzx5CixcNANg2VGj27IAxycAPPnEoy7+QA31Eil7QQ==",
|
||||||
|
"requires": {
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"eventsource": "^1.0.7",
|
||||||
|
"fetch-cookie": "^0.11.0",
|
||||||
|
"node-fetch": "^2.6.7",
|
||||||
|
"ws": "^7.4.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@microsoft/signalr-protocol-msgpack": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-Xj3QuH/HMcLGtc+iZjE8BH/auQVn4FQY9adv7M/wsMpT9TEe1iQJKjD0Hlh+Y42ioNaO0MFVlfFXhkYUK7y5QQ==",
|
||||||
|
"requires": {
|
||||||
|
"@microsoft/signalr": ">=6.0.10",
|
||||||
|
"@msgpack/msgpack": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@msgpack/msgpack": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ=="
|
||||||
|
},
|
||||||
|
"abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"requires": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
|
||||||
|
},
|
||||||
|
"eventsource": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA=="
|
||||||
|
},
|
||||||
|
"fetch-cookie": {
|
||||||
|
"version": "0.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz",
|
||||||
|
"integrity": "sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA==",
|
||||||
|
"requires": {
|
||||||
|
"tough-cookie": "^2.3.3 || ^3.0.1 || ^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
|
"requires": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"psl": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
|
||||||
|
},
|
||||||
|
"punycode": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||||
|
},
|
||||||
|
"querystringify": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
|
||||||
|
},
|
||||||
|
"requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
||||||
|
},
|
||||||
|
"tough-cookie": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
|
||||||
|
"requires": {
|
||||||
|
"psl": "^1.1.33",
|
||||||
|
"punycode": "^2.1.1",
|
||||||
|
"universalify": "^0.2.0",
|
||||||
|
"url-parse": "^1.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
|
"universalify": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
|
||||||
|
},
|
||||||
|
"url-parse": {
|
||||||
|
"version": "1.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||||
|
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||||
|
"requires": {
|
||||||
|
"querystringify": "^2.1.1",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"requires": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.5.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||||
|
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
package.json
Normal file
6
package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@microsoft/signalr": "^6.0.10",
|
||||||
|
"@microsoft/signalr-protocol-msgpack": "^6.0.10"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user