use std::{ num::NonZeroU32, time::{Duration, Instant}, }; use game::GameState; use winit::{ dpi::PhysicalSize, 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(); let window = WindowBuilder::new() .with_inner_size(winit::dpi::Size::Physical(PhysicalSize::new(1200, 700))) .build(&event_loop) .unwrap(); // 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(); let mut game = GameState::init(); event_loop .run(|event, elwt| { elwt.set_control_flow(ControlFlow::WaitUntil( Instant::now() + Duration::from_millis(1000 / 60), )); 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); game.update(); game.render(&mut buffer, (width, height)); 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(); } 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 } } } } }