//****************************************************************************** // // Life.c // // Conway's Game of Life for Game Boy advance. // // By Andrew H. Cox. // http://www.btinternet.com/~ahcox/index.html // // Contact: ahcox@btinternet.com or andy4294967296@hotmail.com // // Macros for putting data in fast memory: #define EXTERN_VAR_IN_FASTEST_DEC(type, name) \ extern type name __attribute__((section(".iwram"))); #define EXTERN_VAR_IN_FASTEST_DEF(type, name, value) \ type name __attribute__((section(".iwram"))) = value; #define STATIC_VAR_IN_FASTEST_DEF(type, name, value) \ static type name __attribute__((section(".iwram"))) = value; #define STATIC_VAR_IN_FASTEST(type, name) \ static type name __attribute__((section(".iwram"))); #define STATIC_VAR_IN_MIDDLE(type, name) \ static type name __attribute__((section(".ewram"))); //****************************************************************************** // Macros for declaring and defining functions that specify attributes of the // functions such as what type of code to generate and from which memory pool to // run them: // ARM code in IWRAM and alters no global variables: // Macro for declaring the function: #define EXTERN_FAST_CODE_FAST_MEM_NO_SE_DEC(ret, func, parms) \ extern ret func##_func parms __attribute__((section(".iwram"))) __attribute__((const)); \ extern ret (*func) parms __attribute__((section(".iwram"))); // Macro for defining the function: // __attribute__((section(".iwram"))) part is not needed? [FixMe] #define EXTERN_FAST_CODE_FAST_MEM_NO_SE_DEF(ret, func, parms) \ ret (*func) parms __attribute__((section(".iwram"))) = func##_func; \ ret func##_func parms // Macro for declaring and defining a static function: #define STATIC_FAST_CODE_FAST_MEM_NO_SE_DEF(ret, func, parms) \ static ret func##_func parms __attribute__((section(".iwram"))) __attribute__((const));\ static ret (*func) parms __attribute__((section(".iwram"))) = func##_func; \ ret func##_func parms // Macro like above but which avoids wasting 4 bytes in IWRAM for a func pointer: #define STATIC_FAST_CODE_FAST_MEM_NO_SE_SHORT_CALL_ONLY_DEF(ret, func, parms) \ static ret func parms __attribute__((section(".iwram"))) __attribute__((const)); \ ret func parms #define ALWAYS_INLINE(ret, func, parms) static inline ret func parms // Platform-spefic data types: typedef unsigned char Byte; typedef unsigned SizeType; typedef unsigned char w8_t; typedef char i8_t; typedef unsigned short w16_t; typedef short int i16_t; typedef unsigned w32_t; typedef int i32_t; //****************************************************************************** // GBA hardware macros: #define REG_BASE_ADDRESS 0x4000000 #define REG_DISPLAY_CONTROL (*(w16_t *)0x4000000) #define SET_REG_DISPLAY_CONTROL(value) (REG_DISPLAY_CONTROL = value) #define REG_DISPLAY_CONTROL_MODE_MASK (~7u) #define REG_DISPLAY_CONTROL_BUFFER_BIT (16u) #define REG_DISPLAY_CONTROL_BUFFER_MASK (~REG_DISPLAY_CONTROL_BUFFER_BIT) #define REG_DISPLAY_CONTROL_WHITE_SCREEN_MASK (~0x80u) #define SCREEN_BUFFER_1 ((Byte*) 0x6000000) #define SCREEN_BUFFER_2 ((Byte*) 0x600A000) #define SCREEN_PALETTE ((w16_t*) 0x5000000) #define REG_KEY_STATUS (*(w16_t *)(REG_BASE_ADDRESS + 0x130)) // Timer registers: #define REG_TIMER0_CONTROL (*(w16_t *)(REG_BASE_ADDRESS + 0x102)) #define REG_TIMER1_CONTROL (*(w16_t *)(REG_BASE_ADDRESS + 0x106)) #define REG_TIMER2_CONTROL (*(w16_t *)(REG_BASE_ADDRESS + 0x10A)) #define REG_TIMER3_CONTROL (*(w16_t *)(REG_BASE_ADDRESS + 0x10E)) #define REG_TIMER_CONTROL_CASCADE_BIT (1u << 2u) #define REG_TIMER_CONTROL_IRQ_BIT (1u << 6u) #define REG_TIMER_CONTROL_ENABLE_BIT (1u << 7u) #define REG_TIMER0_DATA (*(w16_t *)(REG_BASE_ADDRESS + 0x100)) #define REG_TIMER1_DATA (*(w16_t *)(REG_BASE_ADDRESS + 0x104)) #define REG_TIMER2_DATA (*(w16_t *)(REG_BASE_ADDRESS + 0x108)) #define REG_TIMER3_DATA (*(w16_t *)(REG_BASE_ADDRESS + 0x10C)) //****************************************************************************** // framebuffers STATIC_VAR_IN_FASTEST_DEF(unsigned char*, g_back_buffer, SCREEN_BUFFER_2); STATIC_VAR_IN_FASTEST_DEF(unsigned char*, g_front_buffer, SCREEN_BUFFER_1); //****************************************************************************** // Display global variables: (on some platforms they may be #defines) #define g_screen_width 240 #define g_screen_height 160 // For address calculations when there are non-displayed bytes off the end of // each scanline: #define g_bytes_per_scanline g_screen_width //****************************************************************************** // init() static void init(void){ REG_DISPLAY_CONTROL = 0u; // Set display mode: REG_DISPLAY_CONTROL &= REG_DISPLAY_CONTROL_MODE_MASK; REG_DISPLAY_CONTROL |= 4u; // Make first buffer the frontbuffer and the second our backbuffer: REG_DISPLAY_CONTROL &= REG_DISPLAY_CONTROL_BUFFER_MASK; g_back_buffer = SCREEN_BUFFER_2; // Turn Display on: REG_DISPLAY_CONTROL &= REG_DISPLAY_CONTROL_WHITE_SCREEN_MASK; // Turn on background 2 only: REG_DISPLAY_CONTROL |= (1u << 10u);// | (1u << 9u) | (1u << 10u) | (1u << 11u)); // Turn all backgrounds on: //REG_DISPLAY_CONTROL |= ((1u << 8u) | (1u << 9u) | (1u << 10u) | (1u << 11u)); { w16_t *dest = SCREEN_PALETTE; unsigned entry = 0; // Black on white palette: dest = SCREEN_PALETTE; *dest++ = 0xffffu;//0u; for (entry = 1; entry < 256; ++entry){ *dest++ = 0u;//0xffffu; } } } //****************************************************************************** #define QUAD_ADDRESS_MASK (((SizeType)-1) << 2u) //****************************************************************************** ALWAYS_INLINE(w32_t*, address_quad, (Byte* framebuffer, unsigned x, unsigned y)){ return (w32_t*) ((SizeType)framebuffer + y * g_bytes_per_scanline + x & 0xfffffffc); } //****************************************************************************** ALWAYS_INLINE(w32_t*, address_quad_containing_byte, (Byte* byte)){ return (w32_t*) ((SizeType)byte & QUAD_ADDRESS_MASK); } //****************************************************************************** ALWAYS_INLINE(void, modify_pixel,(w32_t* address, w32_t x, w32_t colour_index)) { w32_t pixel_quad = *address; w32_t pixel_shift = (x & 3u) << 3; pixel_quad &= ~(0xff << pixel_shift); pixel_quad |= colour_index << pixel_shift; *address = pixel_quad; } //****************************************************************************** // extract_pixel() ALWAYS_INLINE(unsigned, extract_pixel,(w32_t* address, w32_t x)) { w32_t pixel_quad = *address; w32_t pixel_shift = (x & 3u) << 3; pixel_quad >>= pixel_shift; pixel_quad &= 0xff; return pixel_quad; } //****************************************************************************** // inline_plot_pixel() ALWAYS_INLINE(void, inline_plot_pixel, (Byte* framebuffer, unsigned x, unsigned y, w32_t colour_index)){ // FixMe: Mask the colour index or what? // if( x < g_screen_width && y < g_screen_height){ w32_t* address = address_quad(framebuffer, x, y); modify_pixel(address, x, colour_index); /* } else if (1) { // Debug Warning, spray the pallete down the right screen edge: unsigned y_; for(y_ = 0; y_ < g_screen_height; ++y_){ w32_t* address = address_quad(framebuffer, g_screen_width - 1, y_); modify_pixel(address, g_screen_width - 1, colour_index); } } */ } //****************************************************************************** // inline_plot_pixel_n() // Viewing a framebuffer as a linear array of memory from one corner to the // oposite, set the colour of the Nth pixel in that sequence. ALWAYS_INLINE(void, inline_plot_pixel_n, (Byte* framebuffer, unsigned n, w32_t colour_index)){ // FixMe: Mask the colour index or what? w32_t* address = address_quad_containing_byte(framebuffer + n); modify_pixel(address, (w32_t)framebuffer + n, colour_index); } //****************************************************************************** // inline_read_pixel() ALWAYS_INLINE(unsigned, inline_read_pixel, (Byte* framebuffer, unsigned x, unsigned y)){ w32_t* address = address_quad(framebuffer, x, y); return extract_pixel(address, x); } //****************************************************************************** // The Grid: #define g_grid_width g_screen_width #define g_grid_height g_screen_height // ! Have to put these explicitly into EWRAM or compiler/linker shoves them // into IWRAM: STATIC_VAR_IN_MIDDLE(w8_t, g_grid[g_grid_width*g_grid_height]); STATIC_VAR_IN_MIDDLE(w8_t, g_old_grid[g_grid_width*g_grid_height]); // If grid were well under 32kb one could go in IWRAM: // STATIC_VAR_IN_FASTEST(w8_t, g_old_grid[g_grid_width*g_grid_height]); //****************************************************************************** // grid() // Returns the address of the cell at the specified coordinates. ALWAYS_INLINE(w8_t*, grid,(unsigned x, unsigned y)){ return &(g_grid[y * g_grid_width + x]); } //****************************************************************************** // clear_grid() //STATIC_FAST_CODE_FAST_MEM_NO_SE_DEF(void, clear_grid, (void)){ static void clear_grid (void){ unsigned i = (g_grid_width*g_grid_height) >> 2u; w32_t* quad = (w32_t*) g_grid; do{ *quad++ = 0u; }while(--i); } //****************************************************************************** // backup_grid() // Makes a copy of the grid in it's current state. // Opt: // > Assembly copy, hardwired to grid size and using read/write multiple DWORDS // instructions. STATIC_FAST_CODE_FAST_MEM_NO_SE_DEF(void, backup_grid, (void)){ // memcpy is not in IWRAM, test if it's faster than C from IWRAM. // memcpy(g_old_grid, g_grid, g_grid_width*g_grid_height); unsigned i = (g_grid_width*g_grid_height) >> 2u; w32_t* in_quad = (w32_t*) g_grid; w32_t* out_quad = (w32_t*) g_old_grid; do{ *out_quad++ = *in_quad++; }while(--i); } //****************************************************************************** // read_cell() // returns full value of cell, including the packed neighbour count. ALWAYS_INLINE(unsigned, read_cell,(unsigned x, unsigned y)){ return *(grid(x,y)); } //****************************************************************************** // read_cell_state() // returns 0 or 1 for unset/set. ALWAYS_INLINE(unsigned, read_cell_state,(unsigned x, unsigned y)){ return read_cell(x, y) & 1u; } //****************************************************************************** // clear_cell() // Assumes cell realy is set. ALWAYS_INLINE(void, clear_cell,(unsigned x, unsigned y)){ w8_t* cell = grid(x, y); // Turn off bit that stores set/clear state: *cell &= 0xfe; // Decrease counts of eight neighbouring cells: { const unsigned w = g_grid_width, h = g_grid_height; int left, right, up, down; if(x > 0){ left = -1; } else { left = w - 1; } if(x < w-1){ right = 1; } else { right = -(w - 1); } if(y > 0){ up = -w; } else { up = w * (h - 1); } if(y < h-1){ down = w; } else { down = -(w * (h - 1)); } *(cell + up + left) -= 2u; *(cell + up) -= 2u; *(cell + up + right) -= 2u; *(cell + left) -= 2u; *(cell + right) -= 2u; *(cell + down + left) -= 2u; *(cell + down) -= 2u; *(cell + down + right) -= 2u; } } //****************************************************************************** // set_cell() // Assumes cell realy is not set. ALWAYS_INLINE(void, set_cell,(unsigned x, unsigned y)){ w8_t* cell = grid(x, y); // Turn on bit that stores set/clear state: *cell |= 1u; // Increase counts of eight neighbouring cells: { const unsigned w = g_grid_width, h = g_grid_height; int left, right, up, down; if(x > 0){ left = -1; } else { left = w - 1; } if(x < w-1){ right = 1; } else { right = -(w - 1); } if(y > 0){ up = -w; } else { up = w * (h - 1); } if(y < h-1){ down = w; } else { down = -(w * (h - 1)); } *(cell + up + left) += 2u; *(cell + up) += 2u; *(cell + up + right) += 2u; *(cell + left) += 2u; *(cell + right) += 2u; *(cell + down + left) += 2u; *(cell + down) += 2u; *(cell + down + right) += 2u; } } //****************************************************************************** // set_cell_if_not_set() // Doesn't assume cell isnt already set. ALWAYS_INLINE(void, set_cell_if_not_set,(unsigned x, unsigned y)){ w8_t* cell = grid(x, y); if((*cell & 1u) == 0u){ set_cell(x, y); } } //****************************************************************************** // draw_grid() // Any time the framebuffer gets out of sync with the grid, this func can be // used to refill it. // cell_on and cell_off are the colours to write. STATIC_FAST_CODE_FAST_MEM_NO_SE_DEF(void, draw_grid, (w8_t* framebuffer, unsigned cell_on, unsigned cell_off)){ unsigned x, y; for(y = 0; y < g_grid_height; ++y){ for(x = 0; x < g_grid_width; ++x){ if(read_cell_state(x, y) == 0u) { inline_plot_pixel(framebuffer, x, y, cell_off); } else { inline_plot_pixel(framebuffer, x, y, cell_on); } } } } //****************************************************************************** // draw_zero_cells_of_grid() // Draws the cells that are off and are surrounded by other off cells.. // cell_not_zero and cell_zero are the colours to write. STATIC_FAST_CODE_FAST_MEM_NO_SE_DEF(void, draw_zero_cells_of_grid, (w8_t* framebuffer, unsigned cell_not_zero, unsigned cell_zero)){ unsigned x, y; for(y = 0; y < g_grid_height; ++y){ for(x = 0; x < g_grid_width; ++x){ if(read_cell(x, y) == 0u) { inline_plot_pixel(framebuffer, x, y, cell_zero); } else { inline_plot_pixel(framebuffer, x, y, cell_not_zero); } } } } //****************************************************************************** // update_grid() // cell_on and cell_off are the colours to write. STATIC_FAST_CODE_FAST_MEM_NO_SE_DEF(void, update_grid, (w8_t* framebuffer, unsigned cell_on, unsigned cell_off)){ const unsigned w = g_grid_width, h = g_grid_height; unsigned y; w8_t* cell; backup_grid(); cell = g_old_grid; for(y = 0; y < h; ++y){ unsigned x; for(x = 0; x < w; ++x, ++cell){ if(*cell == 0){ continue; } // Decide whether this cell should be modified: { unsigned count = *cell >> 1u; if(*cell & 1u){ // Cell is on so turn it off if it has the wrong number of neighbours: if(count < 2u || count > 3u){ clear_cell(x, y); inline_plot_pixel(framebuffer, x, y, cell_off); } } else { // Cell is off so turn it on if it has the right number of neighbours: if(count == 3u){ set_cell(x, y); inline_plot_pixel(framebuffer, x, y, cell_on); } } } } } } //****************************************************************************** STATIC_VAR_IN_FASTEST_DEF(w32_t, g_random_w32_t_seed, 0); #define g_random_w32_t_max (2147483648u); //****************************************************************************** // random_w32_t() ALWAYS_INLINE(w32_t, random_w32_t,(void)){ return g_random_w32_t_seed = g_random_w32_t_seed * 1103515245 + 12345; } //****************************************************************************** // seed_random_w32_t() ALWAYS_INLINE(void, seed_random_w32_t,(w32_t seed)){ g_random_w32_t_seed = seed; } //****************************************************************************** // random_w32_t_in_range() ALWAYS_INLINE(w32_t, random_w32_t_in_range,(w32_t max)){ return random_w32_t() % max; } //****************************************************************************** // seed_grid() // Uniform random distribution of on cells in grid. // density_percent in range 0 to 100. static void seed_grid(unsigned density_percent){ unsigned num_cells_in_grid = g_grid_width * g_grid_height; unsigned num_cells = (num_cells_in_grid * density_percent) / 100u; unsigned cell_i; for (cell_i = 0; cell_i < num_cells; ++cell_i){ w32_t x, y; w32_t ran_in_range = random_w32_t_in_range(num_cells_in_grid); x = ran_in_range % g_grid_width; y = ran_in_range / g_grid_width; set_cell_if_not_set(x, y); } } //****************************************************************************** // get_key_state() static unsigned get_key_state(void){ return ~REG_KEY_STATUS; } typedef enum {KeyA, KeyB, KeySelect, KeyStart, KeyRight, KeyLeft, KeyUp, KeyDown, KeyShoulderRight, KeyShoulderLeft, KeyEnd, KeySentry = KeyEnd} Key; //****************************************************************************** // main() extern int main(){ static unsigned counter = 0; static w16_t old_key_state = 0; init(); inline_plot_pixel(g_front_buffer, 0, 1, 1); clear_grid(); inline_plot_pixel(g_front_buffer, 3, 1, 1); seed_grid(12); inline_plot_pixel(g_front_buffer, 6, 1, 1); draw_grid(g_front_buffer, 1, 0); draw_grid(g_back_buffer, 1, 0); while(1){ static unsigned start_down_counter; w16_t key_state = get_key_state(); if((key_state & (1<> 1); draw_grid(g_front_buffer, 1, 0); } update_grid(g_front_buffer, 1, 0); // Make a button control this: //draw_zero_cells_of_grid(g_front_buffer, 1, 0); ++counter; old_key_state = key_state; } return 1; }