Compare commits
	
		
			15 Commits 
		
	
	
		
			master
			...
			multi-mode
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
									
								
								 | 
						09e08e58a6 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						02641ac008 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						c0d2b618da | 10 years ago | 
| 
							
							
								
									
								
								 | 
						43ab7abce7 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						736567af8c | 10 years ago | 
| 
							
							
								
									
								
								 | 
						a4400c25f5 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						e99ea3d0ec | 10 years ago | 
| 
							
							
								
									
								
								 | 
						d49b10a802 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						56a0d0c63b | 10 years ago | 
| 
							
							
								
									
								
								 | 
						7fe3863f1b | 10 years ago | 
| 
							
							
								
									
								
								 | 
						d4e386daa9 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						b4fe9dc546 | 10 years ago | 
| 
							
							
								
									
								
								 | 
						0f07ce0a4a | 10 years ago | 
| 
							
							
								
									
								
								 | 
						dbd2d6a7ea | 10 years ago | 
| 
							
							
								
									
								
								 | 
						7af5f0e35e | 10 years ago | 
@ -1,113 +1,130 @@ | 
				
			||||
#include "max2719.h" | 
				
			||||
#include "dotmatrix.h" | 
				
			||||
#include "malloc_safe.h" | 
				
			||||
#include "com/debug.h" | 
				
			||||
 | 
				
			||||
