gba 0.15.0

A crate for 'raw' style GBA development. If you want a 'managed' experience, try the `agb` crate instead.
Documentation
/*
 * Made by Evan Goemer
 * Discord: @evangoemer
 */

#![no_std]
#![no_main]

use gba::prelude::*;

const SCREEN_WIDTH: u16 = 240;
const SCREEN_HEIGHT: u16 = 160;

const PADDLE_WIDTH: u16 = 4;
const PADDLE_HEIGHT: u16 = 20;
const BALL_SIZE: u16 = 2;

struct Paddle {
  x: u16,
  y: u16,
}

struct Ball {
  x: u16,
  y: u16,
  dx: i16,
  dy: i16,
}

impl Paddle {
  fn new(x: u16, y: u16) -> Self {
    Self { x, y }
  }

  fn update(&mut self) {
    let keys = KEYINPUT.read();
    if keys.up() && self.y > 1 {
      self.y -= 1;
    }

    if keys.down() && self.y + PADDLE_HEIGHT + 1 < SCREEN_HEIGHT {
      self.y += 1;
    }
  }
}

impl Ball {
  fn new(x: u16, y: u16) -> Self {
    Self { x, y, dx: 1, dy: 1 }
  }

  fn update(&mut self, paddle1: &Paddle, paddle2: &Paddle) {
    if self.y <= 0 || self.y + BALL_SIZE >= SCREEN_HEIGHT {
      self.dy = -self.dy;
    }

    if self.x + BALL_SIZE >= paddle1.x
      && self.x <= paddle1.x + PADDLE_WIDTH
      && self.y + BALL_SIZE >= paddle1.y
      && self.y <= paddle1.y + PADDLE_HEIGHT
    {
      self.dx = -self.dx;
      self.dy = self.dy;
    }

    if self.x + BALL_SIZE >= paddle2.x
      && self.x <= paddle2.x + PADDLE_WIDTH
      && self.y + BALL_SIZE >= paddle2.y
      && self.y <= paddle2.y + PADDLE_HEIGHT
    {
      self.dx = -self.dx;
      self.dy = self.dy;
    }

    if self.x + BALL_SIZE <= 1 + BALL_SIZE {
      self.x = SCREEN_WIDTH / 2 - BALL_SIZE / 2;
      self.y = SCREEN_HEIGHT / 2 - BALL_SIZE / 2;
      self.dx = 1;
      self.dy = 1;
    }

    if self.x >= SCREEN_WIDTH - BALL_SIZE - 1 {
      self.x = SCREEN_WIDTH / 2 - BALL_SIZE / 2;
      self.y = SCREEN_HEIGHT / 2 - BALL_SIZE / 2;
      self.dx = -1;
      self.dy = 1;
    }
    self.x = (self.x as i16 + self.dx) as u16;
    self.y = (self.y as i16 + self.dy) as u16;
  }
}

static SPRITE_POSITIONS: [GbaCell<u16>; 6] = [
  GbaCell::new(0),
  GbaCell::new(0),
  GbaCell::new(0),
  GbaCell::new(0),
  GbaCell::new(0),
  GbaCell::new(0),
];

#[panic_handler]
fn panic_handler(_: &core::panic::PanicInfo) -> ! {
  loop {}
}

#[no_mangle]
fn main() -> ! {
  DISPCNT.write(
    DisplayControl::new().with_video_mode(VideoMode::_3).with_show_bg2(true),
  );

  RUST_IRQ_HANDLER.write(Some(draw_sprites));
  DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
  IE.write(IrqBits::VBLANK);
  IME.write(true);

  let mut left_paddle =
    Paddle::new(10, SCREEN_HEIGHT as u16 / 2 - PADDLE_HEIGHT / 2);
  let mut right_paddle = Paddle::new(
    SCREEN_WIDTH as u16 - 10 - PADDLE_WIDTH,
    SCREEN_HEIGHT as u16 / 2 - PADDLE_HEIGHT / 2,
  );
  let mut ball = Ball::new(SCREEN_WIDTH as u16 / 2, SCREEN_HEIGHT as u16 / 2);

  loop {
    left_paddle.update();
    right_paddle.update();
    ball.update(&left_paddle, &right_paddle);

    SPRITE_POSITIONS[0].write(left_paddle.x);
    SPRITE_POSITIONS[1].write(left_paddle.y);
    SPRITE_POSITIONS[2].write(right_paddle.x);
    SPRITE_POSITIONS[3].write(right_paddle.y);
    SPRITE_POSITIONS[4].write(ball.x);
    SPRITE_POSITIONS[5].write(ball.y);

    VBlankIntrWait();
  }
}

#[link_section = ".iwram.draw_sprites"]
extern "C" fn draw_sprites(_bits: IrqBits) {
  video3_clear_to(Color::BLACK);

  draw_rect(
    SPRITE_POSITIONS[0].read(),
    SPRITE_POSITIONS[1].read(),
    PADDLE_WIDTH,
    PADDLE_HEIGHT,
    Color::WHITE,
  );
  draw_rect(
    SPRITE_POSITIONS[2].read(),
    SPRITE_POSITIONS[3].read(),
    PADDLE_WIDTH,
    PADDLE_HEIGHT,
    Color::WHITE,
  );
  draw_rect(
    SPRITE_POSITIONS[4].read(),
    SPRITE_POSITIONS[5].read(),
    BALL_SIZE,
    BALL_SIZE,
    Color::WHITE,
  );
}

#[link_section = ".iwram.draw_rect"]
fn draw_rect(x: u16, y: u16, width: u16, height: u16, color: Color) {
  for i in 0..width {
    for j in 0..height {
      VIDEO3_VRAM.index((x + i) as usize, (y + j) as usize).write(color);
    }
  }
}