ブロック崩し

作成日: 2022-12-10 (土)

src/main.rs

use sfml::{graphics::*, system::*, window::*};

struct MoveFlags {
    left: bool,
    right: bool,
}

type Brick<'a> = RectangleShape<'a>;

struct Game<'a> {
    window: RenderWindow,
    paddle: RectangleShape<'a>,
    paddle_speed: f32,
    ball: CircleShape<'a>,
    ball_velocity: Vector2f,
    move_flags: MoveFlags,
    bricks: Vec<Brick<'a>>,
}

impl<'a> Game<'a> {
    const FPS: f32 = 60.0; // Time::seconds(1.0 / 60.0) is not allowed for const
    const WIDTH: u32 = 1000;
    const HEIGHT: u32 = 800;
    const PADDLE_SPEED: f32 = 300.0;
    const PADDLE_SIZE_X: f32 = 100.0;
    const PADDLE_SIZE_Y: f32 = 30.0;
    const BALL_SPEED: f32 = 600.0;
    const BALL_RADIUS: f32 = 15.0;
    const BRICK_COUNT_X: u32 = 10;
    const BRICK_COUNT_Y: u32 = 8;

    pub fn new() -> Game<'a> {
        let window = RenderWindow::new(
            (Self::WIDTH, Self::HEIGHT),
            "Pong with Rust and SFML",
            Style::CLOSE,
            &Default::default(),
        );

        // setup paddle
        let paddle = RectangleShape::from_rect(FloatRect {
            left: (Self::WIDTH as f32 / 2.0) - Self::PADDLE_SIZE_X / 2.0,
            top: Self::HEIGHT as f32 - Self::PADDLE_SIZE_Y * 2.0,
            width: Self::PADDLE_SIZE_X,
            height: Self::PADDLE_SIZE_Y,
        });

        let paddle_speed = Self::PADDLE_SPEED;

        // setup ball
        let mut ball = CircleShape::new(Self::BALL_RADIUS, 30);
        ball.set_position((Self::WIDTH as f32 / 2.0, Self::HEIGHT as f32 / 2.0));
        ball.set_origin((Self::BALL_RADIUS, Self::BALL_RADIUS));

        let ball_velocity = Vector2f::new(Self::BALL_SPEED, Self::BALL_SPEED);

        // setup bricks

        // calc brick and gap size
        //   g = r*b
        //   w = r*b*(n+1) + b*n
        //   w = b*{r*(n+1) + n}
        //   b = w / {r*(n+1) + n}
        let gap_ratio = 1.0 / 8.0;
        let brick_width = Self::WIDTH as f32
            / (gap_ratio * (Self::BRICK_COUNT_X + 1) as f32 + Self::BRICK_COUNT_X as f32);
        let gap = brick_width * gap_ratio;
        let brick_yx_ratio = 1.0 / 3.0;
        let brick_height = brick_width * brick_yx_ratio;

        let mut bricks =
            Vec::with_capacity(Self::BRICK_COUNT_X as usize * Self::BRICK_COUNT_Y as usize);
        for i in 0..Self::BRICK_COUNT_Y {
            for j in 0..Self::BRICK_COUNT_X {
                let mut brick = Brick::from_rect(FloatRect {
                    left: gap + j as f32 * (gap + brick_width),
                    top: gap + i as f32 * (gap + brick_height),
                    width: brick_width,
                    height: brick_height,
                });

                brick.set_fill_color(Color::rgb(
                    ((Self::BRICK_COUNT_X - j) * (255 / Self::BRICK_COUNT_X)) as u8,
                    ((i + 1) * (255 / Self::BRICK_COUNT_Y)) as u8,
                    ((j + 1) * (255 / Self::BRICK_COUNT_X)) as u8,
                ));

                bricks.push(brick);
            }
        }

        Self {
            window,
            paddle,
            paddle_speed,
            ball,
            ball_velocity,
            move_flags: MoveFlags {
                left: false,
                right: false,
            },
            bricks,
        }
    }

    pub fn run(&mut self) {
        let mut clock = Clock::start();
        let mut time_since_last_update = Time::ZERO;
        let time_per_frame = Time::seconds(1.0 / Self::FPS);

        while self.window.is_open() {
            self.process_events();

            time_since_last_update += clock.restart();
            while time_since_last_update > time_per_frame {
                time_since_last_update -= time_per_frame;
                self.process_events();
                self.update(&time_per_frame);
            }
            self.render();
        }
    }

    fn process_events(&mut self) {
        while let Some(event) = self.window.poll_event() {
            match event {
                Event::Closed => self.window.close(),
                Event::KeyPressed { code, .. } => {
                    if code == Key::Enter || code == Key::Escape {
                        self.window.close();
                    } else {
                        self.handle_player_input(code, true);
                    }
                }
                Event::KeyReleased { code, .. } => self.handle_player_input(code, false),
                _ => {}
            }
        }
    }

    fn update(&mut self, delta_time: &Time) {
        self.update_paddle(delta_time);
        self.update_ball(delta_time);
    }

    fn update_paddle(&mut self, delta_time: &Time) {
        let mut movement = Vector2f::new(0.0, 0.0);

        if self.move_flags.left {
            if self.paddle.position().x > 0.0 {
                movement.x -= self.paddle_speed;
            }
        }
        if self.move_flags.right {
            if self.paddle.position().x + self.paddle.size().x < Self::WIDTH as f32 {
                movement.x += self.paddle_speed;
            }
        }
        self.paddle.move_(movement * delta_time.as_seconds());
    }

    fn update_ball(&mut self, delta_time: &Time) {
        self.update_ball_x(delta_time);
        self.update_ball_y(delta_time);
    }

    fn update_ball_x(&mut self, delta_time: &Time) {
        self.ball
            .move_((self.ball_velocity.x * delta_time.as_seconds(), 0.0));

        if self.ball.position().x + self.ball.radius() > Self::WIDTH as f32 {
            self.ball_velocity.x *= -1.0;
            // align to right of screen
            self.ball.set_position((
                Self::WIDTH as f32 - self.ball.radius(),
                self.ball.position().y,
            ));
        }

        if self.ball.position().x - self.ball.radius() < 0.0 {
            self.ball_velocity.x *= -1.0;
            //  align to left of screen
            self.ball
                .set_position((self.ball.radius(), self.ball.position().y));
        }

        let paddle_rect = self.paddle.global_bounds();
        let ball_rect = self.ball.global_bounds();
        let mut hit = false;

        if ball_rect.intersection(&paddle_rect) != None {
            if self.ball_velocity.x < 0.0 {
                // align ball to right of paddle
                self.ball.set_position((
                    paddle_rect.left + paddle_rect.width + self.ball.radius(),
                    self.ball.position().y,
                ));
            } else {
                // align ball to left of paddle
                self.ball.set_position((
                    paddle_rect.left - self.ball.radius(),
                    self.ball.position().y,
                ))
            }
            hit = true;
        }

        self.bricks.retain(|b| {
            let brick_rect = b.global_bounds();
            if ball_rect.intersection(&brick_rect) != None {
                if self.ball_velocity.x < 0.0 {
                    // align to right of brick
                    self.ball.set_position((
                        brick_rect.left + brick_rect.width + self.ball.radius(),
                        self.ball.position().y,
                    ));
                } else {
                    // align to left of brick
                    self.ball.set_position((
                        brick_rect.left - self.ball.radius(),
                        self.ball.position().y,
                    ));
                }
                hit = true;
                false
            } else {
                true
            }
        });

        if hit {
            self.ball_velocity.x *= -1.0;
        }
    }

    fn update_ball_y(&mut self, delta_time: &Time) {
        self.ball
            .move_((0.0, self.ball_velocity.y * delta_time.as_seconds()));

        if self.ball.position().y - self.ball.radius() < 0.0 {
            // align to top of screen
            self.ball_velocity.y *= -1.0;
            self.ball
                .set_position((self.ball.position().x, self.ball.radius()));
        }

        if self.ball.position().y + self.ball.radius() > Self::HEIGHT as f32 {
            // align to bottom of screen
            self.ball_velocity.y *= -1.0;
            self.ball.set_position((
                self.ball.position().x,
                Self::HEIGHT as f32 - self.ball.radius(),
            ));
        }

        let paddle_rect = self.paddle.global_bounds();
        let ball_rect = self.ball.global_bounds();
        let mut hit = false;

        if ball_rect.intersection(&paddle_rect) != None {
            if self.ball_velocity.y < 0.0 {
                // align to bottom of paddle
                self.ball
                    .set_position((self.ball.position().x, paddle_rect.top + paddle_rect.height));
            } else {
                // align to top of paddle
                self.ball
                    .set_position((self.ball.position().x, paddle_rect.top - self.ball.radius()));
            }

            hit = true;
        }

        self.bricks.retain(|b| {
            let brick_rect = b.global_bounds();
            if ball_rect.intersection(&brick_rect) != None {
                if self.ball_velocity.y < 0.0 {
                    // align to bottom of brick
                    self.ball.set_position((
                        self.ball.position().x,
                        brick_rect.top + brick_rect.height + self.ball.radius(),
                    ));
                } else {
                    // align top top of brick
                    self.ball.set_position((
                        self.ball.position().x,
                        brick_rect.top - self.ball.radius(),
                    ));
                }
                hit = true;
                false
            } else {
                true
            }
        });

        if hit {
            self.ball_velocity.y *= -1.0;
        }
    }

    fn render(&mut self) {
        self.window.clear(Color::BLUE);
        self.window.draw(&self.paddle);
        self.window.draw(&self.ball);
        for brick in &self.bricks {
            self.window.draw(brick);
        }
        self.window.display();
    }

    fn handle_player_input(&mut self, code: Key, pressed: bool) {
        match code {
            Key::Left => self.move_flags.left = pressed,
            Key::Right => self.move_flags.right = pressed,
            _ => {}
        }
    }
}

fn main() {
    let mut game = Game::new();
    game.run();
}

Cargo.toml

[package]
name = "bricks"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
sfml = "0.19.0"