mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-24 05:34:45 -07:00
Update tetris_game.c
This commit is contained in:
@@ -59,7 +59,7 @@ typedef struct {
|
|||||||
} Piece;
|
} Piece;
|
||||||
|
|
||||||
// Shapes @ spawn locations, rotation point first
|
// Shapes @ spawn locations, rotation point first
|
||||||
static const Piece shapes[] = {
|
static Piece shapes[] = {
|
||||||
{ .p = {{5, 1}, {4, 0}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // Z
|
{ .p = {{5, 1}, {4, 0}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // Z
|
||||||
{ .p = {{5, 1}, {4, 1}, {5, 0}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // S
|
{ .p = {{5, 1}, {4, 1}, {5, 0}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // S
|
||||||
{ .p = {{5, 1}, {4, 1}, {6, 1}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // L
|
{ .p = {{5, 1}, {4, 1}, {6, 1}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // L
|
||||||
@@ -69,9 +69,18 @@ static const Piece shapes[] = {
|
|||||||
{ .p = {{5, 1}, {5, 0}, {6, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeO } // O
|
{ .p = {{5, 1}, {5, 0}, {6, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeO } // O
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GameStatePlaying,
|
||||||
|
GameStateGameOver
|
||||||
|
} GameState;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool playField[FIELD_HEIGHT][FIELD_WIDTH];
|
bool playField[FIELD_HEIGHT][FIELD_WIDTH];
|
||||||
Piece currPiece;
|
Piece currPiece;
|
||||||
|
uint16_t numLines;
|
||||||
|
uint16_t fallSpeed;
|
||||||
|
GameState gameState;
|
||||||
|
osTimerId_t timer;
|
||||||
} TetrisState;
|
} TetrisState;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -140,6 +149,23 @@ static void tetris_game_render_callback(Canvas* const canvas, void* ctx) {
|
|||||||
|
|
||||||
tetris_game_draw_border(canvas);
|
tetris_game_draw_border(canvas);
|
||||||
tetris_game_draw_playfield(canvas, tetris_state);
|
tetris_game_draw_playfield(canvas, tetris_state);
|
||||||
|
|
||||||
|
if(tetris_state->gameState == GameStateGameOver) {
|
||||||
|
// 128 x 64
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
canvas_draw_box(canvas, 1, 52, 62, 24);
|
||||||
|
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_draw_frame(canvas, 1, 52, 62, 24);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 4, 63, "Game Over");
|
||||||
|
|
||||||
|
char buffer[13];
|
||||||
|
snprintf(buffer, sizeof(buffer), "Lines: %u", tetris_state->numLines);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str_aligned(canvas, 32, 73, AlignCenter, AlignBottom, buffer);
|
||||||
|
}
|
||||||
release_mutex((ValueMutex *)ctx, tetris_state);
|
release_mutex((ValueMutex *)ctx, tetris_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +177,14 @@ static void tetris_game_input_callback(InputEvent* input_event, osMessageQueueId
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void tetris_game_init_state(TetrisState* tetris_state) {
|
static void tetris_game_init_state(TetrisState* tetris_state) {
|
||||||
|
tetris_state->gameState = GameStatePlaying;
|
||||||
|
tetris_state->numLines = 0;
|
||||||
|
tetris_state->fallSpeed = 500;
|
||||||
memset(tetris_state->playField, 0, sizeof(tetris_state->playField));
|
memset(tetris_state->playField, 0, sizeof(tetris_state->playField));
|
||||||
|
|
||||||
memcpy(&tetris_state->currPiece, &shapes[rand() % 7], sizeof(tetris_state->currPiece));
|
memcpy(&tetris_state->currPiece, &shapes[rand() % 7], sizeof(tetris_state->currPiece));
|
||||||
|
|
||||||
|
osTimerStart(tetris_state->timer, tetris_state->fallSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tetris_game_remove_curr_piece(TetrisState* tetris_state) {
|
static void tetris_game_remove_curr_piece(TetrisState* tetris_state) {
|
||||||
@@ -267,15 +298,68 @@ static void tetris_game_update_timer_callback(osMessageQueueId_t event_queue) {
|
|||||||
osMessageQueuePut(event_queue, &event, 0, osWaitForever);
|
osMessageQueuePut(event_queue, &event, 0, osWaitForever);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void tetris_game_process_step(TetrisState* tetris_state, Piece* newPiece, bool wasDownMove) {
|
||||||
|
if(tetris_state->gameState == GameStateGameOver)
|
||||||
|
return;
|
||||||
|
|
||||||
int32_t tetris_game_app(void* p) {
|
tetris_game_remove_curr_piece(tetris_state);
|
||||||
(void)p;
|
|
||||||
|
if(wasDownMove) {
|
||||||
|
if(tetris_game_piece_at_bottom(tetris_state, newPiece)) {
|
||||||
|
osTimerStop(tetris_state->timer);
|
||||||
|
|
||||||
|
tetris_game_render_curr_piece(tetris_state);
|
||||||
|
uint8_t numLines = 0;
|
||||||
|
uint8_t lines[] = { 0,0,0,0 };
|
||||||
|
|
||||||
|
tetris_game_check_for_lines(tetris_state, lines, &numLines);
|
||||||
|
if(numLines > 0) {
|
||||||
|
for(int i = 0; i < numLines; i++) {
|
||||||
|
|
||||||
|
// zero out row
|
||||||
|
for(int j = 0; j < FIELD_WIDTH; j++) {
|
||||||
|
tetris_state->playField[lines[i]][j] = false;
|
||||||
|
}
|
||||||
|
// move all above rows down
|
||||||
|
for(int k = lines[i]; k >= 0 ; k--) {
|
||||||
|
for(int m = 0; m < FIELD_WIDTH; m++) {
|
||||||
|
tetris_state->playField[k][m] = (k == 0) ? false : tetris_state->playField[k-1][m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t oldNumLines = tetris_state->numLines;
|
||||||
|
tetris_state->numLines += numLines;
|
||||||
|
if((oldNumLines / 10) % 10 != (tetris_state->numLines / 10) % 10) {
|
||||||
|
tetris_state->fallSpeed -= 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for game over
|
||||||
|
Piece* spawnedPiece = &shapes[rand() % 7];
|
||||||
|
if(!tetris_game_is_valid_pos(tetris_state, spawnedPiece->p)) {
|
||||||
|
tetris_state->gameState = GameStateGameOver;
|
||||||
|
} else {
|
||||||
|
memcpy(&tetris_state->currPiece, spawnedPiece, sizeof(tetris_state->currPiece));
|
||||||
|
osTimerStart(tetris_state->timer, tetris_state->fallSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tetris_game_is_valid_pos(tetris_state, newPiece->p)) {
|
||||||
|
memcpy(&tetris_state->currPiece, newPiece, sizeof(tetris_state->currPiece));
|
||||||
|
}
|
||||||
|
|
||||||
|
tetris_game_render_curr_piece(tetris_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t tetris_game_app() {
|
||||||
srand(DWT->CYCCNT);
|
srand(DWT->CYCCNT);
|
||||||
|
|
||||||
osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(TetrisEvent), NULL);
|
osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(TetrisEvent), NULL);
|
||||||
|
|
||||||
TetrisState* tetris_state = malloc(sizeof(TetrisState));
|
TetrisState* tetris_state = malloc(sizeof(TetrisState));
|
||||||
tetris_game_init_state(tetris_state);
|
|
||||||
|
|
||||||
ValueMutex state_mutex;
|
ValueMutex state_mutex;
|
||||||
if(!init_mutex(&state_mutex, tetris_state, sizeof(TetrisState))) {
|
if(!init_mutex(&state_mutex, tetris_state, sizeof(TetrisState))) {
|
||||||
@@ -284,43 +368,59 @@ int32_t tetris_game_app(void* p) {
|
|||||||
return 255;
|
return 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Not doing this eventually causes issues with TimerSvc due to not sleeping/yielding enough in this task
|
||||||
|
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
|
||||||
|
TaskHandle_t curr_task = xTaskGetHandle("Tetris Game");
|
||||||
|
|
||||||
|
uint32_t origTimerPrio = uxTaskPriorityGet(timer_task);
|
||||||
|
uint32_t myPrio = uxTaskPriorityGet(curr_task);
|
||||||
|
vTaskPrioritySet(timer_task, myPrio + 1);
|
||||||
|
|
||||||
ViewPort* view_port = view_port_alloc();
|
ViewPort* view_port = view_port_alloc();
|
||||||
view_port_set_orientation(view_port, ViewPortOrientationVertical);
|
view_port_set_orientation(view_port, ViewPortOrientationVertical);
|
||||||
view_port_draw_callback_set(view_port, tetris_game_render_callback, &state_mutex);
|
view_port_draw_callback_set(view_port, tetris_game_render_callback, &state_mutex);
|
||||||
view_port_input_callback_set(view_port, tetris_game_input_callback, event_queue);
|
view_port_input_callback_set(view_port, tetris_game_input_callback, event_queue);
|
||||||
|
|
||||||
osTimerId_t timer =
|
|
||||||
osTimerNew(tetris_game_update_timer_callback, osTimerPeriodic, event_queue, NULL);
|
|
||||||
osTimerStart(timer, 500U);
|
|
||||||
|
|
||||||
// Open GUI and register view_port
|
// Open GUI and register view_port
|
||||||
Gui* gui = furi_record_open("gui");
|
Gui* gui = furi_record_open("gui");
|
||||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
tetris_state->timer = osTimerNew(tetris_game_update_timer_callback, osTimerPeriodic, event_queue, NULL);
|
||||||
|
tetris_game_init_state(tetris_state);
|
||||||
|
|
||||||
TetrisEvent event;
|
TetrisEvent event;
|
||||||
// Point *newShape = malloc(sizeof(Point) * 4);
|
|
||||||
Piece *newPiece = malloc(sizeof(Piece));
|
Piece *newPiece = malloc(sizeof(Piece));
|
||||||
|
uint8_t downRepeatCounter = 0;
|
||||||
|
|
||||||
for(bool processing = true; processing;) {
|
for(bool processing = true; processing;) {
|
||||||
osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100);
|
// This 10U implicitly sets the game loop speed. downRepeatCounter relies on this value
|
||||||
|
osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 10U);
|
||||||
|
|
||||||
TetrisState* tetris_state = (TetrisState*)acquire_mutex_block(&state_mutex);
|
TetrisState* tetris_state = (TetrisState*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
memcpy(newPiece, &tetris_state->currPiece, sizeof(tetris_state->currPiece));
|
memcpy(newPiece, &tetris_state->currPiece, sizeof(tetris_state->currPiece));
|
||||||
bool wasDownMove = false;
|
bool wasDownMove = false;
|
||||||
|
|
||||||
|
if(!furi_hal_gpio_read(&gpio_button_right)) {
|
||||||
|
if(downRepeatCounter > 3) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
newPiece->p[i].y += 1;
|
||||||
|
}
|
||||||
|
downRepeatCounter = 0;
|
||||||
|
wasDownMove = true;
|
||||||
|
} else {
|
||||||
|
downRepeatCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(event_status == osOK) {
|
if(event_status == osOK) {
|
||||||
if(event.type == EventTypeKey) {
|
if(event.type == EventTypeKey) {
|
||||||
if(event.input.type == InputTypePress || event.input.type == InputTypeLong || event.input.type == InputTypeRepeat) {
|
if(event.input.type == InputTypePress || event.input.type == InputTypeLong || event.input.type == InputTypeRepeat) {
|
||||||
switch(event.input.key) {
|
switch(event.input.key) {
|
||||||
case InputKeyUp:
|
case InputKeyUp:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case InputKeyDown:
|
case InputKeyDown:
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
newPiece->p[i].y += 1;
|
|
||||||
}
|
|
||||||
wasDownMove = true;
|
|
||||||
break;
|
break;
|
||||||
case InputKeyRight:
|
case InputKeyRight:
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
@@ -333,9 +433,13 @@ int32_t tetris_game_app(void* p) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case InputKeyOk:
|
case InputKeyOk:
|
||||||
tetris_game_remove_curr_piece(tetris_state);
|
if(tetris_state->gameState == GameStatePlaying) {
|
||||||
tetris_game_try_rotation(tetris_state, newPiece);
|
tetris_game_remove_curr_piece(tetris_state);
|
||||||
tetris_game_render_curr_piece(tetris_state);
|
tetris_game_try_rotation(tetris_state, newPiece);
|
||||||
|
tetris_game_render_curr_piece(tetris_state);
|
||||||
|
} else {
|
||||||
|
tetris_game_init_state(tetris_state);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case InputKeyBack:
|
case InputKeyBack:
|
||||||
processing = false;
|
processing = false;
|
||||||
@@ -354,50 +458,20 @@ int32_t tetris_game_app(void* p) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tetris_game_remove_curr_piece(tetris_state);
|
tetris_game_process_step(tetris_state, newPiece, wasDownMove);
|
||||||
|
|
||||||
if(wasDownMove) {
|
|
||||||
if(tetris_game_piece_at_bottom(tetris_state, newPiece)) {
|
|
||||||
tetris_game_render_curr_piece(tetris_state);
|
|
||||||
uint8_t numLines = 0;
|
|
||||||
uint8_t lines[] = { 0,0,0,0 };
|
|
||||||
|
|
||||||
tetris_game_check_for_lines(tetris_state, lines, &numLines);
|
|
||||||
for(int i = 0; i < numLines; i++) {
|
|
||||||
|
|
||||||
// zero/falsify out row
|
|
||||||
for(int j = 0; j < FIELD_WIDTH; j++) {
|
|
||||||
tetris_state->playField[lines[i]][j] = false;
|
|
||||||
}
|
|
||||||
// move all above rows down
|
|
||||||
for(int k = lines[i]; k >= 0 ; k--) {
|
|
||||||
for(int m = 0; m < FIELD_WIDTH; m++) {
|
|
||||||
tetris_state->playField[k][m] = (k == 0) ? false : tetris_state->playField[k-1][m];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&tetris_state->currPiece, &shapes[rand() % 7], sizeof(tetris_state->currPiece));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tetris_game_is_valid_pos(tetris_state, newPiece->p)) {
|
|
||||||
memcpy(&tetris_state->currPiece, newPiece, sizeof(tetris_state->currPiece));
|
|
||||||
}
|
|
||||||
|
|
||||||
tetris_game_render_curr_piece(tetris_state);
|
|
||||||
|
|
||||||
view_port_update(view_port);
|
view_port_update(view_port);
|
||||||
release_mutex(&state_mutex, tetris_state);
|
release_mutex(&state_mutex, tetris_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
osTimerDelete(timer);
|
osTimerDelete(tetris_state->timer);
|
||||||
view_port_enabled_set(view_port, false);
|
view_port_enabled_set(view_port, false);
|
||||||
gui_remove_view_port(gui, view_port);
|
gui_remove_view_port(gui, view_port);
|
||||||
furi_record_close("gui");
|
furi_record_close("gui");
|
||||||
view_port_free(view_port);
|
view_port_free(view_port);
|
||||||
osMessageQueueDelete(event_queue);
|
osMessageQueueDelete(event_queue);
|
||||||
delete_mutex(&state_mutex);
|
delete_mutex(&state_mutex);
|
||||||
|
vTaskPrioritySet(timer_task, origTimerPrio);
|
||||||
free(newPiece);
|
free(newPiece);
|
||||||
free(tetris_state);
|
free(tetris_state);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user