mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 03:11:29 +11:00
more writing, but the demo just shows white
This commit is contained in:
parent
b40ee565d0
commit
e9b62f1832
|
@ -297,7 +297,7 @@ do this as our first thing at startup, but we might show the title and like a
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/// Mucks with the settings of Timers 0 and 1.
|
/// Mucks with the settings of Timers 0 and 1.
|
||||||
fn u32_from_user_wait() -> u32 {
|
unsafe fn u32_from_user_wait() -> u32 {
|
||||||
let mut t = TimerControl::default();
|
let mut t = TimerControl::default();
|
||||||
t.set_enabled(true);
|
t.set_enabled(true);
|
||||||
t.set_cascading(true);
|
t.set_cascading(true);
|
||||||
|
|
|
@ -1,2 +1,212 @@
|
||||||
# memory_game
|
# memory_game
|
||||||
|
|
||||||
|
For this example to show off our new skills we'll make a "memory" game. The idea
|
||||||
|
is that there's some face down cards and you pick one, it flips, you pick a
|
||||||
|
second, if they match they both go away, if they don't match they both turn back
|
||||||
|
face down. The player keeps going until all the cards are gone, then we'll deal
|
||||||
|
the cards again.
|
||||||
|
|
||||||
|
For this example, I started with the `light_cycle.rs` example and then just
|
||||||
|
copied it into a new file, `memory_game.rs`. Then I added most all the code from
|
||||||
|
the previous sections right into that file, so we'll assume that all those
|
||||||
|
definitions are in scope.
|
||||||
|
|
||||||
|
## Getting Some Images
|
||||||
|
|
||||||
|
First we need some images to show! Let's have one for our little selector thingy
|
||||||
|
that we'll move around to pick cards with. How about some little triangles at
|
||||||
|
the corner of a square like on a picture frame.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const CARD_SELECTOR: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x44400444,
|
||||||
|
0x44000044,
|
||||||
|
0x40000004,
|
||||||
|
0x00000000,
|
||||||
|
0x00000000,
|
||||||
|
0x40000004,
|
||||||
|
0x44000044,
|
||||||
|
0x44400444
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
That weird looking attribute keeps rustfmt from spreading out the values, so
|
||||||
|
that we can see it as an ASCII art. Now that we understand what an individual
|
||||||
|
tile looks like, let's add some mono-color squares.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const FULL_ONE: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const FULL_TWO: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const FULL_THREE: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
We can control the rest with palbank selection. Since there's 16 palbanks,
|
||||||
|
that's 48 little colored squares we can make, and 16 different selector colors,
|
||||||
|
which should be plenty in both cases.
|
||||||
|
|
||||||
|
## Setup The Images
|
||||||
|
|
||||||
|
### Arrange the PALRAM
|
||||||
|
|
||||||
|
Alright, so, as we went over, the first step is to make sure that we've got our
|
||||||
|
palette data in order. We'll be using this to set our palette values.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn set_bg_palette(slot: usize, color: u16) {
|
||||||
|
assert!(slot < 256);
|
||||||
|
unsafe { PALRAM_BG_BASE.offset(slot as isize).write(color) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Should the type of `slot` be changed to `u8` instead of `usize`? Well, maybe.
|
||||||
|
Let's not worry about it at the moment.
|
||||||
|
|
||||||
|
Of course, we don't need to set the color black, all the values start as black.
|
||||||
|
We just need to set the other colors we'll be wanting. For this demo, we'll just
|
||||||
|
use the same basic colors for both the BG and Object stuff.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn init_palette() {
|
||||||
|
// palbank 0: black/white/gray
|
||||||
|
set_bg_palette(2, rgb16(31, 31, 31));
|
||||||
|
set_bg_palette(3, rgb16(15, 15, 15));
|
||||||
|
// palbank 1 is reds
|
||||||
|
set_bg_palette(1 * 16 + 1, rgb16(31, 0, 0));
|
||||||
|
set_bg_palette(1 * 16 + 2, rgb16(22, 0, 0));
|
||||||
|
set_bg_palette(1 * 16 + 3, rgb16(10, 0, 0));
|
||||||
|
// palbank 2 is greens
|
||||||
|
set_bg_palette(2 * 16 + 1, rgb16(0, 31, 0));
|
||||||
|
set_bg_palette(2 * 16 + 2, rgb16(0, 22, 0));
|
||||||
|
set_bg_palette(2 * 16 + 3, rgb16(0, 10, 0));
|
||||||
|
// palbank 2 is blues
|
||||||
|
set_bg_palette(3 * 16 + 1, rgb16(0, 0, 31));
|
||||||
|
set_bg_palette(3 * 16 + 2, rgb16(0, 0, 22));
|
||||||
|
set_bg_palette(3 * 16 + 3, rgb16(0, 0, 10));
|
||||||
|
|
||||||
|
// Direct copy all BG selections into OBJ palette too
|
||||||
|
let mut bgp = PALRAM_BG_BASE;
|
||||||
|
let mut objp = PALRAM_OBJECT_BASE;
|
||||||
|
for _ in 0..(4 * 16) {
|
||||||
|
objp.write(bgp.read());
|
||||||
|
bgp = bgp.offset(1);
|
||||||
|
objp = objp.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arrange the Objects
|
||||||
|
|
||||||
|
So, time to think about objects. I'm thinking we'll have 13 objects in use. One
|
||||||
|
for the selector, and then 12 for the cards (a simple grid that's 4 wide and 3
|
||||||
|
tall).
|
||||||
|
|
||||||
|
We want a way to easily clear away all the objects that we're not using, which
|
||||||
|
is all the slots starting at some index and then going to the end.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn clear_objects_starting_with(base_slot: usize) {
|
||||||
|
let mut obj = ObjectAttributes::default();
|
||||||
|
obj.set_rendering(ObjectRenderMode::Disabled);
|
||||||
|
for s in base_slot..128 {
|
||||||
|
set_object_attributes(s, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next we set out the positions of our cards. We set the tile data we need, and
|
||||||
|
then assign the object attributes to go with it. For this, we'll make the
|
||||||
|
position finder function be its own thing since we'll also need it for the card
|
||||||
|
selector to move around. Finally, we set our selector as being at position 0,0
|
||||||
|
of the card grid.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn position_of_card(card_col: usize, card_row: usize) -> (u16, u16) {
|
||||||
|
(10 + card_col as u16 * 17, 5 + card_row as u16 * 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arrange_cards() {
|
||||||
|
set_obj_tile_4bpp(1, FULL_ONE);
|
||||||
|
set_obj_tile_4bpp(2, FULL_TWO);
|
||||||
|
set_obj_tile_4bpp(3, FULL_THREE);
|
||||||
|
let mut obj = ObjectAttributes::default();
|
||||||
|
obj.set_tile_index(2); // along with palbank0, this is a white card
|
||||||
|
for card_row in 0..3 {
|
||||||
|
for card_col in 0..4 {
|
||||||
|
let (col, row) = position_of_card(card_col, card_row);
|
||||||
|
obj.set_column(col);
|
||||||
|
obj.set_row(row);
|
||||||
|
set_object_attributes(1 + card_col as usize + (card_row as usize * 3), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_selector() {
|
||||||
|
set_obj_tile_4bpp(0, CARD_SELECTOR);
|
||||||
|
let mut obj = ObjectAttributes::default();
|
||||||
|
let (col, row) = position_of_card(0, 0);
|
||||||
|
obj.set_column(col);
|
||||||
|
obj.set_row(row);
|
||||||
|
set_object_attributes(0, obj);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arrange the Background
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Shuffling The Cards
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Picking One Card
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Picking The Second Card
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Resetting The Game
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
|
@ -76,28 +76,28 @@ within, and then you add the index of the tile slot you're placing it into times
|
||||||
the size of that type of tile. Like this:
|
the size of that type of tile. Like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn bg_tile_4pp(base_block: usize, tile_index: usize) -> Tile4bpp {
|
pub fn bg_tile_4bpp(base_block: usize, tile_index: usize) -> Tile4bpp {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 512);
|
assert!(tile_index < 512);
|
||||||
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_bg_tile_4pp(base_block: usize, tile_index: usize, tile: Tile4bpp) {
|
pub fn set_bg_tile_4bpp(base_block: usize, tile_index: usize, tile: Tile4bpp) {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 512);
|
assert!(tile_index < 512);
|
||||||
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bg_tile_8pp(base_block: usize, tile_index: usize) -> Tile8bpp {
|
pub fn bg_tile_8bpp(base_block: usize, tile_index: usize) -> Tile8bpp {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 256);
|
assert!(tile_index < 256);
|
||||||
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_bg_tile_8pp(base_block: usize, tile_index: usize, tile: Tile8bpp) {
|
pub fn set_bg_tile_8bpp(base_block: usize, tile_index: usize, tile: Tile8bpp) {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 256);
|
assert!(tile_index < 256);
|
||||||
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
||||||
|
@ -154,7 +154,7 @@ appropriately.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct RegularScreenblock {
|
pub struct RegularScreenblock {
|
||||||
data: [RegularScreenblockEntry; 32 * 32],
|
pub data: [RegularScreenblockEntry; 32 * 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
@ -207,7 +207,7 @@ impl RegularScreenblockEntry {
|
||||||
}
|
}
|
||||||
pub fn set_palbank_index(&mut self, palbank_index: u16) {
|
pub fn set_palbank_index(&mut self, palbank_index: u16) {
|
||||||
self.0 &= 0b1111_1111_1111;
|
self.0 &= 0b1111_1111_1111;
|
||||||
self.0 |= palbank_index;
|
self.0 |= palbank_index << 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -67,6 +67,32 @@ two object charblocks. However, in video modes 3, 4, and 5 the space for the
|
||||||
background cuts into the lower charblock, so you can only safely use the upper
|
background cuts into the lower charblock, so you can only safely use the upper
|
||||||
charblock.
|
charblock.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn obj_tile_4bpp(tile_index: usize) -> Tile4bpp {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock4bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_obj_tile_4bpp(tile_index: usize, tile: Tile4bpp) {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock4bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn obj_tile_8bpp(tile_index: usize) -> Tile8bpp {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock8bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_obj_tile_8bpp(tile_index: usize, tile: Tile8bpp) {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock8bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile8bpp).write(tile) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
With backgrounds you picked every single tile individually with a bunch of
|
With backgrounds you picked every single tile individually with a bunch of
|
||||||
screen entry values. Objects don't do that at all. Instead you pick a base tile,
|
screen entry values. Objects don't do that at all. Instead you pick a base tile,
|
||||||
size, and shape, then it figures out the rest from there. However, you may
|
size, and shape, then it figures out the rest from there. However, you may
|
||||||
|
|
|
@ -26,13 +26,13 @@ Knowing this, we can write the following definitions:
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Tile4bpp {
|
pub struct Tile4bpp {
|
||||||
data: [u32; 8]
|
pub data: [u32; 8]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Tile8bpp {
|
pub struct Tile8bpp {
|
||||||
data: [u32; 16]
|
pub data: [u32; 16]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -65,13 +65,13 @@ tiles, and with 8bpp there's 256 tiles. So they'd be something like this:
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Charblock4bpp {
|
pub struct Charblock4bpp {
|
||||||
data: [Tile4bpp; 512],
|
pub data: [Tile4bpp; 512],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Charblock8bpp {
|
pub struct Charblock8bpp {
|
||||||
data: [Tile8bpp; 256],
|
pub data: [Tile8bpp; 256],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -12,46 +12,14 @@ 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 {
|
unsafe {
|
||||||
DISPCNT.write(MODE3 | BG2);
|
init_palette();
|
||||||
}
|
init_background();
|
||||||
|
clear_objects_starting_with(13);
|
||||||
let mut px = SCREEN_WIDTH / 2;
|
arrange_cards();
|
||||||
let mut py = SCREEN_HEIGHT / 2;
|
init_selector();
|
||||||
let mut color = rgb16(31, 0, 0);
|
loop {
|
||||||
|
// TODO the game
|
||||||
loop {
|
|
||||||
// read the input for this frame
|
|
||||||
let this_frame_keys = key_input();
|
|
||||||
|
|
||||||
// adjust game state and wait for vblank
|
|
||||||
px += 2 * this_frame_keys.column_direction() as isize;
|
|
||||||
py += 2 * this_frame_keys.row_direction() as isize;
|
|
||||||
wait_until_vblank();
|
|
||||||
|
|
||||||
// draw the new game and wait until the next frame starts.
|
|
||||||
unsafe {
|
|
||||||
if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT {
|
|
||||||
// out of bounds, reset the screen and position.
|
|
||||||
mode3_clear_screen(0);
|
|
||||||
color = color.rotate_left(5);
|
|
||||||
px = SCREEN_WIDTH / 2;
|
|
||||||
py = SCREEN_HEIGHT / 2;
|
|
||||||
} else {
|
|
||||||
let color_here = mode3_read_pixel(px, py);
|
|
||||||
if color_here != 0 {
|
|
||||||
// crashed into our own line, reset the screen
|
|
||||||
mode3_clear_screen(0);
|
|
||||||
color = color.rotate_left(5);
|
|
||||||
} 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,25 +148,25 @@ pub fn wait_until_vdraw() {
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Tile4bpp {
|
pub struct Tile4bpp {
|
||||||
data: [u32; 8],
|
pub data: [u32; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Tile8bpp {
|
pub struct Tile8bpp {
|
||||||
data: [u32; 16],
|
pub data: [u32; 16],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Charblock4bpp {
|
pub struct Charblock4bpp {
|
||||||
data: [Tile4bpp; 512],
|
pub data: [Tile4bpp; 512],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Charblock8bpp {
|
pub struct Charblock8bpp {
|
||||||
data: [Tile8bpp; 256],
|
pub data: [Tile8bpp; 256],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PALRAM_BG_BASE: VolatilePtr<u16> = VolatilePtr(0x500_0000 as *mut u16);
|
pub const PALRAM_BG_BASE: VolatilePtr<u16> = VolatilePtr(0x500_0000 as *mut u16);
|
||||||
|
@ -213,34 +181,87 @@ pub fn set_bg_palette(slot: usize, color: u16) {
|
||||||
unsafe { PALRAM_BG_BASE.offset(slot as isize).write(color) }
|
unsafe { PALRAM_BG_BASE.offset(slot as isize).write(color) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bg_tile_4pp(base_block: usize, tile_index: usize) -> Tile4bpp {
|
pub unsafe fn init_palette() {
|
||||||
|
// palbank 0: black/white/gray
|
||||||
|
set_bg_palette(2, rgb16(31, 31, 31));
|
||||||
|
set_bg_palette(3, rgb16(15, 15, 15));
|
||||||
|
// palbank 1 is reds
|
||||||
|
set_bg_palette(1 * 16 + 1, rgb16(31, 0, 0));
|
||||||
|
set_bg_palette(1 * 16 + 2, rgb16(22, 0, 0));
|
||||||
|
set_bg_palette(1 * 16 + 3, rgb16(10, 0, 0));
|
||||||
|
// palbank 2 is greens
|
||||||
|
set_bg_palette(2 * 16 + 1, rgb16(0, 31, 0));
|
||||||
|
set_bg_palette(2 * 16 + 2, rgb16(0, 22, 0));
|
||||||
|
set_bg_palette(2 * 16 + 3, rgb16(0, 10, 0));
|
||||||
|
// palbank 2 is blues
|
||||||
|
set_bg_palette(3 * 16 + 1, rgb16(0, 0, 31));
|
||||||
|
set_bg_palette(3 * 16 + 2, rgb16(0, 0, 22));
|
||||||
|
set_bg_palette(3 * 16 + 3, rgb16(0, 0, 10));
|
||||||
|
|
||||||
|
// Direct copy all BG selections into OBJ palette too
|
||||||
|
let mut bgp = PALRAM_BG_BASE;
|
||||||
|
let mut objp = PALRAM_OBJECT_BASE;
|
||||||
|
for _ in 0..(4 * 16) {
|
||||||
|
objp.write(bgp.read());
|
||||||
|
bgp = bgp.offset(1);
|
||||||
|
objp = objp.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bg_tile_4bpp(base_block: usize, tile_index: usize) -> Tile4bpp {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 512);
|
assert!(tile_index < 512);
|
||||||
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_bg_tile_4pp(base_block: usize, tile_index: usize, tile: Tile4bpp) {
|
pub fn set_bg_tile_4bpp(base_block: usize, tile_index: usize, tile: Tile4bpp) {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 512);
|
assert!(tile_index < 512);
|
||||||
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bg_tile_8pp(base_block: usize, tile_index: usize) -> Tile8bpp {
|
pub fn bg_tile_8bpp(base_block: usize, tile_index: usize) -> Tile8bpp {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 256);
|
assert!(tile_index < 256);
|
||||||
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_bg_tile_8pp(base_block: usize, tile_index: usize, tile: Tile8bpp) {
|
pub fn set_bg_tile_8bpp(base_block: usize, tile_index: usize, tile: Tile8bpp) {
|
||||||
assert!(base_block < 4);
|
assert!(base_block < 4);
|
||||||
assert!(tile_index < 256);
|
assert!(tile_index < 256);
|
||||||
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
||||||
unsafe { VolatilePtr(address as *mut Tile8bpp).write(tile) }
|
unsafe { VolatilePtr(address as *mut Tile8bpp).write(tile) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
pub fn obj_tile_4bpp(tile_index: usize) -> Tile4bpp {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock4bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_obj_tile_4bpp(tile_index: usize, tile: Tile4bpp) {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock4bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn obj_tile_8bpp(tile_index: usize) -> Tile8bpp {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock8bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_obj_tile_8bpp(tile_index: usize, tile: Tile8bpp) {
|
||||||
|
assert!(tile_index < 512);
|
||||||
|
let address = VRAM + size_of::<Charblock8bpp>() * 4 + 32 * tile_index;
|
||||||
|
unsafe { VolatilePtr(address as *mut Tile8bpp).write(tile) }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct RegularScreenblock {
|
pub struct RegularScreenblock {
|
||||||
|
@ -284,7 +305,7 @@ impl RegularScreenblockEntry {
|
||||||
}
|
}
|
||||||
pub fn set_palbank_index(&mut self, palbank_index: u16) {
|
pub fn set_palbank_index(&mut self, palbank_index: u16) {
|
||||||
self.0 &= 0b1111_1111_1111;
|
self.0 &= 0b1111_1111_1111;
|
||||||
self.0 |= palbank_index;
|
self.0 |= palbank_index << 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,6 +345,69 @@ pub fn set_object_attributes(slot: usize, obj: ObjectAttributes) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_objects_starting_with(base_slot: usize) {
|
||||||
|
let mut obj = ObjectAttributes::default();
|
||||||
|
obj.set_rendering(ObjectRenderMode::Disabled);
|
||||||
|
for s in base_slot..128 {
|
||||||
|
set_object_attributes(s, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position_of_card(card_col: usize, card_row: usize) -> (u16, u16) {
|
||||||
|
(10 + card_col as u16 * 17, 5 + card_row as u16 * 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arrange_cards() {
|
||||||
|
set_obj_tile_4bpp(1, FULL_ONE);
|
||||||
|
set_obj_tile_4bpp(2, FULL_TWO);
|
||||||
|
set_obj_tile_4bpp(3, FULL_THREE);
|
||||||
|
let mut obj = ObjectAttributes::default();
|
||||||
|
obj.set_tile_index(2); // along with palbank0, this is a white card
|
||||||
|
for card_row in 0..3 {
|
||||||
|
for card_col in 0..4 {
|
||||||
|
let (col, row) = position_of_card(card_col, card_row);
|
||||||
|
obj.set_column(col);
|
||||||
|
obj.set_row(row);
|
||||||
|
set_object_attributes(1 + card_col as usize + (card_row as usize * 3), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_selector() {
|
||||||
|
set_obj_tile_4bpp(0, CARD_SELECTOR);
|
||||||
|
let mut obj = ObjectAttributes::default();
|
||||||
|
let (col, row) = position_of_card(0, 0);
|
||||||
|
obj.set_column(col);
|
||||||
|
obj.set_row(row);
|
||||||
|
set_object_attributes(0, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// BG2 Control
|
||||||
|
pub const BG2CNT: VolatilePtr<u16> = VolatilePtr(0x400_000C as *mut u16);
|
||||||
|
|
||||||
|
pub unsafe fn init_background() {
|
||||||
|
// put the bg tiles in charblock 0
|
||||||
|
set_bg_tile_4bpp(0, 0, FULL_ONE);
|
||||||
|
set_bg_tile_4bpp(0, 1, FULL_THREE);
|
||||||
|
// make a checker pattern, place at screenblock 8 (aka the start of charblock 1)
|
||||||
|
let entry_black = RegularScreenblockEntry::default();
|
||||||
|
let mut entry_gray = RegularScreenblockEntry::default();
|
||||||
|
entry_gray.set_tile_id(1);
|
||||||
|
let mut using_black = true;
|
||||||
|
let mut screenblock: RegularScreenblock = core::mem::zeroed();
|
||||||
|
for entry_mut in screenblock.data.iter_mut() {
|
||||||
|
*entry_mut = if using_black { entry_black } else { entry_gray };
|
||||||
|
using_black = !using_black;
|
||||||
|
}
|
||||||
|
let p: VolatilePtr<RegularScreenblock> = VolatilePtr((VRAM + size_of::<Charblock8bpp>()) as *mut RegularScreenblock);
|
||||||
|
p.write(screenblock);
|
||||||
|
// turn on bg2 and configure it
|
||||||
|
let display_control_value = DISPCNT.read();
|
||||||
|
DISPCNT.write(display_control_value | BG2);
|
||||||
|
const SCREEN_BASE_BLOCK_FIRST_BIT: u32 = 8;
|
||||||
|
BG2CNT.write(8 << SCREEN_BASE_BLOCK_FIRST_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
pub struct ObjectAttributes {
|
pub struct ObjectAttributes {
|
||||||
attr0: u16,
|
attr0: u16,
|
||||||
|
@ -501,11 +585,11 @@ pub fn div_modulus(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
let mod_out: i32;
|
let mod_out: i32;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(/* assembly template */ "swi 0x06"
|
asm!(/* assembly template */ "swi 0x06"
|
||||||
:/* output operands */ "={r0}"(div_out), "={r1}"(mod_out)
|
:/* output operands */ "={r0}"(div_out), "={r1}"(mod_out)
|
||||||
:/* input operands */ "{r0}"(numerator), "{r1}"(denominator)
|
:/* input operands */ "{r0}"(numerator), "{r1}"(denominator)
|
||||||
:/* clobbers */ "r3"
|
:/* clobbers */ "r3"
|
||||||
:/* options */
|
:/* options */
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(div_out, mod_out)
|
(div_out, mod_out)
|
||||||
}
|
}
|
||||||
|
@ -634,7 +718,7 @@ impl TimerControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mucks with the settings of Timers 0 and 1.
|
/// Mucks with the settings of Timers 0 and 1.
|
||||||
fn u32_from_user_wait() -> u32 {
|
unsafe fn u32_from_user_wait() -> u32 {
|
||||||
let mut t = TimerControl::default();
|
let mut t = TimerControl::default();
|
||||||
t.set_enabled(true);
|
t.set_enabled(true);
|
||||||
t.set_cascading(true);
|
t.set_cascading(true);
|
||||||
|
@ -649,3 +733,60 @@ fn u32_from_user_wait() -> u32 {
|
||||||
let high = TM1D.read() as u32;
|
let high = TM1D.read() as u32;
|
||||||
(high << 32) | low
|
(high << 32) | low
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For the user's "cursor" to select a card
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const CARD_SELECTOR: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x11100111,
|
||||||
|
0x11000011,
|
||||||
|
0x10000001,
|
||||||
|
0x00000000,
|
||||||
|
0x00000000,
|
||||||
|
0x10000001,
|
||||||
|
0x11000011,
|
||||||
|
0x11100111
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const FULL_ONE: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
0x11111111,
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const FULL_TWO: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222,
|
||||||
|
0x22222222
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub const FULL_THREE: Tile4bpp = Tile4bpp {
|
||||||
|
data : [
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333,
|
||||||
|
0x33333333
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
/// Read and Write are made to be volatile. Offset is made to be
|
/// Read and Write are made to be volatile. Offset is made to be
|
||||||
/// wrapping_offset. This makes it much easier to correctly work with IO
|
/// wrapping_offset. This makes it much easier to correctly work with IO
|
||||||
/// Registers and all display related memory on the GBA.
|
/// Registers and all display related memory on the GBA.
|
||||||
///
|
|
||||||
/// As a bonus, use of this type is mostly `cargo test` safe. Reads will return
|
|
||||||
/// a `zeroed()` value instead, and writes will do nothing.
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct VolatilePtr<T>(pub *mut T);
|
pub struct VolatilePtr<T>(pub *mut T);
|
||||||
|
@ -20,47 +17,23 @@ impl<T> core::fmt::Pointer for VolatilePtr<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VolatilePtr<T> {
|
impl<T> VolatilePtr<T> {
|
||||||
/// Performs a volatile read.
|
/// Performs a `read_volatile`.
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This method adds absolutely no additional safety, so all safety concerns
|
|
||||||
/// for a normal raw pointer volatile read apply.
|
|
||||||
pub unsafe fn read(&self) -> T {
|
pub unsafe fn read(&self) -> T {
|
||||||
#[cfg(not(test))]
|
self.0.read_volatile()
|
||||||
{
|
|
||||||
core::ptr::read_volatile(self.0)
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
{
|
|
||||||
core::mem::zeroed::<T>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a volatile write.
|
/// Performs a `write_volatile`.
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This method adds absolutely no additional safety, so all safety concerns
|
|
||||||
/// for a normal raw pointer volatile write apply.
|
|
||||||
pub unsafe fn write(&self, data: T) {
|
pub unsafe fn write(&self, data: T) {
|
||||||
#[cfg(not(test))]
|
self.0.write_volatile(data);
|
||||||
{
|
|
||||||
core::ptr::write_volatile(self.0, data);
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
{
|
|
||||||
drop(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a wrapping_offset by the number of slots given to a new position.
|
/// Performs a `wrapping_offset`.
|
||||||
///
|
pub fn offset(self, count: isize) -> Self {
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// This is a wrapping_offset, so all safety concerns of a normal raw pointer
|
|
||||||
/// wrapping_offset apply.
|
|
||||||
pub unsafe fn offset(self, count: isize) -> Self {
|
|
||||||
VolatilePtr(self.0.wrapping_offset(count))
|
VolatilePtr(self.0.wrapping_offset(count))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs a cast into some new pointer type.
|
||||||
|
pub fn cast<Z>(self) -> VolatilePtr<Z> {
|
||||||
|
VolatilePtr(self.0 as *mut Z)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue