Skip to content

Instantly share code, notes, and snippets.

@xdcrafts
Created August 13, 2021 10:46
Show Gist options
  • Save xdcrafts/5909e364df1c1683be4b4227e9a012df to your computer and use it in GitHub Desktop.
Save xdcrafts/5909e364df1c1683be4b4227e9a012df to your computer and use it in GitHub Desktop.
use ggez::graphics::{self, Color, DrawMode, DrawParam, FillOptions, Mesh, Rect};
use ggez::{Context, ContextBuilder, GameError, GameResult};
use ggez::mint;
use ggez::event::{self, EventHandler};
use ggez::input::keyboard::{self, KeyCode};
use rand::prelude::*;
use std::ops::Neg;
// -------------------------------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------------------------------
const FIELD_COLOR: Color = Color::BLACK;
const OBJ_COLOR: Color = Color::WHITE;
const DRAW_MODE: DrawMode = DrawMode::Fill(FillOptions::DEFAULT);
const RACKET_HEIGHT: f32 = 100.0;
const RACKET_WIDTH: f32 = 20.0;
const RACKET_SPEED_MULTIPLIER: f32 = 500.0;
const BALL_RADIUS: f32 = 15.0;
const BALL_SPEED_MULTIPLIER: f32 = 500.0;
// -------------------------------------------------------------------------------------------------
// State
// -------------------------------------------------------------------------------------------------
struct Field {
score: (u32, u32),
width: f32,
height: f32,
center: mint::Point2<f32>,
mesh: Mesh,
}
impl Field {
fn new(_ctx: &mut Context, dimensions: (f32, f32)) -> GameResult<Self> {
let (width, height) = dimensions;
let center = mint::Point2 {
x: width * 0.5,
y: height * 0.5,
};
let rect = Rect::new(-0.5, -height * 0.5, 1.0, height);
let mesh = Mesh::new_rectangle(_ctx, DRAW_MODE, rect, OBJ_COLOR)?;
Ok(Field {
score: (0, 0),
width,
height,
center,
mesh,
})
}
fn draw(&self, context: &mut Context) -> GameResult {
let line_draw_params = DrawParam::default().dest(self.center);
graphics::draw(context, &self.mesh, line_draw_params)?;
let score_text = graphics::Text::new(format!("{} {}", self.score.0, self.score.1));
let score_text_width = score_text.dimensions(context).w;
let score_draw_params = DrawParam::default().dest(mint::Point2 {
x: self.center.x - score_text_width * 0.5,
y: 0.0,
});
graphics::draw(context, &score_text, score_draw_params)
}
fn fit(&self, position: mint::Point2<f32>, width: f32, height: f32) -> mint::Point2<f32> {
let x: f32 = if position.x - width * 0.5 < 0.0 {
width * 0.5
} else if position.x + width * 0.5 > self.width {
self.width - width * 0.5
} else {
position.x
};
let y: f32 = if position.y - height * 0.5 < 0.0 {
height * 0.5
} else if position.y + height * 0.5 > self.height {
self.height - height * 0.5
} else {
position.y
};
mint::Point2 { x, y }
}
}
struct Racket {
width: f32,
height: f32,
position: mint::Point2<f32>,
mesh: Mesh,
}
impl Racket {
fn new(_ctx: &mut Context, width: f32, height: f32, x: f32, y: f32) -> GameResult<Self> {
let rect = Rect::new(-width * 0.5, -height * 0.5, width, height);
let mesh = Mesh::new_rectangle(_ctx, DRAW_MODE, rect, OBJ_COLOR)?;
let position = mint::Point2 { x, y };
Ok(Racket {
width,
height,
position,
mesh,
})
}
fn draw(&self, context: &mut Context) -> GameResult {
let draw_params = DrawParam::default().dest(self.position);
graphics::draw(context, &self.mesh, draw_params)
}
fn handle_controls(
&mut self,
_ctx: &Context,
field: &Field,
up: KeyCode,
down: KeyCode,
) -> GameResult {
let delta_time = ggez::timer::delta(_ctx).as_secs_f32();
if keyboard::is_key_pressed(_ctx, up) {
self.position.y -= 1.0 * RACKET_SPEED_MULTIPLIER * delta_time;
}
if keyboard::is_key_pressed(_ctx, down) {
self.position.y += 1.0 * RACKET_SPEED_MULTIPLIER * delta_time;
}
self.position = field.fit(self.position, self.width, self.height);
Ok(())
}
}
struct Ball {
radius: f32,
position: mint::Point2<f32>,
direction: mint::Vector2<f32>,
mesh: Mesh,
}
impl Ball {
fn new(context: &mut Context, radius: f32, position: mint::Point2<f32>) -> GameResult<Self> {
let rect = Rect::new(-radius, -radius, radius * 2.0, radius * 2.0);
let mesh = Mesh::new_rectangle(context, DRAW_MODE, rect, OBJ_COLOR)?;
let direction = mint::Vector2 { x: 0.0, y: 0.0 };
let mut ball = Ball {
radius,
position,
direction,
mesh,
};
ball.reset(position);
Ok(ball)
}
fn draw(&self, context: &mut Context) -> GameResult {
let draw_params = DrawParam::default().dest(self.position);
graphics::draw(context, &self.mesh, draw_params)
}
fn reset(&mut self, position: mint::Point2<f32>) {
let (x, y): (f32, f32) = (random(), random());
let direction = mint::Vector2 {
x: if random() { x + 0.1 } else { x.neg() - 0.1 },
y: if random() { y } else { y.neg() },
};
self.position = position;
self.direction = direction;
}
fn advance(
&mut self,
_ctx: &Context,
field: &mut Field,
racket_1: &Racket,
racket_2: &Racket,
) -> GameResult {
let delta_time = ggez::timer::delta(_ctx).as_secs_f32();
self.position.y += self.direction.y * delta_time * BALL_SPEED_MULTIPLIER;
self.position.x += self.direction.x * delta_time * BALL_SPEED_MULTIPLIER;
self.position = field.fit(self.position, self.radius * 2.0, self.radius * 2.0);
// reflect from rackets
let rocket_1_reflect = self.direction.x < 0.0
&& self.position.x - self.radius <= racket_1.position.x + racket_1.width * 0.5
&& (self.position.y + self.radius >= racket_1.position.y - racket_1.height * 0.5
&& self.position.y + self.radius <= racket_1.position.y + racket_1.height * 0.5
|| self.position.y - self.radius >= racket_1.position.y - racket_1.height * 0.5
&& self.position.y - self.radius
<= racket_1.position.y + racket_1.height * 0.5);
let rocket_2_reflect = self.direction.x > 0.0
&& self.position.x + self.radius >= racket_2.position.x - racket_2.width * 0.5
&& (self.position.y + self.radius >= racket_2.position.y - racket_2.height * 0.5
&& self.position.y + self.radius <= racket_2.position.y + racket_2.height * 0.5
|| self.position.y - self.radius >= racket_2.position.y - racket_2.height * 0.5
&& self.position.y - self.radius
<= racket_2.position.y + racket_2.height * 0.5);
if rocket_1_reflect || rocket_2_reflect {
self.direction.x = self.direction.x.neg();
self.direction.y = random();
return Ok(());
}
// score and reset if crossed left or right border
if self.position.x + self.radius == field.width {
field.score.0 += 1;
self.reset(field.center);
return Ok(());
}
if self.position.x - self.radius == 0.0 {
field.score.1 += 1;
self.reset(field.center);
return Ok(());
}
// reflect from top and bottom
if self.position.y + self.radius == field.height {
self.direction.y = self.direction.y.neg();
return Ok(());
}
if self.position.y - self.radius == 0.0 {
self.direction.y = self.direction.y.neg();
return Ok(());
}
Ok(())
}
}
struct State {
field: Field,
racket_1: Racket,
racket_2: Racket,
ball: Ball,
}
impl State {
pub fn new(_ctx: &mut Context) -> GameResult<Self> {
let field = Field::new(_ctx, graphics::drawable_size(_ctx))?;
let racket_1 = Racket::new(
_ctx,
RACKET_WIDTH,
RACKET_HEIGHT,
RACKET_WIDTH * 0.5,
field.center.y,
)?;
let racket_2 = Racket::new(
_ctx,
RACKET_WIDTH,
RACKET_HEIGHT,
field.width - RACKET_WIDTH * 0.5,
field.center.y,
)?;
let ball = Ball::new(_ctx, BALL_RADIUS, field.center)?;
Ok(State {
field,
racket_1,
racket_2,
ball,
})
}
}
impl EventHandler<GameError> for State {
fn update(&mut self, _ctx: &mut Context) -> Result<(), GameError> {
self.racket_1
.handle_controls(_ctx, &self.field, KeyCode::W, KeyCode::S)?;
self.racket_2
.handle_controls(_ctx, &self.field, KeyCode::Up, KeyCode::Down)?;
self.ball
.advance(_ctx, &mut self.field, &self.racket_1, &self.racket_2)?;
Ok(())
}
fn draw(&mut self, _ctx: &mut Context) -> Result<(), GameError> {
graphics::clear(_ctx, FIELD_COLOR);
self.field.draw(_ctx)?;
self.racket_1.draw(_ctx)?;
self.racket_2.draw(_ctx)?;
self.ball.draw(_ctx)?;
graphics::present(_ctx)
}
}
// -------------------------------------------------------------------------------------------------
// Main
// -------------------------------------------------------------------------------------------------
fn main() -> GameResult {
let context_builder = ContextBuilder::new("pong", "xdcrafts");
let (mut ctx, event_loop) = context_builder
.build()?;
graphics::set_window_title(&ctx, "Pong");
let state = State::new(&mut ctx)?;
event::run(ctx, event_loop, state)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment