#include "step4.h" __attribute__((unused)) static const char *TAG = "step4"; static lv_obj_t *old_scr; static lv_obj_t* scr; static lv_obj_t* img; static const int height = 22; static const int width = 10; static int board[height][width] = {0}; static lv_obj_t* visual_board[height][width] = {0}; static lv_obj_t* line_clear_img; static lv_style_t game_over_style; static lv_obj_t* game_over_label; #define MUSIC_FILE MOUNT_POINT "/piano.wav" #ifdef TETRIS_USE_FLASH_BG_IMG LV_IMG_DECLARE(bg); static const void* BACKGROUND_SRC = (void*) &bg; #else static const void* BACKGROUND_SRC = (void*)"A:" MOUNT_POINT "/bg.bin"; #endif #ifdef TETRIS_USE_FLASH_IMG LV_IMG_DECLARE(clear); LV_IMG_DECLARE(db); LV_IMG_DECLARE(green); LV_IMG_DECLARE(lb); LV_IMG_DECLARE(orange); LV_IMG_DECLARE(purple); LV_IMG_DECLARE(red); LV_IMG_DECLARE(yellow); static const void* LINE_CLEAR_SRC = (void*) &clear; static const void* PIECE_IMG_SRC[] = { NULL, &lb, &db, &orange, &yellow, &green, &purple, &red, }; #else static const void* LINE_CLEAR_SRC = (void*)"A:" MOUNT_POINT "/clear.bin"; static const void* PIECE_IMG_SRC[] = { NULL, "A:" MOUNT_POINT "/lb.bin", "A:" MOUNT_POINT "/db.bin", "A:" MOUNT_POINT "/orange.bin", "A:" MOUNT_POINT "/yellow.bin", "A:" MOUNT_POINT "/green.bin", "A:" MOUNT_POINT "/purple.bin", "A:" MOUNT_POINT "/red.bin", }; #endif static bool game = true; static int target_score = 0; static int score = 0; static int piece = 0; static int piece_rotation = 0; static int piece_location[] = {0, 0}; static int piece_nodes[4][2] = { {0, 0}, {0, 0}, {0, 0}, {0, 0}, }; lv_obj_t* piece_imgs[4] = {}; static bool music_playing; static std::random_device rd; static std::mt19937 gen(rd()); static std::uniform_int_distribution<> piece_dist(2, 7); static void generate_block(); static void show_board(); static void get_node_locations(); static bool check_overlap(); static void line_clear(int hi); static void check_line_clears(); static void place_piece(); static void move_left(); static void move_right(); static void drop(); static void rotate_block(); static void update_score(); static void clear_board(); static int bbcc(int i) { // [0,1,2,3] -> [ 0, 0,+1,+1] const int map[] = {0, 0, 1, 1}; return map[i]; } static int bccb(int i) { // [0,1,2,3] -> [ 0,+1,+1, 0] const int map[] = {0, 1, 1, 0}; return map[i]; } static int ccbb(int i) { // [0,1,2,3] -> [+1,+1, 0, 0] const int map[] = {1, 1, 0, 0}; return map[i]; } static int acca(int i) { // [0,1,2,3] -> [-1,+1,+1,-1] const int map[] = {-1, 1, 1, -1}; return map[i]; } static int aacc(int i) { // [0,1,2,3] -> [-1,-1,+1,+1] const int map[] = {-1, -1, 1, 1}; return map[i]; } static int babc(int i) { // [0,1,2,3] -> [ 0,-1, 0,+1] const int map[] = {0, -1, 0, 1}; return map[i]; } static int abcb(int i) { // [0,1,2,3] -> [-1, 0,+1, 0] const int map[] = {-1, 0, 1, 0}; return map[i]; } static int acdb(int i) { // [0,1,2,3] -> [-1,+1,+2, 0] const int map[] = {-1, +1, 2, 0}; return map[i]; } static int bacd(int i) { // [0,1,2,3] -> [ 0,-1,+1,+2] const int map[] = {0, -1, 1, 2}; return map[i]; } static int dcab(int i) { // [0,1,2,3] -> [+2,+1,-1, 0] const int map[] = {2, 1, -1, 0}; return map[i]; } static int bdca(int i) { // [0,1,2,3] -> [ 0,+2,+1,-1] const int map[] = {0, 2, 1, -1}; return map[i]; } static void init_screen() { while (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdFALSE) vTaskDelay(pdMS_TO_TICKS(10)); scr = lv_obj_create(NULL); img = lv_img_create(scr); lv_img_set_src(img, BACKGROUND_SRC); lv_obj_align(img, LV_ALIGN_CENTER, 0, 0); line_clear_img = lv_img_create(scr); lv_obj_add_flag(line_clear_img, LV_OBJ_FLAG_HIDDEN); lv_img_set_src(line_clear_img, LINE_CLEAR_SRC); lv_obj_align(line_clear_img, LV_ALIGN_BOTTOM_LEFT, 159, -(height*16)); lv_style_init(&game_over_style); lv_style_set_text_color(&game_over_style, lv_color_white()); lv_style_set_bg_color(&game_over_style, lv_color_black()); lv_style_set_bg_opa(&game_over_style, LV_OPA_100); lv_style_set_text_align(&game_over_style, LV_TEXT_ALIGN_CENTER); game_over_label = lv_label_create(scr); lv_obj_add_flag(game_over_label, LV_OBJ_FLAG_HIDDEN); lv_obj_align(game_over_label, LV_ALIGN_LEFT_MID, 0, 0); lv_obj_set_width(game_over_label, 160); lv_obj_add_style(game_over_label, &game_over_style, LV_STATE_DEFAULT); for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { visual_board[h][w] = lv_img_create(scr); lv_obj_align(visual_board[h][w], LV_ALIGN_BOTTOM_LEFT, 159 + w*16, -(h*16)); lv_obj_add_flag(visual_board[h][w], LV_OBJ_FLAG_HIDDEN); } } for (int i = 0; i < 4; i++) { piece_imgs[i] = lv_img_create(scr); lv_obj_align(piece_imgs[i], LV_ALIGN_BOTTOM_LEFT, 159, -320); } old_scr = lv_scr_act(); lv_scr_load(scr); xSemaphoreGive(xGuiSemaphore); play_clip_wav(MUSIC_FILE, true, true, 4, 0); music_playing = true; } static void deinit_screen() { while (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdFALSE) vTaskDelay(pdMS_TO_TICKS(10)); lv_scr_load(old_scr); lv_obj_del(scr); xSemaphoreGive(xGuiSemaphore); if (!stop_clip(MUSIC_FILE, pdMS_TO_TICKS(1000))) { ESP_LOGW(TAG, "Can't stop, addicted to the shindig."); } music_playing = false; } static void reveal_board() { for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { if (PIECE_IMG_SRC[board[h][w]]) { lv_img_set_src(visual_board[h][w], PIECE_IMG_SRC[board[h][w]]); lv_obj_clear_flag(visual_board[h][w], LV_OBJ_FLAG_HIDDEN); } } } } static void hide_board() { for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { lv_obj_add_flag(visual_board[h][w], LV_OBJ_FLAG_HIDDEN); lv_img_set_src(visual_board[h][w], NULL); } } } bool play_game(int time, int required_score) { game = true; target_score = required_score; score = 0; update_score(); set_module_time(time); start_module_timer(); clear_board(); generate_block(); show_board(); ButtonKey button; while (get_button_pressed(&button)); // SwitchKey switch_; const TickType_t first_repeat_time = pdMS_TO_TICKS(700); const TickType_t repeat_time = pdMS_TO_TICKS(100); TickType_t down_held = 0; TickType_t last_repeat = 0; while(game) { while (get_button_pressed(&button)) { switch (button) { case ButtonKey::b1: move_left(); break; case ButtonKey::b2: move_right(); break; case ButtonKey::b3: down_held = xTaskGetTickCount(); last_repeat = 0; drop(); break; case ButtonKey::b4: rotate_block(); break; } show_board(); if (score >= required_score) { stop_module_timer(); return true; } } while (get_button_released(&button)) { if (button == ButtonKey::b3) { down_held = 0; } } // Toggle music with switch4 SwitchKey switch_; while (get_switch_flipped(&switch_)) { if (switch_ == SwitchKey::s4) { if (music_playing) { stop_clip(MUSIC_FILE, 0); music_playing = false; } else { play_clip_wav(MUSIC_FILE, true, true, 4, 0); music_playing = true; } } } if (down_held != 0) { // check repeat TickType_t now = xTaskGetTickCount(); if (now - down_held > first_repeat_time) { // repeat! if (now - last_repeat > repeat_time) { last_repeat = now; drop(); show_board(); if (score >= required_score) { stop_module_timer(); return true; } } } } if (get_module_time() <= 0) { stop_module_timer(); strike("Out of time"); return false; } // if (get_switch_flipped(&switch_)) { // printf("%d\n", piece); // for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { // int* p = piece_nodes[i]; // printf("PieceLocation: %d, %d\n", p[0], p[1]); // } // printf("PieceRotation: %d\n", piece_rotation); // } vTaskDelay(pdMS_TO_TICKS(10)); } // game over ESP_LOGI(TAG, "Game Over. Score: %d", score); stop_module_timer(); strike("Out of room"); return false; } static void complete() { if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { reveal_board(); lv_label_set_text(game_over_label, "Winner!\nPress any button to continue"); lv_obj_clear_flag(game_over_label, LV_OBJ_FLAG_HIDDEN); xSemaphoreGive(xGuiSemaphore); } vTaskDelay(pdMS_TO_TICKS(500)); while (get_button_pressed(nullptr)); while (1) { if (get_button_pressed(nullptr)) break; vTaskDelay(pdMS_TO_TICKS(10)); } if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { lv_obj_add_flag(game_over_label, LV_OBJ_FLAG_HIDDEN); hide_board(); xSemaphoreGive(xGuiSemaphore); } } static void fail() { if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { lv_label_set_text(game_over_label, "Game Over\nPress any button to continue"); lv_obj_clear_flag(game_over_label, LV_OBJ_FLAG_HIDDEN); reveal_board(); xSemaphoreGive(xGuiSemaphore); } vTaskDelay(pdMS_TO_TICKS(2000)); while (get_button_pressed(nullptr)); while (1) { if (get_button_pressed(nullptr)) break; vTaskDelay(pdMS_TO_TICKS(10)); } if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { lv_obj_add_flag(game_over_label, LV_OBJ_FLAG_HIDDEN); hide_board(); xSemaphoreGive(xGuiSemaphore); } } void step4() { // TODO: extract to helper function SemaphoreHandle_t continue_sem = xSemaphoreCreateBinary(); if (continue_sem == nullptr) { ESP_LOGE(TAG, "could not create semaphore"); } StarCodeEntry start_code = { .code = "3850", .display_text = "Starting...", .delay_ticks = pdMS_TO_TICKS(2000), .callback = nullptr, .triggered_sem = continue_sem, }; add_star_code(start_code); xSemaphoreTake(continue_sem, portMAX_DELAY); rm_star_code(start_code.code); vSemaphoreDelete(continue_sem); init_screen(); while (!play_game(4*60*1000, 2)) fail(); // TODO: create constants for common assets, and put them in a folder. play_clip_wav(MOUNT_POINT "/partdone.wav", true, false, 0, 0); complete(); while (!play_game(4*60*1000, 4)) fail(); play_clip_wav(MOUNT_POINT "/partdone.wav", true, false, 0, 0); complete(); while (!play_game(7*60*1000, 8)) fail(); play_clip_wav(MOUNT_POINT "/stepdone.wav", true, false, 1, 0); complete(); deinit_screen(); } static void show_board() { for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { if (board[h][w] == 9) { board[h][w] = 0; } } } for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; board[p[0]][p[1]] = 9; } if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; lv_obj_t* piece_img = piece_imgs[i]; lv_obj_align(piece_img, LV_ALIGN_BOTTOM_LEFT, 159 + p[1]*16, -(p[0]*16)); } xSemaphoreGive(xGuiSemaphore); } } static void generate_block() { int new_piece = piece_dist(gen); if (new_piece == piece) { new_piece = 1; } piece = new_piece; piece_rotation = 0; piece_location[0] = 18; piece_location[1] = 4; get_node_locations(); for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; if (board[p[0]][p[1]] != 0) { game = false; return; } } } } if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { for (int i = 0; i < 4; i++) { lv_obj_t* piece_img = piece_imgs[i]; lv_img_set_src(piece_img, PIECE_IMG_SRC[piece]); } xSemaphoreGive(xGuiSemaphore); } } static void rotate_block() { piece_rotation++; if (piece_rotation > 3) { piece_rotation = 0; } get_node_locations(); // Check overlap without moving if (check_overlap()) { // Check overlap after moving up 1 piece_location[0]--; get_node_locations(); if (check_overlap()) { // Check overlap after moving down 1 piece_location[0]++; piece_location[0]++; get_node_locations(); if (check_overlap()) { // Check overlap after moving left 1 piece_location[0]--; piece_location[1]--; get_node_locations(); if (check_overlap()) { // Check overlap after moving right 1 piece_location[1]++; piece_location[1]++; get_node_locations(); if (check_overlap()) { piece_location[1]--; // This is Original Position // If Line Piece, check 2 left, 2 right, 2 up and 2 down // Check 2 left if (piece == 1 && piece_rotation == 0) { piece_location[1]-=2; get_node_locations(); if (check_overlap()) { piece_location[1]+=2; get_node_locations(); } // Check 2 up } else if (piece == 1 && piece_rotation == 1) { piece_location[0]+=2; get_node_locations(); if (check_overlap()) { piece_location[0]-=2; get_node_locations(); } // Check 2 right } else if (piece == 1 && piece_rotation == 2) { piece_location[1]+=2; get_node_locations(); if (check_overlap()) { piece_location[1]-=2; get_node_locations(); } // Check 2 down } else if (piece == 1 && piece_rotation == 3) { piece_location[0]-=2; get_node_locations(); if (check_overlap()) { piece_location[0]+=2; get_node_locations(); } } } } } } } } // Check if nodes are outside of map or interposed onto placed nodes static bool check_overlap() { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; if (p[0] < 0 || p[1] < 0 || p[1] >= width) { return true; } else if (board[p[0]][p[1]] != 0 && board[p[0]][p[1]] != 9) { return true; } } return false; } static void move_left() { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; if (p[1] == 0) { return; } else if (board[p[0]][p[1]-1] != 0 && board[p[0]][p[1]-1] != 9) { return; } } piece_location[1]--; get_node_locations(); } static void move_right() { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; if (p[1] == width-1) { return; } else if (board[p[0]][p[1]+1] != 0 && board[p[0]][p[1]+1] != 9) { return; } } piece_location[1]++; get_node_locations(); } static void drop() { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { int* p = piece_nodes[i]; if (p[0] == 0) { place_piece(); check_line_clears(); generate_block(); return; } else if (board[p[0]-1][p[1]] != 0 && board[p[0]-1][p[1]] != 9) { place_piece(); check_line_clears(); generate_block(); return; } } piece_location[0]--; get_node_locations(); } static void place_piece() { for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { if (board[h][w] == 9) { board[h][w] = piece; } } } if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { for (int i = 0; i < sizeof(piece_nodes)/sizeof(piece_nodes[0]); i++) { lv_obj_align(piece_imgs[i], LV_ALIGN_BOTTOM_LEFT, 159, -(height*16)); } xSemaphoreGive(xGuiSemaphore); } ESP_LOGI(TAG, "Placed Piece: %d", piece); } static void get_node_locations() { piece_nodes[0][0] = piece_location[0]; piece_nodes[0][1] = piece_location[1]; if (piece == 1) { piece_nodes[0][0] = piece_location[0]-bacd(piece_rotation); piece_nodes[0][1] = piece_location[1]+acdb(piece_rotation); piece_nodes[1][0] = piece_location[0]-bbcc(piece_rotation); piece_nodes[1][1] = piece_location[1]+bccb(piece_rotation); piece_nodes[2][0] = piece_location[0]-bccb(piece_rotation); piece_nodes[2][1] = piece_location[1]+ccbb(piece_rotation); piece_nodes[3][0] = piece_location[0]-bdca(piece_rotation); piece_nodes[3][1] = piece_location[1]+dcab(piece_rotation); } else if (piece == 2) { piece_nodes[1][0] = piece_location[0]-babc(piece_rotation); piece_nodes[1][1] = piece_location[1]+abcb(piece_rotation); piece_nodes[2][0] = piece_location[0]+babc(piece_rotation); piece_nodes[2][1] = piece_location[1]-abcb(piece_rotation); piece_nodes[3][0] = piece_location[0]-aacc(piece_rotation); piece_nodes[3][1] = piece_location[1]+acca(piece_rotation); } else if (piece == 3) { piece_nodes[1][0] = piece_location[0]-babc(piece_rotation); piece_nodes[1][1] = piece_location[1]+abcb(piece_rotation); piece_nodes[2][0] = piece_location[0]+babc(piece_rotation); piece_nodes[2][1] = piece_location[1]-abcb(piece_rotation); piece_nodes[3][0] = piece_location[0]-acca(piece_rotation); piece_nodes[3][1] = piece_location[1]-aacc(piece_rotation); } else if (piece == 4) { piece_nodes[1][0] = piece_location[0]+1; piece_nodes[1][1] = piece_location[1]; piece_nodes[2][0] = piece_location[0]; piece_nodes[2][1] = piece_location[1]+1; piece_nodes[3][0] = piece_location[0]+1; piece_nodes[3][1] = piece_location[1]+1; } else if (piece == 5) { piece_nodes[1][0] = piece_location[0]-babc(piece_rotation); piece_nodes[1][1] = piece_location[1]+abcb(piece_rotation); piece_nodes[2][0] = piece_location[0]-abcb(piece_rotation); piece_nodes[2][1] = piece_location[1]-babc(piece_rotation); piece_nodes[3][0] = piece_location[0]-acca(piece_rotation); piece_nodes[3][1] = piece_location[1]-aacc(piece_rotation); } else if (piece == 6) { piece_nodes[1][0] = piece_location[0]-babc(piece_rotation); piece_nodes[1][1] = piece_location[1]+abcb(piece_rotation); piece_nodes[2][0] = piece_location[0]-abcb(piece_rotation); piece_nodes[2][1] = piece_location[1]-babc(piece_rotation); piece_nodes[3][0] = piece_location[0]+babc(piece_rotation); piece_nodes[3][1] = piece_location[1]-abcb(piece_rotation); } else if (piece == 7) { piece_nodes[1][0] = piece_location[0]+babc(piece_rotation); piece_nodes[1][1] = piece_location[1]-abcb(piece_rotation); piece_nodes[2][0] = piece_location[0]-abcb(piece_rotation); piece_nodes[2][1] = piece_location[1]-babc(piece_rotation); piece_nodes[3][0] = piece_location[0]-aacc(piece_rotation); piece_nodes[3][1] = piece_location[1]+acca(piece_rotation); } } static void check_line_clears() { for (int h = height-2; h >= 0; h--) { for (int w = 0; w < width; w++) { if (board[h][w] != 0 && board[h][w] != 9) { if (w == width-1) { line_clear(h); } } else { break; } } } } static void update_score() { char buff[16] = {}; sprintf(buff, "%d/%d", score, target_score); lcd_clear(); lcd_print(1, 1, buff); } static void line_clear(int hi) { for (int h = hi; h < height; h++) { for (int w = 0; w < width; w++) { board[h][w] = board[h+1][w]; } } for (int w = 0; w < width; w++) { board[height-1][w] = 0; } score++; update_score(); if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { lv_obj_align(line_clear_img, LV_ALIGN_BOTTOM_LEFT, 159, -(hi*16)); xSemaphoreGive(xGuiSemaphore); } for (int i = 0; i < 3; i++) { if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { lv_obj_clear_flag(line_clear_img, LV_OBJ_FLAG_HIDDEN); xSemaphoreGive(xGuiSemaphore); } vTaskDelay(pdMS_TO_TICKS(300)); if (xSemaphoreTake(xGuiSemaphore, portMAX_DELAY) == pdTRUE) { lv_obj_add_flag(line_clear_img, LV_OBJ_FLAG_HIDDEN); xSemaphoreGive(xGuiSemaphore); } vTaskDelay(pdMS_TO_TICKS(300)); } } void clear_board() { for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) { board[h][w] = 0; } } }