pong-game/PongGame/Hubs/PongGameState.cs

94 lines
3.4 KiB
C#
Raw Normal View History

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 = 50.0f;
public const float PADDLE2_OFFSET = 1000 - PADDLE1_OFFSET;
public static readonly PongGameState Initial = new() {
BallState = PongBallState.Initial,
Paddle1 = PongPaddleState.Initial,
Paddle2 = PongPaddleState.Initial,
Status = GameStatus.WaitingForPlayers
};
public PongPaddleState Paddle1;
public PongPaddleState Paddle2;
public PongBallState BallState;
public GameStatus Status;
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);
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.Y - paddle.Height + PongPaddleState.PADDLE_HALF_LENGTH) / PongPaddleState.PADDLE_LENGTH;
var upAngle = left ? MathF.PI * 3 / 8 : MathF.PI * 5 / 8;
var downAngle = -upAngle;
ballState.BallAngle = ratio * downAngle + (1 - ratio) * upAngle;
}
public struct PongBallState {
public const float BALL_SPEED = 8;
public const float BALL_RADIUS = 2;
public static readonly PongBallState Initial = new() {
BallAngle = 0.0f,
X = WIDTH / 2,
Y = HEIGHT / 2
};
public float X;
public float Y;
public float BallAngle;
public static void Update(ref PongBallState state) {
var (dy, dx) = MathF.SinCos(state.BallAngle);
state.X += BALL_SPEED * dx;
state.Y -= BALL_SPEED * dy;
}
public RectangleF GetCollider() => new(X - BALL_RADIUS, 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 / 6;
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_HALF_LENGTH, Height - PADDLE_HALF_LENGTH, PADDLE_WIDTH, PADDLE_LENGTH);
}
}