Added game state, allow paddle movement

This commit is contained in:
Michael Chen 2022-11-04 00:13:00 +01:00
parent 462e27c88a
commit a4e22a4c52
Signed by: cnml
GPG Key ID: 5845BF3F82D5F629
7 changed files with 96 additions and 13 deletions

View File

@ -1,6 +1,6 @@
namespace PongGame;
public enum GameState {
public enum GameStatus {
WaitingForPlayers,
InProgress,
Finished,

View File

@ -1,6 +1,6 @@
namespace PongGame.Hubs;
public interface IPongClient {
Task GameStateChanged(GameState state);
Task GameStateChanged(GameStatus state);
Task UsernameChanged(string value);
}

View File

@ -0,0 +1,35 @@
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 {
private const double HEIGHT = 500.0d;
private const double WIDTH = 2 * HEIGHT;
private const double PADDLE_LENGTH = HEIGHT / 10;
private const double BALL_SPEED = 8;
public static readonly PongGameState Initial = new() {
BallPosition = PongBallPosition.Initial,
P1Height = HEIGHT / 2,
P2Height = HEIGHT / 2,
P1Direction = PongPaddleDirection.Stop,
P2Direction = PongPaddleDirection.Stop,
Status = GameStatus.WaitingForPlayers
};
public double P1Height { get; set; }
public double P2Height { get; set; }
public PongBallPosition BallPosition { get; set; }
public GameStatus Status { get; set; }
public PongPaddleDirection P1Direction { get; set; }
public PongPaddleDirection P2Direction { get; set; }
public struct PongBallPosition {
public static readonly PongBallPosition Initial = new() {
X = WIDTH / 2,
Y = HEIGHT / 2
};
public double X { get; set; }
public double Y { get; set; }
}
}

View File

@ -30,6 +30,12 @@ public class PongHub : Hub<IPongClient> {
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);
@ -42,6 +48,15 @@ public class PongHub : Hub<IPongClient> {
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;

View File

@ -0,0 +1,7 @@
namespace PongGame;
public enum PongPaddleDirection {
Up = -1,
Stop,
Down,
}

View File

@ -5,6 +5,9 @@ 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;
@ -13,16 +16,16 @@ public class PongRoom {
public PongPlayer? Player1 { get; private set; }
public PongPlayer? Player2 { get; private set; }
public GameState State { get; private set; } = GameState.WaitingForPlayers;
private PongGameState State = PongGameState.Initial;
public void Join(PongPlayer player) {
// TODO: synchronize this
if (Player1 is null) {
Player1 = player;
Logger.LogInformation($"[{ID}] {player} joined pong room as player 1!");
Logger.LogInformation(JOIN_LOG_TEMPLATE, ID, player, 1);
} else if (Player2 is null) {
Player2 = player;
Logger.LogInformation($"[{ID}] {player} joined pong room as player 2!");
Logger.LogInformation(JOIN_LOG_TEMPLATE, ID, player, 1);
} else
throw new HubException($"Lobby [{ID}] is already full!");
_ = Task.Run(PlayersChanged);
@ -32,10 +35,10 @@ public class PongRoom {
public void Leave(PongPlayer player) {
if (Player1 == player) {
Player1 = null;
Logger.LogInformation($"[{ID}] Player 1 {player} left pong room!");
Logger.LogInformation(LEAVE_LOG_TEMPLATE, ID, 1, player);
} else if (Player2 == player) {
Player2 = null;
Logger.LogInformation($"[{ID}] Player 2 {player} left pong room!");
Logger.LogInformation(LEAVE_LOG_TEMPLATE, ID, 2, player);
}
player.ConnectedRoom = null;
_ = Task.Run(PlayersChanged);
@ -52,14 +55,27 @@ public class PongRoom {
}
private void ResumeGame(PongPlayer player1, PongPlayer player2) {
Logger.LogInformation($"[{ID}] Pong game started: {player1} vs. {player2}");
State = GameState.InProgress;
player1.Client.GameStateChanged(State);
player2.Client.GameStateChanged(State);
Logger.LogInformation("[{ID}] Pong game started: {player1} vs. {player2}", ID, player1, player2);
State.Status = GameStatus.InProgress;
player1.Client.GameStateChanged(State.Status);
player2.Client.GameStateChanged(State.Status);
}
private void PauseGame() => State = GameState.WaitingForPlayers;
private void CloseRoom() => State = GameState.Finished;
private void PauseGame() => State.Status = GameStatus.WaitingForPlayers;
private void CloseRoom() => State.Status = GameStatus.Finished;
public override string ToString() => $"[{ID}]";
public void MovePaddle(PongPlayer player, PongPaddleDirection direction) {
if (Player1 == player) {
State.P1Direction = direction;
Logger.LogInformation(DIRECTION_LOG_TEMPLATE, ID, 1, player, direction);
return;
} else if (Player2 == player) {
State.P2Direction = 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.");
}
}

View File

@ -79,6 +79,16 @@ leavelobby.addEventListener("click", function (event) {
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); }
connection.start().then(function () {
console.info(`Connected!`);
connectionStatus.textContent = "Connected!";