You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
413 lines
8.6 KiB
413 lines
8.6 KiB
#include <avr/io.h>
|
|
#include <avr/interrupt.h>
|
|
#include <util/delay.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "lib/meta.h"
|
|
#include "lib/arduino_pins.h"
|
|
#include "lib/calc.h"
|
|
#include "lib/colors.h"
|
|
#include "lib/ws2812.h"
|
|
#include "lib/adc.h"
|
|
|
|
#define DEBO_CHANNELS 6
|
|
#define DEBO_TICKS 1 // in 0.01s
|
|
|
|
#include "lib/debounce.h"
|
|
|
|
|
|
// #define BOARD_WIDTH 6
|
|
// #define BOARD_HEIGHT 5
|
|
#define BOARD_WIDTH 6
|
|
#define BOARD_HEIGHT 5
|
|
|
|
// number of cards
|
|
#define CARD_COUNT (BOARD_WIDTH * BOARD_HEIGHT)
|
|
|
|
// number of pairs
|
|
#define PAIR_COUNT (CARD_COUNT / 2)
|
|
|
|
// when the "small" pin is DOWN, only this many cards are dealt - on the same board size
|
|
#define CARD_COUNT_SMALL 18
|
|
|
|
// color palette
|
|
const xrgb_t COLORS[] = {
|
|
rgb24_xrgbc(0x00FF99), // emerald
|
|
rgb24_xrgbc(0x0000CC), // full blue
|
|
rgb24_xrgbc(0xFF00FF), // magenta
|
|
rgb24_xrgbc(0xFF0000), // red
|
|
rgb24_xrgbc(0xFF2B00), // orange
|
|
rgb24_xrgbc(0xFFFF00), // yellow
|
|
rgb24_xrgbc(0x0BEE00), // green
|
|
rgb24_xrgbc(0xFF6D00), // tangerine yellow/orange
|
|
rgb24_xrgbc(0x00CCCC), // cyan
|
|
rgb24_xrgbc(0x4400FF), // blue-purple
|
|
rgb24_xrgbc(0x5FBA00), // yellow-green
|
|
rgb24_xrgbc(0xD70053), // wine
|
|
rgb24_xrgbc(0xCD2B64), // brick
|
|
rgb24_xrgbc(0xED1B24), // firetruck red
|
|
rgb24_xrgbc(0xFF6D55), // salmon?
|
|
};
|
|
|
|
|
|
// assert valid board size
|
|
#if CARD_COUNT % 2 == 1
|
|
# error "Board size is not even!"
|
|
#endif
|
|
|
|
|
|
// Pin assignments (see pins.h)
|
|
|
|
// RGB LED strip data line
|
|
#define WS1 D10
|
|
|
|
// Buttons (to ground)
|
|
#define BTN_LEFT D2
|
|
#define BTN_RIGHT D3
|
|
#define BTN_UP D4
|
|
#define BTN_DOWN D5
|
|
#define BTN_SELECT D6
|
|
#define BTN_RESTART D7
|
|
|
|
// connect this pin to ground to get smaller board size (for kids ^^)
|
|
#define FLAG_SMALL D12
|
|
|
|
// Debouncer channels for buttons
|
|
// (Must be added in this order to debouncer)
|
|
#define D_LEFT 0
|
|
#define D_RIGHT 1
|
|
#define D_UP 2
|
|
#define D_DOWN 3
|
|
#define D_SELECT 4
|
|
#define D_RESTART 5
|
|
|
|
// [ IMPORTANT ]
|
|
// Pin A0 must not be connected, it is used to get
|
|
// entropy for the random number generator
|
|
|
|
|
|
// Prototypes
|
|
void render();
|
|
void update();
|
|
void deal_cards();
|
|
|
|
|
|
/** Program initialization */
|
|
void SECTION(".init8") init()
|
|
{
|
|
// Randomize RNG
|
|
adc_init();
|
|
srand(adc_read_word(0));
|
|
|
|
// led strip data
|
|
as_output(WS1);
|
|
|
|
// gamepad buttons
|
|
as_input_pu(BTN_LEFT);
|
|
as_input_pu(BTN_RIGHT);
|
|
as_input_pu(BTN_UP);
|
|
as_input_pu(BTN_DOWN);
|
|
as_input_pu(BTN_SELECT);
|
|
as_input_pu(BTN_RESTART);
|
|
|
|
as_input_pu(FLAG_SMALL); // when LOW, use smaller board.
|
|
|
|
// add buttons to debouncer
|
|
debo_add_rev(BTN_LEFT);
|
|
debo_add_rev(BTN_RIGHT);
|
|
debo_add_rev(BTN_UP);
|
|
debo_add_rev(BTN_DOWN);
|
|
debo_add_rev(BTN_SELECT);
|
|
debo_add_rev(BTN_RESTART);
|
|
|
|
// setup timer
|
|
TCCR0A = _BV(WGM01); // CTC
|
|
TCCR0B = _BV(CS02) | _BV(CS00); // prescaler 1024
|
|
OCR0A = 156; // interrupt every 10 ms
|
|
sbi(TIMSK0, OCIE0A);
|
|
|
|
// prepare game board
|
|
deal_cards();
|
|
|
|
// enable timer interrupts (update & render)
|
|
sei();
|
|
}
|
|
|
|
|
|
|
|
/** Tile state enum */
|
|
typedef enum {
|
|
SECRET,
|
|
REVEALED,
|
|
GONE
|
|
} tilestate_t;
|
|
|
|
|
|
/** Tile struct */
|
|
typedef struct {
|
|
uint8_t color; // color index from COLORS[]
|
|
tilestate_t state; // state of the tile (used for render)
|
|
} tile_t;
|
|
|
|
|
|
// board tiles
|
|
tile_t board[CARD_COUNT];
|
|
|
|
|
|
/** Randomly place pairs of cards on the board */
|
|
void deal_cards()
|
|
{
|
|
// clear the board
|
|
for (uint8_t i = 0; i < CARD_COUNT; ++i) {
|
|
board[i] = (tile_t) { .color = 0, .state = GONE };
|
|
}
|
|
|
|
const uint8_t dealt_cards = get_pin(FLAG_SMALL) ? CARD_COUNT : CARD_COUNT_SMALL;
|
|
|
|
// for all pair_COUNT
|
|
for (uint8_t i = 0; i < (dealt_cards / 2); ++i) {
|
|
// for both cards in pair
|
|
for (uint8_t j = 0; j < 2; j++) {
|
|
// loop until empty slot is found
|
|
while(1) {
|
|
const uint8_t pos = rand() % dealt_cards;
|
|
|
|
if (board[pos].state == GONE) {
|
|
board[pos] = (tile_t) { .color = i, .state = SECRET };
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** timer 0 interrupt vector */
|
|
ISR(TIMER0_COMPA_vect)
|
|
{
|
|
debo_tick(); // poll debouncer
|
|
update(); // update game state
|
|
render();
|
|
}
|
|
|
|
|
|
// player cursor position
|
|
uint8_t cursor = 0;
|
|
uint8_t animframe = 0;
|
|
|
|
bool hide_timeout_match;
|
|
uint8_t hide_timeout = 0;
|
|
|
|
// Game state
|
|
uint8_t tiles_revealed = 0;
|
|
uint8_t tile1;
|
|
uint8_t tile2;
|
|
|
|
// length of pulse animation (in 10ms)
|
|
#define F_ANIM_LEN 20
|
|
#define HIDE_TIME 100
|
|
|
|
// length of button holding before it's repeated (in 10ms)
|
|
#define BTNHOLD_REPEAT 15
|
|
|
|
uint8_t btn_hold_cnt[DEBO_CHANNELS];
|
|
|
|
|
|
/** Handle a button press event */
|
|
void button_click(uint8_t n)
|
|
{
|
|
switch (n) {
|
|
case D_UP:
|
|
if (cursor < BOARD_WIDTH) // first row
|
|
cursor += (CARD_COUNT - BOARD_WIDTH);
|
|
else
|
|
cursor -= BOARD_WIDTH;
|
|
break;
|
|
|
|
case D_DOWN:
|
|
if (cursor >= (CARD_COUNT - BOARD_WIDTH)) // last row
|
|
cursor -= (CARD_COUNT - BOARD_WIDTH);
|
|
else
|
|
cursor += BOARD_WIDTH;
|
|
break;
|
|
|
|
case D_LEFT:
|
|
if (cursor > 0) // last row
|
|
cursor--;
|
|
else
|
|
cursor = (CARD_COUNT - 1);
|
|
break;
|
|
|
|
case D_RIGHT:
|
|
if (cursor < (CARD_COUNT - 1)) // last row
|
|
cursor++;
|
|
else
|
|
cursor = 0;
|
|
break;
|
|
|
|
case D_SELECT:
|
|
if (tiles_revealed == 2) break; // two already shown
|
|
if (board[cursor].state != SECRET) break; // selected tile not secret
|
|
|
|
// reveal a tile
|
|
if (tiles_revealed < 2) {
|
|
board[cursor].state = REVEALED;
|
|
tiles_revealed++;
|
|
|
|
if(tiles_revealed == 1) {
|
|
tile1 = cursor;
|
|
} else {
|
|
tile2 = cursor;
|
|
}
|
|
}
|
|
|
|
// Check equality if it's the second
|
|
if (tiles_revealed == 2) {
|
|
hide_timeout_match = (board[tile1].color == board[tile2].color);
|
|
hide_timeout = HIDE_TIME;
|
|
}
|
|
|
|
break;
|
|
|
|
case D_RESTART:
|
|
deal_cards();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/** Press arrow key, skip empty tiles */
|
|
void safe_press_arrow_key(uint8_t n)
|
|
{
|
|
// attempt to arrive at some secret tile
|
|
for (uint8_t j = 0; j < BOARD_HEIGHT; j++) {
|
|
|
|
for (uint8_t k = 0; k < BOARD_WIDTH; k++) {
|
|
button_click(n);
|
|
if (board[cursor].state != GONE) break;
|
|
}
|
|
|
|
if (board[cursor].state != GONE) break;
|
|
|
|
// traverse right since current column is empty
|
|
//
|
|
button_click(D_RIGHT);
|
|
}
|
|
}
|
|
|
|
|
|
#define is_arrow_key(id) ((id) == D_LEFT || (id) == D_RIGHT || (id) == D_UP || (id) == D_DOWN)
|
|
|
|
|
|
/** Update game (every 10 ms) */
|
|
void update()
|
|
{
|
|
// handle buttons (with repeating when held down)
|
|
for (uint8_t i = 0; i < DEBO_CHANNELS; i++) {
|
|
if (debo_get_pin(i)) {
|
|
if (btn_hold_cnt[i] == 0) {
|
|
if (is_arrow_key(i)) {
|
|
safe_press_arrow_key(i);
|
|
} else {
|
|
button_click(i);
|
|
}
|
|
}
|
|
|
|
// non-arrows wrap to 1 -> do not generate repeated clicks
|
|
inc_wrap(btn_hold_cnt[i], is_arrow_key(i) ? 0 : 1, BTNHOLD_REPEAT);
|
|
|
|
} else {
|
|
btn_hold_cnt[i] = 0;
|
|
}
|
|
}
|
|
|
|
// game logic - hide or remove cards when time is up
|
|
if (hide_timeout > 0) {
|
|
if (--hide_timeout == 0) {
|
|
if (hide_timeout_match) {
|
|
// Tiles removed from board
|
|
board[tile1].state = GONE;
|
|
board[tile2].state = GONE;
|
|
|
|
if (board[cursor].state == GONE) {
|
|
// move to some other tile
|
|
// try not to change row if possible
|
|
if ((cursor % BOARD_WIDTH) == (BOARD_WIDTH - 1))
|
|
safe_press_arrow_key(D_LEFT);
|
|
else
|
|
safe_press_arrow_key(D_RIGHT);
|
|
}
|
|
} else {
|
|
// Tiles made secret again
|
|
board[tile1].state = SECRET;
|
|
board[tile2].state = SECRET;
|
|
}
|
|
|
|
tiles_revealed = 0; // no revealed
|
|
}
|
|
}
|
|
|
|
// Animation for pulsing the active color
|
|
inc_wrap(animframe, 0, F_ANIM_LEN * 2);
|
|
}
|
|
|
|
// LED off
|
|
#define BLACK rgb24_xrgb(0x000000)
|
|
// LED on - secret tile
|
|
#define WHITE rgb24_xrgb(0x555555)
|
|
|
|
// colors to be displayed
|
|
xrgb_t screen[CARD_COUNT];
|
|
|
|
|
|
/** Update screen[] and send to display */
|
|
void render()
|
|
{
|
|
// Prepare screen (compute colors)
|
|
for (uint8_t i = 0; i < CARD_COUNT; i++) {
|
|
// get tile color to show
|
|
switch (board[i].state) {
|
|
case SECRET:
|
|
screen[i] = WHITE;
|
|
break;
|
|
|
|
case REVEALED:
|
|
screen[i] = COLORS[board[i].color];
|
|
break;
|
|
|
|
default:
|
|
case GONE:
|
|
screen[i] = BLACK;
|
|
break;
|
|
}
|
|
|
|
// pulse active tile
|
|
if (i == cursor) {
|
|
uint16_t mult;
|
|
|
|
if (animframe < F_ANIM_LEN) {
|
|
mult = animframe;
|
|
} else {
|
|
mult = (F_ANIM_LEN * 2) - animframe;
|
|
}
|
|
|
|
screen[i] = (xrgb_t) {
|
|
.r = (uint8_t) ((((uint16_t) screen[i].r) * mult) / F_ANIM_LEN),
|
|
.g = (uint8_t) ((((uint16_t) screen[i].g) * mult) / F_ANIM_LEN),
|
|
.b = (uint8_t) ((((uint16_t) screen[i].b) * mult) / F_ANIM_LEN),
|
|
};
|
|
}
|
|
}
|
|
|
|
// Send to LEDs
|
|
ws_send_xrgb_array(WS1, screen, CARD_COUNT);
|
|
ws_show();
|
|
}
|
|
|
|
|
|
void main()
|
|
{
|
|
while(1); // Timer does everything
|
|
}
|
|
|