Source

inpulse / life.c

/**
*
* GOL Watch
*
* Watch based on Conway's Game of Life: http://en.wikipedia.org/wiki/Conway's_Game_of_Life
*
* Features:
* 'low power mode' displays plain text time & date, updates on the minute (HH:MM YYYY:MM:DD)
* Game of Life Mode displays GoL grid, and time (HH:MM:SS)
* To switch from low power mode to GoL mode, press & release button
* To switch from GoL mode to lowpower mode, press & hold button for 2 seconds
* To re-seed GoL grid, press & release button
* GoL grid will automatically re-seed itself if it detects the 'Life' has died or
* if it detects that 'Life' has entered a static mode.
*
* Copyright (C) 2011:
* Stephanie Maksylewich <stephanie@felesmagus.com> http://planetstephanie.net/
* Trammell Hudson <hudson@osresearch.net>
*
* Permission to use, copy, modify, and/or distribute this software for 
* any purpose with or without fee is hereby granted, provided that the 
* above copyright notice and this permission notice appear in all copies. 
* 
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 
* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
* SOFTWARE.
*
**/

#include "app.h"


#define GAME_GRID (SCREEN_WIDTH)

#define COLOR_GREEN24		((color24_t){0x00, 0x80, 0x00, 0})
#define COLOR_YELLOW24		((color24_t){0x10, 0xFF, 0x00, 0})
#define COLOR_DARKGREEN24	((color24_t){0x10, 0x40, 0x00, 0})



struct life_game
{
	uint32_t cur_grid[GAME_GRID][GAME_GRID / 32];
	uint32_t temp_row[2][GAME_GRID / 32]; // 0 == current row, 1 == previous row

	// When did we last update the clock?
	uint8_t last_sec;
	uint16_t iter;

#ifdef CONFIG_TIME
	// Our time display
	char time_text_buffer[sizeof("HH:MM:SS")];
	struct PWTextBox time_text_box;
	struct PWidgetTextDynamic time_text_widget;
#endif
} __attribute__((packed));


#define DATA ((struct life_game *) app_common_data)

// Ensure that the size of the life_game struct is smaller than common block
enum { test_size = 1 / (sizeof(*DATA) <= APP_COMMON_SIZE) };



#if 0
/** Check the current row, using the temp_row for the one that is currently
 * being processed. */
static unsigned
alive(
	unsigned x,
	unsigned y
)
{
	// Allow the sides to wrap around
	if (y == GAME_GRID + 1)
		y = 0;
	else
	if (y == (unsigned) - 1)
		y = GAME_GRID - 1;

	// If we are processing row x right now, retrieve the current
	// value from temp_row[0];
	uint32_t * row;
	if (x == DATA->cur_row)
		row = DATA->temp_row[0];
	else
	if (x == DATA->cur_row - 1)
		row = DATA->temp_row[1];
	else
		row = DATA->cur_grid[x];

	uint32_t cell = row[y >> 5];
	uint32_t bit = 1 << (y & 31);
	return (cell & bit) != 0;
}
#endif


static unsigned
state(
	uint8_t col,
	uint32_t * const row
)
{
	uint32_t cell = row[col >> 5];
	uint32_t bit = 1 << (col & 31);
	return (cell & bit) != 0;
}


/** Retrieve the state of column col in the current row.
 * Current state is always stored in temp_row[0].
 */
static inline unsigned
cur_state(
	uint8_t col
)
{
	return state(col, DATA->temp_row[0]);
}


/** Retrieve the state of the three neighbors of the current
 * column in the provided row.
 */
static unsigned
neighbor_count(
	uint8_t col,
	uint32_t * const row
)
{
	unsigned count = 0;

	if (col != 0 && state(col - 1, row))
		count++;
	if (state(col, row))
		count++;
	if (col != GAME_GRID-1 && state(col + 1, row))
		count++;

	return count;
}



/** Update the row in place */
static void
mark(
	unsigned x,
	unsigned y,
	unsigned status
)
{
	uint32_t * cell = &DATA->cur_grid[x][y >> 5];
	uint32_t bit = 1 << (y & 31);
	if (status)
		*cell |= bit;
	else
		*cell &= ~bit;
}


static uint8_t
neighbors(
	unsigned x,
	unsigned y
)
{
	int folks = 0;
	folks += neighbor_count(y, DATA->temp_row[0]);
	folks += neighbor_count(y, DATA->temp_row[1]);

	if (y != GAME_GRID-1)
		folks += neighbor_count(y, DATA->cur_grid[x+1]);

	return folks;
}

static void
grid_randomize(void)
{
	unsigned i, j;

	for(i=0; i<GAME_GRID; i++)
		for(j=0; j<GAME_GRID; j++)
			mark(i, j, (rand() % 10) < 4);

	DATA->iter = 0;
}


static void
grid_update(void)
{
	if (DATA->iter++ == 800)
		grid_randomize();
	
	// Fade the entire board as it gets older
	pulse_oled_set_brightness((800 - DATA->iter) >> 3);

	unsigned i, j;
	memset(DATA->temp_row[1], 0, sizeof(DATA->temp_row[1]));
	pulse_set_draw_window(0, 0, GAME_GRID-1, GAME_GRID-1);

	for(i=0; i<GAME_GRID; i++)
	{
		memcpy(
			DATA->temp_row[0],
			DATA->cur_grid[i],
			sizeof(DATA->temp_row[0])
		);

		for(j=0; j<GAME_GRID; j++)
		{
			uint8_t near = neighbors(i, j);
			uint8_t old_state = cur_state(j);
			uint8_t new_state;

			// count includes the current state;
			if(old_state)
				new_state = (near == 2+1 || near == 3+1);
			else
				new_state = (near == 3);

			mark(i, j, new_state);

			// alive and same == green,
			// alive and new == white
			// newly dead == dark green
			// old dead == black
			static const color24_t colors[2][2] = {
				{{0x00, 0x00, 0x00, 0}, {0x10, 0x40, 0x00, 0}},
				{{0x10, 0xFF, 0x00, 0}, {0x00, 0x80, 0x00, 0}},
			};

			pulse_draw_point24(colors[new_state][old_state]);
		}

		// Shuffle the current temporary row to the previous
		// temporary row.
		memcpy(
			DATA->temp_row[1],
			DATA->temp_row[0],
			sizeof(DATA->temp_row[0])
		);
	}
}


static void
time_update(
	const struct pulse_time_tm * now
)
{
	if (now->tm_sec == DATA->last_sec)
		return;
	DATA->last_sec = now->tm_sec;

#ifdef CONFIG_TIME
	pulse_set_draw_window(0, 100, 95, 127);

	for(int i=0; i<2688; i++)
		pulse_draw_point24(COLOR_BLACK24);

	sprintf(DATA->time_text_buffer,
		"%02d:%02d:%02d",
		now->tm_hour,
		now->tm_min,
		now->tm_sec
	);

	pulse_render_text(&DATA->time_text_box, &DATA->time_text_widget);
#endif
}



static void
life_button_down(void)
{
	grid_randomize();
}


static void
life_loop(
	struct pulse_time_tm * now
)
{
	grid_update();
	time_update(now);
}



static void
life_init(void)
{
	pulse_oled_set_brightness(100);

	DATA->last_sec = 99;

#ifdef CONFIG_TIME
	DATA->time_text_box = (struct PWTextBox) {
		.top		= 100,
		.bottom		= SCREEN_HEIGHT - 1,
		.left		= 0,
		.right		= SCREEN_WIDTH - 1,
	};

	pulse_init_dynamic_text_widget(
		&DATA->time_text_widget,
		DATA->time_text_buffer,
		FONT_CLOCKOPIA_22,
		COLOR_WHITE24,
		PWTS_TRUNCATE | PWTS_CENTER
	);

	memset(DATA->time_text_buffer, 0, sizeof(DATA->time_text_buffer));
#endif

	grid_randomize();
}


const app_t life_app = {
	.init		= life_init,
	.loop		= life_loop,
	.button_down	= life_button_down,
};
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.