16 Commits

Author SHA1 Message Date
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
dr20ervin 2672a54b4c Refactor scoring and add Game Over screen 2026-05-20 14:24:05 +03:00
dr20ervin ce3df94b68 Add textures, gameplay pause, and menu state placeholders 2026-05-20 14:08:57 +03:00
dr20ervin 25a7fefa04 Add textures, gameplay pause, and menu state placeholders. 2026-05-20 13:59:01 +03:00
Vajda Ervin-Oliver 1c7762ea3b Update .gitattributes 2026-05-20 13:48:51 +03:00
Vajda Ervin-Oliver a651fa6870 Delete . gitattributes 2026-05-20 13:47:29 +03:00
Vajda Ervin-Oliver 1664939b69 Create . gitattributes 2026-05-20 13:47:00 +03:00
Vajda Ervin-Oliver 434b9c3064 Fix typo in project description 2026-05-20 13:02:14 +03:00
dr20ervin 67f5fcf374 Uploading assets & developing multiplayer implementations 2026-05-20 12:11:04 +03:00
28 changed files with 1098 additions and 148 deletions
+4
View File
@@ -2,6 +2,10 @@
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
# Mark specific Raylib files as vendored to exclude from language stats
include/raylib.h linguist-vendored
include/raymath.h linguist-vendored
include/rlgl.h linguist-vendored
###############################################################################
# Set default behavior for command prompt diff.
+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}}
+25 -5
View File
@@ -2,9 +2,29 @@
A classic arcade game rebuilt from the ground up using modern C++ and the Raylib library.
**Features:**
* Clean, Object-Oriented entity architecture.
* Game state management (Main Menu, Gameplay, Game Over).
* A CPU AI opponent with adjustable difficulty levels (Easy, Normal, Hard).
---
*This is a project developed for UTCN - ETTI Helios Additional_activity.*
## Credits & Attributions
### Code Base Template
* **Original Author:** educ8s (Nick Koumaris)
* **Original Repository:** [github.com/educ8s/Cpp-Pong-Game-Raylib](https://github.com/educ8s/Cpp-Pong-Game-Raylib)
* **Description:** This project utilizes core architectural patterns and Raylib integrations adapted from the original open-source template repository listed above.
### Asset Credits & Attribution
The multimedia resources (graphics, sprites, and audio files) used in this game are not my personal creations. They have been curated and adapted from the open-source community for educational development.
* **Original Project:** Moddable Pong
* **Author / Creator:** Endless OS Foundation & Endless Studios
* **Website:** [endlessos.org](https://endlessos.org)
* **Source Code Repository:** [github.com/endlessm/moddable-pong](https://github.com/endlessm/moddable-pong/)
* **Usage Notice:** These assets are integrated strictly for non-commercial, academic layout validation and development milestones. All rights, ownership, and copyrights belong entirely to the original creators at the Endless OS Foundation.
---
## Features
* **Clean Object-Oriented Architecture:** Developed using rigorous OOP principles, utilizing a base abstract `GameObject` class with virtual override structures for specialized modular entities (`Ball`, `Paddle`, and `CpuPaddle`).
* **Robust Game State Machine:** Features an integrated game loop managing distinct application states including `MainMenu`, `DifficultySelect`, `Multiplayer`, `Settings`, and `Playing`.
* **Dynamic AI Opponent:** A singleplayer CPU opponent equipped with configurable movement velocities tailored across three distinct difficulty tiers: Easy, Normal, and Hard.
* **Enhanced Visual Layout:** Fully upgraded with high-resolution texture mapping via Raylib's `DrawTexturePro`, featuring dedicated game court backdrops, localized wall segments, styled central dashed lines, and custom entity sprites.
* **Academic Context:** This work is part of a student engineering project within the Helios Additional_activity initiative at the Technical University of Cluj-Napoca (UTCN) - Faculty of Electronics, Telecommunications and Information Technology (ETTI).
+16
View File
@@ -0,0 +1,16 @@
# Asset Credits & Attribution
The multimedia resources (graphics, sprites, and audio files) contained within this folder are not my personal creations. They have been curated and adapted from the open-source community for educational development.
## Original Source Information
* **Project:** Moddable Pong
* **Author / Creator:** Endless OS Foundation & Endless Studios
* **Website:** [endlessos.org](https://endlessos.org)
* **Source Code Repository:** [github.com/endlessm/moddable-pong](https://github.com/endlessm/moddable-pong/)
---
## Usage Notice
These assets are integrated strictly for non-commercial, academic layout validation and development milestones. This work is part of a student engineering project within the **Helios Additional_activity** initiative at the **Technical University of Cluj-Napoca (UTCN) - Faculty of Electronics, Telecommunications and Information Technology (ETTI)**.
All rights, ownership, and copyrights belong entirely to the original creators at the Endless OS Foundation.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 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

+112 -26
View File
@@ -4,9 +4,13 @@
#include <string>
#include <vector>
// Game States & Configurations
// Game states and configurations
enum class GameState {
MainMenu,
DifficultySelect,
MultiplayerLobby,
Multiplayer,
Settings,
Playing,
Paused,
GameOver
@@ -18,37 +22,103 @@ 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);
// 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;
Color color;
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;
Paddle(Vector2 pos, Color c, float w, float h)
Texture2D texture;
Paddle(Vector2 pos, Color c, float w, float h, int textureId = 0)
: GameObject(pos, c), width(w), height(h) {
if (textureId > 0) {
texture = LoadTextureFromResource(textureId);
} else {
texture = { 0 };
}
}
void Update() override;
~Paddle() override {
if (texture.id > 0) {
UnloadTexture(texture);
}
}
bool Update() override;
void Draw() override;
};
@@ -56,12 +126,25 @@ class Ball : public GameObject {
public:
float radius;
Vector2 velocity;
Texture2D texture;
Ball(Vector2 pos, Color c, float r)
public:
Ball(Vector2 pos, Color c, float r, int textureId = 0)
: GameObject(pos, c), radius(r), velocity({ 5.0f, 5.0f }) {
if (textureId > 0) {
texture = LoadTextureFromResource(textureId);
} else {
texture = { 0 };
}
}
void Update() override;
~Ball() override {
if (texture.id > 0) {
UnloadTexture(texture);
}
}
bool Update() override;
void Draw() override;
};
@@ -71,8 +154,8 @@ private:
Difficulty currentDifficulty;
public:
CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal)
: Paddle(pos, c, w, h) {
CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal, int textureId = 0)
: Paddle(pos, c, w, h, textureId) {
SetDifficulty(diff);
}
@@ -87,25 +170,28 @@ 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 <= 0) {
position.y = 0;
if (position.y <= 20.0f) {
position.y = 20.0f;
}
if (position.y + height >= GetScreenHeight()) {
position.y = GetScreenHeight() - height;
if (position.y + height >= GetScreenHeight() - 20.0f) {
position.y = GetScreenHeight() - 20.0f - height;
}
}
};
+11 -4
View File
@@ -3,8 +3,7 @@
#include <string>
#include <vector>
// --- UI / Systems ---
// Menu UI controller
class Menu {
private:
std::string title;
@@ -16,6 +15,14 @@ public:
: title(menuTitle), options(menuOptions), selectedIndex(0) {
}
void Update(GameState& currentState);
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
+21
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>
@@ -143,8 +144,28 @@
<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\textures\ball\basic_ball_5.png" />
<Image Include="assets\textures\hud\line.png" />
<Image Include="assets\textures\paddles\basic_paddle.png" />
<Image Include="assets\textures\paddles\basic_paddle_2.png" />
<Image Include="assets\textures\spaces\basic_space.png" />
<Image Include="assets\textures\spaces\walls.png" />
<Image Include="icon.ico" />
<Image Include="icon.jpg" />
</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>
+61 -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>
@@ -16,6 +12,16 @@
<Filter Include="Header Files\RayLib">
<UniqueIdentifier>{2f179593-5e9f-4095-be57-3f12f03a9705}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files\textures">
<UniqueIdentifier>{2d4ce555-d6fc-4e9c-a30e-eadb4c1336d0}</UniqueIdentifier>
</Filter>
<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">
@@ -27,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">
@@ -44,5 +53,53 @@
<ClInclude Include="include\menu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="assets\textures\ball\basic_ball_5.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="assets\textures\hud\line.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="assets\textures\paddles\basic_paddle.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="assets\textures\paddles\basic_paddle_2.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="assets\textures\spaces\basic_space.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="assets\textures\spaces\walls.png">
<Filter>Resource Files\textures</Filter>
</Image>
<Image Include="icon.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="icon.jpg">
<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 "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"
+345 -26
View File
@@ -1,43 +1,362 @@
#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
if (position.y <= 0) {
position.y = 0;
// Keep paddle within screen boundaries
if (position.y <= 20.0f) {
position.y = 20.0f;
}
if (position.y + height >= GetScreenHeight()) {
position.y = GetScreenHeight() - height;
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() {
DrawRectangleRounded(Rectangle{ position.x, position.y, width, height }, 0.8f, 0, color);
}
// --- Ball Implementation ---
void Ball::Update() {
position.x += velocity.x;
position.y += velocity.y;
if (position.y + radius >= GetScreenHeight() || position.y - radius <= 0) {
velocity.y *= -1;
if (texture.id > 0) {
DrawTexturePro(
texture,
Rectangle{ 0.0f, 0.0f, (float)texture.width, (float)texture.height },
Rectangle{ position.x, position.y, width, height },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
} else {
DrawRectangleRounded(Rectangle{ position.x, position.y, width, height }, 0.8f, 0, color);
}
}
// 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() {
// Cast to int as DrawCircle expects integers for coordinates
DrawCircle((int)position.x, (int)position.y, radius, color);
}
if (texture.id > 0) {
DrawTexturePro(
texture,
Rectangle{ 0.0f, 0.0f, (float)texture.width, (float)texture.height },
Rectangle{ position.x - radius, position.y - radius, radius * 2.0f, radius * 2.0f },
Vector2{ 0.0f, 0.0f },
0.0f,
WHITE
);
} else {
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);
}
+145 -67
View File
@@ -1,104 +1,173 @@
#include <iostream>
#include "game.h"
#include "menu.h"
#include "resource.h"
// Colors
// 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 };
int player_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)];
}
int main() {
std::cout << "Starting the game" << std::endl;
const int screen_width = 1280;
const int screen_height = 800;
InitWindow(screen_width, screen_height, "My Pong Game!");
std::cout << "Starting game session" << std::endl;
int screen_width = 1280;
int screen_height = 800;
InitWindow(screen_width, screen_height, "Pong Reloaded");
SetTargetFPS(60);
InitAudioDevice();
// --- Instantiate Objects using the new Constructors ---
// App state context
GameContext ctx;
Ball ball(Vector2{ screen_width / 2.0f, screen_height / 2.0f }, Yellow, 20.0f);
ball.velocity = Vector2{ 7.0f, 7.0f };
// 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,
IDR_TEX_BALL
);
ResetBall(ball);
Paddle player(
Vector2{ screen_width - 35.0f, screen_height / 2.0f - 60.0f },
WHITE, 25.0f, 120.0f
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
WHITE, 25.0f, 120.0f,
IDR_TEX_PADDLE
);
CpuPaddle cpu(
Vector2{ 10.0f, screen_height / 2.0f - 60.0f },
Vector2{ 20.0f + 10.0f, screen_height / 2.0f - 60.0f },
WHITE, 25.0f, 120.0f,
Difficulty::Normal
);
Difficulty::Normal,
IDR_TEX_PADDLE2
);
// --- Setup Menu and Game State ---
// Menu instantiation
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
GameState currentState = GameState::MainMenu;
Menu mainMenu("PONG RELOADED", { "Start Game", "Quit" });
// Main loop
while (WindowShouldClose() == false && !ctx.shouldQuit) {
screen_width = GetScreenWidth();
screen_height = GetScreenHeight();
// --- Main Game Loop ---
// Update loop
switch (ctx.currentState) {
case GameState::MainMenu:
{
int selected = mainMenu.Update();
if (selected == 0) {
ctx.isMultiplayer = false;
ctx.currentState = GameState::DifficultySelect;
}
else if (selected == 1) {
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) {
ctx.currentState = GameState::Settings;
}
else if (selected == 3) {
ctx.shouldQuit = true;
}
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);
while (WindowShouldClose() == false && currentState != GameState::GameOver) {
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 (currentState) {
switch (ctx.currentState) {
case GameState::MainMenu:
{
mainMenu.Update(currentState);
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::Playing:
case GameState::DifficultySelect:
{
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) {
cpu_score++;
ResetBall(ball, screen_width, screen_height);
}
if (ball.position.x - ball.radius <= 0) {
player_score++;
ResetBall(ball, screen_width, screen_height);
}
DrawRectangle(screen_width / 2, 0, screen_width / 2, screen_height, Green);
DrawCircle(screen_width / 2, screen_height / 2, 150, Light_Green);
DrawLine(screen_width / 2, 0, screen_width / 2, screen_height, WHITE);
DrawText(TextFormat("%i", cpu_score), screen_width / 4 - 20, 20, 80, WHITE);
DrawText(TextFormat("%i", player_score), 3 * screen_width / 4 - 20, 20, 80, WHITE);
ball.Draw();
cpu.Draw();
player.Draw();
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;
}
@@ -106,6 +175,15 @@ int main() {
EndDrawing();
}
// 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;
}
+233 -15
View File
@@ -1,37 +1,255 @@
#include "menu.h"
void Menu::Update(GameState& currentState) {
// Handle Navigation
int Menu::Update() {
// Menu navigation
if (IsKeyPressed(KEY_DOWN)) {
selectedIndex++;
if (selectedIndex >= options.size()) selectedIndex = 0;
if (selectedIndex >= static_cast<int>(options.size())) selectedIndex = 0;
}
if (IsKeyPressed(KEY_UP)) {
selectedIndex--;
if (selectedIndex < 0) selectedIndex = options.size() - 1;
if (selectedIndex < 0) selectedIndex = static_cast<int>(options.size()) - 1;
}
// Handle Selection
// Option selection
if (IsKeyPressed(KEY_ENTER)) {
if (options[selectedIndex] == "Start Game") {
currentState = GameState::Playing;
}
else if (options[selectedIndex] == "Quit") {
currentState = GameState::GameOver;
}
return selectedIndex;
}
return -1;
}
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 < options.size(); i++) {
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);
}
}
}
// 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);
}
+52
View File
@@ -0,0 +1,52 @@
#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;
}