diff --git a/src/test/kc/complex/nes-balls/kickballs-2.c b/src/test/kc/complex/nes-balls/kickballs-2.c new file mode 100644 index 000000000..7654894a5 --- /dev/null +++ b/src/test/kc/complex/nes-balls/kickballs-2.c @@ -0,0 +1,161 @@ +#pragma target(nes) +#pragma emulator("java -jar /Applications/Nintaco_bin_2020-05-01/Nintaco.jar") +#include +#include + +#define MAX_BALLS 50 +#define WEIGHT 0x0010 +#define RELEASE_TIMER 0x09 + +#define poke(addr) (*(unsigned byte *)(addr)) + +typedef struct +{ + unsigned short x_position; + unsigned short y_position; + unsigned short x_velocity; + unsigned short y_velocity; +} ball; + +#pragma data_seg(GameRam) +// Moving balls (in GameRAM) +ball balls[64]; + +#pragma data_seg(Data) + + static const unsigned char sine_table[256] = { + 0x40,0x42,0x43,0x45,0x46,0x48,0x49,0x4b,0x4c,0x4e,0x50,0x51,0x53,0x54,0x56,0x57, + 0x58,0x5a,0x5b,0x5d,0x5e,0x60,0x61,0x62,0x64,0x65,0x66,0x67,0x69,0x6a,0x6b,0x6c, + 0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x78,0x79,0x7a,0x7b, + 0x7b,0x7c,0x7c,0x7d,0x7d,0x7e,0x7e,0x7e,0x7f,0x7f,0x7f,0x80,0x80,0x80,0x80,0x80, + 0x80,0x80,0x80,0x80,0x80,0x80,0x7f,0x7f,0x7f,0x7e,0x7e,0x7e,0x7d,0x7d,0x7c,0x7c, + 0x7b,0x7b,0x7a,0x79,0x78,0x78,0x77,0x76,0x75,0x74,0x73,0x72,0x71,0x70,0x6f,0x6e, + 0x6d,0x6c,0x6b,0x6a,0x69,0x67,0x66,0x65,0x64,0x62,0x61,0x60,0x5e,0x5d,0x5b,0x5a, + 0x58,0x57,0x56,0x54,0x53,0x51,0x50,0x4e,0x4c,0x4b,0x49,0x48,0x46,0x45,0x43,0x42, + 0x40,0x3e,0x3d,0x3b,0x3a,0x38,0x37,0x35,0x34,0x32,0x30,0x2f,0x2d,0x2c,0x2a,0x29, + 0x28,0x26,0x25,0x23,0x22,0x20,0x1f,0x1e,0x1c,0x1b,0x1a,0x19,0x17,0x16,0x15,0x14, + 0x13,0x12,0x11,0x10,0xf,0xe,0xd,0xc,0xb,0xa,0x9,0x8,0x8,0x7,0x6,0x5, + 0x5,0x4,0x4,0x3,0x3,0x2,0x2,0x2,0x1,0x1,0x1,0x0,0x0,0x0,0x0,0x0, + 0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x1,0x1,0x2,0x2,0x2,0x3,0x3,0x4,0x4, + 0x5,0x5,0x6,0x7,0x8,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,0x10,0x11,0x12, + 0x13,0x14,0x15,0x16,0x17,0x19,0x1a,0x1b,0x1c,0x1e,0x1f,0x20,0x22,0x23,0x25,0x26, + 0x28,0x29,0x2a,0x2c,0x2d,0x2f,0x30,0x32,0x34,0x35,0x37,0x38,0x3a,0x3b,0x3d,0x3e + }; + + static const unsigned char object[5] = { 0, 0, 10, 3, 128 }; + + const unsigned char palette[] = { 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04, 0x34,0x24,0x14,0x04 }; + + const unsigned char h_bar_tilemap[] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }; + + volatile unsigned char scroll_y = 0; + volatile unsigned char vblank_hit = 0; + +int main(void) +{ + // Initialize NES after RESET + initNES(); + // Transfer the palette + ppuDataTransfer(PPU_PALETTE, palette, sizeof(palette)); + // Fill the PPU attribute table + ppuDataFill(PPU_NAME_TABLE_0, 0, 32*30); + ppuDataFill(PPU_ATTRIBUTE_TABLE_0, 0, 0x40); + ppuDataTransfer(0x2040, h_bar_tilemap, sizeof(h_bar_tilemap)); + // Enable screen rendering and vblank + enableVideoOutput(); + // Enable vertical blank interrupt, select sprite pattern table 1 + PPU->PPUCTRL = 0b10001000; + + unsigned int i; + unsigned char active_balls = 0; + unsigned char timer = 0; + unsigned char timer_2 = 0; + unsigned char h_bar = 0x80; + unsigned char sprite_idx = 0; + + for (i = 0; i < MAX_BALLS; i++) + { + balls[i].x_velocity = rand() & 0x3FF; + balls[i].y_velocity = rand() & 0x0FF; + } + + while (1) + { + timer_2++; + h_bar = sine_table[timer_2] + 0x60; + scroll_y = h_bar ^ 0xFF; + + if (active_balls < MAX_BALLS) + { + if (timer++ == RELEASE_TIMER) + { + timer = 0; + active_balls++; + balls[active_balls].x_position = 0; + balls[active_balls].y_position = 0; + } + } + + sprite_idx = 0; + for (i = 0; i < active_balls; i++) + { + balls[i].x_position += balls[i].x_velocity; + balls[i].y_position += (balls[i].y_velocity += WEIGHT); + + if ((balls[i].x_position >> 8) < 8) + { + balls[i].x_velocity ^= 0xFFFF; + } + + if (((balls[i].y_position >> 8) >= h_bar) && (balls[i].y_position >> 8) < h_bar + 8) + { + balls[i].y_velocity ^= 0xFFFF; + balls[i].y_position = ((unsigned short)(h_bar - 2) << 8); + } + + SPRITE_BUFFER[sprite_idx].y = (unsigned char) (balls[i].y_position >> 8); + SPRITE_BUFFER[sprite_idx].tile = 0x0a; + SPRITE_BUFFER[sprite_idx].attributes = 3; + SPRITE_BUFFER[sprite_idx].x = (unsigned char) (balls[i].x_position >> 8); + sprite_idx++; + } + + poke(0x2001) = 0x98; + while (!vblank_hit); // wait for vblank + vblank_hit = 0; + poke(0x2001) = 0x18; + } + return 0; +} + +// NMI Called when the PPU refreshes the screen (also known as the V-Blank period) +interrupt(hardware_stack) void vblank() { + // Set scroll + PPU->PPUSCROLL = 0; + PPU->PPUSCROLL = scroll_y; + // DMA transfer the entire sprite buffer to the PPU + ppuSpriteBufferDmaTransfer(SPRITE_BUFFER); + vblank_hit++; +} + +// Tile Set (in CHR ROM) +#pragma data_seg(Tiles) +export char TILES[] = kickasm(resource "lazydata.chr") {{ + .import binary "lazydata.chr" +}}; + +// Sprite Buffer (in GAME RAM) +// Will be transferred to the PPU via DMA during vblank +#pragma data_seg(GameRam) +struct SpriteData align(0x100) SPRITE_BUFFER[0x100]; + +// Interrupt Vectors (in PRG ROM) +#pragma data_seg(Vectors) +export void()* const VECTORS[] = { + // NMI Called when the PPU refreshes the screen (also known as the V-Blank period) + &vblank, + // RESET Called when the NES is reset, including when it is turned on. + &main, + // IRQ Called when a BRK instruction is executed. + 0 +}; \ No newline at end of file diff --git a/src/test/kc/complex/nes-balls/lazydata.chr b/src/test/kc/complex/nes-balls/lazydata.chr new file mode 100644 index 000000000..2d4925915 Binary files /dev/null and b/src/test/kc/complex/nes-balls/lazydata.chr differ