Skip to content
This repository was archived by the owner on Oct 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8c9a4c3
Add game mechanics and UI components for Rust-Pong
Oct 24, 2025
3a3aff3
Remove unused modules and refactor imports for cleaner code structure
Oct 24, 2025
27cd4bf
Remove re-exports from game and player modules for cleaner structure
Oct 24, 2025
f539b06
Merge branch 'main' into refactor/modularize_code
Oct 24, 2025
84cbaef
bump version to 1.0.1 in Cargo.toml
Oct 24, 2025
3d515b5
Refactor UI components: implement game over and pause screen renderin…
Vianpyro Oct 25, 2025
d331c9d
Fix formatting of continue text in game over screen
Vianpyro Oct 25, 2025
aaaf04e
Refactor UI: centralize title drawing logic into common module
Vianpyro Oct 25, 2025
343e7eb
Update README.md with project details and add LICENSE file
Vianpyro Oct 25, 2025
5863b04
Super-Linter: Fix linting issues
Vianpyro Oct 25, 2025
57b3f75
Update README.md: clarify notes on sound effect files and distribution
Vianpyro Oct 25, 2025
5d76169
Merge branch 'refactor/modularize_code' of https://github.com/Vianpyr…
Vianpyro Oct 25, 2025
384ec57
Fix formatting issues in README.md
Vianpyro Oct 25, 2025
d0120b7
Super-Linter: Fix linting issues
Vianpyro Oct 25, 2025
429704b
Update super-linter workflow: upgrade actions and dependencies
Vianpyro Oct 25, 2025
c2d0dce
Update super-linter workflow: adjust credential persistence and filte…
Vianpyro Oct 25, 2025
816464b
Update super-linter workflow: refine filter regex and add Rust Clippy…
Vianpyro Oct 25, 2025
6518e28
Update super-linter workflow: add validation for GitHub Actions Zizmor
Vianpyro Oct 25, 2025
48dfa34
Update super-linter workflow: add fetch for PR base branch to improve…
Vianpyro Oct 25, 2025
dc1ed85
Update super-linter workflow: add debug step for workspace and event …
Vianpyro Oct 25, 2025
56ce5e1
Update super-linter workflow: enable Rust Clippy fixer for all events
Vianpyro Oct 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .github/workflows/super-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0

Expand All @@ -33,24 +33,29 @@ jobs:
prettier --write .

- name: Super-linter
uses: super-linter/super-linter/slim@v7
uses: super-linter/super-linter/slim@v8
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_ALL_CODEBASE: false
FILTER_REGEX_EXCLUDE: "(.devcontainer/Dockerfile|.github/pull_request_template.md|.github/ISSUE_TEMPLATE/*.md)"
VALIDATE_ALL_CODEBASE: true
FILTER_REGEX_EXCLUDE: "(.devcontainer/Dockerfile|.github/pull_request_template.md|.github/ISSUE_TEMPLATE/*.md|.github/workflows/*.(yml|yaml))"
# Disable problematic validators
VALIDATE_BIOME_FORMAT: false
VALIDATE_DOCKERFILE_HADOLINT: false
VALIDATE_GIT_COMMITLINT: false
VALIDATE_GITHUB_ACTIONS_ZIZMOR: false
# Enable fixers for PR events
FIX_JSON: true
FIX_JSON_PRETTIER: true
FIX_MARKDOWN: true
FIX_MARKDOWN_PRETTIER: true
FIX_NATURAL_LANGUAGE: ${{ github.event_name == 'pull_request' }}
FIX_RUST_CLIPPY: true
# Custom options for linters
RUST_CLIPPY_COMMAND_OPTIONS: "--config max_width=160"

- name: Commit and push fixes
if: github.event_name == 'pull_request' && github.event.pull_request.head.ref != github.event.repository.default_branch
uses: stefanzweifel/git-auto-commit-action@v5
uses: stefanzweifel/git-auto-commit-action@v7
with:
branch: ${{ github.event.pull_request.head.ref }}
commit_message: "Super-Linter: Fix linting issues"
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "Rust-Pong"
version = "1.0.0"
version = "1.0.1"
authors = ["Vianpyro"]
edition = "2024"

Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Vianney Veremme

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
76 changes: 74 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,75 @@
# Template
# Rust-Pong

