rockbox/apps/plugins/rocklife.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

496 lines
16 KiB
C

/***************************************************************************
* __________ __ ___.
* 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 "pluginlib_actions.h"
#include "helper.h"
PLUGIN_HEADER
#define ROCKLIFE_PLAY_PAUSE PLA_FIRE
#define ROCKLIFE_INIT PLA_DOWN
#define ROCKLIFE_NEXT PLA_RIGHT
#define ROCKLIFE_NEXT_REP PLA_RIGHT_REPEAT
#define ROCKLIFE_QUIT PLA_QUIT
#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 /* not yet implemented */
static const struct plugin_api* rb;
const struct button_mapping *plugin_contexts[]
= {generic_directions, generic_actions};
unsigned char grid_a[LCD_WIDTH][LCD_HEIGHT];
unsigned char grid_b[LCD_WIDTH][LCD_HEIGHT];
int generation = 0;
int population = 0;
int status_line = 0;
char buf[30];
static inline void set_cell(int x, int y, char *pgrid){
pgrid[x+y*LCD_WIDTH]=1;
}
/* clear grid */
void init_grid(char *pgrid){
int x, y;
for(y=0; y<LCD_HEIGHT; y++){
for(x=0; x<LCD_WIDTH; x++){
pgrid[x+y*LCD_WIDTH] = 0;
}
}
}
/* fill grid with initial pattern */
static void setup_grid(char *pgrid, int pattern){
int n, max;
int xmid, ymid;
max = LCD_HEIGHT*LCD_WIDTH;
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 = (LCD_WIDTH>>1) - 2;
ymid = (LCD_HEIGHT>>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 = (LCD_WIDTH>>1) - 3;
ymid = (LCD_HEIGHT>>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 = (LCD_WIDTH>>1) - 4;
ymid = (LCD_HEIGHT>>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;
int m;
unsigned char age;
rb->lcd_clear_display();
for(y=0; y<LCD_HEIGHT; y++){
for(x=0; x<LCD_WIDTH; x++){
m = y*LCD_WIDTH+x;
age = pgrid[m];
if(age){
#if LCD_DEPTH >= 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){
rb->snprintf(buf, sizeof(buf), "g:%d p:%d", generation, population);
#if LCD_DEPTH > 1
rb->lcd_set_foreground( LCD_BLACK );
#endif
rb->lcd_puts(0, 0, buf);
}
rb->lcd_update();
}
/* check state of cell depending on the number of neighbours */
static inline int check_cell(unsigned char *n){
int sum;
int empty_cells = 0;
unsigned char live = 0;
/* 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 */
sum = 8 - empty_cells;
/* 1st and 2nd rule*/
if (n[4] && (sum<2 || sum>3))
live = false;
/* 3rd rule */
if (n[4] && (sum==2 || sum==3))
live = true;
/* 4rd rule */
if (!n[4] && sum==3)
live = true;
return live;
}
/* 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;
unsigned char cell;
int age;
int m;
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<LCD_HEIGHT; y++){
for(x=0; x<LCD_WIDTH; x++){
if(y==0 && x==0){
/* first cell in first row, we have to load all neighbours */
n[0] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
n[1] = pgrid[((x )%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
n[2] = pgrid[((x +1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
n[3] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
n[5] = pgrid[((x +1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
n[6] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
n[7] = pgrid[((x )%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
n[8] = pgrid[((x +1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
} else {
if(x==0){
/* beginning of a row, copy what we know about our predecessor,
0, 1, 3 are known, 2, 5, 6, 7, 8 have to be loaded
*/
n[0] = n[4];
n[1] = n[5];
n[2] = pgrid[((x +1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
n[3] = n[7];
n[5] = pgrid[((x +1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
n[6] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
n[7] = pgrid[((x )%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
n[8] = pgrid[((x +1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
} else {
/* we are moving right in a row,
* copy what we know about the neighbours on our left side,
* 2, 5, 8 have to be loaded
*/
n[0] = n[1];
n[1] = n[2];
n[2] = pgrid[((x +1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
n[3] = n[4];
n[5] = pgrid[((x +1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
n[6] = n[7];
n[7] = n[8];
n[8] = pgrid[((x +1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
}
}
m = x+y*LCD_WIDTH;
/* how old is our cell? */
n[4] = pgrid[m];
age = n[4];
/* calculate the cell based on given neighbour information */
cell = check_cell(n);
/* is the actual cell alive? */
if(cell){
population++;
/* prevent overflow */
if(age>252){
pnext_grid[m] = 252;
} else {
pnext_grid[m] = age + 1;
}
}
else
pnext_grid[m] = 0;
#if 0
DEBUGF("x=%d,y=%d\n", x, y);
DEBUGF("cell: %d\n", cell);
DEBUGF("%d %d %d\n", n[0],n[1],n[2]);
DEBUGF("%d %d %d\n", n[3],n[4],n[5]);
DEBUGF("%d %d %d\n", n[6],n[7],n[8]);
DEBUGF("----------------\n");
#endif
}
}
generation++;
}
/**********************************/
/* this is the plugin entry point */
/**********************************/
enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
{
int button = 0;
int quit = 0;
int stop = 0;
int pattern = 0;
char *pgrid;
char *pnext_grid;
char *ptemp;
(void)parameter;
rb = api;
backlight_force_on(rb); /* backlight control in lib/helper.c */
#if LCD_DEPTH > 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);
setup_grid(pgrid, pattern++);
show_grid(pgrid);
while(!quit) {
button = pluginlib_getaction(rb, TIMEOUT_BLOCK, plugin_contexts, 2);
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(rb, 0, plugin_contexts, 2);
switch(button) {
case ROCKLIFE_PLAY_PAUSE:
case ROCKLIFE_QUIT:
stop = 1;
break;
default:
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=true;
return PLUGIN_OK;
break;
default:
if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
return PLUGIN_USB_CONNECTED;
}
break;
}
rb->yield();
}
backlight_use_settings(rb); /* backlight control in lib/helper.c */
return PLUGIN_OK;
}