mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-24 08:02:27 +00:00
gb: updated presets
This commit is contained in:
1 parent
a5a6ea184b
commit
6bc9ac85f2
16 files changed
+2151
-4
No files matched your search
+2
-1
@@ -12,4 +12,5 @@ config.js
|
||||
chromedriver.log
|
||||
nightwatch.conf.js
|
||||
/src/docs/toolchains.md
|
||||
LICENSES/
|
||||
LICENSES/
|
||||
.vscode
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
Streaming animation: only the current frame lives in VRAM.
|
||||
|
||||
A 32x32 pre-rendered ball (see ballframes.h, made by the book's
|
||||
tools/prerender.py) has 16 animation frames of 16 tiles each: 256 tiles,
|
||||
which is more than the sprite half of VRAM holds for *everything*. Games
|
||||
with lots of big, smooth animation -- Donkey Kong Land, Shantae -- don't
|
||||
keep every frame in VRAM. They keep only the frame being shown, and copy
|
||||
the next one in from ROM as it's needed.
|
||||
|
||||
To avoid showing a half-copied frame, sprite tiles are double-buffered:
|
||||
|
||||
tiles 0..15 set A the sprites point at one set while we
|
||||
tiles 16..31 set B copy the next frame into the other, then flip
|
||||
|
||||
How fast is the copy? On a DMG the CPU copies 256 bytes with the usual
|
||||
VRAM-safe loop. On a Game Boy Color, one general-purpose DMA (GDMA)
|
||||
moves it in about one scanline while the CPU waits. copy_start and
|
||||
copy_end record the scanline (LY) before and after, so you can compare:
|
||||
run this on the "gb" and "gb.color" platforms and watch them in the
|
||||
memory view.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/cgb.h"
|
||||
|
||||
// DMA reads its source on 16-byte boundaries, so pin the frames to an
|
||||
// aligned address in the upper ROM (this demo's code stays below it)
|
||||
#define BALL_FRAMES_AT __at(0x7000)
|
||||
#include "ballframes.h"
|
||||
|
||||
#define SET_A 0
|
||||
#define SET_B 16
|
||||
|
||||
uint8_t copy_start, copy_end; // LY before and after the last copy
|
||||
|
||||
// Copy one frame's 16 tiles into sprite tiles 'dest'..dest+15.
|
||||
void load_frame(uint8_t frame, uint8_t dest) {
|
||||
const uint8_t* src = ball_frames[frame];
|
||||
copy_start = LY_REG;
|
||||
if (_cpu == CGB_TYPE) {
|
||||
uint16_t s = (uint16_t)src;
|
||||
uint16_t d = 0x8000 + dest * 16;
|
||||
HDMA1_REG = s >> 8; // source, high/low
|
||||
HDMA2_REG = s & 0xff;
|
||||
HDMA3_REG = d >> 8; // destination in VRAM, high/low
|
||||
HDMA4_REG = d & 0xff;
|
||||
HDMA5_REG = BALL_TILES - 1; // bit 7 = 0: general-purpose, 16 blocks
|
||||
// of 16 bytes; the CPU halts until done
|
||||
} else {
|
||||
set_sprite_data(dest, BALL_TILES, src);
|
||||
}
|
||||
copy_end = LY_REG;
|
||||
}
|
||||
|
||||
// point the 8 sprites (8x16 mode, 4 across x 2 down) at a tile set
|
||||
void show_set(uint8_t base) {
|
||||
uint8_t i;
|
||||
for (i = 0; i < 8; i++) set_sprite_tile(i, base + i * 2);
|
||||
}
|
||||
|
||||
void place(uint8_t x, uint8_t y) {
|
||||
uint8_t i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
move_sprite(i, x + (i & 3) * 8, y + (i >> 2) * 16);
|
||||
}
|
||||
}
|
||||
|
||||
/*{pal:555,n:4,bpw:16}*/
|
||||
const palette_color_t ball_pal[4] = {
|
||||
0x0000, 0x4B9F, 0x11DC, 0x088C // RGB(0,0,0), (31,28,18), (28,14,4), (12,4,2)
|
||||
};
|
||||
/*{pal:555,n:4,bpw:16}*/
|
||||
const palette_color_t bg_pal[4] = {
|
||||
0x7F54, 0x2A8A, 0x1986, 0x0000 // RGB(20,26,31), (10,20,10), (6,12,6), (0,0,0)
|
||||
};
|
||||
|
||||
void main(void) {
|
||||
uint8_t frame = 0, tick = 0, cur = SET_A, x = 20;
|
||||
int8_t dx = 1;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
// sprite colors 1..3 = white, light gray, dark gray (0 is transparent)
|
||||
OBP0_REG = 0x90;
|
||||
if (_cpu == CGB_TYPE) {
|
||||
set_sprite_palette(0, 1, (palette_color_t*)ball_pal);
|
||||
set_bkg_palette(0, 1, (palette_color_t*)bg_pal);
|
||||
}
|
||||
SPRITES_8x16;
|
||||
load_frame(0, SET_A);
|
||||
show_set(SET_A);
|
||||
place(x, 70);
|
||||
SHOW_BKG;
|
||||
SHOW_SPRITES;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
wait_vbl_done();
|
||||
// next frame every other video frame: 30 fps animation. Copy first,
|
||||
// while we're still in VBlank: GDMA must not run while the PPU is
|
||||
// drawing (mode 3), because VRAM writes are ignored then.
|
||||
if (++tick & 1) {
|
||||
uint8_t back = (cur == SET_A) ? SET_B : SET_A;
|
||||
frame = (frame + (dx > 0 ? 1 : BALL_FRAMES - 1)) & (BALL_FRAMES - 1);
|
||||
load_frame(frame, back); // copy into the hidden set...
|
||||
show_set(back); // ...then flip (OAM updates next VBlank)
|
||||
cur = back;
|
||||
}
|
||||
// roll back and forth
|
||||
x += dx;
|
||||
if (x < 10 || x > 130) dx = -dx;
|
||||
place(x, 70);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
Generates random sounds in the Game Boy APU, printing the parameters
|
||||
to the screen. Also shows an asterisk while each channel is
|
||||
playing, i.e. while its length counter is active (NR52 bits 0-3).
|
||||
|
||||
Port of presets/nes/aputest.c. The Game Boy has two pulse channels
|
||||
(the first with a frequency sweep), a wave channel and a noise channel.
|
||||
Only one or two channels are picked at a time so that all of their
|
||||
parameters fit on the 20x18 tile screen.
|
||||
|
||||
A = new sound, B or START = replay the same sound.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
typedef uint8_t byte;
|
||||
typedef uint16_t word;
|
||||
|
||||
// channel bits, same as NR52 status bits
|
||||
#define CH_PULSE1 0x01
|
||||
#define CH_PULSE2 0x02
|
||||
#define CH_WAVE 0x04
|
||||
#define CH_NOISE 0x08
|
||||
|
||||
typedef struct APUParam {
|
||||
byte chmask;
|
||||
const char* name;
|
||||
word valmask;
|
||||
word valmin;
|
||||
} APUParam;
|
||||
|
||||
// parameter indices (order of APU_DEFS)
|
||||
enum {
|
||||
P1_FREQ, P1_DUTY, P1_VOL, P1_ENVPER, P1_LEN, P1_SWPPER, P1_SWPDN, P1_SWPSHF,
|
||||
P2_FREQ, P2_DUTY, P2_VOL, P2_ENVPER, P2_LEN,
|
||||
W_FREQ, W_LEVEL, W_LEN,
|
||||
N_SHIFT, N_WIDTH, N_DIV, N_VOL, N_ENVPER, N_LEN,
|
||||
APU_DEFCOUNT
|
||||
};
|
||||
|
||||
const APUParam APU_DEFS[APU_DEFCOUNT] = {
|
||||
{CH_PULSE1, "Pulse1 Freq", 0x7ff, 0x100 },
|
||||
{CH_PULSE1, "Pulse1 Duty", 0x03, 0 },
|
||||
{CH_PULSE1, "Pulse1 Vol", 0x0f, 1 },
|
||||
{CH_PULSE1, "Pulse1 EnvPer", 0x07, 0 },
|
||||
{CH_PULSE1, "Pulse1 Length", 0x3f, 0 },
|
||||
{CH_PULSE1, "Pulse1 SwpPer", 0x07, 0 },
|
||||
{CH_PULSE1, "Pulse1 SwpDown?",0x01, 0 },
|
||||
{CH_PULSE1, "Pulse1 SwpShf", 0x07, 0 },
|
||||
{CH_PULSE2, "Pulse2 Freq", 0x7ff, 0x100 },
|
||||
{CH_PULSE2, "Pulse2 Duty", 0x03, 0 },
|
||||
{CH_PULSE2, "Pulse2 Vol", 0x0f, 1 },
|
||||
{CH_PULSE2, "Pulse2 EnvPer", 0x07, 0 },
|
||||
{CH_PULSE2, "Pulse2 Length", 0x3f, 0 },
|
||||
{CH_WAVE, "Wave Freq", 0x7ff, 0x100 },
|
||||
{CH_WAVE, "Wave Level", 0x03, 1 },
|
||||
{CH_WAVE, "Wave Length", 0xff, 0 },
|
||||
{CH_NOISE, "Noise Shift", 0x0f, 0 },
|
||||
{CH_NOISE, "Noise 7-bit?", 0x01, 0 },
|
||||
{CH_NOISE, "Noise Divisor", 0x07, 0 },
|
||||
{CH_NOISE, "Noise Vol", 0x0f, 1 },
|
||||
{CH_NOISE, "Noise EnvPer", 0x07, 0 },
|
||||
{CH_NOISE, "Noise Length", 0x3f, 0 },
|
||||
};
|
||||
|
||||
/* 32-sample waveform, two 4-bit samples per byte. */
|
||||
const byte wave_pattern[16] = {
|
||||
0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10
|
||||
};
|
||||
|
||||
// 16-bit LCG
|
||||
static word rnd = 0xCACE;
|
||||
static word rand16(void) {
|
||||
rnd = rnd * 17 + 53;
|
||||
return rnd ^ (rnd >> 7);
|
||||
}
|
||||
|
||||
byte enmask;
|
||||
word vals[APU_DEFCOUNT];
|
||||
|
||||
static byte popcount4(byte x) {
|
||||
return (x & 1) + ((x >> 1) & 1) + ((x >> 2) & 1) + ((x >> 3) & 1);
|
||||
}
|
||||
|
||||
void random_sound(void) {
|
||||
byte i;
|
||||
// pick one or two channels
|
||||
do {
|
||||
enmask = rand16() & 15;
|
||||
} while (enmask == 0 || popcount4(enmask) > 2);
|
||||
for (i = 0; i < APU_DEFCOUNT; i++) {
|
||||
const APUParam* p = &APU_DEFS[i];
|
||||
vals[i] = p->valmin + (rand16() & p->valmask);
|
||||
if (vals[i] > p->valmask && p->valmask < 0x100) vals[i] = p->valmask;
|
||||
if (vals[i] > 0x7ff) vals[i] = 0x7ff;
|
||||
}
|
||||
}
|
||||
|
||||
// write decimal number, right-aligned in a field of 'width' chars
|
||||
static void put_num(byte x, byte y, word v, byte width) {
|
||||
char buf[6];
|
||||
byte i = width;
|
||||
memset(buf, ' ', width);
|
||||
do {
|
||||
buf[--i] = '0' + (v % 10);
|
||||
v /= 10;
|
||||
} while (v && i);
|
||||
set_bkg_tiles(x, y, width, 1, (uint8_t*)buf);
|
||||
}
|
||||
|
||||
// only rows of enabled channels are shown, in a compact list;
|
||||
// row_ch[] remembers which channel each screen row belongs to
|
||||
byte row_ch[16];
|
||||
byte row_count;
|
||||
|
||||
void print_sound(void) {
|
||||
byte i;
|
||||
byte y = 0;
|
||||
// clear the parameter area
|
||||
for (i = 0; i < 16; i++) put_str(0, i, " ");
|
||||
row_count = 0;
|
||||
for (i = 0; i < APU_DEFCOUNT; i++) {
|
||||
if ((enmask & APU_DEFS[i].chmask) && y < 16) {
|
||||
put_str(0, y, APU_DEFS[i].name);
|
||||
put_num(14, y, vals[i], 5);
|
||||
row_ch[y] = APU_DEFS[i].chmask;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
row_count = y;
|
||||
}
|
||||
|
||||
void play_sound(void) {
|
||||
byte envdown = 0; // envelope always decays (bit 3 = 0)
|
||||
NR51_REG = enmask | (enmask << 4); // route only the chosen channels
|
||||
if (enmask & CH_PULSE1) {
|
||||
NR10_REG = (vals[P1_SWPPER] << 4) | (vals[P1_SWPDN] << 3) | vals[P1_SWPSHF];
|
||||
NR11_REG = (vals[P1_DUTY] << 6) | vals[P1_LEN];
|
||||
NR12_REG = (vals[P1_VOL] << 4) | envdown | vals[P1_ENVPER];
|
||||
NR13_REG = vals[P1_FREQ] & 0xff;
|
||||
NR14_REG = 0xc0 | (vals[P1_FREQ] >> 8); // trigger + length enable
|
||||
}
|
||||
if (enmask & CH_PULSE2) {
|
||||
NR21_REG = (vals[P2_DUTY] << 6) | vals[P2_LEN];
|
||||
NR22_REG = (vals[P2_VOL] << 4) | envdown | vals[P2_ENVPER];
|
||||
NR23_REG = vals[P2_FREQ] & 0xff;
|
||||
NR24_REG = 0xc0 | (vals[P2_FREQ] >> 8);
|
||||
}
|
||||
if (enmask & CH_WAVE) {
|
||||
NR30_REG = 0x80; // wave DAC on
|
||||
NR31_REG = vals[W_LEN];
|
||||
NR32_REG = vals[W_LEVEL] << 5;
|
||||
NR33_REG = vals[W_FREQ] & 0xff;
|
||||
NR34_REG = 0xc0 | (vals[W_FREQ] >> 8);
|
||||
}
|
||||
if (enmask & CH_NOISE) {
|
||||
NR41_REG = vals[N_LEN];
|
||||
NR42_REG = (vals[N_VOL] << 4) | envdown | vals[N_ENVPER];
|
||||
NR43_REG = (vals[N_SHIFT] << 4) | (vals[N_WIDTH] << 3) | vals[N_DIV];
|
||||
NR44_REG = 0xc0;
|
||||
}
|
||||
}
|
||||
|
||||
void print_status(void) {
|
||||
byte i;
|
||||
byte st = NR52_REG;
|
||||
for (i = 0; i < row_count; i++) {
|
||||
put_char(19, i, (st & row_ch[i]) ? '*' : ' ');
|
||||
}
|
||||
}
|
||||
|
||||
void apu_init(void) {
|
||||
byte i;
|
||||
NR52_REG = 0x80; // sound on
|
||||
NR50_REG = 0x77; // master volume
|
||||
NR30_REG = 0x00; // wave DAC off while loading wave RAM
|
||||
for (i = 0; i < 16; i++) _AUD3WAVERAM[i] = wave_pattern[i];
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
byte keys;
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
font_init();
|
||||
put_str(0, 17, "A=New B/Start=Replay");
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
apu_init();
|
||||
random_sound();
|
||||
while (1) {
|
||||
print_sound();
|
||||
play_sound();
|
||||
// wait for a key, updating the channel status each frame
|
||||
do {
|
||||
wait_vbl_done();
|
||||
print_status();
|
||||
rnd++; // stir the RNG while waiting
|
||||
keys = joypad();
|
||||
while (joypad()) ;
|
||||
} while (!(keys & (J_A | J_B | J_START)));
|
||||
if (keys & J_A) random_sound();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
ballframes.h -- generated by tools/prerender.py; do not edit.
|
||||
|
||||
16 frames of a 32x32 lit, striped sphere, each frame
|
||||
16 tiles in 8x16 sprite order (4 sprites across, 2 down).
|
||||
|
||||
raw: 256 tiles = 4096 bytes
|
||||
unique: 188 tiles = 3008 bytes (no blank tile)
|
||||
*/
|
||||
|
||||
#define BALL_FRAMES 16
|
||||
#define BALL_TILES 16
|
||||
|
||||
// define BALL_FRAMES_AT (e.g. __at(0x7000)) before including to pin
|
||||
// the data to an address -- DMA needs a 16-byte-aligned source
|
||||
#ifndef BALL_FRAMES_AT
|
||||
#define BALL_FRAMES_AT
|
||||
#endif
|
||||
|
||||
/*{w:32,h:32,bpp:1,count:16,brev:1,np:2,pofs:1,sl:4,wpimg:256,remap:[5,6,1,2,3,4,7,0,8,9,10,11]}*/
|
||||
const uint8_t BALL_FRAMES_AT ball_frames[BALL_FRAMES][BALL_TILES * 16] = {
|
||||
{ // frame 0
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x01,0x06,0x03,0x08,0x07,0x10,0x0F,
|
||||
0x30,0x0F,0x12,0x2F,0x20,0x1F,0x48,0x3F,0x60,0x1F,0x42,0x3F,0x20,0x5F,0x4A,0x3F,
|
||||
0x00,0x00,0x02,0x1F,0x80,0x7F,0x8A,0xFF,0x00,0xFF,0x22,0xFF,0x00,0xFF,0x80,0xFF,
|
||||
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x40,0xB8,0xAB,0x55,0x50,0xAF,0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0xC0,0x40,0x20,0xE0,0x10,0xF0,0x08,0xF8,
|
||||
0x84,0x7C,0x2C,0xFC,0x04,0xFC,0x2E,0xFE,0x04,0xFE,0x2E,0xFE,0x04,0xFE,0xAE,0xFE,
|
||||
0x20,0x5F,0x4A,0x3F,0x24,0x5F,0x0A,0x7F,0x31,0x5F,0x0A,0x3F,0x05,0x3F,0x2A,0x3F,
|
||||
0x05,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
|
||||
0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFF,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x15,0xFF,0xAA,0xFF,0x55,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAE,0xFE,0x54,0xFE,0xAE,0xFE,0x54,0xFE,0xAC,0xFC,0x54,0xFC,0xAC,0xFC,
|
||||
0x50,0xF8,0xB0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 1
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x03,0x02,0x07,0x08,0x07,0x18,0x0F,
|
||||
0x20,0x1F,0x02,0x3F,0x20,0x1F,0x68,0x3F,0x40,0x3F,0x62,0x3F,0x00,0x7F,0x6A,0x3F,
|
||||
0x00,0x00,0x03,0x1E,0x81,0x7E,0x8B,0xFE,0x01,0xFE,0x23,0xFE,0x03,0xFC,0x81,0xFE,
|
||||
0x0B,0xF4,0x17,0xE8,0x3B,0xC4,0x13,0xEC,0x2A,0xD5,0x01,0xFE,0x02,0xFD,0xA9,0xFE,
|
||||
0x00,0x00,0x48,0xB8,0xA9,0x57,0x50,0xAF,0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0x20,0xE0,0x10,0xF0,0x28,0xF8,
|
||||
0x94,0x7C,0x3C,0xFC,0x14,0xFC,0x2E,0xFE,0x04,0xFE,0x2E,0xFE,0x04,0xFE,0xAE,0xFE,
|
||||
0x00,0x7F,0x6A,0x3F,0x04,0x7F,0x2A,0x7F,0x11,0x7F,0x0A,0x3F,0x15,0x3F,0x2A,0x3F,
|
||||
0x05,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x02,0xFD,0xA8,0xFF,0x46,0xFD,0xA8,0xFF,0x10,0xFF,0xA8,0xFF,0x54,0xFF,0xAC,0xFF,
|
||||
0x54,0xFF,0xBA,0xFF,0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFE,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x15,0xFF,0xAA,0xFF,0x55,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAE,0xFE,0x54,0xFE,0xAE,0xFE,0x54,0xFE,0xBC,0xFC,0x54,0xFC,0xBC,0xFC,
|
||||
0x50,0xF8,0xB0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 2
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
|
||||
0x20,0x1F,0x22,0x3F,0x00,0x3F,0x68,0x3F,0x00,0x7F,0x22,0x7F,0x00,0x7F,0x2A,0x7F,
|
||||
0x00,0x00,0x03,0x1E,0x83,0x7C,0x89,0xFE,0x07,0xF8,0x25,0xFA,0x07,0xF8,0x85,0xFA,
|
||||
0x0F,0xF0,0x1F,0xE0,0x3F,0xC0,0x1F,0xE0,0x2E,0xD1,0x0D,0xF2,0x0A,0xF5,0xA5,0xFA,
|
||||
0x00,0x00,0x48,0xB8,0xAD,0x57,0x52,0xAF,0xEB,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0x28,0xF8,
|
||||
0x94,0x7C,0x3C,0xFC,0x14,0xFC,0x2E,0xFE,0x14,0xFE,0x3E,0xFE,0x14,0xFE,0xBE,0xFE,
|
||||
0x00,0x7F,0x2A,0x7F,0x44,0x7F,0x2A,0x7F,0x11,0x7F,0x2A,0x3F,0x15,0x3F,0x2A,0x3F,
|
||||
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x0A,0xF5,0xA4,0xFB,0x4A,0xF5,0xA0,0xFF,0x18,0xF7,0xA0,0xFF,0x50,0xFF,0xA0,0xFF,
|
||||
0x50,0xFF,0xBA,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFE,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x15,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xBE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xBC,0xFC,0x54,0xFC,0xBC,0xFC,
|
||||
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 3
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
|
||||
0x20,0x1F,0x22,0x3F,0x00,0x3F,0x28,0x7F,0x00,0x7F,0x22,0x7F,0x00,0x7F,0x2A,0x7F,
|
||||
0x00,0x00,0x01,0x1E,0x07,0xF8,0x85,0xFA,0x0F,0xF0,0x2D,0xF2,0x1F,0xE0,0x95,0xEA,
|
||||
0x1F,0xE0,0x1F,0xE0,0x3F,0xC0,0x3F,0xC0,0x3E,0xC1,0x3D,0xC2,0x3A,0xC5,0x95,0xEA,
|
||||
0x00,0x00,0x48,0xB8,0xAD,0x57,0x52,0xAF,0xE9,0x17,0x54,0xAB,0xAB,0x55,0x50,0xAF,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
|
||||
0x54,0xFC,0x3C,0xFC,0x54,0xFC,0x6E,0xFE,0x54,0xFE,0x3E,0xFE,0x54,0xFE,0xFE,0xFE,
|
||||
0x00,0x7F,0x2A,0x7F,0x44,0x7F,0x2A,0x7F,0x51,0x7F,0x2A,0x3F,0x15,0x3F,0x2A,0x3F,
|
||||
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x2A,0xD5,0x94,0xEB,0x6A,0xD5,0x80,0xFF,0x28,0xD7,0x80,0xFF,0x40,0xFF,0xA0,0xFF,
|
||||
0x40,0xFF,0xA2,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFA,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xFC,0xFC,
|
||||
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 4
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
|
||||
0x00,0x3F,0x22,0x3F,0x00,0x3F,0x28,0x7F,0x00,0x7F,0x22,0x7F,0x00,0x7F,0x2A,0x7F,
|
||||
0x00,0x00,0x05,0x1A,0x0F,0xF0,0x95,0xEA,0x1F,0xE0,0x1D,0xE2,0x3F,0xC0,0xB5,0xCA,
|
||||
0x7F,0x80,0x7F,0x80,0x7F,0x80,0x7F,0x80,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
|
||||
0x00,0x00,0x48,0xB8,0xA5,0x5F,0x52,0xAF,0xE9,0x17,0x56,0xAB,0xA9,0x57,0x52,0xAF,
|
||||
0xEB,0x15,0x54,0xAB,0xAB,0x55,0x50,0xAF,0xA8,0x57,0x40,0xBF,0xA0,0x5F,0x00,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
|
||||
0x54,0xFC,0xBC,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
|
||||
0x00,0x7F,0x2A,0x7F,0x44,0x7F,0x2A,0x7F,0x51,0x7F,0x2A,0x3F,0x15,0x3F,0x2A,0x3F,
|
||||
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0x28,0xD7,0x80,0xFF,0x20,0xDF,0x80,0xFF,
|
||||
0x40,0xFF,0xA2,0xFF,0x45,0xFF,0xEA,0xFF,0x55,0xFF,0xFA,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA8,0x57,0x02,0xFF,0x00,0xFF,0x0A,0xFF,0x81,0x7F,0x2B,0xFF,0x05,0xFF,0xAB,0xFF,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xAF,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xFC,0xFC,
|
||||
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 5
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x00,0x0F,0x08,0x1F,
|
||||
0x00,0x3F,0x23,0x3E,0x01,0x3E,0x29,0x7E,0x01,0x7E,0x23,0x7E,0x01,0x7E,0x2B,0x7E,
|
||||
0x00,0x00,0x05,0x1A,0x1F,0xE0,0x95,0xEA,0x3F,0xC0,0x5D,0xA2,0x7F,0x80,0x75,0x8A,
|
||||
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
|
||||
0x00,0x00,0x68,0xB8,0xA5,0x5F,0x5A,0xAF,0xE1,0x1F,0x5A,0xAF,0xAD,0x57,0x52,0xAF,
|
||||
0xE9,0x17,0x52,0xAF,0xA9,0x57,0x52,0xAF,0xA9,0x57,0x42,0xBF,0xA1,0x5F,0x02,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
|
||||
0x54,0xFC,0xBC,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
|
||||
0x00,0x7F,0x2B,0x7E,0x44,0x7F,0x2B,0x7E,0x50,0x7F,0x2A,0x3F,0x14,0x3F,0x2A,0x3F,
|
||||
0x15,0x1F,0x0B,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
|
||||
0x00,0xFF,0xA2,0xFF,0x05,0xFF,0xEA,0xFF,0x55,0xFF,0xEA,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA9,0x57,0x03,0xFF,0x01,0xFF,0x0B,0xFF,0x81,0x7F,0x2B,0xFF,0x05,0xFF,0xAF,0xFF,
|
||||
0x15,0xFF,0xAF,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBF,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xF8,0xFC,
|
||||
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 6
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x02,0x07,0x01,0x0E,0x09,0x1E,
|
||||
0x03,0x3C,0x21,0x3E,0x03,0x3C,0x29,0x7E,0x07,0x78,0x25,0x7A,0x03,0x7C,0x2D,0x7A,
|
||||
0x00,0x00,0x05,0x1A,0x1F,0xE0,0x95,0xEA,0x7F,0x80,0xDD,0x22,0xFF,0x00,0x75,0x8A,
|
||||
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
|
||||
0x00,0x00,0x28,0xF8,0x84,0x7F,0x6A,0xBF,0xE1,0x1F,0x4A,0xBF,0xA5,0x5F,0x4A,0xBF,
|
||||
0xE1,0x1F,0x5A,0xAF,0xA5,0x5F,0x5A,0xAF,0xA1,0x5F,0x4A,0xBF,0xA5,0x5F,0x0A,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xC0,0xA0,0xE0,0x50,0xF0,0xA8,0xF8,
|
||||
0x54,0xFC,0xBC,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
|
||||
0x06,0x79,0x2D,0x7A,0x42,0x7D,0x29,0x7E,0x52,0x7D,0x28,0x3F,0x16,0x3D,0x28,0x3F,
|
||||
0x14,0x1F,0x0A,0x0F,0x05,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
|
||||
0x00,0xFF,0x22,0xFF,0x05,0xFF,0xAA,0xFF,0x55,0xFF,0xEA,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0xA5,0x5F,0x0B,0xFF,0x05,0xFF,0x0F,0xFF,0x85,0x7F,0x2B,0xFF,0x05,0xFF,0xAF,0xFF,
|
||||
0x15,0xFF,0xBF,0xFF,0x55,0xFF,0xBF,0xFF,0x55,0xFF,0xBE,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xFC,0xFC,0x54,0xFC,0xF8,0xFC,
|
||||
0x50,0xF8,0xF0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 7
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x03,0x06,0x03,0x0C,0x0B,0x1C,
|
||||
0x07,0x38,0x25,0x3A,0x07,0x38,0x25,0x7A,0x0F,0x70,0x25,0x7A,0x0B,0x74,0x25,0x7A,
|
||||
0x00,0x00,0x05,0x1A,0x3F,0xC0,0xD5,0xAA,0xFF,0x00,0xDD,0x22,0xFF,0x00,0x75,0x8A,
|
||||
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
|
||||
0x00,0x00,0x28,0xF8,0x84,0x7F,0x2A,0xFF,0x81,0x7F,0x2A,0xFF,0x85,0x7F,0x6A,0xBF,
|
||||
0xC1,0x3F,0x6A,0xBF,0x85,0x7F,0x6A,0xBF,0x91,0x7F,0x6A,0xBF,0x85,0x7F,0x2A,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0xC0,0xA0,0xE0,0x40,0xF0,0xA8,0xF8,
|
||||
0x54,0xFC,0xB8,0xFC,0x54,0xFC,0xEE,0xFE,0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,
|
||||
0x0E,0x71,0x25,0x7A,0x4A,0x75,0x21,0x7E,0x5A,0x75,0x28,0x3F,0x12,0x3D,0x28,0x3F,
|
||||
0x14,0x1F,0x0A,0x0F,0x04,0x07,0x03,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
|
||||
0x00,0xFF,0x22,0xFF,0x05,0xFF,0xAA,0xFF,0x55,0xFF,0xEA,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x95,0x7F,0x2B,0xFF,0x15,0xFF,0x2F,0xFF,0x95,0x7F,0x3B,0xFF,0x15,0xFF,0xBF,0xFF,
|
||||
0x15,0xFF,0xBF,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBE,0xFE,0x54,0xFE,0xFE,0xFE,0x54,0xFE,0xF8,0xFC,0x54,0xFC,0xF8,0xFC,
|
||||
0x50,0xF8,0xE0,0xF0,0x40,0xE0,0xC0,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 8
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x01,0x06,0x07,0x08,0x0F,0x18,
|
||||
0x0F,0x30,0x2D,0x32,0x1F,0x20,0x35,0x6A,0x1F,0x60,0x35,0x6A,0x1B,0x64,0x35,0x6A,
|
||||
0x00,0x00,0x15,0x0A,0x7F,0x80,0x55,0xAA,0xFF,0x00,0xDD,0x22,0xFF,0x00,0x75,0x8A,
|
||||
0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFE,0x01,0xFD,0x02,0xFA,0x05,0x55,0xAA,
|
||||
0x00,0x00,0xA8,0xF8,0x04,0xFF,0xAA,0xFF,0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0xC0,0xA0,0xE0,0x40,0xF0,0xA8,0xF8,
|
||||
0x54,0xFC,0xB8,0xFC,0x54,0xFC,0xEA,0xFE,0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,
|
||||
0x0E,0x71,0x35,0x6A,0x4A,0x75,0x21,0x7E,0x4A,0x75,0x20,0x3F,0x12,0x3D,0x20,0x3F,
|
||||
0x10,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xEA,0x15,0x54,0xAB,0xAA,0x55,0x00,0xFF,0xA8,0x57,0x00,0xFF,0x20,0xDF,0x00,0xFF,
|
||||
0x00,0xFF,0x22,0xFF,0x05,0xFF,0xAA,0xFF,0x55,0xFF,0xAA,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
|
||||
0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,0x54,0xFE,0xF8,0xFC,0x54,0xFC,0xF8,0xFC,
|
||||
0x50,0xF8,0xE0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 9
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x00,0x05,0x02,0x07,0x08,0x07,0x18,
|
||||
0x1F,0x20,0x3D,0x22,0x1F,0x20,0x15,0x6A,0x3F,0x40,0x15,0x6A,0x3B,0x44,0x15,0x6A,
|
||||
0x00,0x00,0x14,0x0B,0x7E,0x81,0x54,0xAB,0xFE,0x01,0xDC,0x23,0xFC,0x03,0x74,0x8B,
|
||||
0xFC,0x03,0xFC,0x03,0xFC,0x03,0xFC,0x03,0xFC,0x03,0xFE,0x03,0xF8,0x07,0x56,0xAB,
|
||||
0x00,0x00,0xA0,0xF8,0x06,0xFD,0xAA,0xFF,0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0xA0,0xE0,0x40,0xF0,0x88,0xF8,
|
||||
0x44,0xFC,0xA8,0xFC,0x44,0xFC,0xEA,0xFE,0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,
|
||||
0x2E,0x51,0x15,0x6A,0x6A,0x55,0x01,0x7E,0x6A,0x55,0x20,0x3F,0x02,0x3D,0x20,0x3F,
|
||||
0x10,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xE9,0x17,0x56,0xAB,0xA9,0x57,0x02,0xFF,0xA9,0x57,0x02,0xFF,0x21,0xDF,0x02,0xFF,
|
||||
0x01,0xFF,0x23,0xFF,0x05,0xFF,0xAB,0xFF,0x55,0xFF,0xAB,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
|
||||
0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xBA,0xFE,0x54,0xFE,0xFA,0xFE,0x54,0xFE,0xE8,0xFC,0x54,0xFC,0xE8,0xFC,
|
||||
0x50,0xF8,0xE0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 10
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
|
||||
0x1F,0x20,0x1D,0x22,0x3F,0x00,0x15,0x6A,0x7F,0x00,0x55,0x2A,0x3B,0x44,0x55,0x2A,
|
||||
0x00,0x00,0x14,0x0B,0x7C,0x83,0x56,0xAB,0xF8,0x07,0xDA,0x27,0xF8,0x07,0x70,0x8F,
|
||||
0xF8,0x07,0xF4,0x0B,0xF8,0x07,0xF0,0x0F,0xF8,0x07,0xF2,0x0F,0xF0,0x0F,0x5A,0xAF,
|
||||
0x00,0x00,0xA0,0xF8,0x02,0xFD,0xA8,0xFF,0x00,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,
|
||||
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x88,0xF8,
|
||||
0x44,0xFC,0xA8,0xFC,0x44,0xFC,0xEA,0xFE,0x44,0xFE,0xAA,0xFE,0x44,0xFE,0xEA,0xFE,
|
||||
0x2E,0x51,0x55,0x2A,0x2A,0x55,0x01,0x7E,0x6A,0x55,0x00,0x3F,0x02,0x3D,0x20,0x3F,
|
||||
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xE1,0x1F,0x5A,0xAF,0xA5,0x5F,0x0A,0xFF,0xA1,0x5F,0x0A,0xFF,0x25,0xDF,0x0E,0xFF,
|
||||
0x05,0xFF,0x23,0xFF,0x05,0xFF,0xAF,0xFF,0x55,0xFF,0xAB,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
|
||||
0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFA,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xEA,0xFE,0x54,0xFE,0xE8,0xFC,0x54,0xFC,0xE8,0xFC,
|
||||
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 11
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
|
||||
0x1F,0x20,0x1D,0x22,0x3F,0x00,0x55,0x2A,0x7F,0x00,0x55,0x2A,0x3B,0x44,0x55,0x2A,
|
||||
0x00,0x00,0x16,0x0B,0xF8,0x07,0x5A,0xAF,0xF0,0x0F,0xD2,0x2F,0xE0,0x1F,0x60,0x9F,
|
||||
0xE8,0x17,0xF4,0x0B,0xF8,0x07,0xD0,0x2F,0xE8,0x17,0xC2,0x3F,0xC0,0x3F,0x6A,0xBF,
|
||||
0x00,0x00,0xA0,0xF8,0x02,0xFD,0xA8,0xFF,0x02,0xFD,0x2A,0xFF,0x04,0xFF,0xAA,0xFF,
|
||||
0x01,0xFF,0x2A,0xFF,0x05,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
|
||||
0x84,0x7C,0xA8,0xFC,0x04,0xFC,0xAA,0xFE,0x04,0xFE,0xAA,0xFE,0x04,0xFE,0xAA,0xFE,
|
||||
0x2E,0x51,0x55,0x2A,0x2A,0x55,0x01,0x7E,0x2A,0x55,0x00,0x3F,0x02,0x3D,0x20,0x3F,
|
||||
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0xC1,0x3F,0x6A,0xBF,0x85,0x7F,0x2A,0xFF,0x91,0x7F,0x2A,0xFF,0x35,0xDF,0x0E,0xFF,
|
||||
0x15,0xFF,0x3B,0xFF,0x05,0xFF,0xAF,0xFF,0x55,0xFF,0xAF,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,
|
||||
0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFA,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xA8,0xFC,
|
||||
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 12
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
|
||||
0x3F,0x00,0x1D,0x22,0x3F,0x00,0x55,0x2A,0x7F,0x00,0x55,0x2A,0x3B,0x44,0x55,0x2A,
|
||||
0x00,0x00,0x12,0x0F,0xF0,0x0F,0x4A,0xBF,0xE0,0x1F,0xE2,0x3F,0xC0,0x3F,0x40,0xBF,
|
||||
0x88,0x77,0x94,0x6B,0xB8,0x47,0x90,0x6F,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0xA0,0xF8,0x0A,0xF5,0xA8,0xFF,0x02,0xFD,0x28,0xFF,0x06,0xFD,0xA8,0xFF,
|
||||
0x00,0xFF,0x2A,0xFF,0x04,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
|
||||
0x84,0x7C,0x28,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
|
||||
0x2E,0x51,0x55,0x2A,0x2A,0x55,0x01,0x7E,0x2A,0x55,0x00,0x3F,0x02,0x3D,0x20,0x3F,
|
||||
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x91,0x7F,0x2A,0xFF,0x55,0xFF,0x2E,0xFF,
|
||||
0x15,0xFF,0x3B,0xFF,0x15,0xFF,0xBF,0xFF,0x55,0xFF,0xAF,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x15,0xFF,0xAB,0xFF,0x55,0xFF,0xAF,0xFF,0x55,0xFF,0xBA,0xFF,0x55,0xFF,0xFE,0xFF,
|
||||
0x55,0xFF,0xFE,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFA,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xA8,0xFC,
|
||||
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 13
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0F,0x00,0x17,0x08,
|
||||
0x3F,0x00,0x1C,0x23,0x3E,0x01,0x54,0x2B,0x7E,0x01,0x54,0x2B,0x3A,0x45,0x54,0x2B,
|
||||
0x00,0x00,0x12,0x0F,0xE0,0x1F,0x4A,0xBF,0xC0,0x3F,0xA2,0x7F,0x80,0x7F,0x80,0xFF,
|
||||
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0x80,0xF8,0x0A,0xF5,0xA0,0xFF,0x0A,0xF5,0x24,0xFB,0x02,0xFD,0xA8,0xFF,
|
||||
0x02,0xFD,0x2C,0xFB,0x06,0xFD,0xA8,0xFF,0x10,0xFF,0xA8,0xFF,0x44,0xFF,0xA8,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
|
||||
0x84,0x7C,0x28,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
|
||||
0x2E,0x51,0x54,0x2B,0x2A,0x55,0x00,0x7F,0x2B,0x55,0x00,0x3F,0x03,0x3D,0x20,0x3F,
|
||||
0x00,0x1F,0x0A,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
|
||||
0x55,0xFF,0x3B,0xFF,0x55,0xFF,0xBF,0xFF,0x55,0xFF,0xBF,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x14,0xFF,0xAA,0xFF,0x54,0xFF,0xAE,0xFF,0x55,0xFF,0xBA,0xFF,0x55,0xFF,0xFA,0xFF,
|
||||
0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xFA,0xFF,0x55,0xFF,0xEA,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xAC,0xFC,
|
||||
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 14
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x05,0x02,0x0E,0x01,0x16,0x09,
|
||||
0x3C,0x03,0x1E,0x23,0x3C,0x03,0x54,0x2B,0x78,0x07,0x52,0x2F,0x38,0x47,0x52,0x2F,
|
||||
0x00,0x00,0x12,0x0F,0xE0,0x1F,0x4A,0xBF,0x80,0x7F,0x22,0xFF,0x00,0xFF,0x80,0xFF,
|
||||
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0xC0,0xB8,0x2B,0xD5,0x90,0xEF,0x0A,0xF5,0x34,0xEB,0x0A,0xF5,0xB0,0xEF,
|
||||
0x0A,0xF5,0x24,0xFB,0x0A,0xF5,0xA0,0xFF,0x18,0xF7,0xA0,0xFF,0x40,0xFF,0xA0,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x40,0x20,0xE0,0x00,0xF0,0x08,0xF8,
|
||||
0x84,0x7C,0x28,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
|
||||
0x28,0x57,0x52,0x2F,0x2C,0x57,0x02,0x7F,0x29,0x57,0x02,0x3F,0x01,0x3F,0x22,0x3F,
|
||||
0x01,0x1F,0x0B,0x0F,0x04,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
|
||||
0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xBF,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x18,0xF7,0xA2,0xFF,0x50,0xFF,0xAA,0xFF,0x51,0xFF,0xBA,0xFF,0x55,0xFF,0xFA,0xFF,
|
||||
0x55,0xFF,0xEA,0xFF,0x55,0xFF,0xEA,0xFF,0x55,0xFF,0xEB,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xA8,0xFC,0x54,0xFC,0xAC,0xFC,
|
||||
0x50,0xF8,0xA0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
{ // frame 15
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x03,0x00,0x04,0x03,0x0C,0x03,0x14,0x0B,
|
||||
0x38,0x07,0x1A,0x27,0x38,0x07,0x58,0x2F,0x70,0x0F,0x52,0x2F,0x30,0x4F,0x5A,0x2F,
|
||||
0x00,0x00,0x12,0x0F,0xC0,0x3F,0x0A,0xFF,0x00,0xFF,0x22,0xFF,0x00,0xFF,0x80,0xFF,
|
||||
0x08,0xF7,0x14,0xEB,0x38,0xC7,0x10,0xEF,0x28,0xD7,0x02,0xFF,0x00,0xFF,0xAA,0xFF,
|
||||
0x00,0x00,0xC0,0xB8,0x2B,0xD5,0xD0,0xAF,0x6A,0x95,0x54,0xAB,0x2A,0xD5,0x90,0xEF,
|
||||
0x2A,0xD5,0x14,0xEB,0x2A,0xD5,0x90,0xEF,0x28,0xD7,0x80,0xFF,0x60,0xDF,0x80,0xFF,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x40,0x20,0xE0,0x10,0xF0,0x08,0xF8,
|
||||
0x84,0x7C,0x2C,0xFC,0x04,0xFC,0x2A,0xFE,0x04,0xFE,0x2A,0xFE,0x04,0xFE,0xAA,0xFE,
|
||||
0x20,0x5F,0x5A,0x2F,0x24,0x5F,0x0A,0x7F,0x21,0x5F,0x02,0x3F,0x05,0x3F,0x22,0x3F,
|
||||
0x01,0x1F,0x0B,0x0F,0x05,0x07,0x02,0x03,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x01,0xFF,0xAA,0xFF,0x45,0xFF,0xAA,0xFF,0x11,0xFF,0xAA,0xFF,0x55,0xFF,0xAE,0xFF,
|
||||
0x55,0xFF,0xBB,0xFF,0x55,0xFF,0xFF,0xFF,0x55,0xFF,0xBF,0xFF,0x15,0x1F,0x00,0x00,
|
||||
0x28,0xD7,0x82,0xFF,0x40,0xFF,0x8A,0xFF,0x41,0xFF,0xAA,0xFF,0x45,0xFF,0xEA,0xFF,
|
||||
0x55,0xFF,0xEA,0xFF,0x55,0xFF,0xAA,0xFF,0x55,0xFF,0xAB,0xFF,0x50,0xF8,0x00,0x00,
|
||||
0x14,0xFE,0xAA,0xFE,0x54,0xFE,0xAA,0xFE,0x54,0xFE,0xAC,0xFC,0x54,0xFC,0xAC,0xFC,
|
||||
0x50,0xF8,0xB0,0xF0,0x40,0xE0,0x80,0xC0,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Which Game Boy is this?
|
||||
|
||||
The boot ROM leaves a fingerprint in the CPU registers when it jumps to
|
||||
the cartridge at $0100:
|
||||
|
||||
A = $01 original Game Boy (DMG) or Super Game Boy
|
||||
A = $FF Game Boy Pocket / Light, or Super Game Boy 2
|
||||
A = $11 Game Boy Color -- or Game Boy Advance, in which case
|
||||
bit 0 of B is also set
|
||||
|
||||
GBDK's startup code (crt0) saves A in _cpu and, on color hardware, the B
|
||||
bit in _is_GBA, before anything else can overwrite them. The Super Game
|
||||
Boy can't be told apart by registers; the game has to send it a command
|
||||
packet and see whether it answers (sgb_check()).
|
||||
|
||||
What games did with this:
|
||||
- Shantae (2002) brightens its palettes on a GBA, whose unlit screen made
|
||||
GBC colors look muddy, and unlocks a bonus transformation there.
|
||||
- Many DMG games (Donkey Kong Land, Kirby's Dream Land 2) add a border
|
||||
and colors when they find a Super Game Boy.
|
||||
- "Dual-mode" cartridges use the color palettes only if _cpu == CGB_TYPE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gb/cgb.h"
|
||||
#include "gb/sgb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
// a normal palette, and a brighter one for the GBA's darker screen.
|
||||
// Stored as 15-bit CGB words; the RGB() values are in the comments.
|
||||
/*{pal:555,n:4,bpw:16}*/
|
||||
const palette_color_t pal_gbc[4] = {
|
||||
0x639C, 0x2214, 0x110C, 0x0842 // RGB(28,28,24), (20,16,8), (12,8,4), (2,2,2)
|
||||
};
|
||||
/*{pal:555,n:4,bpw:16}*/
|
||||
const palette_color_t pal_gba[4] = {
|
||||
0x73FF, 0x3B1C, 0x21D4, 0x1084 // RGB(31,31,28), (28,24,14), (20,14,8), (4,4,4)
|
||||
};
|
||||
|
||||
void put_hex(uint8_t x, uint8_t y, uint8_t v) {
|
||||
char s[3];
|
||||
const char* hex = "0123456789ABCDEF";
|
||||
s[0] = hex[v >> 4];
|
||||
s[1] = hex[v & 15];
|
||||
s[2] = 0;
|
||||
put_str(x, y, s);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t is_sgb;
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
font_init();
|
||||
fill_bkg_rect(0, 0, 20, 18, ' ');
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
|
||||
// the SGB needs a few frames after power-on before it listens
|
||||
wait_vbl_done(); wait_vbl_done(); wait_vbl_done(); wait_vbl_done();
|
||||
is_sgb = sgb_check();
|
||||
|
||||
put_str(2, 1, "WHICH GAME BOY?");
|
||||
put_str(2, 4, "_CPU = $");
|
||||
put_hex(13, 4, _cpu);
|
||||
put_str(2, 5, "_IS_GBA = $");
|
||||
put_hex(13, 5, _is_GBA);
|
||||
put_str(2, 6, "SGB = ");
|
||||
put_str(12, 6, is_sgb ? "YES" : "NO");
|
||||
|
||||
put_str(2, 9, "RUNNING ON:");
|
||||
if (_cpu == CGB_TYPE && _is_GBA == GBA_DETECTED) {
|
||||
put_str(3, 11, "GAME BOY ADVANCE");
|
||||
set_bkg_palette(0, 1, (palette_color_t*)pal_gba);
|
||||
put_str(3, 12, "(BRIGHT PALETTE)");
|
||||
} else if (_cpu == CGB_TYPE) {
|
||||
put_str(3, 11, "GAME BOY COLOR");
|
||||
set_bkg_palette(0, 1, (palette_color_t*)pal_gbc);
|
||||
} else if (is_sgb) {
|
||||
put_str(3, 11, "SUPER GAME BOY");
|
||||
} else if (_cpu == MGB_TYPE) {
|
||||
put_str(3, 11, "GAME BOY POCKET");
|
||||
} else {
|
||||
put_str(3, 11, "GAME BOY (DMG)");
|
||||
}
|
||||
|
||||
while (1) wait_vbl_done();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Flicker, on purpose: transparency and sprite cycling.
|
||||
|
||||
1. Transparency. The Game Boy has no blending hardware, but the original
|
||||
DMG screen is slow: a pixel takes several frames to fully change. So a
|
||||
sprite shown on every *other* frame (30 Hz) doesn't blink -- it
|
||||
smears into a half-tone ghost over the background. Chikyu Kaiho Gun
|
||||
ZAS (T&E Soft, 1992) used this for see-through clouds and effects,
|
||||
and Link's Awakening DX (1998) for see-through portraits. On a fast
|
||||
modern screen or an emulator without frame blending, it just flickers.
|
||||
|
||||
2. Sprite cycling. The PPU draws at most 10 sprites per scanline: the
|
||||
first 10 in OAM order whose rows overlap it. Here 16 balls share one
|
||||
row, so 6 would simply vanish. Rotating which ball goes in which OAM
|
||||
slot every frame spreads the loss around: every ball is visible
|
||||
10 frames out of 16, and the eye sees them all, flickering.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
|
||||
/*{w:8,h:8,bpp:1,count:5,brev:1,np:2,pofs:1,sl:2}*/
|
||||
const uint8_t sprite_tiles[] = {
|
||||
// 0: ball
|
||||
0x3C,0x3C, 0x42,0x7E, 0x81,0xFF, 0x85,0xFB, 0x85,0xFB, 0x81,0xFF, 0x42,0x7E, 0x3C,0x3C,
|
||||
// 1-4: 16-by-16 ghost, solid color 3 (the flicker makes it look gray)
|
||||
0x07,0x07, 0x1F,0x1F, 0x3F,0x3F, 0x7F,0x7F, 0x73,0x73, 0xF3,0xF3, 0xFF,0xFF, 0xFF,0xFF,
|
||||
0xE0,0xE0, 0xF8,0xF8, 0xFC,0xFC, 0xFE,0xFE, 0xCE,0xCE, 0xCF,0xCF, 0xFF,0xFF, 0xFF,0xFF,
|
||||
0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xDB,0xDB, 0x89,0x89,
|
||||
0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xDB,0xDB, 0x91,0x91,
|
||||
};
|
||||
|
||||
// background: a checkerboard, so the ghost's transparency is visible
|
||||
/*{w:8,h:8,bpp:1,count:2,brev:1,np:2,pofs:1,sl:2}*/
|
||||
const uint8_t bg_tiles[] = {
|
||||
0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
|
||||
0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF, 0x00,0xFF,
|
||||
};
|
||||
|
||||
#define NBALLS 16
|
||||
#define GHOST_OAM 36 // OAM slots 36..39 for the ghost
|
||||
|
||||
uint8_t ball_x[NBALLS];
|
||||
|
||||
void main(void) {
|
||||
uint8_t i, x, y, frame = 0, rot = 0;
|
||||
uint8_t row[32];
|
||||
uint8_t gx = 40;
|
||||
int8_t gdx = 1;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
OBP0_REG = 0xE4;
|
||||
set_bkg_data(0, 2, bg_tiles);
|
||||
for (y = 0; y < 18; y++) {
|
||||
for (x = 0; x < 32; x++) row[x] = ((x >> 1) ^ (y >> 1)) & 1;
|
||||
set_bkg_tiles(0, y, 32, 1, row);
|
||||
}
|
||||
set_sprite_data(0, 5, sprite_tiles);
|
||||
for (i = 0; i < NBALLS; i++) {
|
||||
ball_x[i] = 12 + i * 9; // 16 balls across 144 pixels
|
||||
set_sprite_tile(i, 0);
|
||||
}
|
||||
set_sprite_tile(GHOST_OAM+0, 1);
|
||||
set_sprite_tile(GHOST_OAM+1, 2);
|
||||
set_sprite_tile(GHOST_OAM+2, 3);
|
||||
set_sprite_tile(GHOST_OAM+3, 4);
|
||||
SHOW_BKG;
|
||||
SHOW_SPRITES;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
wait_vbl_done();
|
||||
frame++;
|
||||
|
||||
// --- sprite cycling: ball (i + rot) goes in OAM slot i ---
|
||||
rot = (rot + 7) & (NBALLS - 1); // step by 7: coprime with 16, so
|
||||
// every ball gets every slot
|
||||
for (i = 0; i < NBALLS; i++) {
|
||||
uint8_t b = (i + rot) & (NBALLS - 1);
|
||||
move_sprite(i, ball_x[b], 40);
|
||||
if (++ball_x[b] >= 12 + 144) ball_x[b] = 12; // wrap on screen
|
||||
}
|
||||
|
||||
// --- 30 Hz ghost: visible on even frames only ---
|
||||
gx += gdx;
|
||||
if (gx < 16 || gx > 150) gdx = -gdx;
|
||||
if (frame & 1) {
|
||||
// hide by moving off the top of the screen (Y = 0)
|
||||
for (i = 0; i < 4; i++) move_sprite(GHOST_OAM + i, 0, 0);
|
||||
} else {
|
||||
move_sprite(GHOST_OAM+0, gx, 96);
|
||||
move_sprite(GHOST_OAM+1, gx + 8, 96);
|
||||
move_sprite(GHOST_OAM+2, gx, 104);
|
||||
move_sprite(GHOST_OAM+3, gx + 8, 104);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
Shared 3D math for the tile-framebuffer demos (wireframe.c, polyfill.c).
|
||||
|
||||
Everything here avoids the SM83's missing multiply and divide:
|
||||
- sin_tab: a 256-step sine table, so an angle is one byte
|
||||
- mul8(): 8x8 signed multiply from a quarter-square table
|
||||
- recip[]: 24576/z, so perspective is a multiply instead of a divide
|
||||
- transform(): rotates and projects the 8 corners of a cube
|
||||
*/
|
||||
|
||||
#ifndef _GB3D_H
|
||||
#define _GB3D_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "tilebuf.h"
|
||||
|
||||
// sin(a) * 127, a = 0..255 is one full turn
|
||||
const int8_t sin_tab[256] = {
|
||||
0,3,6,9,12,16,19,22,25,28,31,34,37,40,43,46,
|
||||
49,51,54,57,60,63,65,68,71,73,76,78,81,83,85,88,
|
||||
90,92,94,96,98,100,102,104,106,107,109,111,112,113,115,116,
|
||||
117,118,120,121,122,122,123,124,125,125,126,126,126,127,127,127,
|
||||
127,127,127,127,126,126,126,125,125,124,123,122,122,121,120,118,
|
||||
117,116,115,113,112,111,109,107,106,104,102,100,98,96,94,92,
|
||||
90,88,85,83,81,78,76,73,71,68,65,63,60,57,54,51,
|
||||
49,46,43,40,37,34,31,28,25,22,19,16,12,9,6,3,
|
||||
0,-3,-6,-9,-12,-16,-19,-22,-25,-28,-31,-34,-37,-40,-43,-46,
|
||||
-49,-51,-54,-57,-60,-63,-65,-68,-71,-73,-76,-78,-81,-83,-85,-88,
|
||||
-90,-92,-94,-96,-98,-100,-102,-104,-106,-107,-109,-111,-112,-113,-115,-116,
|
||||
-117,-118,-120,-121,-122,-122,-123,-124,-125,-125,-126,-126,-126,-127,-127,-127,
|
||||
-127,-127,-127,-127,-126,-126,-126,-125,-125,-124,-123,-122,-122,-121,-120,-118,
|
||||
-117,-116,-115,-113,-112,-111,-109,-107,-106,-104,-102,-100,-98,-96,-94,-92,
|
||||
-90,-88,-85,-83,-81,-78,-76,-73,-71,-68,-65,-63,-60,-57,-54,-51,
|
||||
-49,-46,-43,-40,-37,-34,-31,-28,-25,-22,-19,-16,-12,-9,-6,-3,
|
||||
};
|
||||
|
||||
// quarter squares: sq4[n+256] = floor(n*n/4), n = -256..255
|
||||
const uint16_t sq4[512] = {
|
||||
16384,16256,16129,16002,15876,15750,15625,15500,15376,15252,15129,15006,
|
||||
14884,14762,14641,14520,14400,14280,14161,14042,13924,13806,13689,13572,
|
||||
13456,13340,13225,13110,12996,12882,12769,12656,12544,12432,12321,12210,
|
||||
12100,11990,11881,11772,11664,11556,11449,11342,11236,11130,11025,10920,
|
||||
10816,10712,10609,10506,10404,10302,10201,10100,10000,9900,9801,9702,
|
||||
9604,9506,9409,9312,9216,9120,9025,8930,8836,8742,8649,8556,
|
||||
8464,8372,8281,8190,8100,8010,7921,7832,7744,7656,7569,7482,
|
||||
7396,7310,7225,7140,7056,6972,6889,6806,6724,6642,6561,6480,
|
||||
6400,6320,6241,6162,6084,6006,5929,5852,5776,5700,5625,5550,
|
||||
5476,5402,5329,5256,5184,5112,5041,4970,4900,4830,4761,4692,
|
||||
4624,4556,4489,4422,4356,4290,4225,4160,4096,4032,3969,3906,
|
||||
3844,3782,3721,3660,3600,3540,3481,3422,3364,3306,3249,3192,
|
||||
3136,3080,3025,2970,2916,2862,2809,2756,2704,2652,2601,2550,
|
||||
2500,2450,2401,2352,2304,2256,2209,2162,2116,2070,2025,1980,
|
||||
1936,1892,1849,1806,1764,1722,1681,1640,1600,1560,1521,1482,
|
||||
1444,1406,1369,1332,1296,1260,1225,1190,1156,1122,1089,1056,
|
||||
1024,992,961,930,900,870,841,812,784,756,729,702,
|
||||
676,650,625,600,576,552,529,506,484,462,441,420,
|
||||
400,380,361,342,324,306,289,272,256,240,225,210,
|
||||
196,182,169,156,144,132,121,110,100,90,81,72,
|
||||
64,56,49,42,36,30,25,20,16,12,9,6,
|
||||
4,2,1,0,0,0,1,2,4,6,9,12,
|
||||
16,20,25,30,36,42,49,56,64,72,81,90,
|
||||
100,110,121,132,144,156,169,182,196,210,225,240,
|
||||
256,272,289,306,324,342,361,380,400,420,441,462,
|
||||
484,506,529,552,576,600,625,650,676,702,729,756,
|
||||
784,812,841,870,900,930,961,992,1024,1056,1089,1122,
|
||||
1156,1190,1225,1260,1296,1332,1369,1406,1444,1482,1521,1560,
|
||||
1600,1640,1681,1722,1764,1806,1849,1892,1936,1980,2025,2070,
|
||||
2116,2162,2209,2256,2304,2352,2401,2450,2500,2550,2601,2652,
|
||||
2704,2756,2809,2862,2916,2970,3025,3080,3136,3192,3249,3306,
|
||||
3364,3422,3481,3540,3600,3660,3721,3782,3844,3906,3969,4032,
|
||||
4096,4160,4225,4290,4356,4422,4489,4556,4624,4692,4761,4830,
|
||||
4900,4970,5041,5112,5184,5256,5329,5402,5476,5550,5625,5700,
|
||||
5776,5852,5929,6006,6084,6162,6241,6320,6400,6480,6561,6642,
|
||||
6724,6806,6889,6972,7056,7140,7225,7310,7396,7482,7569,7656,
|
||||
7744,7832,7921,8010,8100,8190,8281,8372,8464,8556,8649,8742,
|
||||
8836,8930,9025,9120,9216,9312,9409,9506,9604,9702,9801,9900,
|
||||
10000,10100,10201,10302,10404,10506,10609,10712,10816,10920,11025,11130,
|
||||
11236,11342,11449,11556,11664,11772,11881,11990,12100,12210,12321,12432,
|
||||
12544,12656,12769,12882,12996,13110,13225,13340,13456,13572,13689,13806,
|
||||
13924,14042,14161,14280,14400,14520,14641,14762,14884,15006,15129,15252,
|
||||
15376,15500,15625,15750,15876,16002,16129,16256,
|
||||
};
|
||||
|
||||
// Quarter-square multiplication:
|
||||
// a*b = ((a+b)^2 - (a-b)^2) / 4
|
||||
// With a table of floor(n^2/4), an 8x8 signed multiply costs two table
|
||||
// lookups and a subtraction. (The floors cancel: a+b and a-b are always
|
||||
// both even or both odd.)
|
||||
int16_t mul8(int8_t a, int8_t b) {
|
||||
return sq4[256 + a + b] - sq4[256 + a - b];
|
||||
}
|
||||
|
||||
// Perspective divide by table: recip[z] = 24576 / z, built once at boot
|
||||
// with the slow library divide. At run time x/z becomes x * recip[z].
|
||||
uint16_t recip[256];
|
||||
|
||||
#define ZDIST 160 // distance from eye to cube center
|
||||
#define CX (TB_W/2)
|
||||
#define CY (TB_H/2)
|
||||
|
||||
// cube corners
|
||||
const int8_t cube[8][3] = {
|
||||
{-32,-32,-32}, { 32,-32,-32}, { 32, 32,-32}, {-32, 32,-32},
|
||||
{-32,-32, 32}, { 32,-32, 32}, { 32, 32, 32}, {-32, 32, 32},
|
||||
};
|
||||
// 12 edges as pairs of corner indices
|
||||
const uint8_t edges[12][2] = {
|
||||
{0,1},{1,2},{2,3},{3,0}, {4,5},{5,6},{6,7},{7,4}, {0,4},{1,5},{2,6},{3,7},
|
||||
};
|
||||
|
||||
uint8_t sx[8], sy[8]; // projected corners
|
||||
|
||||
void transform(uint8_t ay, uint8_t ax) {
|
||||
int8_t sa = sin_tab[ay], ca = sin_tab[(uint8_t)(ay + 64)];
|
||||
int8_t sb = sin_tab[ax], cb = sin_tab[(uint8_t)(ax + 64)];
|
||||
uint8_t i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
int8_t x = cube[i][0], y = cube[i][1], z = cube[i][2];
|
||||
int8_t x1, z1, y2, z2;
|
||||
uint8_t zz;
|
||||
// rotate around Y (results scaled back down by 127 ~ >>7)
|
||||
x1 = (mul8(x, ca) - mul8(z, sa)) >> 7;
|
||||
z1 = (mul8(x, sa) + mul8(z, ca)) >> 7;
|
||||
// rotate around X
|
||||
y2 = (mul8(y, cb) - mul8(z1, sb)) >> 7;
|
||||
z2 = (mul8(y, sb) + mul8(z1, cb)) >> 7;
|
||||
// project: screen = center + x * (96 / z)
|
||||
zz = ZDIST + z2;
|
||||
sx[i] = CX + (int8_t)(((int16_t)x1 * (int16_t)recip[zz]) >> 8);
|
||||
sy[i] = CY + (int8_t)(((int16_t)y2 * (int16_t)recip[zz]) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
// call once at startup: fill recip[] using the (slow) library divide
|
||||
void gb3d_init(void) {
|
||||
uint16_t z;
|
||||
for (z = 1; z < 256; z++) recip[z] = 24576U / z;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
Parallax scrolling with one background layer.
|
||||
|
||||
The Game Boy has one scrolling background (plus the non-scrolling-ish
|
||||
Window), so there is no second "far" layer to scroll at a different
|
||||
speed. Games fake one in two ways, and this demo does both:
|
||||
|
||||
1. Raster splits. The screen is cut into horizontal bands. An LY==LYC
|
||||
interrupt fires just before each band starts and rewrites SCX, so
|
||||
each band scrolls at its own speed: clouds at 1/8, mountains at 1/4,
|
||||
trees at 1/2, the ground at full speed.
|
||||
|
||||
2. Tile animation. The water band at the bottom never scrolls (SCX = 0
|
||||
there), but its one tile's pixels are rotated a bit each frame, so the
|
||||
whole band appears to move. This works anywhere -- even in the middle
|
||||
of a band scrolling at another speed -- because every copy of a tile
|
||||
on screen changes at once. The cost: the rotated tile can't be used
|
||||
anywhere else.
|
||||
|
||||
Chikyu Kaiho Gun ZAS (T&E Soft, 1992) and Shantae (WayForward, 2002) are
|
||||
both known for parallax on this hardware; this demo shows the general
|
||||
technique, not their code.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// tiles: each row is two bytes, low bit-plane then high bit-plane
|
||||
|
||||
#define T_SKY 0
|
||||
#define T_CLOUDL 1
|
||||
#define T_CLOUD 2
|
||||
#define T_CLOUDR 3
|
||||
#define T_MTNL 4
|
||||
#define T_MTNR 5
|
||||
#define T_MTN 6
|
||||
#define T_TREE 7
|
||||
#define T_TRUNK 8
|
||||
#define T_GRASS 9
|
||||
#define T_DIRT 10
|
||||
#define T_WATER 11
|
||||
|
||||
/*{w:8,h:8,bpp:1,count:12,brev:1,np:2,pofs:1,sl:2}*/
|
||||
const uint8_t tiles[] = {
|
||||
// T_SKY: color 0
|
||||
0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00,
|
||||
// T_CLOUDL
|
||||
0x00,0x00, 0x07,0x00, 0x1F,0x00, 0x3F,0x00, 0x7F,0x00, 0xFF,0x00, 0x7F,0x00, 0x00,0x00,
|
||||
// T_CLOUD
|
||||
0x3C,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0x00,0x00,
|
||||
// T_CLOUDR
|
||||
0x00,0x00, 0xE0,0x00, 0xF8,0x00, 0xFC,0x00, 0xFE,0x00, 0xFF,0x00, 0xFE,0x00, 0x00,0x00,
|
||||
// T_MTNL: '/' edge, color 2 below
|
||||
0x00,0x01, 0x00,0x03, 0x00,0x07, 0x00,0x0F, 0x00,0x1F, 0x00,0x3F, 0x00,0x7F, 0x00,0xFF,
|
||||
// T_MTNR: '\' edge
|
||||
0x00,0x80, 0x00,0xC0, 0x00,0xE0, 0x00,0xF0, 0x00,0xF8, 0x00,0xFC, 0x00,0xFE, 0x00,0xFF,
|
||||
// T_MTN: solid color 2, a little snow-line texture
|
||||
0x00,0xFF, 0x00,0xFF, 0x22,0xFF, 0x00,0xFF, 0x00,0xFF, 0x88,0xFF, 0x00,0xFF, 0x00,0xFF,
|
||||
// T_TREE: round top, color 3
|
||||
0x3C,0x3C, 0x7E,0x7E, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0xFF,0xFF, 0x7E,0x7E, 0x3C,0x3C,
|
||||
// T_TRUNK
|
||||
0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x18,0x18, 0x3C,0x3C, 0xFF,0xFF,
|
||||
// T_GRASS: color 1 with blades of color 3
|
||||
0x00,0x00, 0x44,0x44, 0xEE,0xEE, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00, 0xFF,0x00,
|
||||
// T_DIRT: color 2 with color 3 pebbles
|
||||
0x00,0xFF, 0x40,0xFF, 0x00,0xFF, 0x04,0xFF, 0x00,0xFF, 0x20,0xFF, 0x00,0xFF, 0x02,0xFF,
|
||||
// T_WATER: waves (animated by rotating each row's bits)
|
||||
0x00,0xFF, 0x00,0xFF, 0xC3,0x3C, 0xFF,0x00, 0x00,0xFF, 0x00,0xFF, 0x3C,0xC3, 0xFF,0x00,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// the map: an 8-tile-wide pattern repeated across all 32 columns, so the
|
||||
// 256-pixel background wraps seamlessly as SCX rolls over
|
||||
|
||||
const uint8_t pattern[18][8] = {
|
||||
{ T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY },
|
||||
{ T_SKY,T_CLOUDL,T_CLOUD,T_CLOUDR,T_SKY,T_SKY,T_SKY,T_SKY },
|
||||
{ T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_CLOUDL,T_CLOUDR,T_SKY },
|
||||
{ T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY,T_SKY },
|
||||
{ T_SKY,T_SKY,T_SKY,T_MTNL,T_MTNR,T_SKY,T_SKY,T_SKY },
|
||||
{ T_SKY,T_SKY,T_MTNL,T_MTN,T_MTN,T_MTNR,T_SKY,T_SKY },
|
||||
{ T_SKY,T_MTNL,T_MTN,T_MTN,T_MTN,T_MTN,T_MTNR,T_SKY },
|
||||
{ T_MTNL,T_MTN,T_MTN,T_MTN,T_MTN,T_MTN,T_MTN,T_MTNR },
|
||||
{ T_SKY,T_TREE,T_SKY,T_SKY,T_TREE,T_SKY,T_TREE,T_SKY },
|
||||
{ T_SKY,T_TRUNK,T_SKY,T_SKY,T_TRUNK,T_SKY,T_TRUNK,T_SKY },
|
||||
{ T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS },
|
||||
{ T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT },
|
||||
{ T_TREE,T_SKY,T_SKY,T_TREE,T_SKY,T_SKY,T_SKY,T_SKY },
|
||||
{ T_TRUNK,T_SKY,T_SKY,T_TRUNK,T_SKY,T_SKY,T_SKY,T_SKY },
|
||||
{ T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS,T_GRASS },
|
||||
{ T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT,T_DIRT },
|
||||
{ T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER },
|
||||
{ T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER,T_WATER },
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// raster bands
|
||||
|
||||
#define NBANDS 5
|
||||
// LYC value for each band: the line *before* the band starts, so the
|
||||
// handler has time to wait for HBlank and write SCX before it's drawn
|
||||
const uint8_t band_lyc[NBANDS] = { 0, 31, 63, 95, 127 };
|
||||
uint8_t band_scx[NBANDS]; // SCX for each band, set by main()
|
||||
volatile uint8_t band; // band the raster is in
|
||||
|
||||
void vbl_isr(void) {
|
||||
// top of the frame: band 0 and arm the first split
|
||||
band = 0;
|
||||
SCX_REG = band_scx[0];
|
||||
LYC_REG = band_lyc[1];
|
||||
}
|
||||
|
||||
void lcd_isr(void) {
|
||||
// we're at the start of the line before the next band; wait for
|
||||
// HBlank so the change doesn't land in the middle of a visible line
|
||||
while (STAT_REG & 3) ;
|
||||
band++;
|
||||
SCX_REG = band_scx[band];
|
||||
// arm the next split, or park LYC below the screen
|
||||
LYC_REG = (band < NBANDS-1) ? band_lyc[band+1] : 200;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t x, y, i;
|
||||
uint16_t pos = 0;
|
||||
uint8_t water[16];
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
set_bkg_data(0, sizeof(tiles)/16, tiles);
|
||||
for (y = 0; y < 18; y++) {
|
||||
for (x = 0; x < 32; x += 8) {
|
||||
set_bkg_tiles(x, y, 8, 1, pattern[y]);
|
||||
}
|
||||
}
|
||||
for (i = 0; i < 16; i++) water[i] = tiles[T_WATER*16 + i];
|
||||
|
||||
STAT_REG = STATF_LYC;
|
||||
LYC_REG = band_lyc[1];
|
||||
disable_interrupts();
|
||||
add_VBL(vbl_isr);
|
||||
add_LCD(lcd_isr);
|
||||
enable_interrupts();
|
||||
set_interrupts(VBL_IFLAG | LCD_IFLAG);
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
wait_vbl_done();
|
||||
// 1. new scroll positions for next frame
|
||||
pos++;
|
||||
band_scx[0] = pos >> 3; // clouds
|
||||
band_scx[1] = pos >> 2; // mountains
|
||||
band_scx[2] = pos >> 1; // far trees
|
||||
band_scx[3] = pos; // near ground
|
||||
band_scx[4] = 0; // water: fixed, animated below
|
||||
// 2. animate the water tile: rotate every row one pixel left
|
||||
// (every other frame, so it moves at half speed)
|
||||
if (pos & 1) {
|
||||
for (i = 0; i < 16; i++) {
|
||||
uint8_t b = water[i];
|
||||
water[i] = (b << 1) | (b >> 7);
|
||||
}
|
||||
set_bkg_data(T_WATER, 1, water);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Filled, dithered polygons in a tile framebuffer, in the style of
|
||||
Race Drivin' (Argonaut, 1993).
|
||||
|
||||
The same spinning cube as wireframe.c, but solid:
|
||||
|
||||
1. back-face culling: a face whose corners appear clockwise on screen is
|
||||
facing away, so it is skipped -- on a convex object that is all the
|
||||
hidden-surface removal we need
|
||||
2. each visible face is scan-converted into a table of left/right X
|
||||
coordinates, one pair per pixel row
|
||||
3. each row is filled a *byte* at a time: a masked partial byte at each
|
||||
end, and plain writes for the 8-pixel runs in between
|
||||
4. shading: 4 gray levels plus checkerboard dithers between neighbors
|
||||
give 7 shades; the more a face points at the viewer, the lighter it is
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "tilebuf.h"
|
||||
#include "gb3d.h"
|
||||
|
||||
// the six faces, corners listed in the same winding order
|
||||
const uint8_t faces[6][4] = {
|
||||
{0,1,2,3}, {5,4,7,6}, {4,0,3,7}, {1,5,6,2}, {4,5,1,0}, {3,2,6,7},
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// scan conversion
|
||||
|
||||
uint8_t xleft[TB_H], xright[TB_H];
|
||||
uint8_t ytop, ybot;
|
||||
|
||||
// Walk one edge with an integer error term (no divide), recording the
|
||||
// X at every row into both the left and right tables: the smaller X
|
||||
// ends up in xleft[], the larger in xright[].
|
||||
void edge(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
|
||||
uint8_t dx, dy, y, x;
|
||||
int8_t xdir;
|
||||
int16_t err;
|
||||
if (y0 > y1) {
|
||||
y = x0; x0 = x1; x1 = y;
|
||||
y = y0; y0 = y1; y1 = y;
|
||||
}
|
||||
if (y0 < ytop) ytop = y0;
|
||||
if (y1 > ybot) ybot = y1;
|
||||
dy = y1 - y0;
|
||||
if (x1 >= x0) { dx = x1 - x0; xdir = 1; }
|
||||
else { dx = x0 - x1; xdir = -1; }
|
||||
x = x0;
|
||||
err = dy >> 1;
|
||||
for (y = y0; ; y++) {
|
||||
if (x < xleft[y]) xleft[y] = x;
|
||||
if (x > xright[y]) xright[y] = x;
|
||||
if (y == y1) break;
|
||||
// advance X until the error term says it's time for the next row
|
||||
err -= dx;
|
||||
while (err < 0) {
|
||||
err += dy;
|
||||
x += xdir;
|
||||
if (x < xleft[y]) xleft[y] = x;
|
||||
if (x > xright[y]) xright[y] = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fill_face(const uint8_t* f, const Shade* s) {
|
||||
uint8_t y;
|
||||
ytop = TB_H - 1; ybot = 0;
|
||||
for (y = 0; y < TB_H; y++) { xleft[y] = 255; xright[y] = 0; }
|
||||
edge(sx[f[0]], sy[f[0]], sx[f[1]], sy[f[1]]);
|
||||
edge(sx[f[1]], sy[f[1]], sx[f[2]], sy[f[2]]);
|
||||
edge(sx[f[2]], sy[f[2]], sx[f[3]], sy[f[3]]);
|
||||
edge(sx[f[3]], sy[f[3]], sx[f[0]], sy[f[0]]);
|
||||
for (y = ytop; y <= ybot; y++) {
|
||||
span(y, xleft[y], xright[y], s);
|
||||
}
|
||||
}
|
||||
|
||||
// Twice the signed screen area of triangle (a,b,c): positive when the
|
||||
// corners run counter-clockwise, i.e. the face points toward us. Its
|
||||
// size also says how squarely the face points at us, which makes a
|
||||
// free light source "behind the camera".
|
||||
int16_t facing(const uint8_t* f) {
|
||||
int16_t ax = sx[f[1]] - sx[f[0]], ay = sy[f[1]] - sy[f[0]];
|
||||
int16_t bx = sx[f[2]] - sx[f[0]], by = sy[f[2]] - sy[f[0]];
|
||||
return ax * by - ay * bx;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t ay = 0, ax = 0, i, y;
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
gb3d_init();
|
||||
tb_init();
|
||||
DISPLAY_ON;
|
||||
while (1) {
|
||||
// ground: dithered lower half, like a road-racer horizon
|
||||
for (y = TB_H/2 + 20; y < TB_H; y++) span(y, 0, TB_W - 1, &shades[1]);
|
||||
transform(ay, ax);
|
||||
for (i = 0; i < 6; i++) {
|
||||
int16_t area = facing(faces[i]);
|
||||
if (area > 0) {
|
||||
// bigger area = facing us more squarely = lighter shade
|
||||
// (faces use shades 3..6 so they never match the ground's shade 1)
|
||||
int16_t d = area / 600;
|
||||
uint8_t shade = (d >= 3) ? 3 : 6 - d;
|
||||
fill_face(faces[i], &shades[shade]);
|
||||
}
|
||||
}
|
||||
tb_blit();
|
||||
ay += 3;
|
||||
ax += 2;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -42,7 +42,8 @@ void lcd_handler(void) NONBANKED {
|
||||
uint8_t ly = LY_REG;
|
||||
if (ly < 143) {
|
||||
// offset this line and arm the interrupt for the next one
|
||||
SCX_REG = 248 + (int8_t)wave[(ly + phase) & 31];
|
||||
// (ly advances 8 lines per interrupt, so ly >> 3 steps one table entry)
|
||||
SCX_REG = 248 + (int8_t)wave[((ly >> 3) + phase) & 31];
|
||||
LYC_REG = ly + 8;
|
||||
} else {
|
||||
// last line: reset so the next frame starts at the top
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
A grid raycaster in a tile framebuffer, in the spirit of Faceball 2000
|
||||
(Xanth Software F/X, 1991) and Wolfenstein 3D.
|
||||
|
||||
The world is a 16x16 grid of cells. For each 4-pixel-wide column of the
|
||||
144x112 viewport we cast one ray from the player through the grid, one
|
||||
cell boundary at a time (the "DDA" walk), until it hits a wall. The
|
||||
distance to the wall sets the height of that column's wall slice.
|
||||
|
||||
Why this suits the Game Boy:
|
||||
- only 36 rays per frame, and each ray visits a few cells
|
||||
- walls are vertical spans centered on the horizon, so every pixel is
|
||||
written exactly once -- no sorting, no overdraw, no buffer clear needed
|
||||
- flat shading: walls facing east/west get one shade, north/south
|
||||
another -- the same cheap trick Faceball and Wolfenstein use
|
||||
|
||||
Controls: left/right to turn, up/down to move. With no input, the
|
||||
player slowly turns in place.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "tilebuf.h"
|
||||
#include "gb3d.h"
|
||||
|
||||
#define NCOLS (TB_W/4) // 36 rays, 4 pixels wide each
|
||||
|
||||
const uint8_t map[16][16] = {
|
||||
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1},
|
||||
{1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1},
|
||||
{1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
|
||||
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
|
||||
};
|
||||
|
||||
// player position in 8.8 fixed point (high byte = map cell)
|
||||
uint16_t px = 0x0880, py = 0x0880;
|
||||
uint8_t pa = 0; // facing angle, 256 = full turn
|
||||
|
||||
// camera-plane offset for each column, -80..+80 (a ~64 degree view)
|
||||
int8_t camx[NCOLS];
|
||||
|
||||
// inv[r] = 32512 / r: the distance along a ray between two grid lines,
|
||||
// in 8.8 cells, for a ray whose X (or Y) component is r/127
|
||||
uint16_t inv[256];
|
||||
|
||||
// wall half-height for each distance: htab[d >> 3] = (TB_H/2) / d,
|
||||
// with d in 8.8 cells -- a table instead of a divide per column
|
||||
uint8_t htab[512];
|
||||
|
||||
// wall slice for each column, computed by cast()
|
||||
uint8_t wall_h[NCOLS]; // half-height, 0..TB_H/2
|
||||
uint8_t wall_side[NCOLS]; // 0 = east/west face, 1 = north/south face
|
||||
|
||||
// distance to the first grid line = fraction of a cell * ddx.
|
||||
// ddx is 8.8 and frac is 0..256, so pre-shift both to stay in 16 bits.
|
||||
uint16_t first_side(uint16_t frac, uint16_t dd) {
|
||||
if (dd >= 0x1000) return 0xffff; // ray nearly parallel: never
|
||||
return ((dd >> 4) * frac) >> 4;
|
||||
}
|
||||
|
||||
void cast(void) {
|
||||
int8_t dirx = sin_tab[(uint8_t)(pa + 64)]; // cos(pa)
|
||||
int8_t diry = sin_tab[pa]; // sin(pa)
|
||||
uint8_t col;
|
||||
for (col = 0; col < NCOLS; col++) {
|
||||
// ray direction = view direction + camera plane * camx
|
||||
int16_t rx = dirx - (mul8(diry, camx[col]) >> 7);
|
||||
int16_t ry = diry + (mul8(dirx, camx[col]) >> 7);
|
||||
uint8_t mx = px >> 8, my = py >> 8;
|
||||
uint8_t arx = rx < 0 ? -rx : rx, ary = ry < 0 ? -ry : ry;
|
||||
uint16_t ddx = arx ? inv[arx] : 0xffff;
|
||||
uint16_t ddy = ary ? inv[ary] : 0xffff;
|
||||
uint16_t sdx, sdy, dist;
|
||||
int8_t stepx, stepy;
|
||||
uint8_t side;
|
||||
// distance to the first vertical and horizontal grid lines
|
||||
if (rx < 0) { stepx = -1; sdx = first_side(px & 0xff, ddx); }
|
||||
else { stepx = 1; sdx = first_side(256 - (px & 0xff), ddx); }
|
||||
if (ry < 0) { stepy = -1; sdy = first_side(py & 0xff, ddy); }
|
||||
else { stepy = 1; sdy = first_side(256 - (py & 0xff), ddy); }
|
||||
// DDA: step to whichever grid line is nearer, until we hit a wall
|
||||
while (1) {
|
||||
if (sdx < sdy) { sdx += ddx; mx += stepx; side = 0; }
|
||||
else { sdy += ddy; my += stepy; side = 1; }
|
||||
if (map[my][mx]) break;
|
||||
}
|
||||
// perpendicular distance (no fish-eye) in 8.8 cells
|
||||
dist = side ? sdy - ddy : sdx - ddx;
|
||||
dist >>= 3;
|
||||
wall_h[col] = dist < 512 ? htab[dist] : 0;
|
||||
wall_side[col] = side;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// drawing
|
||||
//
|
||||
// Two 4-pixel columns share each byte, so drawing one column at a time
|
||||
// means a read-modify-write for every row -- far too slow in C. Instead
|
||||
// we draw columns in *pairs*, top to bottom, writing whole bytes. Because
|
||||
// walls are centered on the horizon, each pair splits into at most five
|
||||
// bands where the byte pattern doesn't change:
|
||||
//
|
||||
// ceiling | taller wall + ceiling | both walls | taller wall + floor | floor
|
||||
//
|
||||
// so we compute one pattern per band and blast it down the rows.
|
||||
|
||||
uint8_t *g_p; // current framebuffer byte
|
||||
uint8_t g_y; // current row
|
||||
uint8_t g_n; // rows to fill
|
||||
uint8_t ple, phe, plo, pho; // pattern: even-row lo/hi, odd-row lo/hi
|
||||
|
||||
// Write g_n rows of the current pattern, starting at g_p / row g_y.
|
||||
//
|
||||
// This is the innermost loop of the renderer (18 pairs x 112 rows =
|
||||
// 2016 rows per frame), so it's in assembly: about 20 cycles per row,
|
||||
// versus about 120 for the same loop compiled from C.
|
||||
void fill_rows(void) __naked {
|
||||
__asm
|
||||
ld a, (_g_n)
|
||||
or a, a
|
||||
ret z
|
||||
ld b, a ; B = row count
|
||||
ld a, (_g_p)
|
||||
ld l, a
|
||||
ld a, (_g_p+1)
|
||||
ld h, a ; HL = framebuffer pointer
|
||||
ld a, (_g_y)
|
||||
ld c, a ; C = row number
|
||||
ld a, (_ple)
|
||||
ld d, a
|
||||
ld a, (_phe)
|
||||
ld e, a ; DE = even-row pattern
|
||||
1$:
|
||||
bit 0, c
|
||||
jr nz, 2$
|
||||
ld a, d ; even row
|
||||
ld (hl+), a
|
||||
ld a, e
|
||||
ld (hl+), a
|
||||
jr 3$
|
||||
2$:
|
||||
ld a, (_plo) ; odd row
|
||||
ld (hl+), a
|
||||
ld a, (_pho)
|
||||
ld (hl+), a
|
||||
3$:
|
||||
ld a, c
|
||||
inc c
|
||||
and a, #7
|
||||
cp a, #7
|
||||
jr nz, 4$
|
||||
; last row of a tile: skip to the next row of tiles
|
||||
; (+288 bytes per tile row, minus the 16 we walked through)
|
||||
ld a, l
|
||||
add a, #<(288-16)
|
||||
ld l, a
|
||||
ld a, h
|
||||
adc a, #>(288-16)
|
||||
ld h, a
|
||||
4$:
|
||||
dec b
|
||||
jr nz, 1$
|
||||
ld a, l
|
||||
ld (_g_p), a
|
||||
ld a, h
|
||||
ld (_g_p+1), a
|
||||
ld a, c
|
||||
ld (_g_y), a
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
#define CEIL 0 // shades[] index for ceiling
|
||||
#define FLOOR 1 // shades[] index for floor
|
||||
#define WALL_EW 6 // east/west walls
|
||||
#define WALL_NS 4 // north/south walls
|
||||
|
||||
// pattern bytes for a left/right nibble pair of two shades
|
||||
void pair_pattern(uint8_t sl, uint8_t sr) {
|
||||
const Shade* a = &shades[sl];
|
||||
const Shade* b = &shades[sr];
|
||||
ple = (a->lo0 & 0xf0) | (b->lo0 & 0x0f);
|
||||
phe = (a->hi0 & 0xf0) | (b->hi0 & 0x0f);
|
||||
plo = (a->lo1 & 0xf0) | (b->lo1 & 0x0f);
|
||||
pho = (a->hi1 & 0xf0) | (b->hi1 & 0x0f);
|
||||
}
|
||||
|
||||
void draw_pair(uint8_t pair) {
|
||||
uint8_t c = pair << 1;
|
||||
uint8_t hl = wall_h[c], hr = wall_h[c+1];
|
||||
uint8_t wl = wall_side[c] ? WALL_NS : WALL_EW;
|
||||
uint8_t wr = wall_side[c+1] ? WALL_NS : WALL_EW;
|
||||
uint8_t hs = hl < hr ? hl : hr; // shorter wall
|
||||
uint8_t ht = hl < hr ? hr : hl; // taller wall
|
||||
g_p = tb_buf + (pair << 4);
|
||||
g_y = 0;
|
||||
// 1. ceiling over both
|
||||
pair_pattern(CEIL, CEIL);
|
||||
g_n = TB_H/2 - ht; fill_rows();
|
||||
// 2. taller wall beside ceiling
|
||||
if (hl > hr) pair_pattern(wl, CEIL); else pair_pattern(CEIL, wr);
|
||||
g_n = ht - hs; fill_rows();
|
||||
// 3. both walls, across the horizon
|
||||
pair_pattern(wl, wr);
|
||||
g_n = hs * 2; fill_rows();
|
||||
// 4. taller wall beside floor
|
||||
if (hl > hr) pair_pattern(wl, FLOOR); else pair_pattern(FLOOR, wr);
|
||||
g_n = ht - hs; fill_rows();
|
||||
// 5. floor under both
|
||||
pair_pattern(FLOOR, FLOOR);
|
||||
g_n = TB_H/2 - ht; fill_rows();
|
||||
}
|
||||
|
||||
void render(void) {
|
||||
uint8_t pair;
|
||||
for (pair = 0; pair < NCOLS/2; pair++) draw_pair(pair);
|
||||
}
|
||||
|
||||
void move(int8_t speed) {
|
||||
// move along the facing direction, if the destination cell is open
|
||||
uint16_t nx = px + (((int16_t)sin_tab[(uint8_t)(pa + 64)] * speed) >> 5);
|
||||
uint16_t ny = py + (((int16_t)sin_tab[pa] * speed) >> 5);
|
||||
if (!map[ny >> 8][nx >> 8]) { px = nx; py = ny; }
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t i;
|
||||
uint16_t d;
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
for (i = 1; i != 0; i++) inv[i] = 32512U / i;
|
||||
for (d = 0; d < 512; d++) {
|
||||
uint16_t h = d ? (TB_H/2 * 32U) / d : 255; // (TB_H/2) / (d*8/256)
|
||||
htab[d] = h > TB_H/2 ? TB_H/2 : h;
|
||||
}
|
||||
for (i = 0; i < NCOLS; i++) camx[i] = (int8_t)(((int16_t)i * 160) / (NCOLS - 1) - 80);
|
||||
tb_init();
|
||||
DISPLAY_ON;
|
||||
while (1) {
|
||||
uint8_t j = joypad();
|
||||
if (j & J_LEFT) pa -= 4;
|
||||
else if (j & J_RIGHT) pa += 4;
|
||||
else if (!j) pa += 2;
|
||||
if (j & J_UP) move(4);
|
||||
if (j & J_DOWN) move(-4);
|
||||
cast();
|
||||
render();
|
||||
tb_blit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Battery-backed save RAM for Game Boy.
|
||||
|
||||
The cartridge header declares MBC5 + RAM + battery, with 8 KB of RAM
|
||||
mapped at 0xA000-0xBFFF. The RAM is switched off at power-up; we
|
||||
write 0x0A to 0x0000 to enable it and anything else to disable it.
|
||||
|
||||
The save is a small record with a signature and a checksum, so that
|
||||
a fresh cartridge (or one whose battery has died) is not mistaken
|
||||
for a real save:
|
||||
|
||||
0xA000 "GBSV" signature
|
||||
0xA004 boots how many times the game has been started
|
||||
0xA006 best best score
|
||||
0xA008 check sum of the bytes above, plus 0x5A
|
||||
|
||||
Controls: A adds a point, Start saves the score if it's a new best,
|
||||
B erases the save. Reset the emulator (or switch a real console off
|
||||
and on) and the numbers come back.
|
||||
*/
|
||||
|
||||
//#symbol ld GB_MAPPER=0x1B
|
||||
//#symbol ld GB_RAM_SIZE=0x02
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
typedef struct {
|
||||
char magic[4];
|
||||
uint16_t boots;
|
||||
uint16_t best;
|
||||
uint8_t check;
|
||||
} Save;
|
||||
|
||||
#define SAVE ((volatile Save*)0xA000)
|
||||
|
||||
#define ENABLE_SAVE_RAM (*(volatile uint8_t*)0x0000 = 0x0A)
|
||||
#define DISABLE_SAVE_RAM (*(volatile uint8_t*)0x0000 = 0x00)
|
||||
|
||||
// sum of every byte of the record except the checksum itself
|
||||
static uint8_t save_sum(void) {
|
||||
volatile uint8_t* p = (volatile uint8_t*)SAVE;
|
||||
uint8_t i, sum = 0x5A;
|
||||
for (i = 0; i < sizeof(Save) - 1; i++) sum += p[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
static uint8_t save_valid(void) {
|
||||
return SAVE->magic[0] == 'G' && SAVE->magic[1] == 'B' &&
|
||||
SAVE->magic[2] == 'S' && SAVE->magic[3] == 'V' &&
|
||||
SAVE->check == save_sum();
|
||||
}
|
||||
|
||||
// RAM must be enabled by the caller
|
||||
static void save_reset(void) {
|
||||
SAVE->magic[0] = 'G'; SAVE->magic[1] = 'B';
|
||||
SAVE->magic[2] = 'S'; SAVE->magic[3] = 'V';
|
||||
SAVE->boots = 0;
|
||||
SAVE->best = 0;
|
||||
SAVE->check = save_sum();
|
||||
}
|
||||
|
||||
// format a number as five digits (the division is slow, so
|
||||
// we do this while the screen is drawing, not during VBlank)
|
||||
static void fmt_num(char* buf, uint16_t v) {
|
||||
uint8_t i;
|
||||
for (i = 5; i > 0; i--) {
|
||||
buf[i - 1] = '0' + v % 10;
|
||||
v /= 10;
|
||||
}
|
||||
buf[5] = '\0';
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
char bootbuf[6], bestbuf[6], scorebuf[6];
|
||||
const char* msg;
|
||||
uint16_t score = 0, best;
|
||||
uint8_t keys, prev = 0, fresh, dirty;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
font_init();
|
||||
|
||||
// load the save, or start a new one
|
||||
ENABLE_SAVE_RAM;
|
||||
fresh = !save_valid();
|
||||
if (fresh) save_reset();
|
||||
SAVE->boots++;
|
||||
SAVE->check = save_sum();
|
||||
fmt_num(bootbuf, SAVE->boots);
|
||||
best = SAVE->best;
|
||||
DISABLE_SAVE_RAM;
|
||||
fmt_num(bestbuf, best);
|
||||
fmt_num(scorebuf, score);
|
||||
|
||||
put_str(5, 2, "SAVE RAM");
|
||||
put_str(2, 5, "BOOTS");
|
||||
put_str(2, 7, "BEST");
|
||||
put_str(2, 9, "SCORE");
|
||||
put_str(2, 12, fresh ? "NEW SAVE" : "SAVE LOADED");
|
||||
put_str(2, 15, "A ADD START SAVE");
|
||||
put_str(2, 16, "B ERASE");
|
||||
put_str(9, 5, bootbuf);
|
||||
put_str(9, 7, bestbuf);
|
||||
put_str(9, 9, scorebuf);
|
||||
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
|
||||
while (1) {
|
||||
keys = joypad();
|
||||
dirty = 0;
|
||||
msg = 0;
|
||||
|
||||
if ((keys & J_A) && !(prev & J_A)) {
|
||||
score++;
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
if ((keys & J_START) && !(prev & J_START) && score > best) {
|
||||
best = score;
|
||||
ENABLE_SAVE_RAM;
|
||||
SAVE->best = best;
|
||||
SAVE->check = save_sum();
|
||||
DISABLE_SAVE_RAM;
|
||||
msg = "SAVED ";
|
||||
dirty = 1;
|
||||
}
|
||||
|
||||
if ((keys & J_B) && !(prev & J_B)) {
|
||||
ENABLE_SAVE_RAM;
|
||||
save_reset();
|
||||
DISABLE_SAVE_RAM;
|
||||
best = 0;
|
||||
score = 0;
|
||||
msg = "ERASED ";
|
||||
dirty = 1;
|
||||
}
|
||||
prev = keys;
|
||||
|
||||
if (dirty) {
|
||||
fmt_num(bestbuf, best);
|
||||
fmt_num(scorebuf, score);
|
||||
}
|
||||
|
||||
wait_vbl_done();
|
||||
if (dirty) {
|
||||
put_str(9, 7, bestbuf);
|
||||
put_str(9, 9, scorebuf);
|
||||
if (msg) put_str(2, 12, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
Assembly language on the SM83.
|
||||
|
||||
The same loop -- add up N bytes -- written twice: once in C, and once
|
||||
by hand as a __naked function. The timer counts how long each version
|
||||
takes, so we can see what the compiler's version costs.
|
||||
|
||||
SDCC passes arguments on the stack (the first at SP+2 once the return
|
||||
address is pushed), and an 8-bit result comes back in E. A __naked
|
||||
function gets no prologue or epilogue at all, so the hand-written
|
||||
version has to end with its own ret.
|
||||
|
||||
TAC = 5 makes TIMA tick at 262144 Hz: once every 16 clocks, which is
|
||||
4 machine cycles. TIMA is only 8 bits wide, so keep the runs short
|
||||
enough that it can't wrap (256 ticks = 1024 machine cycles).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "gbtext.h"
|
||||
|
||||
#define COUNT 16
|
||||
|
||||
uint8_t sum_c(const uint8_t *p, uint8_t n) {
|
||||
uint8_t s = 0;
|
||||
while (n--) s += *p++;
|
||||
return s;
|
||||
}
|
||||
|
||||
uint8_t sum_asm(const uint8_t *p, uint8_t n) __naked {
|
||||
p; n; // (silences unused-argument warnings)
|
||||
__asm
|
||||
ldhl sp,#2 ; HL -> first argument
|
||||
ld a,(hl+)
|
||||
ld e,a
|
||||
ld a,(hl+)
|
||||
ld d,a ; DE = p
|
||||
ld b,(hl) ; B = n
|
||||
ld h,d
|
||||
ld l,e ; HL = p
|
||||
xor a,a ; A = 0
|
||||
inc b ; enter the loop at the test, so n = 0 works
|
||||
jr 2$
|
||||
1$: add a,(hl) ; A += *p
|
||||
inc hl ; p++
|
||||
2$: dec b
|
||||
jr nz,1$
|
||||
ld e,a ; the result goes in E
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
uint8_t buf[COUNT];
|
||||
|
||||
// clear the timer, run one call, and return the ticks it took
|
||||
#define TIME(var, expr) \
|
||||
do { TIMA_REG = 0; sink = (expr); var = TIMA_REG; } while (0)
|
||||
|
||||
volatile uint8_t sink;
|
||||
|
||||
static void put_dec(uint8_t x, uint8_t y, uint16_t v) {
|
||||
char s[6];
|
||||
uint8_t i = 5;
|
||||
s[5] = '\0';
|
||||
do { s[--i] = '0' + v % 10; v /= 10; } while (v);
|
||||
put_str(x, y, s + i);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t i, c0, c1, a0, a1, same;
|
||||
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
font_init();
|
||||
|
||||
for (i = 0; i < COUNT; i++) buf[i] = i * 3;
|
||||
TMA_REG = 0;
|
||||
TAC_REG = 0x05; // enabled, 262144 Hz
|
||||
|
||||
TIME(c0, sum_c(buf, 0));
|
||||
TIME(c1, sum_c(buf, COUNT));
|
||||
TIME(a0, sum_asm(buf, 0));
|
||||
TIME(a1, sum_asm(buf, COUNT));
|
||||
same = sum_c(buf, COUNT) == sum_asm(buf, COUNT);
|
||||
|
||||
put_str(2, 1, "C VS ASSEMBLY");
|
||||
put_str(1, 4, "SAME ANSWER");
|
||||
put_str(14, 4, same ? "YES" : "NO");
|
||||
put_str(1, 7, "C ");
|
||||
put_str(1, 8, "ASM");
|
||||
put_str(6, 6, "TICKS");
|
||||
put_dec(7, 7, c1 - c0);
|
||||
put_dec(7, 8, a1 - a0);
|
||||
put_str(1, 11, "CYCLES PER BYTE");
|
||||
// one tick is 4 machine cycles (rounded; TIMA can be off by one tick)
|
||||
put_str(1, 12, "C ");
|
||||
put_str(1, 13, "ASM");
|
||||
put_dec(7, 12, ((uint16_t)(c1 - c0) * 4 + COUNT / 2) / COUNT);
|
||||
put_dec(7, 13, ((uint16_t)(a1 - a0) * 4 + COUNT / 2) / COUNT);
|
||||
|
||||
SHOW_BKG;
|
||||
DISPLAY_ON;
|
||||
while (1) wait_vbl_done();
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
A software framebuffer made of background tiles.
|
||||
|
||||
The Game Boy has no bitmap mode, but if every cell in a rectangle of the
|
||||
tile map points at a *different* tile, then each pixel in that rectangle
|
||||
belongs to exactly one bit of one tile -- and the tiles become a bitmap.
|
||||
|
||||
This header uses the layout from Faceball 2000 (1991):
|
||||
|
||||
- The viewport is 18x14 tiles (144x112 pixels): 252 unique tiles.
|
||||
- The background uses the $8800 "signed" tile-data mode (LCDC bit 4 = 0).
|
||||
In that mode tile numbers $80..$FF live at $8800..$8FFF and $00..$7F at
|
||||
$9000..$97FF, so counting upward from tile $84 and wrapping past $FF
|
||||
gives one *linear* run of VRAM: $8840..$983F. One copy loop fills it.
|
||||
- Tiles $80..$83 are left over for the border around the viewport.
|
||||
- The CPU draws into a 4032-byte buffer in WRAM (tb_buf), which has the
|
||||
same tile-by-tile layout as VRAM, then tb_blit() copies it to VRAM
|
||||
eight bytes per scanline, during HBlank, while the screen is on.
|
||||
|
||||
The price is VRAM bandwidth: 4032 bytes / 8 per line = 504 scanlines,
|
||||
about 3.3 frames just to copy one picture. That is why 3D games on the
|
||||
original Game Boy ran at single-digit frame rates.
|
||||
*/
|
||||
|
||||
#ifndef _TILEBUF_H
|
||||
#define _TILEBUF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define TB_COLS 18 // viewport width in tiles
|
||||
#define TB_ROWS 14 // viewport height in tiles
|
||||
#define TB_W (TB_COLS*8) // 144 pixels
|
||||
#define TB_H (TB_ROWS*8) // 112 pixels
|
||||
#define TB_ROWBYTES (TB_COLS*16) // 288 bytes per row of tiles
|
||||
#define TB_BYTES (TB_ROWS*TB_ROWBYTES) // 4032
|
||||
#define TB_FIRST_TILE 0x84 // first viewport tile number
|
||||
#define TB_VRAM ((uint8_t*)0x8840) // tile $84 in $8800 mode
|
||||
#define TB_MAPX 1 // viewport position on the tile map
|
||||
#define TB_MAPY 1
|
||||
|
||||
// the framebuffer, in WRAM, laid out exactly like the VRAM tiles
|
||||
uint8_t tb_buf[TB_BYTES];
|
||||
|
||||
// address of pixel row y, column 0 -- Faceball keeps this same
|
||||
// table at $0300 in its ROM ("FrameBufferRAMOffsets")
|
||||
uint8_t* tb_rowaddr[TB_H];
|
||||
|
||||
// bit mask for each pixel column within a byte
|
||||
const uint8_t tb_bit[8] = { 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01 };
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// setup
|
||||
|
||||
// Border tiles: $80 blank, $81 horizontal edge, $82 vertical edge.
|
||||
/*{w:8,h:8,bpp:1,count:3,brev:1,np:2,pofs:1,sl:2}*/
|
||||
static const uint8_t tb_border_tiles[] = {
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
|
||||
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
|
||||
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
|
||||
};
|
||||
|
||||
void tb_init(void) {
|
||||
uint8_t x, y, t;
|
||||
uint8_t row[TB_COLS];
|
||||
// build the row-address table
|
||||
for (y = 0; y < TB_H; y++) {
|
||||
tb_rowaddr[y] = tb_buf + (y >> 3) * TB_ROWBYTES + (y & 7) * 2;
|
||||
}
|
||||
// border tiles live in the $8800 half, tile numbers $80..$82
|
||||
set_bkg_data(0x80, 3, tb_border_tiles);
|
||||
fill_bkg_rect(0, 0, 20, 18, 0x80);
|
||||
fill_bkg_rect(TB_MAPX, 0, TB_COLS, 1, 0x81);
|
||||
fill_bkg_rect(0, TB_MAPY, 1, TB_ROWS, 0x82);
|
||||
// number the viewport cells $84, $85, ... $FF, $00, ... $7F
|
||||
t = TB_FIRST_TILE;
|
||||
for (y = 0; y < TB_ROWS; y++) {
|
||||
for (x = 0; x < TB_COLS; x++) row[x] = t++;
|
||||
set_bkg_tiles(TB_MAPX, TB_MAPY + y, TB_COLS, 1, row);
|
||||
}
|
||||
// clear buffer and VRAM
|
||||
for (x = 0; x < TB_ROWS; x++) {
|
||||
uint8_t* p = tb_buf + x * TB_ROWBYTES;
|
||||
uint16_t i;
|
||||
for (i = 0; i < TB_ROWBYTES; i++) p[i] = 0;
|
||||
}
|
||||
// $8800 addressing: LCDC bit 4 clear
|
||||
LCDC_REG = LCDCF_ON | LCDCF_BG8800 | LCDCF_BG9800 | LCDCF_BGON;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// the blitter
|
||||
|
||||
// Interrupts need a real stack while tb_blit() is borrowing SP,
|
||||
// so we give them this one (Faceball uses $C9A1).
|
||||
static uint8_t tb_isr_stack[96];
|
||||
static uint16_t tb_saved_sp;
|
||||
|
||||
/*
|
||||
Copy tb_buf to VRAM, 8 bytes per scanline, and clear tb_buf behind us.
|
||||
|
||||
This is a transcription of Faceball 2000's UpdateFrameBuffer routine
|
||||
($13F0 in the ROM). The trick is the stack pointer: POP BC reads two
|
||||
bytes and advances the pointer in 3 cycles, the fastest way to read
|
||||
memory on the SM83. So SP is pointed at the buffer and the loop pops
|
||||
its way through it:
|
||||
|
||||
1. POP the first two bytes before we wait (saves time in HBlank)
|
||||
2. wait until STAT bit 1 is set (mode 2/3: the PPU is drawing)
|
||||
3. wait until STAT bit 1 is clear (mode 0: HBlank -- VRAM is free)
|
||||
4. write 8 bytes to VRAM. Mode 0 plus the following mode 2 (VRAM is
|
||||
only locked in mode 3) gives at least ~40 M-cycles, enough for 8.
|
||||
5. park SP on a safe stack and enable interrupts while the PPU draws
|
||||
the next line, so the VBlank handler/music can still run
|
||||
6. PUSH zeros over the 8 bytes we just copied -- clearing the buffer
|
||||
for the next frame costs almost nothing
|
||||
|
||||
Note that in VBlank (mode 1) STAT bit 1 is 0, so step 2 waits out all
|
||||
of VBlank: this routine only uses the 144 visible lines per frame.
|
||||
*/
|
||||
void tb_blit(void) __naked {
|
||||
__asm
|
||||
ld (_tb_saved_sp), sp
|
||||
ld hl, #0x8840 ; VRAM destination (tile $84)
|
||||
ld de, #504 ; 4032 / 8 lines
|
||||
di
|
||||
ld sp, #_tb_buf
|
||||
1$:
|
||||
pop bc ; bytes 0,1
|
||||
2$:
|
||||
ldh a, (0x41) ; STAT
|
||||
and a, #0x02
|
||||
jr z, 2$ ; wait for mode 2/3
|
||||
3$:
|
||||
ldh a, (0x41)
|
||||
and a, #0x02
|
||||
jr nz, 3$ ; wait for mode 0 (HBlank)
|
||||
ld a, c
|
||||
ld (hl+), a
|
||||
ld a, b
|
||||
ld (hl+), a
|
||||
pop bc ; bytes 2,3
|
||||
ld a, c
|
||||
ld (hl+), a
|
||||
ld a, b
|
||||
ld (hl+), a
|
||||
pop bc ; bytes 4,5
|
||||
ld a, c
|
||||
ld (hl+), a
|
||||
ld a, b
|
||||
ld (hl+), a
|
||||
pop bc ; bytes 6,7
|
||||
ld a, c
|
||||
ld (hl+), a
|
||||
ld a, b
|
||||
ld (hl+), a
|
||||
; swap SP to the ISR stack, let interrupts in
|
||||
ld c, l
|
||||
ld b, h ; BC = VRAM pointer
|
||||
ldhl sp, #0 ; HL = buffer pointer
|
||||
ld sp, #_tb_isr_stack+96
|
||||
ei
|
||||
4$:
|
||||
ldh a, (0x41)
|
||||
and a, #0x02
|
||||
jr z, 4$ ; wait until HBlank is over
|
||||
di
|
||||
ld sp, hl
|
||||
ld l, c
|
||||
ld h, b ; HL = VRAM pointer again
|
||||
ld bc, #0
|
||||
push bc ; clear the 8 bytes we just popped
|
||||
push bc
|
||||
push bc
|
||||
push bc
|
||||
add sp, #8
|
||||
dec de
|
||||
ld a, d
|
||||
or a, e
|
||||
jr nz, 1$
|
||||
; restore the C stack
|
||||
ld a, (_tb_saved_sp)
|
||||
ld l, a
|
||||
ld a, (_tb_saved_sp+1)
|
||||
ld h, a
|
||||
ld sp, hl
|
||||
ei
|
||||
ret
|
||||
__endasm;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// drawing
|
||||
//
|
||||
// Each pixel row of a tile is two bytes: the low bit-plane, then the high
|
||||
// bit-plane. Color c (0..3) sets bit (c & 1) in the first and (c & 2) in
|
||||
// the second. So one pixel = two read-modify-writes, but eight pixels of
|
||||
// the same color = two plain writes. Fast code draws bytes, not pixels.
|
||||
|
||||
uint8_t tb_color = 3;
|
||||
|
||||
void tb_pset(uint8_t x, uint8_t y) {
|
||||
uint8_t* p = tb_rowaddr[y] + ((x & 0xf8) << 1);
|
||||
uint8_t m = tb_bit[x & 7];
|
||||
if (tb_color & 1) p[0] |= m; else p[0] &= ~m;
|
||||
if (tb_color & 2) p[1] |= m; else p[1] &= ~m;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// shades
|
||||
//
|
||||
// Shade 0,2,4,6 are the solid colors 0..3. Odd shades are a checkerboard
|
||||
// of the two neighbors. Each shade is described by its two bit-plane
|
||||
// bytes on even rows and on odd rows.
|
||||
|
||||
typedef struct { uint8_t lo0, hi0, lo1, hi1; } Shade;
|
||||
|
||||
const Shade shades[7] = {
|
||||
// lo0 hi0 lo1 hi1
|
||||
{ 0x00, 0x00, 0x00, 0x00 }, // color 0
|
||||
{ 0xAA, 0x00, 0x55, 0x00 }, // 0/1 checker
|
||||
{ 0xFF, 0x00, 0xFF, 0x00 }, // color 1
|
||||
{ 0x55, 0xAA, 0xAA, 0x55 }, // 1/2 checker
|
||||
{ 0x00, 0xFF, 0x00, 0xFF }, // color 2
|
||||
{ 0xAA, 0xFF, 0x55, 0xFF }, // 2/3 checker
|
||||
{ 0xFF, 0xFF, 0xFF, 0xFF }, // color 3
|
||||
};
|
||||
|
||||
// left mask for x&7, right mask for x&7
|
||||
const uint8_t lmask[8] = { 0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01 };
|
||||
const uint8_t rmask[8] = { 0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF };
|
||||
|
||||
// Fill pixels xl..xr (inclusive) of row y with a shade.
|
||||
void span(uint8_t y, uint8_t xl, uint8_t xr, const Shade* s) {
|
||||
uint8_t *p = tb_rowaddr[y] + ((xl & 0xf8) << 1);
|
||||
uint8_t lo = (y & 1) ? s->lo1 : s->lo0;
|
||||
uint8_t hi = (y & 1) ? s->hi1 : s->hi0;
|
||||
uint8_t m = lmask[xl & 7];
|
||||
uint8_t n = (xr >> 3) - (xl >> 3); // number of byte boundaries crossed
|
||||
if (n == 0) {
|
||||
m &= rmask[xr & 7]; // span starts and ends in one byte
|
||||
} else {
|
||||
// left partial byte
|
||||
p[0] = (p[0] & ~m) | (lo & m);
|
||||
p[1] = (p[1] & ~m) | (hi & m);
|
||||
p += 16;
|
||||
// whole bytes: no masking, two writes per 8 pixels
|
||||
while (--n) {
|
||||
p[0] = lo;
|
||||
p[1] = hi;
|
||||
p += 16;
|
||||
}
|
||||
m = rmask[xr & 7];
|
||||
}
|
||||
// right (or only) partial byte
|
||||
p[0] = (p[0] & ~m) | (lo & m);
|
||||
p[1] = (p[1] & ~m) | (hi & m);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Wireframe 3D in a tile framebuffer, in the style of X (Argonaut, 1992).
|
||||
|
||||
A cube spins in the 144x112 viewport from tilebuf.h. Each frame:
|
||||
|
||||
1. rotate the 8 corners around two axes -- 8-bit fixed point, using a
|
||||
quarter-square table because the SM83 has no multiply instruction
|
||||
2. project them with a reciprocal table instead of dividing by Z
|
||||
3. draw the 12 edges with Bresenham's line algorithm, stepping a VRAM-style
|
||||
pointer and bit mask instead of recomputing each pixel's address
|
||||
4. tb_blit() copies the finished picture to VRAM (and clears the buffer)
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "gb/types.h"
|
||||
#include "gb/hardware.h"
|
||||
#include "gb/gb.h"
|
||||
#include "tilebuf.h"
|
||||
#include "gb3d.h"
|
||||
|
||||
// Bresenham's line, walking a framebuffer pointer and a bit mask.
|
||||
// Moving right one pixel = shift the mask; every 8 pixels, step 16 bytes
|
||||
// to the next tile. Moving down one pixel = 2 bytes, except every 8 rows,
|
||||
// where we jump to the next row of tiles.
|
||||
void line(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
|
||||
uint8_t *p;
|
||||
uint8_t mask, dx, dy, n, y;
|
||||
int16_t err;
|
||||
int8_t xdir;
|
||||
if (y0 > y1) { // always draw downward
|
||||
n = x0; x0 = x1; x1 = n;
|
||||
n = y0; y0 = y1; y1 = n;
|
||||
}
|
||||
dy = y1 - y0;
|
||||
if (x1 >= x0) { dx = x1 - x0; xdir = 1; }
|
||||
else { dx = x0 - x1; xdir = -1; }
|
||||
p = tb_rowaddr[y0] + ((x0 & 0xf8) << 1);
|
||||
mask = tb_bit[x0 & 7];
|
||||
y = y0;
|
||||
if (dx >= dy) { // mostly horizontal
|
||||
err = dx >> 1;
|
||||
for (n = dx + 1; n; n--) {
|
||||
p[0] |= mask; p[1] |= mask; // color 3
|
||||
if (xdir > 0) {
|
||||
mask >>= 1;
|
||||
if (!mask) { mask = 0x80; p += 16; }
|
||||
} else {
|
||||
mask <<= 1;
|
||||
if (!mask) { mask = 0x01; p -= 16; }
|
||||
}
|
||||
err -= dy;
|
||||
if (err < 0) {
|
||||
err += dx;
|
||||
p += ((++y & 7) == 0) ? (TB_ROWBYTES - 14) : 2;
|
||||
}
|
||||
}
|
||||
} else { // mostly vertical
|
||||
err = dy >> 1;
|
||||
for (n = dy + 1; n; n--) {
|
||||
p[0] |= mask; p[1] |= mask;
|
||||
p += ((++y & 7) == 0) ? (TB_ROWBYTES - 14) : 2;
|
||||
err -= dx;
|
||||
if (err < 0) {
|
||||
err += dy;
|
||||
if (xdir > 0) {
|
||||
mask >>= 1;
|
||||
if (!mask) { mask = 0x80; p += 16; }
|
||||
} else {
|
||||
mask <<= 1;
|
||||
if (!mask) { mask = 0x01; p -= 16; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
uint8_t ay = 0, ax = 0, i;
|
||||
DISPLAY_OFF;
|
||||
BGP_REG = 0xE4;
|
||||
gb3d_init();
|
||||
tb_init();
|
||||
DISPLAY_ON;
|
||||
while (1) {
|
||||
transform(ay, ax);
|
||||
for (i = 0; i < 12; i++) {
|
||||
uint8_t a = edges[i][0], b = edges[i][1];
|
||||
line(sx[a], sy[a], sx[b], sy[b]);
|
||||
}
|
||||
tb_blit(); // ~3.3 frames: copy to VRAM and clear the buffer
|
||||
ay += 3;
|
||||
ax += 2;
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -16,19 +16,29 @@ const GB_PRESETS: Preset[] = [
|
||||
{ id: 'large_map.c', name: 'Large Scrolling Map' },
|
||||
{ id: 'window.c', name: 'Window Status Bar' },
|
||||
{ id: 'galaxy.c', name: 'Window + Sprites' },
|
||||
{ id: 'raster.c', name: 'STAT/LYC Raster Effects' },
|
||||
{ id: 'interrupts.c', name: 'VBlank + Timer Interrupts' },
|
||||
{ id: 'vram.c', name: 'VRAM/OAM Access' },
|
||||
{ id: 'detect.c', name: 'Detect Hardware' },
|
||||
{ id: 'gbc.c', name: 'Game Boy Color' },
|
||||
{ id: 'gbdecompress.c', name: 'Decompression' },
|
||||
{ id: 'music.c', name: 'Music Player' },
|
||||
{ id: 'aputest.c', name: 'Sound Tester' },
|
||||
{ id: 'clock.c', name: 'MBC3 Real-Time Clock' },
|
||||
{ id: 'savegame.c', name: 'Battery Save RAM' },
|
||||
{ id: 'banking.c', name: 'ROM Bank Switching' },
|
||||
{ id: 'flicker.c', name: 'Flicker Transparency', category: 'Advanced Graphics' },
|
||||
{ id: 'parallax.c', name: 'Parallax Scrolling' },
|
||||
{ id: 'animstream.c', name: 'Streaming Animation' },
|
||||
{ id: 'raster.c', name: 'STAT/LYC Raster Effects' },
|
||||
{ id: 'wireframe.c', name: 'Wireframe 3D' },
|
||||
{ id: 'polyfill.c', name: 'Filled Polygons' },
|
||||
{ id: 'raycast.c', name: 'Raycasting 3D' },
|
||||
{ id: 'siegegame.c', name: 'Siege Game', category: 'Games' },
|
||||
{ id: 'climber.c', name: 'Climber Game' },
|
||||
{ id: 'chase.c', name: "Shiru's Chase Game" },
|
||||
{ id: 'pakupaku.c', name: 'Paku Paku' },
|
||||
{ id: 'hello.sgb', name: 'Hello World (ASM)', category: 'Assembler' },
|
||||
{ id: 'sm83.c', name: 'C vs. Assembly', category: 'Assembler' },
|
||||
{ id: 'hello.sgb', name: 'Hello World (ASM)' },
|
||||
{ id: 'main.wiz', name: 'Snake Game (Wiz)' },
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user