mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
light cycle is also 100% safe now
This commit is contained in:
parent
35ed03cb44
commit
7ab96c35f2
|
@ -1,5 +1,6 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(start)]
|
#![feature(start)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use gba::{
|
use gba::{
|
||||||
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
io::display::{DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(start)]
|
#![feature(start)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use gba::{
|
||||||
|
io::{
|
||||||
|
display::{spin_until_vblank, spin_until_vdraw, DisplayControlMode, DisplayControlSetting, DISPCNT},
|
||||||
|
keypad::read_key_input,
|
||||||
|
},
|
||||||
|
video::Mode3,
|
||||||
|
Color,
|
||||||
|
};
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
@ -8,168 +18,44 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
|
||||||
#[start]
|
#[start]
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
unsafe {
|
const SETTING: DisplayControlSetting = DisplayControlSetting::new().with_mode(DisplayControlMode::Bitmap3).with_display_bg2(true);
|
||||||
DISPCNT.write(MODE3 | BG2);
|
DISPCNT.write(SETTING);
|
||||||
}
|
|
||||||
|
|
||||||
let mut px = SCREEN_WIDTH / 2;
|
let mut px = Mode3::SCREEN_WIDTH / 2;
|
||||||
let mut py = SCREEN_HEIGHT / 2;
|
let mut py = Mode3::SCREEN_HEIGHT / 2;
|
||||||
let mut color = rgb16(31, 0, 0);
|
let mut color = Color::from_rgb(31, 0, 0);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// read the input for this frame
|
// read the input for this frame
|
||||||
let this_frame_keys = key_input();
|
let this_frame_keys = read_key_input();
|
||||||
|
|
||||||
// adjust game state and wait for vblank
|
// adjust game state and wait for vblank
|
||||||
px += 2 * this_frame_keys.column_direction() as isize;
|
px = px.wrapping_add(2 * this_frame_keys.column_direction() as usize);
|
||||||
py += 2 * this_frame_keys.row_direction() as isize;
|
py = py.wrapping_add(2 * this_frame_keys.row_direction() as usize);
|
||||||
wait_until_vblank();
|
spin_until_vblank();
|
||||||
|
|
||||||
// draw the new game and wait until the next frame starts.
|
// draw the new game and wait until the next frame starts.
|
||||||
unsafe {
|
const BLACK: Color = Color::from_rgb(0, 0, 0);
|
||||||
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
if px >= Mode3::SCREEN_WIDTH || py >= Mode3::SCREEN_HEIGHT {
|
||||||
// out of bounds, reset the screen and position.
|
// out of bounds, reset the screen and position.
|
||||||
mode3_clear_screen(0);
|
Mode3::clear_to(BLACK);
|
||||||
|
color = color.rotate_left(5);
|
||||||
|
px = Mode3::SCREEN_WIDTH / 2;
|
||||||
|
py = Mode3::SCREEN_HEIGHT / 2;
|
||||||
|
} else {
|
||||||
|
let color_here = Mode3::read_pixel(px, py);
|
||||||
|
if color_here != Some(BLACK) {
|
||||||
|
// crashed into our own line, reset the screen
|
||||||
|
Mode3::clear_to(BLACK);
|
||||||
color = color.rotate_left(5);
|
color = color.rotate_left(5);
|
||||||
px = SCREEN_WIDTH / 2;
|
|
||||||
py = SCREEN_HEIGHT / 2;
|
|
||||||
} else {
|
} else {
|
||||||
let color_here = mode3_read_pixel(px, py);
|
// draw the new part of the line
|
||||||
if color_here != 0 {
|
Mode3::write_pixel(px, py, color);
|
||||||
// crashed into our own line, reset the screen
|
Mode3::write_pixel(px, py + 1, color);
|
||||||
mode3_clear_screen(0);
|
Mode3::write_pixel(px + 1, py, color);
|
||||||
color = color.rotate_left(5);
|
Mode3::write_pixel(px + 1, py + 1, color);
|
||||||
} else {
|
|
||||||
// draw the new part of the line
|
|
||||||
mode3_draw_pixel(px, py, color);
|
|
||||||
mode3_draw_pixel(px, py + 1, color);
|
|
||||||
mode3_draw_pixel(px + 1, py, color);
|
|
||||||
mode3_draw_pixel(px + 1, py + 1, color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wait_until_vdraw();
|
spin_until_vdraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct VolatilePtr<T>(pub *mut T);
|
|
||||||
impl<T> VolatilePtr<T> {
|
|
||||||
pub unsafe fn read(&self) -> T {
|
|
||||||
core::ptr::read_volatile(self.0)
|
|
||||||
}
|
|
||||||
pub unsafe fn write(&self, data: T) {
|
|
||||||
core::ptr::write_volatile(self.0, data);
|
|
||||||
}
|
|
||||||
pub unsafe fn offset(self, count: isize) -> Self {
|
|
||||||
VolatilePtr(self.0.wrapping_offset(count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
|
|
||||||
pub const MODE3: u16 = 3;
|
|
||||||
pub const BG2: u16 = 0b100_0000_0000;
|
|
||||||
|
|
||||||
pub const VRAM: usize = 0x06000000;
|
|
||||||
pub const SCREEN_WIDTH: isize = 240;
|
|
||||||
pub const SCREEN_HEIGHT: isize = 160;
|
|
||||||
|
|
||||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
|
||||||
blue << 10 | green << 5 | red
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn mode3_clear_screen(color: u16) {
|
|
||||||
let color = color as u32;
|
|
||||||
let bulk_color = color << 16 | color;
|
|
||||||
let mut ptr = VolatilePtr(VRAM as *mut u32);
|
|
||||||
for _ in 0..SCREEN_HEIGHT {
|
|
||||||
for _ in 0..(SCREEN_WIDTH / 2) {
|
|
||||||
ptr.write(bulk_color);
|
|
||||||
ptr = ptr.offset(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
|
||||||
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
|
||||||
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x400_0130 as *mut u16);
|
|
||||||
|
|
||||||
/// A newtype over the key input state of the GBA.
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct KeyInputSetting(u16);
|
|
||||||
|
|
||||||
/// A "tribool" value helps us interpret the arrow pad.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
pub enum TriBool {
|
|
||||||
Minus = -1,
|
|
||||||
Neutral = 0,
|
|
||||||
Plus = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn key_input() -> KeyInputSetting {
|
|
||||||
unsafe { KeyInputSetting(KEYINPUT.read() ^ 0b0000_0011_1111_1111) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const KEY_A: u16 = 1 << 0;
|
|
||||||
pub const KEY_B: u16 = 1 << 1;
|
|
||||||
pub const KEY_SELECT: u16 = 1 << 2;
|
|
||||||
pub const KEY_START: u16 = 1 << 3;
|
|
||||||
pub const KEY_RIGHT: u16 = 1 << 4;
|
|
||||||
pub const KEY_LEFT: u16 = 1 << 5;
|
|
||||||
pub const KEY_UP: u16 = 1 << 6;
|
|
||||||
pub const KEY_DOWN: u16 = 1 << 7;
|
|
||||||
pub const KEY_R: u16 = 1 << 8;
|
|
||||||
pub const KEY_L: u16 = 1 << 9;
|
|
||||||
|
|
||||||
impl KeyInputSetting {
|
|
||||||
pub fn contains(&self, key: u16) -> bool {
|
|
||||||
(self.0 & key) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
|
||||||
KeyInputSetting(self.0 ^ other.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn column_direction(&self) -> TriBool {
|
|
||||||
if self.contains(KEY_RIGHT) {
|
|
||||||
TriBool::Plus
|
|
||||||
} else if self.contains(KEY_LEFT) {
|
|
||||||
TriBool::Minus
|
|
||||||
} else {
|
|
||||||
TriBool::Neutral
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn row_direction(&self) -> TriBool {
|
|
||||||
if self.contains(KEY_DOWN) {
|
|
||||||
TriBool::Plus
|
|
||||||
} else if self.contains(KEY_UP) {
|
|
||||||
TriBool::Minus
|
|
||||||
} else {
|
|
||||||
TriBool::Neutral
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const VCOUNT: VolatilePtr<u16> = VolatilePtr(0x0400_0006 as *mut u16);
|
|
||||||
|
|
||||||
pub fn vcount() -> u16 {
|
|
||||||
unsafe { VCOUNT.read() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_until_vblank() {
|
|
||||||
while vcount() < SCREEN_HEIGHT as u16 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_until_vdraw() {
|
|
||||||
while vcount() >= SCREEN_HEIGHT as u16 {}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![cfg_attr(not(test), no_std)]
|
#![cfg_attr(not(test), no_std)]
|
||||||
#![feature(asm)]
|
#![feature(asm)]
|
||||||
#![feature(const_int_wrapping)]
|
#![feature(const_int_wrapping)]
|
||||||
|
#![feature(const_int_rotate)]
|
||||||
#![feature(min_const_unsafe_fn)]
|
#![feature(min_const_unsafe_fn)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![allow(clippy::cast_lossless)]
|
#![allow(clippy::cast_lossless)]
|
||||||
|
@ -78,6 +79,13 @@ impl Color {
|
||||||
pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color {
|
pub const fn from_rgb(r: u16, g: u16, b: u16) -> Color {
|
||||||
Color(b << 10 | g << 5 | r)
|
Color(b << 10 | g << 5 | r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does a left rotate of the bits.
|
||||||
|
///
|
||||||
|
/// This has no particular meaning but is a wild way to cycle colors.
|
||||||
|
pub const fn rotate_left(self, n: u32) -> Color {
|
||||||
|
Color(self.0.rotate_left(n))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue