#include "yatzee_icons.h" #include #include #include #include #include #include #include #define BASE_X 18 #define BASE_Y 44 #define DICE_OFFSET 12 #define HOLD "*" #define MAX_DICE 5 #define NUM_SCORES 13 bool new_game = true; bool game_over = false; bool bonus_added = false; int8_t num_bonus_yatzees = 0; // struct to hold image posistion for dice typedef struct { // +-----x // | // | // y uint8_t x; uint8_t y; } ImagePosition; typedef struct { char* name; uint32_t value; bool used; int8_t row; int8_t col; uint8_t (*fn)(); // pointer to function that calculates score } Score; typedef struct { uint8_t index; uint8_t value; bool isHeld; } Die; typedef struct { int index; char* symbol; } Cursor; // locations for the dice images ImagePosition position[5] = { {.x = BASE_X - DICE_OFFSET, .y = BASE_Y}, {.x = BASE_X * 2 - DICE_OFFSET, .y = BASE_Y}, {.x = BASE_X * 3 - DICE_OFFSET, .y = BASE_Y}, {.x = BASE_X * 4 - DICE_OFFSET, .y = BASE_Y}, {.x = BASE_X * 5 - DICE_OFFSET, .y = BASE_Y}, }; // these are the positions that the score cursor will cycle through ImagePosition score_positions[13] = { {.x = 15, .y = 0}, {.x = 15, .y = 9}, {.x = 15, .y = 18}, {.x = 15, .y = 27}, {.x = 44, .y = 0}, {.x = 44, .y = 9}, {.x = 44, .y = 18}, {.x = 44, .y = 27}, {.x = 77, .y = 0}, {.x = 77, .y = 9}, {.x = 77, .y = 18}, {.x = 77, .y = 27}, {.x = 91, .y = 21}, }; // cursor to select dice Cursor cursor = {.index = 0, .symbol = "^"}; // cursor to select score Cursor scoreCursor = {.index = -1, .symbol = "_"}; // setup array to store dice info Die die[5] = { {.index = 0, .value = 1, .isHeld = false}, {.index = 1, .value = 1, .isHeld = false}, {.index = 2, .value = 1, .isHeld = false}, {.index = 3, .value = 1, .isHeld = false}, {.index = 4, .value = 1, .isHeld = false}, }; uint8_t upperScore = 0; int32_t lowerScore = 0; int32_t totalScore = 0; uint8_t roll = 0; uint8_t totalrolls = 0; // ############################################# // # The following methods add the score for # // # whichever number is mentioned. # // ############################################# static uint8_t ones() { uint8_t sum = 0; for(uint8_t i = 0; i < 5; i++) { if(die[i].value == 1) { sum++; } } return sum; } static uint8_t twos() { uint8_t sum = 0; for(uint8_t i = 0; i < 5; i++) { if(die[i].value == 2) { sum = sum + 2; } } return sum; } static uint8_t threes() { uint8_t sum = 0; for(uint8_t i = 0; i < 5; i++) { if(die[i].value == 3) { sum = sum + 3; } } return sum; } static uint8_t fours() { uint8_t sum = 0; for(uint8_t i = 0; i < 5; i++) { if(die[i].value == 4) { sum = sum + 4; } } return sum; } static uint8_t fives() { uint8_t sum = 0; for(uint8_t i = 0; i < 5; i++) { if(die[i].value == 5) { sum = sum + 5; } } return sum; } static uint8_t sixes() { uint8_t sum = 0; for(uint8_t i = 0; i < 5; i++) { if(die[i].value == 6) { sum = sum + 6; } } return sum; } // #################################################### // # Helper methods for the special score types # // # defined before them so they can be used # // # since this whole thing is a linear mess # // # lol. # // # add_dice: # // # inputs: none # // # output: int8_t value of roll # // # check_if_score_used: // # inputs: Score // # output: true if score.used = true // # # # # # # # # # # # # # # # # # # # # # # # # # # int8_t add_dice() { int8_t sum = 0; for(int8_t i = 0; i < MAX_DICE; i++) { sum += die[i].value; } return sum; } bool check_if_score_used(Score score) { if(score.used == true) { return true; } else { return false; } } // ############################################# // # Methods to calculate scores for the fancy # // # scoring types: 3 of a kind, 4 of a kind, # // # Full house, small straight, large straight# // # chance & yatzee. # // ############################################# static uint8_t threekind() { int8_t score = 0; for(int8_t num = 1; num < 7; num++) { int8_t sum = 0; for(int8_t i = 0; i < MAX_DICE; i++) { if(die[i].value == num) { sum++; } if(sum > 2) { score = add_dice(); } } } return score; } static uint8_t fourkind() { int8_t score = 0; for(int8_t num = 1; num < 7; num++) { int8_t sum = 0; for(int8_t i = 0; i < MAX_DICE; i++) { if(die[i].value == num) { sum++; } if(sum > 3) { score = add_dice(); } } } return score; } static uint8_t fullhouse() { bool check1 = false; bool check2 = false; int8_t val1 = 0; int8_t val2 = 0; UNUSED(val2); UNUSED(val1); //check 1 for three of a kind for(int8_t num = 1; num < 7; num++) { int8_t sum = 0; for(int8_t i = 0; i < MAX_DICE; i++) { if(die[i].value == num) { sum++; } if(sum > 2) { val1 = die[i].value; check1 = true; } } } // return if check 1 failed if(check1 == false) { return 0; } // check 2 for two of a kind. for(int8_t num = 1; num < 7; num++) { if(num == val1) { continue; } int8_t sum = 0; for(int8_t i = 0; i < MAX_DICE; i++) { if(die[i].value == num) { sum++; } if(sum > 1) { val2 = die[i].value; check2 = true; } } if(check1 && check2) { return 25; } } return 0; } // # # # # # # # # # # # # # # # # # # # # # # # # # # # // # I'm dumb so I asked ChatGPT to write the # // # smallstraight function for me. Then I adapted it # // # fo the largestraight function. # // # # # # # # # # # # # # # # # # # # # # # # # # # # # static uint8_t smallstraight() { // Create a new array with the frequencies of the different die faces int8_t frequencies[6] = {0}; for(int8_t i = 0; i < 5; i++) { int8_t face = die[i].value; frequencies[face - 1]++; } // Check if there is a sequence of 4 consecutive die faces with at least one die bool found_small_straight = false; for(int i = 0; i < 3 && !found_small_straight; i++) { if(frequencies[i] > 0 && frequencies[i + 1] > 0 && frequencies[i + 2] > 0 && frequencies[i + 3] > 0) { found_small_straight = true; } } if(found_small_straight) { return 30; } else { return 0; } } static uint8_t largestraight() { // Create a new array with the frequencies of the different die faces int8_t frequencies[6] = {0}; for(int8_t i = 0; i < 5; i++) { int8_t face = die[i].value; frequencies[face - 1]++; } // Check if there is a sequence of 4 consecutive die faces with at least one die bool found_large_straight = false; for(int i = 0; i < 3 && !found_large_straight; i++) { if(frequencies[i] > 0 && frequencies[i + 1] > 0 && frequencies[i + 2] > 0 && frequencies[i + 3] > 0 && frequencies[i + 4] > 0) { found_large_straight = true; } } if(found_large_straight) { return 40; } else { return 0; } } static uint8_t chance() { // chance allows your roll to count for the raw number of pips showing int8_t sum = 0; for(int8_t i = 0; i < MAX_DICE; i++) { sum += die[i].value; } return sum; } static uint8_t yatzee() { // checks if all die.values are equal to the first die int8_t val = die[0].value; for(int8_t i = 1; i < MAX_DICE; i++) { // if value is the same as the first die, continue to next if(die[i].value == val) { continue; } else { // if any value is not equal to the first die, // this is not a yatzee and we return 0 return 0; } } return 50; } // # # # # # # # # # # # # # # # # # # # # # # # # # # // # Method to return true if yatzee returns 50 # // # # // # # # # # # # # # # # # # # # # # # # # # # # # # # // static bool check_for_bonus_yatzee() { // if (yatzee()==50){ // return true; // } else { // return false; // } // } // Scorecard defined here so that we can use pointers to the functions // defined above Score scorecard[13] = { {.name = "1", .value = 0, .used = false, .row = 0, .col = 0, .fn = &ones}, {.name = "2", .value = 0, .used = false, .row = 1, .col = 0, .fn = &twos}, {.name = "3", .value = 0, .used = false, .row = 2, .col = 0, .fn = &threes}, {.name = "4", .value = 0, .used = false, .row = 3, .col = 0, .fn = &fours}, {.name = "5", .value = 0, .used = false, .row = 0, .col = 1, .fn = &fives}, {.name = "6", .value = 0, .used = false, .row = 1, .col = 1, .fn = &sixes}, {.name = "3k", .value = 0, .used = false, .row = 2, .col = 1, .fn = &threekind}, {.name = "4k", .value = 0, .used = false, .row = 3, .col = 1, .fn = &fourkind}, {.name = "Fh", .value = 0, .used = false, .row = 0, .col = 2, .fn = &fullhouse}, {.name = "Sm", .value = 0, .used = false, .row = 1, .col = 2, .fn = &smallstraight}, {.name = "Lg", .value = 0, .used = false, .row = 2, .col = 2, .fn = &largestraight}, {.name = "Ch", .value = 0, .used = false, .row = 3, .col = 2, .fn = &chance}, {.name = "Yz", .value = 0, .used = false, .row = 2, .col = 3, .fn = &yatzee}, }; // ############################################# // # begin draw callback # // # # // ############################################# // define the callback for telling ViewPort how to update the screen // not sure what ctx is but it seems important static void app_draw_callback(Canvas* canvas, void* ctx) { UNUSED(ctx); canvas_set_font(canvas, FontSecondary); //define a font so we can put letters on the screen int8_t selectorOffsetX = 8; int8_t selectorOffsetY = 16; char buffer[36]; char bigbuffer[256]; canvas_clear(canvas); // if new_game, show user instructions if(new_game) { canvas_set_font(canvas, FontPrimary); elements_multiline_text_aligned(canvas, 64, 0, AlignCenter, AlignTop, "Yatzee!"); canvas_set_font(canvas, FontSecondary); snprintf( bigbuffer, sizeof(bigbuffer), "Up: Roll\nLeft/Right: Move cursor\nOK: Hold Die\nDown: Score"); elements_multiline_text_aligned(canvas, 0, 8, AlignLeft, AlignTop, bigbuffer); elements_button_center(canvas, "Start!"); return; } else { // draw border lines canvas_draw_line(canvas, 0, 37, 104, 37); canvas_draw_line(canvas, 104, 0, 104, 64); // iterate through dice and draw icon that correlates to die[n].value, and the x,y position indicated by position[die[i].index] for(int8_t i = 0; i < 5; i++) { if(die[i].value == 1) { canvas_draw_icon( canvas, position[die[i].index].x % 128, position[die[i].index].y % 64, &I_die_1); } else if(die[i].value == 2) { canvas_draw_icon( canvas, position[die[i].index].x % 128, position[die[i].index].y % 64, &I_die_2); } else if(die[i].value == 3) { canvas_draw_icon( canvas, position[die[i].index].x % 128, position[die[i].index].y % 64, &I_die_3); } else if(die[i].value == 4) { canvas_draw_icon( canvas, position[die[i].index].x % 128, position[die[i].index].y % 64, &I_die_4); } else if(die[i].value == 5) { canvas_draw_icon( canvas, position[die[i].index].x % 128, position[die[i].index].y % 64, &I_die_5); } else if(die[i].value == 6) { canvas_draw_icon( canvas, position[die[i].index].x % 128, position[die[i].index].y % 64, &I_die_6); } } // Puts an '*' above the die if hold is selected. int8_t holdOffsetX = 8; int8_t holdOffsetY = -5; for(int8_t i = 0; i < 5; i++) { if(die[i].isHeld == 1) { elements_multiline_text_aligned( canvas, position[die[i].index].x + holdOffsetX, position[die[i].index].y + holdOffsetY, AlignCenter, AlignTop, HOLD); } } // Update die cursor location if(cursor.index != -1) { elements_multiline_text_aligned( canvas, position[cursor.index].x + selectorOffsetX, position[cursor.index].y + selectorOffsetY, AlignCenter, AlignTop, cursor.symbol); } // Update score cursor location if(scoreCursor.index != -1) { elements_multiline_text_aligned( canvas, score_positions[scoreCursor.index].x, score_positions[scoreCursor.index].y + 1, AlignLeft, AlignTop, scoreCursor.symbol); } // Update Roll // Scores are updated in groups on screen to help with formatting // first group is scorecard[0:7], second group is [8:12] // Cycle through first 8 scores, if cursor at score, update to show possible score // otherwise, show current scores value. for(int8_t i = 0; i < 8; i++) { if(scoreCursor.index == i && scorecard[i].used == false) { int possiblescore = (int)(*scorecard[i].fn)(); snprintf(buffer, sizeof(buffer), "%s: %3u ", scorecard[i].name, possiblescore); canvas_draw_str_aligned( canvas, 23 + 29 * scorecard[i].col, 9 * scorecard[i].row, AlignRight, AlignTop, buffer); } else { uint8_t currentscore = scorecard[i].value; snprintf(buffer, sizeof(buffer), "%s: %3u ", scorecard[i].name, currentscore); canvas_draw_str_aligned( canvas, 23 + 29 * scorecard[i].col, 9 * scorecard[i].row, AlignRight, AlignTop, buffer); } if(scorecard[i].used) { canvas_draw_dot(canvas, 23 + 29 * scorecard[i].col, 3 + 9 * scorecard[i].row); } } // cycle through lower scores // NUM_SCORES minus one because the yatzee is 12 and is handled separately for(int8_t i = 8; i < NUM_SCORES - 1; i++) { if(scoreCursor.index == i && scorecard[i].used == false) { int possiblescore = (int)(*scorecard[i].fn)(); snprintf(buffer, sizeof(buffer), " %s: %3u ", scorecard[i].name, possiblescore); canvas_draw_str_aligned( canvas, 31 + 27 * scorecard[i].col, 9 * scorecard[i].row, AlignRight, AlignTop, buffer); } else { uint8_t currentscore = scorecard[i].value; snprintf(buffer, sizeof(buffer), " %s: %3u ", scorecard[i].name, currentscore); canvas_draw_str_aligned( canvas, 31 + 27 * scorecard[i].col, 9 * scorecard[i].row, AlignRight, AlignTop, buffer); } if(scorecard[i].used) { canvas_draw_dot(canvas, 31 + 27 * scorecard[i].col, 3 + 9 * scorecard[i].row); } } // update yatzee score if(scoreCursor.index == 12 && scorecard[12].used == false) { int possiblescore = (int)(*scorecard[12].fn)(); snprintf(buffer, sizeof(buffer), "Yz\n%u", possiblescore); elements_multiline_text_aligned(canvas, 93, 10, AlignCenter, AlignTop, buffer); } else { snprintf(buffer, sizeof(buffer), "Yz\n%ld", scorecard[12].value); elements_multiline_text_aligned(canvas, 93, 10, AlignCenter, AlignTop, buffer); } // Scores and roll number updated // sub score shows the 1-6 scores only. If this is >63 at the end of the game, // a 35 point bonus is added to the total score snprintf(buffer, sizeof(buffer), "Sub\n%u", upperScore); elements_multiline_text_aligned(canvas, 117, 0, AlignCenter, AlignTop, buffer); snprintf(buffer, sizeof(buffer), "Total\n%ld", totalScore); elements_multiline_text_aligned(canvas, 117, 22, AlignCenter, AlignTop, buffer); if(totalrolls == 0) { snprintf(buffer, sizeof(buffer), "Roll\n%s", " "); elements_multiline_text_aligned(canvas, 117, 64, AlignCenter, AlignBottom, buffer); } else { snprintf(buffer, sizeof(buffer), "Roll\n%u", totalrolls); elements_multiline_text_aligned(canvas, 117, 64, AlignCenter, AlignBottom, buffer); } // Check for then handle end of game // add num_bonus_yatzees to total rounds so that multiple // yatzees can be scored without impacting the number of rounds before // the game is over int8_t total_rounds = num_bonus_yatzees; // add up number of scores counted so far for(int8_t i = 0; i < NUM_SCORES; i++) { if(scorecard[i].used) { total_rounds++; } } // if total rounds is 13 + the number of bonus rounds, // thats it, game over. if(total_rounds == NUM_SCORES + num_bonus_yatzees) { // if scores of 1-6 add up to 63, a 35 point bonus is bonus_added // bonus_added = true keeps the game loop from // adding bonuses indefinetly if(upperScore >= 63 && bonus_added == false) { totalScore += 35; bonus_added = true; } // set game over to true and tell the user the game is over game_over = true; elements_button_center(canvas, "Game Over"); } } } // define the callback for helping ViewPort get InputEvent and place it in the event_queue defined in the main method static void app_input_callback(InputEvent* input_event, void* ctx) { furi_assert(ctx); FuriMessageQueue* event_queue = ctx; furi_message_queue_put(event_queue, input_event, FuriWaitForever); } // roll them diiiiceeee static void roll_dice() { // increment roll count totalrolls++; for(uint8_t i = 0; i < MAX_DICE; i++) { // dont reroll if the dice is being held if(die[i].isHeld == false) { die[i].value = 1 + rand() % 6; } } // if 3 rolls have been used, force user to select a score. if(totalrolls == 3) { scoreCursor.index = 0; } } static void clear_board() { // reset board after adding score totalrolls = 0; for(int8_t i = 0; i < MAX_DICE; i++) { die[i].isHeld = false; } scoreCursor.index = -1; cursor.index = 0; } static void add_score() { // return when scoring is not possible if(cursor.index != -1 || totalrolls == 0 || (scorecard[scoreCursor.index].used && strcmp(scorecard[scoreCursor.index].name, "Yz") != 0)) { return; } // extra yatzee scores if(scoreCursor.index == 12 && scorecard[scoreCursor.index].used) { uint8_t yatzee_score = (*scorecard[12].fn)(); scorecard[12].value += 2 * yatzee_score; lowerScore += 100; num_bonus_yatzees++; } // upper score for(int8_t i = 0; i < 6; i++) { if(scoreCursor.index == i && scorecard[scoreCursor.index].used == false) { scorecard[i].value = (*scorecard[i].fn)(); upperScore += scorecard[i].value; scorecard[i].used = true; } } // lower score for(int8_t i = 6; i < 13; i++) { if(scoreCursor.index == i && scorecard[scoreCursor.index].used == false) { scorecard[i].value = (*scorecard[i].fn)(); lowerScore += scorecard[i].value; scorecard[i].used = true; } } // recalculate total score totalScore = lowerScore + upperScore; clear_board(); } // Entry Point int32_t yatzee_main(void* p) { UNUSED(p); // Initialize event queue to handle incoming events like button presses // Use FuriMessageQueue as type as defined in furi api // InputEvents are supported by app_input_callback FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); // Initialize viewport ViewPort* view_port = view_port_alloc(); // Set system callbacks view_port_draw_callback_set(view_port, app_draw_callback, view_port); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Open GUI & register viewport Gui* gui = furi_record_open(RECORD_GUI); gui_add_view_port(gui, view_port, GuiLayerFullscreen); // hold input event InputEvent event; // Create a loop for the app to run in and handle InputEvents bool isRunning = true; while(isRunning) { if(totalrolls == 3) { cursor.index = -1; } if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { if((event.type == InputTypePress) || event.type == InputTypeRepeat) { switch(event.key) { case InputKeyLeft: if(cursor.index == -1) { if(scoreCursor.index == 0 && totalrolls == 3) { scoreCursor.index = NUM_SCORES - 1; } else if(scoreCursor.index == 0) { scoreCursor.index = -1; cursor.index = 4; } else { scoreCursor.index--; } } else { if(cursor.index == 0) { cursor.index = -1; scoreCursor.index = NUM_SCORES - 1; } else { cursor.index--; } } break; case InputKeyRight: // cursor.index == -1 means that scoreCursor is active if(cursor.index == -1) { if(scoreCursor.index == NUM_SCORES - 1 && totalrolls == 3) { scoreCursor.index = 0; } else if(scoreCursor.index == NUM_SCORES - 1) { scoreCursor.index = -1; cursor.index = 0; } else { scoreCursor.index++; } // if cursor.index is not -1, then dice cursor is active } else { if(cursor.index == 4) { cursor.index = -1; scoreCursor.index = 0; } else { cursor.index++; } } break; case InputKeyUp: if(totalrolls < 3) { roll_dice(); } // if (check_for_bonus_yatzee() && scorecard[13].used) { // num_bonus_yatzees++; // totalScore+=100; // // clear_board(); // } break; case InputKeyDown: add_score(); break; case InputKeyOk: if(new_game) { new_game = false; break; } if(game_over) { isRunning = false; } if(cursor.index == -1 || totalrolls == 0) { break; } if(die[cursor.index].isHeld == false) { die[cursor.index].isHeld = true; } else { die[cursor.index].isHeld = false; } break; default: isRunning = false; break; } } } // after every event, update view_port // uses app_draw_callback which is set before the game loop begins. view_port_update(view_port); } // cleanup view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); view_port_free(view_port); furi_message_queue_free(event_queue); furi_record_close(RECORD_GUI); return 0; }