#include #include #include #include #include // Screen and color space #define Screen ((byte *)0x0400) #define Color ((byte *)0xd800) // Macro for easy access to screen and color space #define sline(x, y) (Screen + 40 * (y) + (x)) #define cline(x, y) (Color + 40 * (y) + (x)) // Tile data, each column has four rows of four tiles static const char quad[4][4 * 4] = { { 0x20, 0x55, 0x6c, 0x4e, 0x20, 0x5d, 0xe1, 0x65, 0x20, 0x5d, 0xe1, 0x65, 0x20, 0x4a, 0x7c, 0x4d, }, { 0x20, 0x40, 0x62, 0x77, 0x20, 0x20, 0xa0, 0x20, 0x20, 0x20, 0xa0, 0x20, 0x20, 0x40, 0xe2, 0x6f, }, { 0x20, 0x40, 0x62, 0x77, 0x20, 0x20, 0xa0, 0x20, 0x20, 0x20, 0xa0, 0x20, 0x20, 0x40, 0xe2, 0x6f, }, { 0x20, 0x49, 0x7b, 0x4d, 0x20, 0x5d, 0x61, 0x6a, 0x20, 0x5d, 0x61, 0x6a, 0x20, 0x4b, 0x7e, 0x4e, } }; #pragma align(quad, 256) // Expand a single row into an offscreen buffer void expandrow(char * dp, char * cp, const char * grid, char ly, char lx) { char hx = 0; for(char x=0; x<40; x++) { dp[x] = quad[lx][ly + grid[hx]]; cp[x] = grid[hx]; lx++; if (lx == 4) { lx = 0; hx++; } } } // Expand a single column into an offscreen buffer void expandcol(char * dp, char * cp, const char * grid, char ly, char lx) { for(char y=0; y<25; y++) { dp[y] = quad[lx][ly + grid[0]]; cp[y] = grid[0]; ly += 4; if (ly == 16) { grid += 16; ly = 0; } } } // Two split scroll for left, right and up #define VSPLIT 12 // Three split scroll for down. Downscrolling is more tricky because // we have to copy towards the raster #define VSPLIT 12 #define VSPLIT2 20 // New line/column of screen and color data char news[40], newc[40]; // All scroll routines start with a pixel offset of 4, 4 // Scroll one character left in two pixel increments void scroll_left(void) { // Wait for one frame vic_waitTop(); vic_waitBottom(); // Switch to offset 2, 4 vic.ctrl2 = 0x02; vic_waitTop(); vic_waitBottom(); // Switch to offset 0, 4 vic.ctrl2 = 0x00; // Wait until bottom of section vic_waitLine(50 + 8 * VSPLIT); // Scroll upper section for(char x=0; x<39; x++) { #assign ty 0 #repeat sline(0, ty)[x] = sline(1, ty)[x]; cline(0, ty)[x] = cline(1, ty)[x]; #assign ty ty + 1 #until ty == VSPLIT } // Update column #assign ty 0 #repeat sline(0, ty)[39] = news[ty]; cline(0, ty)[39] = newc[ty]; #assign ty ty + 1 #until ty == VSPLIT // Wait for bottom of visible screen vic_waitBottom(); // Switch to offset 6, 4 vic.ctrl2 = 0x06; // Scroll lower part of the screen, while top is redrawn for(char x=0; x<39; x++) { #assign ty VSPLIT #repeat sline(0, ty)[x] = sline(1, ty)[x]; cline(0, ty)[x] = cline(1, ty)[x]; #assign ty ty + 1 #until ty == 25 } // Update new column #assign ty VSPLIT #repeat sline(0, ty)[39] = news[ty]; cline(0, ty)[39] = newc[ty]; #assign ty ty + 1 #until ty == 25 // Wait for bottom vic_waitBottom(); // Now back to 4, 4 vic.ctrl2 = 0x04; } // Scroll one character right in two pixel increments void scroll_right(void) { vic_waitTop(); vic_waitBottom(); vic.ctrl2 = 0x06; vic_waitLine(50 + 8 * VSPLIT); for(char x=39; x>0; x--) { #assign ty 0 #repeat sline(0, ty)[x] = sline(-1, ty)[x]; cline(0, ty)[x] = cline(-1, ty)[x]; #assign ty ty + 1 #until ty == VSPLIT } #assign ty 0 #repeat sline(0, ty)[0] = news[ty]; cline(0, ty)[0] = newc[ty]; #assign ty ty + 1 #until ty == VSPLIT vic_waitBottom(); vic.ctrl2 = 0x00; for(char x=39; x>0; x--) { #assign ty VSPLIT #repeat sline(0, ty)[x] = sline(-1, ty)[x]; cline(0, ty)[x] = cline(-1, ty)[x]; #assign ty ty + 1 #until ty == 25 } #assign ty VSPLIT #repeat sline(0, ty)[0] = news[ty]; cline(0, ty)[0] = newc[ty]; #assign ty ty + 1 #until ty == 25 vic_waitBottom(); vic.ctrl2 = 0x02; vic_waitTop(); vic_waitBottom(); vic.ctrl2 = 0x04; } // Scroll one character up in two pixel increments void scroll_up(void) { vic_waitTop(); vic_waitBottom(); vic.ctrl1 = 0x02 | VIC_CTRL1_DEN; vic_waitTop(); vic_waitBottom(); vic.ctrl1 = 0x00 | VIC_CTRL1_DEN; vic_waitLine(50 + 8 * VSPLIT); for(char x=0; x<40; x++) { #assign ty 0 #repeat sline(0, ty)[x] = sline(0, ty + 1)[x]; cline(0, ty)[x] = cline(0, ty + 1)[x]; #assign ty ty + 1 #until ty == VSPLIT } vic_waitBottom(); vic.ctrl1 = 0x06 | VIC_CTRL1_DEN; for(char x=0; x<40; x++) { #assign ty VSPLIT #repeat sline(0, ty)[x] = sline(0, ty + 1)[x]; cline(0, ty)[x] = cline(0, ty + 1)[x]; #assign ty ty + 1 #until ty == 24 sline(0, ty)[x] = news[x]; cline(0, ty)[x] = newc[x]; } vic_waitBottom(); vic.ctrl1 = 0x04 | VIC_CTRL1_DEN; } char tmp0[40], tmp1[40], tmp2[40], tmp3[40]; // Scroll one character down in two pixel increments. This is more tricky than // the other three cases, because we have to work towards the beam because // we have to copy backwards in memory. // // The scroll is split into three sections, the seam rows are saved into // intermediate arrays, so we can copy the top section first and the bottom // section last, and stay ahead of the beam. void scroll_down(void) { // Wait one frame vic_waitTop(); vic_waitBottom(); // Save seam lines for(char x=0; x<40; x++) { tmp0[x] = sline(0, VSPLIT)[x]; tmp1[x] = cline(0, VSPLIT)[x]; tmp2[x] = sline(0, VSPLIT2)[x]; tmp3[x] = cline(0, VSPLIT2)[x]; } // Now switch to 4, 6 vic.ctrl1 = 0x06 | VIC_CTRL1_DEN; // Wait for bottom of top section vic_waitLine(58 + 8 * VSPLIT); // Scroll top section down and copy new column for(char x=0; x<40; x++) { #assign ty VSPLIT #repeat sline(0, ty)[x] = sline(0, ty - 1)[x]; cline(0, ty)[x] = cline(0, ty - 1)[x]; #assign ty ty - 1 #until ty == 0 sline(0, ty)[x] = news[x]; cline(0, ty)[x] = newc[x]; } // vic_waitBottom(); // We have already reached the bottom, switch to 4, 0 vic.ctrl1 = 0x00 | VIC_CTRL1_DEN; // Copy the second section, update the seam line from the buffer for(char x=0; x<40; x++) { #assign ty VSPLIT2 #repeat sline(0, ty)[x] = sline(0, ty - 1)[x]; cline(0, ty)[x] = cline(0, ty - 1)[x]; #assign ty ty - 1 #until ty == VSPLIT + 1 sline(0, ty)[x] = tmp0[x]; cline(0, ty)[x] = tmp1[x]; } // Copy the third section, update the seam line from the buffer for(char x=0; x<40; x++) { #assign ty 24 #repeat sline(0, ty)[x] = sline(0, ty - 1)[x]; cline(0, ty)[x] = cline(0, ty - 1)[x]; #assign ty ty - 1 #until ty == VSPLIT2 + 1 sline(0, ty)[x] = tmp2[x]; cline(0, ty)[x] = tmp3[x]; } // Switch to 4, 2 vic_waitBottom(); vic.ctrl1 = 0x02 | VIC_CTRL1_DEN; // Switch to 4, 4 vic_waitTop(); vic_waitBottom(); vic.ctrl1 = 0x04 | VIC_CTRL1_DEN; } char grid[16][16]; #pragma align(grid, 256) int main(void) { // We need some more accurate timing for this, so kill the kernal IRQ __asm { sei } // Init the grid for(char y=0; y<16; y++) { for(char x=0; x<16; x++) { grid[y][x] = rand() & 3; } } char gridX = 0, gridY = 0; // Initial drawing of the screen char * dp = Screen, * cp = Color; for(char y=0; y<25; y++) { expandrow(dp, cp, &(grid[y >> 2][0]), 4 * (y & 3), 0); dp += 40; cp += 40; } // setup initial scroll offset vic.ctrl1 = 0x04 | VIC_CTRL1_DEN; vic.ctrl2 = 0x04; for(;;) { // Check the joystick joy_poll(0); if (joyx[0] == 1) { // Move to the right if (gridX < 24) { gridX++; expandcol(news, newc, &(grid[gridY >> 2][(gridX + 39) >> 2]), 4 * (gridY & 3), (gridX + 39) & 3); scroll_left(); } } else if (joyx[0] == -1) { // Move to the left if (gridX > 0) { gridX--; expandcol(news, newc, &(grid[gridY >> 2][gridX >> 2]), 4 * (gridY & 3), gridX & 3); scroll_right(); } } else if (joyy[0] == 1) { // Move down if (gridY < 39) { gridY++; expandrow(news, newc, &(grid[(gridY + 24) >> 2][gridX >> 2]), 4 * ((gridY + 24) & 3), gridX & 3); scroll_up(); } } else if (joyy[0] == -1) { // Move up if (gridY > 0) { gridY--; expandrow(news, newc, &(grid[gridY >> 2][gridX >> 2]), 4 * (gridY & 3), gridX & 3); scroll_down(); } } } return 0; }