20e9d56ba5
It changes pluginlib actions to contain only a single and simple context (and other one for remote directional buttons), consisting of 7(9) buttons: up/down/left/right, select OR short select and long select, exit and cancel (plus 2 for scrollwheel targets). This ensures contexts don't clash with other contexts and simplifies them, at the expense of reduced versatility. However, the versatility made it largely unusable due to the great number of targets. This should allow for using pluginlib actions safely for the most simple plugins (e.g. almost all demos). git-svn-id: svn://svn.rockbox.org/rockbox/trunk@26202 a1c6a512-1295-4272-9138-f99709370657
584 lines
17 KiB
C
584 lines
17 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 "lib/pluginlib_actions.h"
|
|
#include "lib/helper.h"
|
|
|
|
PLUGIN_HEADER
|
|
|
|
#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;
|
|
char buf[30];
|
|
|
|
|
|
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 */
|
|
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<GRID_H; y++){
|
|
for(x=0; x<GRID_W; x++){
|
|
age = get_cell(x, y, pgrid);
|
|
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();
|
|
}
|
|
|
|
|
|
/* 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<GRID_H; y++){
|
|
for(x=0; x<GRID_W; x++){
|
|
if(y==0 && x==0){
|
|
/* first cell in first row, we have to load all neighbours */
|
|
n[0] = get_cell(x-1, y-1, pgrid);
|
|
n[1] = get_cell(x, y-1, pgrid);
|
|
n[2] = get_cell(x+1, y-1, pgrid);
|
|
n[3] = get_cell(x-1, y, pgrid);
|
|
n[4] = get_cell(x, y, pgrid);
|
|
n[5] = get_cell(x+1, y, pgrid);
|
|
n[6] = get_cell(x-1, y+1, pgrid);
|
|
n[7] = get_cell(x, y+1, pgrid);
|
|
n[8] = get_cell(x+1, y+1, pgrid);
|
|
} else {
|
|
if(x==0){
|
|
/* beginning of a row, copy what we know about our predecessor,
|
|
0, 1, 3, 4 are known, 2, 5, 6, 7, 8 have to be loaded
|
|
*/
|
|
n[0] = n[4];
|
|
n[1] = n[5];
|
|
n[2] = get_cell(x+1, y-1, pgrid);
|
|
n[3] = n[7];
|
|
n[4] = n[8];
|
|
n[5] = get_cell(x+1, y, pgrid);
|
|
n[6] = get_cell(x-1, y+1, pgrid);
|
|
n[7] = get_cell(x, y+1, pgrid);
|
|
n[8] = get_cell(x+1, y+1, pgrid);
|
|
} 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] = get_cell(x+1, y-1, pgrid);
|
|
n[3] = n[4];
|
|
n[4] = n[5];
|
|
n[5] = get_cell(x+1, y, pgrid);
|
|
n[6] = n[7];
|
|
n[7] = n[8];
|
|
n[8] = get_cell(x+1, y+1, pgrid);
|
|
}
|
|
}
|
|
|
|
/* how old is our cell? */
|
|
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)
|
|
age++;
|
|
set_cell_age(x, y, age, pnext_grid);
|
|
}
|
|
else
|
|
set_cell_age(x, y, 0, pnext_grid);
|
|
#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 void* parameter)
|
|
{
|
|
int button = 0;
|
|
int quit = 0;
|
|
int stop = 0;
|
|
int usb = 0;
|
|
int pattern = 0;
|
|
char *pgrid;
|
|
char *pnext_grid;
|
|
char *ptemp;
|
|
(void)(parameter);
|
|
|
|
backlight_force_on(); /* 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);
|
|
|
|
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(); /* backlight control in lib/helper.c */
|
|
return usb? PLUGIN_USB_CONNECTED: PLUGIN_OK;
|
|
}
|