Solid GitHub template repository
Welcome to the **Rust-Pong** repository! This repository contains a small Pong clone implemented in Rust to demonstrate modular game structure, audio, and simple UI.

## 🚀 Getting Started

To get started with this project:

1. Clone the repository:
```bash
git clone https://github.com/Vianpyro/Rust-Pong.git
cd Rust-Pong
```
2. Install the Rust toolchain if you don't already have it: <https://rustup.rs/>
3. Build the project:
```bash
cargo build
```
4. Run the game:
```bash
cargo run --release
```

> [!NOTE]
>
> - Sound effect files (sfx) are embedded in the binary, so you don't need to include them when distributing the executable.
> - If you encounter platform-specific audio or windowing issues, ensure the required system libraries (for audio/display) are available for your OS.

## 📁 Project Structure

The repository contains the following directories and files (high level):

- `assets/` - Game assets (sounds, images, etc.)
- `src/` - Application source code
- `audio/` - Audio handling
- `game/` - Game objects and physics (ball, racket, score)
- `player/` - Player and controller code
- `ui/` - Menus, HUD, and screens
- `main.rs` - Application entry point
- `main_state.rs`, `debug.rs` - Game state and debugging helpers
- `Cargo.toml` - Rust package manifest
- `LICENSE` - Project license (see file for terms)

## 🛠 Features

- Rust-based Pong clone demonstrating basic game loop, physics, and UI.
- Modular code organization (audio, game logic, players, UI).
- Lightweight and easy to extend for experimentation or learning.

## 📖 Documentation

The code is organized into clear modules under `src/`. For details, explore the following files and folders:

- `src/game/` — core game logic and physics
- `src/audio/` — audio playback and resource handling
- `src/ui/` — UI screens (menu, HUD, pause, game over)

Expand this readme as the project grows to include contribution guidelines, a development roadmap, and detailed architecture notes.

## 🤝 Contributing

Contributions are welcome. Suggested workflow:

1. Fork the repository.
2. Create a feature branch: `git checkout -b feature/your-feature`
3. Make your changes and add tests where applicable.
4. Open a pull request to the main repository.

When opening issues or PRs, provide reproduction steps and any relevant logs or OS details.

## 📝 License

See the [`LICENSE`](/LICENSE) file in this repository for license terms.

Happy coding! 🎉
File renamed without changes.
2 changes: 1 addition & 1 deletion src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use ggez::graphics::{Canvas, Color, DrawMode, DrawParam, Mesh, PxScale, Text};
use ggez::{Context, GameResult, glam::Vec2};
use ggez::graphics::{Canvas, Color, DrawMode, DrawParam, Mesh, PxScale, Text};

