Merge branch 'UNLEASHED' into 420

This commit is contained in:
RogueMaster
2022-12-25 01:31:22 -05:00
12 changed files with 1041 additions and 48 deletions

View File

@@ -12,40 +12,88 @@ void init_types() {
upcA->name = "UPC-A";
upcA->numberOfDigits = 12;
upcA->startPos = 19;
upcA->bartype = BarTypeUPCA;
barcodeTypes[0] = upcA;
BarcodeType* ean8 = malloc(sizeof(BarcodeType));
ean8->name = "EAN-8";
ean8->numberOfDigits = 8;
ean8->startPos = 33;
ean8->bartype = BarTypeEAN8;
barcodeTypes[1] = ean8;
BarcodeType* ean13 = malloc(sizeof(BarcodeType));
ean13->name = "EAN-13";
ean13->numberOfDigits = 13;
ean13->startPos = 19;
ean13->bartype = BarTypeEAN13;
barcodeTypes[2] = ean13;
}
void draw_digit(Canvas* canvas, int digit, bool rightHand, int startingPosition) {
void draw_digit(
Canvas* canvas,
int digit,
BarEncodingType rightHand,
int startingPosition,
bool drawlines) {
char digitStr[2];
snprintf(digitStr, 2, "%u", digit);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(
canvas, startingPosition, BARCODE_Y_START + BARCODE_HEIGHT + BARCODE_TEXT_OFFSET, digitStr);
if(rightHand) {
canvas_set_color(canvas, ColorBlack);
} else {
canvas_set_color(canvas, ColorWhite);
}
int count = 0;
for(int i = 0; i < 4; i++) {
canvas_draw_box(
canvas, startingPosition + count, BARCODE_Y_START, DIGITS[digit][i], BARCODE_HEIGHT);
canvas_invert_color(canvas);
count += DIGITS[digit][i];
if(drawlines) {
switch(rightHand) {
case BarEncodingTypeLeft:
case BarEncodingTypeRight:
canvas_set_color(
canvas, (rightHand == BarEncodingTypeRight) ? ColorBlack : ColorWhite);
//int count = 0;
for(int i = 0; i < 4; i++) {
canvas_draw_box(
canvas, startingPosition, BARCODE_Y_START, DIGITS[digit][i], BARCODE_HEIGHT);
canvas_invert_color(canvas);
startingPosition += DIGITS[digit][i];
}
break;
case BarEncodingTypeG:
canvas_set_color(canvas, ColorWhite);
//int count = 0;
for(int i = 3; i >= 0; i--) {
canvas_draw_box(
canvas, startingPosition, BARCODE_Y_START, DIGITS[digit][i], BARCODE_HEIGHT);
canvas_invert_color(canvas);
startingPosition += DIGITS[digit][i];
}
break;
default:
break;
}
}
}
int get_digit_position(int index, BarcodeType* type) {
int pos = type->startPos + index * 7;
if(index >= type->numberOfDigits / 2) {
pos += 5;
int pos = 0;
switch(type->bartype) {
case BarTypeEAN8:
case BarTypeUPCA:
pos = type->startPos + index * 7;
if(index >= type->numberOfDigits / 2) {
pos += 5;
}
break;
case BarTypeEAN13:
if(index == 0)
pos = type->startPos - 10;
else {
pos = type->startPos + (index - 1) * 7;
if((index - 1) >= type->numberOfDigits / 2) {
pos += 5;
}
}
break;
default:
break;
}
return pos;
}
@@ -54,18 +102,30 @@ int get_menu_text_location(int index) {
return 20 + 10 * index;
}
int get_barcode_max_index(PluginState* plugin_state) {
return plugin_state->doParityCalculation ?
barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits - 1 :
barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits;
}
int calculate_check_digit(PluginState* plugin_state, BarcodeType* type) {
int checkDigit = 0;
int checkDigitOdd = 0;
int checkDigitEven = 0;
//add all odd positions. Confusing because 0index
for(int i = 0; i < type->numberOfDigits - 1; i += 2) {
checkDigit += plugin_state->barcodeNumeral[i];
checkDigitOdd += plugin_state->barcodeNumeral[i];
}
checkDigit = checkDigit * 3; //times 3
//add all even positions to above. Confusing because 0index
for(int i = 1; i < type->numberOfDigits - 1; i += 2) {
checkDigit += plugin_state->barcodeNumeral[i];
checkDigitEven += plugin_state->barcodeNumeral[i];
}
if(type->bartype == BarTypeEAN13) {
checkDigit = checkDigitEven * 3 + checkDigitOdd;
} else {
checkDigit = checkDigitOdd * 3 + checkDigitEven;
}
checkDigit = checkDigit % 10; //mod 10
@@ -103,34 +163,57 @@ static void render_callback(Canvas* const canvas, void* ctx) {
AlignCenter,
(barcodeTypes[plugin_state->barcodeTypeIndex])->name);
canvas_draw_disc(
canvas, 40, get_menu_text_location(plugin_state->menuIndex) - 1, 2); //draw menu cursor
canvas,
40,
get_menu_text_location(plugin_state->menuIndex) - 1,
2); //draw menu cursor
} else {
BarcodeType* type = barcodeTypes[plugin_state->barcodeTypeIndex];
//start saftey
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, type->startPos - 3, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
canvas_draw_box(
canvas,
(type->startPos - 1),
BARCODE_Y_START,
1,
BARCODE_HEIGHT + 2); //start saftey
canvas_draw_box(canvas, (type->startPos - 1), BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
for(int index = 0; index < type->numberOfDigits; index++) {
bool isOnRight = false;
if(index >= type->numberOfDigits / 2) {
isOnRight = true;
}
if((index == type->numberOfDigits - 1) &&
(plugin_state->doParityCalculation)) { //calculate the check digit
int checkDigit = calculate_check_digit(plugin_state, type);
plugin_state->barcodeNumeral[type->numberOfDigits - 1] = checkDigit;
int startpos = 0;
int endpos = type->numberOfDigits;
if(type->bartype == BarTypeEAN13) {
startpos++;
draw_digit(
canvas,
plugin_state->barcodeNumeral[0],
BarEncodingTypeRight,
get_digit_position(0, barcodeTypes[plugin_state->barcodeTypeIndex]),
false);
}
if(plugin_state->doParityCalculation) { //calculate the check digit
plugin_state->barcodeNumeral[type->numberOfDigits - 1] =
calculate_check_digit(plugin_state, type);
}
for(int index = startpos; index < endpos; index++) {
BarEncodingType barEncodingType = BarEncodingTypeLeft;
if(type->bartype == BarTypeEAN13) {
if(index - 1 >= (type->numberOfDigits - 1) / 2) {
barEncodingType = BarEncodingTypeRight;
} else {
barEncodingType =
(FURI_BIT(EAN13ENCODE[plugin_state->barcodeNumeral[0]], index - 1)) ?
BarEncodingTypeG :
BarEncodingTypeLeft;
}
} else {
if(index >= type->numberOfDigits / 2) {
barEncodingType = BarEncodingTypeRight;
}
}
int digitPosition =
get_digit_position(index, barcodeTypes[plugin_state->barcodeTypeIndex]);
draw_digit(canvas, plugin_state->barcodeNumeral[index], isOnRight, digitPosition);
draw_digit(
canvas, plugin_state->barcodeNumeral[index], barEncodingType, digitPosition, true);
}
//central separator
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 62, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
canvas_draw_box(canvas, 64, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
@@ -147,15 +230,11 @@ static void render_callback(Canvas* const canvas, void* ctx) {
1); //draw editing cursor
}
//end safety
int endSafetyPosition = get_digit_position(type->numberOfDigits - 1, type) + 7;
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, endSafetyPosition, BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
canvas_draw_box(
canvas,
(endSafetyPosition + 2),
BARCODE_Y_START,
1,
BARCODE_HEIGHT + 2); //end safety
canvas_draw_box(canvas, (endSafetyPosition + 2), BARCODE_Y_START, 1, BARCODE_HEIGHT + 2);
}
release_mutex((ValueMutex*)ctx, plugin_state);
@@ -169,7 +248,7 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
}
static void barcode_generator_state_init(PluginState* const plugin_state) {
for(int i = 0; i < 12; ++i) {
for(int i = 0; i < BARCODE_MAX_LENS; ++i) {
plugin_state->barcodeNumeral[i] = i % 10;
}
plugin_state->editingIndex = 0;
@@ -194,9 +273,7 @@ static bool handle_key_press_view(InputKey key, PluginState* plugin_state) {
}
static bool handle_key_press_edit(InputKey key, PluginState* plugin_state) {
int barcodeMaxIndex = plugin_state->doParityCalculation ?
barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits - 1 :
barcodeTypes[plugin_state->barcodeTypeIndex]->numberOfDigits;
int barcodeMaxIndex = get_barcode_max_index(plugin_state);
switch(key) {
case InputKeyUp:
@@ -286,6 +363,9 @@ static bool handle_key_press_menu(InputKey key, PluginState* plugin_state) {
default:
break;
}
int barcodeMaxIndex = get_barcode_max_index(plugin_state);
if(plugin_state->editingIndex >= barcodeMaxIndex)
plugin_state->editingIndex = barcodeMaxIndex - 1;
return true;
}

View File

@@ -1,7 +1,8 @@
#define BARCODE_HEIGHT 50
#define BARCODE_Y_START 3
#define BARCODE_TEXT_OFFSET 9
#define NUMBER_OF_BARCODE_TYPES 2
#define BARCODE_MAX_LENS 13
#define NUMBER_OF_BARCODE_TYPES 3
#define MENU_INDEX_VIEW 0
#define MENU_INDEX_EDIT 1
#define MENU_INDEX_PARITY 2
@@ -23,14 +24,27 @@ typedef enum {
MenuMode,
} Mode;
typedef enum {
BarEncodingTypeLeft,
BarEncodingTypeRight,
BarEncodingTypeG,
} BarEncodingType;
typedef enum {
BarTypeEAN8,
BarTypeUPCA,
BarTypeEAN13,
} BarType;
typedef struct {
char* name;
int numberOfDigits;
int startPos;
BarType bartype;
} BarcodeType;
typedef struct {
int barcodeNumeral[12]; //The current barcode number
int barcodeNumeral[BARCODE_MAX_LENS]; //The current barcode number
int editingIndex; //The index of the editing symbol
int menuIndex; //The index of the menu cursor
Mode mode; //View, edit or menu
@@ -50,3 +64,16 @@ static const int DIGITS[10][4] = {
{1, 2, 1, 3},
{3, 1, 1, 2},
};
static const uint8_t EAN13ENCODE[10] = {
0b000000,
0b110100,
0b101100,
0b011100,
0b110010,
0b100110,
0b001110,
0b101010,
0b011010,
0b010110,
};

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Eugene Kirzhanov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,17 @@
# "2048" game for Flipper Zero
- play up to 65K
- progress is saved on exit
![Game screen](images/screenshot1.png)
![Menu screen](images/screenshot2.png)
#### TODO:
- add animations
#### Thanks to:
- [DroomOne's FlappyBird](https://github.com/DroomOne/flipperzero-firmware/tree/dev/applications/flappy_bird)
- [x27's "15" Game](https://github.com/x27/flipperzero-game15)
#### License
[MIT](LICENSE)
Copyright 2022 Eugene Kirzhanov

View File

@@ -0,0 +1,14 @@
App(
appid="game_2048",
name="2048",
apptype=FlipperAppType.EXTERNAL,
entry_point="game_2048_app",
cdefines=["APP_GAME_2048"],
requires=[
"gui",
],
stack_size=1 * 1024,
order=90,
fap_icon="game_2048.png",
fap_category="Games"
)

View File

@@ -0,0 +1,40 @@
#include "array_utils.h"
void reverse_array(int length, uint8_t arr[length]) {
uint8_t tmp;
for(int low = 0, high = length - 1; low < high; low++, high--) {
tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
}
bool shift_array_to_left(int length, uint8_t arr[length], uint8_t from_index, uint8_t offset) {
if(from_index >= length) return false;
for(uint8_t i = from_index; i < length; i++) {
arr[i] = i < length - offset ? arr[i + offset] : 0;
}
return true;
}
void get_column_from_array(
int rows,
int cols,
uint8_t arr[rows][cols],
uint8_t column_index,
uint8_t* out) {
for(uint8_t i = 0; i < rows; i++) {
out[i] = arr[i][column_index];
}
}
void set_column_to_array(
int rows,
int cols,
uint8_t arr[rows][cols],
uint8_t column_index,
uint8_t* src) {
for(uint8_t i = 0; i < rows; i++) {
arr[i][column_index] = src[i];
}
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
void reverse_array(int length, uint8_t arr[length]);
bool shift_array_to_left(int length, uint8_t arr[length], uint8_t from_index, uint8_t offset);
void get_column_from_array(
int rows,
int cols,
uint8_t arr[rows][cols],
uint8_t column_index,
uint8_t* out);
void set_column_to_array(
int rows,
int cols,
uint8_t arr[rows][cols],
uint8_t column_index,
uint8_t* src);

View File

@@ -0,0 +1,263 @@
#pragma once
#include <stdint.h>
uint8_t digits[16][14][14] = {
// 2
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 4
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 8
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 16
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 32
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 64
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0},
{0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0},
{0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0},
{0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 128
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0},
{0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0},
{0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 256
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0},
{0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0},
{0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0},
{0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
{0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 512
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0},
{0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0},
{0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 1K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 2K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 4K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 8K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0},
{0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 16K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0},
{0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0},
{0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0},
{0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 32K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0},
{0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0},
{0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0},
{0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
// 64K
{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0},
{0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0},
{0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0},
{0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0},
{0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}
};

View File

@@ -0,0 +1,509 @@
/*
* Copyright 2022 Eugene Kirzhanov
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*
* Thanks to:
* - DroomOne: https://github.com/DroomOne/flipperzero-firmware
* - x27: https://github.com/x27/flipperzero-game15
*/
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <storage/storage.h>
#include "digits.h"
#include "array_utils.h"
#define CELLS_COUNT 4
#define CELL_INNER_SIZE 14
#define FRAME_LEFT 10
#define FRAME_TOP 1
#define FRAME_SIZE 61
#define SAVING_DIRECTORY "/ext/apps/Games"
#define SAVING_FILENAME SAVING_DIRECTORY "/game_2048.save"
typedef enum {
GameStateMenu,
GameStateInProgress,
GameStateGameOver,
} State;
typedef struct {
State state;
uint8_t table[CELLS_COUNT][CELLS_COUNT];
uint32_t score;
uint32_t moves;
int8_t selected_menu_item;
uint32_t top_score;
} GameState;
typedef struct {
uint32_t points;
bool is_table_updated;
} MoveResult;
#define MENU_ITEMS_COUNT 2
static const char* popup_menu_strings[] = {"Resume", "New Game"};
static void input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
static void draw_frame(Canvas* canvas) {
canvas_draw_frame(canvas, FRAME_LEFT, FRAME_TOP, FRAME_SIZE, FRAME_SIZE);
uint8_t offs = FRAME_LEFT + CELL_INNER_SIZE + 1;
for(uint8_t i = 0; i < CELLS_COUNT - 1; i++) {
canvas_draw_line(canvas, offs, FRAME_TOP + 1, offs, FRAME_TOP + FRAME_SIZE - 2);
offs += CELL_INNER_SIZE + 1;
}
offs = FRAME_TOP + CELL_INNER_SIZE + 1;
for(uint8_t i = 0; i < CELLS_COUNT - 1; i++) {
canvas_draw_line(canvas, FRAME_LEFT + 1, offs, FRAME_LEFT + FRAME_SIZE - 2, offs);
offs += CELL_INNER_SIZE + 1;
}
}
static void draw_digit(Canvas* canvas, uint8_t row, uint8_t column, uint8_t value) {
if(value == 0) return;
uint8_t left = FRAME_LEFT + 1 + (column * (CELL_INNER_SIZE + 1));
uint8_t top = FRAME_TOP + 1 + (row * (CELL_INNER_SIZE + 1));
for(uint8_t r = 0; r < CELL_INNER_SIZE; r++) {
for(u_int8_t c = 0; c < CELL_INNER_SIZE; c++) {
if(digits[value - 1][r][c] == 1) {
canvas_draw_dot(canvas, left + c, top + r);
}
}
}
}
static void draw_table(Canvas* canvas, const uint8_t table[CELLS_COUNT][CELLS_COUNT]) {
for(uint8_t row = 0; row < CELLS_COUNT; row++) {
for(uint8_t column = 0; column < CELLS_COUNT; column++) {
draw_digit(canvas, row, column, table[row][column]);
}
}
}
static void gray_canvas(Canvas* const canvas) {
canvas_set_color(canvas, ColorWhite);
for(int x = 0; x < 128; x += 2) {
for(int y = 0; y < 64; y++) {
canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y);
}
}
}
static void draw_callback(Canvas* const canvas, void* ctx) {
const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25);
if(game_state == NULL) return;
canvas_clear(canvas);
draw_frame(canvas);
draw_table(canvas, game_state->table);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 128, FRAME_TOP, AlignRight, AlignTop, "Score");
canvas_draw_str_aligned(canvas, 128, FRAME_TOP + 20, AlignRight, AlignTop, "Moves");
canvas_draw_str_aligned(canvas, 128, FRAME_TOP + 40, AlignRight, AlignTop, "Top Score");
int bufSize = 12;
char buf[bufSize];
snprintf(buf, sizeof(buf), "%lu", game_state->score);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 128, FRAME_TOP + 10, AlignRight, AlignTop, buf);
memset(buf, 0, bufSize);
snprintf(buf, sizeof(buf), "%lu", game_state->moves);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 128, FRAME_TOP + 30, AlignRight, AlignTop, buf);
memset(buf, 0, bufSize);
snprintf(buf, sizeof(buf), "%lu", game_state->top_score);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas, 128, FRAME_TOP + 50, AlignRight, AlignTop, buf);
if(game_state->state == GameStateMenu) {
gray_canvas(canvas);
canvas_set_color(canvas, ColorWhite);
canvas_draw_rbox(canvas, 28, 16, 72, 32, 4);
canvas_set_color(canvas, ColorBlack);
canvas_draw_rframe(canvas, 28, 16, 72, 32, 4);
for(int i = 0; i < MENU_ITEMS_COUNT; i++) {
if(i == game_state->selected_menu_item) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 34, 20 + 12 * i, 60, 12);
}
canvas_set_color(
canvas, i == game_state->selected_menu_item ? ColorWhite : ColorBlack);
canvas_draw_str_aligned(
canvas, 64, 26 + 12 * i, AlignCenter, AlignCenter, popup_menu_strings[i]);
}
} else if(game_state->state == GameStateGameOver) {
gray_canvas(canvas);
bool record_broken = game_state->score > game_state->top_score;
canvas_set_color(canvas, ColorWhite);
canvas_draw_rbox(canvas, 14, 12, 100, 40, 4);
canvas_set_color(canvas, ColorBlack);
canvas_draw_line(canvas, 14, 26, 114, 26);
canvas_draw_rframe(canvas, 14, 12, 100, 40, 4);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 15, AlignCenter, AlignTop, "Game Over");
canvas_set_font(canvas, FontSecondary);
if(record_broken) {
canvas_draw_str_aligned(canvas, 64, 29, AlignCenter, AlignTop, "New Top Score!!!");
} else {
canvas_draw_str_aligned(canvas, 64, 29, AlignCenter, AlignTop, "Your Score");
}
memset(buf, 0, bufSize);
snprintf(buf, sizeof(buf), "%lu", game_state->score);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 48, AlignCenter, AlignBottom, buf);
}
release_mutex((ValueMutex*)ctx, game_state);
}
void calculate_move_to_left(uint8_t arr[], MoveResult* const move_result) {
uint8_t index = 0;
uint8_t next_index;
uint8_t offset;
bool was_moved;
while(index < CELLS_COUNT - 1) {
// find offset from [index] to next non-empty value
offset = 1;
while(index + offset < CELLS_COUNT && arr[index + offset] == 0) offset++;
// if all remaining values in this row are empty then go to next row
if(index + offset >= CELLS_COUNT) break;
// if current cell is empty then shift all cells [index+offset .. CELLS_COUNT-1] to [index]
if(arr[index] == 0) {
was_moved = shift_array_to_left(CELLS_COUNT, arr, index, offset);
if(was_moved) move_result->is_table_updated = true;
}
next_index = index + 1;
if(arr[next_index] == 0) {
// find offset from [next_index] to next non-empty value
offset = 1;
while(next_index + offset < CELLS_COUNT && arr[next_index + offset] == 0) offset++;
// if all remaining values in this row are empty then go to next row
if(next_index + offset >= CELLS_COUNT) break;
// if next cell is empty then shift cells [next_index+offset .. CELLS_COUNT-1] to [next_index]
was_moved = shift_array_to_left(CELLS_COUNT, arr, next_index, offset);
if(was_moved) move_result->is_table_updated = true;
}
if(arr[index] == arr[next_index]) {
arr[index]++;
shift_array_to_left(CELLS_COUNT, arr, next_index, 1);
move_result->is_table_updated = true;
move_result->points += 2 << (arr[index] - 1);
}
index++;
}
}
void move_left(uint8_t table[CELLS_COUNT][CELLS_COUNT], MoveResult* const move_result) {
for(uint8_t row_index = 0; row_index < CELLS_COUNT; row_index++) {
calculate_move_to_left(table[row_index], move_result);
}
}
void move_right(uint8_t table[CELLS_COUNT][CELLS_COUNT], MoveResult* const move_result) {
for(uint8_t row_index = 0; row_index < CELLS_COUNT; row_index++) {
reverse_array(CELLS_COUNT, table[row_index]);
calculate_move_to_left(table[row_index], move_result);
reverse_array(CELLS_COUNT, table[row_index]);
}
}
void move_up(uint8_t table[CELLS_COUNT][CELLS_COUNT], MoveResult* const move_result) {
uint8_t column[CELLS_COUNT];
for(uint8_t column_index = 0; column_index < CELLS_COUNT; column_index++) {
get_column_from_array(CELLS_COUNT, CELLS_COUNT, table, column_index, column);
calculate_move_to_left(column, move_result);
set_column_to_array(CELLS_COUNT, CELLS_COUNT, table, column_index, column);
}
}
void move_down(uint8_t table[CELLS_COUNT][CELLS_COUNT], MoveResult* const move_result) {
uint8_t column[CELLS_COUNT];
for(uint8_t column_index = 0; column_index < CELLS_COUNT; column_index++) {
get_column_from_array(CELLS_COUNT, CELLS_COUNT, table, column_index, column);
reverse_array(CELLS_COUNT, column);
calculate_move_to_left(column, move_result);
reverse_array(CELLS_COUNT, column);
set_column_to_array(CELLS_COUNT, CELLS_COUNT, table, column_index, column);
}
}
void add_new_digit(GameState* const game_state) {
uint8_t empty_cell_indexes[CELLS_COUNT * CELLS_COUNT];
uint8_t empty_cells_count = 0;
for(u_int8_t i = 0; i < CELLS_COUNT; i++) {
for(u_int8_t j = 0; j < CELLS_COUNT; j++) {
if(game_state->table[i][j] == 0) {
empty_cell_indexes[empty_cells_count++] = i * CELLS_COUNT + j;
}
}
}
if(empty_cells_count == 0) return;
int random_empty_cell_index = empty_cell_indexes[random() % empty_cells_count];
u_int8_t row = random_empty_cell_index / CELLS_COUNT;
u_int8_t col = random_empty_cell_index % CELLS_COUNT;
int random_value_percent = random() % 100;
game_state->table[row][col] = random_value_percent < 90 ? 1 : 2; // 90% for 2, 25% for 4
}
void init_game(GameState* const game_state, bool clear_top_score) {
memset(game_state->table, 0, CELLS_COUNT * CELLS_COUNT * sizeof(uint8_t));
add_new_digit(game_state);
add_new_digit(game_state);
game_state->score = 0;
game_state->moves = 0;
game_state->state = GameStateInProgress;
game_state->selected_menu_item = 0;
if(clear_top_score) {
game_state->top_score = 0;
}
}
bool load_game(GameState* game_state) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
uint16_t bytes_readed = 0;
if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
bytes_readed = storage_file_read(file, game_state, sizeof(GameState));
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return bytes_readed == sizeof(GameState);
}
void save_game(GameState* game_state) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) {
if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) {
return;
}
}
File* file = storage_file_alloc(storage);
if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
storage_file_write(file, game_state, sizeof(GameState));
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
bool is_game_over(GameState* const game_state) {
FURI_LOG_I("is_game_over", "====check====");
// check if table contains at least one empty cell
for(uint8_t i = 0; i < CELLS_COUNT; i++) {
for(u_int8_t j = 0; j < CELLS_COUNT; j++) {
if(game_state->table[i][j] == 0) {
FURI_LOG_I("is_game_over", "has empty cells");
return false;
}
}
}
FURI_LOG_I("is_game_over", "no empty cells");
uint8_t tmp_table[CELLS_COUNT][CELLS_COUNT];
MoveResult* tmp_move_result = malloc(sizeof(MoveResult));
// check if we can move to any direction
memcpy(tmp_table, game_state->table, CELLS_COUNT * CELLS_COUNT * sizeof(uint8_t));
move_left(tmp_table, tmp_move_result);
if(tmp_move_result->is_table_updated) return false;
FURI_LOG_I("is_game_over", "can't move left");
memcpy(tmp_table, game_state->table, CELLS_COUNT * CELLS_COUNT * sizeof(uint8_t));
move_right(tmp_table, tmp_move_result);
if(tmp_move_result->is_table_updated) return false;
FURI_LOG_I("is_game_over", "can't move right");
memcpy(tmp_table, game_state->table, CELLS_COUNT * CELLS_COUNT * sizeof(uint8_t));
move_up(tmp_table, tmp_move_result);
if(tmp_move_result->is_table_updated) return false;
FURI_LOG_I("is_game_over", "can't move up");
memcpy(tmp_table, game_state->table, CELLS_COUNT * CELLS_COUNT * sizeof(uint8_t));
move_down(tmp_table, tmp_move_result);
if(tmp_move_result->is_table_updated) return false;
FURI_LOG_I("is_game_over", "can't move down");
return true;
}
int32_t game_2048_app() {
GameState* game_state = malloc(sizeof(GameState));
if(!load_game(game_state)) {
init_game(game_state, true);
}
MoveResult* move_result = malloc(sizeof(MoveResult));
ValueMutex state_mutex;
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
FURI_LOG_E("SnakeGame", "cannot create mutex\r\n");
free(game_state);
return 255;
}
InputEvent input;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, draw_callback, &state_mutex);
view_port_input_callback_set(view_port, input_callback, event_queue);
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
bool is_finished = false;
while(!is_finished) {
FuriStatus event_status = furi_message_queue_get(event_queue, &input, FuriWaitForever);
if(event_status == FuriStatusOk) {
// handle only press event, ignore repeat/release events
if(input.type != InputTypePress) continue;
GameState* game_state = (GameState*)acquire_mutex_block(&state_mutex);
switch(game_state->state) {
case GameStateMenu:
switch(input.key) {
case InputKeyUp:
game_state->selected_menu_item--;
if(game_state->selected_menu_item < 0) {
game_state->selected_menu_item = MENU_ITEMS_COUNT - 1;
}
break;
case InputKeyDown:
game_state->selected_menu_item++;
if(game_state->selected_menu_item >= MENU_ITEMS_COUNT) {
game_state->selected_menu_item = 0;
}
break;
case InputKeyOk:
if(game_state->selected_menu_item == 1) {
// new game
init_game(game_state, false);
save_game(game_state);
}
game_state->state = GameStateInProgress;
break;
case InputKeyBack:
game_state->state = GameStateInProgress;
break;
default:
break;
}
break;
case GameStateInProgress:
move_result->is_table_updated = false;
move_result->points = 0;
switch(input.key) {
case InputKeyLeft:
move_left(game_state->table, move_result);
break;
case InputKeyRight:
move_right(game_state->table, move_result);
break;
case InputKeyUp:
move_up(game_state->table, move_result);
break;
case InputKeyDown:
move_down(game_state->table, move_result);
break;
case InputKeyOk:
game_state->state = GameStateMenu;
game_state->selected_menu_item = 0;
break;
case InputKeyBack:
save_game(game_state);
is_finished = true;
break;
case InputKeyMAX:
break;
}
game_state->score += move_result->points;
if(move_result->is_table_updated) {
game_state->moves++;
add_new_digit(game_state);
}
if(is_game_over(game_state)) {
game_state->state = GameStateGameOver;
if(game_state->score >= game_state->top_score) {
game_state->top_score = game_state->score;
}
}
break;
case GameStateGameOver:
if(input.key == InputKeyOk || input.key == InputKeyBack) {
init_game(game_state, false);
save_game(game_state);
}
}
view_port_update(view_port);
release_mutex(&state_mutex, game_state);
}
}
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
delete_mutex(&state_mutex);
free(game_state);
free(move_result);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB