/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007 Matthias Wientapper * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ /* * This is an implementatino of Conway's Game of Life * * from http://en.wikipedia.org/wiki/Conway's_Game_of_Life: * * Rules * * The universe of the Game of Life is an infinite two-dimensional * orthogonal grid of square cells, each of which is in one of two * possible states, live or dead. Every cell interacts with its eight * neighbours, which are the cells that are directly horizontally, * vertically, or diagonally adjacent. At each step in time, the * following transitions occur: * * 1. Any live cell with fewer than two live neighbours dies, as if by * loneliness. * * 2. Any live cell with more than three live neighbours dies, as if * by overcrowding. * * 3. Any live cell with two or three live neighbours lives, * unchanged, to the next generation. * * 4. Any dead cell with exactly three live neighbours comes to life. * * The initial pattern constitutes the first generation of the * system. The second generation is created by applying the above * rules simultaneously to every cell in the first generation -- * births and deaths happen simultaneously, and the discrete moment at * which this happens is sometimes called a tick. (In other words, * each generation is based entirely on the one before.) The rules * continue to be applied repeatedly to create further generations. * * * * TODO: * - nicer colours for pixels with respect to age * - editor for start patterns * - probably tons of speed-up opportunities */ #include "plugin.h" #include "lib/pluginlib_actions.h" #include "lib/helper.h" #define ROCKLIFE_PLAY_PAUSE PLA_SELECT #define ROCKLIFE_INIT PLA_DOWN #define ROCKLIFE_NEXT PLA_RIGHT #define ROCKLIFE_NEXT_REP PLA_RIGHT_REPEAT #define ROCKLIFE_QUIT PLA_CANCEL #define ROCKLIFE_STATUS PLA_LEFT #define PATTERN_RANDOM 0 #define PATTERN_GROWTH_1 1 #define PATTERN_GROWTH_2 2 #define PATTERN_ACORN 3 #define PATTERN_GLIDER_GUN 4 const struct button_mapping *plugin_contexts[] = {pla_main_ctx}; #define GRID_W LCD_WIDTH #define GRID_H LCD_HEIGHT unsigned char grid_a[GRID_W][GRID_H]; unsigned char grid_b[GRID_W][GRID_H]; int generation = 0; int population = 0; int status_line = 0; static inline bool is_valid_cell(int x, int y) { return (x >= 0 && x < GRID_W && y >= 0 && y < GRID_H); } static inline void set_cell_age(int x, int y, unsigned char age, char *pgrid) { pgrid[x+y*GRID_W] = age; } static inline void set_cell(int x, int y, char *pgrid) { set_cell_age(x, y, 1, pgrid); } static inline unsigned char get_cell(int x, int y, char *pgrid) { if (x < 0) x += GRID_W; else if (x >= GRID_W) x -= GRID_W; if (y < 0) y += GRID_H; else if (y >= GRID_H) y -= GRID_H; return pgrid[x+y*GRID_W]; } /* clear grid */ static void init_grid(char *pgrid){ memset(pgrid, 0, GRID_W * GRID_H); } /*fill grid with pattern from file (viewer mode)*/ static bool load_cellfile(const char *file, char *pgrid){ int fd; fd = rb->open(file, O_RDONLY); if (fd<0) return false; init_grid(pgrid); char c; int nc, x, y, xmid, ymid; bool comment; x=0; y=0; xmid = (GRID_W>>1) - 2; ymid = (GRID_H>>1) - 2; comment = false; while (true) { nc = rb->read(fd, &c, 1); if (nc <= 0) break; switch(c) { case '!': comment = true; case '.': if (!comment) x++; break; case 'O': if (!comment) { if (is_valid_cell(xmid + x, ymid + y)) set_cell(xmid + x, ymid + y, pgrid); x++; } break; case '\n': y++; x=0; comment = false; break; default: break; } } rb->close(fd); return true; } /* fill grid with initial pattern */ static void setup_grid(char *pgrid, int pattern){ int n, max; int xmid, ymid; max = GRID_W * GRID_H; switch(pattern){ case PATTERN_RANDOM: rb->splash(HZ, "Random"); #if 0 /* two oscilators, debug pattern */ set_cell( 0, 1 , pgrid); set_cell( 1, 1 , pgrid); set_cell( 2, 1 , pgrid); set_cell( 6, 7 , pgrid); set_cell( 7, 7 , pgrid); set_cell( 8, 7 , pgrid); #endif /* fill screen randomly */ for(n=0; n<(max>>2); n++) pgrid[rb->rand()%max] = 1; break; case PATTERN_GROWTH_1: rb->splash(HZ, "Growth"); xmid = (GRID_W>>1) - 2; ymid = (GRID_H>>1) - 2; set_cell(xmid + 6, ymid + 0 , pgrid); set_cell(xmid + 4, ymid + 1 , pgrid); set_cell(xmid + 6, ymid + 1 , pgrid); set_cell(xmid + 7, ymid + 1 , pgrid); set_cell(xmid + 4, ymid + 2 , pgrid); set_cell(xmid + 6, ymid + 2 , pgrid); set_cell(xmid + 4, ymid + 3 , pgrid); set_cell(xmid + 2, ymid + 4 , pgrid); set_cell(xmid + 0, ymid + 5 , pgrid); set_cell(xmid + 2, ymid + 5 , pgrid); break; case PATTERN_ACORN: rb->splash(HZ, "Acorn"); xmid = (GRID_W>>1) - 3; ymid = (GRID_H>>1) - 1; set_cell(xmid + 1, ymid + 0 , pgrid); set_cell(xmid + 3, ymid + 1 , pgrid); set_cell(xmid + 0, ymid + 2 , pgrid); set_cell(xmid + 1, ymid + 2 , pgrid); set_cell(xmid + 4, ymid + 2 , pgrid); set_cell(xmid + 5, ymid + 2 , pgrid); set_cell(xmid + 6, ymid + 2 , pgrid); break; case PATTERN_GROWTH_2: rb->splash(HZ, "Growth 2"); xmid = (GRID_W>>1) - 4; ymid = (GRID_H>>1) - 1; set_cell(xmid + 0, ymid + 0 , pgrid); set_cell(xmid + 1, ymid + 0 , pgrid); set_cell(xmid + 2, ymid + 0 , pgrid); set_cell(xmid + 4, ymid + 0 , pgrid); set_cell(xmid + 0, ymid + 1 , pgrid); set_cell(xmid + 3, ymid + 2 , pgrid); set_cell(xmid + 4, ymid + 2 , pgrid); set_cell(xmid + 1, ymid + 3 , pgrid); set_cell(xmid + 2, ymid + 3 , pgrid); set_cell(xmid + 4, ymid + 3 , pgrid); set_cell(xmid + 0, ymid + 4 , pgrid); set_cell(xmid + 2, ymid + 4 , pgrid); set_cell(xmid + 4, ymid + 4 , pgrid); break; case PATTERN_GLIDER_GUN: rb->splash(HZ, "Glider Gun"); set_cell( 24, 0, pgrid); set_cell( 22, 1, pgrid); set_cell( 24, 1, pgrid); set_cell( 12, 2, pgrid); set_cell( 13, 2, pgrid); set_cell( 20, 2, pgrid); set_cell( 21, 2, pgrid); set_cell( 34, 2, pgrid); set_cell( 35, 2, pgrid); set_cell( 11, 3, pgrid); set_cell( 15, 3, pgrid); set_cell( 20, 3, pgrid); set_cell( 21, 3, pgrid); set_cell( 34, 3, pgrid); set_cell( 35, 3, pgrid); set_cell( 0, 4, pgrid); set_cell( 1, 4, pgrid); set_cell( 10, 4, pgrid); set_cell( 16, 4, pgrid); set_cell( 20, 4, pgrid); set_cell( 21, 4, pgrid); set_cell( 0, 5, pgrid); set_cell( 1, 5, pgrid); set_cell( 10, 5, pgrid); set_cell( 14, 5, pgrid); set_cell( 16, 5, pgrid); set_cell( 17, 5, pgrid); set_cell( 22, 5, pgrid); set_cell( 24, 5, pgrid); set_cell( 10, 6, pgrid); set_cell( 16, 6, pgrid); set_cell( 24, 6, pgrid); set_cell( 11, 7, pgrid); set_cell( 15, 7, pgrid); set_cell( 12, 8, pgrid); set_cell( 13, 8, pgrid); break; } } /* display grid */ static void show_grid(char *pgrid){ int x, y; unsigned char age; rb->lcd_clear_display(); for(y=0; y= 16 rb->lcd_set_foreground( LCD_RGBPACK( age, age, age )); #elif LCD_DEPTH == 2 rb->lcd_set_foreground(age>>7); #endif rb->lcd_drawpixel(x, y); } } } if(status_line){ #if LCD_DEPTH > 1 rb->lcd_set_foreground( LCD_BLACK ); #endif rb->lcd_putsf(0, 0, "g:%d p:%d", generation, population); } rb->lcd_update(); } /* Calculates whether the cell will be alive in the next generation. n is the array with 9 elements that represent the cell itself and its neighborhood like this (the cell itself is n[4]): 0 1 2 3 4 5 6 7 8 */ static inline bool check_cell(unsigned char *n) { int empty_cells = 0; int alive_cells; bool result; /* count empty neighbour cells */ if(n[0]==0) empty_cells++; if(n[1]==0) empty_cells++; if(n[2]==0) empty_cells++; if(n[3]==0) empty_cells++; if(n[5]==0) empty_cells++; if(n[6]==0) empty_cells++; if(n[7]==0) empty_cells++; if(n[8]==0) empty_cells++; /* now we build the number of non-zero neighbours :-P */ alive_cells = 8 - empty_cells; if (n[4]) { /* If the cell is alive, it stays alive iff it has 2 or 3 alive neighbours */ result = (alive_cells==2 || alive_cells==3); } else { /* If the cell is dead, it gets alive iff it has 3 alive neighbours */ result = (alive_cells==3); } return result; } /* Calculate the next generation of cells * * The borders of the grid are connected to their opposite sides. * * To avoid multiplications while accessing data in the 2-d grid * (pgrid) we try to re-use previously accessed neighbourhood * information which is stored in an 3x3 array. */ static void next_generation(char *pgrid, char *pnext_grid){ int x, y; bool cell; unsigned char age; unsigned char n[9]; rb->memset(n, 0, sizeof(n)); /* * cell is (4) with 8 neighbours * * 0|1|2 * ----- * 3|4|5 * ----- * 6|7|8 */ population = 0; /* go through the grid */ for(y=0; y 1 rb->lcd_set_backdrop(NULL); #ifdef HAVE_LCD_COLOR rb->lcd_set_background(LCD_RGBPACK(182, 198, 229)); /* rockbox blue */ #else rb->lcd_set_background(LCD_DEFAULT_BG); #endif /* HAVE_LCD_COLOR */ #endif /* LCD_DEPTH > 1 */ /* link pointers to grids */ pgrid = (char *)grid_a; pnext_grid = (char *)grid_b; init_grid(pgrid); if( parameter == NULL ) { setup_grid(pgrid, pattern++); } else { if( load_cellfile(parameter, pgrid) ) { rb->splashf( 1*HZ, "Cells loaded (%s)", (char *)parameter ); } else { rb->splash( 1*HZ, "File Open Error"); setup_grid(pgrid, pattern++); /* fall back to stored patterns */ } } show_grid(pgrid); while(!quit) { button = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); switch(button) { case ROCKLIFE_NEXT: case ROCKLIFE_NEXT_REP: /* calculate next generation */ next_generation(pgrid, pnext_grid); /* swap buffers, grid is the new generation */ ptemp = pgrid; pgrid = pnext_grid; pnext_grid = ptemp; /* show new generation */ show_grid(pgrid); break; case ROCKLIFE_PLAY_PAUSE: stop = 0; while(!stop){ /* calculate next generation */ next_generation(pgrid, pnext_grid); /* swap buffers, grid is the new generation */ ptemp = pgrid; pgrid = pnext_grid; pnext_grid = ptemp; /* show new generation */ rb->yield(); show_grid(pgrid); button = pluginlib_getaction(0, plugin_contexts, ARRAYLEN(plugin_contexts)); switch(button) { case ROCKLIFE_PLAY_PAUSE: case ROCKLIFE_QUIT: stop = 1; break; default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) { stop = 1; quit = 1; usb = 1; } break; } rb->yield(); } break; case ROCKLIFE_INIT: init_grid(pgrid); setup_grid(pgrid, pattern); show_grid(pgrid); pattern++; pattern%=5; break; case ROCKLIFE_STATUS: status_line = !status_line; show_grid(pgrid); break; case ROCKLIFE_QUIT: /* quit plugin */ quit = 1; break; default: if (rb->default_event_handler(button) == SYS_USB_CONNECTED) { quit = 1; usb = 1; } break; } rb->yield(); } backlight_use_settings(); return usb? PLUGIN_USB_CONNECTED: PLUGIN_OK; }