25 Commits

Author SHA1 Message Date
dr20ervin 88a90fa7ac Improve build, UI, and add GitHub issue templates 2026-05-27 02:57:07 +03:00
dr20ervin f6749f80e1 Add game version display and DevLog global variables 2026-05-27 02:41:46 +03:00
dr20ervin 73f103f8d2 Enhance README with new sections and improved structure 2026-05-27 02:35:24 +03:00
dr20ervin c606bd315e Added new assets 2026-05-27 01:43:30 +03:00
dr20ervin 20309da2af Clean up unused header files in main.h 2026-05-27 00:20:27 +03:00
dr20ervin cc718ee511 Refactor global variable declarations and usage 2026-05-27 00:18:54 +03:00
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
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
40 changed files with 1575 additions and 154 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.
+32
View File
@@ -0,0 +1,32 @@
---
name: 🐛 Bug Report
about: Create a report to help us improve Pong Reloaded
title: '[BUG] '
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS: [e.g. Windows 11]
- Display Resolution: [e.g. 1920x1080]
- Graphic Settings used: [e.g. Fullscreen, VSync, 60fps]
**Additional Context**
Add any other context about the problem here (e.g. logs from the F7 Developer Console).
+20
View File
@@ -0,0 +1,20 @@
---
name: ✨ Feature Request
about: Suggest an idea or enhancement for Pong Reloaded
title: '[FEATURE] '
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+47
View File
@@ -0,0 +1,47 @@
# 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}} /p:Platform=x64 ${{env.SOLUTION_FILE_PATH}}
- name: Upload Build Artifact
uses: actions/upload-artifact@v4
with:
name: pong-reloaded-exe
path: x64/Release/pong-reloaded.exe
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) [year] [fullname]
Copyright (c) 2026 Dr20Ervin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+99 -7
View File
@@ -1,10 +1,102 @@
# Pong Reloaded
# 🏓 Pong Reloaded
A classic arcade game rebuilt from the ground up using modern C++ and the Raylib library.
A classic arcade game rebuilt from the ground up using **Modern C++ (C++20)** and the **Raylib** library. Designed with performance, customization, and modularity in mind.
**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).
[![C++ Version](https://img.shields.io/badge/Language-C%2B%2B20-blue?logo=c%2B%2B&style=flat-square)](https://en.cppreference.com/w/cpp/compiler_support/20)
[![Raylib](https://img.shields.io/badge/Framework-Raylib--5.0-red?style=flat-square)](https://www.raylib.com/)
[![Platform](https://img.shields.io/badge/Platform-Windows-0078d7?logo=windows&style=flat-square)](https://www.microsoft.com/windows)
[![License](https://img.shields.io/badge/License-MIT-green?style=flat-square)](LICENSE.txt)
*This is a project developed for UTCN - ETTI Helios Additional_activity.*
---
## 🚀 About & Motivation
**Pong Reloaded** is an educational and exploratory game development project. Built as a student engineering activity, it serves as a personal challenge to push past standard university curricula and experiment with low-level graphic structures.
The key goals of this project:
* **Master Raylib & Game Design:** Experiment with 2D rendering pipelines, sound synthesis, custom sprites, and real-time game state management.
* **Optimize for Low Resources:** Implement performance-first programming in C++, minimizing memory overhead and achieving a highly optimized execution path that runs perfectly on low-end laptops.
* **Challenge Knowledge Boundaries:** Apply advanced Object-Oriented Programming (OOP) concepts, robust state machine architectures, and direct Win32 API integrations.
> [!NOTE]
> This project is developed within the **Helios Additional Activity** initiative at the **Technical University of Cluj-Napoca (UTCN)** *Faculty of Electronics, Telecommunications and Information Technology (ETTI)*.
---
## 🎮 Features
* **👾 Zero-Dependency Portability:** The game compiles into a single, standalone executable. Textures, audio, and icon files are embedded directly into the binary's resource section (`.rc`) using Windows PE resources, ensuring it never breaks due to missing assets.
* **⚡ Highly Optimized Engine:** Optimized game loops, zero-allocation physics updates in the hot-path, and efficient CPU sleeping patterns. Smooth frame rates on hardware with minimal system resources.
* **🤖 Dynamic AI Opponent:** A singleplayer CPU opponent powered by adaptive movement algorithms with three difficulty settings: **Easy**, **Normal**, and **Hard**.
* **👥 Local Co-op Multiplayer:** Local 1v1 couch-co-op mode featuring an interactive **Multiplayer Lobby** where both players toggle ready states before initiating the match.
* **🎨 Premium Settings & Theme Engine:**
* **Screen Settings:** Customizable resolutions (1280x800, 1600x900, 1920x1080) and Fullscreen / Windowed modes.
* **Framerate Controls:** Selectable 60 FPS, 144 FPS, or VSync limits.
* **Score Limits:** Custom thresholds (5, 11, 15, or 21 points).
* **Visual Themes:** Toggleable background space themes (Default, Galaxy, Trap), custom ball skins, and paddle textures.
* **🛠️ Developer Console:** Built-in console overlay toggled dynamically via `F7` for real-time engine tracing and logging.
---
## 💾 Quick Install & Play
No installers, DLLs, or complex dependency installations are required!
1. Head over to the [**Latest Releases**](https://github.com/Dr20Ervin/Pong-Reloaded/releases) section.
2. Download the `pong-reloaded.exe` binary.
3. Double-click the `.exe` and start playing immediately!
---
## ⌨️ Controls Reference
| Context | Action / Key | Player 1 (Left Paddle) | Player 2 / Singleplayer (Right Paddle) |
| :--- | :--- | :--- | :--- |
| **Menu Navigation** | Up / Down | — | `Arrow Up` / `Arrow Down` |
| **Menu Confirm** | Enter | — | `Enter` |
| **Lobby Ready Toggle** | Ready State | `W` | `Arrow Up` |
| **Gameplay Movement**| Up / Down | `W` / `S` | `Arrow Up` / `Arrow Down` |
| **Gameplay Actions** | Pause / Resume | — | `P` |
| **Gameplay Actions** | Force Game Over / Reset | — | `Space` |
| **Developer Tools** | Toggle Dev Console | — | `F7` |
---
## 🛠️ Building From Source
### Prerequisites
* Windows OS
* Visual Studio 2022 (with "Desktop development with C++" workload)
* Raylib library installed/configured
### Steps
1. Clone this repository:
```bash
git clone https://github.com/Dr20Ervin/Pong-Reloaded.git
cd Pong-Reloaded
```
2. Open `pong-reloaded.sln` in Visual Studio.
3. Build the project under `Release` / `x64` configuration to generate the optimized, standalone executable.
---
## 📜 Credits & Attributions
### Codebase Template
* **Original Author:** educ8s (Nick Koumaris)
* **Original Repository:** [educ8s/Cpp-Pong-Game-Raylib](https://github.com/educ8s/Cpp-Pong-Game-Raylib)
* This project adapts core architectural concepts and Raylib integrations from the original repository.
### Asset Credits
The graphical and audio resources used in this game were curated from the open-source gaming community:
* **Original Project:** Moddable Pong
* **Author/Creator:** Endless OS Foundation & Endless Studios
* **Website:** [endlessos.org](https://endlessos.org)
* **Repository:** [endlessm/moddable-pong](https://github.com/endlessm/moddable-pong/)
* *Notice:* All assets are utilized strictly for academic research, non-commercial development, and validation milestones. All rights and copyrights belong to their respective owners.
---
## 📄 License
This project is licensed under the [MIT License](LICENSE.txt).
+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.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

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

+119 -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,51 +22,137 @@ 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 spaceThemeOption = 0; // 0 = Default, 1 = Galaxy, 2 = Trap
int ballThemeOption = 0; // 0 = Default, 1 = Banana, 2 = Beach, 3 = Cloudy, 4 = Simple
int paddleThemeOption = 0; // 0 = Default, 1 = Cloudy, 2 = CPU, 3 = Human
int selectedSettingLine = 0; // 0 = Resolution, 1 = Framerate, 2 = Screen Mode, 3 = Score Limit, 4 = Sound, 5 = Space, 6 = Ball, 7 = Paddle, 8 = 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;
void ApplyPaddleTextures(const GameContext& ctx, Paddle& player, CpuPaddle& cpu);
// 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;
void ReloadTexture(int resourceId);
};
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;
void ReloadTexture(int resourceId);
};
class CpuPaddle : public Paddle {
@@ -71,8 +161,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 +177,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;
}
}
};
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <mutex>
#include <raylib.h>
// Global colors
extern Color Green;
extern Color Dark_Green;
extern Color Light_Green;
extern Color Yellow;
// DevLog buffering & global variables
extern std::vector<std::string> logHistory;
extern std::mutex logMutex;
extern bool isConsoleVisible;
void CustomLogCallback(int logLevel, const char* text, va_list args);
+12 -4
View File
@@ -1,10 +1,10 @@
#pragma once
#include "game.h"
#include <string>
#include <vector>
// --- UI / Systems ---
// Menu UI controller
class Menu {
private:
std::string title;
@@ -16,6 +16,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, Ball& ball, Paddle& player, CpuPaddle& cpu);
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
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#define IDI_ICON1 100
// Backgrounds
#define IDR_TEX_SPACE_DEFAULT 101
#define IDR_TEX_SPACE_GALAXY 107
#define IDR_TEX_SPACE_TRAP 108
#define IDR_TEX_WALLS 102
#define IDR_TEX_LINE 103
// Balls
#define IDR_TEX_BALL_DEFAULT 104
#define IDR_TEX_BALL_BANANA 109
#define IDR_TEX_BALL_BEACH 110
#define IDR_TEX_BALL_CLOUDY 111
#define IDR_TEX_BALL_SIMPLE 112
// Paddles
#define IDR_TEX_PADDLE_DEFAULT 105
#define IDR_TEX_PADDLE_CLOUDY 106
#define IDR_TEX_PADDLE_CPU 113
#define IDR_TEX_PADDLE_HUMAN 114
// Audio
#define IDR_SND_PADDLE_HIT 201
#define IDR_SND_WALL_HIT 202
#define IDR_SND_SCORE 203
#define IDR_SND_BANANA 204
+29
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,11 +141,39 @@
</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\banana_ball.png" />
<Image Include="assets\textures\ball\beach_ball.png" />
<Image Include="assets\textures\ball\cloudy__ball.png" />
<Image Include="assets\textures\ball\default_ball.png" />
<Image Include="assets\textures\ball\simple_ball.png" />
<Image Include="assets\textures\hud\line.png" />
<Image Include="assets\textures\paddles\cloudy_paddle.png" />
<Image Include="assets\textures\paddles\cpu_paddle.png" />
<Image Include="assets\textures\paddles\default_paddle.png" />
<Image Include="assets\textures\paddles\human_paddle.png" />
<Image Include="assets\textures\spaces\default_space.png" />
<Image Include="assets\textures\spaces\galaxy_space.png" />
<Image Include="assets\textures\spaces\trap_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>
+97 -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,28 @@
<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>
<Filter Include="Resource Files\textures\ball">
<UniqueIdentifier>{89cd7d41-de3f-4bd4-a3b4-c7aab8c5ffe9}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files\textures\hud">
<UniqueIdentifier>{3a7b2941-0560-4ab7-b8eb-255ef37bd3a4}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files\textures\paddles">
<UniqueIdentifier>{03e96034-55b9-4397-9f56-467d7c351e20}</UniqueIdentifier>
</Filter>
<Filter Include="Resource Files\textures\spaces">
<UniqueIdentifier>{5b322e46-cf19-4411-bcdd-5c3ca59fc236}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\main.cpp">
@@ -27,6 +45,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 +65,77 @@
<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\icon.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="assets\textures\ball\banana_ball.png">
<Filter>Resource Files\textures\ball</Filter>
</Image>
<Image Include="assets\textures\ball\beach_ball.png">
<Filter>Resource Files\textures\ball</Filter>
</Image>
<Image Include="assets\textures\ball\cloudy__ball.png">
<Filter>Resource Files\textures\ball</Filter>
</Image>
<Image Include="assets\textures\ball\default_ball.png">
<Filter>Resource Files\textures\ball</Filter>
</Image>
<Image Include="assets\textures\ball\simple_ball.png">
<Filter>Resource Files\textures\ball</Filter>
</Image>
<Image Include="assets\textures\hud\line.png">
<Filter>Resource Files\textures\hud</Filter>
</Image>
<Image Include="assets\textures\paddles\cloudy_paddle.png">
<Filter>Resource Files\textures\paddles</Filter>
</Image>
<Image Include="assets\textures\paddles\cpu_paddle.png">
<Filter>Resource Files\textures\paddles</Filter>
</Image>
<Image Include="assets\textures\paddles\default_paddle.png">
<Filter>Resource Files\textures\paddles</Filter>
</Image>
<Image Include="assets\textures\paddles\human_paddle.png">
<Filter>Resource Files\textures\paddles</Filter>
</Image>
<Image Include="assets\textures\spaces\walls.png">
<Filter>Resource Files\textures\spaces</Filter>
</Image>
<Image Include="assets\textures\spaces\default_space.png">
<Filter>Resource Files\textures\spaces</Filter>
</Image>
<Image Include="assets\textures\spaces\galaxy_space.png">
<Filter>Resource Files\textures\spaces</Filter>
</Image>
<Image Include="assets\textures\spaces\trap_space.png">
<Filter>Resource Files\textures\spaces</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>
+30
View File
@@ -0,0 +1,30 @@
#include "include/resource.h"
IDI_ICON1 ICON "assets/icon.ico"
// Backgrounds
IDR_TEX_SPACE_DEFAULT RCDATA "assets/textures/spaces/default_space.png"
IDR_TEX_SPACE_GALAXY RCDATA "assets/textures/spaces/galaxy_space.png"
IDR_TEX_SPACE_TRAP RCDATA "assets/textures/spaces/trap_space.png"
IDR_TEX_WALLS RCDATA "assets/textures/spaces/walls.png"
IDR_TEX_LINE RCDATA "assets/textures/hud/line.png"
// Balls
IDR_TEX_BALL_DEFAULT RCDATA "assets/textures/ball/default_ball.png"
IDR_TEX_BALL_BANANA RCDATA "assets/textures/ball/banana_ball.png"
IDR_TEX_BALL_BEACH RCDATA "assets/textures/ball/beach_ball.png"
IDR_TEX_BALL_CLOUDY RCDATA "assets/textures/ball/cloudy__ball.png"
IDR_TEX_BALL_SIMPLE RCDATA "assets/textures/ball/simple_ball.png"
// Paddles
IDR_TEX_PADDLE_DEFAULT RCDATA "assets/textures/paddles/default_paddle.png"
IDR_TEX_PADDLE_CLOUDY RCDATA "assets/textures/paddles/cloudy_paddle.png"
IDR_TEX_PADDLE_CPU RCDATA "assets/textures/paddles/cpu_paddle.png"
IDR_TEX_PADDLE_HUMAN RCDATA "assets/textures/paddles/human_paddle.png"
// Audio
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"
+384 -26
View File
@@ -1,43 +1,401 @@
#include "game.h"
#include "main.h"
#include "resource.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);
}
}
void Paddle::ReloadTexture(int resourceId) {
if (texture.id > 0) {
UnloadTexture(texture);
}
if (resourceId > 0) {
texture = LoadTextureFromResource(resourceId);
} else {
texture = { 0 };
}
}
// 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);
}
}
void Ball::ReloadTexture(int resourceId) {
if (texture.id > 0) {
UnloadTexture(texture);
}
if (resourceId > 0) {
texture = LoadTextureFromResource(resourceId);
} else {
texture = { 0 };
}
}
// 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 = Green;
}
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 = Green;
}
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);
}
void ApplyPaddleTextures(const GameContext& ctx, Paddle& player, CpuPaddle& cpu) {
int playerResId = IDR_TEX_PADDLE_DEFAULT;
if (ctx.config.paddleThemeOption == 1) playerResId = IDR_TEX_PADDLE_CLOUDY;
else if (ctx.config.paddleThemeOption == 2) playerResId = IDR_TEX_PADDLE_CPU;
else if (ctx.config.paddleThemeOption == 3) playerResId = IDR_TEX_PADDLE_HUMAN;
player.ReloadTexture(playerResId);
if (ctx.isMultiplayer) {
cpu.ReloadTexture(playerResId);
} else {
cpu.ReloadTexture(IDR_TEX_PADDLE_CPU);
}
}
+227 -68
View File
@@ -1,104 +1,225 @@
#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 };
// Application Version Metadata
constexpr const char* gameVersion = "1.0";
// Global Theme Color Configurations
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 };
Color Yellow = Color{ 243, 213, 91, 255 };
int player_score = 0;
int cpu_score = 0;
std::vector<std::string> logHistory;
std::mutex logMutex;
bool isConsoleVisible = false;
// 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();
}
#endif
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;
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;
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_SPACE_DEFAULT);
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_DEFAULT
);
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_DEFAULT
);
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_PADDLE_DEFAULT
);
// --- 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 ---
#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 && currentState != GameState::GameOver) {
// Update loop
switch (ctx.currentState) {
case GameState::MainMenu:
{
int selected = mainMenu.Update();
if (selected == 0) {
TraceLog(LOG_INFO, "State Transition: MainMenu -> DifficultySelect (Singleplayer)");
ctx.isMultiplayer = false;
ctx.currentState = GameState::DifficultySelect;
}
else if (selected == 1) {
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) {
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); 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;
ApplyPaddleTextures(ctx, player, cpu);
}
else if (selected == 3) {
ctx.currentState = GameState::MainMenu;
}
break;
}
case GameState::MultiplayerLobby:
{
GameState oldState = ctx.currentState;
UpdateLobbyState(ctx);
if (ctx.currentState == GameState::Multiplayer && oldState != GameState::Multiplayer) {
ApplyPaddleTextures(ctx, player, cpu);
}
break;
}
case GameState::Multiplayer:
UpdateMultiplayerState(ctx, ball, player, cpu);
break;
case GameState::Settings:
UpdateSettingsState(ctx, ball, player, cpu);
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();
// Render navigation controls hint
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);
// Render version string in bottom-left corner
DrawText(TextFormat("v%s", gameVersion), 20, screen_height - 40, 20, LIGHTGRAY);
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 +227,44 @@ 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;
}
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
}
+342 -17
View File
@@ -1,37 +1,362 @@
#include "menu.h"
#include "main.h"
#include "resource.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
DrawText(title.c_str(), screenWidth / 2 - MeasureText(title.c_str(), 60) / 2, screenHeight / 4, 60, WHITE);
// Render Menu Title (aligned down for balanced spacing)
DrawText(title.c_str(), screenWidth / 2 - MeasureText(title.c_str(), 60) / 2, screenHeight / 4 + 20, 60, WHITE);
// Draw Options
for (int i = 0; i < options.size(); i++) {
int numOptions = static_cast<int>(options.size());
int spacing = 60;
// Isolate "Back" buttons at the bottom; group all other menu items in the center
bool separateLast = (!options.empty() && options.back() == "Back");
int groupCount = separateLast ? (numOptions - 1) : numOptions;
// Center grouped options vertically
int startY = (screenHeight / 2) - (((groupCount - 1) * spacing) / 2);
for (int i = 0; i < numOptions; 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);
if (separateLast && i == numOptions - 1) {
// Render separated "Back" action button at the bottom of the screen
DrawText(options[i].c_str(), screenWidth / 2 - MeasureText(options[i].c_str(), 40) / 2, screenHeight - 120, 40, textColor);
} else {
// Render standard grouped options in the center
DrawText(options[i].c_str(), screenWidth / 2 - MeasureText(options[i].c_str(), 40) / 2, startY + (i * spacing), 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, Ball& ball, Paddle& player, CpuPaddle& cpu) {
// Menu navigation
if (IsKeyPressed(KEY_UP)) {
ctx.config.selectedSettingLine--;
if (ctx.config.selectedSettingLine < 0) ctx.config.selectedSettingLine = 8;
}
if (IsKeyPressed(KEY_DOWN)) {
ctx.config.selectedSettingLine++;
if (ctx.config.selectedSettingLine > 8) 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) {
bool changed = false;
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
ctx.config.spaceThemeOption = (ctx.config.spaceThemeOption + 1) % 3;
changed = true;
}
else if (IsKeyPressed(KEY_LEFT)) {
ctx.config.spaceThemeOption = (ctx.config.spaceThemeOption - 1 + 3) % 3;
changed = true;
}
if (changed) {
int resId = IDR_TEX_SPACE_DEFAULT;
if (ctx.config.spaceThemeOption == 1) resId = IDR_TEX_SPACE_GALAXY;
else if (ctx.config.spaceThemeOption == 2) resId = IDR_TEX_SPACE_TRAP;
if (ctx.courtBackground.id > 0) {
UnloadTexture(ctx.courtBackground);
}
ctx.courtBackground = LoadTextureFromResource(resId);
}
}
else if (ctx.config.selectedSettingLine == 6) {
bool changed = false;
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
ctx.config.ballThemeOption = (ctx.config.ballThemeOption + 1) % 5;
changed = true;
}
else if (IsKeyPressed(KEY_LEFT)) {
ctx.config.ballThemeOption = (ctx.config.ballThemeOption - 1 + 5) % 5;
changed = true;
}
if (changed) {
int resId = IDR_TEX_BALL_DEFAULT;
if (ctx.config.ballThemeOption == 1) resId = IDR_TEX_BALL_BANANA;
else if (ctx.config.ballThemeOption == 2) resId = IDR_TEX_BALL_BEACH;
else if (ctx.config.ballThemeOption == 3) resId = IDR_TEX_BALL_CLOUDY;
else if (ctx.config.ballThemeOption == 4) resId = IDR_TEX_BALL_SIMPLE;
ball.ReloadTexture(resId);
}
}
else if (ctx.config.selectedSettingLine == 7) {
bool changed = false;
if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) {
ctx.config.paddleThemeOption = (ctx.config.paddleThemeOption + 1) % 4;
changed = true;
}
else if (IsKeyPressed(KEY_LEFT)) {
ctx.config.paddleThemeOption = (ctx.config.paddleThemeOption - 1 + 4) % 4;
changed = true;
}
if (changed) {
int resId = IDR_TEX_PADDLE_DEFAULT;
if (ctx.config.paddleThemeOption == 1) resId = IDR_TEX_PADDLE_CLOUDY;
else if (ctx.config.paddleThemeOption == 2) resId = IDR_TEX_PADDLE_CPU;
else if (ctx.config.paddleThemeOption == 3) resId = IDR_TEX_PADDLE_HUMAN;
player.ReloadTexture(resId);
cpu.ReloadTexture(resId);
}
}
else if (ctx.config.selectedSettingLine == 8) {
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 + 20, 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";
std::string spaceThemeStr = "Space Theme: ";
if (ctx.config.spaceThemeOption == 0) spaceThemeStr += "Default";
else if (ctx.config.spaceThemeOption == 1) spaceThemeStr += "Galaxy";
else if (ctx.config.spaceThemeOption == 2) spaceThemeStr += "Trap";
std::string ballThemeStr = "Ball Theme: ";
if (ctx.config.ballThemeOption == 0) ballThemeStr += "Default";
else if (ctx.config.ballThemeOption == 1) ballThemeStr += "Banana";
else if (ctx.config.ballThemeOption == 2) ballThemeStr += "Beach";
else if (ctx.config.ballThemeOption == 3) ballThemeStr += "Cloudy";
else if (ctx.config.ballThemeOption == 4) ballThemeStr += "Simple";
std::string paddleThemeStr = "Paddle Theme: ";
if (ctx.config.paddleThemeOption == 0) paddleThemeStr += "Default";
else if (ctx.config.paddleThemeOption == 1) paddleThemeStr += "Cloudy";
else if (ctx.config.paddleThemeOption == 2) paddleThemeStr += "CPU Style";
else if (ctx.config.paddleThemeOption == 3) paddleThemeStr += "Human Style";
// 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 spaceColor = (ctx.config.selectedSettingLine == 5) ? YELLOW : WHITE;
Color ballColor = (ctx.config.selectedSettingLine == 6) ? YELLOW : WHITE;
Color paddleColor = (ctx.config.selectedSettingLine == 7) ? YELLOW : WHITE;
Color backColor = (ctx.config.selectedSettingLine == 8) ? YELLOW : WHITE;
// Render settings toggles centered in the middle of the screen
int startY = 300;
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 + 40, 30, fpsColor);
DrawText(modeStr.c_str(), screenWidth / 2 - MeasureText(modeStr.c_str(), 30) / 2, startY + 80, 30, modeColor);
DrawText(scoreLimitStr.c_str(), screenWidth / 2 - MeasureText(scoreLimitStr.c_str(), 30) / 2, startY + 120, 30, scoreColor);
DrawText(sfxStr.c_str(), screenWidth / 2 - MeasureText(sfxStr.c_str(), 30) / 2, startY + 160, 30, sfxColor);
DrawText(spaceThemeStr.c_str(), screenWidth / 2 - MeasureText(spaceThemeStr.c_str(), 30) / 2, startY + 200, 30, spaceColor);
DrawText(ballThemeStr.c_str(), screenWidth / 2 - MeasureText(ballThemeStr.c_str(), 30) / 2, startY + 240, 30, ballColor);
DrawText(paddleThemeStr.c_str(), screenWidth / 2 - MeasureText(paddleThemeStr.c_str(), 30) / 2, startY + 280, 30, paddleColor);
// Render separated "Back" action button at the bottom of the screen
DrawText("Back", screenWidth / 2 - MeasureText("Back", 30) / 2, screenHeight - 120, 30, backColor);
// Render bottom hint for settings controls
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);
}
}