schule-pong/src/main.rs

449 lines
15 KiB
Rust
Raw Normal View History

2024-02-22 11:14:40 +01:00
use std::{
num::NonZeroU32,
time::{Duration, Instant},
};
2024-02-02 08:52:26 +01:00
2024-02-22 11:14:40 +01:00
use game::GameState;
2024-02-02 08:52:26 +01:00
use winit::{
2024-02-22 11:14:40 +01:00
dpi::PhysicalSize,
2024-02-02 08:52:26 +01:00
event::{Event, KeyEvent, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
raw_window_handle::{HasDisplayHandle, HasWindowHandle},
window::WindowBuilder,
};
fn main() {
let event_loop = EventLoop::new().unwrap();
2024-02-22 11:14:40 +01:00
let window = WindowBuilder::new()
.with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700)))
.build(&event_loop)
.unwrap();
2024-02-02 08:52:26 +01:00
// ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
// dispatched any events. This is ideal for games and similar applications.
event_loop.set_control_flow(ControlFlow::Poll);
// ControlFlow::Wait pauses the event loop if no events are available to process.
// This is ideal for non-game applications that only update in response to user
// input, and uses significantly less power/CPU time than ControlFlow::Poll.
event_loop.set_control_flow(ControlFlow::Wait);
let context = softbuffer::Context::new(window.display_handle().unwrap()).unwrap();
let mut surface = softbuffer::Surface::new(&context, window.window_handle().unwrap()).unwrap();
2024-02-22 11:14:40 +01:00
let mut game = GameState::init();
2024-02-02 08:52:26 +01:00
event_loop
.run(|event, elwt| {
2024-02-22 11:14:40 +01:00
elwt.set_control_flow(ControlFlow::WaitUntil(
Instant::now() + Duration::from_millis(1000 / 60),
));
2024-02-02 08:52:26 +01:00
match event {
Event::WindowEvent {
window_id,
event: WindowEvent::RedrawRequested,
} if window_id == window.id() => {
if let (Some(width), Some(height)) = {
let size = window.inner_size();
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
} {
surface.resize(width, height).unwrap();
let mut buffer = surface.buffer_mut().unwrap();
// for y in 0..height.get() {
// for x in 0..width.get() {
// let red = x % 255;
// let green = y % 255;
// let blue = (x * y) % 255;
// let index = y as usize * width.get() as usize + x as usize;
// buffer[index] = blue | (green << 8) | (red << 16);
// }
// }
buffer.fill(0);
2024-02-22 11:14:40 +01:00
game.update();
game.render(&mut buffer, (width, height));
2024-02-02 08:52:26 +01:00
buffer.present().unwrap();
}
}
Event::WindowEvent {
event:
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
event:
KeyEvent {
logical_key: Key::Named(NamedKey::Escape),
..
},
..
},
window_id,
} if window_id == window.id() => {
elwt.exit();
}
_ => {}
}
})
.unwrap();
}
2024-02-22 11:14:40 +01:00
mod game {
use std::{convert::identity, num::NonZeroU32, time::Instant};
use softbuffer::Buffer;
use winit::raw_window_handle::{DisplayHandle, WindowHandle};
const PADDLE_SIZE: (u32, u32) = (5, 50);
const BALL_RADIUS: u32 = 10;
const BALL_VEC_RIGHT_DOWN: (i16, i16) = (8, 12);
const FIELD_SIZE: (u32, u32) = (1200, 700);
const FIELD_BORDER_WIDTH: u32 = 5;
const TICK_LEN: f32 = 1000. / 60.;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BallDirection {
RightUp,
RightDown,
LeftUp,
LeftDown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HorizontalCollisionCheck {
None,
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum VerticalCollisionCheck {
None,
Top,
Bottom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct CollisionCheck(HorizontalCollisionCheck, VerticalCollisionCheck);
impl CollisionCheck {
fn check_field(obj_pos: (u32, u32), obj_size: (u32, u32)) -> Self {
let horizontal = if obj_pos.0 == 0 {
HorizontalCollisionCheck::Left
} else if (obj_pos.0 + obj_size.0) >= FIELD_SIZE.0 {
HorizontalCollisionCheck::Right
} else {
HorizontalCollisionCheck::None
};
let vertical = if obj_pos.1 == 0 {
VerticalCollisionCheck::Top
} else if (obj_pos.1 + obj_size.1) >= FIELD_SIZE.1 {
VerticalCollisionCheck::Bottom
} else {
VerticalCollisionCheck::None
};
Self(horizontal, vertical)
}
fn no_collisions(&self) -> bool {
matches!(
self,
CollisionCheck(HorizontalCollisionCheck::None, VerticalCollisionCheck::None)
)
}
}
struct MovingObject {
prev_pos: (u32, u32),
}
impl BallDirection {
fn get_vec(&self) -> (i16, i16) {
match self {
BallDirection::RightDown => BALL_VEC_RIGHT_DOWN,
BallDirection::RightUp => (BALL_VEC_RIGHT_DOWN.0, -BALL_VEC_RIGHT_DOWN.1),
BallDirection::LeftUp => (-BALL_VEC_RIGHT_DOWN.0, BALL_VEC_RIGHT_DOWN.1),
BallDirection::LeftDown => (-BALL_VEC_RIGHT_DOWN.0, -BALL_VEC_RIGHT_DOWN.1),
}
}
}
pub struct GameState {
ball_pos: (u32, u32),
ball_direction: BallDirection,
paddle_r_pos: u32,
paddle_y_pos: u32,
scores: (u32, u32),
last_frame_time: Instant,
}
impl GameState {
pub fn init() -> Self {
Self {
ball_pos: (FIELD_SIZE.0 / 2, FIELD_SIZE.1 / 2),
paddle_r_pos: FIELD_SIZE.0 / 2,
paddle_y_pos: FIELD_SIZE.0 / 2,
ball_direction: BallDirection::RightDown,
scores: (0, 0),
last_frame_time: Instant::now(),
}
}
pub fn render(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (NonZeroU32, NonZeroU32),
) {
let win_size = (win_size.0.get(), win_size.1.get());
self.render_field(buf, win_size);
self.render_ball(buf, win_size);
}
pub fn update(&mut self) {
let delta_t = self.delta_time();
self.last_frame_time = Instant::now();
self.move_ball();
}
fn move_ball(&mut self) {
let vec = self.ball_direction.get_vec();
let delta_t = self.delta_time();
let new_pos = (
self.ball_pos
.0
.saturating_add_signed((vec.0 as f32 * delta_t) as i32)
.clamp(0, FIELD_SIZE.0 - BALL_RADIUS),
self.ball_pos
.1
.saturating_add_signed((vec.1 as f32 * delta_t) as i32)
.clamp(0, FIELD_SIZE.1 - BALL_RADIUS),
);
// println!("{:?} -> {:?}", self.ball_pos, new_pos);
self.ball_pos = new_pos;
let check_res = CollisionCheck::check_field(
(
new_pos.0.saturating_sub(BALL_RADIUS),
new_pos.1.saturating_sub(BALL_RADIUS),
),
(BALL_RADIUS * 2, BALL_RADIUS * 2),
);
let CollisionCheck(horizontal, vertical) = check_res;
self.ball_direction = match (self.ball_direction, (horizontal, vertical)) {
(
BallDirection::RightUp,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::Bottom),
)
| (
BallDirection::RightDown,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::None),
)
| (
BallDirection::LeftUp,
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::None),
)
| (
BallDirection::LeftDown,
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::Top),
) => BallDirection::RightDown,
(
BallDirection::RightUp,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::None),
)
| (
BallDirection::RightDown,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::Top),
)
| (
BallDirection::LeftUp,
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::Top),
)
| (
BallDirection::LeftDown,
(HorizontalCollisionCheck::Left, VerticalCollisionCheck::None),
) => BallDirection::RightUp,
(
BallDirection::RightUp,
(HorizontalCollisionCheck::Right, VerticalCollisionCheck::Bottom),
)
| (
BallDirection::RightDown,
(HorizontalCollisionCheck::Right, VerticalCollisionCheck::None),
)
| (
BallDirection::LeftUp,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::None),
)
| (
BallDirection::LeftDown,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::Bottom),
) => BallDirection::LeftUp,
(
BallDirection::RightUp,
(HorizontalCollisionCheck::Right, VerticalCollisionCheck::None),
)
| (
BallDirection::RightDown,
(HorizontalCollisionCheck::Right, VerticalCollisionCheck::Top),
)
| (
BallDirection::LeftUp,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::Top),
)
| (
BallDirection::LeftDown,
(HorizontalCollisionCheck::None, VerticalCollisionCheck::None),
) => BallDirection::LeftDown,
other => panic!("Invalid collision: {other:#?}"),
};
}
fn delta_time(&self) -> f32 {
Instant::now()
.duration_since(self.last_frame_time)
.as_millis() as f32
/ TICK_LEN
}
fn render_field(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (u32, u32),
) {
let field_pos = (
(win_size.0 / 2).saturating_sub(FIELD_SIZE.0 / 2),
(win_size.1 / 2).saturating_sub(FIELD_SIZE.1 / 2),
);
// top border
draw_rect(
buf,
win_size,
(
field_pos.0.saturating_sub(FIELD_BORDER_WIDTH),
field_pos.1.saturating_sub(FIELD_BORDER_WIDTH),
),
(
FIELD_SIZE.0.saturating_add(FIELD_BORDER_WIDTH * 2),
FIELD_BORDER_WIDTH,
),
0xff0000,
);
// right border
draw_rect(
buf,
win_size,
(field_pos.0.saturating_add(FIELD_SIZE.0), field_pos.1),
(FIELD_BORDER_WIDTH, FIELD_SIZE.1),
0x00ff00,
);
// bottom border
draw_rect(
buf,
win_size,
(
field_pos.0.saturating_sub(FIELD_BORDER_WIDTH),
field_pos.1.saturating_add(FIELD_SIZE.1),
),
(
FIELD_SIZE.0.saturating_add(FIELD_BORDER_WIDTH * 2),
FIELD_BORDER_WIDTH,
),
0xff00ff,
);
// left border
draw_rect(
buf,
win_size,
(field_pos.0.saturating_sub(FIELD_BORDER_WIDTH), field_pos.1),
(FIELD_BORDER_WIDTH, FIELD_SIZE.1),
0x00ffff,
);
// draw midline
draw_rect(
buf,
win_size,
((win_size.0 / 2) - (FIELD_BORDER_WIDTH / 2), field_pos.1),
(FIELD_BORDER_WIDTH, FIELD_SIZE.1),
0x7f7f7f,
)
}
fn render_ball(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (u32, u32),
) {
let field_pos = (
(win_size.0 / 2).saturating_sub(FIELD_SIZE.0 / 2),
(win_size.1 / 2).saturating_sub(FIELD_SIZE.1 / 2),
);
// let field_pos = identity((
// (win_size.0 / 2).saturating_sub(FIELD_SIZE.0 / 2),
// (win_size.1 / 2).saturating_sub(FIELD_SIZE.1 / 2),
// ));
// println!("{}, {}", field_pos.0, field_pos.1);
let ball_render_pos = (
(self.ball_pos.0 + field_pos.0).saturating_sub(BALL_RADIUS),
(self.ball_pos.1 + field_pos.1).saturating_sub(BALL_RADIUS),
);
draw_rect(
buf,
win_size,
ball_render_pos,
(BALL_RADIUS * 2, BALL_RADIUS * 2),
0xffffff,
);
}
fn render_paddles(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (u32, u32),
) {
todo!()
}
fn render_paddle(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (u32, u32),
) {
todo!()
}
fn render_scores(
&self,
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
win_size: (u32, u32),
) {
todo!()
}
}
fn draw_rect(
buf: &mut Buffer<'_, DisplayHandle<'_>, WindowHandle<'_>>,
buf_size: (u32, u32),
pos: (u32, u32),
size: (u32, u32),
color: u32,
) {
for y in pos.1..(pos.1 + size.1) {
for x in pos.0..(pos.0 + size.0) {
let index = y as usize * buf_size.0 as usize + x as usize;
if let Some(px) = buf.get_mut(index) {
*px = color
}
}
}
}
}