Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d0084cf87 | |||
| 82d978dfb8 | |||
| d9fcbd3204 | |||
| e749965459 | |||
| 937912765a | |||
| fa687286c2 | |||
| 88a90fa7ac | |||
| f6749f80e1 | |||
| 73f103f8d2 | |||
| c606bd315e | |||
| 20309da2af | |||
| cc718ee511 | |||
| 6c8fa690d3 | |||
| fac5b95a82 | |||
| b7954e4234 | |||
| 45462a3ba7 | |||
| 7493daf004 | |||
| 0573b384b3 | |||
| a7e123c045 | |||
| ab69b5d2e5 | |||
| 0aeff08e38 | |||
| fad1ed6496 | |||
| f042226dcf |
@@ -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).
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL Advanced"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '17 11 * * 1'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze (${{ matrix.language }})
|
||||||
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
|
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||||
|
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
permissions:
|
||||||
|
# required for all workflows
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
# required to fetch internal or private CodeQL packs
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
# only required for workflows in private repositories
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- language: actions
|
||||||
|
build-mode: none
|
||||||
|
- language: c-cpp
|
||||||
|
build-mode: autobuild
|
||||||
|
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
||||||
|
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||||
|
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||||
|
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||||
|
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||||
|
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||||
|
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||||
|
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Add any setup steps before running the `github/codeql-action/init` action.
|
||||||
|
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
||||||
|
# or others). This is typically only required for manual builds.
|
||||||
|
# - name: Setup runtime (example)
|
||||||
|
# uses: actions/setup-example@v1
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v4
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
build-mode: ${{ matrix.build-mode }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
# If the analyze step fails for one of the languages you are analyzing with
|
||||||
|
# "We were unable to automatically build your code", modify the matrix above
|
||||||
|
# to set the build mode to "manual" for that language. Then modify this step
|
||||||
|
# to build your code.
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
- name: Run manual build steps
|
||||||
|
if: matrix.build-mode == 'manual'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||||
|
'languages you are analyzing, replace this with the commands to build' \
|
||||||
|
'your code, for example:'
|
||||||
|
echo ' make bootstrap'
|
||||||
|
echo ' make release'
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v4
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
@@ -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,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) [year] [fullname]
|
Copyright (c) 2026 Dr20Ervin
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,30 +1,122 @@
|
|||||||
# Pong Reloaded
|
<p align="center">
|
||||||
|
<img src="icon.svg" alt="Pong Reloaded Logo" width="128" height="128" />
|
||||||
|
</p>
|
||||||
|
|
||||||
A classic arcade game rebuilt from the ground up using modern C++ and the Raylib library.
|
<h1 align="center">🏓 Pong Reloaded</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
A classic arcade game rebuilt from the ground up using <strong>Modern C++ (C++20)</strong> and the <strong>Raylib</strong> library. Designed with performance, customization, and modularity in mind.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://en.cppreference.com/w/cpp/compiler_support/20"><img src="https://img.shields.io/badge/Language-C%2B%2B20-blue?logo=c%2B%2B&style=flat-square" alt="C++ Version" /></a>
|
||||||
|
<a href="https://www.raylib.com/"><img src="https://img.shields.io/badge/Framework-Raylib--5.0-red?style=flat-square" alt="Raylib" /></a>
|
||||||
|
<a href="https://www.microsoft.com/windows"><img src="https://img.shields.io/badge/Platform-Windows-0078d7?logo=windows&style=flat-square" alt="Platform" /></a>
|
||||||
|
<a href="LICENSE.txt"><img src="https://img.shields.io/badge/License-MIT-green?style=flat-square" alt="License" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Credits & Attributions
|
## 🚀 About & Motivation
|
||||||
|
|
||||||
### Code Base Template
|
**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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 Architecture & System Design
|
||||||
|
|
||||||
|
To ensure modularity, high performance, and clear separation of concerns, **Pong Reloaded** is built around a decoupled Object-Oriented design.
|
||||||
|
|
||||||
|
Below is the UML class diagram outlining the relationships between the game entities, screen controllers, state management, and resource loaders:
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="UML.svg" alt="Pong Reloaded UML Diagram" width="100%" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💾 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 Author:** educ8s (Nick Koumaris)
|
||||||
* **Original Repository:** [github.com/educ8s/Cpp-Pong-Game-Raylib](https://github.com/educ8s/Cpp-Pong-Game-Raylib)
|
* **Original Repository:** [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.
|
* This project adapts core architectural concepts and Raylib integrations from the original repository.
|
||||||
|
|
||||||
### Asset Credits & Attribution
|
### Asset Credits
|
||||||
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.
|
The graphical and audio resources used in this game were curated from the open-source gaming community:
|
||||||
* **Original Project:** Moddable Pong
|
* **Original Project:** Moddable Pong
|
||||||
* **Author / Creator:** Endless OS Foundation & Endless Studios
|
* **Author/Creator:** Endless OS Foundation & Endless Studios
|
||||||
* **Website:** [endlessos.org](https://endlessos.org)
|
* **Website:** [endlessos.org](https://endlessos.org)
|
||||||
* **Source Code Repository:** [github.com/endlessm/moddable-pong](https://github.com/endlessm/moddable-pong/)
|
* **Repository:** [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.
|
* *Notice:* All assets are utilized strictly for academic research, non-commercial development, and validation milestones. All rights and copyrights belong to their respective owners.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Features
|
## 📄 License
|
||||||
|
|
||||||
* **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`).
|
This project is licensed under the [MIT License](LICENSE.txt).
|
||||||
* **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).
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,16 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 64 KiB |
@@ -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 |
@@ -4,10 +4,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// Game States & Configurations
|
// Game states and configurations
|
||||||
enum class GameState {
|
enum class GameState {
|
||||||
MainMenu,
|
MainMenu,
|
||||||
DifficultySelect,
|
DifficultySelect,
|
||||||
|
MultiplayerLobby,
|
||||||
Multiplayer,
|
Multiplayer,
|
||||||
Settings,
|
Settings,
|
||||||
Playing,
|
Playing,
|
||||||
@@ -21,12 +22,75 @@ enum class Difficulty {
|
|||||||
Hard
|
Hard
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr float CPU_SPEED_EASY = 3.0f;
|
constexpr float CPU_SPEED_EASY = 180.0f;
|
||||||
constexpr float CPU_SPEED_NORMAL = 5.5f;
|
constexpr float CPU_SPEED_NORMAL = 330.0f;
|
||||||
constexpr float CPU_SPEED_HARD = 8.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 {
|
class GameObject {
|
||||||
public:
|
public:
|
||||||
Vector2 position;
|
Vector2 position;
|
||||||
@@ -34,23 +98,20 @@ public:
|
|||||||
GameObject(Vector2 pos, Color c) : position(pos), color(c) {}
|
GameObject(Vector2 pos, Color c) : position(pos), color(c) {}
|
||||||
virtual ~GameObject() = default;
|
virtual ~GameObject() = default;
|
||||||
|
|
||||||
virtual void Update() = 0;
|
virtual bool Update() = 0;
|
||||||
virtual void Draw() = 0;
|
virtual void Draw() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Game Objects ---
|
// Entity classes
|
||||||
|
|
||||||
class Paddle : public GameObject {
|
class Paddle : public GameObject {
|
||||||
public:
|
public:
|
||||||
float width;
|
float width;
|
||||||
float height;
|
float height;
|
||||||
Texture2D texture;
|
Texture2D texture;
|
||||||
|
Paddle(Vector2 pos, Color c, float w, float h, int textureId = 0)
|
||||||
public:
|
|
||||||
Paddle(Vector2 pos, Color c, float w, float h, const std::string& texturePath = "")
|
|
||||||
: GameObject(pos, c), width(w), height(h) {
|
: GameObject(pos, c), width(w), height(h) {
|
||||||
if (!texturePath.empty()) {
|
if (textureId > 0) {
|
||||||
texture = LoadTexture(texturePath.c_str());
|
texture = LoadTextureFromResource(textureId);
|
||||||
} else {
|
} else {
|
||||||
texture = { 0 };
|
texture = { 0 };
|
||||||
}
|
}
|
||||||
@@ -62,8 +123,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() override;
|
bool Update() override;
|
||||||
void Draw() override;
|
void Draw() override;
|
||||||
|
void ReloadTexture(int resourceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Ball : public GameObject {
|
class Ball : public GameObject {
|
||||||
@@ -73,10 +135,10 @@ public:
|
|||||||
Texture2D texture;
|
Texture2D texture;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Ball(Vector2 pos, Color c, float r, const std::string& texturePath = "")
|
Ball(Vector2 pos, Color c, float r, int textureId = 0)
|
||||||
: GameObject(pos, c), radius(r), velocity({ 5.0f, 5.0f }) {
|
: GameObject(pos, c), radius(r), velocity({ 5.0f, 5.0f }) {
|
||||||
if (!texturePath.empty()) {
|
if (textureId > 0) {
|
||||||
texture = LoadTexture(texturePath.c_str());
|
texture = LoadTextureFromResource(textureId);
|
||||||
} else {
|
} else {
|
||||||
texture = { 0 };
|
texture = { 0 };
|
||||||
}
|
}
|
||||||
@@ -88,8 +150,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() override;
|
bool Update() override;
|
||||||
void Draw() override;
|
void Draw() override;
|
||||||
|
void ReloadTexture(int resourceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
class CpuPaddle : public Paddle {
|
class CpuPaddle : public Paddle {
|
||||||
@@ -98,8 +161,8 @@ private:
|
|||||||
Difficulty currentDifficulty;
|
Difficulty currentDifficulty;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal, const std::string& texturePath = "")
|
CpuPaddle(Vector2 pos, Color c, float w, float h, Difficulty diff = Difficulty::Normal, int textureId = 0)
|
||||||
: Paddle(pos, c, w, h, texturePath) {
|
: Paddle(pos, c, w, h, textureId) {
|
||||||
SetDifficulty(diff);
|
SetDifficulty(diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,18 +177,21 @@ public:
|
|||||||
|
|
||||||
void Update(float ball_y) {
|
void Update(float ball_y) {
|
||||||
float paddleCenter = position.y + (height / 2.0f);
|
float paddleCenter = position.y + (height / 2.0f);
|
||||||
|
float dt = GetFrameTime();
|
||||||
|
float moveAmount = speed * dt;
|
||||||
|
|
||||||
if (paddleCenter > ball_y + speed) {
|
if (paddleCenter > ball_y + moveAmount) {
|
||||||
position.y -= speed;
|
position.y -= moveAmount;
|
||||||
}
|
}
|
||||||
else if (paddleCenter < ball_y - speed) {
|
else if (paddleCenter < ball_y - moveAmount) {
|
||||||
position.y += speed;
|
position.y += moveAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
LimitMovement();
|
LimitMovement();
|
||||||
|
position.x = 20.0f + 10.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update() override {}
|
bool Update() override { return false; }
|
||||||
|
|
||||||
void LimitMovement() {
|
void LimitMovement() {
|
||||||
if (position.y <= 20.0f) {
|
if (position.y <= 20.0f) {
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// --- UI / Systems ---
|
// Menu UI controller
|
||||||
|
|
||||||
class Menu {
|
class Menu {
|
||||||
private:
|
private:
|
||||||
std::string title;
|
std::string title;
|
||||||
@@ -18,4 +18,12 @@ public:
|
|||||||
|
|
||||||
int Update();
|
int Update();
|
||||||
void Draw();
|
void Draw();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Settings and lobby routines
|
||||||
|
void ApplyResolution(int width, int height);
|
||||||
|
void ApplyFramerate(int option);
|
||||||
|
void UpdateSettingsState(GameContext& ctx, 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);
|
||||||
|
|||||||
@@ -1333,7 +1333,7 @@ RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vect
|
|||||||
// Basic shapes collision detection functions
|
// Basic shapes collision detection functions
|
||||||
RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles
|
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 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 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 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
|
RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -131,6 +131,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="src\game.cpp" />
|
<ClCompile Include="src\game.cpp" />
|
||||||
<ClCompile Include="src\main.cpp" />
|
<ClCompile Include="src\main.cpp" />
|
||||||
|
<ClCompile Include="src\resource_loader.cpp" />
|
||||||
<ClCompile Include="src\menu.cpp">
|
<ClCompile Include="src\menu.cpp">
|
||||||
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir)/include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
@@ -140,19 +141,39 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="include\game.h" />
|
<ClInclude Include="include\game.h" />
|
||||||
|
<ClInclude Include="include\main.h" />
|
||||||
<ClInclude Include="include\menu.h" />
|
<ClInclude Include="include\menu.h" />
|
||||||
<ClInclude Include="include\raylib.h" />
|
<ClInclude Include="include\raylib.h" />
|
||||||
<ClInclude Include="include\raymath.h" />
|
<ClInclude Include="include\raymath.h" />
|
||||||
|
<ClInclude Include="include\resource.h" />
|
||||||
<ClInclude Include="include\rlgl.h" />
|
<ClInclude Include="include\rlgl.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="assets\textures\ball\basic_ball_5.png" />
|
<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\hud\line.png" />
|
||||||
<Image Include="assets\textures\paddles\basic_paddle.png" />
|
<Image Include="assets\textures\paddles\cloudy_paddle.png" />
|
||||||
<Image Include="assets\textures\paddles\basic_paddle_2.png" />
|
<Image Include="assets\textures\paddles\cpu_paddle.png" />
|
||||||
<Image Include="assets\textures\spaces\basic_space.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" />
|
<Image Include="assets\textures\spaces\walls.png" />
|
||||||
</ItemGroup>
|
</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" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<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">
|
<Filter Include="Header Files">
|
||||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||||
@@ -22,6 +18,22 @@
|
|||||||
<Filter Include="Resource Files\audio">
|
<Filter Include="Resource Files\audio">
|
||||||
<UniqueIdentifier>{5f42ff18-01e2-4266-a4e0-6dc77ba54e75}</UniqueIdentifier>
|
<UniqueIdentifier>{5f42ff18-01e2-4266-a4e0-6dc77ba54e75}</UniqueIdentifier>
|
||||||
</Filter>
|
</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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="src\main.cpp">
|
<ClCompile Include="src\main.cpp">
|
||||||
@@ -33,6 +45,9 @@
|
|||||||
<ClCompile Include="src\menu.cpp">
|
<ClCompile Include="src\menu.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="src\resource_loader.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="include\raylib.h">
|
<ClInclude Include="include\raylib.h">
|
||||||
@@ -50,25 +65,77 @@
|
|||||||
<ClInclude Include="include\menu.h">
|
<ClInclude Include="include\menu.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\resource.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="include\main.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="assets\textures\ball\basic_ball_5.png">
|
<Image Include="assets\icon.ico">
|
||||||
<Filter>Resource Files\textures</Filter>
|
<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>
|
||||||
<Image Include="assets\textures\hud\line.png">
|
<Image Include="assets\textures\hud\line.png">
|
||||||
<Filter>Resource Files\textures</Filter>
|
<Filter>Resource Files\textures\hud</Filter>
|
||||||
</Image>
|
</Image>
|
||||||
<Image Include="assets\textures\paddles\basic_paddle.png">
|
<Image Include="assets\textures\paddles\cloudy_paddle.png">
|
||||||
<Filter>Resource Files\textures</Filter>
|
<Filter>Resource Files\textures\paddles</Filter>
|
||||||
</Image>
|
</Image>
|
||||||
<Image Include="assets\textures\paddles\basic_paddle_2.png">
|
<Image Include="assets\textures\paddles\cpu_paddle.png">
|
||||||
<Filter>Resource Files\textures</Filter>
|
<Filter>Resource Files\textures\paddles</Filter>
|
||||||
</Image>
|
</Image>
|
||||||
<Image Include="assets\textures\spaces\basic_space.png">
|
<Image Include="assets\textures\paddles\default_paddle.png">
|
||||||
<Filter>Resource Files\textures</Filter>
|
<Filter>Resource Files\textures\paddles</Filter>
|
||||||
|
</Image>
|
||||||
|
<Image Include="assets\textures\paddles\human_paddle.png">
|
||||||
|
<Filter>Resource Files\textures\paddles</Filter>
|
||||||
</Image>
|
</Image>
|
||||||
<Image Include="assets\textures\spaces\walls.png">
|
<Image Include="assets\textures\spaces\walls.png">
|
||||||
<Filter>Resource Files\textures</Filter>
|
<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>
|
</Image>
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -1,24 +1,30 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
// --- Paddle Implementation ---
|
// Paddle implementation
|
||||||
|
bool Paddle::Update() {
|
||||||
void Paddle::Update() {
|
float dt = GetFrameTime();
|
||||||
float speed = 6.0f;
|
|
||||||
|
|
||||||
if (IsKeyDown(KEY_UP)) {
|
if (IsKeyDown(KEY_UP)) {
|
||||||
position.y -= speed;
|
position.y -= PLAYER_SPEED * dt;
|
||||||
}
|
}
|
||||||
if (IsKeyDown(KEY_DOWN)) {
|
if (IsKeyDown(KEY_DOWN)) {
|
||||||
position.y += speed;
|
position.y += PLAYER_SPEED * dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit movement
|
// Keep paddle within screen boundaries
|
||||||
if (position.y <= 20.0f) {
|
if (position.y <= 20.0f) {
|
||||||
position.y = 20.0f;
|
position.y = 20.0f;
|
||||||
}
|
}
|
||||||
if (position.y + height >= GetScreenHeight() - 20.0f) {
|
if (position.y + height >= GetScreenHeight() - 20.0f) {
|
||||||
position.y = GetScreenHeight() - 20.0f - height;
|
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() {
|
void Paddle::Draw() {
|
||||||
@@ -36,16 +42,29 @@ void Paddle::Draw() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Paddle::ReloadTexture(int resourceId) {
|
||||||
|
if (texture.id > 0) {
|
||||||
|
UnloadTexture(texture);
|
||||||
|
}
|
||||||
|
if (resourceId > 0) {
|
||||||
|
texture = LoadTextureFromResource(resourceId);
|
||||||
|
} else {
|
||||||
|
texture = { 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Ball Implementation ---
|
|
||||||
|
|
||||||
void Ball::Update() {
|
// Ball implementation
|
||||||
position.x += velocity.x;
|
bool Ball::Update() {
|
||||||
position.y += velocity.y;
|
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) {
|
if (position.y + radius >= GetScreenHeight() - 20.0f || position.y - radius <= 20.0f) {
|
||||||
velocity.y *= -1;
|
velocity.y *= -1;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ball::Draw() {
|
void Ball::Draw() {
|
||||||
@@ -59,7 +78,324 @@ void Ball::Draw() {
|
|||||||
WHITE
|
WHITE
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Cast to int as DrawCircle expects integers for coordinates
|
|
||||||
DrawCircle((int)position.x, (int)position.y, radius, color);
|
DrawCircle((int)position.x, (int)position.y, radius, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,336 +1,225 @@
|
|||||||
#include <iostream>
|
#include "main.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
// Colors
|
// Application Version Metadata
|
||||||
Color Green = Color{ 38, 185, 154, 255 };
|
constexpr const char* gameVersion = "1.0";
|
||||||
Color Dark_Green = Color{ 20, 160, 133, 255 };
|
|
||||||
|
// 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 Light_Green = Color{ 129, 204, 184, 255 };
|
||||||
Color Yellow = Color{ 243, 213, 91, 255 };
|
Color Yellow = Color{ 243, 213, 91, 255 };
|
||||||
|
|
||||||
struct ScoreBoard
|
std::vector<std::string> logHistory;
|
||||||
{
|
std::mutex logMutex;
|
||||||
int player_score = 0;
|
bool isConsoleVisible = false;
|
||||||
int player2_score = 0;
|
|
||||||
int cpu_score = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
// Helper function to reset the ball
|
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
|
||||||
void ResetBall(Ball& ball, int screenWidth, int screenHeight) {
|
extern "C" {
|
||||||
ball.position.x = screenWidth / 2.0f;
|
int __stdcall AllocConsole();
|
||||||
ball.position.y = screenHeight / 2.0f;
|
int __stdcall FreeConsole();
|
||||||
|
|
||||||
int speed_choices[2] = { -1, 1 };
|
|
||||||
ball.velocity.x *= speed_choices[GetRandomValue(0, 1)];
|
|
||||||
ball.velocity.y *= speed_choices[GetRandomValue(0, 1)];
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
std::cout << "Starting the game" << std::endl;
|
std::cout << "Starting game session" << std::endl;
|
||||||
const int screen_width = 1280;
|
int screen_width = 1280;
|
||||||
const int screen_height = 800;
|
int screen_height = 800;
|
||||||
|
|
||||||
|
SetTraceLogCallback(CustomLogCallback);
|
||||||
InitWindow(screen_width, screen_height, "Pong Reloaded");
|
InitWindow(screen_width, screen_height, "Pong Reloaded");
|
||||||
|
SetWindowIconFromResource(IDI_ICON1);
|
||||||
SetTargetFPS(60);
|
SetTargetFPS(60);
|
||||||
|
InitAudioDevice();
|
||||||
|
|
||||||
// --- Instantiate Objects using the new Constructors ---
|
// App state context
|
||||||
|
GameContext ctx;
|
||||||
|
|
||||||
|
// 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(
|
Ball ball(
|
||||||
Vector2{ screen_width / 2.0f, screen_height / 2.0f },
|
Vector2{ screen_width / 2.0f, screen_height / 2.0f },
|
||||||
Yellow, 20.0f,
|
Yellow, 20.0f,
|
||||||
"assets/textures/ball/basic_ball_5.png"
|
IDR_TEX_BALL_DEFAULT
|
||||||
);
|
);
|
||||||
ball.velocity = Vector2{ 7.0f, 7.0f };
|
ResetBall(ball);
|
||||||
|
|
||||||
Paddle player(
|
Paddle player(
|
||||||
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
|
Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f },
|
||||||
WHITE, 25.0f, 120.0f,
|
WHITE, 25.0f, 120.0f,
|
||||||
"assets/textures/paddles/basic_paddle.png"
|
IDR_TEX_PADDLE_DEFAULT
|
||||||
);
|
);
|
||||||
|
|
||||||
CpuPaddle cpu(
|
CpuPaddle cpu(
|
||||||
Vector2{ 20.0f + 10.0f, screen_height / 2.0f - 60.0f },
|
Vector2{ 20.0f + 10.0f, screen_height / 2.0f - 60.0f },
|
||||||
WHITE, 25.0f, 120.0f,
|
WHITE, 25.0f, 120.0f,
|
||||||
Difficulty::Normal,
|
Difficulty::Normal,
|
||||||
"assets/textures/paddles/basic_paddle_2.png"
|
IDR_TEX_PADDLE_DEFAULT
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- Setup Menu and Game State ---
|
// Menu instantiation
|
||||||
|
|
||||||
GameState currentState = GameState::MainMenu;
|
|
||||||
float sessionPlayTime = 0.0f;
|
|
||||||
bool isPaused = false;
|
|
||||||
bool shouldQuit = false;
|
|
||||||
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
|
Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" });
|
||||||
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
|
Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" });
|
||||||
ScoreBoard score;
|
|
||||||
|
|
||||||
// --- Load Background and Wall Textures ---
|
// Main loop
|
||||||
Texture2D courtBackground = LoadTexture("assets/textures/spaces/basic_space.png");
|
while (WindowShouldClose() == false && !ctx.shouldQuit) {
|
||||||
Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png");
|
screen_width = GetScreenWidth();
|
||||||
Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png");
|
screen_height = GetScreenHeight();
|
||||||
|
|
||||||
// --- Main Game Loop ---
|
#ifdef _WIN32
|
||||||
|
if (IsKeyPressed(KEY_F7)) {
|
||||||
|
isConsoleVisible = !isConsoleVisible;
|
||||||
|
if (isConsoleVisible) {
|
||||||
|
AllocConsole();
|
||||||
|
FILE* dummy;
|
||||||
|
freopen_s(&dummy, "CONOUT$", "w", stdout);
|
||||||
|
freopen_s(&dummy, "CONOUT$", "w", stderr);
|
||||||
|
|
||||||
|
// Dump history
|
||||||
|
std::lock_guard<std::mutex> lock(logMutex);
|
||||||
|
for (const auto& log : logHistory) {
|
||||||
|
printf("%s", log.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FreeConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
while (WindowShouldClose() == false && !shouldQuit) {
|
// Update loop
|
||||||
BeginDrawing();
|
switch (ctx.currentState) {
|
||||||
ClearBackground(Dark_Green);
|
|
||||||
|
|
||||||
switch (currentState) {
|
|
||||||
case GameState::MainMenu:
|
case GameState::MainMenu:
|
||||||
{
|
{
|
||||||
int selected = mainMenu.Update();
|
int selected = mainMenu.Update();
|
||||||
if (selected == 0) {
|
if (selected == 0) {
|
||||||
currentState = GameState::DifficultySelect;
|
TraceLog(LOG_INFO, "State Transition: MainMenu -> DifficultySelect (Singleplayer)");
|
||||||
|
ctx.isMultiplayer = false;
|
||||||
|
ctx.currentState = GameState::DifficultySelect;
|
||||||
}
|
}
|
||||||
else if (selected == 1) {
|
else if (selected == 1) {
|
||||||
currentState = GameState::Multiplayer;
|
TraceLog(LOG_INFO, "State Transition: MainMenu -> MultiplayerLobby");
|
||||||
|
ctx.isMultiplayer = true;
|
||||||
|
ctx.score.player_score = 0;
|
||||||
|
ctx.score.player2_score = 0;
|
||||||
|
ctx.score.cpu_score = 0;
|
||||||
|
ctx.sessionPlayTime = 0.0f;
|
||||||
|
ctx.isPaused = false;
|
||||||
|
ctx.p1Ready = false;
|
||||||
|
ctx.p2Ready = false;
|
||||||
|
ResetBall(ball);
|
||||||
|
ctx.currentState = GameState::MultiplayerLobby;
|
||||||
}
|
}
|
||||||
else if (selected == 2) {
|
else if (selected == 2) {
|
||||||
currentState = GameState::Settings;
|
ctx.currentState = GameState::Settings;
|
||||||
}
|
}
|
||||||
else if (selected == 3) {
|
else if (selected == 3) {
|
||||||
shouldQuit = true;
|
ctx.shouldQuit = true;
|
||||||
}
|
}
|
||||||
mainMenu.Draw();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case GameState::DifficultySelect:
|
case GameState::DifficultySelect:
|
||||||
{
|
{
|
||||||
int selected = difficultyMenu.Update();
|
int selected = difficultyMenu.Update();
|
||||||
if (selected >= 0 && selected <= 2) {
|
if (selected >= 0 && selected <= 2) {
|
||||||
if (selected == 0) {
|
if (selected == 0) { cpu.SetDifficulty(Difficulty::Easy); TraceLog(LOG_INFO, "CPU Difficulty set to: EASY"); }
|
||||||
cpu.SetDifficulty(Difficulty::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"); }
|
||||||
else if (selected == 1) {
|
|
||||||
cpu.SetDifficulty(Difficulty::Normal);
|
TraceLog(LOG_INFO, "State Transition: DifficultySelect -> Playing");
|
||||||
}
|
ctx.score.player_score = 0;
|
||||||
else if (selected == 2) {
|
ctx.score.player2_score = 0;
|
||||||
cpu.SetDifficulty(Difficulty::Hard);
|
ctx.score.cpu_score = 0;
|
||||||
}
|
ctx.sessionPlayTime = 0.0f;
|
||||||
|
ctx.isPaused = false;
|
||||||
// Reset game session stats
|
ResetBall(ball);
|
||||||
score.player_score = 0;
|
ctx.currentState = GameState::Playing;
|
||||||
score.cpu_score = 0;
|
ApplyPaddleTextures(ctx, player, cpu);
|
||||||
sessionPlayTime = 0.0f;
|
|
||||||
isPaused = false;
|
|
||||||
ResetBall(ball, screen_width, screen_height);
|
|
||||||
currentState = GameState::Playing;
|
|
||||||
}
|
}
|
||||||
else if (selected == 3) {
|
else if (selected == 3) {
|
||||||
currentState = GameState::MainMenu;
|
ctx.currentState = GameState::MainMenu;
|
||||||
}
|
}
|
||||||
difficultyMenu.Draw();
|
|
||||||
break;
|
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:
|
case GameState::Playing:
|
||||||
{
|
UpdatePlayingState(ctx, ball, player, cpu);
|
||||||
// Toggle pause state with 'P' key
|
|
||||||
if (IsKeyPressed(KEY_P)) {
|
|
||||||
isPaused = !isPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force GameOver with SPACEBAR key
|
|
||||||
if (IsKeyPressed(KEY_SPACE)) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPaused) {
|
|
||||||
sessionPlayTime += GetFrameTime();
|
|
||||||
|
|
||||||
ball.Update();
|
|
||||||
player.Update();
|
|
||||||
cpu.Update(ball.position.y);
|
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) {
|
|
||||||
ball.velocity.x *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) {
|
|
||||||
ball.velocity.x *= -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ball.position.x + ball.radius >= screen_width - 20.0f) {
|
|
||||||
score.cpu_score++;
|
|
||||||
if (score.cpu_score >= 5) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResetBall(ball, screen_width, screen_height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ball.position.x - ball.radius <= 20.0f) {
|
|
||||||
score.player_score++;
|
|
||||||
if (score.player_score >= 5) {
|
|
||||||
currentState = GameState::GameOver;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ResetBall(ball, screen_width, screen_height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Draw Textured Court Background ---
|
|
||||||
// Left Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ 20.0f, 20.0f, 570.0f, 760.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Court (basic_space.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
courtBackground,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height },
|
|
||||||
Rectangle{ 690.0f, 20.0f, 570.0f, 760.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Center strip (Dark Green background area) - already cleared by ClearBackground
|
|
||||||
|
|
||||||
// Center Circle
|
|
||||||
DrawCircle(screen_width / 2, screen_height / 2, 50.0f, Color{ 102, 51, 153, 100 });
|
|
||||||
DrawCircleLines(screen_width / 2, screen_height / 2, 50.0f, Color{ 50, 25, 75, 250 });
|
|
||||||
|
|
||||||
// Tiled Center Dashed Line (line.png)
|
|
||||||
int lineY = 20;
|
|
||||||
while (lineY < 780) {
|
|
||||||
DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE);
|
|
||||||
lineY += lineTexture.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bottom Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, (float)screen_height - 20.0f, (float)screen_width, 20.0f },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Left Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ 0.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Right Wall (walls.png)
|
|
||||||
DrawTexturePro(
|
|
||||||
wallsTexture,
|
|
||||||
Rectangle{ 0.0f, 0.0f, (float)wallsTexture.width, (float)wallsTexture.height },
|
|
||||||
Rectangle{ (float)screen_width - 20.0f, 0.0f, 20.0f, (float)screen_height },
|
|
||||||
Vector2{ 0.0f, 0.0f },
|
|
||||||
0.0f,
|
|
||||||
WHITE
|
|
||||||
);
|
|
||||||
|
|
||||||
DrawText(TextFormat("%i", score.cpu_score), screen_width / 4 - 20, 20, 80, WHITE);
|
|
||||||
DrawText(TextFormat("%i", score.player_score), 3 * screen_width / 4 - 20, 20, 80, WHITE);
|
|
||||||
|
|
||||||
// Draw Playtime Counter
|
|
||||||
int minutes = (int)sessionPlayTime / 60;
|
|
||||||
int seconds = (int)sessionPlayTime % 60;
|
|
||||||
int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32);
|
|
||||||
|
|
||||||
// Draw background box to block out the center line and increase contrast
|
|
||||||
DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 });
|
|
||||||
DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 });
|
|
||||||
DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, 721, 32, YELLOW);
|
|
||||||
|
|
||||||
ball.Draw();
|
|
||||||
cpu.Draw();
|
|
||||||
player.Draw();
|
|
||||||
|
|
||||||
// Draw Pause Overlay and Text
|
|
||||||
if (isPaused) {
|
|
||||||
// Semi-transparent overlay inside the borders
|
|
||||||
DrawRectangle(20, 20, screen_width - 40, screen_height - 40, Color{ 0, 0, 0, 150 });
|
|
||||||
|
|
||||||
int pausedTextWidth = MeasureText("PAUSED", 60);
|
|
||||||
DrawText("PAUSED", screen_width / 2 - pausedTextWidth / 2, screen_height / 2 - 30, 60, YELLOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case GameState::GameOver:
|
case GameState::GameOver:
|
||||||
{
|
UpdateGameOverState(ctx);
|
||||||
if (IsKeyPressed(KEY_SPACE)) {
|
break;
|
||||||
currentState = GameState::MainMenu;
|
default:
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a nice centered dark panel for game over info
|
|
||||||
int panelWidth = 600;
|
|
||||||
int panelHeight = 400;
|
|
||||||
int panelX = screen_width / 2 - panelWidth / 2;
|
|
||||||
int panelY = screen_height / 2 - panelHeight / 2;
|
|
||||||
|
|
||||||
// Semi-transparent background panel
|
|
||||||
DrawRectangle(panelX, panelY, panelWidth, panelHeight, Color{ 15, 15, 15, 230 });
|
|
||||||
DrawRectangleLines(panelX, panelY, panelWidth, panelHeight, Color{ 120, 120, 120, 255 });
|
|
||||||
|
|
||||||
// Draw "GAME OVER" header
|
|
||||||
int gameOverWidth = MeasureText("GAME OVER", 60);
|
|
||||||
DrawText("GAME OVER", screen_width / 2 - gameOverWidth / 2, panelY + 40, 60, RED);
|
|
||||||
|
|
||||||
// Determine winner text and color
|
|
||||||
std::string winnerText = "GAME ENDED";
|
|
||||||
Color winnerColor = YELLOW;
|
|
||||||
if (score.player_score > score.cpu_score) {
|
|
||||||
winnerText = "YOU WIN!";
|
|
||||||
winnerColor = Green;
|
|
||||||
}
|
|
||||||
else if (score.cpu_score > score.player_score) {
|
|
||||||
winnerText = "CPU WINS!";
|
|
||||||
winnerColor = RED;
|
|
||||||
}
|
|
||||||
int winnerWidth = MeasureText(winnerText.c_str(), 40);
|
|
||||||
DrawText(winnerText.c_str(), screen_width / 2 - winnerWidth / 2, panelY + 120, 40, winnerColor);
|
|
||||||
|
|
||||||
// Draw final scores
|
|
||||||
std::string playerScoreStr = "Player Score: " + std::to_string(score.player_score);
|
|
||||||
std::string cpuScoreStr = "CPU Score: " + std::to_string(score.cpu_score);
|
|
||||||
|
|
||||||
int playerTextWidth = MeasureText(playerScoreStr.c_str(), 30);
|
|
||||||
int cpuTextWidth = MeasureText(cpuScoreStr.c_str(), 30);
|
|
||||||
|
|
||||||
DrawText(playerScoreStr.c_str(), screen_width / 2 - playerTextWidth / 2, panelY + 200, 30, WHITE);
|
|
||||||
DrawText(cpuScoreStr.c_str(), screen_width / 2 - cpuTextWidth / 2, panelY + 250, 30, WHITE);
|
|
||||||
|
|
||||||
// Draw playtime
|
|
||||||
int minutes = (int)sessionPlayTime / 60;
|
|
||||||
int seconds = (int)sessionPlayTime % 60;
|
|
||||||
std::string timeStr = "Playtime: " + std::to_string(minutes) + "m " + std::to_string(seconds) + "s";
|
|
||||||
int timeTextWidth = MeasureText(timeStr.c_str(), 20);
|
|
||||||
DrawText(timeStr.c_str(), screen_width / 2 - timeTextWidth / 2, panelY + 300, 20, LIGHTGRAY);
|
|
||||||
|
|
||||||
// Instruction to return
|
|
||||||
int instWidth = MeasureText("Press SPACEBAR to return to Main Menu", 20);
|
|
||||||
DrawText("Press SPACEBAR to return to Main Menu", screen_width / 2 - instWidth / 2, panelY + 340, 20, YELLOW);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw loop
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground(Dark_Green);
|
||||||
|
|
||||||
|
switch (ctx.currentState) {
|
||||||
|
case GameState::MainMenu:
|
||||||
|
{
|
||||||
|
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::DifficultySelect:
|
||||||
|
{
|
||||||
|
difficultyMenu.Draw();
|
||||||
|
int gameHintWidth = MeasureText("Player: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over", 20);
|
||||||
|
DrawText("Player: UP/DOWN Arrows to move | P to Pause | SPACE to Force Game Over",
|
||||||
|
screen_width / 2 - gameHintWidth / 2,
|
||||||
|
screen_height - 50, 20, WHITE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GameState::MultiplayerLobby:
|
||||||
|
DrawLobbyState(ctx, screen_width, screen_height);
|
||||||
|
break;
|
||||||
|
case GameState::Multiplayer:
|
||||||
|
DrawMultiplayerState(ctx, ball, player, cpu, screen_width, screen_height);
|
||||||
|
break;
|
||||||
|
case GameState::Settings:
|
||||||
|
DrawSettingsState(ctx, screen_width, screen_height);
|
||||||
|
break;
|
||||||
|
case GameState::Playing:
|
||||||
|
DrawPlayingState(ctx, ball, player, cpu, screen_width, screen_height);
|
||||||
|
break;
|
||||||
|
case GameState::GameOver:
|
||||||
|
DrawGameOverState(ctx, screen_width, screen_height);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -338,10 +227,44 @@ int main() {
|
|||||||
EndDrawing();
|
EndDrawing();
|
||||||
}
|
}
|
||||||
|
|
||||||
UnloadTexture(courtBackground);
|
// Asset unloading and cleanup
|
||||||
UnloadTexture(wallsTexture);
|
UnloadTexture(ctx.courtBackground);
|
||||||
UnloadTexture(lineTexture);
|
UnloadTexture(ctx.wallsTexture);
|
||||||
|
UnloadTexture(ctx.lineTexture);
|
||||||
|
UnloadSound(ctx.paddleHitSound);
|
||||||
|
UnloadSound(ctx.wallHitSound);
|
||||||
|
UnloadSound(ctx.scoreSound);
|
||||||
|
|
||||||
|
CloseAudioDevice();
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
return 0;
|
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
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
int Menu::Update() {
|
int Menu::Update() {
|
||||||
// Handle Navigation
|
// Menu navigation
|
||||||
if (IsKeyPressed(KEY_DOWN)) {
|
if (IsKeyPressed(KEY_DOWN)) {
|
||||||
selectedIndex++;
|
selectedIndex++;
|
||||||
if (selectedIndex >= static_cast<int>(options.size())) selectedIndex = 0;
|
if (selectedIndex >= static_cast<int>(options.size())) selectedIndex = 0;
|
||||||
@@ -11,7 +13,7 @@ int Menu::Update() {
|
|||||||
if (selectedIndex < 0) selectedIndex = static_cast<int>(options.size()) - 1;
|
if (selectedIndex < 0) selectedIndex = static_cast<int>(options.size()) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Selection
|
// Option selection
|
||||||
if (IsKeyPressed(KEY_ENTER)) {
|
if (IsKeyPressed(KEY_ENTER)) {
|
||||||
return selectedIndex;
|
return selectedIndex;
|
||||||
}
|
}
|
||||||
@@ -22,14 +24,339 @@ void Menu::Draw() {
|
|||||||
int screenWidth = GetScreenWidth();
|
int screenWidth = GetScreenWidth();
|
||||||
int screenHeight = GetScreenHeight();
|
int screenHeight = GetScreenHeight();
|
||||||
|
|
||||||
// Draw Title
|
// Render Menu Title (aligned down for balanced spacing)
|
||||||
DrawText(title.c_str(), screenWidth / 2 - MeasureText(title.c_str(), 60) / 2, screenHeight / 4, 60, WHITE);
|
DrawText(title.c_str(), screenWidth / 2 - MeasureText(title.c_str(), 60) / 2, screenHeight / 4 + 20, 60, WHITE);
|
||||||
|
|
||||||
// Draw Options
|
int numOptions = static_cast<int>(options.size());
|
||||||
for (int i = 0; i < static_cast<int>(options.size()); i++) {
|
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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw Control keys
|
// 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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.2 MiB |