pub struct DebugInfo {
enabled: bool,
Expand Down
File renamed without changes.
4 changes: 4 additions & 0 deletions src/game/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod ball;
pub mod physics;
pub mod racket;
pub mod score;
4 changes: 2 additions & 2 deletions src/physics.rs → src/game/physics.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::ball::{BALL_SIZE, BALL_SPEED_INCREMENT, BALL_SPEED_MAX, Ball};
use crate::racket::{RACKET_HEIGHT_HALF, RACKET_WIDTH_HALF, Racket};
use crate::game::ball::{BALL_SIZE, BALL_SPEED_INCREMENT, BALL_SPEED_MAX, Ball};
use crate::game::racket::{RACKET_HEIGHT_HALF, RACKET_WIDTH_HALF, Racket};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Player {
Expand Down
4 changes: 2 additions & 2 deletions src/racket.rs → src/game/racket.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::controller::{Controller, RacketAction::*};
use crate::player::controller::{Controller, RacketAction::*};
use ggez::graphics::{Canvas, Color, DrawParam, Mesh, Rect};
use ggez::{Context, GameResult};

Expand Down Expand Up @@ -33,7 +33,7 @@ impl Racket {
canvas.draw(&self.racket_mesh, DrawParam::default().dest([self.position_x, self.position_y]));
}

pub fn update(&mut self, input: &crate::controller::ControllerInput, delta_time: f32) {
pub fn update(&mut self, input: &crate::player::controller::ControllerInput, delta_time: f32) {
match self.controller.get_action(input) {
MoveUp => {
self.position_y -= RACKET_SPEED * delta_time;
Expand Down
File renamed without changes.
8 changes: 2 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
#![windows_subsystem = "windows"]

mod audio;
mod ball;
mod controller;
mod debug;
mod game;
mod main_state;
mod physics;
mod player_type;
mod racket;
mod score;
mod player;
mod ui;

use crate::main_state::MainState;
Expand Down
83 changes: 13 additions & 70 deletions src/main_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
// - Mouse Click: Select and cycle player type
// - SPACE/ENTER: Start game

use crate::game::ball::*;
use crate::game::physics::*;
use crate::game::racket::*;
use crate::game::score::Score;
use crate::player::controller::ControllerInput;
use crate::player::player_type::PlayerType;
use crate::ui::menu as ui_menu;
use crate::{audio::play_embedded_sound, ball::*, controller::ControllerInput, debug::DebugInfo, physics::*, player_type::PlayerType, racket::*, score::Score};
use ggez::graphics::{Canvas, Color, DrawMode, DrawParam, Mesh, Rect, Text};
use crate::{audio::play_embedded_sound, debug::DebugInfo};
use ggez::graphics::{Canvas, Color, DrawMode, DrawParam, Mesh, Rect};
use ggez::{Context, GameResult, event, glam::Vec2, input::keyboard::KeyCode};
use std::collections::HashSet;

Expand Down Expand Up @@ -69,16 +75,6 @@ impl MainState {
})
}

fn draw_centered_title(&self, canvas: &mut Canvas, context: &mut Context, text: &str, color: Color) -> GameResult {
let (screen_width, screen_height) = context.gfx.drawable_size();
let mut title = Text::new(text);
title.set_scale(screen_height / 10.0);
let title_dimensions = title.measure(context)?;
let title_position = Vec2::new((screen_width - title_dimensions.x) / 2.0, screen_height / 3.0);
canvas.draw(&title, DrawParam::default().dest(title_position).color(color));
Ok(())
}

fn update_controllers(&mut self, context: &mut Context) -> GameResult {
let screen_height = context.gfx.drawable_size().1;
let screen_height_center = screen_height / 2.0;
Expand Down Expand Up @@ -245,14 +241,16 @@ impl event::EventHandler for MainState {
self.draw_playing(&mut canvas);
}
GameState::Paused => {
self.draw_paused(context, &mut canvas)?;
self.draw_playing(&mut canvas);
crate::ui::pause_screen::draw_pause_screen(context, &mut canvas)?;
}
GameState::GameOver { winner } => {
self.draw_game_over(context, &mut canvas, *winner)?;
self.draw_playing(&mut canvas);
crate::ui::game_over::draw_game_over(context, &mut canvas, *winner)?;
}
}

self.debug.draw(&mut canvas);
crate::ui::hud::draw_hud(context, &mut canvas, &self.debug)?;
canvas.finish(context)?;
Ok(())
}
Expand Down Expand Up @@ -344,59 +342,4 @@ impl MainState {
self.player_right.draw_on_canvas(canvas);
self.ball.draw_on_canvas(canvas);
}

fn draw_game_over(&self, context: &mut Context, canvas: &mut Canvas, winner: Player) -> GameResult {
// First draw the game state
self.draw_playing(canvas);

// Semi-transparent overlay
let overlay_rect = Rect::new(0.0, 0.0, context.gfx.drawable_size().0, context.gfx.drawable_size().1);
let overlay_mesh = Mesh::new_rectangle(context, DrawMode::fill(), overlay_rect, Color::from_rgba(0, 0, 0, 180))?;
canvas.draw(&overlay_mesh, DrawParam::default());

let (screen_width, screen_height) = context.gfx.drawable_size();

// Winner text
let winner_text = match winner {
Player::Left => "Player 1 Wins!",
Player::Right => "Player 2 Wins!",
};
self.draw_centered_title(canvas, context, winner_text, Color::WHITE)?;

// Press to continue
let mut continue_text = Text::new("SPACE/ENTER: Menu | R: Restart | Esc: Menu");
continue_text.set_scale(screen_height / 30.0);
let continue_dimensions = continue_text.measure(context)?;
let continue_position = Vec2::new((screen_width - continue_dimensions.x) / 2.0, screen_height * 0.65);
canvas.draw(
&continue_text,
DrawParam::default().dest(continue_position).color(Color::from_rgb(200, 200, 200)),
);

Ok(())
}

fn draw_paused(&self, context: &mut Context, canvas: &mut Canvas) -> GameResult {
// Draw current game state in the background
self.draw_playing(canvas);

// Semi-transparent overlay
let overlay_rect = Rect::new(0.0, 0.0, context.gfx.drawable_size().0, context.gfx.drawable_size().1);
let overlay_mesh = Mesh::new_rectangle(context, DrawMode::fill(), overlay_rect, Color::from_rgba(0, 0, 0, 160))?;
canvas.draw(&overlay_mesh, DrawParam::default());

let (screen_width, screen_height) = context.gfx.drawable_size();

// Paused title
self.draw_centered_title(canvas, context, "Paused", Color::WHITE)?;

// Hints
let mut hint = Text::new("P: Resume | Esc: Menu");
hint.set_scale(screen_height / 30.0);
let hint_dimensions = hint.measure(context)?;
let hint_position = Vec2::new((screen_width - hint_dimensions.x) / 2.0, screen_height * 0.65);
canvas.draw(&hint, DrawParam::default().dest(hint_position).color(Color::from_rgb(200, 200, 200)));

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/controller.rs → src/player/controller.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::racket::RACKET_HEIGHT_HALF;
use crate::game::racket::RACKET_HEIGHT_HALF;
use ggez::{glam::Vec2, input::keyboard::KeyCode};
use std::collections::HashSet;

Expand Down
3 changes: 3 additions & 0 deletions src/player/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod controller;
pub mod player_type;

4 changes: 2 additions & 2 deletions src/player_type.rs → src/player/player_type.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::controller::{AIController, Controller, HumanController};
use crate::physics::Player;
use crate::game::physics::Player;
use crate::player::controller::{AIController, Controller, HumanController};
use ggez::input::keyboard::KeyCode;

#[derive(Debug, Clone, Copy, PartialEq)]
Expand Down
12 changes: 12 additions & 0 deletions src/ui/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use ggez::{Context, GameResult, glam::Vec2};
use ggez::graphics::{Canvas, Color, DrawParam, Text};

pub fn draw_centered_title(context: &mut Context, canvas: &mut Canvas, text: &str, color: Color) -> GameResult {
let (screen_width, screen_height) = context.gfx.drawable_size();
let mut title = Text::new(text);
title.set_scale(screen_height / 10.0);
let title_dimensions = title.measure(context)?;
let title_position = Vec2::new((screen_width - title_dimensions.x) / 2.0, screen_height / 3.0);
canvas.draw(&title, DrawParam::default().dest(title_position).color(color));
Ok(())
}
33 changes: 33 additions & 0 deletions src/ui/game_over.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use ggez::graphics::{Canvas, Color, DrawMode, DrawParam, Mesh, Rect, Text};
use ggez::{Context, GameResult, glam::Vec2};

use crate::game::physics::Player;

pub fn draw_game_over(context: &mut Context, canvas: &mut Canvas, winner: Player) -> GameResult {
// Semi-transparent overlay
let overlay_rect = Rect::new(0.0, 0.0, context.gfx.drawable_size().0, context.gfx.drawable_size().1);
let overlay_mesh = Mesh::new_rectangle(context, DrawMode::fill(), overlay_rect, Color::from_rgba(0, 0, 0, 180))?;
canvas.draw(&overlay_mesh, DrawParam::default());

let (screen_width, screen_height) = context.gfx.drawable_size();

// Winner text
let winner_text = match winner {
Player::Left => "Player 1 Wins!",
Player::Right => "Player 2 Wins!",
};

super::common::draw_centered_title(context, canvas, winner_text, Color::WHITE)?;

// Press to continue
let mut continue_text = Text::new("R: Restart | Esc: Menu");
continue_text.set_scale(screen_height / 30.0);
let continue_dimensions = continue_text.measure(context)?;
let continue_position = Vec2::new((screen_width - continue_dimensions.x) / 2.0, screen_height * 0.65);
canvas.draw(
&continue_text,
DrawParam::default().dest(continue_position).color(Color::from_rgb(200, 200, 200)),
);

Ok(())
}
10 changes: 10 additions & 0 deletions src/ui/hud.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use ggez::graphics::Canvas;
use ggez::{Context, GameResult};

use crate::debug::DebugInfo;

pub fn draw_hud(_context: &mut Context, canvas: &mut Canvas, debug: &DebugInfo) -> GameResult {
// Delegate debug drawing to the DebugInfo helper
debug.draw(canvas);
Ok(())
}
Loading
Loading