DotMatrix_Cfg *dmtx; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
DotMatrix_Cfg* dmtx_init(DotMatrix_Init *init) | 
				
			||||
{ | 
				
			||||
	DotMatrix_Cfg *dmtx = calloc_s(1, sizeof(DotMatrix_Cfg)); | 
				
			||||
	DotMatrix_Cfg *disp = calloc_s(1, sizeof(DotMatrix_Cfg)); | 
				
			||||
 | 
				
			||||
	dmtx->drv.SPIx = init->SPIx; | 
				
			||||
	dmtx->drv.CS_GPIOx = init->CS_GPIOx; | 
				
			||||
	dmtx->drv.CS_PINx = init->CS_PINx; | 
				
			||||
	dmtx->drv.chain_len = init->cols * init->rows; | 
				
			||||
	dmtx->cols = init->cols; | 
				
			||||
	dmtx->rows = init->rows; | 
				
			||||
	disp->drv.SPIx = init->SPIx; | 
				
			||||
	disp->drv.CS_GPIOx = init->CS_GPIOx; | 
				
			||||
	disp->drv.CS_PINx = init->CS_PINx; | 
				
			||||
	disp->drv.chain_len = init->cols * init->rows; | 
				
			||||
	disp->cols = init->cols; | 
				
			||||
	disp->rows = init->rows; | 
				
			||||
 | 
				
			||||
	dmtx->screen = calloc_s(init->cols * init->rows * 8, 1); // 8 bytes per driver
 | 
				
			||||
	disp->screen = calloc_s(init->cols * init->rows * 8, 1); // 8 bytes per driver
 | 
				
			||||
 | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DECODE_MODE, 0x00); // no decode
 | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SCAN_LIMIT, 0x07); // scan all 8
 | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SHUTDOWN, 0x01); // not shutdown
 | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DISPLAY_TEST, 0x00); // not test
 | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_INTENSITY, 0x07); // half intensity
 | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_DECODE_MODE, 0x00); // no decode
 | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_SCAN_LIMIT, 0x07); // scan all 8
 | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, 0x01); // not shutdown
 | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_DISPLAY_TEST, 0x00); // not test
 | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_INTENSITY, 0x07); // half intensity
 | 
				
			||||
 | 
				
			||||
	// clear
 | 
				
			||||
	for (uint8_t i = 0; i < 8; i++) { | 
				
			||||
		max2719_cmd_all(&dmtx->drv, MAX2719_CMD_DIGIT0+i, 0); | 
				
			||||
		max2719_cmd_all(&disp->drv, MAX2719_CMD_DIGIT0+i, 0); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	return dmtx; | 
				
			||||
	return disp; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void dmtx_show(DotMatrix_Cfg* dmtx) | 
				
			||||
void dmtx_show(DotMatrix_Cfg* disp) | 
				
			||||
{ | 
				
			||||
	for (uint8_t i = 0; i < 8; i++) { | 
				
			||||
		// show each digit's array in turn
 | 
				
			||||
		max2719_cmd_all_data(&dmtx->drv, MAX2719_CMD_DIGIT0+i, dmtx->screen + (i * dmtx->drv.chain_len)); | 
				
			||||
		max2719_cmd_all_data(&disp->drv, MAX2719_CMD_DIGIT0+i, disp->screen + (i * disp->drv.chain_len)); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void dmtx_clear(DotMatrix_Cfg* dmtx) | 
				
			||||
void dmtx_clear(DotMatrix_Cfg* disp) | 
				
			||||
{ | 
				
			||||
	memset(dmtx->screen, 0, dmtx->drv.chain_len*8); | 
				
			||||
	memset(disp->screen, 0, disp->drv.chain_len*8); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void dmtx_intensity(DotMatrix_Cfg* dmtx, uint8_t intensity) | 
				
			||||
void dmtx_intensity(DotMatrix_Cfg* disp, uint8_t intensity) | 
				
			||||
{ | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_INTENSITY, intensity & 0x0F); | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_INTENSITY, intensity & 0x0F); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void dmtx_blank(DotMatrix_Cfg* dmtx, bool blank) | 
				
			||||
void dmtx_blank(DotMatrix_Cfg* disp, bool blank) | 
				
			||||
{ | 
				
			||||
	max2719_cmd_all(&dmtx->drv, MAX2719_CMD_SHUTDOWN, blank & 0x01); | 
				
			||||
	max2719_cmd_all(&disp->drv, MAX2719_CMD_SHUTDOWN, (!blank) & 0x01); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/**
 | 
				
			||||
 * @brief Get a cell pointer | 
				
			||||
 * @param dmtx : driver inst | 
				
			||||
 * @param disp : driver inst | 
				
			||||
 * @param x : x coord | 
				
			||||
 * @param y : y coord | 
				
			||||
 * @param xd : pointer to store the offset in the cell | 
				
			||||
 * @return cell ptr | 
				
			||||
 */ | 
				
			||||
static uint8_t* cell_ptr(DotMatrix_Cfg* dmtx, int32_t x, int32_t y, uint8_t *xd) | 
				
			||||
static uint8_t* cell_ptr(DotMatrix_Cfg* disp, int32_t x, int32_t y, uint8_t *xd) | 
				
			||||
{ | 
				
			||||
	if (x < 0 || y < 0) return NULL; | 
				
			||||
	if ((uint32_t)x >= dmtx->cols*8 || (uint32_t)y >= dmtx->rows*8) return NULL; | 
				
			||||
	if ((uint32_t)x >= disp->cols*8 || (uint32_t)y >= disp->rows*8) return NULL; | 
				
			||||
 | 
				
			||||
	uint32_t cell_x = (uint32_t)x >> 3; | 
				
			||||
	*xd = x & 7; | 
				
			||||
 | 
				
			||||
	// resolve cell
 | 
				
			||||
	uint32_t digit = y & 7; | 
				
			||||
	cell_x += ((uint32_t)y >> 3) * dmtx->cols; | 
				
			||||
	cell_x += ((uint32_t)y >> 3) * disp->cols; | 
				
			||||
 | 
				
			||||
	uint32_t cell_idx = (digit * dmtx->drv.chain_len) + cell_x; | 
				
			||||
	uint32_t cell_idx = (digit * disp->drv.chain_len) + cell_x; | 
				
			||||
 | 
				
			||||
	return &dmtx->screen[cell_idx]; | 
				
			||||
	return &disp->screen[cell_idx]; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
bool dmtx_get(DotMatrix_Cfg* dmtx, int32_t x, int32_t y) | 
				
			||||
bool dmtx_get(DotMatrix_Cfg* disp, int32_t x, int32_t y) | 
				
			||||
{ | 
				
			||||
	uint8_t xd; | 
				
			||||
	uint8_t *cell = cell_ptr(dmtx, x, y, &xd); | 
				
			||||
	uint8_t *cell = cell_ptr(disp, x, y, &xd); | 
				
			||||
	if (cell == NULL) return 0; | 
				
			||||
 | 
				
			||||
	return (bool)(*cell & (1 << xd)); | 
				
			||||
	return (*cell >> xd) & 1; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void dmtx_toggle(DotMatrix_Cfg* dmtx, int32_t x, int32_t y) | 
				
			||||
void dmtx_toggle(DotMatrix_Cfg* disp, int32_t x, int32_t y) | 
				
			||||
{ | 
				
			||||
	uint8_t xd; | 
				
			||||
	uint8_t *cell = cell_ptr(dmtx, x, y, &xd); | 
				
			||||
	uint8_t *cell = cell_ptr(disp, x, y, &xd); | 
				
			||||
	if (cell == NULL) return; | 
				
			||||
 | 
				
			||||
	*cell ^= 1 << xd; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void dmtx_set(DotMatrix_Cfg* dmtx, int32_t x, int32_t y, bool bit) | 
				
			||||
void dmtx_set(DotMatrix_Cfg* disp, int32_t x, int32_t y, bool bit) | 
				
			||||
{ | 
				
			||||
	uint8_t xd; | 
				
			||||
	uint8_t *cell = cell_ptr(dmtx, x, y, &xd); | 
				
			||||
	uint8_t *cell = cell_ptr(disp, x, y, &xd); | 
				
			||||
	if (cell == NULL) return; | 
				
			||||
 | 
				
			||||
	if (bit) { | 
				
			||||
		*cell |= bit << xd; | 
				
			||||
		*cell |= 1 << xd; | 
				
			||||
	} else { | 
				
			||||
		*cell &= ~(bit << xd); | 
				
			||||
		*cell &= ~(1 << xd); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void dmtx_set_block(DotMatrix_Cfg* disp, int32_t startX, int32_t startY, uint32_t *data_rows, uint32_t width, uint16_t height) | 
				
			||||
{ | 
				
			||||
	for (uint32_t y = 0; y < height; y++) { | 
				
			||||
		uint32_t row = data_rows[y]; | 
				
			||||
 | 
				
			||||
		for (uint32_t x = 0; x < width; x++) { | 
				
			||||
			int xx = startX + (int)x; | 
				
			||||
			int yy = startY + (int)y; | 
				
			||||
			bool val = (row >> (width - x - 1)) & 1; | 
				
			||||
 | 
				
			||||
			dmtx_set(disp, xx, yy, val); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
@ -0,0 +1,108 @@ | 
				
			||||
#ifndef FONT_H | 
				
			||||
#define FONT_H | 
				
			||||
 | 
				
			||||
#define FONT_MIN ' ' | 
				
			||||
#define FONT_MAX '~' | 
				
			||||
 | 
				
			||||
#define FONT_WIDTH 6 | 
				
			||||
 | 
				
			||||
const unsigned char font[96][6] = { | 
				
			||||
	{0x00,0x00,0x00,0x00,0x00,0x00}, //
 | 
				
			||||
	{0x5c,0x00,0x00,0x00,0x00,0x00}, // !
 | 
				
			||||
	{0x06,0x00,0x06,0x00,0x00,0x00}, // "
 | 
				
			||||
	{0x28,0x7c,0x28,0x7c,0x28,0x00}, // #
 | 
				
			||||
	{0x5c,0x54,0xfe,0x54,0x74,0x00}, // $
 | 
				
			||||
	{0x44,0x20,0x10,0x08,0x44,0x00}, // %
 | 
				
			||||
	{0x28,0x54,0x54,0x20,0x50,0x00}, // &
 | 
				
			||||
	{0x06,0x00,0x00,0x00,0x00,0x00}, // '
 | 
				
			||||
	{0x38,0x44,0x00,0x00,0x00,0x00}, // (
 | 
				
			||||
	{0x44,0x38,0x00,0x00,0x00,0x00}, // )
 | 
				
			||||
	{0x02,0x07,0x02,0x00,0x00,0x00}, // *
 | 
				
			||||
	{0x10,0x10,0x7c,0x10,0x10,0x00}, // +
 | 
				
			||||
	{0xc0,0x00,0x00,0x00,0x00,0x00}, // ,
 | 
				
			||||
	{0x10,0x10,0x10,0x10,0x10,0x00}, // -
 | 
				
			||||
	{0x40,0x00,0x00,0x00,0x00,0x00}, // .
 | 
				
			||||
	{0x60,0x10,0x0c,0x00,0x00,0x00}, // /
 | 
				
			||||
	{0x7c,0x64,0x54,0x4c,0x7c,0x00}, // 0
 | 
				
			||||
	{0x48,0x7c,0x40,0x00,0x00,0x00}, // 1
 | 
				
			||||
	{0x64,0x54,0x54,0x54,0x48,0x00}, // 2
 | 
				
			||||
	{0x44,0x54,0x54,0x54,0x6c,0x00}, // 3
 | 
				
			||||
	{0x3c,0x20,0x70,0x20,0x20,0x00}, // 4
 | 
				
			||||
	{0x5c,0x54,0x54,0x54,0x24,0x00}, // 5
 | 
				
			||||
	{0x7c,0x54,0x54,0x54,0x74,0x00}, // 6
 | 
				
			||||
	{0x04,0x04,0x64,0x14,0x0c,0x00}, // 7
 | 
				
			||||
	{0x7c,0x54,0x54,0x54,0x7c,0x00}, // 8
 | 
				
			||||
	{0x5c,0x54,0x54,0x54,0x7c,0x00}, // 9
 | 
				
			||||
	{0x44,0x00,0x00,0x00,0x00,0x00}, // :
 | 
				
			||||
	{0xc4,0x00,0x00,0x00,0x00,0x00}, // ;
 | 
				
			||||
	{0x10,0x28,0x44,0x00,0x00,0x00}, // <
 | 
				
			||||
	{0x28,0x28,0x28,0x28,0x28,0x00}, // =
 | 
				
			||||
	{0x44,0x28,0x10,0x00,0x00,0x00}, // >
 | 
				
			||||
	{0x08,0x04,0x54,0x08,0x00,0x00}, // ?
 | 
				
			||||
	{0x7c,0x44,0x54,0x54,0x5c,0x00}, // @
 | 
				
			||||
	{0x7c,0x24,0x24,0x24,0x7c,0x00}, // A
 | 
				
			||||
	{0x7c,0x54,0x54,0x54,0x6c,0x00}, // B
 | 
				
			||||
	{0x7c,0x44,0x44,0x44,0x44,0x00}, // C
 | 
				
			||||
	{0x7c,0x44,0x44,0x44,0x38,0x00}, // D
 | 
				
			||||
	{0x7c,0x54,0x54,0x54,0x44,0x00}, // E
 | 
				
			||||
	{0x7c,0x14,0x14,0x14,0x04,0x00}, // F
 | 
				
			||||
	{0x7c,0x44,0x44,0x54,0x74,0x00}, // G
 | 
				
			||||
	{0x7c,0x10,0x10,0x10,0x7c,0x00}, // H
 | 
				
			||||
	{0x44,0x44,0x7c,0x44,0x44,0x00}, // I
 | 
				
			||||
	{0x60,0x40,0x40,0x44,0x7c,0x00}, // J
 | 
				
			||||
	{0x7c,0x10,0x10,0x28,0x44,0x00}, // K
 | 
				
			||||
	{0x7c,0x40,0x40,0x40,0x40,0x00}, // L
 | 
				
			||||
	{0x7c,0x08,0x10,0x08,0x7c,0x00}, // M
 | 
				
			||||
	{0x7c,0x08,0x10,0x20,0x7c,0x00}, // N
 | 
				
			||||
	{0x38,0x44,0x44,0x44,0x38,0x00}, // O
 | 
				
			||||
	{0x7c,0x14,0x14,0x14,0x08,0x00}, // P
 | 
				
			||||
	{0x3c,0x24,0x64,0x24,0x3c,0x00}, // Q
 | 
				
			||||
	{0x7c,0x14,0x14,0x14,0x68,0x00}, // R
 | 
				
			||||
	{0x5c,0x54,0x54,0x54,0x74,0x00}, // S
 | 
				
			||||
	{0x04,0x04,0x7c,0x04,0x04,0x00}, // T
 | 
				
			||||
	{0x7c,0x40,0x40,0x40,0x7c,0x00}, // U
 | 
				
			||||
	{0x0c,0x30,0x40,0x30,0x0c,0x00}, // V
 | 
				
			||||
	{0x3c,0x40,0x30,0x40,0x3c,0x00}, // W
 | 
				
			||||
	{0x44,0x28,0x10,0x28,0x44,0x00}, // X
 | 
				
			||||
	{0x0c,0x10,0x60,0x10,0x0c,0x00}, // Y
 | 
				
			||||
	{0x44,0x64,0x54,0x4c,0x44,0x00}, // Z
 | 
				
			||||
	{0x7c,0x44,0x00,0x00,0x00,0x00}, // [
 | 
				
			||||
	{0x0c,0x10,0x60,0x00,0x00,0x00}, // "\"
 | 
				
			||||
	{0x44,0x7c,0x00,0x00,0x00,0x00}, // ]
 | 
				
			||||
	{0x00,0x01,0x00,0x01,0x00,0x00}, // ^
 | 
				
			||||
	{0x40,0x40,0x40,0x40,0x40,0x40}, // _
 | 
				
			||||
	{0x00,0x01,0x00,0x00,0x00,0x00}, // `
 | 
				
			||||
	{0x7c,0x24,0x24,0x24,0x7c,0x00}, // a
 | 
				
			||||
	{0x7c,0x54,0x54,0x54,0x6c,0x00}, // b
 | 
				
			||||
	{0x7c,0x44,0x44,0x44,0x44,0x00}, // c
 | 
				
			||||
	{0x7c,0x44,0x44,0x44,0x38,0x00}, // d
 | 
				
			||||
	{0x7c,0x54,0x54,0x54,0x44,0x00}, // e
 | 
				
			||||
	{0x7c,0x14,0x14,0x14,0x04,0x00}, // f
 | 
				
			||||
	{0x7c,0x44,0x44,0x54,0x74,0x00}, // g
 | 
				
			||||
	{0x7c,0x10,0x10,0x10,0x7c,0x00}, // h
 | 
				
			||||
	{0x44,0x44,0x7c,0x44,0x44,0x00}, // i
 | 
				
			||||
	{0x60,0x40,0x40,0x44,0x7c,0x00}, // j
 | 
				
			||||
	{0x7c,0x10,0x10,0x28,0x44,0x00}, // k
 | 
				
			||||
	{0x7c,0x40,0x40,0x40,0x40,0x00}, // l
 | 
				
			||||
	{0x7c,0x08,0x10,0x08,0x7c,0x00}, // m
 | 
				
			||||
	{0x7c,0x08,0x10,0x20,0x7c,0x00}, // n
 | 
				
			||||
	{0x38,0x44,0x44,0x44,0x38,0x00}, // o
 | 
				
			||||
	{0x7c,0x14,0x14,0x14,0x08,0x00}, // p
 | 
				
			||||
	{0x3c,0x24,0x64,0x24,0x3c,0x00}, // q
 | 
				
			||||
	{0x7c,0x14,0x14,0x14,0x68,0x00}, // r
 | 
				
			||||
	{0x5c,0x54,0x54,0x54,0x74,0x00}, // s
 | 
				
			||||
	{0x04,0x04,0x7c,0x04,0x04,0x00}, // t
 | 
				
			||||
	{0x7c,0x40,0x40,0x40,0x7c,0x00}, // u
 | 
				
			||||
	{0x0c,0x30,0x40,0x30,0x0c,0x00}, // v
 | 
				
			||||
	{0x3c,0x40,0x30,0x40,0x3c,0x00}, // w
 | 
				
			||||
	{0x44,0x28,0x10,0x28,0x44,0x00}, // x
 | 
				
			||||
	{0x0c,0x10,0x60,0x10,0x0c,0x00}, // y
 | 
				
			||||
	{0x44,0x64,0x54,0x4c,0x44,0x00}, // z
 | 
				
			||||
	{0x10,0x7c,0x44,0x00,0x00,0x00}, // {
 | 
				
			||||
	{0x6c,0x00,0x00,0x00,0x00,0x00}, // |
 | 
				
			||||
	{0x44,0x7c,0x10,0x00,0x00,0x00}, // }
 | 
				
			||||
	{0x02,0x01,0x02,0x01,0x00,0x00}, // ~
 | 
				
			||||
	{0x00,0x00,0x00,0x00,0x00,0x00} | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
#endif // FONT_H
 | 
				
			||||
@ -0,0 +1,175 @@ | 
				
			||||
#include "mode_audio.h" | 
				
			||||
#include <arm_math.h> | 
				
			||||
#include "bus/event_queue.h" | 
				
			||||
#include "utils/timebase.h" | 
				
			||||
 | 
				
			||||
#include "dotmatrix.h" | 
				
			||||
 | 
				
			||||
static bool audio_mode_active = true; | 
				
			||||
 | 
				
			||||
static volatile bool capture_pending = false; | 
				
			||||
//static volatile bool print_next_fft = false;
 | 
				
			||||
 | 
				
			||||
#define SAMP_BUF_LEN 256 | 
				
			||||
 | 
				
			||||
union samp_buf_union { | 
				
			||||
	uint32_t uints[SAMP_BUF_LEN]; | 
				
			||||
	float floats[SAMP_BUF_LEN]; | 
				
			||||
	uint8_t as_bytes[SAMP_BUF_LEN * sizeof(uint32_t)]; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// sample buffers (static - invalidated when sampling starts anew).
 | 
				
			||||
static union samp_buf_union samp_buf; | 
				
			||||
 | 
				
			||||
static task_pid_t capture_task_id; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// prototypes
 | 
				
			||||
static void audio_capture_done(void* unused); | 
				
			||||
static void capture_audio(void *unused); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
static void boot_animation(void) | 
				
			||||
{ | 
				
			||||
	dmtx_clear(dmtx); | 
				
			||||
 | 
				
			||||
	// Boot animation (for FFT)
 | 
				
			||||
	for (int i = 0; i < SCREEN_W; i++) { | 
				
			||||
		dmtx_set(dmtx, i, 0, 1); | 
				
			||||
		dmtx_show(dmtx); | 
				
			||||
		delay_ms(20); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** Init audio mode */ | 
				
			||||
void mode_audio_init(void) | 
				
			||||
{ | 
				
			||||
	capture_task_id = add_periodic_task(capture_audio, NULL, 10, false); | 
				
			||||
	enable_periodic_task(capture_task_id, false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Start audio mode */ | 
				
			||||
void mode_audio_start(void) | 
				
			||||
{ | 
				
			||||
	boot_animation(); | 
				
			||||
 | 
				
			||||
	audio_mode_active = true; | 
				
			||||
	enable_periodic_task(capture_task_id, true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Stop audio mode */ | 
				
			||||
void mode_audio_stop(void) | 
				
			||||
{ | 
				
			||||
	audio_mode_active = false; | 
				
			||||
	enable_periodic_task(capture_task_id, false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Start DMA capture */ | 
				
			||||
static void start_adc_dma(uint32_t *memory, uint32_t count) | 
				
			||||
{ | 
				
			||||
	ADC_Cmd(ADC1, DISABLE); | 
				
			||||
	DMA_DeInit(DMA1_Channel1); | 
				
			||||
	DMA_InitTypeDef dma_cnf; | 
				
			||||
	dma_cnf.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; | 
				
			||||
	dma_cnf.DMA_MemoryBaseAddr = (uint32_t)memory; | 
				
			||||
	dma_cnf.DMA_DIR = DMA_DIR_PeripheralSRC; | 
				
			||||
	dma_cnf.DMA_BufferSize = count; | 
				
			||||
	dma_cnf.DMA_PeripheralInc = DMA_PeripheralInc_Disable; | 
				
			||||
	dma_cnf.DMA_MemoryInc = DMA_MemoryInc_Enable; | 
				
			||||
	dma_cnf.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; | 
				
			||||
	dma_cnf.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; | 
				
			||||
	dma_cnf.DMA_Mode = DMA_Mode_Normal; | 
				
			||||
	dma_cnf.DMA_Priority = DMA_Priority_Low; | 
				
			||||
	dma_cnf.DMA_M2M = DMA_M2M_Disable; | 
				
			||||
	DMA_Init(DMA1_Channel1, &dma_cnf); | 
				
			||||
	DMA_ITConfig(DMA1_Channel1, DMA1_IT_TC1, ENABLE); | 
				
			||||
 | 
				
			||||
	ADC_Cmd(ADC1, ENABLE); | 
				
			||||
	ADC_DMACmd(ADC1, ENABLE); | 
				
			||||
	DMA_Cmd(DMA1_Channel1, ENABLE); | 
				
			||||
	TIM_Cmd(TIM3, ENABLE); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** IRQ */ | 
				
			||||
void DMA1_Channel1_IRQHandler(void) | 
				
			||||
{ | 
				
			||||
	DMA_ClearITPendingBit(DMA1_IT_TC1); | 
				
			||||
	DMA_ClearITPendingBit(DMA1_IT_TE1); | 
				
			||||
 | 
				
			||||
	DMA_DeInit(DMA1_Channel1); | 
				
			||||
	TIM_Cmd(TIM3, DISABLE); | 
				
			||||
	ADC_DMACmd(ADC1, DISABLE); | 
				
			||||
 | 
				
			||||
	tq_post(audio_capture_done, NULL); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** Capture done callback */ | 
				
			||||
static void audio_capture_done(void* unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
 | 
				
			||||
	if (! audio_mode_active) { | 
				
			||||
		capture_pending = false; | 
				
			||||
		return; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	const int samp_count = SAMP_BUF_LEN / 2; | 
				
			||||
	const int bin_count = SAMP_BUF_LEN / 4; | 
				
			||||
 | 
				
			||||
	float *bins = samp_buf.floats; | 
				
			||||
 | 
				
			||||
	// Convert to floats
 | 
				
			||||
	for (int i = 0; i < samp_count; i++) { | 
				
			||||
		samp_buf.floats[i] = (float)samp_buf.uints[i]; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// normalize
 | 
				
			||||
	float mean; | 
				
			||||
	arm_mean_f32(samp_buf.floats, samp_count, &mean); | 
				
			||||
 | 
				
			||||
	for (int i = 0; i < samp_count; i++) { | 
				
			||||
		samp_buf.floats[i] -= mean; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	for (int i = samp_count - 1; i >= 0; i--) { | 
				
			||||
		bins[i * 2 + 1] = 0;              // imaginary
 | 
				
			||||
		bins[i * 2] = samp_buf.floats[i]; // real
 | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	const arm_cfft_instance_f32 *S; | 
				
			||||
	S = &arm_cfft_sR_f32_len128; | 
				
			||||
 | 
				
			||||
	arm_cfft_f32(S, bins, 0, true); // bit reversed FFT
 | 
				
			||||
	arm_cmplx_mag_f32(bins, bins, bin_count); // get magnitude (extract real values)
 | 
				
			||||
 | 
				
			||||
	// normalize
 | 
				
			||||
	dmtx_clear(dmtx); | 
				
			||||
 | 
				
			||||
	float factor = (1.0f / bin_count) * 0.25f; | 
				
			||||
	for (int i = 0; i < MIN(bin_count, SCREEN_W) + 1; i++) { // +1 because bin 0 is always 0
 | 
				
			||||
		bins[i] *= factor; | 
				
			||||
 | 
				
			||||
		for (int j = 0; j < 1 + floorf(bins[i]); j++) { | 
				
			||||
			dmtx_set(dmtx, i - 1, j, 1); // hide zero 0th bin
 | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	dmtx_show(dmtx); | 
				
			||||
 | 
				
			||||
	capture_pending = false; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void capture_audio(void *unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
	if (capture_pending) return; | 
				
			||||
	if (! audio_mode_active) return; | 
				
			||||
 | 
				
			||||
	capture_pending = true; | 
				
			||||
 | 
				
			||||
	start_adc_dma(samp_buf.uints, SAMP_BUF_LEN / 2); | 
				
			||||
} | 
				
			||||
@ -0,0 +1,10 @@ | 
				
			||||
#ifndef MODE_AUDIO_H | 
				
			||||
#define MODE_AUDIO_H | 
				
			||||
 | 
				
			||||
#include "main.h" | 
				
			||||
 | 
				
			||||
void mode_audio_init(void); | 
				
			||||
void mode_audio_start(void); | 
				
			||||
void mode_audio_stop(void); | 
				
			||||
 | 
				
			||||
#endif // MODE_AUDIO_H
 | 
				
			||||
@ -0,0 +1,465 @@ | 
				
			||||
#include "mode_life.h" | 
				
			||||
#include "utils/timebase.h" | 
				
			||||
#include "com/debug.h" | 
				
			||||
 | 
				
			||||
#include "dotmatrix.h" | 
				
			||||
 | 
				
			||||
static bool life_enabled = false; | 
				
			||||
 | 
				
			||||
static void show_board(bool do_show); | 
				
			||||
static void apply_paint(void); | 
				
			||||
 | 
				
			||||
#define BOARD_W SCREEN_W | 
				
			||||
#define BOARD_H SCREEN_H | 
				
			||||
 | 
				
			||||
#define SIZEOF_BOARD (BOARD_H*sizeof(uint32_t)) | 
				
			||||
 | 
				
			||||
// 32-bit for easy blit
 | 
				
			||||
static bool is_orig_board = true; // marks that currently displayed board is the orig board to reset back to.
 | 
				
			||||
static uint32_t board_orig[BOARD_H]; | 
				
			||||
 | 
				
			||||
static uint32_t board_prev[BOARD_H]; | 
				
			||||
static uint32_t board[BOARD_H]; | 
				
			||||
 | 
				
			||||
static task_pid_t task_gametick; // game update tick
 | 
				
			||||
static task_pid_t task_movetick; // repeated move
 | 
				
			||||
static task_pid_t task_start_moveticks; // scheduled task to start moveticks after a delay
 | 
				
			||||
 | 
				
			||||
static task_pid_t task_movepress; // move on press (delayed to allow for diagonals)
 | 
				
			||||
 | 
				
			||||
static task_pid_t task_blink; // periodic blink task
 | 
				
			||||
static task_pid_t task_blink2; // scheduled 'unblink' task
 | 
				
			||||
 | 
				
			||||
static ms_time_t step_time = 200; // game step time
 | 
				
			||||
 | 
				
			||||
#define BLINKLEN 350 // blink on time (total)
 | 
				
			||||
#define BLINKLEN_ONE 300 // blink on time for '1'
 | 
				
			||||
#define BLINKLEN_ZERO 10 // blink on time for '0'
 | 
				
			||||
 | 
				
			||||
#define MOVE_TIME 80 // mouse move
 | 
				
			||||
#define MOVE_START_TIME 600 | 
				
			||||
 | 
				
			||||
#define WRAPPING 0 | 
				
			||||
 | 
				
			||||
static int cursorX = BOARD_W / 2 - 1; | 
				
			||||
static int cursorY = BOARD_H / 2 - 1; | 
				
			||||
 | 
				
			||||
static bool running = false; | 
				
			||||
 | 
				
			||||
static bool painting_1 = false; | 
				
			||||
static bool painting_0 = false; | 
				
			||||
 | 
				
			||||
static bool modA_down = false; | 
				
			||||
static bool modB_down = false; | 
				
			||||
 | 
				
			||||
// X+,X-,Y+,Y-
 | 
				
			||||
typedef enum { | 
				
			||||
	DIR_NONE = 0b0000, | 
				
			||||
	// straight
 | 
				
			||||
	DIR_N = 0b0010, | 
				
			||||
	DIR_S = 0b0001, | 
				
			||||
	DIR_E = 0b1000, | 
				
			||||
	DIR_W = 0b0100, | 
				
			||||
	// diagonals
 | 
				
			||||
	DIR_NE = DIR_N | DIR_E, | 
				
			||||
	DIR_NW = DIR_N | DIR_W, | 
				
			||||
	DIR_SE = DIR_S | DIR_E, | 
				
			||||
	DIR_SW = DIR_S | DIR_W, | 
				
			||||
} MoveDir; | 
				
			||||
 | 
				
			||||
static MoveDir active_dir = DIR_NONE; | 
				
			||||
 | 
				
			||||
// ---
 | 
				
			||||
 | 
				
			||||
static void board_set(int x, int y, bool color) | 
				
			||||
{ | 
				
			||||
	if (x < 0 || y < 0 || x >= BOARD_W || y >= BOARD_H) return; | 
				
			||||
 | 
				
			||||
	uint32_t mask = 1 << (BOARD_W - x - 1); | 
				
			||||
 | 
				
			||||
	if (color) { | 
				
			||||
		board[y] |= mask; | 
				
			||||
	} else { | 
				
			||||
		board[y] &= ~mask; | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static bool board_get(uint32_t *brd, int x, int y) | 
				
			||||
{ | 
				
			||||
#if WRAPPING | 
				
			||||
	while (x < 0) x += BOARD_W; | 
				
			||||
	while (y < 0) y += BOARD_H; | 
				
			||||
	while (x >= BOARD_W) x -= BOARD_W; | 
				
			||||
	while (y >= BOARD_H) y -= BOARD_H; | 
				
			||||
#else | 
				
			||||
	if (x < 0 || y < 0 || x >= BOARD_W || y >= BOARD_H) return 0; | 
				
			||||
#endif | 
				
			||||
 | 
				
			||||
	return (brd[y] >> (BOARD_W - x - 1)) & 1; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void move_cursor(void) | 
				
			||||
{ | 
				
			||||
	if (active_dir & DIR_N) { | 
				
			||||
		cursorY++; | 
				
			||||
	} else if (active_dir & DIR_S) { | 
				
			||||
		cursorY--; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	if (active_dir & DIR_E) { | 
				
			||||
		cursorX++; | 
				
			||||
	} else if (active_dir & DIR_W) { | 
				
			||||
		cursorX--; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// clamp
 | 
				
			||||
	if (cursorX >= BOARD_W) cursorX = BOARD_W - 1; | 
				
			||||
	if (cursorX < 0) cursorX = 0; | 
				
			||||
 | 
				
			||||
	if (cursorY >= BOARD_H) cursorY = BOARD_H - 1; | 
				
			||||
	if (cursorY < 0) cursorY = 0; | 
				
			||||
 | 
				
			||||
	// apply paint if active
 | 
				
			||||
	apply_paint(); | 
				
			||||
 | 
				
			||||
	show_board(true); | 
				
			||||
	dmtx_set(dmtx, cursorX, cursorY, 1); | 
				
			||||
	abort_scheduled_task(task_blink2); // abort unblink task FIXME ???
 | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void apply_paint(void) | 
				
			||||
{ | 
				
			||||
	if (painting_0) { | 
				
			||||
		board_set(cursorX, cursorY, 0); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	if (painting_1) { | 
				
			||||
		board_set(cursorX, cursorY, 1); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** start periodic move */ | 
				
			||||
static void start_moveticks_cb(void *unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
	if (!life_enabled) return; | 
				
			||||
 | 
				
			||||
	task_start_moveticks = 0; | 
				
			||||
	enable_periodic_task(task_movetick, true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** Perform one game step */ | 
				
			||||
static void gametick_cb(void *unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
	if (!life_enabled) return; | 
				
			||||
 | 
				
			||||
	dbg("Game tick!"); | 
				
			||||
 | 
				
			||||
	memcpy(board_prev, board, SIZEOF_BOARD); | 
				
			||||
	for (int i = 0; i < BOARD_W; i++) { | 
				
			||||
		for (int j = 0; j < BOARD_H; j++) { | 
				
			||||
			int count = 0; | 
				
			||||
 | 
				
			||||
			// Above
 | 
				
			||||
			count += board_get(board_prev, i - 1, j - 1); | 
				
			||||
			count += board_get(board_prev, i, j - 1); | 
				
			||||
			count += board_get(board_prev, i + 1, j - 1); | 
				
			||||
 | 
				
			||||
			// Sides
 | 
				
			||||
			count += board_get(board_prev, i - 1, j); | 
				
			||||
			count += board_get(board_prev, i + 1, j); | 
				
			||||
 | 
				
			||||
			// Below
 | 
				
			||||
			count += board_get(board_prev, i - 1, j + 1); | 
				
			||||
			count += board_get(board_prev, i, j + 1); | 
				
			||||
			count += board_get(board_prev, i + 1, j + 1); | 
				
			||||
 | 
				
			||||
			bool at = board_get(board_prev, i, j); | 
				
			||||
 | 
				
			||||
			//board_set(i, j, count == 2 || count == 3);
 | 
				
			||||
 | 
				
			||||
			if (at) { | 
				
			||||
				// live cell
 | 
				
			||||
				board_set(i, j, count == 2 || count == 3); | 
				
			||||
			} else { | 
				
			||||
				board_set(i, j, count == 3); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	show_board(true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Move cursor one step in active direction */ | 
				
			||||
static void movetick_cb(void *unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
	if (!life_enabled) return; | 
				
			||||
 | 
				
			||||
	move_cursor(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void blink_cb(void *arg) | 
				
			||||
{ | 
				
			||||
	if (!life_enabled) return; // pending leftover...
 | 
				
			||||
 | 
				
			||||
	uint32_t which = (uint32_t)arg; | 
				
			||||
 | 
				
			||||
	if (which == 0) { | 
				
			||||
		// first
 | 
				
			||||
		show_board(false); | 
				
			||||
 | 
				
			||||
		bool at = board_get(board, cursorX, cursorY); | 
				
			||||
		dmtx_set(dmtx, cursorX, cursorY, 1); | 
				
			||||
		dmtx_show(dmtx); | 
				
			||||
 | 
				
			||||
		// schedule unblink
 | 
				
			||||
		schedule_task(blink_cb, (void*)1, at ? BLINKLEN_ONE : BLINKLEN_ZERO, true); | 
				
			||||
 | 
				
			||||
	} else { | 
				
			||||
		show_board(false); | 
				
			||||
		dmtx_set(dmtx, cursorX, cursorY, 0); | 
				
			||||
		dmtx_show(dmtx); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// ---
 | 
				
			||||
 | 
				
			||||
static void show_board(bool do_show) | 
				
			||||
{ | 
				
			||||
	dmtx_set_block(dmtx, 0, 0, board, 16, 16); | 
				
			||||
	if (do_show) dmtx_show(dmtx); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// ---
 | 
				
			||||
 | 
				
			||||
void mode_life_init(void) | 
				
			||||
{ | 
				
			||||
	// prepare tasks
 | 
				
			||||
	task_gametick = add_periodic_task(gametick_cb, NULL, step_time, true); | 
				
			||||
	task_movetick = add_periodic_task(movetick_cb, NULL, MOVE_TIME, true); | 
				
			||||
	task_blink = add_periodic_task(blink_cb, 0, BLINKLEN, true); | 
				
			||||
 | 
				
			||||
	// stop all - we may not be starting this mode just yet
 | 
				
			||||
	enable_periodic_task(task_gametick, false); | 
				
			||||
	enable_periodic_task(task_movetick, false); | 
				
			||||
	enable_periodic_task(task_blink, false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void mode_life_start(void) | 
				
			||||
{ | 
				
			||||
	life_enabled = true; | 
				
			||||
 | 
				
			||||
	enable_periodic_task(task_blink, true); | 
				
			||||
 | 
				
			||||
//	if (running) {
 | 
				
			||||
//		enable_periodic_task(task_gametick, true);
 | 
				
			||||
//	} else {
 | 
				
			||||
//		enable_periodic_task(task_blink, true);
 | 
				
			||||
//	}
 | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void mode_life_stop(void) | 
				
			||||
{ | 
				
			||||
	enable_periodic_task(task_gametick, false); | 
				
			||||
	enable_periodic_task(task_movetick, false); | 
				
			||||
	enable_periodic_task(task_blink, false); | 
				
			||||
 | 
				
			||||
	abort_scheduled_task(task_blink2); | 
				
			||||
	abort_scheduled_task(task_movepress); | 
				
			||||
	abort_scheduled_task(task_start_moveticks); | 
				
			||||
 | 
				
			||||
	painting_1 = painting_0 = false; | 
				
			||||
	modA_down = modB_down = false; | 
				
			||||
	running = false; // stop
 | 
				
			||||
 | 
				
			||||
	life_enabled = false; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
static void toggle_game(bool run) | 
				
			||||
{ | 
				
			||||
	if (run) { | 
				
			||||
		running = true; | 
				
			||||
		enable_periodic_task(task_movetick, false); | 
				
			||||
		enable_periodic_task(task_blink, false); | 
				
			||||
		abort_scheduled_task(task_movepress); | 
				
			||||
		abort_scheduled_task(task_start_moveticks); | 
				
			||||
 | 
				
			||||
		// Go!!
 | 
				
			||||
		enable_periodic_task(task_gametick, true); | 
				
			||||
	} else { | 
				
			||||
		running = false; | 
				
			||||
		enable_periodic_task(task_gametick, false); | 
				
			||||
		enable_periodic_task(task_blink, true); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	show_board(true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** do move (had ample time to press both arrows for diagonal) */ | 
				
			||||
static void movepress_cb(void *unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
	if (!life_enabled) return; | 
				
			||||
 | 
				
			||||
	move_cursor(); | 
				
			||||
	task_movepress = 0; | 
				
			||||
 | 
				
			||||
	task_start_moveticks = schedule_task(start_moveticks_cb, NULL, MOVE_START_TIME, true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void mode_life_btn(char key) | 
				
			||||
{ | 
				
			||||
	// Common for run / stop mode
 | 
				
			||||
	switch (key) { | 
				
			||||
		case 'A': modA_down = true; break; | 
				
			||||
		case 'a': modA_down = false; break; | 
				
			||||
		case 'B': modB_down = true; break; | 
				
			||||
		case 'b': modB_down = false; break; | 
				
			||||
 | 
				
			||||
		case 'K': | 
				
			||||
			// Reset btn
 | 
				
			||||
			if (modB_down) { | 
				
			||||
				// total reset
 | 
				
			||||
				dbg("Reset to blank"); | 
				
			||||
				memset(board_orig, 0, SIZEOF_BOARD); | 
				
			||||
				memset(board, 0, SIZEOF_BOARD); | 
				
			||||
 | 
				
			||||
				toggle_game(false); | 
				
			||||
				is_orig_board = true; | 
				
			||||
			} else { | 
				
			||||
				// reset to orig
 | 
				
			||||
				dbg("Reset to original"); | 
				
			||||
				memcpy(board, board_orig, SIZEOF_BOARD); | 
				
			||||
				toggle_game(false); | 
				
			||||
				is_orig_board = true; | 
				
			||||
			} | 
				
			||||
			break; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
	if (running) { | 
				
			||||
 | 
				
			||||
		switch (key) { | 
				
			||||
			case 'Y': // slower
 | 
				
			||||
				if (step_time < 1000) { | 
				
			||||
					step_time += 50; | 
				
			||||
					set_periodic_task_interval(task_gametick, step_time); | 
				
			||||
				} | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'X': // faster
 | 
				
			||||
				if (step_time > 50) { | 
				
			||||
					step_time -= 50; | 
				
			||||
					set_periodic_task_interval(task_gametick, step_time); | 
				
			||||
				} | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'J': // stop
 | 
				
			||||
				dbg("STOP game!"); | 
				
			||||
				toggle_game(false); | 
				
			||||
				break; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
	} else { | 
				
			||||
 | 
				
			||||
		bool want_move = false; | 
				
			||||
		bool clear_moveticks = false; | 
				
			||||
		bool want_apply_paint = false; | 
				
			||||
 | 
				
			||||
		// Groups
 | 
				
			||||
		switch (key) { | 
				
			||||
			case 'U': | 
				
			||||
			case 'D': | 
				
			||||
			case 'R': | 
				
			||||
			case 'L': | 
				
			||||
				want_move = true; | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'u': | 
				
			||||
			case 'd': | 
				
			||||
			case 'r': | 
				
			||||
			case 'l': | 
				
			||||
				clear_moveticks = true; | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'Y': // AA
 | 
				
			||||
			case 'X': // BB
 | 
				
			||||
				want_apply_paint = true; | 
				
			||||
				break; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		// Individual effects
 | 
				
			||||
		switch (key) { | 
				
			||||
			// --- ARROW DOWN ---
 | 
				
			||||
 | 
				
			||||
			case 'U': | 
				
			||||
				active_dir &= ~DIR_S; | 
				
			||||
				active_dir |= DIR_N; | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'D': | 
				
			||||
				active_dir &= ~DIR_N; | 
				
			||||
				active_dir |= DIR_S; | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'R': | 
				
			||||
				active_dir &= ~DIR_W; | 
				
			||||
				active_dir |= DIR_E; | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			case 'L': | 
				
			||||
				active_dir &= ~DIR_E; | 
				
			||||
				active_dir |= DIR_W; | 
				
			||||
				break; | 
				
			||||
 | 
				
			||||
			// --- ARROW UP ---
 | 
				
			||||
 | 
				
			||||
			case 'u': active_dir &= ~DIR_N; break; | 
				
			||||
			case 'd': active_dir &= ~DIR_S; break; | 
				
			||||
			case 'r': active_dir &= ~DIR_E; break; | 
				
			||||
			case 'l': active_dir &= ~DIR_W; break; | 
				
			||||
 | 
				
			||||
			// --- Paint BTN ---
 | 
				
			||||
 | 
				
			||||
			case 'X': painting_1 = true; break; | 
				
			||||
			case 'x': painting_1 = false; break; | 
				
			||||
			case 'Y': painting_0 = true; break; | 
				
			||||
			case 'y': painting_0 = false; break; | 
				
			||||
 | 
				
			||||
			// --- Control ---
 | 
				
			||||
 | 
				
			||||
			case 'J': | 
				
			||||
				dbg("Start game!"); | 
				
			||||
 | 
				
			||||
				// starting
 | 
				
			||||
				if (is_orig_board) { | 
				
			||||
					// save
 | 
				
			||||
					memcpy(board_orig, board, SIZEOF_BOARD); | 
				
			||||
				} | 
				
			||||
 | 
				
			||||
				is_orig_board = false; | 
				
			||||
 | 
				
			||||
				toggle_game(true); | 
				
			||||
				break; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (clear_moveticks) { | 
				
			||||
			enable_periodic_task(task_movetick, false); | 
				
			||||
			abort_scheduled_task(task_start_moveticks); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (want_apply_paint) { | 
				
			||||
			apply_paint(); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if (want_move) { | 
				
			||||
			if (task_movepress == 0) { | 
				
			||||
				task_movepress = schedule_task(movepress_cb, NULL, 10, true); | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,11 @@ | 
				
			||||
#ifndef MODE_LIFE_H | 
				
			||||
#define MODE_LIFE_H | 
				
			||||
 | 
				
			||||
#include "main.h" | 
				
			||||
 | 
				
			||||
void mode_life_init(void); | 
				
			||||
void mode_life_start(void); | 
				
			||||
void mode_life_stop(void); | 
				
			||||
void mode_life_btn(char key); | 
				
			||||
 | 
				
			||||
#endif // MODE_LIFE_H
 | 
				
			||||
@ -0,0 +1,560 @@ | 
				
			||||
#include "mode_snake.h" | 
				
			||||
#include "com/debug.h" | 
				
			||||
#include "dotmatrix.h" | 
				
			||||
#include "utils/timebase.h" | 
				
			||||
 | 
				
			||||
#include "scrolltext.h" | 
				
			||||
 | 
				
			||||
#define BOARD_W SCREEN_W | 
				
			||||
#define BOARD_H SCREEN_H | 
				
			||||
 | 
				
			||||
static bool snake_active = false; | 
				
			||||
 | 
				
			||||
/** Snake movement direction */ | 
				
			||||
typedef enum { | 
				
			||||
	NORTH, | 
				
			||||
	SOUTH, | 
				
			||||
	EAST, | 
				
			||||
	WEST | 
				
			||||
} Direction; | 
				
			||||
 | 
				
			||||
typedef enum { | 
				
			||||
	CELL_EMPTY, | 
				
			||||
	CELL_BODY, | 
				
			||||
	CELL_FOOD, | 
				
			||||
	CELL_WALL | 
				
			||||
} CellType; | 
				
			||||
 | 
				
			||||
/** Board cell */ | 
				
			||||
typedef struct __attribute__((packed)) | 
				
			||||
{ | 
				
			||||
	CellType type : 2; // Cell type
 | 
				
			||||
	Direction dir : 2; // direction the head moved to from this tile, if body = 1
 | 
				
			||||
} Cell; | 
				
			||||
 | 
				
			||||
/** Wall tile, invariant, used for out-of-screen coords */ | 
				
			||||
static Cell WALL_TILE = {CELL_WALL, 0}; | 
				
			||||
 | 
				
			||||
/** Game board */ | 
				
			||||
static Cell board[BOARD_H][BOARD_W]; | 
				
			||||
 | 
				
			||||
typedef struct { | 
				
			||||
	int x; | 
				
			||||
	int y; | 
				
			||||
} Coord; | 
				
			||||
 | 
				
			||||
/** Snake coords */ | 
				
			||||
static Coord head; | 
				
			||||
static Coord tail; | 
				
			||||
static Direction head_dir; | 
				
			||||
 | 
				
			||||
/** Limit dir changes to 1 per move */ | 
				
			||||
static bool changed_dir_this_tick = false; | 
				
			||||
 | 
				
			||||
/** blinking to visually change 'color' */ | 
				
			||||
 | 
				
			||||
typedef struct __attribute__((packed)) | 
				
			||||
{ | 
				
			||||
	bool pwm_bit : 1; | 
				
			||||
	bool offtask_pending : 1; | 
				
			||||
	task_pid_t on_task; // periodic
 | 
				
			||||
	task_pid_t off_task; // scheduled
 | 
				
			||||
	ms_time_t interval_ms; | 
				
			||||
	ms_time_t offtime_ms; | 
				
			||||
} snake_pwm; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
static bool alive = false; | 
				
			||||
static bool moving = false; | 
				
			||||
static task_pid_t task_snake_move; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
static snake_pwm food_pwm = { | 
				
			||||
	.interval_ms = 400, | 
				
			||||
	.offtime_ms = 330 | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
static snake_pwm head_pwm = { | 
				
			||||
	.interval_ms = 100, | 
				
			||||
	.offtime_ms = 90 | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
#define MIN_MOVE_INTERVAL 64 | 
				
			||||
#define POINTS_TO_LEVEL_UP 5 | 
				
			||||
#define PIECES_REMOVED_ON_LEVEL_UP 4 | 
				
			||||
 | 
				
			||||
#define PREDEFINED_LEVELS 10 | 
				
			||||
static const ms_time_t speeds_for_levels[PREDEFINED_LEVELS] = { | 
				
			||||
	260, 200, 160, 120, 95, 80, 65, 55, 50, 45 | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
static ms_time_t move_interval; | 
				
			||||
 | 
				
			||||
static uint32_t score; | 
				
			||||
static uint32_t level_up_score; // counter
 | 
				
			||||
static uint32_t level; | 
				
			||||
 | 
				
			||||
// used to reduce length seamlessly after a level-up
 | 
				
			||||
static uint32_t double_tail_move_cnt = 0; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
static Cell *cell_at_xy(int x, int y); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// ---
 | 
				
			||||
 | 
				
			||||
static void show_board(void) | 
				
			||||
{ | 
				
			||||
	if (!snake_active) return; | 
				
			||||
 | 
				
			||||
	dmtx_clear(dmtx); | 
				
			||||
 | 
				
			||||
	for (int x = 0; x < BOARD_W; x++) { | 
				
			||||
		for (int y = 0; y < BOARD_H; y++) { | 
				
			||||
			Cell *cell = cell_at_xy(x, y); | 
				
			||||
 | 
				
			||||
			bool set = 0; | 
				
			||||
 | 
				
			||||
			switch (cell->type) { | 
				
			||||
				case CELL_EMPTY: set = 0; break; | 
				
			||||
				case CELL_BODY: set = 1; break; | 
				
			||||
				case CELL_FOOD: | 
				
			||||
					set = food_pwm.pwm_bit; | 
				
			||||
					break; | 
				
			||||
 | 
				
			||||
				case CELL_WALL: set = 1; break; | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			if (x == head.x && y == head.y) set = head_pwm.pwm_bit; | 
				
			||||
 | 
				
			||||
			dmtx_set(dmtx, x, y, set); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	dmtx_show(dmtx); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// --- Snake PWM ---
 | 
				
			||||
 | 
				
			||||
/** Turn off a PWM bit (scheduled callback) */ | 
				
			||||
static void task_pwm_off_cb(void *ptr) | 
				
			||||
{ | 
				
			||||
	if (!snake_active) return; | 
				
			||||
	snake_pwm* pwm = ptr; | 
				
			||||
 | 
				
			||||
	if (!pwm->offtask_pending) return; | 
				
			||||
	pwm->offtask_pending = false; | 
				
			||||
 | 
				
			||||
	pwm->pwm_bit = 0; | 
				
			||||
	show_board(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Turn on a PWM bit and schedule the turn-off task (periodic callback) */ | 
				
			||||
static void task_pwm_on_cb(void *ptr) | 
				
			||||
{ | 
				
			||||
	if (!snake_active) return; | 
				
			||||
	snake_pwm* pwm = ptr; | 
				
			||||
 | 
				
			||||
	pwm->pwm_bit = 1; | 
				
			||||
	show_board(); | 
				
			||||
 | 
				
			||||
	pwm->offtask_pending = true; | 
				
			||||
	pwm->off_task = schedule_task(task_pwm_off_cb, ptr, pwm->offtime_ms, true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Initialize a snake PWM channel */ | 
				
			||||
static void snake_pwm_init(snake_pwm *pwm) | 
				
			||||
{ | 
				
			||||
	pwm->on_task = add_periodic_task(task_pwm_on_cb, pwm, pwm->interval_ms, true); | 
				
			||||
	enable_periodic_task(pwm->on_task, false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Clear &  start a snake PWM channel */ | 
				
			||||
static void snake_pwm_start(snake_pwm *pwm) | 
				
			||||
{ | 
				
			||||
	pwm->pwm_bit = 1; | 
				
			||||
	pwm->offtask_pending = false; | 
				
			||||
	reset_periodic_task(pwm->on_task); | 
				
			||||
	enable_periodic_task(pwm->on_task, true); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Stop a snake PWM channel */ | 
				
			||||
static void snake_pwm_stop(snake_pwm *pwm) | 
				
			||||
{ | 
				
			||||
	pwm->offtask_pending = false; | 
				
			||||
	enable_periodic_task(pwm->on_task, false); | 
				
			||||
	abort_scheduled_task(pwm->off_task); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Get board cell at coord (x,y) */ | 
				
			||||
static Cell *cell_at_xy(int x, int y) | 
				
			||||
{ | 
				
			||||
	if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H) { | 
				
			||||
		return &WALL_TILE; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	return &board[y][x]; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Get board cell at coord */ | 
				
			||||
static Cell *cell_at(Coord *coord) | 
				
			||||
{ | 
				
			||||
	return cell_at_xy(coord->x, coord->y); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Place food in a random empty tile */ | 
				
			||||
static void place_food(void) | 
				
			||||
{ | 
				
			||||
	Coord food; | 
				
			||||
 | 
				
			||||
	// 1000 tries - timeout in case board is full of snake (?)
 | 
				
			||||
	for (int i = 0; i < 1000; i++) { | 
				
			||||
		food.x = rand() % BOARD_W; | 
				
			||||
		food.y = rand() % BOARD_H; | 
				
			||||
 | 
				
			||||
		Cell *cell = cell_at(&food); | 
				
			||||
		if (cell->type == CELL_EMPTY) { | 
				
			||||
			cell->type = CELL_FOOD; | 
				
			||||
 | 
				
			||||
			dbg("Placed food at [%d, %d]", food.x, food.y); | 
				
			||||
			break; | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// avoid "doubleblink" glitch
 | 
				
			||||
	reset_periodic_task(food_pwm.on_task); | 
				
			||||
	abort_scheduled_task(food_pwm.off_task); | 
				
			||||
	task_pwm_on_cb(&food_pwm); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Clear the board and prepare for a new game */ | 
				
			||||
static void new_game(void) | 
				
			||||
{ | 
				
			||||
	dbg("Snake NEW GAME"); | 
				
			||||
 | 
				
			||||
	// Stop snake (make sure it's stopped)
 | 
				
			||||
	reset_periodic_task(task_snake_move); | 
				
			||||
	enable_periodic_task(task_snake_move, false); | 
				
			||||
 | 
				
			||||
	moving = false; | 
				
			||||
	alive = true; | 
				
			||||
 | 
				
			||||
	changed_dir_this_tick = false; | 
				
			||||
	double_tail_move_cnt = 0; | 
				
			||||
 | 
				
			||||
	level = 0; | 
				
			||||
	score = 0; | 
				
			||||
	move_interval = speeds_for_levels[level]; | 
				
			||||
	level_up_score = 0; | 
				
			||||
	set_periodic_task_interval(task_snake_move, move_interval); | 
				
			||||
 | 
				
			||||
	// randomize (we can assume it takes random time before the user switches to the Snake mode)
 | 
				
			||||
	srand(SysTick->VAL); | 
				
			||||
 | 
				
			||||
	// Erase the board
 | 
				
			||||
	memset(board, 0, sizeof(board)); | 
				
			||||
 | 
				
			||||
	// Place initial snake
 | 
				
			||||
 | 
				
			||||
	tail.x = BOARD_W / 2 - 5; | 
				
			||||
	tail.y = BOARD_H / 2; | 
				
			||||
 | 
				
			||||
	head.x = tail.x + 4; | 
				
			||||
	head.y = tail.y; | 
				
			||||
 | 
				
			||||
	head_dir = EAST; | 
				
			||||
 | 
				
			||||
	for (int x = tail.x; x < head.x; x++) { | 
				
			||||
		board[tail.y][x].type = CELL_BODY; | 
				
			||||
		board[tail.y][x].dir = EAST; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	place_food(); | 
				
			||||
 | 
				
			||||
	snake_pwm_start(&head_pwm); | 
				
			||||
	snake_pwm_start(&food_pwm); | 
				
			||||
 | 
				
			||||
	show_board(); | 
				
			||||
 | 
				
			||||
	// Discard keys pressed during the death animation
 | 
				
			||||
	uint8_t x; | 
				
			||||
	while(com_rx(gamepad_iface, &x)); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static bool press_any_key(void) | 
				
			||||
{ | 
				
			||||
	bool press = false; | 
				
			||||
	if (com_rx_rdy(gamepad_iface)) { | 
				
			||||
		uint8_t x; | 
				
			||||
		while(com_rx(gamepad_iface, &x)) { | 
				
			||||
			if (x >= 'A' && x <= 'Z') press = true; | 
				
			||||
		} | 
				
			||||
		return press; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	return false; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void snake_died(void) | 
				
			||||
{ | 
				
			||||
	dbg("R.I.P. Snake"); | 
				
			||||
	dbg("Total Score %d, Level %d", score, level); | 
				
			||||
 | 
				
			||||
	moving = false; | 
				
			||||
	alive = false; | 
				
			||||
	enable_periodic_task(task_snake_move, false); | 
				
			||||
 | 
				
			||||
	// stop blinky animation of the snake head.
 | 
				
			||||
	snake_pwm_stop(&head_pwm); | 
				
			||||
	head_pwm.pwm_bit = 1; | 
				
			||||
 | 
				
			||||
	// Let it sink...
 | 
				
			||||
	until_timeout(600) { | 
				
			||||
		tq_poll(); // take care of periodic tasks (anim)
 | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	snake_pwm_stop(&food_pwm); | 
				
			||||
	show_board(); | 
				
			||||
 | 
				
			||||
	snake_active = false; // suppress pending callbacks
 | 
				
			||||
 | 
				
			||||
	delay_ms(400); | 
				
			||||
	dmtx_clear(dmtx); | 
				
			||||
 | 
				
			||||
	// score
 | 
				
			||||
	char txt[6]; | 
				
			||||
	sprintf(txt, "%d", score); | 
				
			||||
	size_t len = strlen(txt); | 
				
			||||
	printtext(txt, (int)(SCREEN_W / 2 - (len * 5) / 2) - 1, SCREEN_H / 2 - 4); | 
				
			||||
 | 
				
			||||
	dmtx_show(dmtx); | 
				
			||||
 | 
				
			||||
	// discard input
 | 
				
			||||
	uint8_t x; | 
				
			||||
	while(com_rx(gamepad_iface, &x)); | 
				
			||||
 | 
				
			||||
	bool interr = false; | 
				
			||||
	until_timeout(10000) { | 
				
			||||
 | 
				
			||||
		for (int x = 0; x < SCREEN_W; x++) { | 
				
			||||
			dmtx_set(dmtx, SCREEN_W - x - 1, 0, 1); | 
				
			||||
			dmtx_set(dmtx, x, SCREEN_H-1, 1); | 
				
			||||
			dmtx_show(dmtx); | 
				
			||||
			delay_ms(7); | 
				
			||||
			dmtx_set(dmtx, SCREEN_W - x - 1, 0, 0); | 
				
			||||
			dmtx_set(dmtx, x, SCREEN_H-1, 0); | 
				
			||||
 | 
				
			||||
			if (press_any_key())  { | 
				
			||||
				interr = true; | 
				
			||||
				break; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		if (interr) break; | 
				
			||||
 | 
				
			||||
		for (int y = SCREEN_H-1; y >= 0; y--) { | 
				
			||||
			dmtx_set(dmtx, 0, SCREEN_H - y - 1, 1); | 
				
			||||
			dmtx_set(dmtx, SCREEN_W-1, y, 1); | 
				
			||||
			dmtx_show(dmtx); | 
				
			||||
			delay_ms(7); | 
				
			||||
			dmtx_set(dmtx, 0, SCREEN_H - y - 1, 0); | 
				
			||||
			dmtx_set(dmtx, SCREEN_W-1, y, 0); | 
				
			||||
 | 
				
			||||
			if (press_any_key())  { | 
				
			||||
				interr = true; | 
				
			||||
				break; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
		if (interr) break; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	dmtx_clear(dmtx); | 
				
			||||
	dmtx_show(dmtx); | 
				
			||||
 | 
				
			||||
	snake_active = true; // resume snake
 | 
				
			||||
	new_game(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void move_coord_by_dir(Coord *coord, Direction dir) | 
				
			||||
{ | 
				
			||||
	switch (dir) { | 
				
			||||
		case NORTH: coord->y++; break; | 
				
			||||
		case SOUTH: coord->y--; break; | 
				
			||||
		case EAST: coord->x++; break; | 
				
			||||
		case WEST: coord->x--; break; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// Wrap-around
 | 
				
			||||
	if (coord->x < 0) coord->x = BOARD_W - 1; | 
				
			||||
	if (coord->y < 0) coord->y = BOARD_H - 1; | 
				
			||||
	if (coord->x >= BOARD_W) coord->x = 0; | 
				
			||||
	if (coord->y >= BOARD_H) coord->y = 0; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void move_tail(void) | 
				
			||||
{ | 
				
			||||
	Cell *tail_cell = cell_at(&tail); | 
				
			||||
	tail_cell->type = CELL_EMPTY; | 
				
			||||
	move_coord_by_dir(&tail, tail_cell->dir); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static void snake_move_cb(void *unused) | 
				
			||||
{ | 
				
			||||
	(void)unused; | 
				
			||||
 | 
				
			||||
	changed_dir_this_tick = false; // allow dir change next move
 | 
				
			||||
	bool want_new_food = false; | 
				
			||||
 | 
				
			||||
	Coord shadowhead = head; | 
				
			||||
 | 
				
			||||
	move_coord_by_dir(&shadowhead, head_dir); | 
				
			||||
 | 
				
			||||
	Cell *head_cell = cell_at(&head); | 
				
			||||
	Cell *future_head_cell = cell_at(&shadowhead); | 
				
			||||
 | 
				
			||||
	switch (future_head_cell->type) { | 
				
			||||
		case CELL_BODY: | 
				
			||||
		case CELL_WALL: | 
				
			||||
			// R.I.P.
 | 
				
			||||
			snake_died(); | 
				
			||||
			return; | 
				
			||||
 | 
				
			||||
		case CELL_FOOD: | 
				
			||||
			// grow - no head move
 | 
				
			||||
			score++; | 
				
			||||
			want_new_food = 1; | 
				
			||||
			level_up_score++; | 
				
			||||
			break; | 
				
			||||
 | 
				
			||||
		case CELL_EMPTY: | 
				
			||||
			// move tail
 | 
				
			||||
			move_tail(); | 
				
			||||
 | 
				
			||||
			if (double_tail_move_cnt > 0) { | 
				
			||||
				move_tail(); // 2x
 | 
				
			||||
				double_tail_move_cnt--; | 
				
			||||
			} | 
				
			||||
			break; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// Move head
 | 
				
			||||
	// (if died, already returned)
 | 
				
			||||
	head_cell->dir = head_dir; | 
				
			||||
	head_cell->type = CELL_BODY; | 
				
			||||
 | 
				
			||||
	future_head_cell->type = CELL_BODY; | 
				
			||||
 | 
				
			||||
	// apply movement
 | 
				
			||||
	head = shadowhead; | 
				
			||||
 | 
				
			||||
	if (want_new_food) { | 
				
			||||
		place_food(); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// render
 | 
				
			||||
	show_board(); | 
				
			||||
 | 
				
			||||
	// level up
 | 
				
			||||
	if (level_up_score == POINTS_TO_LEVEL_UP) { | 
				
			||||
		info("level up"); | 
				
			||||
 | 
				
			||||
		double_tail_move_cnt += PIECES_REMOVED_ON_LEVEL_UP; | 
				
			||||
 | 
				
			||||
		level++; | 
				
			||||
		level_up_score = 0; | 
				
			||||
 | 
				
			||||
		// go faster if not too fast yet
 | 
				
			||||
		if (level < PREDEFINED_LEVELS) { | 
				
			||||
			move_interval = speeds_for_levels[level]; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		set_periodic_task_interval(task_snake_move, move_interval); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** INIT snake */ | 
				
			||||
void mode_snake_init(void) | 
				
			||||
{ | 
				
			||||
	snake_pwm_init(&head_pwm); | 
				
			||||
	snake_pwm_init(&food_pwm); | 
				
			||||
 | 
				
			||||
	task_snake_move = add_periodic_task(snake_move_cb, NULL, move_interval, true); | 
				
			||||
	enable_periodic_task(task_snake_move, false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** START playing */ | 
				
			||||
void mode_snake_start(void) | 
				
			||||
{ | 
				
			||||
	snake_active = true; | 
				
			||||
 | 
				
			||||
	new_game(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** STOP playing */ | 
				
			||||
void mode_snake_stop(void) | 
				
			||||
{ | 
				
			||||
	snake_active = false; | 
				
			||||
 | 
				
			||||
	snake_pwm_stop(&head_pwm); | 
				
			||||
	snake_pwm_stop(&food_pwm); | 
				
			||||
 | 
				
			||||
	enable_periodic_task(task_snake_move, false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** Change dir (safely) */ | 
				
			||||
static void change_direction(Direction dir) | 
				
			||||
{ | 
				
			||||
	if (changed_dir_this_tick) return; | 
				
			||||
 | 
				
			||||
	// This is a compensation for shitty gamepads
 | 
				
			||||
	// with accidental arrow hits
 | 
				
			||||
	Coord shadowhead = head; | 
				
			||||
	move_coord_by_dir(&shadowhead, dir); | 
				
			||||
	Cell *target = cell_at(&shadowhead); // Target cell
 | 
				
			||||
	if (target->type == CELL_BODY) { | 
				
			||||
		move_coord_by_dir(&shadowhead, target->dir); | 
				
			||||
		if (shadowhead.x == head.x && shadowhead.y == head.y) { | 
				
			||||
			warn("Ignoring suicidal dir change"); | 
				
			||||
			return; // Would crash into own neck
 | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	head_dir = dir; | 
				
			||||
	changed_dir_this_tick = true; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** User button */ | 
				
			||||
void mode_snake_btn(char key) | 
				
			||||
{ | 
				
			||||
	switch (key) { | 
				
			||||
		case 'U': change_direction(NORTH); break; | 
				
			||||
		case 'D': change_direction(SOUTH); break; | 
				
			||||
		case 'L': change_direction(WEST); break; | 
				
			||||
		case 'R': change_direction(EAST); break; | 
				
			||||
 | 
				
			||||
		case 'J': // Start
 | 
				
			||||
			if (alive) break; | 
				
			||||
			// dead - reset by pressing 'start'
 | 
				
			||||
 | 
				
			||||
		case 'K': // clear
 | 
				
			||||
			// TODO reset animation
 | 
				
			||||
			new_game(); | 
				
			||||
			return; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	if (!moving && alive && (key == 'U' || key == 'D' || key == 'L' || key == 'R' || key == 'J')) { | 
				
			||||
		// start moving
 | 
				
			||||
		moving = true; | 
				
			||||
		reset_periodic_task(task_snake_move); | 
				
			||||
		enable_periodic_task(task_snake_move, true); | 
				
			||||
		return; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	// running + start -> 'pause'
 | 
				
			||||
	if (alive && moving && key == 'J') { | 
				
			||||
		moving = false; | 
				
			||||
		enable_periodic_task(task_snake_move, false); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,11 @@ | 
				
			||||
#ifndef MODE_SNAKE_H | 
				
			||||
#define MODE_SNAKE_H | 
				
			||||
 | 
				
			||||
#include "main.h" | 
				
			||||
 | 
				
			||||
void mode_snake_init(void); | 
				
			||||
void mode_snake_start(void); | 
				
			||||
void mode_snake_stop(void); | 
				
			||||
void mode_snake_btn(char key); | 
				
			||||
 | 
				
			||||
#endif // MODE_SNAKE_H
 | 
				
			||||
@ -0,0 +1,58 @@ | 
				
			||||
#include "scrolltext.h" | 
				
			||||
 | 
				
			||||
#include "font.h" | 
				
			||||
#include "com/debug.h" | 
				
			||||
 | 
				
			||||
 | 
				
			||||
void printtext(const char *text, int x, int y) | 
				
			||||
{ | 
				
			||||
	int totalX = 0; | 
				
			||||
 | 
				
			||||
	for (int textX = 0; textX < (int)strlen(text); textX++) { | 
				
			||||
		uint8_t ch = (uint8_t)text[textX]; | 
				
			||||
		if (ch < FONT_MIN) ch = '?'; | 
				
			||||
		if (ch > FONT_MAX) ch = '?'; | 
				
			||||
		ch -= ' '; // normalize for font table
 | 
				
			||||
 | 
				
			||||
		if (ch == 0) { // space
 | 
				
			||||
			totalX += 4; | 
				
			||||
			continue; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		// one letter
 | 
				
			||||
		uint8_t blanks = 0; | 
				
			||||
 | 
				
			||||
		// skip empty space on right
 | 
				
			||||
		for (int charX = FONT_WIDTH-1; charX >= 0; charX--) { | 
				
			||||
			uint8_t col = font[ch][charX]; | 
				
			||||
			if (col == 0x00) { | 
				
			||||
				blanks++; | 
				
			||||
			} else { | 
				
			||||
				break; | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		for (int charX = 0; charX < FONT_WIDTH - blanks; charX++) { | 
				
			||||
			uint8_t col = font[ch][charX]; | 
				
			||||
			for (int charY = 0; charY < 8; charY++) { | 
				
			||||
				dmtx_set(dmtx, x + totalX, y + 8 - charY, (col >> charY) & 1); | 
				
			||||
			} | 
				
			||||
			totalX++; | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		totalX+= 2; // gap
 | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
 | 
				
			||||
void scrolltext(const char *text, ms_time_t step) | 
				
			||||
{ | 
				
			||||
	(void)step; | 
				
			||||
 | 
				
			||||
	for (int i = 0; i < (int)strlen(text)*(FONT_WIDTH+1) + SCREEN_W-1; i++) { | 
				
			||||
		if (i > 0) delay_ms(step); | 
				
			||||
 | 
				
			||||
		dmtx_clear(dmtx); | 
				
			||||
		printtext(text, (SCREEN_W-1)-i, SCREEN_H/2-4); | 
				
			||||
		dmtx_show(dmtx); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -0,0 +1,12 @@ | 
				
			||||
#ifndef SCROLLTEXT_H | 
				
			||||
#define SCROLLTEXT_H | 
				
			||||
 | 
				
			||||
#include "main.h" | 
				
			||||
#include "dotmatrix.h" | 
				
			||||
#include "utils/timebase.h" | 
				
			||||
 | 
				
			||||
void printtext(const char *text, int x, int y); | 
				
			||||
 | 
				
			||||
void scrolltext(const char *text, ms_time_t step); | 
				
			||||
 | 
				
			||||
#endif // SCROLLTEXT_H
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue