Last active
February 21, 2021 10:47
-
-
Save conoro/1fb8039a6101201567bf686bebdb4929 to your computer and use it in GitHub Desktop.
Tetris on Raspberry Pi Pico from Reddit post
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Just a copy of the code referenced here: https://www.reddit.com/r/raspberry_pi/comments/l6flip/a_very_tiny_game_of_tetris_my_first_test_of_the/ | |
// With minor changes to make it compile | |
#include <string.h> | |
#include <math.h> | |
#include <vector> | |
#include <cstdlib> | |
#include "pico_display.hpp" | |
//#include "pico/multicore.h" | |
using namespace pimoroni; | |
#define u8 uint8_t | |
#define u16 uint16_t | |
#define u32 uint32_t | |
#define i8 int8_t | |
#define i16 int16_t | |
#define i32 int32_t | |
const u8 TETROMINOES[7][4][16] = { | |
{ // LONG PIECE | |
{ | |
0, 0, 0, 0, | |
1, 1, 1, 1, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 1, 0, | |
0, 0, 1, 0, | |
0, 0, 1, 0, | |
0, 0, 1, 0 | |
}, | |
{ | |
0, 0, 0, 0, | |
0, 0, 0, 0, | |
1, 1, 1, 1, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 1, 0, 0 | |
} | |
}, | |
{ // BACKWARD L | |
{ | |
1, 0, 0, 0, | |
1, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 1, 0, | |
0, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 0, 0, | |
1, 1, 1, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
0, 1, 0, 0, | |
1, 1, 0, 0, | |
0, 0, 0, 0 | |
} | |
}, | |
{ // L | |
{ | |
0, 0, 1, 0, | |
1, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 0, 0, | |
1, 1, 1, 0, | |
1, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
1, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
} | |
}, | |
{ // SQUARE | |
{ | |
0, 1, 1, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 1, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 1, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 1, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
} | |
}, | |
{ // S | |
{ | |
0, 1, 1, 0, | |
1, 1, 0, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
0, 1, 1, 0, | |
0, 0, 1, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 0, 0, | |
0, 1, 1, 0, | |
1, 1, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
1, 0, 0, 0, | |
1, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
} | |
}, | |
{ // T | |
{ | |
0, 1, 0, 0, | |
1, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
0, 1, 1, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 0, 0, | |
1, 1, 1, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
1, 1, 0, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
} | |
}, | |
{ // BACKWARD S | |
{ | |
1, 1, 0, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 1, 0, | |
0, 1, 1, 0, | |
0, 1, 0, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 0, 0, 0, | |
1, 1, 0, 0, | |
0, 1, 1, 0, | |
0, 0, 0, 0 | |
}, | |
{ | |
0, 1, 0, 0, | |
1, 1, 0, 0, | |
1, 0, 0, 0, | |
0, 0, 0, 0 | |
} | |
} | |
}; | |
const u16 SCREEN_WIDTH = PicoDisplay::WIDTH / 2; | |
const u16 SCREEN_HEIGHT = PicoDisplay::HEIGHT / 2; | |
uint16_t buffer[PicoDisplay::WIDTH * PicoDisplay::HEIGHT]; | |
PicoDisplay pico_display(buffer); | |
const u8 BUTTON_X = 0; | |
const u8 BUTTON_Y = 1; | |
const u8 BUTTON_A = 2; | |
const u8 BUTTON_B = 3; | |
const u8 pico_button_map[] = { | |
pico_display.X, | |
pico_display.Y, | |
pico_display.A, | |
pico_display.B | |
}; | |
u8 buttons[4]; | |
u8 buttons_pressed[4]; | |
u8 buttons_released[4]; | |
bool check_button(u8 button) { | |
return buttons[button]; | |
} | |
bool check_button_pressed(u8 button) { | |
return buttons_pressed[button]; | |
} | |
bool check_button_released(u8 button) { | |
return buttons_released[button]; | |
} | |
u16 color(u8 r, u8 g, u8 b) { | |
return pico_display.create_pen(r, g, b); | |
} | |
void pixel(u16 x, u16 y, u16 color) { | |
x *= 2; | |
y *= 2; | |
pico_display.frame_buffer[x + y * 240] = color; | |
pico_display.frame_buffer[x + 1 + y * 240] = color; | |
y++; | |
pico_display.frame_buffer[x + y * 240] = color; | |
pico_display.frame_buffer[x + 1 + y * 240] = color; | |
} | |
void rectangle(u16 x, u16 y, u16 width, u16 height, u16 color) { | |
for(u16 i = x; i < x + width; i++) { | |
for(u16 t = y; t < y + height; t++) { | |
pixel(i, t, color); | |
} | |
} | |
} | |
const u16 background = color(64, 51, 83); | |
const u16 field_background = color(36, 34, 52); | |
const u16 block_colors[] = { | |
color(180, 32, 42), | |
color(250, 106, 10), | |
color(255, 213, 65), | |
color(89, 193, 53), | |
color(40, 92, 196), | |
color(188, 74, 155), | |
color(32, 214, 199) | |
}; | |
u8 piece_index = 0; | |
u8 piece_angle = 0; | |
u8 piece_x = 0; | |
u8 piece_y = 0; | |
u8 piece_dropping = false; | |
u8 field[10][20]; | |
inline void next_piece() { | |
piece_index = (piece_index + 1) % 7; | |
piece_angle = 0; | |
piece_x = 3; | |
piece_y = 0; | |
} | |
inline void init() { | |
for(u8 i = 0; i < 10; ++i) { | |
for(u8 t = 0; t < 20; ++t) { | |
field[i][t] = 0; | |
} | |
} | |
next_piece(); | |
} | |
inline bool piece_position_allowed() { | |
const u8* piece = TETROMINOES[piece_index][piece_angle]; | |
for(u8 i = 0; i < 4; ++i) { | |
for(u8 t = 0; t < 4; ++t) { | |
u8 block = piece[i + t * 4]; | |
if(block) { | |
u8 x = piece_x + i; | |
u8 y = piece_y + t; | |
if(x >= 10 || y >= 20 || field[x][y]) { | |
return false; | |
} | |
} | |
} | |
} | |
return true; | |
} | |
inline void place_piece() { | |
piece_dropping = false; | |
const u8* piece = TETROMINOES[piece_index][piece_angle]; | |
for(u8 i = 0; i < 4; ++i) { | |
for(u8 t = 0; t < 4; ++t) { | |
u8 block = piece[i + t * 4]; | |
if(block) { | |
u8 x = piece_x + i; | |
u8 y = piece_y + t; | |
field[x][y] = piece_index + 1; | |
} | |
} | |
} | |
for(u8 t = 1; t < 20; ++t) { | |
bool line_filled = true; | |
for(u8 i = 0; i < 10; ++i) { | |
if(field[i][t] == 0) { | |
line_filled = false; | |
break; | |
} | |
} | |
if(line_filled) { | |
for(u8 j = t - 1; j > 0; --j) { | |
for(u8 i = 0; i < 10; ++i) { | |
field[i][j + 1] = field[i][j]; | |
} | |
} | |
for(u8 i = 0; i < 10; ++i) { | |
field[i][0] = 0; | |
} | |
--t; | |
} | |
} | |
} | |
inline void tick(bool update_board) { | |
pico_display.set_pen(64, 51, 83); | |
pico_display.clear(); | |
// Turn the piece | |
if(check_button_pressed(BUTTON_A)) { | |
piece_angle++; | |
if(!piece_position_allowed()) { | |
piece_angle--; | |
} | |
piece_angle = piece_angle % 4; | |
} | |
if(check_button_pressed(BUTTON_B)) { | |
piece_dropping = true; | |
} | |
if(check_button(BUTTON_B) && piece_dropping) { | |
update_board = true; | |
} | |
if(check_button_pressed(BUTTON_Y)) { | |
piece_x++; | |
if(!piece_position_allowed()) { | |
piece_x--; | |
} | |
} | |
if(check_button_pressed(BUTTON_X)) { | |
piece_x--; | |
if(!piece_position_allowed()) { | |
piece_x++; | |
} | |
} | |
if(update_board) { | |
piece_y++; | |
if(!piece_position_allowed()) { | |
piece_y--; | |
place_piece(); | |
next_piece(); | |
} | |
} | |
u16 offset_x = 9; | |
u16 offset_y = 8; | |
// Draw the field | |
rectangle(offset_x, offset_y, 100, 50, field_background); | |
for(u8 i = 0; i < 10; ++i) { | |
for(u8 t = 0; t < 20; ++t) { | |
u8 tile = field[i][t]; | |
if(field[i][t] > 0) { | |
rectangle(offset_x + t * 5, offset_y + i * 5, 5, 5, block_colors[tile - 1]); | |
} | |
} | |
} | |
// Draw the active piece | |
const u8* piece = TETROMINOES[piece_index][piece_angle]; | |
for(u8 i = 0; i < 4; ++i) { | |
for(u8 t = 0; t < 4; ++t) { | |
u8 block = piece[i + t * 4]; | |
if(block) { | |
u8 x = piece_x + i; | |
u8 y = piece_y + t; | |
if(x < 10 && y < 20) { | |
rectangle(offset_x + (y) * 5, offset_y + (x) * 5, 5, 5, block_colors[piece_index]); | |
} | |
} | |
} | |
} | |
} | |
void input_init() { | |
for(u8 i = 0; i < 4; ++i) { | |
buttons[i] = false; | |
buttons_pressed[i] = false; | |
buttons_released[i] = false; | |
} | |
} | |
const u8 button_gpio_map[] = { 12, 13, 14, 15 }; | |
#define EVENT_QUEUE_SIZE 128 | |
u16 event_queue[EVENT_QUEUE_SIZE]; | |
u16 event_queue_head = 0; | |
u16 event_queue_tail = 0; | |
bool event_queue_has_next() { | |
return event_queue_head != event_queue_tail; | |
} | |
bool event_queue_full() { | |
return (event_queue_head + 1) % EVENT_QUEUE_SIZE == event_queue_tail; | |
} | |
void event_queue_append(u16 value) { | |
if(!event_queue_full()) { | |
event_queue[event_queue_tail] = value; | |
event_queue_tail = (event_queue_tail + 1) % EVENT_QUEUE_SIZE; | |
} | |
} | |
u16 event_queue_next() { | |
if(event_queue_has_next()) { | |
event_queue_tail = (event_queue_tail - 1) % EVENT_QUEUE_SIZE; | |
return event_queue[event_queue_tail]; | |
} | |
return 0; | |
} | |
void gpio_callback(unsigned int gpio, u32 events) { | |
if(events & (GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL)) { | |
u8 button; | |
if(gpio == 12) { | |
button = BUTTON_A; | |
} else if(gpio == 13) { | |
button = BUTTON_B; | |
} else if(gpio == 14) { | |
button = BUTTON_X; | |
} else if(gpio == 15) { | |
button = BUTTON_Y; | |
} | |
u8 pressed = GPIO_IRQ_EDGE_RISE & events; | |
event_queue_append(button + pressed * 4); | |
} | |
} | |
void input_start_frame() { | |
for(u8 i = 0; i < 4; ++i) { | |
buttons_pressed[i] = false; | |
buttons_released[i] = false; | |
} | |
while(event_queue_has_next()) { | |
u32 event = event_queue_next(); | |
u8 button = event % 4; | |
u8 pressed = event < 4; | |
buttons[button] = pressed; | |
if(pressed) { | |
buttons_pressed[button] = true; | |
} else { | |
buttons_released[button] = true; | |
} | |
} | |
} | |
int main() { | |
pico_display.init(); | |
pico_display.set_backlight(255); | |
gpio_set_irq_enabled_with_callback(12, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); | |
gpio_set_irq_enabled_with_callback(13, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); | |
gpio_set_irq_enabled_with_callback(14, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); | |
gpio_set_irq_enabled_with_callback(15, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); | |
u8 tick_counter = 0; | |
u8 tick_interval = 30; | |
init(); | |
input_init(); | |
while(true) { | |
input_start_frame(); | |
tick_counter++; | |
if(tick_counter > tick_interval) { | |
tick(true); | |
tick_counter = 0; | |
} else { | |
tick(false); | |
} | |
pico_display.update(); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment