Refactor game architecture and enhance features
This commit is contained in:
+55
-6
@@ -4,7 +4,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Game States & Configurations
|
// Game states and configurations
|
||||||
enum class GameState {
|
enum class GameState {
|
||||||
MainMenu,
|
MainMenu,
|
||||||
DifficultySelect,
|
DifficultySelect,
|
||||||
@@ -28,8 +28,60 @@ constexpr float CPU_SPEED_HARD = 480.0f;
|
|||||||
constexpr float PLAYER_SPEED = 360.0f;
|
constexpr float PLAYER_SPEED = 360.0f;
|
||||||
constexpr float BALL_SPEED = 420.0f;
|
constexpr float BALL_SPEED = 420.0f;
|
||||||
|
|
||||||
// --- Base Entities ---
|
// Game state contexts
|
||||||
|
struct ScoreBoard {
|
||||||
|
int player_score = 0;
|
||||||
|
int player2_score = 0;
|
||||||
|
int cpu_score = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameConfig {
|
||||||
|
int resolutionOption = 0; // 0 = 1280x800, 1 = 1600x900, 2 = 1920x1080
|
||||||
|
int framerateOption = 0; // 0 = 60 FPS, 1 = 144 FPS, 2 = VSync
|
||||||
|
bool isFullscreen = false;
|
||||||
|
int maxScoreOption = 0; // 0 = 5, 1 = 11, 2 = 15, 3 = 21
|
||||||
|
int maxScore = 5;
|
||||||
|
bool sfxEnabled = true;
|
||||||
|
int selectedSettingLine = 0; // 0 = Resolution, 1 = Framerate, 2 = Screen Mode, 3 = Score Limit, 4 = Sound, 5 = Back
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameContext {
|
||||||
|
GameState currentState = GameState::MainMenu;
|
||||||
|
float sessionPlayTime = 0.0f;
|
||||||
|
bool isPaused = false;
|
||||||
|
bool shouldQuit = false;
|
||||||
|
bool isMultiplayer = false;
|
||||||
|
bool p1Ready = false;
|
||||||
|
bool p2Ready = false;
|
||||||
|
ScoreBoard score;
|
||||||
|
GameConfig config;
|
||||||
|
|
||||||
|
// Asset textures and sound handles
|
||||||
|
Texture2D courtBackground = { 0 };
|
||||||
|
Texture2D wallsTexture = { 0 };
|
||||||
|
Texture2D lineTexture = { 0 };
|
||||||
|
Sound paddleHitSound = { 0 };
|
||||||
|
Sound wallHitSound = { 0 };
|
||||||
|
Sound scoreSound = { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class Ball;
|
||||||
|
class Paddle;
|
||||||
|
class CpuPaddle;
|
||||||
|
|
||||||
|
// Game state update and draw routines
|
||||||
|
void ResetBall(Ball& ball);
|
||||||
|
void DrawCourt(const GameContext& ctx, int screenWidth, int screenHeight);
|
||||||
|
void UpdatePlayingState(GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu);
|
||||||
|
void DrawPlayingState(const GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu, int screenWidth, int screenHeight);
|
||||||
|
void UpdateMultiplayerState(GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu);
|
||||||
|
void DrawMultiplayerState(const GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu, int screenWidth, int screenHeight);
|
||||||
|
void UpdateGameOverState(GameContext& ctx);
|
||||||
|
void DrawGameOverState(const GameContext& ctx, int screenWidth, int screenHeight);
|
||||||
|
|
||||||
|
|
||||||
|
// Base entity representation
|
||||||
class GameObject {
|
class GameObject {
|
||||||
public:
|
public:
|
||||||
Vector2 position;
|
Vector2 position;
|
||||||
@@ -41,15 +93,12 @@ public:
|
|||||||
virtual void Draw() = 0;
|
virtual void Draw() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Game Objects ---
|
// Entity classes
|
||||||
|
|
||||||
class Paddle : public GameObject {
|
class Paddle : public GameObject {
|
||||||
public:
|
public:
|
||||||
float width;
|
float width;
|
||||||
float height;
|
float height;
|
||||||
Texture2D texture;
|
Texture2D texture;
|
||||||
|
|
||||||
public:
|
|
||||||
Paddle(Vector2 pos, Color c, float w, float h, const std::string& texturePath = "")
|
Paddle(Vector2 pos, Color c, float w, float h, const std::string& texturePath = "")
|
||||||
: GameObject(pos, c), width(w), height(h) {
|
: GameObject(pos, c), width(w), height(h) {
|
||||||
if (!texturePath.empty()) {
|
if (!texturePath.empty()) {
|
||||||
|
|||||||
+10
-3
@@ -3,8 +3,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// --- UI / Systems ---
|
// Menu UI controller
|
||||||
|
|
||||||
class Menu {
|
class Menu {
|
||||||
private:
|
private:
|
||||||
std::string title;
|
std::string title;
|
||||||
@@ -18,4 +17,12 @@ public:
|
|||||||
|
|
||||||
int Update();
|
int Update();
|
||||||
void Draw();
|
void Draw();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Settings and lobby routines
|
||||||
|
void ApplyResolution(int width, int height);
|
||||||
|
void ApplyFramerate(int option);
|
||||||
|
void UpdateSettingsState(GameContext& ctx);
|
||||||
|
void DrawSettingsState(const GameContext& ctx, int screenWidth, int screenHeight);
|
||||||
|
void UpdateLobbyState(GameContext& ctx);
|
||||||
|
void DrawLobbyState(const GameContext& ctx, int screenWidth, int screenHeight);
|
||||||
|
|||||||
+290
-8
@@ -1,7 +1,6 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
|
||||||
// --- Paddle Implementation ---
|
// Paddle implementation
|
||||||
|
|
||||||
bool Paddle::Update() {
|
bool Paddle::Update() {
|
||||||
float dt = GetFrameTime();
|
float dt = GetFrameTime();
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ bool Paddle::Update() {
|
|||||||
position.y += PLAYER_SPEED * dt;
|
position.y += PLAYER_SPEED * dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit movement
|
// Keep paddle within screen boundaries
|
||||||
if (position.y <= 20.0f) {
|
if (position.y <= 20.0f) {
|
||||||
position.y = 20.0f;
|
position.y = 20.0f;
|
||||||
}
|
}
|
||||||
@@ -20,7 +19,7 @@ bool Paddle::Update() {
|
|||||||
position.y = GetScreenHeight() - 20.0f - height;
|
position.y = GetScreenHeight() - 20.0f - height;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamic X position
|
// Align paddle relative to right screen edge
|
||||||
position.x = GetScreenWidth() - 20.0f - 10.0f - width;
|
position.x = GetScreenWidth() - 20.0f - 10.0f - width;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -42,8 +41,7 @@ void Paddle::Draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Ball Implementation ---
|
// Ball implementation
|
||||||
|
|
||||||
bool Ball::Update() {
|
bool Ball::Update() {
|
||||||
float dt = GetFrameTime();
|
float dt = GetFrameTime();
|
||||||
position.x += velocity.x * dt;
|
position.x += velocity.x * dt;
|
||||||
@@ -67,7 +65,291 @@ void Ball::Draw() {
|
|||||||
WHITE
|
WHITE
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Cast to int as DrawCircle expects integers for coordinates
|
|
||||||
DrawCircle((int)position.x, (int)position.y, radius, color);
|
DrawCircle((int)position.x, (int)position.y, radius, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Game state routines and helpers
|
||||||
|
void ResetBall(Ball& ball) {
|
||||||
|
ball.position.x = GetScreenWidth() / 2.0f;
|
||||||
|
ball.position.y = GetScreenHeight() / 2.0f;
|
||||||
|
|
||||||
|
int speed_choices[2] = { -1, 1 };
|
||||||
|
ball.velocity.x = BALL_SPEED * speed_choices[GetRandomValue(0, 1)];
|
||||||
|
ball.velocity.y = BALL_SPEED * speed_choices[GetRandomValue(0, 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawCourt(const GameContext& ctx, int screenWidth, int screenHeight) {
|
||||||
|
// Left court field background
|
||||||
|
DrawTexturePro(
|
||||||
|
ctx.courtBackground,
|
||||||
|
Rectangle{ 0.0f, 0.0f, (float)ctx.courtBackground.width, (float)ctx.courtBackground.height },
|
||||||
|
Rectangle{ 20.0f, 20.0f, (float)screenWidth / 2.0f - 70.0f, (float)screenHeight - 40.0f },
|
||||||
|
Vector2{ 0.0f, 0.0f },
|
||||||
|
0.0f,
|
||||||
|
WHITE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Right court field background
|
||||||
|
DrawTexturePro(
|
||||||
|
ctx.courtBackground,
|
||||||
|
Rectangle{ 0.0f, 0.0f, (float)ctx.courtBackground.width, (float)ctx.courtBackground.height },
|
||||||
|
Rectangle{ (float)screenWidth / 2.0f + 50.0f, 20.0f, (float)screenWidth / 2.0f - 70.0f, (float)screenHeight - 40.0f },
|
||||||
|
Vector2{ 0.0f, 0.0f },
|
||||||
|
0.0f,
|
||||||
|
WHITE
|
||||||
|
);
|
||||||
|
|
||||||
|
// Center circle decoration
|
||||||
|
DrawCircle(screenWidth / 2, screenHeight / 2, 50.0f, Color{ 102, 51, 153, 100 });
|
||||||
|
DrawCircleLines(screenWidth / 2, screenHeight / 2, 50.0f, Color{ 50, 25, 75, 250 });
|
||||||
|
|
||||||
|
// Center dotted divider
|
||||||
|
int lineY = 20;
|
||||||
|
while (lineY < screenHeight - 20) {
|
||||||
|
DrawTexture(ctx.lineTexture, screenWidth / 2 - ctx.lineTexture.width / 2, lineY, WHITE);
|
||||||
|
lineY += ctx.lineTexture.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border walls (top, bottom, left, right)
|
||||||
|
DrawTexturePro(ctx.wallsTexture, Rectangle{ 0.0f, 0.0f, (float)ctx.wallsTexture.width, (float)ctx.wallsTexture.height }, Rectangle{ 0.0f, 0.0f, (float)screenWidth, 20.0f }, Vector2{ 0.0f, 0.0f }, 0.0f, WHITE);
|
||||||
|
DrawTexturePro(ctx.wallsTexture, Rectangle{ 0.0f, 0.0f, (float)ctx.wallsTexture.width, (float)ctx.wallsTexture.height }, Rectangle{ 0.0f, (float)screenHeight - 20.0f, (float)screenWidth, 20.0f }, Vector2{ 0.0f, 0.0f }, 0.0f, WHITE);
|
||||||
|
DrawTexturePro(ctx.wallsTexture, Rectangle{ 0.0f, 0.0f, (float)ctx.wallsTexture.width, (float)ctx.wallsTexture.height }, Rectangle{ 0.0f, 0.0f, 20.0f, (float)screenHeight }, Vector2{ 0.0f, 0.0f }, 0.0f, WHITE);
|
||||||
|
DrawTexturePro(ctx.wallsTexture, Rectangle{ 0.0f, 0.0f, (float)ctx.wallsTexture.width, (float)ctx.wallsTexture.height }, Rectangle{ (float)screenWidth - 20.0f, 0.0f, 20.0f, (float)screenHeight }, Vector2{ 0.0f, 0.0f }, 0.0f, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdatePlayingState(GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu) {
|
||||||
|
if (IsKeyPressed(KEY_P)) {
|
||||||
|
ctx.isPaused = !ctx.isPaused;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_SPACE)) {
|
||||||
|
ctx.currentState = GameState::GameOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.isPaused) {
|
||||||
|
ctx.sessionPlayTime += GetFrameTime();
|
||||||
|
|
||||||
|
if (ball.Update() && ctx.config.sfxEnabled) {
|
||||||
|
PlaySound(ctx.wallHitSound);
|
||||||
|
}
|
||||||
|
player.Update();
|
||||||
|
cpu.Update(ball.position.y);
|
||||||
|
|
||||||
|
// Paddle collisions
|
||||||
|
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
|
||||||
|
ball.velocity.x *= -1;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.paddleHitSound);
|
||||||
|
}
|
||||||
|
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
|
||||||
|
ball.velocity.x *= -1;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.paddleHitSound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scoring rules
|
||||||
|
int screen_width = GetScreenWidth();
|
||||||
|
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
|
||||||
|
ctx.score.cpu_score++;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.scoreSound);
|
||||||
|
if (ctx.score.cpu_score >= ctx.config.maxScore) {
|
||||||
|
ctx.currentState = GameState::GameOver;
|
||||||
|
} else {
|
||||||
|
ResetBall(ball);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ball.position.x - ball.radius <= 20.0f) {
|
||||||
|
ctx.score.player_score++;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.scoreSound);
|
||||||
|
if (ctx.score.player_score >= ctx.config.maxScore) {
|
||||||
|
ctx.currentState = GameState::GameOver;
|
||||||
|
} else {
|
||||||
|
ResetBall(ball);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawPlayingState(const GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu, int screenWidth, int screenHeight) {
|
||||||
|
DrawCourt(ctx, screenWidth, screenHeight);
|
||||||
|
|
||||||
|
// Header scores
|
||||||
|
DrawText(TextFormat("%i", ctx.score.cpu_score), screenWidth / 4 - 20, 20, 80, WHITE);
|
||||||
|
DrawText(TextFormat("%i", ctx.score.player_score), 3 * screenWidth / 4 - 20, 20, 80, WHITE);
|
||||||
|
|
||||||
|
// Playtime tracker display
|
||||||
|
int minutes = (int)ctx.sessionPlayTime / 60;
|
||||||
|
int seconds = (int)ctx.sessionPlayTime % 60;
|
||||||
|
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
|
||||||
|
|
||||||
|
DrawRectangle(screenWidth / 2 - timeTextWidth / 2 - 15, screenHeight - 85, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
|
||||||
|
DrawRectangleLines(screenWidth / 2 - timeTextWidth / 2 - 15, screenHeight - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
|
||||||
|
DrawText(TextFormat("%02i:%02i", minutes, seconds), screenWidth / 2 - timeTextWidth / 2, screenHeight - 79, 32, YELLOW);
|
||||||
|
|
||||||
|
ball.Draw();
|
||||||
|
cpu.Draw();
|
||||||
|
player.Draw();
|
||||||
|
|
||||||
|
// Pause interface overlay
|
||||||
|
if (ctx.isPaused) {
|
||||||
|
DrawRectangle(20, 20, screenWidth - 40, screenHeight - 40, Color{ 0, 0, 0, 150 });
|
||||||
|
int pausedTextWidth = MeasureText("PAUSED", 60);
|
||||||
|
DrawText("PAUSED", screenWidth / 2 - pausedTextWidth / 2, screenHeight / 2 - 60, 60, YELLOW);
|
||||||
|
int hintTextWidth = MeasureText("UP/DOWN Arrows to move | SPACE: Force Game Over", 20);
|
||||||
|
DrawText("UP/DOWN Arrows to move | SPACE: Force Game Over", screenWidth / 2 - hintTextWidth / 2, screenHeight / 2 + 20, 20, WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateMultiplayerState(GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu) {
|
||||||
|
if (IsKeyPressed(KEY_P)) {
|
||||||
|
ctx.isPaused = !ctx.isPaused;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_SPACE)) {
|
||||||
|
ctx.currentState = GameState::GameOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.isPaused) {
|
||||||
|
ctx.sessionPlayTime += GetFrameTime();
|
||||||
|
|
||||||
|
if (ball.Update() && ctx.config.sfxEnabled) {
|
||||||
|
PlaySound(ctx.wallHitSound);
|
||||||
|
}
|
||||||
|
player.Update();
|
||||||
|
|
||||||
|
// Player 2 controls (Left Paddle)
|
||||||
|
float dt = GetFrameTime();
|
||||||
|
if (IsKeyDown(KEY_W)) {
|
||||||
|
cpu.position.y -= PLAYER_SPEED * dt;
|
||||||
|
}
|
||||||
|
if (IsKeyDown(KEY_S)) {
|
||||||
|
cpu.position.y += PLAYER_SPEED * dt;
|
||||||
|
}
|
||||||
|
cpu.LimitMovement();
|
||||||
|
cpu.position.x = 20.0f + 10.0f;
|
||||||
|
|
||||||
|
// Paddle collisions
|
||||||
|
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
|
||||||
|
ball.velocity.x *= -1;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.paddleHitSound);
|
||||||
|
}
|
||||||
|
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
|
||||||
|
ball.velocity.x *= -1;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.paddleHitSound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scoring rules
|
||||||
|
int screen_width = GetScreenWidth();
|
||||||
|
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
|
||||||
|
ctx.score.player2_score++;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.scoreSound);
|
||||||
|
if (ctx.score.player2_score >= ctx.config.maxScore) {
|
||||||
|
ctx.currentState = GameState::GameOver;
|
||||||
|
} else {
|
||||||
|
ResetBall(ball);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ball.position.x - ball.radius <= 20.0f) {
|
||||||
|
ctx.score.player_score++;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.scoreSound);
|
||||||
|
if (ctx.score.player_score >= ctx.config.maxScore) {
|
||||||
|
ctx.currentState = GameState::GameOver;
|
||||||
|
} else {
|
||||||
|
ResetBall(ball);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawMultiplayerState(const GameContext& ctx, Ball& ball, Paddle& player, CpuPaddle& cpu, int screenWidth, int screenHeight) {
|
||||||
|
DrawCourt(ctx, screenWidth, screenHeight);
|
||||||
|
|
||||||
|
// Header scores
|
||||||
|
DrawText(TextFormat("%i", ctx.score.player2_score), screenWidth / 4 - 20, 20, 80, WHITE);
|
||||||
|
DrawText(TextFormat("%i", ctx.score.player_score), 3 * screenWidth / 4 - 20, 20, 80, WHITE);
|
||||||
|
|
||||||
|
// Playtime tracker display
|
||||||
|
int minutes = (int)ctx.sessionPlayTime / 60;
|
||||||
|
int seconds = (int)ctx.sessionPlayTime % 60;
|
||||||
|
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
|
||||||
|
|
||||||
|
DrawRectangle(screenWidth / 2 - timeTextWidth / 2 - 15, screenHeight - 85, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
|
||||||
|
DrawRectangleLines(screenWidth / 2 - timeTextWidth / 2 - 15, screenHeight - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
|
||||||
|
DrawText(TextFormat("%02i:%02i", minutes, seconds), screenWidth / 2 - timeTextWidth / 2, screenHeight - 79, 32, YELLOW);
|
||||||
|
|
||||||
|
ball.Draw();
|
||||||
|
cpu.Draw();
|
||||||
|
player.Draw();
|
||||||
|
|
||||||
|
// Pause interface overlay
|
||||||
|
if (ctx.isPaused) {
|
||||||
|
DrawRectangle(20, 20, screenWidth - 40, screenHeight - 40, Color{ 0, 0, 0, 150 });
|
||||||
|
int pausedTextWidth = MeasureText("PAUSED", 60);
|
||||||
|
DrawText("PAUSED", screenWidth / 2 - pausedTextWidth / 2, screenHeight / 2 - 60, 60, YELLOW);
|
||||||
|
int hintTextWidth = MeasureText("P1 (Right): UP/DOWN | P2 (Left): W/S | SPACE: Force Game Over", 20);
|
||||||
|
DrawText("P1 (Right): UP/DOWN | P2 (Left): W/S | SPACE: Force Game Over", screenWidth / 2 - hintTextWidth / 2, screenHeight / 2 + 20, 20, WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateGameOverState(GameContext& ctx) {
|
||||||
|
if (IsKeyPressed(KEY_SPACE)) {
|
||||||
|
ctx.currentState = GameState::MainMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawGameOverState(const GameContext& ctx, int screenWidth, int screenHeight) {
|
||||||
|
int panelWidth = 600;
|
||||||
|
int panelHeight = 400;
|
||||||
|
int panelX = screenWidth / 2 - panelWidth / 2;
|
||||||
|
int panelY = screenHeight / 2 - panelHeight / 2;
|
||||||
|
|
||||||
|
DrawRectangle(panelX, panelY, panelWidth, panelHeight, Color{ 15, 15, 15, 230 });
|
||||||
|
DrawRectangleLines(panelX, panelY, panelWidth, panelHeight, Color{ 120, 120, 120, 255 });
|
||||||
|
|
||||||
|
int gameOverWidth = MeasureText("GAME OVER", 60);
|
||||||
|
DrawText("GAME OVER", screenWidth / 2 - gameOverWidth / 2, panelY + 40, 60, RED);
|
||||||
|
|
||||||
|
std::string winnerText = "GAME ENDED";
|
||||||
|
Color winnerColor = YELLOW;
|
||||||
|
std::string score1Str, score2Str;
|
||||||
|
|
||||||
|
if (ctx.isMultiplayer) {
|
||||||
|
if (ctx.score.player_score > ctx.score.player2_score) {
|
||||||
|
winnerText = "PLAYER 1 WINS!";
|
||||||
|
winnerColor = Color{ 38, 185, 154, 255 };
|
||||||
|
}
|
||||||
|
else if (ctx.score.player2_score > ctx.score.player_score) {
|
||||||
|
winnerText = "PLAYER 2 WINS!";
|
||||||
|
winnerColor = RED;
|
||||||
|
}
|
||||||
|
score1Str = "Player 1 Score: " + std::to_string(ctx.score.player_score);
|
||||||
|
score2Str = "Player 2 Score: " + std::to_string(ctx.score.player2_score);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ctx.score.player_score > ctx.score.cpu_score) {
|
||||||
|
winnerText = "YOU WIN!";
|
||||||
|
winnerColor = Color{ 38, 185, 154, 255 };
|
||||||
|
}
|
||||||
|
else if (ctx.score.cpu_score > ctx.score.player_score) {
|
||||||
|
winnerText = "CPU WINS!";
|
||||||
|
winnerColor = RED;
|
||||||
|
}
|
||||||
|
score1Str = "Player Score: " + std::to_string(ctx.score.player_score);
|
||||||
|
score2Str = "CPU Score: " + std::to_string(ctx.score.cpu_score);
|
||||||
|
}
|
||||||
|
|
||||||
|
int winnerWidth = MeasureText(winnerText.c_str(), 40);
|
||||||
|
DrawText(winnerText.c_str(), screenWidth / 2 - winnerWidth / 2, panelY + 120, 40, winnerColor);
|
||||||
|
|
||||||
|
int playerTextWidth = MeasureText(score1Str.c_str(), 30);
|
||||||
|
int cpuTextWidth = MeasureText(score2Str.c_str(), 30);
|
||||||
|
DrawText(score1Str.c_str(), screenWidth / 2 - playerTextWidth / 2, panelY + 200, 30, WHITE);
|
||||||
|
DrawText(score2Str.c_str(), screenWidth / 2 - cpuTextWidth / 2, panelY + 250, 30, WHITE);
|
||||||
|
|
||||||
|
int minutes = (int)ctx.sessionPlayTime / 60;
|
||||||
|
int seconds = (int)ctx.sessionPlayTime % 60;
|
||||||
|
std::string timeStr = "Playtime: " + std::to_string(minutes) + "m " + std::to_string(seconds) + "s";
|
||||||
|
int timeTextWidth = MeasureText(timeStr.c_str(), 20);
|
||||||
|
DrawText(timeStr.c_str(), screenWidth / 2 - timeTextWidth / 2, panelY + 300, 20, LIGHTGRAY);
|
||||||
|
|
||||||
|
int instWidth = MeasureText("Press SPACEBAR to return to Main Menu", 20);
|
||||||
|
DrawText("Press SPACEBAR to return to Main Menu", screenWidth / 2 - instWidth / 2, panelY + 340, 20, YELLOW);
|
||||||
|
}
|
||||||
|
|||||||
+97
-810
@@ -2,77 +2,41 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
|
|
||||||
// Colors
|
// Global colors
|
||||||
Color Green = Color{ 38, 185, 154, 255 };
|
Color Green = Color{ 38, 185, 154, 255 };
|
||||||
Color Dark_Green = Color{ 20, 160, 133, 255 };
|
Color Dark_Green = Color{ 20, 160, 133, 255 };
|
||||||
Color Light_Green = Color{ 129, 204, 184, 255 };
|
Color Light_Green = Color{ 129, 204, 184, 255 };
|
||||||
Color Yellow = Color{ 243, 213, 91, 255 };
|
Color Yellow = Color{ 243, 213, 91, 255 };
|
||||||
|
|
||||||
struct ScoreBoard
|
|
||||||
{
|
|
||||||
int player_score = 0;
|
|
||||||
int player2_score = 0;
|
|
||||||
int cpu_score = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void ApplyResolution(int width, int height) {
|
|
||||||
int monitor = GetCurrentMonitor();
|
|
||||||
int monitorWidth = GetMonitorWidth(monitor);
|
|
||||||
int monitorHeight = GetMonitorHeight(monitor);
|
|
||||||
|
|
||||||
if (width >= monitorWidth || height >= monitorHeight) {
|
|
||||||
SetWindowState(FLAG_WINDOW_MAXIMIZED | FLAG_WINDOW_RESIZABLE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ClearWindowState(FLAG_WINDOW_MAXIMIZED);
|
|
||||||
SetWindowSize(width, height);
|
|
||||||
// Center the window on the active monitor
|
|
||||||
SetWindowPosition(monitorWidth / 2 - width / 2, monitorHeight / 2 - height / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyFramerate(int option) {
|
|
||||||
if (option == 0) {
|
|
||||||
ClearWindowState(FLAG_VSYNC_HINT);
|
|
||||||
SetTargetFPS(60);
|
|
||||||
}
|
|
||||||
else if (option == 1) {
|
|
||||||
ClearWindowState(FLAG_VSYNC_HINT);
|
|
||||||
SetTargetFPS(144);
|
|
||||||
}
|
|
||||||
else if (option == 2) {
|
|
||||||
SetWindowState(FLAG_VSYNC_HINT);
|
|
||||||
SetTargetFPS(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to reset the ball
|
|
||||||
void ResetBall(Ball& ball) {
|
|
||||||
ball.position.x = GetScreenWidth() / 2.0f;
|
|
||||||
ball.position.y = GetScreenHeight() / 2.0f;
|
|
||||||
|
|
||||||
int speed_choices[2] = { -1, 1 };
|
|
||||||
ball.velocity.x = BALL_SPEED * speed_choices[GetRandomValue(0, 1)];
|
|
||||||
ball.velocity.y = BALL_SPEED * speed_choices[GetRandomValue(0, 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
std::cout << "Starting the game" << std::endl;
|
std::cout << "Starting game session" << std::endl;
|
||||||
int screen_width = 1280;
|
int screen_width = 1280;
|
||||||
int screen_height = 800;
|
int screen_height = 800;
|
||||||
|
|
||||||
InitWindow(screen_width, screen_height, "Pong Reloaded");
|
InitWindow(screen_width, screen_height, "Pong Reloaded");
|
||||||
SetTargetFPS(60);
|
SetTargetFPS(60);
|
||||||
InitAudioDevice();
|
InitAudioDevice();
|
||||||
|
|
||||||
// --- Instantiate Objects using the new Constructors ---
|
// App state context
|
||||||
|
GameContext ctx;
|
||||||
|
|
||||||
|
// Asset textures loading
|
||||||
|
ctx.courtBackground = LoadTexture("assets/textures/spaces/basic_space.png");
|
||||||
|
ctx.wallsTexture = LoadTexture("assets/textures/spaces/walls.png");
|
||||||
|
ctx.lineTexture = LoadTexture("assets/textures/hud/line.png");
|
||||||
|
|
||||||
|
// Audio assets loading
|
||||||
|
ctx.paddleHitSound = LoadSound("assets/audio/paddle_hit.ogg");
|
||||||
|
ctx.wallHitSound = LoadSound("assets/audio/wall_hit.ogg");
|
||||||
|
ctx.scoreSound = LoadSound("assets/audio/score.ogg");
|
||||||
|
|
||||||
|
// Entities instantiation
|
||||||
Ball ball(
|
Ball ball(
|
||||||
Vector2{ screen_width / 2.0f, screen_height / 2.0f },
|
Vector2{ screen_width / 2.0f, screen_height / 2.0f },
|
||||||
Yellow, 20.0f,
|
Yellow, 20.0f,
|
||||||
"assets/textures/ball/basic_ball_5.png"
|
"assets/textures/ball/basic_ball_5.png"
|
||||||
);
|
);
|
||||||
ball.velocity = Vector2{ BALL_SPEED, BALL_SPEED };
|
ResetBall(ball);
|
||||||
|
|
||||||
Paddle player(
|
Paddle player(
|
||||||
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
|
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
|
||||||
@@ -87,798 +51,122 @@ int main() {
|
|||||||
"assets/textures/paddles/basic_paddle_2.png"
|
"assets/textures/paddles/basic_paddle_2.png"
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- Setup Menu and Game State ---
|
// Menu instantiation
|
||||||
|
|
||||||
GameState currentState = GameState::MainMenu;
|
|
||||||
float sessionPlayTime = 0.0f;
|
|
||||||
bool isPaused = false;
|
|
||||||
bool shouldQuit = false;
|
|
||||||
bool isMultiplayer = false;
|
|
||||||
bool p1Ready = false;
|
|
||||||
bool p2Ready = false;
|
|
||||||
|
|
||||||
int resolutionOption = 0; // 0 = 1280x800, 1 = 1600x900, 2 = 1920x1080
|
|
||||||
int framerateOption = 0; // 0 = 60 FPS, 1 = 144 FPS, 2 = VSync
|
|
||||||
bool isFullscreen = false;
|
|
||||||
int maxScoreOption = 0; // 0 = 5, 1 = 11, 2 = 15, 3 = 21
|
|
||||||
int maxScore = 5;
|
|
||||||
bool sfxEnabled = true;
|
|
||||||
int selectedSettingLine = 0; // 0 = Resolution, 1 = Framerate, 2 = Screen Mode, 3 = Score Limit, 4 = Sound, 5 = Back
|
|
||||||
|
|
||||||
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
|
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
|
||||||
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
|
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
|
||||||
ScoreBoard score;
|
|
||||||
|
|
||||||
// --- Load Background and Wall Textures ---
|
// Main loop
|
||||||
Texture2D courtBackground = LoadTexture("assets/textures/spaces/basic_space.png");
|
while (WindowShouldClose() == false && !ctx.shouldQuit) {
|
||||||
Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png");
|
|
||||||
Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png");
|
|
||||||
|
|
||||||
// --- Load Sound Effects ---
|
|
||||||
Sound paddleHitSound = LoadSound("assets/audio/paddle_hit.ogg");
|
|
||||||
Sound wallHitSound = LoadSound("assets/audio/wall_hit.ogg");
|
|
||||||
Sound scoreSound = LoadSound("assets/audio/score.ogg");
|
|
||||||
|
|
||||||
// --- Main Game Loop ---
|
|
||||||
|
|
||||||
while (WindowShouldClose() == false && !shouldQuit) {
|
|
||||||
screen_width = GetScreenWidth();
|
screen_width = GetScreenWidth();
|
||||||
screen_height = GetScreenHeight();
|
screen_height = GetScreenHeight();
|
||||||
|
|
||||||
BeginDrawing();
|
// Update loop
|
||||||
ClearBackground(Dark_Green);
|
switch (ctx.currentState) {
|
||||||
|
|
||||||
switch (currentState) {
|
|
||||||
case GameState::MainMenu:
|
case GameState::MainMenu:
|
||||||
{
|
{
|
||||||
int selected = mainMenu.Update();
|
int selected = mainMenu.Update();
|
||||||
if (selected == 0) {
|
if (selected == 0) {
|
||||||
isMultiplayer = false;
|
ctx.isMultiplayer = false;
|
||||||
currentState = GameState::DifficultySelect;
|
ctx.currentState = GameState::DifficultySelect;
|
||||||
}
|
}
|
||||||
else if (selected == 1) {
|
else if (selected == 1) {
|
||||||
isMultiplayer = true;
|
ctx.isMultiplayer = true;
|
||||||
score.player_score = 0;
|
ctx.score.player_score = 0;
|
||||||
score.player2_score = 0;
|
ctx.score.player2_score = 0;
|
||||||
score.cpu_score = 0;
|
ctx.score.cpu_score = 0;
|
||||||
sessionPlayTime = 0.0f;
|
ctx.sessionPlayTime = 0.0f;
|
||||||
isPaused = false;
|
ctx.isPaused = false;
|
||||||
p1Ready = false;
|
ctx.p1Ready = false;
|
||||||
p2Ready = false;
|
ctx.p2Ready = false;
|
||||||
ResetBall(ball);
|
ResetBall(ball);
|
||||||
currentState = GameState::MultiplayerLobby;
|
ctx.currentState = GameState::MultiplayerLobby;
|
||||||
}
|
}
|
||||||
else if (selected == 2) {
|
else if (selected == 2) {
|
||||||
currentState = GameState::Settings;
|
ctx.currentState = GameState::Settings;
|
||||||
}
|
}
|
||||||
else if (selected == 3) {
|
else if (selected == 3) {
|
||||||
shouldQuit = true;
|
ctx.shouldQuit = true;
|
||||||
}
|
}
|
||||||
mainMenu.Draw();
|
break;
|
||||||
|
}
|
||||||
|
case GameState::DifficultySelect:
|
||||||
|
{
|
||||||
|
int selected = difficultyMenu.Update();
|
||||||
|
if (selected >= 0 && selected <= 2) {
|
||||||
|
if (selected == 0) cpu.SetDifficulty(Difficulty::Easy);
|
||||||
|
else if (selected == 1) cpu.SetDifficulty(Difficulty::Normal);
|
||||||
|
else if (selected == 2) cpu.SetDifficulty(Difficulty::Hard);
|
||||||
|
|
||||||
// Controls helper
|
ctx.score.player_score = 0;
|
||||||
|
ctx.score.player2_score = 0;
|
||||||
|
ctx.score.cpu_score = 0;
|
||||||
|
ctx.sessionPlayTime = 0.0f;
|
||||||
|
ctx.isPaused = false;
|
||||||
|
ResetBall(ball);
|
||||||
|
ctx.currentState = GameState::Playing;
|
||||||
|
}
|
||||||
|
else if (selected == 3) {
|
||||||
|
ctx.currentState = GameState::MainMenu;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameState::MultiplayerLobby:
|
||||||
|
UpdateLobbyState(ctx);
|
||||||
|
break;
|
||||||
|
case GameState::Multiplayer:
|
||||||
|
UpdateMultiplayerState(ctx, ball, player, cpu);
|
||||||
|
break;
|
||||||
|
case GameState::Settings:
|
||||||
|
UpdateSettingsState(ctx);
|
||||||
|
break;
|
||||||
|
case GameState::Playing:
|
||||||
|
UpdatePlayingState(ctx, ball, player, cpu);
|
||||||
|
break;
|
||||||
|
case GameState::GameOver:
|
||||||
|
UpdateGameOverState(ctx);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw loop
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground(Dark_Green);
|
||||||
|
|
||||||
|
switch (ctx.currentState) {
|
||||||
|
case GameState::MainMenu:
|
||||||
|
{
|
||||||
|
mainMenu.Draw();
|
||||||
int menuHintWidth = MeasureText("Use UP/DOWN to navigate | ENTER to select", 20);
|
int menuHintWidth = MeasureText("Use UP/DOWN to navigate | ENTER to select", 20);
|
||||||
DrawText("Use UP/DOWN to navigate | ENTER to select",
|
DrawText("Use UP/DOWN to navigate | ENTER to select",
|
||||||
screen_width / 2 - menuHintWidth / 2,
|
screen_width / 2 - menuHintWidth / 2,
|
||||||
screen_height - 80, 20, WHITE);
|
screen_height - 80, 20, WHITE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case GameState::DifficultySelect:
|
case GameState::DifficultySelect:
|
||||||
{
|
{
|
||||||
int selected = difficultyMenu.Update();
|
|
||||||
if (selected >= 0 && selected <= 2) {
|
|
||||||
if (selected == 0) {
|
|
||||||
cpu.SetDifficulty(Difficulty::Easy);
|
|
||||||
}
|
|
||||||
else if (selected == 1) {
|
|
||||||
cpu.SetDifficulty(Difficulty::Normal);
|
|
||||||
}
|
|
||||||
else if (selected == 2) {
|
|
||||||
cpu.SetDifficulty(Difficulty::Hard);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset game session stats
|
|
||||||
score.player_score = 0;
|
|
||||||
score.player2_score = 0;
|
|
||||||
score.cpu_score = 0;
|
|
||||||
sessionPlayTime = 0.0f;
|
|
||||||
isPaused = false;
|
|
||||||
ResetBall(ball);
|
|
||||||
currentState = GameState::Playing;
|
|
||||||
}
|
|
||||||
else if (selected == 3) {
|
|
||||||
currentState = GameState::MainMenu;
|
|
||||||
}
|
|
||||||
difficultyMenu.Draw();
|
difficultyMenu.Draw();
|
||||||
|
int gameHintWidth = MeasureText("Player: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over", 20);
|
||||||
// Controls helper
|
|
||||||
int gameHintWidth = MeasureText("Game: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over", 20);
|
|
||||||
DrawText("Player: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over",
|
DrawText("Player: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over",
|
||||||
screen_width / 2 - gameHintWidth / 2,
|
screen_width / 2 - gameHintWidth / 2,
|
||||||
screen_height - 50, 20, WHITE);
|
screen_height - 50, 20, WHITE);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case GameState::MultiplayerLobby:
|
case GameState::MultiplayerLobby:
|
||||||
{
|
DrawLobbyState(ctx, screen_width, screen_height);
|
||||||
// Input checks
|
|
||||||
if (IsKeyPressed(KEY_W)) {
|
|
||||||
p1Ready = !p1Ready;
|
|
||||||
if (sfxEnabled) PlaySound(paddleHitSound);
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_UP)) {
|
|
||||||
p2Ready = !p2Ready;
|
|
||||||
if (sfxEnabled) PlaySound(paddleHitSound);
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_B)) {
|
|
||||||
currentState = GameState::MainMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p1Ready && p2Ready) {
|
|
||||||
currentState = GameState::Multiplayer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Draw Court Background Behind Panel ---
|
|
||||||
// Left Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ (float)screen_width / 2.0f + 50.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Center Circle
|
|
||||||
DrawCircle(screen_width / 2, screen_height / 2, 50.0f, Color{ 102, 51, 153, 100 });
|
|
||||||
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0f, Color{ 50, 25, 75, 250 });
|
|
||||||
|
|
||||||
// Tiled Center Dashed Line (line.png)
|
|
||||||
int lineY = 20;
|
|
||||||
while (lineY < screen_height - 20) {
|
|
||||||
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
|
|
||||||
lineY += lineTexture.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bottom Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, (float)screen_height - 20.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Left Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ (float)screen_width - 20.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Draw Lobby Panel
|
|
||||||
int panelWidth = 800;
|
|
||||||
int panelHeight = 450;
|
|
||||||
int panelX = screen_width / 2 - panelWidth / 2;
|
|
||||||
int panelY = screen_height / 2 - panelHeight / 2;
|
|
||||||
|
|
||||||
// Semi-transparent background panel
|
|
||||||
DrawRectangle(panelX, panelY, panelWidth, panelHeight, Color{ 15, 15, 15, 230 });
|
|
||||||
DrawRectangleLines(panelX, panelY, panelWidth, panelHeight, Color{ 120, 120, 120, 255 });
|
|
||||||
|
|
||||||
// Title
|
|
||||||
int titleWidth = MeasureText("MULTIPLAYER LOBBY", 40);
|
|
||||||
DrawText("MULTIPLAYER LOBBY", screen_width / 2 - titleWidth / 2, panelY + 30, 40, WHITE);
|
|
||||||
|
|
||||||
// Player 1 Details (Left Paddle controlled by W/S keys)
|
|
||||||
int p1X = panelX + 50;
|
|
||||||
int p1Y = panelY + 110;
|
|
||||||
DrawText("PLAYER 1 (LEFT)", p1X, p1Y, 24, LIGHTGRAY);
|
|
||||||
DrawText("Controls: W / S", p1X, p1Y + 45, 20, WHITE);
|
|
||||||
DrawText("Press 'W' to toggle ready", p1X, p1Y + 75, 18, GRAY);
|
|
||||||
|
|
||||||
if (p1Ready) {
|
|
||||||
DrawRectangle(p1X, p1Y + 115, 220, 50, GREEN);
|
|
||||||
DrawText("READY", p1X + 110 - MeasureText("READY", 20)/2, p1Y + 130, 20, BLACK);
|
|
||||||
} else {
|
|
||||||
DrawRectangle(p1X, p1Y + 115, 220, 50, RED);
|
|
||||||
DrawText("NOT READY", p1X + 110 - MeasureText("NOT READY", 20)/2, p1Y + 130, 20, WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player 2 Details (Right Paddle controlled by UP/DOWN arrow keys)
|
|
||||||
int p2X = panelX + panelWidth - 270;
|
|
||||||
int p2Y = panelY + 110;
|
|
||||||
DrawText("PLAYER 2 (RIGHT)", p2X, p2Y, 24, LIGHTGRAY);
|
|
||||||
DrawText("Controls: UP / DOWN", p2X, p2Y + 45, 20, WHITE);
|
|
||||||
DrawText("Press 'UP' to toggle ready", p2X, p2Y + 75, 18, GRAY);
|
|
||||||
|
|
||||||
if (p2Ready) {
|
|
||||||
DrawRectangle(p2X, p2Y + 115, 220, 50, GREEN);
|
|
||||||
DrawText("READY", p2X + 110 - MeasureText("READY", 20)/2, p2Y + 130, 20, BLACK);
|
|
||||||
} else {
|
|
||||||
DrawRectangle(p2X, p2Y + 115, 220, 50, RED);
|
|
||||||
DrawText("NOT READY", p2X + 110 - MeasureText("NOT READY", 20)/2, p2Y + 130, 20, WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lobby Status Message
|
|
||||||
if (p1Ready && p2Ready) {
|
|
||||||
int startTextWidth = MeasureText("BOTH READY! STARTING GAME...", 24);
|
|
||||||
DrawText("BOTH READY! STARTING GAME...", screen_width / 2 - startTextWidth / 2, panelY + 310, 24, YELLOW);
|
|
||||||
} else {
|
|
||||||
int waitTextWidth = MeasureText("Waiting for both players to be ready...", 20);
|
|
||||||
DrawText("Waiting for both players to be ready...", screen_width / 2 - waitTextWidth / 2, panelY + 315, 20, LIGHTGRAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back Instruction
|
|
||||||
int backTextWidth = MeasureText("Press 'B' to return to Main Menu | P: Pause | SPACE: Force Game Over", 20);
|
|
||||||
DrawText("Press 'B' to return to Main Menu | P: Pause | SPACE: Force Game Over", screen_width / 2 - backTextWidth / 2, panelY + 385, 20, YELLOW);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case GameState::Multiplayer:
|
case GameState::Multiplayer:
|
||||||
{
|
DrawMultiplayerState(ctx, ball, player, cpu, screen_width, screen_height);
|
||||||
// Toggle pause state with 'P' key
|
|
||||||
if (IsKeyPressed(KEY_P)) {
|
|
||||||
isPaused = !isPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force GameOver with SPACEBAR key
|
|
||||||
if (IsKeyPressed(KEY_SPACE)) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPaused) {
|
|
||||||
sessionPlayTime += GetFrameTime();
|
|
||||||
|
|
||||||
if (ball.Update() && sfxEnabled) {
|
|
||||||
PlaySound(wallHitSound);
|
|
||||||
}
|
|
||||||
player.Update();
|
|
||||||
|
|
||||||
// Player 2 controls (Left Paddle - Player 2 paddle)
|
|
||||||
float dt = GetFrameTime();
|
|
||||||
if (IsKeyDown(KEY_W)) {
|
|
||||||
cpu.position.y -= PLAYER_SPEED * dt;
|
|
||||||
}
|
|
||||||
if (IsKeyDown(KEY_S)) {
|
|
||||||
cpu.position.y += PLAYER_SPEED * dt;
|
|
||||||
}
|
|
||||||
cpu.LimitMovement();
|
|
||||||
cpu.position.x = 20.0f + 10.0f; // Keep on left track
|
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
|
|
||||||
ball.velocity.x *= -1;
|
|
||||||
if (sfxEnabled) PlaySound(paddleHitSound);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
|
|
||||||
ball.velocity.x *= -1;
|
|
||||||
if (sfxEnabled) PlaySound(paddleHitSound);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
|
|
||||||
score.player2_score++;
|
|
||||||
if (sfxEnabled) PlaySound(scoreSound);
|
|
||||||
if (score.player2_score >= maxScore) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResetBall(ball);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ball.position.x - ball.radius <= 20.0f) {
|
|
||||||
score.player_score++;
|
|
||||||
if (sfxEnabled) PlaySound(scoreSound);
|
|
||||||
if (score.player_score >= maxScore) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResetBall(ball);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Draw Textured Court Background ---
|
|
||||||
// Left Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ (float)screen_width / 2.0f + 50.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Center Circle
|
|
||||||
DrawCircle(screen_width / 2, screen_height / 2, 50.0f, Color{ 102, 51, 153, 100 });
|
|
||||||
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0f, Color{ 50, 25, 75, 250 });
|
|
||||||
|
|
||||||
// Tiled Center Dashed Line (line.png)
|
|
||||||
int lineY = 20;
|
|
||||||
while (lineY < screen_height - 20) {
|
|
||||||
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
|
|
||||||
lineY += lineTexture.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bottom Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, (float)screen_height - 20.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Left Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ (float)screen_width - 20.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scores: Left shows Player 2 score, Right shows Player 1 score
|
|
||||||
DrawText(TextFormat("%i", score.player2_score), screen_width / 4 - 20, 20, 80, WHITE);
|
|
||||||
DrawText(TextFormat("%i", score.player_score), 3 * screen_width / 4 - 20, 20, 80, WHITE);
|
|
||||||
|
|
||||||
// Draw Playtime Counter
|
|
||||||
int minutes = (int)sessionPlayTime / 60;
|
|
||||||
int seconds = (int)sessionPlayTime % 60;
|
|
||||||
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
|
|
||||||
|
|
||||||
// Draw background box to block out the center line and increase contrast
|
|
||||||
DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
|
|
||||||
DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
|
|
||||||
DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, screen_height - 79, 32, YELLOW);
|
|
||||||
|
|
||||||
ball.Draw();
|
|
||||||
cpu.Draw(); // Left paddle (reused cpu object)
|
|
||||||
player.Draw(); // Right paddle
|
|
||||||
|
|
||||||
// Draw Pause Overlay and Text
|
|
||||||
if (isPaused) {
|
|
||||||
DrawRectangle(20, 20, screen_width - 40, screen_height - 40, Color{ 0, 0, 0, 150 });
|
|
||||||
|
|
||||||
int pausedTextWidth = MeasureText("PAUSED", 60);
|
|
||||||
DrawText("PAUSED", screen_width / 2 - pausedTextWidth / 2, screen_height / 2 - 60, 60, YELLOW);
|
|
||||||
|
|
||||||
int hintTextWidth = MeasureText("P1 (Right): UP/DOWN | P2 (Left): W/S | SPACE: Force Game Over", 20);
|
|
||||||
DrawText("P1 (Right): UP/DOWN | P2 (Left): W/S | SPACE: Force Game Over",
|
|
||||||
screen_width / 2 - hintTextWidth / 2, screen_height / 2 + 20, 20, WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case GameState::Settings:
|
case GameState::Settings:
|
||||||
{
|
DrawSettingsState(ctx, screen_width, screen_height);
|
||||||
// Navigation
|
|
||||||
if (IsKeyPressed(KEY_UP)) {
|
|
||||||
selectedSettingLine--;
|
|
||||||
if (selectedSettingLine < 0) selectedSettingLine = 5;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_DOWN)) {
|
|
||||||
selectedSettingLine++;
|
|
||||||
if (selectedSettingLine > 5) selectedSettingLine = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option selection (LEFT/RIGHT or ENTER)
|
|
||||||
if (selectedSettingLine == 0) {
|
|
||||||
// Resolution
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
|
|
||||||
resolutionOption = (resolutionOption + 1) % 3;
|
|
||||||
if (resolutionOption == 0) ApplyResolution(1280, 800);
|
|
||||||
else if (resolutionOption == 1) ApplyResolution(1600, 900);
|
|
||||||
else if (resolutionOption == 2) ApplyResolution(1920, 1080);
|
|
||||||
}
|
|
||||||
else if (IsKeyPressed(KEY_LEFT)) {
|
|
||||||
resolutionOption = (resolutionOption - 1 + 3) % 3;
|
|
||||||
if (resolutionOption == 0) ApplyResolution(1280, 800);
|
|
||||||
else if (resolutionOption == 1) ApplyResolution(1600, 900);
|
|
||||||
else if (resolutionOption == 2) ApplyResolution(1920, 1080);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (selectedSettingLine == 1) {
|
|
||||||
// Framerate
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
|
|
||||||
framerateOption = (framerateOption + 1) % 3;
|
|
||||||
ApplyFramerate(framerateOption);
|
|
||||||
}
|
|
||||||
else if (IsKeyPressed(KEY_LEFT)) {
|
|
||||||
framerateOption = (framerateOption - 1 + 3) % 3;
|
|
||||||
ApplyFramerate(framerateOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (selectedSettingLine == 2) {
|
|
||||||
// Screen Mode
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) {
|
|
||||||
ToggleFullscreen();
|
|
||||||
isFullscreen = !isFullscreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (selectedSettingLine == 3) {
|
|
||||||
// Score Limit
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
|
|
||||||
maxScoreOption = (maxScoreOption + 1) % 4;
|
|
||||||
}
|
|
||||||
else if (IsKeyPressed(KEY_LEFT)) {
|
|
||||||
maxScoreOption = (maxScoreOption - 1 + 4) % 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxScoreOption == 0) maxScore = 5;
|
|
||||||
else if (maxScoreOption == 1) maxScore = 11;
|
|
||||||
else if (maxScoreOption == 2) maxScore = 15;
|
|
||||||
else if (maxScoreOption == 3) maxScore = 21;
|
|
||||||
}
|
|
||||||
else if (selectedSettingLine == 4) {
|
|
||||||
// Sound Effects
|
|
||||||
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) {
|
|
||||||
sfxEnabled = !sfxEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (selectedSettingLine == 5) {
|
|
||||||
// Back
|
|
||||||
if (IsKeyPressed(KEY_ENTER)) {
|
|
||||||
currentState = GameState::MainMenu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title
|
|
||||||
int titleWidth = MeasureText("SETTINGS", 60);
|
|
||||||
DrawText("SETTINGS", screen_width / 2 - titleWidth / 2, screen_height / 4 - 40, 60, WHITE);
|
|
||||||
|
|
||||||
// Construct resolution string
|
|
||||||
std::string resStr = "Resolution: ";
|
|
||||||
if (resolutionOption == 0) resStr += "1280x800";
|
|
||||||
else if (resolutionOption == 1) resStr += "1600x900";
|
|
||||||
else if (resolutionOption == 2) resStr += "1920x1080 (Windowed)";
|
|
||||||
|
|
||||||
// Construct framerate string
|
|
||||||
std::string fpsStr = "Framerate: ";
|
|
||||||
if (framerateOption == 0) fpsStr += "60 FPS";
|
|
||||||
else if (framerateOption == 1) fpsStr += "144 FPS";
|
|
||||||
else if (framerateOption == 2) fpsStr += "VSync";
|
|
||||||
|
|
||||||
// Construct screen mode string
|
|
||||||
std::string modeStr = "Screen Mode: ";
|
|
||||||
if (isFullscreen) modeStr += "Fullscreen";
|
|
||||||
else modeStr += "Windowed";
|
|
||||||
|
|
||||||
// Construct score limit string
|
|
||||||
std::string scoreLimitStr = "Score Limit: " + std::to_string(maxScore);
|
|
||||||
|
|
||||||
// Construct sound effects string
|
|
||||||
std::string sfxStr = "Sound Effects: ";
|
|
||||||
if (sfxEnabled) sfxStr += "ON";
|
|
||||||
else sfxStr += "OFF";
|
|
||||||
|
|
||||||
// Draw option lines
|
|
||||||
Color resColor = (selectedSettingLine == 0) ? YELLOW : WHITE;
|
|
||||||
Color fpsColor = (selectedSettingLine == 1) ? YELLOW : WHITE;
|
|
||||||
Color modeColor = (selectedSettingLine == 2) ? YELLOW : WHITE;
|
|
||||||
Color scoreColor = (selectedSettingLine == 3) ? YELLOW : WHITE;
|
|
||||||
Color sfxColor = (selectedSettingLine == 4) ? YELLOW : WHITE;
|
|
||||||
Color backColor = (selectedSettingLine == 5) ? YELLOW : WHITE;
|
|
||||||
|
|
||||||
int startY = screen_height / 2 - 90;
|
|
||||||
DrawText(resStr.c_str(), screen_width / 2 - MeasureText(resStr.c_str(), 30) / 2, startY, 30, resColor);
|
|
||||||
DrawText(fpsStr.c_str(), screen_width / 2 - MeasureText(fpsStr.c_str(), 30) / 2, startY + 45, 30, fpsColor);
|
|
||||||
DrawText(modeStr.c_str(), screen_width / 2 - MeasureText(modeStr.c_str(), 30) / 2, startY + 90, 30, modeColor);
|
|
||||||
DrawText(scoreLimitStr.c_str(), screen_width / 2 - MeasureText(scoreLimitStr.c_str(), 30) / 2, startY + 135, 30, scoreColor);
|
|
||||||
DrawText(sfxStr.c_str(), screen_width / 2 - MeasureText(sfxStr.c_str(), 30) / 2, startY + 180, 30, sfxColor);
|
|
||||||
DrawText("Back", screen_width / 2 - MeasureText("Back", 30) / 2, startY + 225, 30, backColor);
|
|
||||||
|
|
||||||
// Controls helper
|
|
||||||
int settingsHintWidth = MeasureText("UP/DOWN to navigate | LEFT/RIGHT to change settings | ENTER to select", 20);
|
|
||||||
DrawText("UP/DOWN to navigate | LEFT/RIGHT to change settings | ENTER to select",
|
|
||||||
screen_width / 2 - settingsHintWidth / 2,
|
|
||||||
screen_height - 50, 20, WHITE);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case GameState::Playing:
|
case GameState::Playing:
|
||||||
{
|
DrawPlayingState(ctx, ball, player, cpu, screen_width, screen_height);
|
||||||
// Toggle pause state with 'P' key
|
|
||||||
if (IsKeyPressed(KEY_P)) {
|
|
||||||
isPaused = !isPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force GameOver with SPACEBAR key
|
|
||||||
if (IsKeyPressed(KEY_SPACE)) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPaused) {
|
|
||||||
sessionPlayTime += GetFrameTime();
|
|
||||||
|
|
||||||
if (ball.Update() && sfxEnabled) {
|
|
||||||
PlaySound(wallHitSound);
|
|
||||||
}
|
|
||||||
player.Update();
|
|
||||||
cpu.Update(ball.position.y);
|
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
|
|
||||||
ball.velocity.x *= -1;
|
|
||||||
if (sfxEnabled) PlaySound(paddleHitSound);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
|
|
||||||
ball.velocity.x *= -1;
|
|
||||||
if (sfxEnabled) PlaySound(paddleHitSound);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
|
|
||||||
score.cpu_score++;
|
|
||||||
if (sfxEnabled) PlaySound(scoreSound);
|
|
||||||
if (score.cpu_score >= maxScore) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResetBall(ball);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ball.position.x - ball.radius <= 20.0f) {
|
|
||||||
score.player_score++;
|
|
||||||
if (sfxEnabled) PlaySound(scoreSound);
|
|
||||||
if (score.player_score >= maxScore) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResetBall(ball);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Draw Textured Court Background ---
|
|
||||||
// Left Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ (float)screen_width / 2.0f + 50.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Center strip (Dark Green background area) - already cleared by ClearBackground
|
|
||||||
|
|
||||||
// Center Circle
|
|
||||||
DrawCircle(screen_width / 2, screen_height / 2, 50.0f, Color{ 102, 51, 153, 100 });
|
|
||||||
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0f, Color{ 50, 25, 75, 250 });
|
|
||||||
|
|
||||||
// Tiled Center Dashed Line (line.png)
|
|
||||||
int lineY = 20;
|
|
||||||
while (lineY < screen_height - 20) {
|
|
||||||
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
|
|
||||||
lineY += lineTexture.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bottom Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, (float)screen_height - 20.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Left Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ (float)screen_width - 20.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawText(TextFormat("%i", score.cpu_score), screen_width / 4 - 20, 20, 80, WHITE);
|
|
||||||
DrawText(TextFormat("%i", score.player_score), 3 * screen_width / 4 - 20, 20, 80, WHITE);
|
|
||||||
|
|
||||||
// Draw Playtime Counter
|
|
||||||
int minutes = (int)sessionPlayTime / 60;
|
|
||||||
int seconds = (int)sessionPlayTime % 60;
|
|
||||||
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
|
|
||||||
|
|
||||||
// Draw background box to block out the center line and increase contrast
|
|
||||||
DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
|
|
||||||
DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
|
|
||||||
DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, screen_height - 79, 32, YELLOW);
|
|
||||||
|
|
||||||
ball.Draw();
|
|
||||||
cpu.Draw();
|
|
||||||
player.Draw();
|
|
||||||
|
|
||||||
// Draw Pause Overlay and Text
|
|
||||||
if (isPaused) {
|
|
||||||
// Semi-transparent overlay inside the borders
|
|
||||||
DrawRectangle(20, 20, screen_width - 40, screen_height - 40, Color{ 0, 0, 0, 150 });
|
|
||||||
|
|
||||||
int pausedTextWidth = MeasureText("PAUSED", 60);
|
|
||||||
DrawText("PAUSED", screen_width / 2 - pausedTextWidth / 2, screen_height / 2 - 60, 60, YELLOW);
|
|
||||||
|
|
||||||
int hintTextWidth = MeasureText("UP/DOWN Arrows to move | SPACE: Force Game Over", 20);
|
|
||||||
DrawText("UP/DOWN Arrows to move | SPACE: Force Game Over",
|
|
||||||
screen_width / 2 - hintTextWidth / 2, screen_height / 2 + 20, 20, WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case GameState::GameOver:
|
case GameState::GameOver:
|
||||||
{
|
DrawGameOverState(ctx, screen_width, screen_height);
|
||||||
if (IsKeyPressed(KEY_SPACE)) {
|
|
||||||
currentState = GameState::MainMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a nice centered dark panel for game over info
|
|
||||||
int panelWidth = 600;
|
|
||||||
int panelHeight = 400;
|
|
||||||
int panelX = screen_width / 2 - panelWidth / 2;
|
|
||||||
int panelY = screen_height / 2 - panelHeight / 2;
|
|
||||||
|
|
||||||
// Semi-transparent background panel
|
|
||||||
DrawRectangle(panelX, panelY, panelWidth, panelHeight, Color{ 15, 15, 15, 230 });
|
|
||||||
DrawRectangleLines(panelX, panelY, panelWidth, panelHeight, Color{ 120, 120, 120, 255 });
|
|
||||||
|
|
||||||
// Draw "GAME OVER" header
|
|
||||||
int gameOverWidth = MeasureText("GAME OVER", 60);
|
|
||||||
DrawText("GAME OVER", screen_width / 2 - gameOverWidth / 2, panelY + 40, 60, RED);
|
|
||||||
|
|
||||||
// Determine winner text and color
|
|
||||||
std::string winnerText = "GAME ENDED";
|
|
||||||
Color winnerColor = YELLOW;
|
|
||||||
std::string score1Str, score2Str;
|
|
||||||
|
|
||||||
if (isMultiplayer) {
|
|
||||||
if (score.player_score > score.player2_score) {
|
|
||||||
winnerText = "PLAYER 1 WINS!";
|
|
||||||
winnerColor = Green;
|
|
||||||
}
|
|
||||||
else if (score.player2_score > score.player_score) {
|
|
||||||
winnerText = "PLAYER 2 WINS!";
|
|
||||||
winnerColor = RED;
|
|
||||||
}
|
|
||||||
score1Str = "Player 1 Score: " + std::to_string(score.player_score);
|
|
||||||
score2Str = "Player 2 Score: " + std::to_string(score.player2_score);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (score.player_score > score.cpu_score) {
|
|
||||||
winnerText = "YOU WIN!";
|
|
||||||
winnerColor = Green;
|
|
||||||
}
|
|
||||||
else if (score.cpu_score > score.player_score) {
|
|
||||||
winnerText = "CPU WINS!";
|
|
||||||
winnerColor = RED;
|
|
||||||
}
|
|
||||||
score1Str = "Player Score: " + std::to_string(score.player_score);
|
|
||||||
score2Str = "CPU Score: " + std::to_string(score.cpu_score);
|
|
||||||
}
|
|
||||||
|
|
||||||
int winnerWidth = MeasureText(winnerText.c_str(), 40);
|
|
||||||
DrawText(winnerText.c_str(), screen_width / 2 - winnerWidth / 2, panelY + 120, 40, winnerColor);
|
|
||||||
|
|
||||||
// Draw final scores
|
|
||||||
int playerTextWidth = MeasureText(score1Str.c_str(), 30);
|
|
||||||
int cpuTextWidth = MeasureText(score2Str.c_str(), 30);
|
|
||||||
|
|
||||||
DrawText(score1Str.c_str(), screen_width / 2 - playerTextWidth / 2, panelY + 200, 30, WHITE);
|
|
||||||
DrawText(score2Str.c_str(), screen_width / 2 - cpuTextWidth / 2, panelY + 250, 30, WHITE);
|
|
||||||
|
|
||||||
// Draw playtime
|
|
||||||
int minutes = (int)sessionPlayTime / 60;
|
|
||||||
int seconds = (int)sessionPlayTime % 60;
|
|
||||||
std::string timeStr = "Playtime: " + std::to_string(minutes) + "m " + std::to_string(seconds) + "s";
|
|
||||||
int timeTextWidth = MeasureText(timeStr.c_str(), 20);
|
|
||||||
DrawText(timeStr.c_str(), screen_width / 2 - timeTextWidth / 2, panelY + 300, 20, LIGHTGRAY);
|
|
||||||
|
|
||||||
// Instruction to return
|
|
||||||
int instWidth = MeasureText("Press SPACEBAR to return to Main Menu", 20);
|
|
||||||
DrawText("Press SPACEBAR to return to Main Menu", screen_width / 2 - instWidth / 2, panelY + 340, 20, YELLOW);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -886,16 +174,15 @@ int main() {
|
|||||||
EndDrawing();
|
EndDrawing();
|
||||||
}
|
}
|
||||||
|
|
||||||
UnloadTexture(courtBackground);
|
// Asset unloading and cleanup
|
||||||
UnloadTexture(wallsTexture);
|
UnloadTexture(ctx.courtBackground);
|
||||||
UnloadTexture(lineTexture);
|
UnloadTexture(ctx.wallsTexture);
|
||||||
|
UnloadTexture(ctx.lineTexture);
|
||||||
UnloadSound(paddleHitSound);
|
UnloadSound(ctx.paddleHitSound);
|
||||||
UnloadSound(wallHitSound);
|
UnloadSound(ctx.wallHitSound);
|
||||||
UnloadSound(scoreSound);
|
UnloadSound(ctx.scoreSound);
|
||||||
|
|
||||||
CloseAudioDevice();
|
CloseAudioDevice();
|
||||||
|
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
+226
-6
@@ -1,7 +1,7 @@
|
|||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
|
|
||||||
int Menu::Update() {
|
int Menu::Update() {
|
||||||
// Handle Navigation
|
// Menu navigation
|
||||||
if (IsKeyPressed(KEY_DOWN)) {
|
if (IsKeyPressed(KEY_DOWN)) {
|
||||||
selectedIndex++;
|
selectedIndex++;
|
||||||
if (selectedIndex >= static_cast<int>(options.size())) selectedIndex = 0;
|
if (selectedIndex >= static_cast<int>(options.size())) selectedIndex = 0;
|
||||||
@@ -11,7 +11,7 @@ int Menu::Update() {
|
|||||||
if (selectedIndex < 0) selectedIndex = static_cast<int>(options.size()) - 1;
|
if (selectedIndex < 0) selectedIndex = static_cast<int>(options.size()) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Selection
|
// Option selection
|
||||||
if (IsKeyPressed(KEY_ENTER)) {
|
if (IsKeyPressed(KEY_ENTER)) {
|
||||||
return selectedIndex;
|
return selectedIndex;
|
||||||
}
|
}
|
||||||
@@ -22,14 +22,234 @@ void Menu::Draw() {
|
|||||||
int screenWidth = GetScreenWidth();
|
int screenWidth = GetScreenWidth();
|
||||||
int screenHeight = GetScreenHeight();
|
int screenHeight = GetScreenHeight();
|
||||||
|
|
||||||
// Draw Title
|
// Menu title and elements rendering
|
||||||
DrawText(title.c_str(), screenWidth / 2 - MeasureText(title.c_str(), 60) / 2, screenHeight / 4, 60, WHITE);
|
DrawText(title.c_str(), screenWidth / 2 - MeasureText(title.c_str(), 60) / 2, screenHeight / 4, 60, WHITE);
|
||||||
|
|
||||||
// Draw Options
|
|
||||||
for (int i = 0; i < static_cast<int>(options.size()); i++) {
|
for (int i = 0; i < static_cast<int>(options.size()); i++) {
|
||||||
Color textColor = (i == selectedIndex) ? YELLOW : WHITE;
|
Color textColor = (i == selectedIndex) ? YELLOW : WHITE;
|
||||||
DrawText(options[i].c_str(), screenWidth / 2 - MeasureText(options[i].c_str(), 40) / 2, screenHeight / 2 + (i * 60), 40, textColor);
|
DrawText(options[i].c_str(), screenWidth / 2 - MeasureText(options[i].c_str(), 40) / 2, screenHeight / 2 + (i * 60), 40, textColor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw Control keys
|
// State routines for settings and multiplayer lobby
|
||||||
}
|
|
||||||
|
void ApplyResolution(int width, int height) {
|
||||||
|
int monitor = GetCurrentMonitor();
|
||||||
|
int monitorWidth = GetMonitorWidth(monitor);
|
||||||
|
int monitorHeight = GetMonitorHeight(monitor);
|
||||||
|
|
||||||
|
if (width >= monitorWidth || height >= monitorHeight) {
|
||||||
|
SetWindowState(FLAG_WINDOW_MAXIMIZED | FLAG_WINDOW_RESIZABLE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ClearWindowState(FLAG_WINDOW_MAXIMIZED);
|
||||||
|
SetWindowSize(width, height);
|
||||||
|
SetWindowPosition(monitorWidth / 2 - width / 2, monitorHeight / 2 - height / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyFramerate(int option) {
|
||||||
|
if (option == 0) {
|
||||||
|
ClearWindowState(FLAG_VSYNC_HINT);
|
||||||
|
SetTargetFPS(60);
|
||||||
|
}
|
||||||
|
else if (option == 1) {
|
||||||
|
ClearWindowState(FLAG_VSYNC_HINT);
|
||||||
|
SetTargetFPS(144);
|
||||||
|
}
|
||||||
|
else if (option == 2) {
|
||||||
|
SetWindowState(FLAG_VSYNC_HINT);
|
||||||
|
SetTargetFPS(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateSettingsState(GameContext& ctx) {
|
||||||
|
// Menu navigation
|
||||||
|
if (IsKeyPressed(KEY_UP)) {
|
||||||
|
ctx.config.selectedSettingLine--;
|
||||||
|
if (ctx.config.selectedSettingLine < 0) ctx.config.selectedSettingLine = 5;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_DOWN)) {
|
||||||
|
ctx.config.selectedSettingLine++;
|
||||||
|
if (ctx.config.selectedSettingLine > 5) ctx.config.selectedSettingLine = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings adjustments (LEFT/RIGHT or ENTER)
|
||||||
|
if (ctx.config.selectedSettingLine == 0) {
|
||||||
|
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
|
||||||
|
ctx.config.resolutionOption = (ctx.config.resolutionOption + 1) % 3;
|
||||||
|
if (ctx.config.resolutionOption == 0) ApplyResolution(1280, 800);
|
||||||
|
else if (ctx.config.resolutionOption == 1) ApplyResolution(1600, 900);
|
||||||
|
else if (ctx.config.resolutionOption == 2) ApplyResolution(1920, 1080);
|
||||||
|
}
|
||||||
|
else if (IsKeyPressed(KEY_LEFT)) {
|
||||||
|
ctx.config.resolutionOption = (ctx.config.resolutionOption - 1 + 3) % 3;
|
||||||
|
if (ctx.config.resolutionOption == 0) ApplyResolution(1280, 800);
|
||||||
|
else if (ctx.config.resolutionOption == 1) ApplyResolution(1600, 900);
|
||||||
|
else if (ctx.config.resolutionOption == 2) ApplyResolution(1920, 1080);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ctx.config.selectedSettingLine == 1) {
|
||||||
|
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
|
||||||
|
ctx.config.framerateOption = (ctx.config.framerateOption + 1) % 3;
|
||||||
|
ApplyFramerate(ctx.config.framerateOption);
|
||||||
|
}
|
||||||
|
else if (IsKeyPressed(KEY_LEFT)) {
|
||||||
|
ctx.config.framerateOption = (ctx.config.framerateOption - 1 + 3) % 3;
|
||||||
|
ApplyFramerate(ctx.config.framerateOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ctx.config.selectedSettingLine == 2) {
|
||||||
|
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) {
|
||||||
|
ToggleFullscreen();
|
||||||
|
ctx.config.isFullscreen = !ctx.config.isFullscreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ctx.config.selectedSettingLine == 3) {
|
||||||
|
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
|
||||||
|
ctx.config.maxScoreOption = (ctx.config.maxScoreOption + 1) % 4;
|
||||||
|
}
|
||||||
|
else if (IsKeyPressed(KEY_LEFT)) {
|
||||||
|
ctx.config.maxScoreOption = (ctx.config.maxScoreOption - 1 + 4) % 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.config.maxScoreOption == 0) ctx.config.maxScore = 5;
|
||||||
|
else if (ctx.config.maxScoreOption == 1) ctx.config.maxScore = 11;
|
||||||
|
else if (ctx.config.maxScoreOption == 2) ctx.config.maxScore = 15;
|
||||||
|
else if (ctx.config.maxScoreOption == 3) ctx.config.maxScore = 21;
|
||||||
|
}
|
||||||
|
else if (ctx.config.selectedSettingLine == 4) {
|
||||||
|
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) {
|
||||||
|
ctx.config.sfxEnabled = !ctx.config.sfxEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ctx.config.selectedSettingLine == 5) {
|
||||||
|
if (IsKeyPressed(KEY_ENTER)) {
|
||||||
|
ctx.currentState = GameState::MainMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawSettingsState(const GameContext& ctx, int screenWidth, int screenHeight) {
|
||||||
|
int titleWidth = MeasureText("SETTINGS", 60);
|
||||||
|
DrawText("SETTINGS", screenWidth / 2 - titleWidth / 2, screenHeight / 4 - 40, 60, WHITE);
|
||||||
|
|
||||||
|
// Option labels formatting
|
||||||
|
std::string resStr = "Resolution: ";
|
||||||
|
if (ctx.config.resolutionOption == 0) resStr += "1280x800";
|
||||||
|
else if (ctx.config.resolutionOption == 1) resStr += "1600x900";
|
||||||
|
else if (ctx.config.resolutionOption == 2) resStr += "1920x1080 (Windowed)";
|
||||||
|
|
||||||
|
std::string fpsStr = "Framerate: ";
|
||||||
|
if (ctx.config.framerateOption == 0) fpsStr += "60 FPS";
|
||||||
|
else if (ctx.config.framerateOption == 1) fpsStr += "144 FPS";
|
||||||
|
else if (ctx.config.framerateOption == 2) fpsStr += "VSync";
|
||||||
|
|
||||||
|
std::string modeStr = "Screen Mode: ";
|
||||||
|
if (ctx.config.isFullscreen) modeStr += "Fullscreen";
|
||||||
|
else modeStr += "Windowed";
|
||||||
|
|
||||||
|
std::string scoreLimitStr = "Score Limit: " + std::to_string(ctx.config.maxScore);
|
||||||
|
|
||||||
|
std::string sfxStr = "Sound Effects: ";
|
||||||
|
if (ctx.config.sfxEnabled) sfxStr += "ON";
|
||||||
|
else sfxStr += "OFF";
|
||||||
|
|
||||||
|
// Menu options highlighting and drawing
|
||||||
|
Color resColor = (ctx.config.selectedSettingLine == 0) ? YELLOW : WHITE;
|
||||||
|
Color fpsColor = (ctx.config.selectedSettingLine == 1) ? YELLOW : WHITE;
|
||||||
|
Color modeColor = (ctx.config.selectedSettingLine == 2) ? YELLOW : WHITE;
|
||||||
|
Color scoreColor = (ctx.config.selectedSettingLine == 3) ? YELLOW : WHITE;
|
||||||
|
Color sfxColor = (ctx.config.selectedSettingLine == 4) ? YELLOW : WHITE;
|
||||||
|
Color backColor = (ctx.config.selectedSettingLine == 5) ? YELLOW : WHITE;
|
||||||
|
|
||||||
|
int startY = screenHeight / 2 - 90;
|
||||||
|
DrawText(resStr.c_str(), screenWidth / 2 - MeasureText(resStr.c_str(), 30) / 2, startY, 30, resColor);
|
||||||
|
DrawText(fpsStr.c_str(), screenWidth / 2 - MeasureText(fpsStr.c_str(), 30) / 2, startY + 45, 30, fpsColor);
|
||||||
|
DrawText(modeStr.c_str(), screenWidth / 2 - MeasureText(modeStr.c_str(), 30) / 2, startY + 90, 30, modeColor);
|
||||||
|
DrawText(scoreLimitStr.c_str(), screenWidth / 2 - MeasureText(scoreLimitStr.c_str(), 30) / 2, startY + 135, 30, scoreColor);
|
||||||
|
DrawText(sfxStr.c_str(), screenWidth / 2 - MeasureText(sfxStr.c_str(), 30) / 2, startY + 180, 30, sfxColor);
|
||||||
|
DrawText("Back", screenWidth / 2 - MeasureText("Back", 30) / 2, startY + 225, 30, backColor);
|
||||||
|
|
||||||
|
int settingsHintWidth = MeasureText("UP/DOWN to navigate | LEFT/RIGHT to change settings | ENTER to select", 20);
|
||||||
|
DrawText("UP/DOWN to navigate | LEFT/RIGHT to change settings | ENTER to select",
|
||||||
|
screenWidth / 2 - settingsHintWidth / 2,
|
||||||
|
screenHeight - 50, 20, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateLobbyState(GameContext& ctx) {
|
||||||
|
if (IsKeyPressed(KEY_W)) {
|
||||||
|
ctx.p1Ready = !ctx.p1Ready;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.paddleHitSound);
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_UP)) {
|
||||||
|
ctx.p2Ready = !ctx.p2Ready;
|
||||||
|
if (ctx.config.sfxEnabled) PlaySound(ctx.paddleHitSound);
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_B)) {
|
||||||
|
ctx.currentState = GameState::MainMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.p1Ready && ctx.p2Ready) {
|
||||||
|
ctx.currentState = GameState::Multiplayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawLobbyState(const GameContext& ctx, int screenWidth, int screenHeight) {
|
||||||
|
// Draw background field
|
||||||
|
DrawCourt(ctx, screenWidth, screenHeight);
|
||||||
|
|
||||||
|
// Render lobby overlay card
|
||||||
|
int panelWidth = 800;
|
||||||
|
int panelHeight = 450;
|
||||||
|
int panelX = screenWidth / 2 - panelWidth / 2;
|
||||||
|
int panelY = screenHeight / 2 - panelHeight / 2;
|
||||||
|
|
||||||
|
DrawRectangle(panelX, panelY, panelWidth, panelHeight, Color{ 15, 15, 15, 230 });
|
||||||
|
DrawRectangleLines(panelX, panelY, panelWidth, panelHeight, Color{ 120, 120, 120, 255 });
|
||||||
|
|
||||||
|
int titleWidth = MeasureText("MULTIPLAYER LOBBY", 40);
|
||||||
|
DrawText("MULTIPLAYER LOBBY", screenWidth / 2 - titleWidth / 2, panelY + 30, 40, WHITE);
|
||||||
|
|
||||||
|
// Player 1 section
|
||||||
|
int p1X = panelX + 50;
|
||||||
|
int p1Y = panelY + 110;
|
||||||
|
DrawText("PLAYER 1 (LEFT)", p1X, p1Y, 24, LIGHTGRAY);
|
||||||
|
DrawText("Controls: W / S", p1X, p1Y + 45, 20, WHITE);
|
||||||
|
DrawText("Press 'W' to toggle ready", p1X, p1Y + 75, 18, GRAY);
|
||||||
|
|
||||||
|
if (ctx.p1Ready) {
|
||||||
|
DrawRectangle(p1X, p1Y + 115, 220, 50, GREEN);
|
||||||
|
DrawText("READY", p1X + 110 - MeasureText("READY", 20)/2, p1Y + 130, 20, BLACK);
|
||||||
|
} else {
|
||||||
|
DrawRectangle(p1X, p1Y + 115, 220, 50, RED);
|
||||||
|
DrawText("NOT READY", p1X + 110 - MeasureText("NOT READY", 20)/2, p1Y + 130, 20, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player 2 section
|
||||||
|
int p2X = panelX + panelWidth - 270;
|
||||||
|
int p2Y = panelY + 110;
|
||||||
|
DrawText("PLAYER 2 (RIGHT)", p2X, p2Y, 24, LIGHTGRAY);
|
||||||
|
DrawText("Controls: UP / DOWN", p2X, p2Y + 45, 20, WHITE);
|
||||||
|
DrawText("Press 'UP' to toggle ready", p2X, p2Y + 75, 18, GRAY);
|
||||||
|
|
||||||
|
if (ctx.p2Ready) {
|
||||||
|
DrawRectangle(p2X, p2Y + 115, 220, 50, GREEN);
|
||||||
|
DrawText("READY", p2X + 110 - MeasureText("READY", 20)/2, p2Y + 130, 20, BLACK);
|
||||||
|
} else {
|
||||||
|
DrawRectangle(p2X, p2Y + 115, 220, 50, RED);
|
||||||
|
DrawText("NOT READY", p2X + 110 - MeasureText("NOT READY", 20)/2, p2Y + 130, 20, WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ready status message
|
||||||
|
if (ctx.p1Ready && ctx.p2Ready) {
|
||||||
|
int startTextWidth = MeasureText("BOTH READY! STARTING GAME...", 24);
|
||||||
|
DrawText("BOTH READY! STARTING GAME...", screenWidth / 2 - startTextWidth / 2, panelY + 310, 24, YELLOW);
|
||||||
|
} else {
|
||||||
|
int waitTextWidth = MeasureText("Waiting for both players to be ready...", 20);
|
||||||
|
DrawText("Waiting for both players to be ready...", screenWidth / 2 - waitTextWidth / 2, panelY + 315, 20, LIGHTGRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
int backTextWidth = MeasureText("Press 'B' to return to Main Menu | P: Pause | SPACE: Force Game Over", 20);
|
||||||
|
DrawText("Press 'B' to return to Main Menu | P: Pause | SPACE: Force Game Over", screenWidth / 2 - backTextWidth / 2, panelY + 385, 20, YELLOW);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user