Colour targets: Revert an optimisation from almost 18 months ago that actually turned...
[Rockbox.git] / apps / plugins / rocklife.c
blob55875257f9becc65c72c0c20a92d47d672822563
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Matthias Wientapper
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
23 * This is an implementatino of Conway's Game of Life
25 * from http://en.wikipedia.org/wiki/Conway's_Game_of_Life:
27 * Rules
29 * The universe of the Game of Life is an infinite two-dimensional
30 * orthogonal grid of square cells, each of which is in one of two
31 * possible states, live or dead. Every cell interacts with its eight
32 * neighbours, which are the cells that are directly horizontally,
33 * vertically, or diagonally adjacent. At each step in time, the
34 * following transitions occur:
36 * 1. Any live cell with fewer than two live neighbours dies, as if by
37 * loneliness.
39 * 2. Any live cell with more than three live neighbours dies, as if
40 * by overcrowding.
42 * 3. Any live cell with two or three live neighbours lives,
43 * unchanged, to the next generation.
45 * 4. Any dead cell with exactly three live neighbours comes to life.
47 * The initial pattern constitutes the first generation of the
48 * system. The second generation is created by applying the above
49 * rules simultaneously to every cell in the first generation --
50 * births and deaths happen simultaneously, and the discrete moment at
51 * which this happens is sometimes called a tick. (In other words,
52 * each generation is based entirely on the one before.) The rules
53 * continue to be applied repeatedly to create further generations.
57 * TODO:
58 * - nicer colours for pixels with respect to age
59 * - editor for start patterns
60 * - probably tons of speed-up opportunities
63 #include "plugin.h"
64 #include "pluginlib_actions.h"
65 #include "helper.h"
67 PLUGIN_HEADER
69 #define ROCKLIFE_PLAY_PAUSE PLA_FIRE
70 #define ROCKLIFE_INIT PLA_DOWN
71 #define ROCKLIFE_NEXT PLA_RIGHT
72 #define ROCKLIFE_NEXT_REP PLA_RIGHT_REPEAT
73 #define ROCKLIFE_QUIT PLA_QUIT
74 #define ROCKLIFE_STATUS PLA_LEFT
76 #define PATTERN_RANDOM 0
77 #define PATTERN_GROWTH_1 1
78 #define PATTERN_GROWTH_2 2
79 #define PATTERN_ACORN 3
80 #define PATTERN_GLIDER_GUN 4 /* not yet implemented */
82 static const struct plugin_api* rb;
83 const struct button_mapping *plugin_contexts[]
84 = {generic_directions, generic_actions};
87 unsigned char grid_a[LCD_WIDTH][LCD_HEIGHT];
88 unsigned char grid_b[LCD_WIDTH][LCD_HEIGHT];
89 int generation = 0;
90 int population = 0;
91 int status_line = 0;
92 char buf[30];
94 static inline void set_cell(int x, int y, char *pgrid){
95 pgrid[x+y*LCD_WIDTH]=1;
98 /* clear grid */
99 void init_grid(char *pgrid){
100 int x, y;
102 for(y=0; y<LCD_HEIGHT; y++){
103 for(x=0; x<LCD_WIDTH; x++){
104 pgrid[x+y*LCD_WIDTH] = 0;
109 /* fill grid with initial pattern */
110 static void setup_grid(char *pgrid, int pattern){
111 int n, max;
112 int xmid, ymid;
114 max = LCD_HEIGHT*LCD_WIDTH;
116 switch(pattern){
117 case PATTERN_RANDOM:
118 rb->splash(HZ, "Random");
119 #if 0 /* two oscilators, debug pattern */
120 set_cell( 0, 1 , pgrid);
121 set_cell( 1, 1 , pgrid);
122 set_cell( 2, 1 , pgrid);
124 set_cell( 6, 7 , pgrid);
125 set_cell( 7, 7 , pgrid);
126 set_cell( 8, 7 , pgrid);
127 #endif
129 /* fill screen randomly */
130 for(n=0; n<(max>>2); n++)
131 pgrid[rb->rand()%max] = 1;
133 break;
135 case PATTERN_GROWTH_1:
136 rb->splash(HZ, "Growth");
137 xmid = (LCD_WIDTH>>1) - 2;
138 ymid = (LCD_HEIGHT>>1) - 2;
139 set_cell(xmid + 6, ymid + 0 , pgrid);
140 set_cell(xmid + 4, ymid + 1 , pgrid);
141 set_cell(xmid + 6, ymid + 1 , pgrid);
142 set_cell(xmid + 7, ymid + 1 , pgrid);
143 set_cell(xmid + 4, ymid + 2 , pgrid);
144 set_cell(xmid + 6, ymid + 2 , pgrid);
145 set_cell(xmid + 4, ymid + 3 , pgrid);
146 set_cell(xmid + 2, ymid + 4 , pgrid);
147 set_cell(xmid + 0, ymid + 5 , pgrid);
148 set_cell(xmid + 2, ymid + 5 , pgrid);
149 break;
150 case PATTERN_ACORN:
151 rb->splash(HZ, "Acorn");
152 xmid = (LCD_WIDTH>>1) - 3;
153 ymid = (LCD_HEIGHT>>1) - 1;
154 set_cell(xmid + 1, ymid + 0 , pgrid);
155 set_cell(xmid + 3, ymid + 1 , pgrid);
156 set_cell(xmid + 0, ymid + 2 , pgrid);
157 set_cell(xmid + 1, ymid + 2 , pgrid);
158 set_cell(xmid + 4, ymid + 2 , pgrid);
159 set_cell(xmid + 5, ymid + 2 , pgrid);
160 set_cell(xmid + 6, ymid + 2 , pgrid);
161 break;
162 case PATTERN_GROWTH_2:
163 rb->splash(HZ, "Growth 2");
164 xmid = (LCD_WIDTH>>1) - 4;
165 ymid = (LCD_HEIGHT>>1) - 1;
166 set_cell(xmid + 0, ymid + 0 , pgrid);
167 set_cell(xmid + 1, ymid + 0 , pgrid);
168 set_cell(xmid + 2, ymid + 0 , pgrid);
169 set_cell(xmid + 4, ymid + 0 , pgrid);
170 set_cell(xmid + 0, ymid + 1 , pgrid);
171 set_cell(xmid + 3, ymid + 2 , pgrid);
172 set_cell(xmid + 4, ymid + 2 , pgrid);
173 set_cell(xmid + 1, ymid + 3 , pgrid);
174 set_cell(xmid + 2, ymid + 3 , pgrid);
175 set_cell(xmid + 4, ymid + 3 , pgrid);
176 set_cell(xmid + 0, ymid + 4 , pgrid);
177 set_cell(xmid + 2, ymid + 4 , pgrid);
178 set_cell(xmid + 4, ymid + 4 , pgrid);
179 break;
180 case PATTERN_GLIDER_GUN:
181 rb->splash(HZ, "Glider Gun");
182 set_cell( 24, 0, pgrid);
183 set_cell( 22, 1, pgrid);
184 set_cell( 24, 1, pgrid);
185 set_cell( 12, 2, pgrid);
186 set_cell( 13, 2, pgrid);
187 set_cell( 20, 2, pgrid);
188 set_cell( 21, 2, pgrid);
189 set_cell( 34, 2, pgrid);
190 set_cell( 35, 2, pgrid);
191 set_cell( 11, 3, pgrid);
192 set_cell( 15, 3, pgrid);
193 set_cell( 20, 3, pgrid);
194 set_cell( 21, 3, pgrid);
195 set_cell( 34, 3, pgrid);
196 set_cell( 35, 3, pgrid);
197 set_cell( 0, 4, pgrid);
198 set_cell( 1, 4, pgrid);
199 set_cell( 10, 4, pgrid);
200 set_cell( 16, 4, pgrid);
201 set_cell( 20, 4, pgrid);
202 set_cell( 21, 4, pgrid);
203 set_cell( 0, 5, pgrid);
204 set_cell( 1, 5, pgrid);
205 set_cell( 10, 5, pgrid);
206 set_cell( 14, 5, pgrid);
207 set_cell( 16, 5, pgrid);
208 set_cell( 17, 5, pgrid);
209 set_cell( 22, 5, pgrid);
210 set_cell( 24, 5, pgrid);
211 set_cell( 10, 6, pgrid);
212 set_cell( 16, 6, pgrid);
213 set_cell( 24, 6, pgrid);
214 set_cell( 11, 7, pgrid);
215 set_cell( 15, 7, pgrid);
216 set_cell( 12, 8, pgrid);
217 set_cell( 13, 8, pgrid);
218 break;
222 /* display grid */
223 static void show_grid(char *pgrid){
224 int x, y;
225 int m;
226 unsigned char age;
228 rb->lcd_clear_display();
229 for(y=0; y<LCD_HEIGHT; y++){
230 for(x=0; x<LCD_WIDTH; x++){
231 m = y*LCD_WIDTH+x;
232 age = pgrid[m];
233 if(age){
234 #if LCD_DEPTH >= 16
235 rb->lcd_set_foreground( LCD_RGBPACK( age, age, age ));
236 #elif LCD_DEPTH == 2
237 rb->lcd_set_foreground(age>>7);
238 #endif
239 rb->lcd_drawpixel(x, y);
243 if(status_line){
244 rb->snprintf(buf, sizeof(buf), "g:%d p:%d", generation, population);
245 #if LCD_DEPTH > 1
246 rb->lcd_set_foreground( LCD_BLACK );
247 #endif
248 rb->lcd_puts(0, 0, buf);
250 rb->lcd_update();
254 /* check state of cell depending on the number of neighbours */
255 static inline int check_cell(unsigned char *n){
256 int sum;
257 int empty_cells = 0;
258 unsigned char live = 0;
260 /* count empty neighbour cells */
261 if(n[0]==0) empty_cells++;
262 if(n[1]==0) empty_cells++;
263 if(n[2]==0) empty_cells++;
264 if(n[3]==0) empty_cells++;
265 if(n[5]==0) empty_cells++;
266 if(n[6]==0) empty_cells++;
267 if(n[7]==0) empty_cells++;
268 if(n[8]==0) empty_cells++;
270 /* now we build the number of non-zero neighbours :-P */
271 sum = 8 - empty_cells;
273 /* 1st and 2nd rule*/
274 if (n[4] && (sum<2 || sum>3))
275 live = false;
277 /* 3rd rule */
278 if (n[4] && (sum==2 || sum==3))
279 live = true;
281 /* 4rd rule */
282 if (!n[4] && sum==3)
283 live = true;
285 return live;
288 /* Calculate the next generation of cells
290 * The borders of the grid are connected to their opposite sides.
293 * To avoid multiplications while accessing data in the 2-d grid
294 * (pgrid) we try to re-use previously accessed neighbourhood
295 * information which is stored in an 3x3 array.
298 static void next_generation(char *pgrid, char *pnext_grid){
299 int x, y;
300 unsigned char cell;
301 int age;
302 int m;
303 unsigned char n[9];
305 rb->memset(n, 0, sizeof(n));
308 * cell is (4) with 8 neighbours
310 * 0|1|2
311 * -----
312 * 3|4|5
313 * -----
314 * 6|7|8
317 population = 0;
319 /* go through the grid */
320 for(y=0; y<LCD_HEIGHT; y++){
321 for(x=0; x<LCD_WIDTH; x++){
322 if(y==0 && x==0){
323 /* first cell in first row, we have to load all neighbours */
324 n[0] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
325 n[1] = pgrid[((x )%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
326 n[2] = pgrid[((x +1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
327 n[3] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
328 n[5] = pgrid[((x +1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
329 n[6] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
330 n[7] = pgrid[((x )%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
331 n[8] = pgrid[((x +1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
332 } else {
333 if(x==0){
334 /* beginning of a row, copy what we know about our predecessor,
335 0, 1, 3 are known, 2, 5, 6, 7, 8 have to be loaded
337 n[0] = n[4];
338 n[1] = n[5];
339 n[2] = pgrid[((x +1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
340 n[3] = n[7];
341 n[5] = pgrid[((x +1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
342 n[6] = pgrid[((x+LCD_WIDTH-1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
343 n[7] = pgrid[((x )%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
344 n[8] = pgrid[((x +1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
345 } else {
346 /* we are moving right in a row,
347 * copy what we know about the neighbours on our left side,
348 * 2, 5, 8 have to be loaded
350 n[0] = n[1];
351 n[1] = n[2];
352 n[2] = pgrid[((x +1)%LCD_WIDTH)+((y+LCD_HEIGHT-1)%LCD_HEIGHT)*LCD_WIDTH];
353 n[3] = n[4];
354 n[5] = pgrid[((x +1)%LCD_WIDTH)+((y )%LCD_HEIGHT)*LCD_WIDTH];
355 n[6] = n[7];
356 n[7] = n[8];
357 n[8] = pgrid[((x +1)%LCD_WIDTH)+((y +1)%LCD_HEIGHT)*LCD_WIDTH];
361 m = x+y*LCD_WIDTH;
363 /* how old is our cell? */
364 n[4] = pgrid[m];
365 age = n[4];
367 /* calculate the cell based on given neighbour information */
368 cell = check_cell(n);
370 /* is the actual cell alive? */
371 if(cell){
372 population++;
373 /* prevent overflow */
374 if(age>252){
375 pnext_grid[m] = 252;
376 } else {
377 pnext_grid[m] = age + 1;
380 else
381 pnext_grid[m] = 0;
382 #if 0
383 DEBUGF("x=%d,y=%d\n", x, y);
384 DEBUGF("cell: %d\n", cell);
385 DEBUGF("%d %d %d\n", n[0],n[1],n[2]);
386 DEBUGF("%d %d %d\n", n[3],n[4],n[5]);
387 DEBUGF("%d %d %d\n", n[6],n[7],n[8]);
388 DEBUGF("----------------\n");
389 #endif
392 generation++;
395 /**********************************/
396 /* this is the plugin entry point */
397 /**********************************/
398 enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
400 int button = 0;
401 int quit = 0;
402 int stop = 0;
403 int pattern = 0;
404 char *pgrid;
405 char *pnext_grid;
406 char *ptemp;
408 (void)parameter;
409 rb = api;
411 backlight_force_on(rb); /* backlight control in lib/helper.c */
412 #if LCD_DEPTH > 1
413 rb->lcd_set_backdrop(NULL);
414 #ifdef HAVE_LCD_COLOR
415 rb->lcd_set_background(LCD_RGBPACK(182, 198, 229)); /* rockbox blue */
416 #else
417 rb->lcd_set_background(LCD_DEFAULT_BG);
418 #endif /* HAVE_LCD_COLOR */
419 #endif /* LCD_DEPTH > 1 */
421 /* link pointers to grids */
422 pgrid = (char *)grid_a;
423 pnext_grid = (char *)grid_b;
425 init_grid(pgrid);
426 setup_grid(pgrid, pattern++);
427 show_grid(pgrid);
429 while(!quit) {
430 button = pluginlib_getaction(rb, TIMEOUT_BLOCK, plugin_contexts, 2);
431 switch(button) {
432 case ROCKLIFE_NEXT:
433 case ROCKLIFE_NEXT_REP:
434 /* calculate next generation */
435 next_generation(pgrid, pnext_grid);
436 /* swap buffers, grid is the new generation */
437 ptemp = pgrid;
438 pgrid = pnext_grid;
439 pnext_grid = ptemp;
440 /* show new generation */
441 show_grid(pgrid);
442 break;
443 case ROCKLIFE_PLAY_PAUSE:
444 stop = 0;
445 while(!stop){
446 /* calculate next generation */
447 next_generation(pgrid, pnext_grid);
448 /* swap buffers, grid is the new generation */
449 ptemp = pgrid;
450 pgrid = pnext_grid;
451 pnext_grid = ptemp;
452 /* show new generation */
453 rb->yield();
454 show_grid(pgrid);
455 button = pluginlib_getaction(rb, 0, plugin_contexts, 2);
456 switch(button) {
457 case ROCKLIFE_PLAY_PAUSE:
458 case ROCKLIFE_QUIT:
459 stop = 1;
460 break;
461 default:
462 break;
464 rb->yield();
466 break;
467 case ROCKLIFE_INIT:
468 init_grid(pgrid);
469 setup_grid(pgrid, pattern);
470 show_grid(pgrid);
471 pattern++;
472 pattern%=5;
473 break;
474 case ROCKLIFE_STATUS:
475 status_line = !status_line;
476 show_grid(pgrid);
477 break;
478 case ROCKLIFE_QUIT:
479 /* quit plugin */
480 quit=true;
481 return PLUGIN_OK;
482 break;
483 default:
484 if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
485 return PLUGIN_USB_CONNECTED;
487 break;
489 rb->yield();
492 backlight_use_settings(rb); /* backlight control in lib/helper.c */
493 return PLUGIN_OK;