11 Commits

Author SHA1 Message Date
dr20ervin 6c8fa690d3 Update application icon and add dynamic icon loading 2026-05-26 23:37:50 +03:00
dr20ervin fac5b95a82 Add custom logging system and improve state logging. Refactor main.cpp and add custom logging system 2026-05-26 23:26:28 +03:00
dr20ervin b7954e4234 Add Windows console toggle functionality with F7 key 2026-05-26 23:14:08 +03:00
dr20ervin 45462a3ba7 Embed resources and update asset loading system 2026-05-26 22:59:14 +03:00
dr20ervin 7493daf004 Refactor game architecture and enhance features 2026-05-21 17:29:18 +03:00
dr20ervin 0573b384b3 Update update audio files in project filters 2026-05-21 03:39:59 +03:00
Vajda Ervin-Oliver a7e123c045 Update MSBuild setup and remove NuGet restore step
Updated MSBuild setup action to version 2 and removed NuGet restore step.
2026-05-21 03:36:12 +03:00
Vajda Ervin-Oliver ab69b5d2e5 Fix formatting in msbuild.yml 2026-05-21 03:29:59 +03:00
Vajda Ervin-Oliver 0aeff08e38 Change solution file path in msbuild.yml
Updated the solution file path for the build workflow.
2026-05-21 03:26:10 +03:00
dr20ervin fad1ed6496 Add multiplayer lobby and gameplay enhancements 2026-05-21 00:40:54 +03:00
dr20ervin f042226dcf Add settings menu, dynamic scaling, and sound effects 2026-05-20 19:57:36 +03:00
16 changed files with 1014 additions and 331 deletions
+41
View File
@@ -0,0 +1,41 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: MSBuild
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
env:
# Path to the solution file relative to the root of the project.
SOLUTION_FILE_PATH: pong-reloaded.sln
# Configuration type to build.
# You can convert this to a build matrix if you need coverage of multiple configuration types.
# https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
BUILD_CONFIGURATION: Release
permissions:
contents: read
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v2
- name: Build
working-directory: ${{env.GITHUB_WORKSPACE}}
# Add additional options to the MSBuild command line here (like platform or verbosity level).
# See https://docs.microsoft.com/visualstudio/msbuild/msbuild-command-line-reference
run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 534 534" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;"><path id="rect1" d="M531.584,66.667l0,400c0,35.829 -29.089,64.918 -64.918,64.918l-400,0c-35.829,0 -64.918,-29.089 -64.918,-64.918l-0,-400c0,-35.829 29.089,-64.918 64.918,-64.918l400,-0c35.829,0 64.918,29.089 64.918,64.918Z" style="fill:#f15a22;stroke:#f37547;stroke-width:16.67px;"/><path id="rect7" d="M219.907,130.436l0,270.833c0,11.498 -9.335,20.833 -20.833,20.833l-62.5,0c-11.498,0 -20.833,-9.335 -20.833,-20.833l0,-270.833c0,-11.498 9.335,-20.833 20.833,-20.833l62.5,0c11.498,0 20.833,9.335 20.833,20.833Z" style="fill:#351103;"/><ellipse id="path8" cx="378.086" cy="132.055" rx="41.819" ry="41.667" style="fill:#351103;"/><path id="path9" d="M419.601,422.103l-166.667,-165.047l83.333,-83.333" style="fill:none;fill-rule:nonzero;stroke:#351103;stroke-width:20.83px;stroke-dasharray:20.833,20.833;"/><g id="Layer1"><path d="M299.642,464.615l102.254,0.439c58.476,0 76.672,-30.142 76.672,-89.976l0,-216.824c0,-59.834 -47.475,-108.412 -105.95,-108.412l-211.901,0c-58.476,0 -105.95,48.578 -105.95,108.412l0,216.824c0,59.834 47.475,108.412 105.95,108.412l0.891,0l0,32.558l-16.8,0c-67.256,0 -121.86,-55.872 -121.86,-124.691l-0,-249.382c0,-68.819 54.604,-124.691 121.86,-124.691l243.72,-0c67.256,0 121.86,55.872 121.86,124.691l0,249.382c0,68.077 -29.412,103.976 -95.669,105.163c-0.722,0.013 -15.591,0.315 -16.316,0.315l-111.682,0.323l0.002,-0.264l-11.062,0l0.211,12.303l-28.555,-29.218l28.555,-29.218l-0.633,13.598l24.403,0.255Z" style="fill:#351103;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+85 -25
View File
@@ -4,10 +4,11 @@
#include <string>
#include <vector>
// Game States & Configurations
// Game states and configurations
enum class GameState {
MainMenu,
DifficultySelect,
MultiplayerLobby,
Multiplayer,
Settings,
Playing,
@@ -21,12 +22,71 @@ enum class Difficulty {
Hard
};
constexpr float CPU_SPEED_EASY = 3.0f;
constexpr float CPU_SPEED_NORMAL = 5.5f;
constexpr float CPU_SPEED_HARD = 8.0f;
constexpr float CPU_SPEED_EASY = 180.0f;
constexpr float CPU_SPEED_NORMAL = 330.0f;
constexpr float CPU_SPEED_HARD = 480.0f;
constexpr float PLAYER_SPEED = 360.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 };
};
// Asset loading helpers
Texture2D LoadTextureFromResource(int id);
Sound LoadSoundFromResource(int id);
void SetWindowIconFromResource(int id);
// 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 {
public:
Vector2 position;
@@ -34,23 +94,20 @@ public:
GameObject(Vector2 pos, Color c) : position(pos), color(c) {}
virtual ~GameObject() = default;
virtual void Update() = 0;
virtual bool Update() = 0;
virtual void Draw() = 0;
};
// --- Game Objects ---
// Entity classes
class Paddle : public GameObject {
public:
float width;
float height;
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, int textureId = 0)
: GameObject(pos, c), width(w), height(h) {
if (!texturePath.empty()) {
texture = LoadTexture(texturePath.c_str());
if (textureId > 0) {
texture = LoadTextureFromResource(textureId);
} else {
texture = { 0 };
}
@@ -62,7 +119,7 @@ public:
}
}
void Update() override;
bool Update() override;
void Draw() override;
};
@@ -73,10 +130,10 @@ public:
Texture2D texture;
public:
Ball(Vector2 pos, Color c, float r, const std::string& texturePath = "")
Ball(Vector2 pos, Color c, float r, int textureId = 0)
: GameObject(pos, c), radius(r), velocity({ 5.0f, 5.0f }) {
if (!texturePath.empty()) {
texture = LoadTexture(texturePath.c_str());
if (textureId > 0) {
texture = LoadTextureFromResource(textureId);
} else {
texture = { 0 };
}
@@ -88,7 +145,7 @@ public:
}
}
void Update() override;
bool Update() override;
void Draw() override;
};
@@ -98,8 +155,8 @@ private:
Difficulty currentDifficulty;
public:
CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal, const std::string& texturePath = "")
: Paddle(pos, c, w, h, texturePath) {
CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal, int textureId = 0)
: Paddle(pos, c, w, h, textureId) {
SetDifficulty(diff);
}
@@ -114,18 +171,21 @@ public:
void Update(float ball_y) {
float paddleCenter = position.y + (height / 2.0f);
float dt = GetFrameTime();
float moveAmount = speed * dt;
if (paddleCenter > ball_y + speed) {
position.y -= speed;
if (paddleCenter > ball_y + moveAmount) {
position.y -= moveAmount;
}
else if (paddleCenter < ball_y - speed) {
position.y += speed;
else if (paddleCenter < ball_y - moveAmount) {
position.y += moveAmount;
}
LimitMovement();
position.x = 20.0f + 10.0f;
}
void Update() override {}
bool Update() override { return false; }
void LimitMovement() {
if (position.y <= 20.0f) {
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <iostream>
#include <cstdio>
#include <vector>
#include <string>
#include <mutex>
#include <cstdarg>
#include <raylib.h>
// Global colors
Color Green = Color{ 38, 185, 154, 255 };
Color Dark_Green = Color{ 20, 160, 133, 255 };
Color Light_Green = Color{ 129, 204, 184, 255 };
Color Yellow = Color{ 243, 213, 91, 255 };
// DevLog buffering & function prototype
std::vector<std::string> logHistory;
std::mutex logMutex;
void CustomLogCallback(int logLevel, const char* text, va_list args);
+10 -3
View File
@@ -3,8 +3,7 @@
#include <string>
#include <vector>
// --- UI / Systems ---
// Menu UI controller
class Menu {
private:
std::string title;
@@ -18,4 +17,12 @@ public:
int Update();
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);
+1 -1
View File
@@ -1333,7 +1333,7 @@ RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vect
// Basic shapes collision detection functions
RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles
RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles
RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle
RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle`
RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2]
RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle
RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#define IDI_ICON1 100
#define IDR_TEX_BASIC_SPACE 101
#define IDR_TEX_WALLS 102
#define IDR_TEX_LINE 103
#define IDR_TEX_BALL 104
#define IDR_TEX_PADDLE 105
#define IDR_TEX_PADDLE2 106
#define IDR_SND_PADDLE_HIT 201
#define IDR_SND_WALL_HIT 202
#define IDR_SND_SCORE 203
#define IDR_SND_BANANA 204
+13
View File
@@ -131,6 +131,7 @@
<ItemGroup>
<ClCompile Include="src\game.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\resource_loader.cpp" />
<ClCompile Include="src\menu.cpp">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
@@ -140,12 +141,18 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\game.h" />
<ClInclude Include="include\main.h" />
<ClInclude Include="include\menu.h" />
<ClInclude Include="include\raylib.h" />
<ClInclude Include="include\raymath.h" />
<ClInclude Include="include\resource.h" />
<ClInclude Include="include\rlgl.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="assets\icon.ico" />
<Image Include="assets\textures\ball\basic_ball_5.png" />
<Image Include="assets\textures\hud\line.png" />
<Image Include="assets\textures\paddles\basic_paddle.png" />
@@ -153,6 +160,12 @@
<Image Include="assets\textures\spaces\basic_space.png" />
<Image Include="assets\textures\spaces\walls.png" />
</ItemGroup>
<ItemGroup>
<None Include="assets\audio\banana_wall_hit.ogg" />
<None Include="assets\audio\paddle_hit.ogg" />
<None Include="assets\audio\score.ogg" />
<None Include="assets\audio\wall_hit.ogg" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
+35 -4
View File
@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
@@ -22,6 +18,10 @@
<Filter Include="Resource Files\audio">
<UniqueIdentifier>{5f42ff18-01e2-4266-a4e0-6dc77ba54e75}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\main.cpp">
@@ -33,6 +33,9 @@
<ClCompile Include="src\menu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\resource_loader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\raylib.h">
@@ -50,6 +53,12 @@
<ClInclude Include="include\menu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\main.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="assets\textures\ball\basic_ball_5.png">
@@ -70,5 +79,27 @@
<Image Include="assets\textures\spaces\walls.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="assets\icon.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<None Include="assets\audio\banana_wall_hit.ogg">
<Filter>Resource Files\audio</Filter>
</None>
<None Include="assets\audio\paddle_hit.ogg">
<Filter>Resource Files\audio</Filter>
</None>
<None Include="assets\audio\score.ogg">
<Filter>Resource Files\audio</Filter>
</None>
<None Include="assets\audio\wall_hit.ogg">
<Filter>Resource Files\audio</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
+15
View File
@@ -0,0 +1,15 @@
#include "include/resource.h"
IDI_ICON1 ICON "assets/icon.ico"
IDR_TEX_BASIC_SPACE RCDATA "assets/textures/spaces/basic_space.png"
IDR_TEX_WALLS RCDATA "assets/textures/spaces/walls.png"
IDR_TEX_LINE RCDATA "assets/textures/hud/line.png"
IDR_TEX_BALL RCDATA "assets/textures/ball/basic_ball_5.png"
IDR_TEX_PADDLE RCDATA "assets/textures/paddles/basic_paddle.png"
IDR_TEX_PADDLE2 RCDATA "assets/textures/paddles/basic_paddle_2.png"
IDR_SND_PADDLE_HIT RCDATA "assets/audio/paddle_hit.ogg"
IDR_SND_WALL_HIT RCDATA "assets/audio/wall_hit.ogg"
IDR_SND_SCORE RCDATA "assets/audio/score.ogg"
IDR_SND_BANANA RCDATA "assets/audio/banana_wall_hit.ogg"
+311 -14
View File
@@ -1,24 +1,28 @@
#include "game.h"
// --- Paddle Implementation ---
void Paddle::Update() {
float speed = 6.0f;
// Paddle implementation
bool Paddle::Update() {
float dt = GetFrameTime();
if (IsKeyDown(KEY_UP)) {
position.y -= speed;
position.y -= PLAYER_SPEED * dt;
}
if (IsKeyDown(KEY_DOWN)) {
position.y += speed;
position.y += PLAYER_SPEED * dt;
}
// Limit movement
// Keep paddle within screen boundaries
if (position.y <= 20.0f) {
position.y = 20.0f;
}
if (position.y + height >= GetScreenHeight() - 20.0f) {
position.y = GetScreenHeight() - 20.0f - height;
}
// Align paddle relative to right screen edge
position.x = GetScreenWidth() - 20.0f - 10.0f - width;
return false;
}
void Paddle::Draw() {
@@ -37,15 +41,17 @@ void Paddle::Draw() {
}
// --- Ball Implementation ---
void Ball::Update() {
position.x += velocity.x;
position.y += velocity.y;
// Ball implementation
bool Ball::Update() {
float dt = GetFrameTime();
position.x += velocity.x * dt;
position.y += velocity.y * dt;
if (position.y + radius >= GetScreenHeight() - 20.0f || position.y - radius <= 20.0f) {
velocity.y *= -1;
return true;
}
return false;
}
void Ball::Draw() {
@@ -59,7 +65,298 @@ void Ball::Draw() {
WHITE
);
} else {
// Cast to int as DrawCircle expects integers for coordinates
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;
}
// Solid background for walls to prevent transparent edge bleeding
Color wallBg = Color{ 72, 85, 107, 255 };
DrawRectangle(0, 0, screenWidth, 20, wallBg);
DrawRectangle(0, screenHeight - 20, screenWidth, 20, wallBg);
DrawRectangle(0, 0, 20, screenHeight, wallBg);
DrawRectangle(screenWidth - 20, 0, 20, screenHeight, wallBg);
// 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 - 30.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);
}
+177 -278
View File
@@ -1,336 +1,201 @@
#include <iostream>
#include "main.h"
#include "game.h"
#include "menu.h"
#include "resource.h"
// Colors
Color Green = Color{ 38, 185, 154, 255 };
Color Dark_Green = Color{ 20, 160, 133, 255 };
Color Light_Green = Color{ 129, 204, 184, 255 };
Color Yellow = Color{ 243, 213, 91, 255 };
struct ScoreBoard
{
int player_score = 0;
int player2_score = 0;
int cpu_score = 0;
};
// Helper function to reset the ball
void ResetBall(Ball& ball, int screenWidth, int screenHeight) {
ball.position.x = screenWidth / 2.0f;
ball.position.y = screenHeight / 2.0f;
int speed_choices[2] = { -1, 1 };
ball.velocity.x *= speed_choices[GetRandomValue(0, 1)];
ball.velocity.y *= speed_choices[GetRandomValue(0, 1)];
#ifdef _WIN32
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
extern "C" {
int __stdcall AllocConsole();
int __stdcall FreeConsole();
}
bool isConsoleVisible = false;
#endif
int main() {
std::cout << "Starting the game" << std::endl;
const int screen_width = 1280;
const int screen_height = 800;
std::cout << "Starting game session" << std::endl;
int screen_width = 1280;
int screen_height = 800;
SetTraceLogCallback(CustomLogCallback);
InitWindow(screen_width, screen_height, "Pong Reloaded");
SetWindowIconFromResource(IDI_ICON1);
SetTargetFPS(60);
InitAudioDevice();
// --- Instantiate Objects using the new Constructors ---
// App state context
GameContext ctx;
// Asset textures loading
ctx.courtBackground = LoadTextureFromResource(IDR_TEX_BASIC_SPACE);
ctx.wallsTexture = LoadTextureFromResource(IDR_TEX_WALLS);
ctx.lineTexture = LoadTextureFromResource(IDR_TEX_LINE);
// Audio assets loading
ctx.paddleHitSound = LoadSoundFromResource(IDR_SND_PADDLE_HIT);
ctx.wallHitSound = LoadSoundFromResource(IDR_SND_WALL_HIT);
ctx.scoreSound = LoadSoundFromResource(IDR_SND_SCORE);
// Entities instantiation
Ball ball(
Vector2{ screen_width / 2.0f, screen_height / 2.0f },
Yellow, 20.0f,
"assets/textures/ball/basic_ball_5.png"
IDR_TEX_BALL
);
ball.velocity = Vector2{ 7.0f, 7.0f };
ResetBall(ball);
Paddle player(
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
WHITE, 25.0f, 120.0f,
"assets/textures/paddles/basic_paddle.png"
IDR_TEX_PADDLE
);
CpuPaddle cpu(
Vector2{ 20.0f + 10.0f, screen_height / 2.0f - 60.0f },
WHITE, 25.0f, 120.0f,
Difficulty::Normal,
"assets/textures/paddles/basic_paddle_2.png"
IDR_TEX_PADDLE2
);
// --- Setup Menu and Game State ---
GameState currentState = GameState::MainMenu;
float sessionPlayTime = 0.0f;
bool isPaused = false;
bool shouldQuit = false;
// Menu instantiation
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
ScoreBoard score;
// --- Load Background and Wall Textures ---
Texture2D courtBackground = LoadTexture("assets/textures/spaces/basic_space.png");
Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png");
Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png");
// Main loop
while (WindowShouldClose() == false && !ctx.shouldQuit) {
screen_width = GetScreenWidth();
screen_height = GetScreenHeight();
// --- Main Game Loop ---
#ifdef _WIN32
if (IsKeyPressed(KEY_F7)) {
isConsoleVisible = !isConsoleVisible;
if (isConsoleVisible) {
AllocConsole();
FILE* dummy;
freopen_s(&dummy, "CONOUT$", "w", stdout);
freopen_s(&dummy, "CONOUT$", "w", stderr);
// Dump history
std::lock_guard<std::mutex> lock(logMutex);
for (const auto& log : logHistory) {
printf("%s", log.c_str());
}
} else {
FreeConsole();
}
}
#endif
while (WindowShouldClose() == false && !shouldQuit) {
BeginDrawing();
ClearBackground(Dark_Green);
switch (currentState) {
// Update loop
switch (ctx.currentState) {
case GameState::MainMenu:
{
int selected = mainMenu.Update();
if (selected == 0) {
currentState = GameState::DifficultySelect;
TraceLog(LOG_INFO, "State Transition: MainMenu -> DifficultySelect (Singleplayer)");
ctx.isMultiplayer = false;
ctx.currentState = GameState::DifficultySelect;
}
else if (selected == 1) {
currentState = GameState::Multiplayer;
TraceLog(LOG_INFO, "State Transition: MainMenu -> MultiplayerLobby");
ctx.isMultiplayer = true;
ctx.score.player_score = 0;
ctx.score.player2_score = 0;
ctx.score.cpu_score = 0;
ctx.sessionPlayTime = 0.0f;
ctx.isPaused = false;
ctx.p1Ready = false;
ctx.p2Ready = false;
ResetBall(ball);
ctx.currentState = GameState::MultiplayerLobby;
}
else if (selected == 2) {
currentState = GameState::Settings;
ctx.currentState = GameState::Settings;
}
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);
}
// Reset game session stats
score.player_score = 0;
score.cpu_score = 0;
sessionPlayTime = 0.0f;
isPaused = false;
ResetBall(ball, screen_width, screen_height);
currentState = GameState::Playing;
if (selected == 0) { cpu.SetDifficulty(Difficulty::Easy); TraceLog(LOG_INFO, "CPU Difficulty set to: EASY"); }
else if (selected == 1) { cpu.SetDifficulty(Difficulty::Normal); TraceLog(LOG_INFO, "CPU Difficulty set to: NORMAL"); }
else if (selected == 2) { cpu.SetDifficulty(Difficulty::Hard); TraceLog(LOG_INFO, "CPU Difficulty set to: HARD"); }
TraceLog(LOG_INFO, "State Transition: DifficultySelect -> Playing");
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) {
currentState = GameState::MainMenu;
ctx.currentState = GameState::MainMenu;
}
difficultyMenu.Draw();
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:
{
// 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();
ball.Update();
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 (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
ball.velocity.x *= -1;
}
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
score.cpu_score++;
if (score.cpu_score >= 5) {
currentState = GameState::GameOver;
}
else {
ResetBall(ball, screen_width, screen_height);
}
}
if (ball.position.x - ball.radius <= 20.0f) {
score.player_score++;
if (score.player_score >= 5) {
currentState = GameState::GameOver;
}
else {
ResetBall(ball, screen_width, screen_height);
}
}
}
// --- 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, 570.0f, 760.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{ 690.0f, 20.0f, 570.0f, 760.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 < 780) {
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, 715, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, 721, 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 - 30, 60, YELLOW);
}
UpdatePlayingState(ctx, ball, player, cpu);
break;
}
case GameState::GameOver:
{
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;
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;
}
int winnerWidth = MeasureText(winnerText.c_str(), 40);
DrawText(winnerText.c_str(), screen_width / 2 - winnerWidth / 2, panelY + 120, 40, winnerColor);
// Draw final scores
std::string playerScoreStr = "Player Score: " + std::to_string(score.player_score);
std::string cpuScoreStr = "CPU Score: " + std::to_string(score.cpu_score);
int playerTextWidth = MeasureText(playerScoreStr.c_str(), 30);
int cpuTextWidth = MeasureText(cpuScoreStr.c_str(), 30);
DrawText(playerScoreStr.c_str(), screen_width / 2 - playerTextWidth / 2, panelY + 200, 30, WHITE);
DrawText(cpuScoreStr.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);
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);
DrawText("Use UP/DOWN to navigate | ENTER to select",
screen_width / 2 - menuHintWidth / 2,
screen_height - 80, 20, WHITE);
break;
}
case GameState::DifficultySelect:
{
difficultyMenu.Draw();
int gameHintWidth = MeasureText("Player: 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",
screen_width / 2 - gameHintWidth / 2,
screen_height - 50, 20, WHITE);
break;
}
case GameState::MultiplayerLobby:
DrawLobbyState(ctx, screen_width, screen_height);
break;
case GameState::Multiplayer:
DrawMultiplayerState(ctx, ball, player, cpu, screen_width, screen_height);
break;
case GameState::Settings:
DrawSettingsState(ctx, screen_width, screen_height);
break;
case GameState::Playing:
DrawPlayingState(ctx, ball, player, cpu, screen_width, screen_height);
break;
case GameState::GameOver:
DrawGameOverState(ctx, screen_width, screen_height);
break;
default:
break;
}
@@ -338,10 +203,44 @@ int main() {
EndDrawing();
}
UnloadTexture(courtBackground);
UnloadTexture(wallsTexture);
UnloadTexture(lineTexture);
// Asset unloading and cleanup
UnloadTexture(ctx.courtBackground);
UnloadTexture(ctx.wallsTexture);
UnloadTexture(ctx.lineTexture);
UnloadSound(ctx.paddleHitSound);
UnloadSound(ctx.wallHitSound);
UnloadSound(ctx.scoreSound);
CloseAudioDevice();
CloseWindow();
return 0;
}
void CustomLogCallback(int logLevel, const char* text, va_list args) {
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), text, args);
std::string logStr;
switch (logLevel) {
case LOG_TRACE: logStr = "TRACE: "; break;
case LOG_DEBUG: logStr = "DEBUG: "; break;
case LOG_INFO: logStr = "INFO: "; break;
case LOG_WARNING: logStr = "WARNING: "; break;
case LOG_ERROR: logStr = "ERROR: "; break;
case LOG_FATAL: logStr = "FATAL: "; break;
default: logStr = "LOG: "; break;
}
logStr += buffer;
logStr += "\n";
std::lock_guard<std::mutex> lock(logMutex);
logHistory.push_back(logStr);
#ifdef _WIN32
if (isConsoleVisible) {
printf("%s", logStr.c_str());
}
#else
printf("%s", logStr.c_str());
#endif
}
+226 -6
View File
@@ -1,7 +1,7 @@
#include "menu.h"
int Menu::Update() {
// Handle Navigation
// Menu navigation
if (IsKeyPressed(KEY_DOWN)) {
selectedIndex++;
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;
}
// Handle Selection
// Option selection
if (IsKeyPressed(KEY_ENTER)) {
return selectedIndex;
}
@@ -22,14 +22,234 @@ void Menu::Draw() {
int screenWidth = GetScreenWidth();
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);
// Draw Options
for (int i = 0; i < static_cast<int>(options.size()); i++) {
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);
}
}
// 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);
}
+63
View File
@@ -0,0 +1,63 @@
#define CloseWindow Win32_CloseWindow
#define ShowCursor Win32_ShowCursor
#define Rectangle Win32_Rectangle
#define PlaySound Win32_PlaySound
#define LoadImage Win32_LoadImage
#define DrawText Win32_DrawText
#define DrawTextEx Win32_DrawTextEx
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef CloseWindow
#undef ShowCursor
#undef Rectangle
#undef PlaySound
#undef LoadImage
#undef DrawText
#undef DrawTextEx
#include "game.h"
Texture2D LoadTextureFromResource(int id) {
Texture2D texture = { 0 };
HRSRC hResInfo = FindResource(NULL, MAKEINTRESOURCE(id), RT_RCDATA);
if (!hResInfo) return texture;
HGLOBAL hResData = LoadResource(NULL, hResInfo);
if (!hResData) return texture;
int size = SizeofResource(NULL, hResInfo);
unsigned char* data = (unsigned char*)LockResource(hResData);
if (!data) return texture;
Image img = LoadImageFromMemory(".png", data, size);
texture = LoadTextureFromImage(img);
UnloadImage(img);
return texture;
}
Sound LoadSoundFromResource(int id) {
Sound sound = { 0 };
HRSRC hResInfo = FindResource(NULL, MAKEINTRESOURCE(id), RT_RCDATA);
if (!hResInfo) return sound;
HGLOBAL hResData = LoadResource(NULL, hResInfo);
if (!hResData) return sound;
int size = SizeofResource(NULL, hResInfo);
unsigned char* data = (unsigned char*)LockResource(hResData);
if (!data) return sound;
Wave wave = LoadWaveFromMemory(".ogg", data, size);
sound = LoadSoundFromWave(wave);
UnloadWave(wave);
return sound;
}
void SetWindowIconFromResource(int id) {
HWND hwnd = (HWND)GetWindowHandle();
if (!hwnd) return;
HICON hIcon = LoadIconA(GetModuleHandle(NULL), MAKEINTRESOURCEA(id));
if (hIcon) {
SendMessageA(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessageA(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
}
}