Reorg, added more sources
@@ -0,0 +1 @@
|
||||
Put your custom applications in this folder.
|
||||
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="Arkanoid",
|
||||
name="Arkanoid",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="arkanoid_game_app",
|
||||
cdefines=["APP_ARKANOID_GAME"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=20,
|
||||
fap_icon="arkanoid_10px.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,474 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include <gui/view.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "Arkanoid"
|
||||
|
||||
#define FLIPPER_LCD_WIDTH 128
|
||||
#define FLIPPER_LCD_HEIGHT 64
|
||||
#define MAX_SPEED 3
|
||||
|
||||
typedef enum { EventTypeTick, EventTypeKey } EventType;
|
||||
|
||||
typedef struct {
|
||||
//Brick Bounds used in collision detection
|
||||
int leftBrick;
|
||||
int rightBrick;
|
||||
int topBrick;
|
||||
int bottomBrick;
|
||||
bool isHit[4][13]; //Array of if bricks are hit or not
|
||||
} BrickState;
|
||||
|
||||
typedef struct {
|
||||
int dx; //Initial movement of ball
|
||||
int dy; //Initial movement of ball
|
||||
int xb; //Balls starting possition
|
||||
int yb; //Balls starting possition
|
||||
bool released; //If the ball has been released by the player
|
||||
//Ball Bounds used in collision detection
|
||||
int leftBall;
|
||||
int rightBall;
|
||||
int topBall;
|
||||
int bottomBall;
|
||||
} BallState;
|
||||
|
||||
typedef struct {
|
||||
BallState ball_state;
|
||||
BrickState brick_state;
|
||||
NotificationApp* notify;
|
||||
unsigned int COLUMNS; //Columns of bricks
|
||||
unsigned int ROWS; //Rows of bricks
|
||||
bool initialDraw; //If the inital draw has happened
|
||||
int xPaddle; //X position of paddle
|
||||
char text[16]; //General string buffer
|
||||
bool bounced; //Used to fix double bounce glitch
|
||||
int lives; //Amount of lives
|
||||
int level; //Current level
|
||||
unsigned int score; //Score for the game
|
||||
unsigned int brickCount; //Amount of bricks hit
|
||||
int tick; //Tick counter
|
||||
bool gameStarted; // Did the game start?
|
||||
int speed; // Ball speed
|
||||
} ArkanoidState;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} GameEvent;
|
||||
|
||||
static const NotificationSequence sequence_short_sound = {
|
||||
&message_note_c5,
|
||||
&message_delay_50,
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
|
||||
// generate number in range [min,max)
|
||||
int rand_range(int min, int max) {
|
||||
return min + rand() % (max - min);
|
||||
}
|
||||
|
||||
void move_ball(Canvas* canvas, ArkanoidState* st) {
|
||||
st->tick++;
|
||||
|
||||
int current_speed = abs(st->speed - 1 - MAX_SPEED);
|
||||
if(st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(st->ball_state.released) {
|
||||
//Move ball
|
||||
if(abs(st->ball_state.dx) == 2) {
|
||||
st->ball_state.xb += st->ball_state.dx / 2;
|
||||
// 2x speed is really 1.5 speed
|
||||
if((st->tick / current_speed) % 2 == 0) st->ball_state.xb += st->ball_state.dx / 2;
|
||||
} else {
|
||||
st->ball_state.xb += st->ball_state.dx;
|
||||
}
|
||||
st->ball_state.yb = st->ball_state.yb + st->ball_state.dy;
|
||||
|
||||
//Set bounds
|
||||
st->ball_state.leftBall = st->ball_state.xb;
|
||||
st->ball_state.rightBall = st->ball_state.xb + 2;
|
||||
st->ball_state.topBall = st->ball_state.yb;
|
||||
st->ball_state.bottomBall = st->ball_state.yb + 2;
|
||||
|
||||
//Bounce off top edge
|
||||
if(st->ball_state.yb <= 0) {
|
||||
st->ball_state.yb = 2;
|
||||
st->ball_state.dy = -st->ball_state.dy;
|
||||
}
|
||||
|
||||
//Lose a life if bottom edge hit
|
||||
if(st->ball_state.yb >= FLIPPER_LCD_HEIGHT) {
|
||||
canvas_draw_frame(canvas, st->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
|
||||
st->xPaddle = 54;
|
||||
st->ball_state.yb = 60;
|
||||
st->ball_state.released = false;
|
||||
st->lives--;
|
||||
st->gameStarted = false;
|
||||
|
||||
if(rand_range(0, 2) == 0) {
|
||||
st->ball_state.dx = 1;
|
||||
} else {
|
||||
st->ball_state.dx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
//Bounce off left side
|
||||
if(st->ball_state.xb <= 0) {
|
||||
st->ball_state.xb = 2;
|
||||
st->ball_state.dx = -st->ball_state.dx;
|
||||
}
|
||||
|
||||
//Bounce off right side
|
||||
if(st->ball_state.xb >= FLIPPER_LCD_WIDTH - 2) {
|
||||
st->ball_state.xb = FLIPPER_LCD_WIDTH - 4;
|
||||
st->ball_state.dx = -st->ball_state.dx;
|
||||
// arduboy.tunes.tone(523, 250);
|
||||
}
|
||||
|
||||
//Bounce off paddle
|
||||
if(st->ball_state.xb + 1 >= st->xPaddle && st->ball_state.xb <= st->xPaddle + 12 &&
|
||||
st->ball_state.yb + 2 >= FLIPPER_LCD_HEIGHT - 1 &&
|
||||
st->ball_state.yb <= FLIPPER_LCD_HEIGHT) {
|
||||
st->ball_state.dy = -st->ball_state.dy;
|
||||
st->ball_state.dx =
|
||||
((st->ball_state.xb - (st->xPaddle + 6)) / 3); //Applies spin on the ball
|
||||
// prevent straight bounce, but not prevent roguuemaster from stealing
|
||||
if(st->ball_state.dx == 0) {
|
||||
st->ball_state.dx = (rand_range(0, 2) == 1) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
//Bounce off Bricks
|
||||
for(unsigned int row = 0; row < st->ROWS; row++) {
|
||||
for(unsigned int column = 0; column < st->COLUMNS; column++) {
|
||||
if(!st->brick_state.isHit[row][column]) {
|
||||
//Sets Brick bounds
|
||||
st->brick_state.leftBrick = 10 * column;
|
||||
st->brick_state.rightBrick = 10 * column + 10;
|
||||
st->brick_state.topBrick = 6 * row + 1;
|
||||
st->brick_state.bottomBrick = 6 * row + 7;
|
||||
|
||||
//If A collison has occured
|
||||
if(st->ball_state.topBall <= st->brick_state.bottomBrick &&
|
||||
st->ball_state.bottomBall >= st->brick_state.topBrick &&
|
||||
st->ball_state.leftBall <= st->brick_state.rightBrick &&
|
||||
st->ball_state.rightBall >= st->brick_state.leftBrick) {
|
||||
st->score += st->level;
|
||||
// Blink led when we hit some brick
|
||||
notification_message(st->notify, &sequence_short_sound);
|
||||
//notification_message(st->notify, &sequence_blink_white_100);
|
||||
|
||||
st->brickCount++;
|
||||
st->brick_state.isHit[row][column] = true;
|
||||
canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
|
||||
|
||||
//Vertical collision
|
||||
if(st->ball_state.bottomBall > st->brick_state.bottomBrick ||
|
||||
st->ball_state.topBall < st->brick_state.topBrick) {
|
||||
//Only bounce once each ball move
|
||||
if(!st->bounced) {
|
||||
st->ball_state.dy = -st->ball_state.dy;
|
||||
st->ball_state.yb += st->ball_state.dy;
|
||||
st->bounced = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Hoizontal collision
|
||||
if(st->ball_state.leftBall < st->brick_state.leftBrick ||
|
||||
st->ball_state.rightBall > st->brick_state.rightBrick) {
|
||||
//Only bounce once brick each ball move
|
||||
if(!st->bounced) {
|
||||
st->ball_state.dx = -st->ball_state.dx;
|
||||
st->ball_state.xb += st->ball_state.dx;
|
||||
st->bounced = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Reset Bounce
|
||||
st->bounced = false;
|
||||
} else {
|
||||
//Ball follows paddle
|
||||
st->ball_state.xb = st->xPaddle + 5;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_lives(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
if(arkanoid_state->lives == 3) {
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
|
||||
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
|
||||
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 15);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 15);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 16);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 16);
|
||||
} else if(arkanoid_state->lives == 2) {
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
|
||||
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
|
||||
} else {
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
|
||||
canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
|
||||
canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_score(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
snprintf(arkanoid_state->text, sizeof(arkanoid_state->text), "%u", arkanoid_state->score);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
FLIPPER_LCD_WIDTH - 2,
|
||||
FLIPPER_LCD_HEIGHT - 6,
|
||||
AlignRight,
|
||||
AlignBottom,
|
||||
arkanoid_state->text);
|
||||
}
|
||||
|
||||
void draw_ball(Canvas* canvas, ArkanoidState* ast) {
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb);
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb);
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb + 1);
|
||||
canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb + 1);
|
||||
|
||||
move_ball(canvas, ast);
|
||||
}
|
||||
|
||||
void draw_paddle(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
|
||||
}
|
||||
|
||||
void reset_level(Canvas* canvas, ArkanoidState* arkanoid_state) {
|
||||
//Undraw paddle
|
||||
canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
|
||||
|
||||
//Undraw ball
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb);
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb);
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb + 1);
|
||||
canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb + 1);
|
||||
|
||||
//Alter various variables to reset the game
|
||||
arkanoid_state->xPaddle = 54;
|
||||
arkanoid_state->ball_state.yb = 60;
|
||||
arkanoid_state->brickCount = 0;
|
||||
arkanoid_state->ball_state.released = false;
|
||||
|
||||
// Reset all brick hit states
|
||||
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
|
||||
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
|
||||
arkanoid_state->brick_state.isHit[row][column] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void arkanoid_state_init(ArkanoidState* arkanoid_state) {
|
||||
// Init notification
|
||||
arkanoid_state->notify = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Set the initial game state
|
||||
arkanoid_state->COLUMNS = 13;
|
||||
arkanoid_state->ROWS = 4;
|
||||
arkanoid_state->ball_state.dx = -1;
|
||||
arkanoid_state->ball_state.dy = -1;
|
||||
arkanoid_state->speed = 2;
|
||||
arkanoid_state->bounced = false;
|
||||
arkanoid_state->lives = 3;
|
||||
arkanoid_state->level = 1;
|
||||
arkanoid_state->score = 0;
|
||||
arkanoid_state->COLUMNS = 13;
|
||||
arkanoid_state->COLUMNS = 13;
|
||||
|
||||
// Reset initial state
|
||||
arkanoid_state->initialDraw = false;
|
||||
arkanoid_state->gameStarted = false;
|
||||
}
|
||||
|
||||
static void arkanoid_draw_callback(Canvas* const canvas, void* ctx) {
|
||||
ArkanoidState* arkanoid_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(arkanoid_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Initial level draw
|
||||
if(!arkanoid_state->initialDraw) {
|
||||
arkanoid_state->initialDraw = true;
|
||||
|
||||
// Set default font for text
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
//Draws the new level
|
||||
reset_level(canvas, arkanoid_state);
|
||||
}
|
||||
|
||||
//Draws new bricks and resets their values
|
||||
for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
|
||||
for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
|
||||
if(!arkanoid_state->brick_state.isHit[row][column]) {
|
||||
canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(arkanoid_state->lives > 0) {
|
||||
draw_paddle(canvas, arkanoid_state);
|
||||
draw_ball(canvas, arkanoid_state);
|
||||
draw_score(canvas, arkanoid_state);
|
||||
draw_lives(canvas, arkanoid_state);
|
||||
|
||||
if(arkanoid_state->brickCount == arkanoid_state->ROWS * arkanoid_state->COLUMNS) {
|
||||
arkanoid_state->level++;
|
||||
reset_level(canvas, arkanoid_state);
|
||||
}
|
||||
} else {
|
||||
reset_level(canvas, arkanoid_state);
|
||||
arkanoid_state->initialDraw = false;
|
||||
arkanoid_state->lives = 3;
|
||||
arkanoid_state->score = 0;
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, arkanoid_state);
|
||||
}
|
||||
|
||||
static void arkanoid_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
GameEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void arkanoid_update_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
GameEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t arkanoid_game_app(void* p) {
|
||||
UNUSED(p);
|
||||
int32_t return_code = 0;
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
||||
|
||||
ArkanoidState* arkanoid_state = malloc(sizeof(ArkanoidState));
|
||||
arkanoid_state_init(arkanoid_state);
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, arkanoid_state, sizeof(ArkanoidState))) {
|
||||
FURI_LOG_E(TAG, "Cannot create mutex\r\n");
|
||||
return_code = 255;
|
||||
goto free_and_exit;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, arkanoid_draw_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, arkanoid_input_callback, event_queue);
|
||||
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(arkanoid_update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
GameEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
ArkanoidState* arkanoid_state = (ArkanoidState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// Key events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
|
||||
event.input.type == InputTypeRepeat) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(arkanoid_state->xPaddle < FLIPPER_LCD_WIDTH - 12) {
|
||||
arkanoid_state->xPaddle += 8;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(arkanoid_state->xPaddle > 0) {
|
||||
arkanoid_state->xPaddle -= 8;
|
||||
}
|
||||
break;
|
||||
case InputKeyUp:
|
||||
if(arkanoid_state->speed < MAX_SPEED) {
|
||||
arkanoid_state->speed++;
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(arkanoid_state->speed > 1) {
|
||||
arkanoid_state->speed--;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(arkanoid_state->gameStarted == false) {
|
||||
//Release ball if FIRE pressed
|
||||
arkanoid_state->ball_state.released = true;
|
||||
|
||||
//Apply random direction to ball on release
|
||||
if(rand_range(0, 2) == 0) {
|
||||
arkanoid_state->ball_state.dx = 1;
|
||||
} else {
|
||||
arkanoid_state->ball_state.dx = -1;
|
||||
}
|
||||
|
||||
//Makes sure the ball heads upwards
|
||||
arkanoid_state->ball_state.dy = -1;
|
||||
//start the game flag
|
||||
arkanoid_state->gameStarted = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, arkanoid_state);
|
||||
}
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
view_port_free(view_port);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
free_and_exit:
|
||||
free(arkanoid_state);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return return_code;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
App(
|
||||
appid="Barcode_Generator",
|
||||
name="Barcode Generator",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="barcode_generator_app",
|
||||
cdefines=["APP_BARCODE_GEN"],
|
||||
requires=[
|
||||
"gui",
|
||||
"dialogs",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=250,
|
||||
fap_icon="barcode_10px.png",
|
||||
fap_category="Misc",
|
||||
)
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,355 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "barcode_generator.h"
|
||||
|
||||
static BarcodeType* barcodeTypes[NUMBER_OF_BARCODE_TYPES];
|
||||
|
||||
void init_types() {
|
||||
BarcodeType* upcA = malloc(sizeof(BarcodeType));
|
||||
upcA->name = "UPC-A";
|
||||
upcA->numberOfDigits = 12;
|
||||
upcA->startPos = 19;
|
||||
barcodeTypes[0] = upcA;
|
||||
|
||||
BarcodeType* ean8 = malloc(sizeof(BarcodeType));
|
||||
ean8->name = "EAN-8";
|
||||
ean8->numberOfDigits = 8;
|
||||
ean8->startPos = 33;
|
||||
barcodeTypes[1] = ean8;
|
||||
}
|
||||
|
||||
void draw_digit(Canvas* canvas, int digit, bool rightHand, int startingPosition) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
int get_digit_position(int index, BarcodeType* type) {
|
||||
int pos = type->startPos + index * 7;
|
||||
if(index >= type->numberOfDigits / 2) {
|
||||
pos += 5;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
int get_menu_text_location(int index) {
|
||||
return 20 + 10 * index;
|
||||
}
|
||||
|
||||
int calculate_check_digit(PluginState* plugin_state, BarcodeType* type) {
|
||||
int checkDigit = 0;
|
||||
//add all odd positions. Confusing because 0index
|
||||
for(int i = 0; i < type->numberOfDigits - 1; i += 2) {
|
||||
checkDigit += 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];
|
||||
}
|
||||
|
||||
checkDigit = checkDigit % 10; //mod 10
|
||||
|
||||
//if m = 0 then x12 = 0, otherwise x12 is 10 - m
|
||||
return (10 - checkDigit) % 10;
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
PluginState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(plugin_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(plugin_state->mode == MenuMode) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignCenter, "MENU");
|
||||
canvas_draw_frame(canvas, 50, 0, 29, 11); //box around Menu
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, get_menu_text_location(0), AlignCenter, AlignCenter, "View");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, get_menu_text_location(1), AlignCenter, AlignCenter, "Edit");
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, get_menu_text_location(2), AlignCenter, AlignCenter, "Parity?");
|
||||
|
||||
canvas_draw_frame(canvas, 83, get_menu_text_location(2) - 3, 6, 6);
|
||||
if(plugin_state->doParityCalculation == true) {
|
||||
canvas_draw_box(canvas, 85, get_menu_text_location(2) - 1, 2, 2);
|
||||
}
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
64,
|
||||
get_menu_text_location(3),
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
(barcodeTypes[plugin_state->barcodeTypeIndex])->name);
|
||||
canvas_draw_disc(
|
||||
canvas, 40, get_menu_text_location(plugin_state->menuIndex) - 1, 2); //draw menu cursor
|
||||
} else {
|
||||
BarcodeType* type = barcodeTypes[plugin_state->barcodeTypeIndex];
|
||||
|
||||
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
|
||||
|
||||
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 digitPosition =
|
||||
get_digit_position(index, barcodeTypes[plugin_state->barcodeTypeIndex]);
|
||||
draw_digit(canvas, plugin_state->barcodeNumeral[index], isOnRight, digitPosition);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if(plugin_state->mode == EditMode) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
get_digit_position(
|
||||
plugin_state->editingIndex, barcodeTypes[plugin_state->barcodeTypeIndex]) -
|
||||
1,
|
||||
63,
|
||||
7,
|
||||
1); //draw editing cursor
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void barcode_generator_state_init(PluginState* const plugin_state) {
|
||||
for(int i = 0; i < 12; ++i) {
|
||||
plugin_state->barcodeNumeral[i] = i % 10;
|
||||
}
|
||||
plugin_state->editingIndex = 0;
|
||||
plugin_state->mode = ViewMode;
|
||||
plugin_state->doParityCalculation = true;
|
||||
plugin_state->menuIndex = MENU_INDEX_VIEW;
|
||||
plugin_state->barcodeTypeIndex = 0;
|
||||
}
|
||||
|
||||
static bool handle_key_press_view(InputKey key, PluginState* plugin_state) {
|
||||
switch(key) {
|
||||
case InputKeyOk:
|
||||
case InputKeyBack:
|
||||
plugin_state->mode = MenuMode;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
switch(key) {
|
||||
case InputKeyUp:
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex] =
|
||||
(plugin_state->barcodeNumeral[plugin_state->editingIndex] + 1) % 10;
|
||||
break;
|
||||
|
||||
case InputKeyDown:
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex] =
|
||||
(plugin_state->barcodeNumeral[plugin_state->editingIndex] == 0) ?
|
||||
9 :
|
||||
plugin_state->barcodeNumeral[plugin_state->editingIndex] - 1;
|
||||
break;
|
||||
|
||||
case InputKeyRight:
|
||||
plugin_state->editingIndex = (plugin_state->editingIndex + 1) % barcodeMaxIndex;
|
||||
break;
|
||||
|
||||
case InputKeyLeft:
|
||||
plugin_state->editingIndex = (plugin_state->editingIndex == 0) ?
|
||||
barcodeMaxIndex - 1 :
|
||||
plugin_state->editingIndex - 1;
|
||||
break;
|
||||
|
||||
case InputKeyOk:
|
||||
case InputKeyBack:
|
||||
plugin_state->mode = MenuMode;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handle_key_press_menu(InputKey key, PluginState* plugin_state) {
|
||||
switch(key) {
|
||||
case InputKeyUp:
|
||||
plugin_state->menuIndex = (plugin_state->menuIndex == MENU_INDEX_VIEW) ?
|
||||
MENU_INDEX_TYPE :
|
||||
plugin_state->menuIndex - 1;
|
||||
break;
|
||||
|
||||
case InputKeyDown:
|
||||
plugin_state->menuIndex = (plugin_state->menuIndex + 1) % 4;
|
||||
break;
|
||||
|
||||
case InputKeyRight:
|
||||
if(plugin_state->menuIndex == MENU_INDEX_TYPE) {
|
||||
plugin_state->barcodeTypeIndex =
|
||||
(plugin_state->barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ?
|
||||
0 :
|
||||
plugin_state->barcodeTypeIndex + 1;
|
||||
} else if(plugin_state->menuIndex == MENU_INDEX_PARITY) {
|
||||
plugin_state->doParityCalculation = !plugin_state->doParityCalculation;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(plugin_state->menuIndex == MENU_INDEX_TYPE) {
|
||||
plugin_state->barcodeTypeIndex = (plugin_state->barcodeTypeIndex == 0) ?
|
||||
NUMBER_OF_BARCODE_TYPES - 1 :
|
||||
plugin_state->barcodeTypeIndex - 1;
|
||||
} else if(plugin_state->menuIndex == MENU_INDEX_PARITY) {
|
||||
plugin_state->doParityCalculation = !plugin_state->doParityCalculation;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputKeyOk:
|
||||
if(plugin_state->menuIndex == MENU_INDEX_VIEW) {
|
||||
plugin_state->mode = ViewMode;
|
||||
} else if(plugin_state->menuIndex == MENU_INDEX_EDIT) {
|
||||
plugin_state->mode = EditMode;
|
||||
} else if(plugin_state->menuIndex == MENU_INDEX_PARITY) {
|
||||
plugin_state->doParityCalculation = !plugin_state->doParityCalculation;
|
||||
} else if(plugin_state->menuIndex == MENU_INDEX_TYPE) {
|
||||
plugin_state->barcodeTypeIndex =
|
||||
(plugin_state->barcodeTypeIndex == NUMBER_OF_BARCODE_TYPES - 1) ?
|
||||
0 :
|
||||
plugin_state->barcodeTypeIndex + 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case InputKeyBack:
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t barcode_generator_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
init_types();
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
|
||||
PluginState* plugin_state = malloc(sizeof(PluginState));
|
||||
barcode_generator_state_init(plugin_state);
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, plugin_state, sizeof(PluginState))) {
|
||||
FURI_LOG_E("barcode_generator", "cannot create mutex\r\n");
|
||||
furi_message_queue_free(event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Set system callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
PluginState* plugin_state = (PluginState*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// press events
|
||||
if(event.type == EventTypeKey &&
|
||||
((event.input.type == InputTypePress) || (event.input.type == InputTypeRepeat))) {
|
||||
switch(plugin_state->mode) {
|
||||
case ViewMode:
|
||||
processing = handle_key_press_view(event.input.key, plugin_state);
|
||||
break;
|
||||
case EditMode:
|
||||
processing = handle_key_press_edit(event.input.key, plugin_state);
|
||||
break;
|
||||
case MenuMode:
|
||||
processing = handle_key_press_menu(event.input.key, plugin_state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, plugin_state);
|
||||
}
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#define BARCODE_HEIGHT 50
|
||||
#define BARCODE_Y_START 3
|
||||
#define BARCODE_TEXT_OFFSET 9
|
||||
#define NUMBER_OF_BARCODE_TYPES 2
|
||||
#define MENU_INDEX_VIEW 0
|
||||
#define MENU_INDEX_EDIT 1
|
||||
#define MENU_INDEX_PARITY 2
|
||||
#define MENU_INDEX_TYPE 3
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef enum {
|
||||
ViewMode,
|
||||
EditMode,
|
||||
MenuMode,
|
||||
} Mode;
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
int numberOfDigits;
|
||||
int startPos;
|
||||
} BarcodeType;
|
||||
|
||||
typedef struct {
|
||||
int barcodeNumeral[12]; //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
|
||||
bool doParityCalculation; //Should do parity check?
|
||||
int barcodeTypeIndex;
|
||||
} PluginState;
|
||||
|
||||
static const int DIGITS[10][4] = {
|
||||
{3, 2, 1, 1},
|
||||
{2, 2, 2, 1},
|
||||
{2, 1, 2, 2},
|
||||
{1, 4, 1, 1},
|
||||
{1, 1, 3, 2},
|
||||
{1, 2, 3, 1},
|
||||
{1, 1, 1, 4},
|
||||
{1, 3, 1, 2},
|
||||
{1, 2, 1, 3},
|
||||
{3, 1, 1, 2},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
App(
|
||||
appid="BlackJack",
|
||||
name="BlackJack",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="blackjack_app",
|
||||
cdefines=["APP_BLACKJACK"],
|
||||
requires=["gui","storage","canvas"],
|
||||
stack_size=2 * 1024,
|
||||
order=30,
|
||||
fap_icon="blackjack_10px.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets"
|
||||
)
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 409 B |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,634 @@
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <stdlib.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
#include <gui/canvas_i.h>
|
||||
|
||||
#include <math.h>
|
||||
#include "util.h"
|
||||
#include "defines.h"
|
||||
#include "common/card.h"
|
||||
#include "common/dml.h"
|
||||
#include "common/queue.h"
|
||||
#include "util.h"
|
||||
#include "ui.h"
|
||||
|
||||
#include "BlackJack_icons.h"
|
||||
|
||||
#define DEALER_MAX 17
|
||||
|
||||
void start_round(GameState* game_state);
|
||||
|
||||
void init(GameState* game_state);
|
||||
|
||||
static void draw_ui(Canvas* const canvas, const GameState* game_state) {
|
||||
draw_money(canvas, game_state->player_score);
|
||||
|
||||
draw_score(canvas, true, hand_count(game_state->player_cards, game_state->player_card_count));
|
||||
|
||||
if(!game_state->queue_state.running && game_state->state == GameStatePlay) {
|
||||
render_menu(game_state->menu, canvas, 2, 47);
|
||||
}
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
|
||||
if(game_state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
if(game_state->state == GameStateStart) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_blackjack);
|
||||
}
|
||||
if(game_state->state == GameStateGameOver) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_endscreen);
|
||||
}
|
||||
|
||||
if(game_state->state == GameStatePlay || game_state->state == GameStateDealer) {
|
||||
if(game_state->state == GameStatePlay)
|
||||
draw_player_scene(canvas, game_state);
|
||||
else
|
||||
draw_dealer_scene(canvas, game_state);
|
||||
render_queue(&(game_state->queue_state), game_state, canvas);
|
||||
draw_ui(canvas, game_state);
|
||||
} else if(game_state->state == GameStateSettings) {
|
||||
settings_page(canvas, game_state);
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, game_state);
|
||||
}
|
||||
|
||||
//region card draw
|
||||
Card draw_card(GameState* game_state) {
|
||||
Card c = game_state->deck.cards[game_state->deck.index];
|
||||
game_state->deck.index++;
|
||||
return c;
|
||||
}
|
||||
|
||||
void drawPlayerCard(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
Card c = draw_card(game_state);
|
||||
game_state->player_cards[game_state->player_card_count] = c;
|
||||
game_state->player_card_count++;
|
||||
if(game_state->player_score < game_state->settings.round_price || game_state->doubled) {
|
||||
set_menu_state(game_state->menu, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void drawDealerCard(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
Card c = draw_card(game_state);
|
||||
game_state->dealer_cards[game_state->dealer_card_count] = c;
|
||||
game_state->dealer_card_count++;
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region queue callbacks
|
||||
void to_lose_state(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
if(game_state->settings.message_duration == 0) return;
|
||||
popup_frame(canvas);
|
||||
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost");
|
||||
}
|
||||
|
||||
void to_bust_state(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
if(game_state->settings.message_duration == 0) return;
|
||||
popup_frame(canvas);
|
||||
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!");
|
||||
}
|
||||
|
||||
void to_draw_state(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
if(game_state->settings.message_duration == 0) return;
|
||||
popup_frame(canvas);
|
||||
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw");
|
||||
}
|
||||
|
||||
void to_dealer_turn(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
if(game_state->settings.message_duration == 0) return;
|
||||
popup_frame(canvas);
|
||||
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn");
|
||||
}
|
||||
|
||||
void to_win_state(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
if(game_state->settings.message_duration == 0) return;
|
||||
popup_frame(canvas);
|
||||
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win");
|
||||
}
|
||||
|
||||
void to_start(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
if(game_state->settings.message_duration == 0) return;
|
||||
popup_frame(canvas);
|
||||
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started");
|
||||
}
|
||||
|
||||
void before_start(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
game_state->dealer_card_count = 0;
|
||||
game_state->player_card_count = 0;
|
||||
}
|
||||
|
||||
void start(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
start_round(game_state);
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
game_state->player_score += game_state->bet;
|
||||
game_state->bet = 0;
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
start,
|
||||
before_start,
|
||||
to_start,
|
||||
game_state->settings.message_duration);
|
||||
}
|
||||
|
||||
void game_over(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
game_state->state = GameStateGameOver;
|
||||
}
|
||||
|
||||
void lose(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
game_state->state = GameStatePlay;
|
||||
game_state->bet = 0;
|
||||
if(game_state->player_score >= game_state->settings.round_price) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
start,
|
||||
before_start,
|
||||
to_start,
|
||||
game_state->settings.message_duration);
|
||||
} else {
|
||||
enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void win(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
game_state->state = GameStatePlay;
|
||||
game_state->player_score += game_state->bet * 2;
|
||||
game_state->bet = 0;
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
start,
|
||||
before_start,
|
||||
to_start,
|
||||
game_state->settings.message_duration);
|
||||
}
|
||||
|
||||
void dealerTurn(void* ctx) {
|
||||
GameState* game_state = ctx;
|
||||
game_state->state = GameStateDealer;
|
||||
}
|
||||
|
||||
float animationTime(const GameState* game_state) {
|
||||
return (float)(furi_get_tick() - game_state->queue_state.start) /
|
||||
(float)(game_state->settings.animation_duration);
|
||||
}
|
||||
|
||||
void dealer_card_animation(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
float t = animationTime(game_state);
|
||||
|
||||
Card animatingCard = game_state->deck.cards[game_state->deck.index];
|
||||
if(game_state->dealer_card_count > 1) {
|
||||
Vector end = card_pos_at_index(game_state->dealer_card_count);
|
||||
draw_card_animation(animatingCard, (Vector){0, 64}, (Vector){0, 32}, end, t, true, canvas);
|
||||
} else {
|
||||
draw_card_animation(
|
||||
animatingCard,
|
||||
(Vector){32, -CARD_HEIGHT},
|
||||
(Vector){64, 32},
|
||||
(Vector){2, 2},
|
||||
t,
|
||||
false,
|
||||
canvas);
|
||||
}
|
||||
}
|
||||
|
||||
void dealer_back_card_animation(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
float t = animationTime(game_state);
|
||||
|
||||
Vector currentPos =
|
||||
quadratic_2d((Vector){32, -CARD_HEIGHT}, (Vector){64, 32}, (Vector){13, 5}, t);
|
||||
draw_card_back_at(currentPos.x, currentPos.y, canvas);
|
||||
}
|
||||
|
||||
void player_card_animation(const void* ctx, Canvas* const canvas) {
|
||||
const GameState* game_state = ctx;
|
||||
float t = animationTime(game_state);
|
||||
|
||||
Card animatingCard = game_state->deck.cards[game_state->deck.index];
|
||||
Vector end = card_pos_at_index(game_state->player_card_count);
|
||||
|
||||
draw_card_animation(
|
||||
animatingCard, (Vector){32, -CARD_HEIGHT}, (Vector){0, 32}, end, t, true, canvas);
|
||||
}
|
||||
//endregion
|
||||
|
||||
void player_tick(GameState* game_state) {
|
||||
uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count);
|
||||
if((game_state->doubled && score <= 21) || score == 21) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
dealerTurn,
|
||||
NULL,
|
||||
to_dealer_turn,
|
||||
game_state->settings.message_duration);
|
||||
} else if(score > 21) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
lose,
|
||||
NULL,
|
||||
to_bust_state,
|
||||
game_state->settings.message_duration);
|
||||
} else {
|
||||
if(game_state->selectDirection == DirectionUp ||
|
||||
game_state->selectDirection == DirectionDown) {
|
||||
move_menu(game_state->menu, game_state->selectDirection == DirectionUp ? -1 : 1);
|
||||
}
|
||||
|
||||
if(game_state->selectDirection == Select) {
|
||||
activate_menu(game_state->menu, game_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dealer_tick(GameState* game_state) {
|
||||
uint8_t dealer_score = hand_count(game_state->dealer_cards, game_state->dealer_card_count);
|
||||
uint8_t player_score = hand_count(game_state->player_cards, game_state->player_card_count);
|
||||
|
||||
if(dealer_score >= DEALER_MAX) {
|
||||
if(dealer_score > 21 || dealer_score < player_score) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
win,
|
||||
NULL,
|
||||
to_win_state,
|
||||
game_state->settings.message_duration);
|
||||
} else if(dealer_score > player_score) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
lose,
|
||||
NULL,
|
||||
to_lose_state,
|
||||
game_state->settings.message_duration);
|
||||
} else if(dealer_score == player_score) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
draw,
|
||||
NULL,
|
||||
to_draw_state,
|
||||
game_state->settings.message_duration);
|
||||
}
|
||||
} else {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawDealerCard,
|
||||
NULL,
|
||||
dealer_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
}
|
||||
}
|
||||
|
||||
void settings_tick(GameState* game_state) {
|
||||
if(game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) {
|
||||
game_state->selectedMenu++;
|
||||
}
|
||||
if(game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
|
||||
game_state->selectedMenu--;
|
||||
}
|
||||
|
||||
if(game_state->selectDirection == DirectionLeft ||
|
||||
game_state->selectDirection == DirectionRight) {
|
||||
int nextScore = 0;
|
||||
switch(game_state->selectedMenu) {
|
||||
case 0:
|
||||
nextScore = game_state->settings.starting_money;
|
||||
if(game_state->selectDirection == DirectionLeft)
|
||||
nextScore -= 10;
|
||||
else
|
||||
nextScore += 10;
|
||||
if(nextScore >= (int)game_state->settings.round_price && nextScore < 400)
|
||||
game_state->settings.starting_money = nextScore;
|
||||
break;
|
||||
case 1:
|
||||
nextScore = game_state->settings.round_price;
|
||||
if(game_state->selectDirection == DirectionLeft)
|
||||
nextScore -= 10;
|
||||
else
|
||||
nextScore += 10;
|
||||
if(nextScore >= 5 && nextScore <= (int)game_state->settings.starting_money)
|
||||
game_state->settings.round_price = nextScore;
|
||||
break;
|
||||
case 2:
|
||||
nextScore = game_state->settings.animation_duration;
|
||||
if(game_state->selectDirection == DirectionLeft)
|
||||
nextScore -= 100;
|
||||
else
|
||||
nextScore += 100;
|
||||
if(nextScore >= 0 && nextScore < 2000)
|
||||
game_state->settings.animation_duration = nextScore;
|
||||
break;
|
||||
case 3:
|
||||
nextScore = game_state->settings.message_duration;
|
||||
if(game_state->selectDirection == DirectionLeft)
|
||||
nextScore -= 100;
|
||||
else
|
||||
nextScore += 100;
|
||||
if(nextScore >= 0 && nextScore < 2000)
|
||||
game_state->settings.message_duration = nextScore;
|
||||
break;
|
||||
case 4:
|
||||
game_state->settings.sound_effects = !game_state->settings.sound_effects;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tick(GameState* game_state) {
|
||||
game_state->last_tick = furi_get_tick();
|
||||
bool queue_ran = run_queue(&(game_state->queue_state), game_state);
|
||||
|
||||
switch(game_state->state) {
|
||||
case GameStateGameOver:
|
||||
case GameStateStart:
|
||||
if(game_state->selectDirection == Select)
|
||||
init(game_state);
|
||||
else if(game_state->selectDirection == DirectionRight) {
|
||||
game_state->selectedMenu = 0;
|
||||
game_state->state = GameStateSettings;
|
||||
}
|
||||
break;
|
||||
case GameStatePlay:
|
||||
if(!game_state->started) {
|
||||
game_state->selectedMenu = 0;
|
||||
game_state->started = true;
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawDealerCard,
|
||||
NULL,
|
||||
dealer_back_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawPlayerCard,
|
||||
NULL,
|
||||
player_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawDealerCard,
|
||||
NULL,
|
||||
dealer_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawPlayerCard,
|
||||
NULL,
|
||||
player_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
}
|
||||
if(!queue_ran) player_tick(game_state);
|
||||
break;
|
||||
case GameStateDealer:
|
||||
if(!queue_ran) dealer_tick(game_state);
|
||||
break;
|
||||
case GameStateSettings:
|
||||
settings_tick(game_state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
game_state->selectDirection = None;
|
||||
}
|
||||
|
||||
void start_round(GameState* game_state) {
|
||||
game_state->menu->current_menu = 1;
|
||||
game_state->player_card_count = 0;
|
||||
game_state->dealer_card_count = 0;
|
||||
set_menu_state(game_state->menu, 0, true);
|
||||
game_state->menu->enabled = true;
|
||||
game_state->started = false;
|
||||
game_state->doubled = false;
|
||||
game_state->queue_state.running = true;
|
||||
shuffle_deck(&(game_state->deck));
|
||||
game_state->doubled = false;
|
||||
game_state->bet = game_state->settings.round_price;
|
||||
if(game_state->player_score < game_state->settings.round_price) {
|
||||
game_state->state = GameStateGameOver;
|
||||
} else {
|
||||
game_state->player_score -= game_state->settings.round_price;
|
||||
}
|
||||
game_state->state = GameStatePlay;
|
||||
}
|
||||
|
||||
void init(GameState* game_state) {
|
||||
set_menu_state(game_state->menu, 0, true);
|
||||
game_state->menu->enabled = true;
|
||||
game_state->menu->current_menu = 1;
|
||||
game_state->settings = load_settings();
|
||||
game_state->last_tick = 0;
|
||||
game_state->processing = true;
|
||||
game_state->selectedMenu = 0;
|
||||
game_state->player_score = game_state->settings.starting_money;
|
||||
generate_deck(&(game_state->deck), 6);
|
||||
start_round(game_state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
AppEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void update_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
AppEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
void doubleAction(void* state) {
|
||||
GameState* game_state = state;
|
||||
if(!game_state->doubled && game_state->player_score >= game_state->settings.round_price) {
|
||||
game_state->player_score -= game_state->settings.round_price;
|
||||
game_state->bet += game_state->settings.round_price;
|
||||
game_state->doubled = true;
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawPlayerCard,
|
||||
NULL,
|
||||
player_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
game_state->player_cards[game_state->player_card_count] =
|
||||
game_state->deck.cards[game_state->deck.index];
|
||||
uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count + 1);
|
||||
if(score > 21) {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
lose,
|
||||
NULL,
|
||||
to_bust_state,
|
||||
game_state->settings.message_duration);
|
||||
} else {
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
dealerTurn,
|
||||
NULL,
|
||||
to_dealer_turn,
|
||||
game_state->settings.message_duration);
|
||||
}
|
||||
set_menu_state(game_state->menu, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void hitAction(void* state) {
|
||||
GameState* game_state = state;
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
drawPlayerCard,
|
||||
NULL,
|
||||
player_card_animation,
|
||||
game_state->settings.animation_duration);
|
||||
}
|
||||
void stayAction(void* state) {
|
||||
GameState* game_state = state;
|
||||
enqueue(
|
||||
&(game_state->queue_state),
|
||||
game_state,
|
||||
dealerTurn,
|
||||
NULL,
|
||||
to_dealer_turn,
|
||||
game_state->settings.message_duration);
|
||||
}
|
||||
|
||||
int32_t blackjack_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
int32_t return_code = 0;
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
|
||||
|
||||
GameState* game_state = malloc(sizeof(GameState));
|
||||
game_state->menu = malloc(sizeof(Menu));
|
||||
game_state->menu->menu_width = 40;
|
||||
init(game_state);
|
||||
add_menu(game_state->menu, "Double", doubleAction);
|
||||
add_menu(game_state->menu, "Hit", hitAction);
|
||||
add_menu(game_state->menu, "Stay", stayAction);
|
||||
set_card_graphics(&I_card_graphics);
|
||||
|
||||
game_state->state = GameStateStart;
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
|
||||
FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
|
||||
return_code = 255;
|
||||
goto free_and_exit;
|
||||
}
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||
view_port_input_callback_set(view_port, input_callback, event_queue);
|
||||
|
||||
FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
|
||||
|
||||
Gui* gui = furi_record_open("gui");
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
AppEvent event;
|
||||
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
GameState* localstate = (GameState*)acquire_mutex_block(&state_mutex);
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
localstate->selectDirection = DirectionUp;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
localstate->selectDirection = DirectionDown;
|
||||
break;
|
||||
case InputKeyRight:
|
||||
localstate->selectDirection = DirectionRight;
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
localstate->selectDirection = DirectionLeft;
|
||||
break;
|
||||
case InputKeyBack:
|
||||
if(localstate->state == GameStateSettings) {
|
||||
localstate->state = GameStateStart;
|
||||
save_settings(localstate->settings);
|
||||
} else
|
||||
processing = false;
|
||||
break;
|
||||
case InputKeyOk:
|
||||
localstate->selectDirection = Select;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
tick(localstate);
|
||||
processing = localstate->processing;
|
||||
}
|
||||
} else {
|
||||
//FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, localstate);
|
||||
}
|
||||
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
free_and_exit:
|
||||
free(game_state->deck.cards);
|
||||
free_menu(game_state->menu);
|
||||
queue_clear(&(game_state->queue_state));
|
||||
free(game_state);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return return_code;
|
||||
}
|
||||
|
After Width: | Height: | Size: 119 B |
@@ -0,0 +1,353 @@
|
||||
#include "card.h"
|
||||
#include "dml.h"
|
||||
#include "ui.h"
|
||||
|
||||
#define CARD_DRAW_X_START 108
|
||||
#define CARD_DRAW_Y_START 38
|
||||
#define CARD_DRAW_X_SPACE 10
|
||||
#define CARD_DRAW_Y_SPACE 8
|
||||
#define CARD_DRAW_X_OFFSET 4
|
||||
#define CARD_DRAW_FIRST_ROW_LENGTH 7
|
||||
|
||||
uint8_t pips[4][3] = {
|
||||
{21, 10, 7}, //spades
|
||||
{7, 10, 7}, //hearts
|
||||
{0, 10, 7}, //diamonds
|
||||
{14, 10, 7}, //clubs
|
||||
};
|
||||
uint8_t letters[13][3] = {
|
||||
{0, 0, 5},
|
||||
{5, 0, 5},
|
||||
{10, 0, 5},
|
||||
{15, 0, 5},
|
||||
{20, 0, 5},
|
||||
{25, 0, 5},
|
||||
{30, 0, 5},
|
||||
{0, 5, 5},
|
||||
{5, 5, 5},
|
||||
{10, 5, 5},
|
||||
{15, 5, 5},
|
||||
{20, 5, 5},
|
||||
{25, 5, 5},
|
||||
};
|
||||
|
||||
//region Player card positions
|
||||
uint8_t playerCardPositions[22][4] = {
|
||||
//first row
|
||||
{108, 38},
|
||||
{98, 38},
|
||||
{88, 38},
|
||||
{78, 38},
|
||||
{68, 38},
|
||||
{58, 38},
|
||||
{48, 38},
|
||||
{38, 38},
|
||||
//second row
|
||||
{104, 26},
|
||||
{94, 26},
|
||||
{84, 26},
|
||||
{74, 26},
|
||||
{64, 26},
|
||||
{54, 26},
|
||||
{44, 26},
|
||||
//third row
|
||||
{99, 14},
|
||||
{89, 14},
|
||||
{79, 14},
|
||||
{69, 14},
|
||||
{59, 14},
|
||||
{49, 14},
|
||||
};
|
||||
//endregion
|
||||
Icon* card_graphics = NULL;
|
||||
|
||||
void set_card_graphics(const Icon* graphics) {
|
||||
card_graphics = (Icon*)graphics;
|
||||
}
|
||||
|
||||
void draw_card_at_colored(
|
||||
int8_t pos_x,
|
||||
int8_t pos_y,
|
||||
uint8_t pip,
|
||||
uint8_t character,
|
||||
bool inverted,
|
||||
Canvas* const canvas) {
|
||||
DrawMode primary = inverted ? Black : White;
|
||||
DrawMode secondary = inverted ? White : Black;
|
||||
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, primary);
|
||||
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
|
||||
|
||||
uint8_t* drawInfo = pips[pip];
|
||||
uint8_t px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
|
||||
|
||||
uint8_t left = pos_x + 2;
|
||||
uint8_t right = (pos_x + CARD_WIDTH - s - 2);
|
||||
uint8_t top = pos_y + 2;
|
||||
uint8_t bottom = (pos_y + CARD_HEIGHT - s - 2);
|
||||
|
||||
draw_icon_clip(canvas, card_graphics, right, top, px, py, s, s, secondary);
|
||||
draw_icon_clip_flipped(canvas, card_graphics, left, bottom, px, py, s, s, secondary);
|
||||
|
||||
drawInfo = letters[character];
|
||||
px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
|
||||
left = pos_x + 2;
|
||||
right = (pos_x + CARD_WIDTH - s - 2);
|
||||
top = pos_y + 2;
|
||||
bottom = (pos_y + CARD_HEIGHT - s - 2);
|
||||
|
||||
draw_icon_clip(canvas, card_graphics, left, top + 1, px, py, s, s, secondary);
|
||||
draw_icon_clip_flipped(canvas, card_graphics, right, bottom - 1, px, py, s, s, secondary);
|
||||
}
|
||||
|
||||
void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas) {
|
||||
draw_card_at_colored(pos_x, pos_y, pip, character, false, canvas);
|
||||
}
|
||||
|
||||
void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas) {
|
||||
for(int i = count - 1; i >= 0; i--) {
|
||||
draw_card_at(
|
||||
playerCardPositions[i][0],
|
||||
playerCardPositions[i][1],
|
||||
cards[i].pip,
|
||||
cards[i].character,
|
||||
canvas);
|
||||
}
|
||||
}
|
||||
|
||||
Vector card_pos_at_index(uint8_t index) {
|
||||
return (Vector){playerCardPositions[index][0], playerCardPositions[index][1]};
|
||||
}
|
||||
|
||||
void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas) {
|
||||
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, White);
|
||||
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
|
||||
|
||||
draw_icon_clip(canvas, card_graphics, pos_x + 1, pos_y + 1, 35, 0, 15, 21, Black);
|
||||
}
|
||||
|
||||
void generate_deck(Deck* deck_ptr, uint8_t deck_count) {
|
||||
uint16_t counter = 0;
|
||||
if(deck_ptr->cards != NULL) {
|
||||
free(deck_ptr->cards);
|
||||
}
|
||||
|
||||
deck_ptr->deck_count = deck_count;
|
||||
deck_ptr->card_count = deck_count * 52;
|
||||
deck_ptr->cards = malloc(sizeof(Card) * deck_ptr->card_count);
|
||||
|
||||
for(uint8_t deck = 0; deck < deck_count; deck++) {
|
||||
for(uint8_t pip = 0; pip < 4; pip++) {
|
||||
for(uint8_t label = 0; label < 13; label++) {
|
||||
deck_ptr->cards[counter] = (Card){pip, label, false, false};
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shuffle_deck(Deck* deck_ptr) {
|
||||
srand(DWT->CYCCNT);
|
||||
deck_ptr->index = 0;
|
||||
int max = deck_ptr->deck_count * 52;
|
||||
for(int i = 0; i < max; i++) {
|
||||
int r = i + (rand() % (max - i));
|
||||
Card tmp = deck_ptr->cards[i];
|
||||
deck_ptr->cards[i] = deck_ptr->cards[r];
|
||||
deck_ptr->cards[r] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t hand_count(const Card* cards, uint8_t count) {
|
||||
uint8_t aceCount = 0;
|
||||
uint8_t score = 0;
|
||||
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
if(cards[i].character == 12)
|
||||
aceCount++;
|
||||
else {
|
||||
if(cards[i].character > 8)
|
||||
score += 10;
|
||||
else
|
||||
score += cards[i].character + 2;
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < aceCount; i++) {
|
||||
if((score + 11) <= 21)
|
||||
score += 11;
|
||||
else
|
||||
score++;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
void draw_card_animation(
|
||||
Card animatingCard,
|
||||
Vector from,
|
||||
Vector control,
|
||||
Vector to,
|
||||
float t,
|
||||
bool extra_margin,
|
||||
Canvas* const canvas) {
|
||||
float time = t;
|
||||
if(extra_margin) {
|
||||
time += 0.2;
|
||||
}
|
||||
|
||||
Vector currentPos = quadratic_2d(from, control, to, time);
|
||||
if(t > 1) {
|
||||
draw_card_at(
|
||||
currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
|
||||
} else {
|
||||
if(t < 0.5)
|
||||
draw_card_back_at(currentPos.x, currentPos.y, canvas);
|
||||
else
|
||||
draw_card_at(
|
||||
currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
void init_hand(Hand* hand_ptr, uint8_t count) {
|
||||
hand_ptr->cards = malloc(sizeof(Card) * count);
|
||||
hand_ptr->index = 0;
|
||||
hand_ptr->max = count;
|
||||
}
|
||||
|
||||
void free_hand(Hand* hand_ptr) {
|
||||
FURI_LOG_D("CARD", "Freeing hand");
|
||||
free(hand_ptr->cards);
|
||||
}
|
||||
|
||||
void add_to_hand(Hand* hand_ptr, Card card) {
|
||||
FURI_LOG_D("CARD", "Adding to hand");
|
||||
if(hand_ptr->index < hand_ptr->max) {
|
||||
hand_ptr->cards[hand_ptr->index] = card;
|
||||
hand_ptr->index++;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas) {
|
||||
if(highlighted) {
|
||||
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
|
||||
draw_rounded_box_frame(
|
||||
canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
|
||||
} else {
|
||||
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
|
||||
draw_rounded_box_frame(
|
||||
canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
|
||||
}
|
||||
}
|
||||
|
||||
int first_non_flipped_card(Hand hand) {
|
||||
for(int i = 0; i < hand.index; i++) {
|
||||
if(!hand.cards[i].flipped) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return hand.index;
|
||||
}
|
||||
|
||||
void draw_hand_column(
|
||||
Hand hand,
|
||||
int16_t pos_x,
|
||||
int16_t pos_y,
|
||||
int8_t highlight,
|
||||
Canvas* const canvas) {
|
||||
if(hand.index == 0) {
|
||||
draw_card_space(pos_x, pos_y, highlight > 0, canvas);
|
||||
if(highlight == 0)
|
||||
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Inverse);
|
||||
return;
|
||||
}
|
||||
|
||||
int loopEnd = hand.index;
|
||||
int hStart = max(loopEnd - 4, 0);
|
||||
int pos = 0;
|
||||
int first = first_non_flipped_card(hand);
|
||||
bool wastop = false;
|
||||
if(first >= 0 && first <= hStart && highlight != first) {
|
||||
if(first > 0) {
|
||||
draw_card_back_at(pos_x, pos_y + pos, canvas);
|
||||
pos += 4;
|
||||
hStart++;
|
||||
wastop = true;
|
||||
}
|
||||
draw_card_at_colored(
|
||||
pos_x, pos_y + pos, hand.cards[first].pip, hand.cards[first].character, false, canvas);
|
||||
pos += 8;
|
||||
hStart++;
|
||||
}
|
||||
if(hStart > highlight && highlight >= 0) {
|
||||
if(!wastop && first > 0) {
|
||||
draw_card_back_at(pos_x, pos_y + pos, canvas);
|
||||
pos += 4;
|
||||
hStart++;
|
||||
}
|
||||
draw_card_at_colored(
|
||||
pos_x,
|
||||
pos_y + pos,
|
||||
hand.cards[highlight].pip,
|
||||
hand.cards[highlight].character,
|
||||
true,
|
||||
canvas);
|
||||
pos += 8;
|
||||
hStart++;
|
||||
}
|
||||
for(int i = hStart; i < loopEnd; i++, pos += 4) {
|
||||
if(hand.cards[i].flipped) {
|
||||
draw_card_back_at(pos_x, pos_y + pos, canvas);
|
||||
if(i == highlight)
|
||||
draw_rounded_box(
|
||||
canvas, pos_x + 1, pos_y + pos + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2, Inverse);
|
||||
} else {
|
||||
draw_card_at_colored(
|
||||
pos_x,
|
||||
pos_y + pos,
|
||||
hand.cards[i].pip,
|
||||
hand.cards[i].character,
|
||||
(i == highlight),
|
||||
canvas);
|
||||
if(i == highlight || i == first) pos += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Card remove_from_deck(uint16_t index, Deck* deck) {
|
||||
FURI_LOG_D("CARD", "Removing from deck");
|
||||
Card result = {0, 0, true, false};
|
||||
if(deck->card_count > 0) {
|
||||
deck->card_count--;
|
||||
for(int i = 0, curr_index = 0; i <= deck->card_count; i++) {
|
||||
if(i != index) {
|
||||
deck->cards[curr_index] = deck->cards[i];
|
||||
curr_index++;
|
||||
} else {
|
||||
result = deck->cards[i];
|
||||
}
|
||||
}
|
||||
if(deck->index >= 0) {
|
||||
deck->index--;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index) {
|
||||
FURI_LOG_D("CARD", "Extracting hand region");
|
||||
if(start_index >= hand->index) return;
|
||||
|
||||
for(uint8_t i = start_index; i < hand->index; i++) {
|
||||
add_to_hand(to, hand->cards[i]);
|
||||
}
|
||||
hand->index = start_index;
|
||||
}
|
||||
|
||||
void add_hand_region(Hand* to, Hand* from) {
|
||||
FURI_LOG_D("CARD", "Adding hand region");
|
||||
if((to->index + from->index) <= to->max) {
|
||||
for(int i = 0; i < from->index; i++) {
|
||||
add_to_hand(to, from->cards[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include "dml.h"
|
||||
|
||||
#define CARD_HEIGHT 23
|
||||
#define CARD_HALF_HEIGHT 11
|
||||
#define CARD_WIDTH 17
|
||||
#define CARD_HALF_WIDTH 8
|
||||
|
||||
//region types
|
||||
typedef struct {
|
||||
uint8_t pip; //Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
|
||||
uint8_t character; //Card letter [0-12], 0 means 2, 12 is Ace
|
||||
bool disabled;
|
||||
bool flipped;
|
||||
} Card;
|
||||
|
||||
typedef struct {
|
||||
uint8_t deck_count; //Number of decks used
|
||||
Card* cards; //Cards in the deck
|
||||
int card_count;
|
||||
int index; //Card index (to know where we at in the deck)
|
||||
} Deck;
|
||||
|
||||
typedef struct {
|
||||
Card* cards; //Cards in the deck
|
||||
uint8_t index; //Current index
|
||||
uint8_t max; //How many cards we want to store
|
||||
} Hand;
|
||||
//endregion
|
||||
|
||||
void set_card_graphics(const Icon* graphics);
|
||||
|
||||
/**
|
||||
* Gets card coordinates at the index (range: 0-20).
|
||||
*
|
||||
* @param index Index to check 0-20
|
||||
* @return Position of the card
|
||||
*/
|
||||
Vector card_pos_at_index(uint8_t index);
|
||||
|
||||
/**
|
||||
* Draws card at a given coordinate (top-left corner)
|
||||
*
|
||||
* @param pos_x X position
|
||||
* @param pos_y Y position
|
||||
* @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
|
||||
* @param character Letter [0-12] 0 is 2, 12 is A
|
||||
* @param canvas Pointer to Flipper's canvas object
|
||||
*/
|
||||
void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Draws card at a given coordinate (top-left corner)
|
||||
*
|
||||
* @param pos_x X position
|
||||
* @param pos_y Y position
|
||||
* @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
|
||||
* @param character Letter [0-12] 0 is 2, 12 is A
|
||||
* @param inverted Invert colors
|
||||
* @param canvas Pointer to Flipper's canvas object
|
||||
*/
|
||||
void draw_card_at_colored(
|
||||
int8_t pos_x,
|
||||
int8_t pos_y,
|
||||
uint8_t pip,
|
||||
uint8_t character,
|
||||
bool inverted,
|
||||
Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Draws 'count' cards at the bottom right corner
|
||||
*
|
||||
* @param cards List of cards
|
||||
* @param count Count of cards
|
||||
* @param canvas Pointer to Flipper's canvas object
|
||||
*/
|
||||
void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Draws card back at a given coordinate (top-left corner)
|
||||
*
|
||||
* @param pos_x X coordinate
|
||||
* @param pos_y Y coordinate
|
||||
* @param canvas Pointer to Flipper's canvas object
|
||||
*/
|
||||
void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Generates the deck
|
||||
*
|
||||
* @param deck_ptr Pointer to the deck
|
||||
* @param deck_count Number of decks
|
||||
*/
|
||||
void generate_deck(Deck* deck_ptr, uint8_t deck_count);
|
||||
|
||||
/**
|
||||
* Shuffles the deck
|
||||
*
|
||||
* @param deck_ptr Pointer to the deck
|
||||
*/
|
||||
void shuffle_deck(Deck* deck_ptr);
|
||||
|
||||
/**
|
||||
* Calculates the hand count for blackjack
|
||||
*
|
||||
* @param cards List of cards
|
||||
* @param count Count of cards
|
||||
* @return Hand value
|
||||
*/
|
||||
uint8_t hand_count(const Card* cards, uint8_t count);
|
||||
|
||||
/**
|
||||
* Draws card animation
|
||||
*
|
||||
* @param animatingCard Card to animate
|
||||
* @param from Starting position
|
||||
* @param control Quadratic lerp control point
|
||||
* @param to End point
|
||||
* @param t Current time (0-1)
|
||||
* @param extra_margin Use extra margin at the end (arrives 0.2 unit before the end so it can stay there a bit)
|
||||
* @param canvas Pointer to Flipper's canvas object
|
||||
*/
|
||||
void draw_card_animation(
|
||||
Card animatingCard,
|
||||
Vector from,
|
||||
Vector control,
|
||||
Vector to,
|
||||
float t,
|
||||
bool extra_margin,
|
||||
Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Init hand pointer
|
||||
* @param hand_ptr Pointer to hand
|
||||
* @param count Number of cards we want to store
|
||||
*/
|
||||
void init_hand(Hand* hand_ptr, uint8_t count);
|
||||
|
||||
/**
|
||||
* Free hand resources
|
||||
* @param hand_ptr Pointer to hand
|
||||
*/
|
||||
void free_hand(Hand* hand_ptr);
|
||||
|
||||
/**
|
||||
* Add card to hand
|
||||
* @param hand_ptr Pointer to hand
|
||||
* @param card Card to add
|
||||
*/
|
||||
void add_to_hand(Hand* hand_ptr, Card card);
|
||||
|
||||
/**
|
||||
* Draw card placement position at coordinate
|
||||
* @param pos_x X coordinate
|
||||
* @param pos_y Y coordinate
|
||||
* @param highlighted Apply highlight effect
|
||||
* @param canvas Canvas object
|
||||
*/
|
||||
void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Draws a column of card, displaying the last [max_cards] cards on the list
|
||||
* @param hand Hand object
|
||||
* @param pos_x X coordinate to draw
|
||||
* @param pos_y Y coordinate to draw
|
||||
* @param highlight Index to highlight, negative means no highlight
|
||||
* @param canvas Canvas object
|
||||
*/
|
||||
void draw_hand_column(
|
||||
Hand hand,
|
||||
int16_t pos_x,
|
||||
int16_t pos_y,
|
||||
int8_t highlight,
|
||||
Canvas* const canvas);
|
||||
|
||||
/**
|
||||
* Removes a card from the deck (Be aware, if you remove the first item, the deck index will be at -1 so you have to handle that)
|
||||
* @param index Index to remove
|
||||
* @param deck Deck reference
|
||||
* @return The removed card
|
||||
*/
|
||||
Card remove_from_deck(uint16_t index, Deck* deck);
|
||||
|
||||
int first_non_flipped_card(Hand hand);
|
||||
|
||||
void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index);
|
||||
|
||||
void add_hand_region(Hand* to, Hand* from);
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "dml.h"
|
||||
#include <math.h>
|
||||
|
||||
float lerp(float v0, float v1, float t) {
|
||||
if(t > 1) return v1;
|
||||
return (1 - t) * v0 + t * v1;
|
||||
}
|
||||
|
||||
Vector lerp_2d(Vector start, Vector end, float t) {
|
||||
return (Vector){
|
||||
lerp(start.x, end.x, t),
|
||||
lerp(start.y, end.y, t),
|
||||
};
|
||||
}
|
||||
|
||||
Vector quadratic_2d(Vector start, Vector control, Vector end, float t) {
|
||||
return lerp_2d(lerp_2d(start, control, t), lerp_2d(control, end, t), t);
|
||||
}
|
||||
|
||||
Vector vector_add(Vector a, Vector b) {
|
||||
return (Vector){a.x + b.x, a.y + b.y};
|
||||
}
|
||||
|
||||
Vector vector_sub(Vector a, Vector b) {
|
||||
return (Vector){a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
Vector vector_mul_components(Vector a, Vector b) {
|
||||
return (Vector){a.x * b.x, a.y * b.y};
|
||||
}
|
||||
|
||||
Vector vector_div_components(Vector a, Vector b) {
|
||||
return (Vector){a.x / b.x, a.y / b.y};
|
||||
}
|
||||
|
||||
Vector vector_normalized(Vector a) {
|
||||
float length = vector_magnitude(a);
|
||||
return (Vector){a.x / length, a.y / length};
|
||||
}
|
||||
|
||||
float vector_magnitude(Vector a) {
|
||||
return sqrt(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
float vector_distance(Vector a, Vector b) {
|
||||
return vector_magnitude(vector_sub(a, b));
|
||||
}
|
||||
|
||||
float vector_dot(Vector a, Vector b) {
|
||||
Vector _a = vector_normalized(a);
|
||||
Vector _b = vector_normalized(b);
|
||||
return _a.x * _b.x + _a.y * _b.y;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Doofy's Math library
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} Vector;
|
||||
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#define abs(x) ((x) > 0 ? (x) : -(x))
|
||||
|
||||
/**
|
||||
* Lerp function
|
||||
*
|
||||
* @param v0 Start value
|
||||
* @param v1 End value
|
||||
* @param t Time (0-1 range)
|
||||
* @return Point between v0-v1 at a given time
|
||||
*/
|
||||
float lerp(float v0, float v1, float t);
|
||||
|
||||
/**
|
||||
* 2D lerp function
|
||||
*
|
||||
* @param start Start vector
|
||||
* @param end End vector
|
||||
* @param t Time (0-1 range)
|
||||
* @return 2d Vector between start and end at time
|
||||
*/
|
||||
Vector lerp_2d(Vector start, Vector end, float t);
|
||||
|
||||
/**
|
||||
* Quadratic lerp function
|
||||
*
|
||||
* @param start Start vector
|
||||
* @param control Control point
|
||||
* @param end End vector
|
||||
* @param t Time (0-1 range)
|
||||
* @return 2d Vector at time
|
||||
*/
|
||||
Vector quadratic_2d(Vector start, Vector control, Vector end, float t);
|
||||
|
||||
/**
|
||||
* Add vector components together
|
||||
*
|
||||
* @param a First vector
|
||||
* @param b Second vector
|
||||
* @return Resulting vector
|
||||
*/
|
||||
Vector vector_add(Vector a, Vector b);
|
||||
|
||||
/**
|
||||
* Subtract vector components together
|
||||
*
|
||||
* @param a First vector
|
||||
* @param b Second vector
|
||||
* @return Resulting vector
|
||||
*/
|
||||
Vector vector_sub(Vector a, Vector b);
|
||||
|
||||
/**
|
||||
* Multiplying vector components together
|
||||
*
|
||||
* @param a First vector
|
||||
* @param b Second vector
|
||||
* @return Resulting vector
|
||||
*/
|
||||
Vector vector_mul_components(Vector a, Vector b);
|
||||
|
||||
/**
|
||||
* Dividing vector components
|
||||
*
|
||||
* @param a First vector
|
||||
* @param b Second vector
|
||||
* @return Resulting vector
|
||||
*/
|
||||
Vector vector_div_components(Vector a, Vector b);
|
||||
|
||||
/**
|
||||
* Calculating Vector length
|
||||
*
|
||||
* @param a Direction vector
|
||||
* @return Length of the vector
|
||||
*/
|
||||
float vector_magnitude(Vector a);
|
||||
|
||||
/**
|
||||
* Get a normalized vector (length of 1)
|
||||
*
|
||||
* @param a Direction vector
|
||||
* @return Normalized vector
|
||||
*/
|
||||
Vector vector_normalized(Vector a);
|
||||
|
||||
/**
|
||||
* Calculate two vector's distance
|
||||
*
|
||||
* @param a First vector
|
||||
* @param b Second vector
|
||||
* @return Distance between vectors
|
||||
*/
|
||||
float vector_distance(Vector a, Vector b);
|
||||
|
||||
/**
|
||||
* Calculate the dot product of the vectors.
|
||||
* No need to normalize, it will do it
|
||||
*
|
||||
* @param a First vector
|
||||
* @param b Second vector
|
||||
* @return value from -1 to 1
|
||||
*/
|
||||
float vector_dot(Vector a, Vector b);
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "menu.h"
|
||||
|
||||
void add_menu(Menu* menu, const char* name, void (*callback)(void*)) {
|
||||
MenuItem* items = menu->items;
|
||||
|
||||
menu->items = malloc(sizeof(MenuItem) * (menu->menu_count + 1));
|
||||
for(uint8_t i = 0; i < menu->menu_count; i++) {
|
||||
menu->items[i] = items[i];
|
||||
}
|
||||
free(items);
|
||||
|
||||
menu->items[menu->menu_count] = (MenuItem){name, true, callback};
|
||||
menu->menu_count++;
|
||||
}
|
||||
|
||||
void free_menu(Menu* menu) {
|
||||
free(menu->items);
|
||||
free(menu);
|
||||
}
|
||||
|
||||
void set_menu_state(Menu* menu, uint8_t index, bool state) {
|
||||
if(menu->menu_count > index) {
|
||||
menu->items[index].enabled = state;
|
||||
}
|
||||
if(!state && menu->current_menu == index) move_menu(menu, 1);
|
||||
}
|
||||
|
||||
void move_menu(Menu* menu, int8_t direction) {
|
||||
if(!menu->enabled) return;
|
||||
int max = menu->menu_count;
|
||||
for(int8_t i = 0; i < max; i++) {
|
||||
FURI_LOG_D(
|
||||
"MENU",
|
||||
"Iteration %i, current %i, direction %i, state %i",
|
||||
i,
|
||||
menu->current_menu,
|
||||
direction,
|
||||
menu->items[menu->current_menu].enabled ? 1 : 0);
|
||||
if(direction < 0 && menu->current_menu == 0) {
|
||||
menu->current_menu = menu->menu_count - 1;
|
||||
} else {
|
||||
menu->current_menu = (menu->current_menu + direction) % menu->menu_count;
|
||||
}
|
||||
FURI_LOG_D(
|
||||
"MENU",
|
||||
"After process current %i, direction %i, state %i",
|
||||
menu->current_menu,
|
||||
direction,
|
||||
menu->items[menu->current_menu].enabled ? 1 : 0);
|
||||
if(menu->items[menu->current_menu].enabled) {
|
||||
FURI_LOG_D("MENU", "Next menu %i", menu->current_menu);
|
||||
return;
|
||||
}
|
||||
}
|
||||
FURI_LOG_D("MENU", "Not found, setting false");
|
||||
menu->enabled = false;
|
||||
}
|
||||
|
||||
void activate_menu(Menu* menu, void* state) {
|
||||
if(!menu->enabled) return;
|
||||
menu->items[menu->current_menu].callback(state);
|
||||
}
|
||||
|
||||
void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y) {
|
||||
if(!menu->enabled) return;
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
|
||||
|
||||
uint8_t w = pos_x + menu->menu_width;
|
||||
uint8_t h = pos_y + 10;
|
||||
uint8_t p1x = pos_x + 2;
|
||||
uint8_t p2x = pos_x + menu->menu_width - 2;
|
||||
uint8_t p1y = pos_y + 2;
|
||||
uint8_t p2y = pos_y + 8;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_line(canvas, p1x, pos_y, p2x, pos_y);
|
||||
canvas_draw_line(canvas, p1x, h, p2x, h);
|
||||
canvas_draw_line(canvas, pos_x, p1y, pos_x, p2y);
|
||||
canvas_draw_line(canvas, w, p1y, w, p2y);
|
||||
canvas_draw_dot(canvas, pos_x + 1, pos_y + 1);
|
||||
canvas_draw_dot(canvas, w - 1, pos_y + 1);
|
||||
canvas_draw_dot(canvas, w - 1, h - 1);
|
||||
canvas_draw_dot(canvas, pos_x + 1, h - 1);
|
||||
|
||||
// canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
pos_x + menu->menu_width / 2,
|
||||
pos_y + 6,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
menu->items[menu->current_menu].name);
|
||||
//9*5
|
||||
int center = pos_x + menu->menu_width / 2;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
for(int8_t j = -i; j <= i; j++) {
|
||||
canvas_draw_dot(canvas, center + j, pos_y - 4 + i);
|
||||
canvas_draw_dot(canvas, center + j, pos_y + 14 - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
typedef struct {
|
||||
const char* name; //Name of the menu
|
||||
bool enabled; //Is the menu item enabled (it will not render, you cannot select it)
|
||||
|
||||
void (*callback)(
|
||||
void* state); //Callback for when the activate_menu is called while this menu is selected
|
||||
} MenuItem;
|
||||
|
||||
typedef struct {
|
||||
MenuItem* items; //list of menu items
|
||||
uint8_t menu_count; //count of menu items (do not change)
|
||||
uint8_t current_menu; //currently selected menu item
|
||||
uint8_t menu_width; //width of the menu
|
||||
bool enabled; //is the menu enabled (it will not render and accept events when disabled)
|
||||
} Menu;
|
||||
|
||||
/**
|
||||
* Cleans up the pointers used by the menu
|
||||
*
|
||||
* @param menu Pointer of the menu to clean up
|
||||
*/
|
||||
void free_menu(Menu* menu);
|
||||
|
||||
/**
|
||||
* Add a new menu item
|
||||
*
|
||||
* @param menu Pointer of the menu
|
||||
* @param name Name of the menu item
|
||||
* @param callback Callback called on activation
|
||||
*/
|
||||
void add_menu(Menu* menu, const char* name, void (*callback)(void*));
|
||||
|
||||
/**
|
||||
* Setting menu item to be enabled/disabled
|
||||
*
|
||||
* @param menu Pointer of the menu
|
||||
* @param index Menu index to set
|
||||
* @param state Enabled (true), Disabled(false)
|
||||
*/
|
||||
void set_menu_state(Menu* menu, uint8_t index, bool state);
|
||||
|
||||
/**
|
||||
* Moves selection up or down
|
||||
*
|
||||
* @param menu Pointer of the menu
|
||||
* @param direction Direction to move -1 down, 1 up
|
||||
*/
|
||||
void move_menu(Menu* menu, int8_t direction);
|
||||
|
||||
/**
|
||||
* Triggers the current menu callback
|
||||
*
|
||||
* @param menu Pointer of the menu
|
||||
* @param state Usually your application state
|
||||
*/
|
||||
void activate_menu(Menu* menu, void* state);
|
||||
|
||||
/**
|
||||
* Renders the menu at a coordinate (call it in your render function).
|
||||
*
|
||||
* Keep in mind that Flipper has a 128x64 pixel screen resolution and the coordinate
|
||||
* you give is the menu's rectangle top-left corner (arrows not included).
|
||||
* The rectangle height is 10 px, the arrows have a 4 pixel height. Space needed is 18px.
|
||||
* The width of the menu can be configured in the menu object.
|
||||
*
|
||||
*
|
||||
* @param menu Pointer of the menu
|
||||
* @param canvas Flippers Canvas pointer
|
||||
* @param pos_x X position to draw
|
||||
* @param pos_y Y position to draw
|
||||
*/
|
||||
void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y);
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "queue.h"
|
||||
|
||||
void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas) {
|
||||
if(queue_state->current != NULL && queue_state->current->render != NULL)
|
||||
((QueueItem*)queue_state->current)->render(app_state, canvas);
|
||||
}
|
||||
|
||||
bool run_queue(QueueState* queue_state, void* app_state) {
|
||||
if(queue_state->current != NULL) {
|
||||
queue_state->running = true;
|
||||
if((furi_get_tick() - queue_state->start) >= queue_state->current->duration)
|
||||
dequeue(queue_state, app_state);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void dequeue(QueueState* queue_state, void* app_state) {
|
||||
((QueueItem*)queue_state->current)->callback(app_state);
|
||||
QueueItem* f = queue_state->current;
|
||||
queue_state->current = f->next;
|
||||
free(f);
|
||||
if(queue_state->current != NULL) {
|
||||
if(queue_state->current->start != NULL) queue_state->current->start(app_state);
|
||||
queue_state->start = furi_get_tick();
|
||||
} else {
|
||||
queue_state->running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void queue_clear(QueueState* queue_state) {
|
||||
queue_state->running = false;
|
||||
QueueItem* curr = queue_state->current;
|
||||
while(curr != NULL) {
|
||||
QueueItem* f = curr;
|
||||
curr = curr->next;
|
||||
free(f);
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue(
|
||||
QueueState* queue_state,
|
||||
void* app_state,
|
||||
void (*done)(void* state),
|
||||
void (*start)(void* state),
|
||||
void (*render)(const void* state, Canvas* const canvas),
|
||||
uint32_t duration) {
|
||||
QueueItem* next;
|
||||
if(queue_state->current == NULL) {
|
||||
queue_state->start = furi_get_tick();
|
||||
queue_state->current = malloc(sizeof(QueueItem));
|
||||
next = queue_state->current;
|
||||
if(next->start != NULL) next->start(app_state);
|
||||
|
||||
} else {
|
||||
next = queue_state->current;
|
||||
while(next->next != NULL) {
|
||||
next = (QueueItem*)(next->next);
|
||||
}
|
||||
next->next = malloc(sizeof(QueueItem));
|
||||
next = next->next;
|
||||
}
|
||||
next->callback = done;
|
||||
next->render = render;
|
||||
next->start = start;
|
||||
next->duration = duration;
|
||||
next->next = NULL;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <furi.h>
|
||||
|
||||
typedef struct {
|
||||
void (*callback)(void* state); //Callback for when the item is dequeued
|
||||
void (*render)(
|
||||
const void* state,
|
||||
Canvas* const canvas); //Callback for the rendering loop while this item is running
|
||||
void (*start)(void* state); //Callback when this item is started running
|
||||
void* next; //Pointer to the next item
|
||||
uint32_t duration; //duration of the item
|
||||
} QueueItem;
|
||||
|
||||
typedef struct {
|
||||
unsigned int start; //current queue item start time
|
||||
QueueItem* current; //current queue item
|
||||
bool running; //is the queue running
|
||||
} QueueState;
|
||||
|
||||
/**
|
||||
* Enqueue a new item.
|
||||
*
|
||||
* @param queue_state The queue state pointer
|
||||
* @param app_state Your app state
|
||||
* @param done Callback for dequeue event
|
||||
* @param start Callback for when the item is activated
|
||||
* @param render Callback to render loop if needed
|
||||
* @param duration Length of the item
|
||||
*/
|
||||
void enqueue(
|
||||
QueueState* queue_state,
|
||||
void* app_state,
|
||||
void (*done)(void* state),
|
||||
void (*start)(void* state),
|
||||
void (*render)(const void* state, Canvas* const canvas),
|
||||
uint32_t duration);
|
||||
/**
|
||||
* Clears all queue items
|
||||
*
|
||||
* @param queue_state The queue state pointer
|
||||
*/
|
||||
void queue_clear(QueueState* queue_state);
|
||||
|
||||
/**
|
||||
* Dequeues the active queue item. Usually you don't need to call it directly.
|
||||
*
|
||||
* @param queue_state The queue state pointer
|
||||
* @param app_state Your application state
|
||||
*/
|
||||
void dequeue(QueueState* queue_state, void* app_state);
|
||||
|
||||
/**
|
||||
* Runs the queue logic (place it in your tick function)
|
||||
*
|
||||
* @param queue_state The queue state pointer
|
||||
* @param app_state Your application state
|
||||
* @return FALSE when there is nothing to run, TRUE otherwise
|
||||
*/
|
||||
bool run_queue(QueueState* queue_state, void* app_state);
|
||||
|
||||
/**
|
||||
* Calls the currently active queue items render callback (if there is any)
|
||||
*
|
||||
* @param queue_state The queue state pointer
|
||||
* @param app_state Your application state
|
||||
* @param canvas Pointer to Flipper's canvas object
|
||||
*/
|
||||
void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas);
|
||||
@@ -0,0 +1,257 @@
|
||||
#include "ui.h"
|
||||
#include <gui/canvas_i.h>
|
||||
#include <u8g2_glue.h>
|
||||
#include <gui/icon_animation_i.h>
|
||||
#include <gui/icon.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
TileMap* tileMap;
|
||||
uint8_t tileMapCount = 0;
|
||||
|
||||
void ui_cleanup() {
|
||||
if(tileMap != NULL) {
|
||||
for(uint8_t i = 0; i < tileMapCount; i++) {
|
||||
if(tileMap[i].data != NULL) free(tileMap[i].data);
|
||||
}
|
||||
free(tileMap);
|
||||
}
|
||||
}
|
||||
|
||||
void add_new_tilemap(uint8_t* data, unsigned long iconId) {
|
||||
TileMap* old = tileMap;
|
||||
tileMapCount++;
|
||||
tileMap = malloc(sizeof(TileMap) * tileMapCount);
|
||||
if(tileMapCount > 1) {
|
||||
for(uint8_t i = 0; i < tileMapCount; i++) tileMap[i] = old[i];
|
||||
}
|
||||
tileMap[tileMapCount - 1] = (TileMap){data, iconId};
|
||||
}
|
||||
|
||||
uint8_t* get_tilemap(unsigned long icon_id) {
|
||||
for(uint8_t i = 0; i < tileMapCount; i++) {
|
||||
if(tileMap[i].iconId == icon_id) return tileMap[i].data;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t pixel_index(uint8_t x, uint8_t y) {
|
||||
return y * SCREEN_WIDTH + x;
|
||||
}
|
||||
|
||||
bool in_screen(int16_t x, int16_t y) {
|
||||
return x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT;
|
||||
}
|
||||
|
||||
unsigned flipBit(uint8_t x, uint8_t bit) {
|
||||
return x ^ (1 << bit);
|
||||
}
|
||||
|
||||
unsigned setBit(uint8_t x, uint8_t bit) {
|
||||
return x | (1 << bit);
|
||||
}
|
||||
|
||||
unsigned unsetBit(uint8_t x, uint8_t bit) {
|
||||
return x & ~(1 << bit);
|
||||
}
|
||||
|
||||
bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w) {
|
||||
uint8_t current_bit = (y % 8);
|
||||
uint8_t current_row = ((y - current_bit) / 8);
|
||||
uint8_t current_value = data[current_row * w + x];
|
||||
return current_value & (1 << current_bit);
|
||||
}
|
||||
|
||||
uint8_t* get_buffer(Canvas* const canvas) {
|
||||
return canvas->fb.tile_buf_ptr;
|
||||
// return canvas_get_buffer(canvas);
|
||||
}
|
||||
uint8_t* make_buffer() {
|
||||
return malloc(sizeof(uint8_t) * 8 * 128);
|
||||
}
|
||||
void clone_buffer(uint8_t* canvas, uint8_t* data) {
|
||||
for(int i = 0; i < 1024; i++) {
|
||||
data[i] = canvas[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool read_pixel(Canvas* const canvas, int16_t x, int16_t y) {
|
||||
if(in_screen(x, y)) {
|
||||
return test_pixel(get_buffer(canvas), x, y, SCREEN_WIDTH);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode) {
|
||||
if(in_screen(x, y)) {
|
||||
uint8_t current_bit = (y % 8);
|
||||
uint8_t current_row = ((y - current_bit) / 8);
|
||||
uint32_t i = pixel_index(x, current_row);
|
||||
uint8_t* buffer = get_buffer(canvas);
|
||||
|
||||
uint8_t current_value = buffer[i];
|
||||
if(draw_mode == Inverse) {
|
||||
buffer[i] = flipBit(current_value, current_bit);
|
||||
} else {
|
||||
if(draw_mode == White) {
|
||||
buffer[i] = unsetBit(current_value, current_bit);
|
||||
} else {
|
||||
buffer[i] = setBit(current_value, current_bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_line(
|
||||
Canvas* const canvas,
|
||||
int16_t x1,
|
||||
int16_t y1,
|
||||
int16_t x2,
|
||||
int16_t y2,
|
||||
DrawMode draw_mode) {
|
||||
for(int16_t x = x2; x >= x1; x--) {
|
||||
for(int16_t y = y2; y >= y1; y--) {
|
||||
set_pixel(canvas, x, y, draw_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_rounded_box_frame(
|
||||
Canvas* const canvas,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode draw_mode) {
|
||||
int16_t xMinCorner = x + 1;
|
||||
int16_t xMax = x + w - 1;
|
||||
int16_t xMaxCorner = x + w - 2;
|
||||
int16_t yMinCorner = y + 1;
|
||||
int16_t yMax = y + h - 1;
|
||||
int16_t yMaxCorner = y + h - 2;
|
||||
draw_line(canvas, xMinCorner, y, xMaxCorner, y, draw_mode);
|
||||
draw_line(canvas, xMinCorner, yMax, xMaxCorner, yMax, draw_mode);
|
||||
draw_line(canvas, x, yMinCorner, x, yMaxCorner, draw_mode);
|
||||
draw_line(canvas, xMax, yMinCorner, xMax, yMaxCorner, draw_mode);
|
||||
}
|
||||
|
||||
void draw_rounded_box(
|
||||
Canvas* const canvas,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode draw_mode) {
|
||||
for(int16_t o = w - 2; o >= 1; o--) {
|
||||
for(int16_t p = h - 2; p >= 1; p--) {
|
||||
set_pixel(canvas, x + o, y + p, draw_mode);
|
||||
}
|
||||
}
|
||||
draw_rounded_box_frame(canvas, x, y, w, h, draw_mode);
|
||||
}
|
||||
|
||||
void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h) {
|
||||
draw_pixels(canvas, data, x, y, w, h, Inverse);
|
||||
}
|
||||
|
||||
void draw_pixels(
|
||||
Canvas* const canvas,
|
||||
uint8_t* data,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode) {
|
||||
for(int8_t o = 0; o < w; o++) {
|
||||
for(int8_t p = 0; p < h; p++) {
|
||||
if(in_screen(o + x, p + y) && data[p * w + o] == 1)
|
||||
set_pixel(canvas, o + x, p + y, drawMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_rectangle(
|
||||
Canvas* const canvas,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode) {
|
||||
for(int8_t o = 0; o < w; o++) {
|
||||
for(int8_t p = 0; p < h; p++) {
|
||||
if(in_screen(o + x, p + y)) {
|
||||
set_pixel(canvas, o + x, p + y, drawMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h) {
|
||||
draw_rectangle(canvas, x, y, w, h, Inverse);
|
||||
}
|
||||
|
||||
uint8_t* image_data(Canvas* const canvas, const Icon* icon) {
|
||||
uint8_t* data = malloc(sizeof(uint8_t) * 8 * 128);
|
||||
uint8_t* screen = canvas->fb.tile_buf_ptr;
|
||||
canvas->fb.tile_buf_ptr = data;
|
||||
canvas_draw_icon(canvas, 0, 0, icon);
|
||||
canvas->fb.tile_buf_ptr = screen;
|
||||
return data;
|
||||
}
|
||||
|
||||
uint8_t* getOrAddIconData(Canvas* const canvas, const Icon* icon) {
|
||||
uint8_t* icon_data = get_tilemap((unsigned long)icon);
|
||||
if(icon_data == NULL) {
|
||||
icon_data = image_data(canvas, icon);
|
||||
add_new_tilemap(icon_data, (unsigned long)icon);
|
||||
}
|
||||
return icon_data;
|
||||
}
|
||||
|
||||
void draw_icon_clip(
|
||||
Canvas* const canvas,
|
||||
const Icon* icon,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t left,
|
||||
uint8_t top,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode) {
|
||||
uint8_t* icon_data = getOrAddIconData(canvas, icon);
|
||||
|
||||
for(int i = 0; i < w; i++) {
|
||||
for(int j = 0; j < h; j++) {
|
||||
bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH);
|
||||
if(drawMode == Filled) {
|
||||
set_pixel(canvas, x + i, y + j, on ? Black : White);
|
||||
} else if(on)
|
||||
set_pixel(canvas, x + i, y + j, drawMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_icon_clip_flipped(
|
||||
Canvas* const canvas,
|
||||
const Icon* icon,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t left,
|
||||
uint8_t top,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode) {
|
||||
uint8_t* icon_data = getOrAddIconData(canvas, icon);
|
||||
|
||||
for(int i = 0; i < w; i++) {
|
||||
for(int j = 0; j < h; j++) {
|
||||
bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH);
|
||||
|
||||
if(drawMode == Filled) {
|
||||
set_pixel(canvas, x + w - i - 1, y + h - j - 1, on ? Black : White);
|
||||
} else if(on)
|
||||
set_pixel(canvas, x + w - i - 1, y + h - j - 1, drawMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/canvas.h>
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
|
||||
typedef enum {
|
||||
Black,
|
||||
White,
|
||||
Inverse,
|
||||
Filled //Currently only for Icon clip drawing
|
||||
} DrawMode;
|
||||
|
||||
// size is the screen size
|
||||
|
||||
typedef struct {
|
||||
uint8_t* data;
|
||||
unsigned long iconId;
|
||||
} TileMap;
|
||||
|
||||
bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w);
|
||||
|
||||
uint8_t* image_data(Canvas* const canvas, const Icon* icon);
|
||||
|
||||
uint32_t pixel_index(uint8_t x, uint8_t y);
|
||||
|
||||
void draw_icon_clip(
|
||||
Canvas* const canvas,
|
||||
const Icon* icon,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t left,
|
||||
uint8_t top,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode);
|
||||
|
||||
void draw_icon_clip_flipped(
|
||||
Canvas* const canvas,
|
||||
const Icon* icon,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t left,
|
||||
uint8_t top,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode);
|
||||
|
||||
void draw_rounded_box(
|
||||
Canvas* const canvas,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode);
|
||||
|
||||
void draw_rounded_box_frame(
|
||||
Canvas* const canvas,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode);
|
||||
|
||||
void draw_rectangle(
|
||||
Canvas* const canvas,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode);
|
||||
|
||||
void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h);
|
||||
|
||||
void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h);
|
||||
|
||||
void draw_pixels(
|
||||
Canvas* const canvas,
|
||||
uint8_t* data,
|
||||
int16_t x,
|
||||
int16_t y,
|
||||
uint8_t w,
|
||||
uint8_t h,
|
||||
DrawMode drawMode);
|
||||
|
||||
bool read_pixel(Canvas* const canvas, int16_t x, int16_t y);
|
||||
|
||||
void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode);
|
||||
|
||||
void draw_line(
|
||||
Canvas* const canvas,
|
||||
int16_t x1,
|
||||
int16_t y1,
|
||||
int16_t x2,
|
||||
int16_t y2,
|
||||
DrawMode draw_mode);
|
||||
|
||||
bool in_screen(int16_t x, int16_t y);
|
||||
|
||||
void ui_cleanup();
|
||||
uint8_t* get_buffer(Canvas* const canvas);
|
||||
uint8_t* make_buffer();
|
||||
void clone_buffer(uint8_t* canvas, uint8_t* data);
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/elements.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include "common/card.h"
|
||||
#include "common/queue.h"
|
||||
#include "common/menu.h"
|
||||
|
||||
#define APP_NAME "Blackjack"
|
||||
|
||||
#define CONF_ANIMATION_DURATION "AnimationDuration"
|
||||
#define CONF_MESSAGE_DURATION "MessageDuration"
|
||||
#define CONF_STARTING_MONEY "StartingMoney"
|
||||
#define CONF_ROUND_PRICE "RoundPrice"
|
||||
#define CONF_SOUND_EFFECTS "SoundEffects"
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
uint32_t animation_duration;
|
||||
uint32_t message_duration;
|
||||
uint32_t starting_money;
|
||||
uint32_t round_price;
|
||||
bool sound_effects;
|
||||
} Settings;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} AppEvent;
|
||||
|
||||
typedef enum {
|
||||
GameStateGameOver,
|
||||
GameStateStart,
|
||||
GameStatePlay,
|
||||
GameStateSettings,
|
||||
GameStateDealer,
|
||||
} PlayState;
|
||||
|
||||
typedef enum {
|
||||
DirectionUp,
|
||||
DirectionDown,
|
||||
DirectionRight,
|
||||
DirectionLeft,
|
||||
Select,
|
||||
Back,
|
||||
None
|
||||
} Direction;
|
||||
|
||||
typedef struct {
|
||||
Card player_cards[21];
|
||||
Card dealer_cards[21];
|
||||
uint8_t player_card_count;
|
||||
uint8_t dealer_card_count;
|
||||
|
||||
Direction selectDirection;
|
||||
Settings settings;
|
||||
|
||||
uint32_t player_score;
|
||||
uint32_t bet;
|
||||
uint8_t selectedMenu;
|
||||
bool doubled;
|
||||
bool started;
|
||||
bool processing;
|
||||
Deck deck;
|
||||
PlayState state;
|
||||
QueueState queue_state;
|
||||
Menu* menu;
|
||||
unsigned int last_tick;
|
||||
} GameState;
|
||||
@@ -0,0 +1,186 @@
|
||||
#include <math.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include "ui.h"
|
||||
|
||||
#define LINE_HEIGHT 16
|
||||
#define ITEM_PADDING 4
|
||||
|
||||
const char MoneyMul[4] = {'K', 'B', 'T', 'S'};
|
||||
|
||||
void draw_player_scene(Canvas* const canvas, const GameState* game_state) {
|
||||
int max_card = game_state->player_card_count;
|
||||
|
||||
if(max_card > 0) draw_deck((game_state->player_cards), max_card, canvas);
|
||||
|
||||
if(game_state->dealer_card_count > 0) draw_card_back_at(13, 5, canvas);
|
||||
|
||||
max_card = game_state->dealer_card_count;
|
||||
if(max_card > 1) {
|
||||
draw_card_at(
|
||||
2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_dealer_scene(Canvas* const canvas, const GameState* game_state) {
|
||||
uint8_t max_card = game_state->dealer_card_count;
|
||||
draw_deck((game_state->dealer_cards), max_card, canvas);
|
||||
}
|
||||
|
||||
void popup_frame(Canvas* const canvas) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 32, 15, 66, 13);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(canvas, 32, 15, 66, 13);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
}
|
||||
|
||||
void draw_play_menu(Canvas* const canvas, const GameState* game_state) {
|
||||
const char* menus[3] = {"Double", "Hit", "Stay"};
|
||||
for(uint8_t m = 0; m < 3; m++) {
|
||||
if(m == 0 &&
|
||||
(game_state->doubled || game_state->player_score < game_state->settings.round_price))
|
||||
continue;
|
||||
int y = m * 13 + 25;
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
if(game_state->selectedMenu == m) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 1, y, 31, 12);
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 1, y, 31, 12);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_frame(canvas, 1, y, 31, 12);
|
||||
}
|
||||
|
||||
if(game_state->selectedMenu == m)
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
else
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, 16, y + 6, AlignCenter, AlignCenter, menus[m]);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_screen(Canvas* const canvas, const bool* points) {
|
||||
for(uint8_t x = 0; x < 128; x++) {
|
||||
for(uint8_t y = 0; y < 64; y++) {
|
||||
if(points[y * 128 + x]) canvas_draw_dot(canvas, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_score(Canvas* const canvas, bool top, uint8_t amount) {
|
||||
char drawChar[20];
|
||||
snprintf(drawChar, sizeof(drawChar), "Player score: %i", amount);
|
||||
if(top)
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, drawChar);
|
||||
else
|
||||
canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, drawChar);
|
||||
}
|
||||
|
||||
void draw_money(Canvas* const canvas, uint32_t score) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char drawChar[11];
|
||||
uint32_t currAmount = score;
|
||||
if(currAmount < 1000) {
|
||||
snprintf(drawChar, sizeof(drawChar), "$%lu", currAmount);
|
||||
} else {
|
||||
char c = 'K';
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
currAmount = currAmount / 1000;
|
||||
if(currAmount < 1000) {
|
||||
c = MoneyMul[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(drawChar, sizeof(drawChar), "$%lu %c", currAmount, c);
|
||||
}
|
||||
canvas_draw_str_aligned(canvas, 126, 2, AlignRight, AlignTop, drawChar);
|
||||
}
|
||||
|
||||
void draw_menu(
|
||||
Canvas* const canvas,
|
||||
const char* text,
|
||||
const char* value,
|
||||
int8_t y,
|
||||
bool left_caret,
|
||||
bool right_caret,
|
||||
bool selected) {
|
||||
UNUSED(selected);
|
||||
if(y < 0 || y >= 64) return;
|
||||
|
||||
if(selected) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 0, y, 122, LINE_HEIGHT);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 4, y + ITEM_PADDING, AlignLeft, AlignTop, text);
|
||||
if(left_caret) canvas_draw_str_aligned(canvas, 80, y + ITEM_PADDING, AlignLeft, AlignTop, "<");
|
||||
canvas_draw_str_aligned(canvas, 100, y + ITEM_PADDING, AlignCenter, AlignTop, value);
|
||||
if(right_caret)
|
||||
canvas_draw_str_aligned(canvas, 120, y + ITEM_PADDING, AlignRight, AlignTop, ">");
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
void settings_page(Canvas* const canvas, const GameState* gameState) {
|
||||
char drawChar[10];
|
||||
int startY = 0;
|
||||
if(LINE_HEIGHT * (gameState->selectedMenu + 1) >= 64) {
|
||||
startY -= (LINE_HEIGHT * (gameState->selectedMenu + 1)) - 64;
|
||||
}
|
||||
|
||||
int scrollHeight = round(64 / 6.0) + ITEM_PADDING * 2;
|
||||
int scrollPos = 64 / (6.0 / (gameState->selectedMenu + 1)) - ITEM_PADDING * 2;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 123, scrollPos, 4, scrollHeight);
|
||||
canvas_draw_box(canvas, 125, 0, 1, 64);
|
||||
|
||||
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.starting_money);
|
||||
draw_menu(
|
||||
canvas,
|
||||
"Start money",
|
||||
drawChar,
|
||||
0 * LINE_HEIGHT + startY,
|
||||
gameState->settings.starting_money > gameState->settings.round_price,
|
||||
gameState->settings.starting_money < 400,
|
||||
gameState->selectedMenu == 0);
|
||||
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.round_price);
|
||||
draw_menu(
|
||||
canvas,
|
||||
"Round price",
|
||||
drawChar,
|
||||
1 * LINE_HEIGHT + startY,
|
||||
gameState->settings.round_price > 10,
|
||||
gameState->settings.round_price < gameState->settings.starting_money,
|
||||
gameState->selectedMenu == 1);
|
||||
|
||||
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.animation_duration);
|
||||
draw_menu(
|
||||
canvas,
|
||||
"Anim. length",
|
||||
drawChar,
|
||||
2 * LINE_HEIGHT + startY,
|
||||
gameState->settings.animation_duration > 0,
|
||||
gameState->settings.animation_duration < 2000,
|
||||
gameState->selectedMenu == 2);
|
||||
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.message_duration);
|
||||
draw_menu(
|
||||
canvas,
|
||||
"Popup time",
|
||||
drawChar,
|
||||
3 * LINE_HEIGHT + startY,
|
||||
gameState->settings.message_duration > 0,
|
||||
gameState->settings.message_duration < 2000,
|
||||
gameState->selectedMenu == 3);
|
||||
// draw_menu(canvas, "Sound", gameState->settings.sound_effects ? "Yes" : "No",
|
||||
// 5 * LINE_HEIGHT + startY,
|
||||
// true,
|
||||
// true,
|
||||
// gameState->selectedMenu == 5
|
||||
// );
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "defines.h"
|
||||
#include <gui/gui.h>
|
||||
|
||||
void draw_player_scene(Canvas* const canvas, const GameState* game_state);
|
||||
|
||||
void draw_dealer_scene(Canvas* const canvas, const GameState* game_state);
|
||||
|
||||
void draw_play_menu(Canvas* const canvas, const GameState* game_state);
|
||||
|
||||
void draw_score(Canvas* const canvas, bool top, uint8_t amount);
|
||||
|
||||
void draw_money(Canvas* const canvas, uint32_t score);
|
||||
void settings_page(Canvas* const canvas, const GameState* gameState);
|
||||
|
||||
void popup_frame(Canvas* const canvas);
|
||||
void draw_screen(Canvas* const canvas, const bool* points);
|
||||
@@ -0,0 +1,123 @@
|
||||
#include <storage/storage.h>
|
||||
#include "util.h"
|
||||
|
||||
const char* CONFIG_FILE_PATH = EXT_PATH(".blackjack.settings");
|
||||
|
||||
void save_settings(Settings settings) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FURI_LOG_D(APP_NAME, "Saving config");
|
||||
if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
|
||||
FURI_LOG_D(
|
||||
APP_NAME, "Saving %s: %ld", CONF_ANIMATION_DURATION, settings.animation_duration);
|
||||
flipper_format_update_uint32(
|
||||
file, CONF_ANIMATION_DURATION, &(settings.animation_duration), 1);
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_MESSAGE_DURATION, settings.message_duration);
|
||||
flipper_format_update_uint32(file, CONF_MESSAGE_DURATION, &(settings.message_duration), 1);
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_STARTING_MONEY, settings.starting_money);
|
||||
flipper_format_update_uint32(file, CONF_STARTING_MONEY, &(settings.starting_money), 1);
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_ROUND_PRICE, settings.round_price);
|
||||
flipper_format_update_uint32(file, CONF_ROUND_PRICE, &(settings.round_price), 1);
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Saving %s: %i", CONF_SOUND_EFFECTS, settings.sound_effects ? 1 : 0);
|
||||
flipper_format_update_bool(file, CONF_SOUND_EFFECTS, &(settings.sound_effects), 1);
|
||||
FURI_LOG_D(APP_NAME, "Config saved");
|
||||
} else {
|
||||
FURI_LOG_E(APP_NAME, "Save error");
|
||||
}
|
||||
flipper_format_file_close(file);
|
||||
flipper_format_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void save_settings_file(FlipperFormat* file, Settings* settings) {
|
||||
flipper_format_write_header_cstr(file, CONFIG_FILE_HEADER, CONFIG_FILE_VERSION);
|
||||
flipper_format_write_comment_cstr(file, "Card animation duration in ms");
|
||||
flipper_format_write_uint32(file, CONF_ANIMATION_DURATION, &(settings->animation_duration), 1);
|
||||
flipper_format_write_comment_cstr(file, "Popup message duration in ms");
|
||||
flipper_format_write_uint32(file, CONF_MESSAGE_DURATION, &(settings->message_duration), 1);
|
||||
flipper_format_write_comment_cstr(file, "Player's starting money");
|
||||
flipper_format_write_uint32(file, CONF_STARTING_MONEY, &(settings->starting_money), 1);
|
||||
flipper_format_write_comment_cstr(file, "Round price");
|
||||
flipper_format_write_uint32(file, CONF_ROUND_PRICE, &(settings->round_price), 1);
|
||||
flipper_format_write_comment_cstr(file, "Enable sound effects");
|
||||
flipper_format_write_bool(file, CONF_SOUND_EFFECTS, &(settings->sound_effects), 1);
|
||||
}
|
||||
|
||||
Settings load_settings() {
|
||||
Settings settings;
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Loading default settings");
|
||||
settings.animation_duration = 800;
|
||||
settings.message_duration = 1500;
|
||||
settings.starting_money = 200;
|
||||
settings.round_price = 10;
|
||||
settings.sound_effects = true;
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Opening storage");
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FURI_LOG_D(APP_NAME, "Allocating file");
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
|
||||
FURI_LOG_D(APP_NAME, "Allocating string");
|
||||
FuriString* string_value;
|
||||
string_value = furi_string_alloc();
|
||||
|
||||
if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) {
|
||||
FURI_LOG_D(APP_NAME, "Config file %s not found, creating new one...", CONFIG_FILE_PATH);
|
||||
if(!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
|
||||
FURI_LOG_E(APP_NAME, "Error creating new file %s", CONFIG_FILE_PATH);
|
||||
flipper_format_file_close(file);
|
||||
} else {
|
||||
save_settings_file(file, &settings);
|
||||
}
|
||||
} else {
|
||||
if(!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
|
||||
FURI_LOG_E(APP_NAME, "Error opening existing file %s", CONFIG_FILE_PATH);
|
||||
flipper_format_file_close(file);
|
||||
} else {
|
||||
uint32_t value;
|
||||
bool valueBool;
|
||||
FURI_LOG_D(APP_NAME, "Checking version");
|
||||
if(!flipper_format_read_header(file, string_value, &value)) {
|
||||
FURI_LOG_E(APP_NAME, "Config file mismatch");
|
||||
} else {
|
||||
FURI_LOG_D(APP_NAME, "Loading %s", CONF_ANIMATION_DURATION);
|
||||
if(flipper_format_read_uint32(file, CONF_ANIMATION_DURATION, &value, 1)) {
|
||||
settings.animation_duration = value;
|
||||
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ANIMATION_DURATION, value);
|
||||
}
|
||||
FURI_LOG_D(APP_NAME, "Loading %s", CONF_MESSAGE_DURATION);
|
||||
if(flipper_format_read_uint32(file, CONF_MESSAGE_DURATION, &value, 1)) {
|
||||
settings.message_duration = value;
|
||||
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_MESSAGE_DURATION, value);
|
||||
}
|
||||
FURI_LOG_D(APP_NAME, "Loading %s", CONF_STARTING_MONEY);
|
||||
if(flipper_format_read_uint32(file, CONF_STARTING_MONEY, &value, 1)) {
|
||||
settings.starting_money = value;
|
||||
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_STARTING_MONEY, value);
|
||||
}
|
||||
FURI_LOG_D(APP_NAME, "Loading %s", CONF_ROUND_PRICE);
|
||||
if(flipper_format_read_uint32(file, CONF_ROUND_PRICE, &value, 1)) {
|
||||
settings.round_price = value;
|
||||
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ROUND_PRICE, value);
|
||||
}
|
||||
FURI_LOG_D(APP_NAME, "Loading %s", CONF_SOUND_EFFECTS);
|
||||
if(flipper_format_read_bool(file, CONF_SOUND_EFFECTS, &valueBool, 1)) {
|
||||
settings.sound_effects = valueBool;
|
||||
FURI_LOG_D(APP_NAME, "Loaded %s: %i", CONF_ROUND_PRICE, valueBool ? 1 : 0);
|
||||
}
|
||||
}
|
||||
flipper_format_file_close(file);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(string_value);
|
||||
// flipper_format_file_close(file);
|
||||
flipper_format_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return settings;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "defines.h"
|
||||
#define CONFIG_FILE_HEADER "Blackjack config file"
|
||||
#define CONFIG_FILE_VERSION 1
|
||||
|
||||
void save_settings(Settings settings);
|
||||
Settings load_settings();
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Caesar Cipher
|
||||
|
||||
A [caesar cipher](https://en.wikipedia.org/wiki/Caesar_cipher) encoder for the Flipper Zero device.
|
||||
|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Start app, painfully input your ciphertext with the onscreen keyboard. Replace spaces with underscores. Hit "Save", scroll output.
|
||||
|
||||
## Compiling
|
||||
|
||||
```
|
||||
./fbt firmware_caesar_cipher
|
||||
```
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
App(
|
||||
appid="Caesar_Cipher",
|
||||
name="Caesar Cipher",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="caesar_cipher_app",
|
||||
cdefines=["APP_CAESAR_CIPHER"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
fap_icon="caesar_cipher_icon.png",
|
||||
fap_category="Misc",
|
||||
order=20,
|
||||
)
|
||||
@@ -0,0 +1,147 @@
|
||||
#include <furi.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
|
||||
#define TEXT_BUFFER_SIZE 256
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
ViewDispatcher* view_dispatcher;
|
||||
TextInput* text_input;
|
||||
TextBox* text_box;
|
||||
char input[TEXT_BUFFER_SIZE];
|
||||
char output[(TEXT_BUFFER_SIZE * 26) + (26)]; // linebreaks
|
||||
} CaesarState;
|
||||
|
||||
static void string_to_uppercase(char* input) {
|
||||
int i;
|
||||
for(i = 0; input[i] != '\0'; i++) {
|
||||
if(input[i] >= 'a' && input[i] <= 'z') {
|
||||
input[i] = input[i] - 32;
|
||||
} else {
|
||||
input[i] = input[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void build_output(char* input, char* output) {
|
||||
int out = 0;
|
||||
for(int rot = 1; rot < 26; rot++) {
|
||||
int in;
|
||||
for(in = 0; input[in] != '\0'; in++) {
|
||||
if(input[in] >= 'A' && input[in] <= 'Z') {
|
||||
output[out] = 65 + (((input[in] - 65) + rot) % 26);
|
||||
} else {
|
||||
output[out] = input[in];
|
||||
}
|
||||
out++;
|
||||
}
|
||||
output[out] = '\n';
|
||||
out++;
|
||||
}
|
||||
output[out] = '\0';
|
||||
}
|
||||
|
||||
static void text_input_callback(void* ctx) {
|
||||
CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
FURI_LOG_D("caesar_cipher", "Input text: %s", caesar_state->input);
|
||||
// this is where we build the output.
|
||||
string_to_uppercase(caesar_state->input);
|
||||
FURI_LOG_D("caesar_cipher", "Upper text: %s", caesar_state->input);
|
||||
build_output(caesar_state->input, caesar_state->output);
|
||||
text_box_set_text(caesar_state->text_box, caesar_state->output);
|
||||
view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 1);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, caesar_state);
|
||||
}
|
||||
|
||||
static bool back_event_callback(void* ctx) {
|
||||
const CaesarState* caesar_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
view_dispatcher_stop(caesar_state->view_dispatcher);
|
||||
release_mutex((ValueMutex*)ctx, caesar_state);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void caesar_cipher_state_init(CaesarState* const caesar_state) {
|
||||
caesar_state->view_dispatcher = view_dispatcher_alloc();
|
||||
caesar_state->text_input = text_input_alloc();
|
||||
caesar_state->text_box = text_box_alloc();
|
||||
text_box_set_font(caesar_state->text_box, TextBoxFontText);
|
||||
}
|
||||
|
||||
static void caesar_cipher_state_free(CaesarState* const caesar_state) {
|
||||
text_input_free(caesar_state->text_input);
|
||||
text_box_free(caesar_state->text_box);
|
||||
view_dispatcher_remove_view(caesar_state->view_dispatcher, 0);
|
||||
view_dispatcher_remove_view(caesar_state->view_dispatcher, 1);
|
||||
view_dispatcher_free(caesar_state->view_dispatcher);
|
||||
free(caesar_state);
|
||||
}
|
||||
|
||||
int32_t caesar_cipher_app() {
|
||||
CaesarState* caesar_state = malloc(sizeof(CaesarState));
|
||||
|
||||
FURI_LOG_D("caesar_cipher", "Running caesar_cipher_state_init");
|
||||
caesar_cipher_state_init(caesar_state);
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, caesar_state, sizeof(CaesarState))) {
|
||||
FURI_LOG_E("caesar_cipher", "cannot create mutex\r\n");
|
||||
free(caesar_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
FURI_LOG_D("caesar_cipher", "Assigning text input callback");
|
||||
text_input_set_result_callback(
|
||||
caesar_state->text_input,
|
||||
text_input_callback,
|
||||
&state_mutex,
|
||||
caesar_state->input,
|
||||
TEXT_BUFFER_SIZE,
|
||||
//clear default text
|
||||
true);
|
||||
text_input_set_header_text(caesar_state->text_input, "Input");
|
||||
|
||||
// Open GUI and register view_port
|
||||
Gui* gui = furi_record_open("gui");
|
||||
//gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
FURI_LOG_D("caesar_cipher", "Enabling view dispatcher queue");
|
||||
view_dispatcher_enable_queue(caesar_state->view_dispatcher);
|
||||
|
||||
FURI_LOG_D("caesar_cipher", "Adding text input view to dispatcher");
|
||||
view_dispatcher_add_view(
|
||||
caesar_state->view_dispatcher, 0, text_input_get_view(caesar_state->text_input));
|
||||
view_dispatcher_add_view(
|
||||
caesar_state->view_dispatcher, 1, text_box_get_view(caesar_state->text_box));
|
||||
FURI_LOG_D("caesar_cipher", "Attaching view dispatcher to GUI");
|
||||
view_dispatcher_attach_to_gui(
|
||||
caesar_state->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
|
||||
FURI_LOG_D("ceasar_cipher", "starting view dispatcher");
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
caesar_state->view_dispatcher, back_event_callback);
|
||||
view_dispatcher_set_event_callback_context(caesar_state->view_dispatcher, &state_mutex);
|
||||
view_dispatcher_switch_to_view(caesar_state->view_dispatcher, 0);
|
||||
view_dispatcher_run(caesar_state->view_dispatcher);
|
||||
|
||||
furi_record_close("gui");
|
||||
delete_mutex(&state_mutex);
|
||||
caesar_cipher_state_free(caesar_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 172 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="Calculator",
|
||||
name="Calculator",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="calculator_app",
|
||||
cdefines=["APP_CALCULATOR"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=45,
|
||||
fap_icon="calcIcon.png",
|
||||
fap_category="Misc",
|
||||
)
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,453 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <stdbool.h> // Header-file for boolean data-type.
|
||||
#include <string.h> // Header-file for string functions.
|
||||
#include "tinyexpr.h" // Header-file for the TinyExpr library.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
const short MAX_TEXT_LENGTH = 20;
|
||||
|
||||
typedef struct {
|
||||
short x;
|
||||
short y;
|
||||
} selectedPosition;
|
||||
|
||||
typedef struct {
|
||||
selectedPosition position;
|
||||
//string with the inputted calculator text
|
||||
char text[20];
|
||||
short textLength;
|
||||
char log[20];
|
||||
} Calculator;
|
||||
|
||||
char getKeyAtPosition(short x, short y) {
|
||||
if(x == 0 && y == 0) {
|
||||
return 'C';
|
||||
}
|
||||
if(x == 1 && y == 0) {
|
||||
return '<';
|
||||
}
|
||||
if(x == 2 && y == 0) {
|
||||
return '%';
|
||||
}
|
||||
if(x == 3 && y == 0) {
|
||||
return '/';
|
||||
}
|
||||
if(x == 0 && y == 1) {
|
||||
return '1';
|
||||
}
|
||||
if(x == 1 && y == 1) {
|
||||
return '2';
|
||||
}
|
||||
if(x == 2 && y == 1) {
|
||||
return '3';
|
||||
}
|
||||
if(x == 3 && y == 1) {
|
||||
return '*';
|
||||
}
|
||||
if(x == 0 && y == 2) {
|
||||
return '4';
|
||||
}
|
||||
if(x == 1 && y == 2) {
|
||||
return '5';
|
||||
}
|
||||
if(x == 2 && y == 2) {
|
||||
return '6';
|
||||
}
|
||||
if(x == 3 && y == 2) {
|
||||
return '-';
|
||||
}
|
||||
if(x == 0 && y == 3) {
|
||||
return '7';
|
||||
}
|
||||
if(x == 1 && y == 3) {
|
||||
return '8';
|
||||
}
|
||||
if(x == 2 && y == 3) {
|
||||
return '9';
|
||||
}
|
||||
if(x == 3 && y == 3) {
|
||||
return '+';
|
||||
}
|
||||
if(x == 0 && y == 4) {
|
||||
return '(';
|
||||
}
|
||||
if(x == 1 && y == 4) {
|
||||
return '0';
|
||||
}
|
||||
if(x == 2 && y == 4) {
|
||||
return '.';
|
||||
}
|
||||
if(x == 3 && y == 4) {
|
||||
return '=';
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
|
||||
short calculateStringWidth(const char* str, short lenght) {
|
||||
/* widths:
|
||||
1 = 2
|
||||
2, 3, 4, 5, 6, 7, 8, 9, 0, X, -, +, . = = 5
|
||||
%, / = 7
|
||||
S = 5
|
||||
(, ) = 3
|
||||
|
||||
*/
|
||||
short width = 0;
|
||||
for(short i = 0; i < lenght; i++) {
|
||||
switch(str[i]) {
|
||||
case '1':
|
||||
width += 2;
|
||||
break;
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '0':
|
||||
case '*':
|
||||
case '-':
|
||||
case '+':
|
||||
case '.':
|
||||
width += 5;
|
||||
break;
|
||||
case '%':
|
||||
case '/':
|
||||
width += 7;
|
||||
break;
|
||||
case 'S':
|
||||
width += 5;
|
||||
break;
|
||||
case '(':
|
||||
case ')':
|
||||
width += 3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
width += 1;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
void generate_calculator_layout(Canvas* canvas) {
|
||||
//draw dotted lines
|
||||
for(int i = 0; i <= 64; i++) {
|
||||
if(i % 2 == 0) {
|
||||
canvas_draw_dot(canvas, i, 14);
|
||||
canvas_draw_dot(canvas, i, 33);
|
||||
}
|
||||
if(i % 2 == 1) {
|
||||
canvas_draw_dot(canvas, i, 15);
|
||||
canvas_draw_dot(canvas, i, 34);
|
||||
}
|
||||
}
|
||||
|
||||
//draw horizontal lines
|
||||
canvas_draw_box(canvas, 0, 41, 64, 2);
|
||||
canvas_draw_box(canvas, 0, 57, 64, 2);
|
||||
canvas_draw_box(canvas, 0, 73, 64, 2);
|
||||
canvas_draw_box(canvas, 0, 89, 64, 2);
|
||||
canvas_draw_box(canvas, 0, 105, 64, 2);
|
||||
canvas_draw_box(canvas, 0, 121, 64, 2);
|
||||
|
||||
//draw vertical lines
|
||||
canvas_draw_box(canvas, 0, 43, 1, 80);
|
||||
canvas_draw_box(canvas, 15, 43, 2, 80);
|
||||
canvas_draw_box(canvas, 31, 43, 2, 80);
|
||||
canvas_draw_box(canvas, 47, 43, 2, 80);
|
||||
canvas_draw_box(canvas, 63, 43, 1, 80);
|
||||
|
||||
//draw buttons
|
||||
//row 1 (C, ;, %, ÷)
|
||||
canvas_draw_str(canvas, 5, 54, "C");
|
||||
canvas_draw_str(canvas, 19, 54, " <-");
|
||||
canvas_draw_str(canvas, 35, 54, " %");
|
||||
canvas_draw_str(canvas, 51, 54, " /");
|
||||
|
||||
//row 2 (1, 2, 3, X)
|
||||
canvas_draw_str(canvas, 5, 70, " 1");
|
||||
canvas_draw_str(canvas, 19, 70, " 2");
|
||||
canvas_draw_str(canvas, 35, 70, " 3");
|
||||
canvas_draw_str(canvas, 51, 70, " X");
|
||||
|
||||
//row 3 (4, 5, 6, -)
|
||||
canvas_draw_str(canvas, 5, 86, " 4");
|
||||
canvas_draw_str(canvas, 19, 86, " 5");
|
||||
canvas_draw_str(canvas, 35, 86, " 6");
|
||||
canvas_draw_str(canvas, 51, 86, " -");
|
||||
|
||||
//row 4 (7, 8, 9, +)
|
||||
canvas_draw_str(canvas, 5, 102, " 7");
|
||||
canvas_draw_str(canvas, 19, 102, " 8");
|
||||
canvas_draw_str(canvas, 35, 102, " 9");
|
||||
canvas_draw_str(canvas, 51, 102, " +");
|
||||
|
||||
//row 5 (+/-, 0, ., =)
|
||||
canvas_draw_str(canvas, 3, 118, "( )");
|
||||
canvas_draw_str(canvas, 19, 118, " 0");
|
||||
canvas_draw_str(canvas, 35, 118, " .");
|
||||
canvas_draw_str(canvas, 51, 118, " =");
|
||||
};
|
||||
|
||||
void calculator_draw_callback(Canvas* canvas, void* ctx) {
|
||||
const Calculator* calculator_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
UNUSED(ctx);
|
||||
canvas_clear(canvas);
|
||||
|
||||
//show selected button
|
||||
short startX = 1;
|
||||
short startY = 43;
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
startX + (calculator_state->position.x) * 16,
|
||||
(startY) + (calculator_state->position.y) * 16,
|
||||
16,
|
||||
16);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(
|
||||
canvas,
|
||||
startX + (calculator_state->position.x) * 16 + 2,
|
||||
(startY) + (calculator_state->position.y) * 16 + 2,
|
||||
10,
|
||||
10);
|
||||
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
generate_calculator_layout(canvas);
|
||||
|
||||
//draw text
|
||||
short stringWidth = calculateStringWidth(calculator_state->text, calculator_state->textLength);
|
||||
short startingPosition = 5;
|
||||
if(stringWidth > 60) {
|
||||
startingPosition += 60 - (stringWidth + 5);
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str(canvas, startingPosition, 28, calculator_state->text);
|
||||
//canvas_draw_str(canvas, 10, 10, calculator_state->log);
|
||||
|
||||
//draw cursor
|
||||
canvas_draw_box(canvas, stringWidth + 5, 29, 5, 1);
|
||||
|
||||
release_mutex((ValueMutex*)ctx, calculator_state);
|
||||
}
|
||||
|
||||
void calculator_input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
void calculate(Calculator* calculator_state) {
|
||||
double result;
|
||||
result = te_interp(calculator_state->text, 0);
|
||||
|
||||
calculator_state->textLength = 0;
|
||||
calculator_state->text[0] = '\0';
|
||||
// sprintf(calculator_state->text, "%f", result);
|
||||
|
||||
//invert sign if negative
|
||||
if(result < 0) {
|
||||
calculator_state->text[calculator_state->textLength++] = '-';
|
||||
result = -result;
|
||||
}
|
||||
|
||||
//get numbers before and after decimal
|
||||
int beforeDecimal = result;
|
||||
int afterDecimal = (result - beforeDecimal) * 100;
|
||||
|
||||
char beforeDecimalString[10];
|
||||
char afterDecimalString[10];
|
||||
int i = 0;
|
||||
//parse to a string
|
||||
while(beforeDecimal > 0) {
|
||||
beforeDecimalString[i++] = beforeDecimal % 10 + '0';
|
||||
beforeDecimal /= 10;
|
||||
}
|
||||
// invert string
|
||||
for(int j = 0; j < i / 2; j++) {
|
||||
char temp = beforeDecimalString[j];
|
||||
beforeDecimalString[j] = beforeDecimalString[i - j - 1];
|
||||
beforeDecimalString[i - j - 1] = temp;
|
||||
}
|
||||
//add it to the answer
|
||||
for(int j = 0; j < i; j++) {
|
||||
calculator_state->text[calculator_state->textLength++] = beforeDecimalString[j];
|
||||
}
|
||||
|
||||
i = 0;
|
||||
if(afterDecimal > 0) {
|
||||
while(afterDecimal > 0) {
|
||||
afterDecimalString[i++] = afterDecimal % 10 + '0';
|
||||
afterDecimal /= 10;
|
||||
}
|
||||
// invert string
|
||||
for(int j = 0; j < i / 2; j++) {
|
||||
char temp = afterDecimalString[j];
|
||||
afterDecimalString[j] = afterDecimalString[i - j - 1];
|
||||
afterDecimalString[i - j - 1] = temp;
|
||||
}
|
||||
|
||||
//add decimal point
|
||||
calculator_state->text[calculator_state->textLength++] = '.';
|
||||
|
||||
//add numbers after decimal
|
||||
for(int j = 0; j < i; j++) {
|
||||
calculator_state->text[calculator_state->textLength++] = afterDecimalString[j];
|
||||
}
|
||||
}
|
||||
calculator_state->text[calculator_state->textLength] = '\0';
|
||||
}
|
||||
|
||||
int32_t calculator_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
Calculator* calculator_state = malloc(sizeof(Calculator));
|
||||
ValueMutex calculator_state_mutex;
|
||||
if(!init_mutex(&calculator_state_mutex, calculator_state, sizeof(Calculator))) {
|
||||
//FURI_LOG_E("calculator", "cannot create mutex\r\n");
|
||||
free(calculator_state);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Configure view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, calculator_draw_callback, &calculator_state_mutex);
|
||||
view_port_input_callback_set(view_port, calculator_input_callback, event_queue);
|
||||
view_port_set_orientation(view_port, ViewPortOrientationVertical);
|
||||
|
||||
// Register view port in GUI
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
//NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
InputEvent event;
|
||||
|
||||
while(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk) {
|
||||
//break out of the loop if the back key is pressed
|
||||
if(event.type == InputTypeShort && event.key == InputKeyBack) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(event.type == InputTypeShort) {
|
||||
switch(event.key) {
|
||||
case InputKeyUp:
|
||||
if(calculator_state->position.y > 0) {
|
||||
calculator_state->position.y--;
|
||||
}
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(calculator_state->position.y < 4) {
|
||||
calculator_state->position.y++;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(calculator_state->position.x > 0) {
|
||||
calculator_state->position.x--;
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(calculator_state->position.x < 3) {
|
||||
calculator_state->position.x++;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk: {
|
||||
//add the selected button to the text
|
||||
//char* text = calculator_state->text;
|
||||
// short* textLength = &calculator_state->textLength;
|
||||
|
||||
char key =
|
||||
getKeyAtPosition(calculator_state->position.x, calculator_state->position.y);
|
||||
|
||||
switch(key) {
|
||||
case 'C':
|
||||
while(calculator_state->textLength > 0) {
|
||||
calculator_state->text[calculator_state->textLength--] = '\0';
|
||||
}
|
||||
calculator_state->text[0] = '\0';
|
||||
calculator_state->log[2] = key;
|
||||
break;
|
||||
case '<':
|
||||
calculator_state->log[2] = key;
|
||||
if(calculator_state->textLength > 0) {
|
||||
calculator_state->text[--calculator_state->textLength] = '\0';
|
||||
} else {
|
||||
calculator_state->text[0] = '\0';
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
calculator_state->log[2] = key;
|
||||
calculate(calculator_state);
|
||||
break;
|
||||
case '%':
|
||||
case '/':
|
||||
case '*':
|
||||
case '-':
|
||||
case '+':
|
||||
case '.':
|
||||
case '(':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '0':
|
||||
if(calculator_state->textLength < MAX_TEXT_LENGTH) {
|
||||
calculator_state->text[calculator_state->textLength++] = key;
|
||||
calculator_state->text[calculator_state->textLength] = '\0';
|
||||
}
|
||||
//calculator_state->log[1] = calculator_state->text[*textLength];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
}
|
||||
|
||||
if(event.type == InputTypeLong) {
|
||||
switch(event.key) {
|
||||
case InputKeyOk:
|
||||
if(calculator_state->position.x == 0 && calculator_state->position.y == 4) {
|
||||
if(calculator_state->textLength < MAX_TEXT_LENGTH) {
|
||||
calculator_state->text[calculator_state->textLength++] = ')';
|
||||
calculator_state->text[calculator_state->textLength] = '\0';
|
||||
}
|
||||
view_port_update(view_port);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,785 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
/*
|
||||
* TINYEXPR - Tiny recursive descent parser and evaluation engine in C
|
||||
*
|
||||
* Copyright (c) 2015-2020 Lewis Van Winkle
|
||||
*
|
||||
* http://CodePlea.com
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgement in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
/* COMPILE TIME OPTIONS */
|
||||
|
||||
/* Exponentiation associativity:
|
||||
For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing.
|
||||
For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/
|
||||
/* #define TE_POW_FROM_RIGHT */
|
||||
|
||||
/* Logarithms
|
||||
For log = base 10 log do nothing
|
||||
For log = natural log uncomment the next line. */
|
||||
/* #define TE_NAT_LOG */
|
||||
|
||||
#include "tinyexpr.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifndef NAN
|
||||
#define NAN (0.0 / 0.0)
|
||||
#endif
|
||||
|
||||
#ifndef INFINITY
|
||||
#define INFINITY (1.0 / 0.0)
|
||||
#endif
|
||||
|
||||
typedef double (*te_fun2)(double, double);
|
||||
|
||||
enum {
|
||||
TOK_NULL = TE_CLOSURE7 + 1,
|
||||
TOK_ERROR,
|
||||
TOK_END,
|
||||
TOK_SEP,
|
||||
TOK_OPEN,
|
||||
TOK_CLOSE,
|
||||
TOK_NUMBER,
|
||||
TOK_VARIABLE,
|
||||
TOK_INFIX
|
||||
};
|
||||
|
||||
enum { TE_CONSTANT = 1 };
|
||||
|
||||
typedef struct state {
|
||||
const char* start;
|
||||
const char* next;
|
||||
int type;
|
||||
union {
|
||||
double value;
|
||||
const double* bound;
|
||||
const void* function;
|
||||
};
|
||||
void* context;
|
||||
|
||||
const te_variable* lookup;
|
||||
int lookup_len;
|
||||
} state;
|
||||
|
||||
#define TYPE_MASK(TYPE) ((TYPE)&0x0000001F)
|
||||
|
||||
#define IS_PURE(TYPE) (((TYPE)&TE_FLAG_PURE) != 0)
|
||||
#define IS_FUNCTION(TYPE) (((TYPE)&TE_FUNCTION0) != 0)
|
||||
#define IS_CLOSURE(TYPE) (((TYPE)&TE_CLOSURE0) != 0)
|
||||
#define ARITY(TYPE) (((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE)&0x00000007) : 0)
|
||||
#define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__})
|
||||
|
||||
static te_expr* new_expr(const int type, const te_expr* parameters[]) {
|
||||
const int arity = ARITY(type);
|
||||
const int psize = sizeof(void*) * arity;
|
||||
const int size =
|
||||
(sizeof(te_expr) - sizeof(void*)) + psize + (IS_CLOSURE(type) ? sizeof(void*) : 0);
|
||||
te_expr* ret = malloc(size);
|
||||
memset(ret, 0, size);
|
||||
if(arity && parameters) {
|
||||
memcpy(ret->parameters, parameters, psize);
|
||||
}
|
||||
ret->type = type;
|
||||
ret->bound = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void te_free_parameters(te_expr* n) {
|
||||
if(!n) return;
|
||||
switch(TYPE_MASK(n->type)) {
|
||||
case TE_FUNCTION7:
|
||||
case TE_CLOSURE7:
|
||||
te_free(n->parameters[6]); /* Falls through. */
|
||||
case TE_FUNCTION6:
|
||||
case TE_CLOSURE6:
|
||||
te_free(n->parameters[5]); /* Falls through. */
|
||||
case TE_FUNCTION5:
|
||||
case TE_CLOSURE5:
|
||||
te_free(n->parameters[4]); /* Falls through. */
|
||||
case TE_FUNCTION4:
|
||||
case TE_CLOSURE4:
|
||||
te_free(n->parameters[3]); /* Falls through. */
|
||||
case TE_FUNCTION3:
|
||||
case TE_CLOSURE3:
|
||||
te_free(n->parameters[2]); /* Falls through. */
|
||||
case TE_FUNCTION2:
|
||||
case TE_CLOSURE2:
|
||||
te_free(n->parameters[1]); /* Falls through. */
|
||||
case TE_FUNCTION1:
|
||||
case TE_CLOSURE1:
|
||||
te_free(n->parameters[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void te_free(te_expr* n) {
|
||||
if(!n) return;
|
||||
te_free_parameters(n);
|
||||
free(n);
|
||||
}
|
||||
|
||||
static double pi(void) {
|
||||
return 3.14159265358979323846;
|
||||
}
|
||||
static double e(void) {
|
||||
return 2.71828182845904523536;
|
||||
}
|
||||
static double fac(double a) { /* simplest version of fac */
|
||||
if(a < 0) return NAN;
|
||||
if(a > UINT_MAX) return INFINITY;
|
||||
unsigned int ua = (unsigned int)(a);
|
||||
unsigned long int result = 1, i;
|
||||
for(i = 1; i <= ua; i++) {
|
||||
if(i > ULONG_MAX / result) return INFINITY;
|
||||
result *= i;
|
||||
}
|
||||
return (double)result;
|
||||
}
|
||||
static double ncr(double n, double r) {
|
||||
if(n < 0 || r < 0 || n < r) return NAN;
|
||||
if(n > UINT_MAX || r > UINT_MAX) return INFINITY;
|
||||
unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i;
|
||||
unsigned long int result = 1;
|
||||
if(ur > un / 2) ur = un - ur;
|
||||
for(i = 1; i <= ur; i++) {
|
||||
if(result > ULONG_MAX / (un - ur + i)) return INFINITY;
|
||||
result *= un - ur + i;
|
||||
result /= i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static double npr(double n, double r) {
|
||||
return ncr(n, r) * fac(r);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma function(ceil)
|
||||
#pragma function(floor)
|
||||
#endif
|
||||
|
||||
static const te_variable functions[] = {
|
||||
/* must be in alphabetical order */
|
||||
{"abs", fabs, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"acos", acos, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"asin", asin, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"atan", atan, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"atan2", atan2, TE_FUNCTION2 | TE_FLAG_PURE, 0},
|
||||
{"ceil", ceil, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"cos", cos, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"cosh", cosh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"e", e, TE_FUNCTION0 | TE_FLAG_PURE, 0},
|
||||
{"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"fac", fac, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"ln", log, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
#ifdef TE_NAT_LOG
|
||||
{"log", log, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
#else
|
||||
{"log", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
#endif
|
||||
{"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"ncr", ncr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
|
||||
{"npr", npr, TE_FUNCTION2 | TE_FLAG_PURE, 0},
|
||||
{"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE, 0},
|
||||
{"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE, 0},
|
||||
{"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"sinh", sinh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"sqrt", sqrt, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"tan", tan, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{"tanh", tanh, TE_FUNCTION1 | TE_FLAG_PURE, 0},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
static const te_variable* find_builtin(const char* name, int len) {
|
||||
int imin = 0;
|
||||
int imax = sizeof(functions) / sizeof(te_variable) - 2;
|
||||
|
||||
/*Binary search.*/
|
||||
while(imax >= imin) {
|
||||
const int i = (imin + ((imax - imin) / 2));
|
||||
int c = strncmp(name, functions[i].name, len);
|
||||
if(!c) c = '\0' - functions[i].name[len];
|
||||
if(c == 0) {
|
||||
return functions + i;
|
||||
} else if(c > 0) {
|
||||
imin = i + 1;
|
||||
} else {
|
||||
imax = i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const te_variable* find_lookup(const state* s, const char* name, int len) {
|
||||
int iters;
|
||||
const te_variable* var;
|
||||
if(!s->lookup) return 0;
|
||||
|
||||
for(var = s->lookup, iters = s->lookup_len; iters; ++var, --iters) {
|
||||
if(strncmp(name, var->name, len) == 0 && var->name[len] == '\0') {
|
||||
return var;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double add(double a, double b) {
|
||||
return a + b;
|
||||
}
|
||||
static double sub(double a, double b) {
|
||||
return a - b;
|
||||
}
|
||||
static double mul(double a, double b) {
|
||||
return a * b;
|
||||
}
|
||||
static double divide(double a, double b) {
|
||||
return a / b;
|
||||
}
|
||||
static double negate(double a) {
|
||||
return -a;
|
||||
}
|
||||
static double comma(double a, double b) {
|
||||
(void)a;
|
||||
return b;
|
||||
}
|
||||
|
||||
void next_token(state* s) {
|
||||
s->type = TOK_NULL;
|
||||
|
||||
do {
|
||||
if(!*s->next) {
|
||||
s->type = TOK_END;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Try reading a number. */
|
||||
if((s->next[0] >= '0' && s->next[0] <= '9') || s->next[0] == '.') {
|
||||
s->value = strtof(s->next, (char**)&s->next);
|
||||
s->type = TOK_NUMBER;
|
||||
} else {
|
||||
/* Look for a variable or builtin function call. */
|
||||
if(isalpha(s->next[0])) {
|
||||
const char* start;
|
||||
start = s->next;
|
||||
while(isalpha(s->next[0]) || isdigit(s->next[0]) || (s->next[0] == '_')) s->next++;
|
||||
|
||||
const te_variable* var = find_lookup(s, start, s->next - start);
|
||||
if(!var) var = find_builtin(start, s->next - start);
|
||||
|
||||
if(!var) {
|
||||
s->type = TOK_ERROR;
|
||||
} else {
|
||||
switch(TYPE_MASK(var->type)) {
|
||||
case TE_VARIABLE:
|
||||
s->type = TOK_VARIABLE;
|
||||
s->bound = var->address;
|
||||
break;
|
||||
|
||||
case TE_CLOSURE0:
|
||||
case TE_CLOSURE1:
|
||||
case TE_CLOSURE2:
|
||||
case TE_CLOSURE3: /* Falls through. */
|
||||
case TE_CLOSURE4:
|
||||
case TE_CLOSURE5:
|
||||
case TE_CLOSURE6:
|
||||
case TE_CLOSURE7: /* Falls through. */
|
||||
s->context = var->context; /* Falls through. */
|
||||
|
||||
case TE_FUNCTION0:
|
||||
case TE_FUNCTION1:
|
||||
case TE_FUNCTION2:
|
||||
case TE_FUNCTION3: /* Falls through. */
|
||||
case TE_FUNCTION4:
|
||||
case TE_FUNCTION5:
|
||||
case TE_FUNCTION6:
|
||||
case TE_FUNCTION7: /* Falls through. */
|
||||
s->type = var->type;
|
||||
s->function = var->address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/* Look for an operator or special character. */
|
||||
switch(s->next++[0]) {
|
||||
case '+':
|
||||
s->type = TOK_INFIX;
|
||||
s->function = add;
|
||||
break;
|
||||
case '-':
|
||||
s->type = TOK_INFIX;
|
||||
s->function = sub;
|
||||
break;
|
||||
case '*':
|
||||
s->type = TOK_INFIX;
|
||||
s->function = mul;
|
||||
break;
|
||||
case '/':
|
||||
s->type = TOK_INFIX;
|
||||
s->function = divide;
|
||||
break;
|
||||
case '^':
|
||||
s->type = TOK_INFIX;
|
||||
s->function = pow;
|
||||
break;
|
||||
case '%':
|
||||
s->type = TOK_INFIX;
|
||||
s->function = fmod;
|
||||
break;
|
||||
case '(':
|
||||
s->type = TOK_OPEN;
|
||||
break;
|
||||
case ')':
|
||||
s->type = TOK_CLOSE;
|
||||
break;
|
||||
case ',':
|
||||
s->type = TOK_SEP;
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
break;
|
||||
default:
|
||||
s->type = TOK_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(s->type == TOK_NULL);
|
||||
}
|
||||
|
||||
static te_expr* list(state* s);
|
||||
static te_expr* expr(state* s);
|
||||
static te_expr* power(state* s);
|
||||
|
||||
static te_expr* base(state* s) {
|
||||
/* <base> = <constant> | <variable> | <function-0> {"(" ")"} | <function-1> <power> | <function-X> "(" <expr> {"," <expr>} ")" | "(" <list> ")" */
|
||||
te_expr* ret;
|
||||
int arity;
|
||||
|
||||
switch(TYPE_MASK(s->type)) {
|
||||
case TOK_NUMBER:
|
||||
ret = new_expr(TE_CONSTANT, 0);
|
||||
ret->value = s->value;
|
||||
next_token(s);
|
||||
break;
|
||||
|
||||
case TOK_VARIABLE:
|
||||
ret = new_expr(TE_VARIABLE, 0);
|
||||
ret->bound = s->bound;
|
||||
next_token(s);
|
||||
break;
|
||||
|
||||
case TE_FUNCTION0:
|
||||
case TE_CLOSURE0:
|
||||
ret = new_expr(s->type, 0);
|
||||
ret->function = s->function;
|
||||
if(IS_CLOSURE(s->type)) ret->parameters[0] = s->context;
|
||||
next_token(s);
|
||||
if(s->type == TOK_OPEN) {
|
||||
next_token(s);
|
||||
if(s->type != TOK_CLOSE) {
|
||||
s->type = TOK_ERROR;
|
||||
} else {
|
||||
next_token(s);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TE_FUNCTION1:
|
||||
case TE_CLOSURE1:
|
||||
ret = new_expr(s->type, 0);
|
||||
ret->function = s->function;
|
||||
if(IS_CLOSURE(s->type)) ret->parameters[1] = s->context;
|
||||
next_token(s);
|
||||
ret->parameters[0] = power(s);
|
||||
break;
|
||||
|
||||
case TE_FUNCTION2:
|
||||
case TE_FUNCTION3:
|
||||
case TE_FUNCTION4:
|
||||
case TE_FUNCTION5:
|
||||
case TE_FUNCTION6:
|
||||
case TE_FUNCTION7:
|
||||
case TE_CLOSURE2:
|
||||
case TE_CLOSURE3:
|
||||
case TE_CLOSURE4:
|
||||
case TE_CLOSURE5:
|
||||
case TE_CLOSURE6:
|
||||
case TE_CLOSURE7:
|
||||
arity = ARITY(s->type);
|
||||
|
||||
ret = new_expr(s->type, 0);
|
||||
ret->function = s->function;
|
||||
if(IS_CLOSURE(s->type)) ret->parameters[arity] = s->context;
|
||||
next_token(s);
|
||||
|
||||
if(s->type != TOK_OPEN) {
|
||||
s->type = TOK_ERROR;
|
||||
} else {
|
||||
int i;
|
||||
for(i = 0; i < arity; i++) {
|
||||
next_token(s);
|
||||
ret->parameters[i] = expr(s);
|
||||
if(s->type != TOK_SEP) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(s->type != TOK_CLOSE || i != arity - 1) {
|
||||
s->type = TOK_ERROR;
|
||||
} else {
|
||||
next_token(s);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TOK_OPEN:
|
||||
next_token(s);
|
||||
ret = list(s);
|
||||
if(s->type != TOK_CLOSE) {
|
||||
s->type = TOK_ERROR;
|
||||
} else {
|
||||
next_token(s);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = new_expr(0, 0);
|
||||
s->type = TOK_ERROR;
|
||||
ret->value = NAN;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static te_expr* power(state* s) {
|
||||
/* <power> = {("-" | "+")} <base> */
|
||||
int sign = 1;
|
||||
while(s->type == TOK_INFIX && (s->function == add || s->function == sub)) {
|
||||
if(s->function == sub) sign = -sign;
|
||||
next_token(s);
|
||||
}
|
||||
|
||||
te_expr* ret;
|
||||
|
||||
if(sign == 1) {
|
||||
ret = base(s);
|
||||
} else {
|
||||
ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, base(s));
|
||||
ret->function = negate;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef TE_POW_FROM_RIGHT
|
||||
static te_expr* factor(state* s) {
|
||||
/* <factor> = <power> {"^" <power>} */
|
||||
te_expr* ret = power(s);
|
||||
|
||||
int neg = 0;
|
||||
|
||||
if(ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) {
|
||||
te_expr* se = ret->parameters[0];
|
||||
free(ret);
|
||||
ret = se;
|
||||
neg = 1;
|
||||
}
|
||||
|
||||
te_expr* insertion = 0;
|
||||
|
||||
while(s->type == TOK_INFIX && (s->function == pow)) {
|
||||
te_fun2 t = s->function;
|
||||
next_token(s);
|
||||
|
||||
if(insertion) {
|
||||
/* Make exponentiation go right-to-left. */
|
||||
te_expr* insert =
|
||||
NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s));
|
||||
insert->function = t;
|
||||
insertion->parameters[1] = insert;
|
||||
insertion = insert;
|
||||
} else {
|
||||
ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
|
||||
ret->function = t;
|
||||
insertion = ret;
|
||||
}
|
||||
}
|
||||
|
||||
if(neg) {
|
||||
ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret);
|
||||
ret->function = negate;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static te_expr* factor(state* s) {
|
||||
/* <factor> = <power> {"^" <power>} */
|
||||
te_expr* ret = power(s);
|
||||
|
||||
while(s->type == TOK_INFIX && (s->function == pow)) {
|
||||
te_fun2 t = s->function;
|
||||
next_token(s);
|
||||
ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s));
|
||||
ret->function = t;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static te_expr* term(state* s) {
|
||||
/* <term> = <factor> {("*" | "/" | "%") <factor>} */
|
||||
te_expr* ret = factor(s);
|
||||
|
||||
while(s->type == TOK_INFIX &&
|
||||
(s->function == mul || s->function == divide || s->function == fmod)) {
|
||||
te_fun2 t = s->function;
|
||||
next_token(s);
|
||||
ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s));
|
||||
ret->function = t;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static te_expr* expr(state* s) {
|
||||
/* <expr> = <term> {("+" | "-") <term>} */
|
||||
te_expr* ret = term(s);
|
||||
|
||||
while(s->type == TOK_INFIX && (s->function == add || s->function == sub)) {
|
||||
te_fun2 t = s->function;
|
||||
next_token(s);
|
||||
ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s));
|
||||
ret->function = t;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static te_expr* list(state* s) {
|
||||
/* <list> = <expr> {"," <expr>} */
|
||||
te_expr* ret = expr(s);
|
||||
|
||||
while(s->type == TOK_SEP) {
|
||||
next_token(s);
|
||||
ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s));
|
||||
ret->function = comma;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define TE_FUN(...) ((double (*)(__VA_ARGS__))n->function)
|
||||
#define M(e) te_eval(n->parameters[e])
|
||||
|
||||
double te_eval(const te_expr* n) {
|
||||
if(!n) return NAN;
|
||||
|
||||
switch(TYPE_MASK(n->type)) {
|
||||
case TE_CONSTANT:
|
||||
return n->value;
|
||||
case TE_VARIABLE:
|
||||
return *n->bound;
|
||||
|
||||
case TE_FUNCTION0:
|
||||
case TE_FUNCTION1:
|
||||
case TE_FUNCTION2:
|
||||
case TE_FUNCTION3:
|
||||
case TE_FUNCTION4:
|
||||
case TE_FUNCTION5:
|
||||
case TE_FUNCTION6:
|
||||
case TE_FUNCTION7:
|
||||
switch(ARITY(n->type)) {
|
||||
case 0:
|
||||
return TE_FUN(void)();
|
||||
case 1:
|
||||
return TE_FUN(double)(M(0));
|
||||
case 2:
|
||||
return TE_FUN(double, double)(M(0), M(1));
|
||||
case 3:
|
||||
return TE_FUN(double, double, double)(M(0), M(1), M(2));
|
||||
case 4:
|
||||
return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3));
|
||||
case 5:
|
||||
return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4));
|
||||
case 6:
|
||||
return TE_FUN(double, double, double, double, double, double)(
|
||||
M(0), M(1), M(2), M(3), M(4), M(5));
|
||||
case 7:
|
||||
return TE_FUN(double, double, double, double, double, double, double)(
|
||||
M(0), M(1), M(2), M(3), M(4), M(5), M(6));
|
||||
default:
|
||||
return NAN;
|
||||
}
|
||||
|
||||
case TE_CLOSURE0:
|
||||
case TE_CLOSURE1:
|
||||
case TE_CLOSURE2:
|
||||
case TE_CLOSURE3:
|
||||
case TE_CLOSURE4:
|
||||
case TE_CLOSURE5:
|
||||
case TE_CLOSURE6:
|
||||
case TE_CLOSURE7:
|
||||
switch(ARITY(n->type)) {
|
||||
case 0:
|
||||
return TE_FUN(void*)(n->parameters[0]);
|
||||
case 1:
|
||||
return TE_FUN(void*, double)(n->parameters[1], M(0));
|
||||
case 2:
|
||||
return TE_FUN(void*, double, double)(n->parameters[2], M(0), M(1));
|
||||
case 3:
|
||||
return TE_FUN(void*, double, double, double)(n->parameters[3], M(0), M(1), M(2));
|
||||
case 4:
|
||||
return TE_FUN(void*, double, double, double, double)(
|
||||
n->parameters[4], M(0), M(1), M(2), M(3));
|
||||
case 5:
|
||||
return TE_FUN(void*, double, double, double, double, double)(
|
||||
n->parameters[5], M(0), M(1), M(2), M(3), M(4));
|
||||
case 6:
|
||||
return TE_FUN(void*, double, double, double, double, double, double)(
|
||||
n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5));
|
||||
case 7:
|
||||
return TE_FUN(void*, double, double, double, double, double, double, double)(
|
||||
n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6));
|
||||
default:
|
||||
return NAN;
|
||||
}
|
||||
|
||||
default:
|
||||
return NAN;
|
||||
}
|
||||
}
|
||||
|
||||
#undef TE_FUN
|
||||
#undef M
|
||||
|
||||
static void optimize(te_expr* n) {
|
||||
/* Evaluates as much as possible. */
|
||||
if(n->type == TE_CONSTANT) return;
|
||||
if(n->type == TE_VARIABLE) return;
|
||||
|
||||
/* Only optimize out functions flagged as pure. */
|
||||
if(IS_PURE(n->type)) {
|
||||
const int arity = ARITY(n->type);
|
||||
int known = 1;
|
||||
int i;
|
||||
for(i = 0; i < arity; ++i) {
|
||||
optimize(n->parameters[i]);
|
||||
if(((te_expr*)(n->parameters[i]))->type != TE_CONSTANT) {
|
||||
known = 0;
|
||||
}
|
||||
}
|
||||
if(known) {
|
||||
const double value = te_eval(n);
|
||||
te_free_parameters(n);
|
||||
n->type = TE_CONSTANT;
|
||||
n->value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
te_expr*
|
||||
te_compile(const char* expression, const te_variable* variables, int var_count, int* error) {
|
||||
state s;
|
||||
s.start = s.next = expression;
|
||||
s.lookup = variables;
|
||||
s.lookup_len = var_count;
|
||||
|
||||
next_token(&s);
|
||||
te_expr* root = list(&s);
|
||||
|
||||
if(s.type != TOK_END) {
|
||||
te_free(root);
|
||||
if(error) {
|
||||
*error = (s.next - s.start);
|
||||
if(*error == 0) *error = 1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
optimize(root);
|
||||
if(error) *error = 0;
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
double te_interp(const char* expression, int* error) {
|
||||
te_expr* n = te_compile(expression, 0, 0, error);
|
||||
double ret;
|
||||
if(n) {
|
||||
ret = te_eval(n);
|
||||
te_free(n);
|
||||
} else {
|
||||
ret = NAN;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pn(const te_expr* n, int depth) {
|
||||
int i, arity;
|
||||
printf("%*s", depth, "");
|
||||
|
||||
switch(TYPE_MASK(n->type)) {
|
||||
case TE_CONSTANT:
|
||||
printf("%f\n", n->value);
|
||||
break;
|
||||
case TE_VARIABLE:
|
||||
printf("bound %p\n", n->bound);
|
||||
break;
|
||||
|
||||
case TE_FUNCTION0:
|
||||
case TE_FUNCTION1:
|
||||
case TE_FUNCTION2:
|
||||
case TE_FUNCTION3:
|
||||
case TE_FUNCTION4:
|
||||
case TE_FUNCTION5:
|
||||
case TE_FUNCTION6:
|
||||
case TE_FUNCTION7:
|
||||
case TE_CLOSURE0:
|
||||
case TE_CLOSURE1:
|
||||
case TE_CLOSURE2:
|
||||
case TE_CLOSURE3:
|
||||
case TE_CLOSURE4:
|
||||
case TE_CLOSURE5:
|
||||
case TE_CLOSURE6:
|
||||
case TE_CLOSURE7:
|
||||
arity = ARITY(n->type);
|
||||
printf("f%d", arity);
|
||||
for(i = 0; i < arity; i++) {
|
||||
printf(" %p", n->parameters[i]);
|
||||
}
|
||||
printf("\n");
|
||||
for(i = 0; i < arity; i++) {
|
||||
pn(n->parameters[i], depth + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void te_print(const te_expr* n) {
|
||||
pn(n, 0);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
/*
|
||||
* TINYEXPR - Tiny recursive descent parser and evaluation engine in C
|
||||
*
|
||||
* Copyright (c) 2015-2020 Lewis Van Winkle
|
||||
*
|
||||
* http://CodePlea.com
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgement in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#ifndef TINYEXPR_H
|
||||
#define TINYEXPR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct te_expr {
|
||||
int type;
|
||||
union {
|
||||
double value;
|
||||
const double* bound;
|
||||
const void* function;
|
||||
};
|
||||
void* parameters[1];
|
||||
} te_expr;
|
||||
|
||||
enum {
|
||||
TE_VARIABLE = 0,
|
||||
|
||||
TE_FUNCTION0 = 8,
|
||||
TE_FUNCTION1,
|
||||
TE_FUNCTION2,
|
||||
TE_FUNCTION3,
|
||||
TE_FUNCTION4,
|
||||
TE_FUNCTION5,
|
||||
TE_FUNCTION6,
|
||||
TE_FUNCTION7,
|
||||
|
||||
TE_CLOSURE0 = 16,
|
||||
TE_CLOSURE1,
|
||||
TE_CLOSURE2,
|
||||
TE_CLOSURE3,
|
||||
TE_CLOSURE4,
|
||||
TE_CLOSURE5,
|
||||
TE_CLOSURE6,
|
||||
TE_CLOSURE7,
|
||||
|
||||
TE_FLAG_PURE = 32
|
||||
};
|
||||
|
||||
typedef struct te_variable {
|
||||
const char* name;
|
||||
const void* address;
|
||||
int type;
|
||||
void* context;
|
||||
} te_variable;
|
||||
|
||||
/* Parses the input expression, evaluates it, and frees it. */
|
||||
/* Returns NaN on error. */
|
||||
double te_interp(const char* expression, int* error);
|
||||
|
||||
/* Parses the input expression and binds variables. */
|
||||
/* Returns NULL on error. */
|
||||
te_expr*
|
||||
te_compile(const char* expression, const te_variable* variables, int var_count, int* error);
|
||||
|
||||
/* Evaluates the expression. */
|
||||
double te_eval(const te_expr* n);
|
||||
|
||||
/* Prints debugging information on the syntax tree. */
|
||||
void te_print(const te_expr* n);
|
||||
|
||||
/* Frees the expression. */
|
||||
/* This is safe to call on NULL pointers. */
|
||||
void te_free(te_expr* n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*TINYEXPR_H*/
|
||||
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="zBroken_Chess",
|
||||
name="Chess",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="chess_app",
|
||||
cdefines=["APP_CHESS"],
|
||||
requires=["storage","gui"],
|
||||
stack_size= 4 * 1024,
|
||||
order=500,
|
||||
fap_icon="chessIcon.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,686 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/icon_i.h>
|
||||
#include "fast_chess.h"
|
||||
|
||||
static bool flag = true;
|
||||
static bool should_exit = false;
|
||||
// static bool ai_should_make_move = false;
|
||||
static bool thinking = false;
|
||||
static bool should_update_screen = true;
|
||||
static uint32_t anim = 0;
|
||||
static char white_move_str[8] = "", black_move_str[8] = ""; // last moves
|
||||
|
||||
static NotificationApp* notification;
|
||||
|
||||
const uint8_t _I_Chess_0[] = {
|
||||
0x01, 0x00, 0x2a, 0x01, 0x80, 0x7f, 0xc0, 0x2c, 0x0f, 0xf0, 0x7f, 0x83, 0xfc, 0x1f, 0xe0, 0xff,
|
||||
0x07, 0xf8, 0x3f, 0xc1, 0xde, 0x0f, 0xf0, 0x7f, 0x83, 0xfc, 0x1f, 0xe0, 0xff, 0x07, 0xf8, 0x3f,
|
||||
0xc2, 0x1e, 0x0f, 0xf0, 0x7f, 0x80, 0x07, 0x00, 0x0f, 0xf1, 0x7f, 0xc0, 0x41, 0xf8, 0x1e, 0x18,
|
||||
0x0f, 0xf2, 0xfe, 0x1f, 0xb8, 0x0e, 0x0a, 0x07, 0x80, 0x81, 0x83, 0xea, 0x05, 0x62, 0x01, 0x8c,
|
||||
0x30, 0x7d, 0x50, 0x48, 0x80, 0x0c, 0x66, 0x00, 0xfa, 0x84, 0x42, 0x00, 0x63, 0x40, 0x07, 0xd4,
|
||||
0x42, 0x08, 0x54, 0x24, 0xf5, 0xc0, 0x8f, 0xd9, 0x70, 0x3f, 0xa2, 0x7a, 0xb0, 0x69, 0xee, 0x57,
|
||||
0xa0, 0x3e, 0xa8, 0x0b, 0xfc, 0xc0, 0x4f, 0xc1, 0xe5, 0x3c, 0x07, 0xcd, 0x03, 0x81, 0x41, 0x06,
|
||||
0x0f, 0x0c, 0x1f, 0x32, 0x08, 0x04, 0x98, 0x46, 0x31, 0xf3, 0x10, 0x83, 0xdd, 0x58, 0x33, 0x88,
|
||||
0x07, 0x02, 0xfe, 0x82, 0x10, 0x7c, 0x88, 0x47, 0x81, 0x73, 0x07, 0xcf, 0x42, 0x07, 0xc0, 0x80,
|
||||
0x78, 0x3e, 0x2b, 0x21, 0x07, 0xbf, 0xc2, 0x1f, 0x00, 0x81, 0x83, 0xef, 0xc1, 0x1f, 0x7e, 0x0f,
|
||||
0x83, 0xff, 0x04, 0x47, 0xc7, 0x82, 0x7e, 0x42, 0x10, 0x7d, 0x96, 0xc4, 0x06, 0x71, 0x60, 0x7c,
|
||||
0x3e, 0x48, 0x1e, 0x52, 0xa5, 0xfc, 0xff, 0xd1, 0x62, 0x8f, 0x1a, 0xa8, 0x3e, 0x7f, 0xd0, 0x31,
|
||||
0x19, 0x07, 0xeb, 0xf9, 0x07, 0x80, 0x58, 0x2c, 0x01, 0xfa, 0xfc, 0x20, 0x06, 0x21, 0x80, 0x0f,
|
||||
0xd7, 0xc1, 0x00, 0x20, 0x01, 0xaa, 0xa3, 0xe1, 0x00, 0x60, 0x01, 0x95, 0x03, 0xe7, 0x80, 0x24,
|
||||
0x38, 0xa0, 0x3e, 0x7f, 0x1c, 0x73, 0x00, 0x9d, 0x8c, 0x02, 0xdc, 0x0d, 0x7c, 0x04, 0xa0, 0x2e,
|
||||
0xe1, 0x07, 0xc5, 0xc2, 0xeb, 0x00, 0x9f, 0x40, 0x12, 0x42, 0x0f, 0x8d, 0xc4, 0x69, 0x22, 0x3c,
|
||||
0x11, 0x7d, 0x5e, 0x20, 0xa0, 0x41, 0x30, 0x98, 0x3d, 0xf1, 0x1f, 0xff, 0xfa, 0x00, 0xcb, 0xd1,
|
||||
0x10, 0x2e, 0x18, 0x3e, 0xa4, 0x00, 0xfe, 0xa0, 0x03, 0xfb, 0x00, 0x6b, 0x20, 0x7d, 0xc0, 0x21,
|
||||
0xc0, 0xfe, 0xf8, 0x3f, 0xc4, 0x1f, 0x90, 0x0f, 0xe0, 0x40, 0xc1, 0xf6, 0x05, 0x30,
|
||||
};
|
||||
const uint8_t* const _I_Chess[] = {_I_Chess_0};
|
||||
|
||||
const uint8_t _I_Chess_Selection1_0[] = {
|
||||
0x00,
|
||||
0x55,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0xAA,
|
||||
};
|
||||
const uint8_t* const _I_Chess_Selection1[] = {_I_Chess_Selection1_0};
|
||||
|
||||
const uint8_t _I_Chess_Selection2_0[] = {
|
||||
0x00,
|
||||
0xAA,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x01,
|
||||
0x80,
|
||||
0x55,
|
||||
};
|
||||
const uint8_t* const _I_Chess_Selection2[] = {_I_Chess_Selection2_0};
|
||||
|
||||
const uint8_t _I_Chess_bb_0[] = {
|
||||
0x00,
|
||||
0x0C,
|
||||
0x1A,
|
||||
0x3D,
|
||||
0x1E,
|
||||
0x0C,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_bb[] = {_I_Chess_bb_0};
|
||||
|
||||
const uint8_t _I_Chess_bw_0[] = {
|
||||
0x00,
|
||||
0x0C,
|
||||
0x16,
|
||||
0x23,
|
||||
0x16,
|
||||
0x0C,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_bw[] = {_I_Chess_bw_0};
|
||||
|
||||
const uint8_t _I_Chess_kb_0[] = {
|
||||
0x00,
|
||||
0x0C,
|
||||
0x2D,
|
||||
0x21,
|
||||
0x12,
|
||||
0x0C,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_kb[] = {_I_Chess_kb_0};
|
||||
|
||||
const uint8_t _I_Chess_kw_0[] = {
|
||||
0x00,
|
||||
0x0C,
|
||||
0x21,
|
||||
0x21,
|
||||
0x12,
|
||||
0x0C,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_kw[] = {_I_Chess_kw_0};
|
||||
|
||||
const uint8_t _I_Chess_nb_0[] = {
|
||||
0x00,
|
||||
0x06,
|
||||
0x0F,
|
||||
0x1F,
|
||||
0x2E,
|
||||
0x0E,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_nb[] = {_I_Chess_nb_0};
|
||||
|
||||
const uint8_t _I_Chess_nw_0[] = {
|
||||
0x00,
|
||||
0x06,
|
||||
0x09,
|
||||
0x11,
|
||||
0x2A,
|
||||
0x0A,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_nw[] = {_I_Chess_nw_0};
|
||||
|
||||
const uint8_t _I_Chess_old_0[] = {
|
||||
0x01, 0x00, 0x35, 0x01, 0x80, 0x7f, 0xc0, 0x2c, 0x0f, 0xf0, 0x7f, 0x83, 0xfc, 0x1f, 0xe0, 0xff,
|
||||
0x07, 0xf8, 0x3f, 0xc0, 0x03, 0x80, 0x0f, 0x70, 0x3f, 0xe0, 0x10, 0x11, 0x77, 0x40, 0x7f, 0x97,
|
||||
0xf0, 0xfd, 0xc0, 0x70, 0x50, 0x3c, 0x04, 0x0c, 0x1f, 0x50, 0x2b, 0x10, 0x0c, 0x61, 0x80, 0xfa,
|
||||
0x82, 0x44, 0x00, 0x63, 0x30, 0x07, 0xd4, 0x22, 0x10, 0x03, 0x1a, 0x00, 0x3e, 0xa2, 0x10, 0x42,
|
||||
0xa1, 0x90, 0x2d, 0x01, 0x97, 0x03, 0xfa, 0x03, 0xe7, 0x01, 0x83, 0x5f, 0xf8, 0x3f, 0xa8, 0x00,
|
||||
0xfc, 0xc0, 0x4f, 0xc1, 0xe5, 0x3c, 0x07, 0xcd, 0x03, 0x81, 0x41, 0x06, 0x0f, 0x0c, 0x1f, 0x32,
|
||||
0x08, 0x04, 0x98, 0x46, 0x31, 0xf8, 0x08, 0xfa, 0x15, 0x83, 0x38, 0x80, 0x70, 0x2f, 0xf0, 0x20,
|
||||
0x7d, 0x08, 0x47, 0x81, 0x73, 0x07, 0xcf, 0x42, 0x07, 0xc0, 0x80, 0x78, 0x3e, 0x30, 0x40, 0x7c,
|
||||
0x7c, 0x21, 0xf0, 0x08, 0x18, 0x3e, 0xfc, 0x11, 0xf7, 0xe0, 0xf8, 0x3f, 0xe0, 0xfa, 0x9f, 0x90,
|
||||
0x84, 0x1f, 0x65, 0xb1, 0x01, 0x9c, 0x59, 0x9d, 0x21, 0x34, 0x95, 0x2f, 0xe7, 0xfe, 0xee, 0x14,
|
||||
0x78, 0xd5, 0x41, 0xf3, 0xfe, 0x81, 0x88, 0xc8, 0x3f, 0x5f, 0xc8, 0x3c, 0x02, 0xc1, 0x60, 0x0f,
|
||||
0xd7, 0xe1, 0x00, 0x31, 0x0c, 0x00, 0x7e, 0xbe, 0x08, 0x01, 0x00, 0x08, 0x7e, 0x90, 0x04, 0x00,
|
||||
0x10, 0xfd, 0x70, 0x00, 0x65, 0x00, 0x8a, 0x0f, 0xeb, 0x8e, 0x60, 0x13, 0xa9, 0x07, 0xa3, 0x5f,
|
||||
0x01, 0x28, 0x0c, 0x08, 0x1f, 0x37, 0x0b, 0xac, 0x02, 0x7d, 0x00, 0x49, 0x08, 0x3e, 0x37, 0x11,
|
||||
0xa4, 0x88, 0xf0, 0x45, 0xf5, 0x78, 0x82, 0x81, 0x44, 0xc2, 0x40, 0xf8, 0xc4, 0x7f, 0xff, 0xe8,
|
||||
0x03, 0x07, 0xc4, 0x40, 0x18, 0x40, 0xfb, 0x90, 0x03, 0xfa, 0x80, 0x0f, 0x50, 0x84, 0x60, 0x0d,
|
||||
0x62, 0x20, 0xd8, 0x70, 0x3f, 0xbe, 0x0f, 0xf1, 0x07, 0xe4, 0x03, 0xf8, 0x10, 0x40, 0x7d, 0x01,
|
||||
0x0c, 0x1f, 0x77, 0xf2, 0x91, 0x83, 0xeb, 0x7e, 0x1f, 0xdf, 0xa5, 0x7c, 0x1d, 0x90, 0x0d, 0x54,
|
||||
0xa8, 0x1f, 0xb5, 0x68, 0xa8, 0x7f, 0xa1, 0x40, 0xfd, 0xaa, 0xc1, 0x41, 0xfb, 0xa1, 0x81, 0x03,
|
||||
0xf5, 0xa0, 0x80, 0xff, 0x07, 0xce, 0x01, 0x9c, 0x80,
|
||||
};
|
||||
const uint8_t* const _I_Chess_old[] = {_I_Chess_old_0};
|
||||
|
||||
const uint8_t _I_Chess_pb_0[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x1E,
|
||||
0x1E,
|
||||
0x0C,
|
||||
0x1E,
|
||||
};
|
||||
const uint8_t* const _I_Chess_pb[] = {_I_Chess_pb_0};
|
||||
|
||||
const uint8_t _I_Chess_pw_0[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x12,
|
||||
0x12,
|
||||
0x0C,
|
||||
0x1E,
|
||||
};
|
||||
const uint8_t* const _I_Chess_pw[] = {_I_Chess_pw_0};
|
||||
|
||||
const uint8_t _I_Chess_qb_0[] = {
|
||||
0x00,
|
||||
0x2D,
|
||||
0x2D,
|
||||
0x2D,
|
||||
0x1E,
|
||||
0x1E,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_qb[] = {_I_Chess_qb_0};
|
||||
|
||||
const uint8_t _I_Chess_qw_0[] = {
|
||||
0x00,
|
||||
0x2D,
|
||||
0x2D,
|
||||
0x2D,
|
||||
0x1E,
|
||||
0x1E,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_qw[] = {_I_Chess_qw_0};
|
||||
|
||||
const uint8_t _I_Chess_rb_0[] = {
|
||||
0x00,
|
||||
0x2D,
|
||||
0x2D,
|
||||
0x1E,
|
||||
0x1E,
|
||||
0x1E,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_rb[] = {_I_Chess_rb_0};
|
||||
|
||||
const uint8_t _I_Chess_rw_0[] = {
|
||||
0x00,
|
||||
0x2D,
|
||||
0x2D,
|
||||
0x12,
|
||||
0x12,
|
||||
0x12,
|
||||
0x3F,
|
||||
};
|
||||
const uint8_t* const _I_Chess_rw[] = {_I_Chess_rw_0};
|
||||
|
||||
const Icon I_Chess_Selection2 =
|
||||
{.width = 8, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_Selection2};
|
||||
const Icon I_Chess_old =
|
||||
{.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_old};
|
||||
const Icon I_Chess_Selection1 =
|
||||
{.width = 8, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_Selection1};
|
||||
const Icon I_Chess =
|
||||
{.width = 128, .height = 64, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess};
|
||||
const Icon I_Chess_kb =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_kb};
|
||||
const Icon I_Chess_rw =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_rw};
|
||||
const Icon I_Chess_rb =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_rb};
|
||||
const Icon I_Chess_kw =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_kw};
|
||||
const Icon I_Chess_qb =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_qb};
|
||||
const Icon I_Chess_qw =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_qw};
|
||||
const Icon I_Chess_pw =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_pw};
|
||||
const Icon I_Chess_pb =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_pb};
|
||||
const Icon I_Chess_nb =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_nb};
|
||||
const Icon I_Chess_bw =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_bw};
|
||||
const Icon I_Chess_bb =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_bb};
|
||||
const Icon I_Chess_nw =
|
||||
{.width = 6, .height = 6, .frame_count = 1, .frame_rate = 0, .frames = _I_Chess_nw};
|
||||
|
||||
typedef struct {
|
||||
uint8_t col, row;
|
||||
} _Position;
|
||||
|
||||
typedef struct {
|
||||
enum {
|
||||
None = 0,
|
||||
Pawn,
|
||||
King,
|
||||
Queen,
|
||||
Bishop,
|
||||
Knight,
|
||||
Rook,
|
||||
} type;
|
||||
enum { White, Black } side;
|
||||
} Piece;
|
||||
|
||||
static const _Position PosNone = {.col = 255, .row = 255};
|
||||
// static Piece board[8][8]; // col, row
|
||||
static _Position sel, move_from = PosNone, move_to = PosNone;
|
||||
|
||||
Game* game;
|
||||
|
||||
// uint8_t sel_col = 0, sel_row = 0;
|
||||
|
||||
// static enum {
|
||||
// SelectingFrom,
|
||||
// SelectingTo
|
||||
// } state = SelectingFrom;
|
||||
|
||||
// static void reset_board() {
|
||||
// memset(board, 0, sizeof(board));
|
||||
|
||||
// board[0][0].type = Rook;
|
||||
// board[1][0].type = Knight;
|
||||
// board[2][0].type = Bishop;
|
||||
// board[3][0].type = Queen;
|
||||
// board[4][0].type = King;
|
||||
// board[5][0].type = Bishop;
|
||||
// board[6][0].type = Knight;
|
||||
// board[7][0].type = Rook;
|
||||
|
||||
// board[0][1].type = Pawn;
|
||||
// board[1][1].type = Pawn;
|
||||
// board[2][1].type = Pawn;
|
||||
// board[3][1].type = Pawn;
|
||||
// board[4][1].type = Pawn;
|
||||
// board[5][1].type = Pawn;
|
||||
// board[6][1].type = Pawn;
|
||||
// board[7][1].type = Pawn;
|
||||
|
||||
// board[0][7].type = Rook; board[0][7].side = Black;
|
||||
// board[1][7].type = Knight; board[1][7].side = Black;
|
||||
// board[2][7].type = Bishop; board[2][7].side = Black;
|
||||
// board[3][7].type = Queen; board[3][7].side = Black;
|
||||
// board[4][7].type = King; board[4][7].side = Black;
|
||||
// board[5][7].type = Bishop; board[5][7].side = Black;
|
||||
// board[6][7].type = Knight; board[6][7].side = Black;
|
||||
// board[7][7].type = Rook; board[7][7].side = Black;
|
||||
|
||||
// board[0][6].type = Pawn; board[0][6].side = Black;
|
||||
// board[1][6].type = Pawn; board[1][6].side = Black;
|
||||
// board[2][6].type = Pawn; board[2][6].side = Black;
|
||||
// board[3][6].type = Pawn; board[3][6].side = Black;
|
||||
// board[4][6].type = Pawn; board[4][6].side = Black;
|
||||
// board[5][6].type = Pawn; board[5][6].side = Black;
|
||||
// board[6][6].type = Pawn; board[6][6].side = Black;
|
||||
// board[7][6].type = Pawn; board[7][6].side = Black;
|
||||
// }
|
||||
|
||||
// static const Icon* get_icon(const Piece* piece) {
|
||||
// if (piece->side == White) {
|
||||
// switch (piece->type) {
|
||||
// case Pawn: return &I_Chess_pw;
|
||||
// case King: return &I_Chess_kw;
|
||||
// case Queen: return &I_Chess_qw;
|
||||
// case Bishop: return &I_Chess_bw;
|
||||
// case Knight: return &I_Chess_nw;
|
||||
// case Rook: return &I_Chess_rw;
|
||||
// default: return NULL;
|
||||
// }
|
||||
// } else {
|
||||
// switch (piece->type) {
|
||||
// case Pawn: return &I_Chess_pb;
|
||||
// case King: return &I_Chess_kb;
|
||||
// case Queen: return &I_Chess_qb;
|
||||
// case Bishop: return &I_Chess_bb;
|
||||
// case Knight: return &I_Chess_nb;
|
||||
// case Rook: return &I_Chess_rb;
|
||||
// default: return NULL;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
static void notify_click() {
|
||||
// static const NotificationSequence sequence = {
|
||||
// &message_click,
|
||||
// &message_delay_1,
|
||||
// &message_sound_off,
|
||||
// NULL,
|
||||
// };
|
||||
|
||||
// notification_message_block(notification, &sequence);
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
}
|
||||
static const Icon* _get_icon(uint8_t file, uint8_t rank) {
|
||||
char piece = getPieceChar((FILES_BB[file] & RANKS_BB[7 - rank]), &(game->position.board));
|
||||
switch(piece) {
|
||||
case 'P':
|
||||
return &I_Chess_pw;
|
||||
case 'K':
|
||||
return &I_Chess_kw;
|
||||
case 'Q':
|
||||
return &I_Chess_qw;
|
||||
case 'B':
|
||||
return &I_Chess_bw;
|
||||
case 'N':
|
||||
return &I_Chess_nw;
|
||||
case 'R':
|
||||
return &I_Chess_rw;
|
||||
case 'p':
|
||||
return &I_Chess_pb;
|
||||
case 'k':
|
||||
return &I_Chess_kb;
|
||||
case 'q':
|
||||
return &I_Chess_qb;
|
||||
case 'b':
|
||||
return &I_Chess_bb;
|
||||
case 'n':
|
||||
return &I_Chess_nb;
|
||||
case 'r':
|
||||
return &I_Chess_rb;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int get_position(uint8_t file, uint8_t rank) {
|
||||
return 8 * rank + file;
|
||||
}
|
||||
|
||||
static int get_rank(int position) {
|
||||
return (int)(position / 8);
|
||||
}
|
||||
|
||||
static int get_file(int position) {
|
||||
return position % 8;
|
||||
}
|
||||
|
||||
static void make_move(uint8_t file1, uint8_t rank1, uint8_t file2, uint8_t rank2) {
|
||||
int from = get_position(file1, rank1);
|
||||
int to = get_position(file2, rank2);
|
||||
Move move = generateMove(from, to);
|
||||
if(!isLegalMove(&game->position, move)) {
|
||||
return;
|
||||
}
|
||||
makeMove(game, move);
|
||||
move2str(white_move_str, game, game->moveListLen - 1);
|
||||
notify_click();
|
||||
black_move_str[0] = 0;
|
||||
anim = furi_get_tick();
|
||||
thinking = true;
|
||||
}
|
||||
|
||||
static int32_t make_ai_move(void* context) {
|
||||
UNUSED(context);
|
||||
// thinking = true;
|
||||
int depth = 1;
|
||||
Move move;
|
||||
Node node =
|
||||
iterativeDeepeningAlphaBeta(&(game->position), (char)depth, INT32_MIN, INT32_MAX, FALSE);
|
||||
move = node.move;
|
||||
makeMove(game, move);
|
||||
move2str(black_move_str, game, game->moveListLen - 1);
|
||||
notify_click();
|
||||
thinking = false;
|
||||
anim = furi_get_tick();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static FuriThread* worker_thread = NULL;
|
||||
|
||||
static int32_t ai_thread(void* context) {
|
||||
while(true) {
|
||||
if(should_exit) break;
|
||||
if(thinking) make_ai_move(context);
|
||||
furi_delay_ms(100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_ai_thread() {
|
||||
if(worker_thread == NULL) {
|
||||
worker_thread = furi_thread_alloc();
|
||||
}
|
||||
|
||||
furi_thread_set_name(worker_thread, "ChessEngine");
|
||||
furi_thread_set_stack_size(worker_thread, 7000);
|
||||
// furi_thread_set_context(thread, bad_usb);
|
||||
furi_thread_set_callback(worker_thread, ai_thread);
|
||||
furi_thread_start(worker_thread);
|
||||
|
||||
// furi_thread_join(worker_thread);
|
||||
// furi_thread_free(worker_thread);
|
||||
}
|
||||
|
||||
static void chess_draw_callback(Canvas* canvas, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
should_update_screen = false;
|
||||
canvas_clear(canvas);
|
||||
|
||||
// canvas_set_color(canvas, flag ? ColorBlack : ColorWhite);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Chess);
|
||||
|
||||
if(!thinking) {
|
||||
canvas_set_color(canvas, (sel.col + sel.row) % 2 != 0 ? ColorBlack : ColorWhite);
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
sel.col * 8,
|
||||
(7 - sel.row) * 8,
|
||||
flag ? &I_Chess_Selection1 : &I_Chess_Selection2);
|
||||
|
||||
if(move_from.col != 255) {
|
||||
canvas_set_color(
|
||||
canvas, (move_from.col + move_from.row) % 2 != 0 ? ColorBlack : ColorWhite);
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
move_from.col * 8,
|
||||
(7 - move_from.row) * 8,
|
||||
flag ? &I_Chess_Selection1 : &I_Chess_Selection2);
|
||||
}
|
||||
}
|
||||
|
||||
// print moves
|
||||
if(game->moveListLen > 0) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
// int num = game->moveListLen;
|
||||
|
||||
// char white_str[8], black_str[8] = "...";
|
||||
|
||||
// if (num == 0) {
|
||||
// } else if (num % 2 == 0) {
|
||||
// // white move
|
||||
// move2str(white_str, game, game->moveListLen - 2);
|
||||
// move2str(black_str, game, game->moveListLen - 1);
|
||||
// } else {
|
||||
// move2str(white_str, game, game->moveListLen - 1);
|
||||
// }
|
||||
|
||||
char str[28];
|
||||
snprintf(
|
||||
str, 28, "%d. %s %s", (game->moveListLen + 1) / 2, white_move_str, black_move_str);
|
||||
canvas_draw_str(canvas, 75, 12, str);
|
||||
}
|
||||
|
||||
Move last_move = getLastMove(game);
|
||||
|
||||
for(uint8_t row = 0; row < 8; row++) {
|
||||
for(uint8_t col = 0; col < 8; col++) {
|
||||
bool white_field = (row + col) % 2 != 0;
|
||||
|
||||
// if (!white_field) {
|
||||
// canvas_draw_box(canvas, col * 8, row * 8, 8, 8);
|
||||
// }
|
||||
const Icon* icon = _get_icon(col, row);
|
||||
if(icon != NULL) {
|
||||
int x = col * 8;
|
||||
int y = row * 8;
|
||||
|
||||
int dt = furi_get_tick() - anim;
|
||||
if(anim && dt >= 300) {
|
||||
anim = 0;
|
||||
}
|
||||
|
||||
if(anim && last_move && get_file(getTo(last_move)) == col &&
|
||||
get_rank(getTo(last_move)) == (7 - row)) {
|
||||
// moving piece
|
||||
uint8_t from_x = get_file(getFrom(last_move)) * 8;
|
||||
uint8_t from_y = (7 - get_rank(getFrom(last_move))) * 8;
|
||||
x = from_x + (x - from_x) * dt / 300;
|
||||
y = from_y + (y - from_y) * dt / 300;
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, white_field ? ColorWhite : ColorBlack);
|
||||
canvas_draw_icon(canvas, x + 1, y + 1, icon);
|
||||
}
|
||||
|
||||
// if (board[col][7 - row].type != None) {
|
||||
// canvas_set_color(canvas, white_field ? ColorWhite : ColorBlack);
|
||||
// canvas_draw_icon(canvas, col * 8 + 1, row * 8 + 1, get_icon(&board[col][7 - row]));
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// for (uint8_t i = 0; i < 4; i++) {
|
||||
// canvas_draw_dot(canvas, sel_col * 8, sel_row * 8);
|
||||
// canvas_draw_dot(canvas, sel_col * 8 + 2, sel_row * 8);
|
||||
// canvas_draw_dot(canvas, sel_col * 8, sel_row * 8);
|
||||
// canvas_draw_dot(canvas, sel_col * 8, sel_row * 8);
|
||||
// }
|
||||
|
||||
// canvas_draw_disc(canvas, GUI_DISPLAY_WIDTH / 2 - 40, GUI_DISPLAY_HEIGHT / 2, 15);
|
||||
// canvas_set_color(canvas, flag ? ColorBlack : ColorWhite);
|
||||
// canvas_draw_disc(canvas, GUI_DISPLAY_WIDTH / 2, GUI_DISPLAY_HEIGHT / 2, 15);
|
||||
}
|
||||
|
||||
static void chess_input_callback(InputEvent* event, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
if(event->type == InputTypeShort) {
|
||||
if(event->key == InputKeyLeft) {
|
||||
sel.col = (sel.col == 0) ? 0 : sel.col - 1;
|
||||
} else if(event->key == InputKeyRight) {
|
||||
sel.col++;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
sel.row = (sel.row == 0) ? 0 : sel.row - 1;
|
||||
} else if(event->key == InputKeyUp) {
|
||||
sel.row++;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
if(move_from.col == 255) {
|
||||
move_from = sel;
|
||||
} else if(move_to.col == 255) {
|
||||
move_to = sel;
|
||||
make_move(move_from.col, move_from.row, move_to.col, move_to.row);
|
||||
// thinking = true;
|
||||
// ai_should_make_move = true;
|
||||
// make_ai_move_threaded();
|
||||
// Piece piece = board[move_from.col][move_from.row];
|
||||
// board[move_from.col][move_from.row].type = None;
|
||||
// board[move_to.col][move_to.row] = piece;
|
||||
move_from = PosNone;
|
||||
move_to = PosNone;
|
||||
}
|
||||
} else if(event->key == InputKeyBack) {
|
||||
should_exit = true;
|
||||
}
|
||||
sel.col = CLAMP(sel.col, 7, 0);
|
||||
sel.row = CLAMP(sel.row, 7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void setup_engine() {
|
||||
// int depth = 1; // DEFAULT_AI_DEPTH;
|
||||
|
||||
getInitialGame(game);
|
||||
|
||||
// Move move;
|
||||
// Node node = iterativeDeepeningAlphaBeta(&(game.position), (char) depth, INT32_MIN, INT32_MAX, FALSE);
|
||||
// move = node.move;
|
||||
|
||||
// node = iterativeDeepeningAlphaBeta(&(game.position), (char) 2, INT32_MIN, INT32_MAX, FALSE);
|
||||
|
||||
// node = iterativeDeepeningAlphaBeta(&(game.position), (char) 3, INT32_MIN, INT32_MAX, FALSE);
|
||||
|
||||
// printf("%d\n", move);
|
||||
}
|
||||
|
||||
// void test_engine() {
|
||||
// FuriThread* thread; //
|
||||
|
||||
// thread = furi_thread_alloc();
|
||||
// furi_thread_set_name(thread, "ChessEngine");
|
||||
// furi_thread_set_stack_size(thread, 20000);
|
||||
// // furi_thread_set_context(thread, bad_usb);
|
||||
// furi_thread_set_callback(thread, setup_engine);
|
||||
|
||||
// furi_thread_start(thread);
|
||||
|
||||
// furi_thread_join(thread);
|
||||
|
||||
// furi_thread_free(thread);
|
||||
// }
|
||||
|
||||
int32_t chess_app(void* p) {
|
||||
UNUSED(p);
|
||||
// Configure view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, chess_draw_callback, NULL);
|
||||
view_port_input_callback_set(view_port, chess_input_callback, NULL);
|
||||
|
||||
// Register view port in GUI
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
should_exit = false;
|
||||
|
||||
game = malloc(sizeof(Game));
|
||||
|
||||
setup_engine();
|
||||
run_ai_thread();
|
||||
|
||||
// test_engine();
|
||||
|
||||
while(!should_exit) {
|
||||
furi_delay_ms(100);
|
||||
if(!thinking) {
|
||||
flag = !flag;
|
||||
should_update_screen = true;
|
||||
}
|
||||
if(anim) {
|
||||
should_update_screen = true;
|
||||
}
|
||||
if(should_update_screen) {
|
||||
view_port_update(view_port);
|
||||
}
|
||||
// flag = true;
|
||||
// delay(40);
|
||||
// flag = false;
|
||||
// view_port_update(view_port);
|
||||
// delay(80);
|
||||
}
|
||||
|
||||
furi_thread_join(worker_thread);
|
||||
furi_thread_free(worker_thread);
|
||||
worker_thread = NULL;
|
||||
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
free(game);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* fast-chess.h
|
||||
*
|
||||
* Created on: 20 de set de 2016
|
||||
* Author: fvj
|
||||
*/
|
||||
|
||||
#ifndef FAST_CHESS_H_
|
||||
#define FAST_CHESS_H_
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ENGINE_VERSION "v1.8.1"
|
||||
|
||||
#define ENGINE_NAME "github.com/fredericojordan/fast-chess " ENGINE_VERSION
|
||||
#define HUMAN_NAME "Unknown Human Player"
|
||||
|
||||
#define NUM_SQUARES (64)
|
||||
#define ENDGAME_PIECE_COUNT (7)
|
||||
|
||||
#define COLOR_MASK (1 << 3)
|
||||
#define WHITE (0)
|
||||
#define BLACK (1 << 3)
|
||||
|
||||
#define PIECE_MASK (0x7)
|
||||
#define EMPTY (0)
|
||||
#define PAWN (1)
|
||||
#define KNIGHT (2)
|
||||
#define BISHOP (3)
|
||||
#define ROOK (4)
|
||||
#define QUEEN (5)
|
||||
#define KING (6)
|
||||
|
||||
#define ALL_SQUARES (0xFFFFFFFFFFFFFFFF)
|
||||
#define FILE_A (0x0101010101010101)
|
||||
#define FILE_B (0x0202020202020202)
|
||||
#define FILE_C (0x0404040404040404)
|
||||
#define FILE_D (0x0808080808080808)
|
||||
#define FILE_E (0x1010101010101010)
|
||||
#define FILE_F (0x2020202020202020)
|
||||
#define FILE_G (0x4040404040404040)
|
||||
#define FILE_H (0x8080808080808080)
|
||||
#define RANK_1 (0x00000000000000FF)
|
||||
#define RANK_2 (0x000000000000FF00)
|
||||
#define RANK_3 (0x0000000000FF0000)
|
||||
#define RANK_4 (0x00000000FF000000)
|
||||
#define RANK_5 (0x000000FF00000000)
|
||||
#define RANK_6 (0x0000FF0000000000)
|
||||
#define RANK_7 (0x00FF000000000000)
|
||||
#define RANK_8 (0xFF00000000000000)
|
||||
#define DIAG_A1H8 (0x8040201008040201)
|
||||
#define ANTI_DIAG_H1A8 (0x0102040810204080)
|
||||
#define LIGHT_SQUARES (0x55AA55AA55AA55AA)
|
||||
#define DARK_SQUARES (0xAA55AA55AA55AA55)
|
||||
|
||||
#define CASTLE_KINGSIDE_WHITE (1 << 0)
|
||||
#define CASTLE_QUEENSIDE_WHITE (1 << 1)
|
||||
#define CASTLE_KINGSIDE_BLACK (1 << 2)
|
||||
#define CASTLE_QUEENSIDE_BLACK (1 << 3)
|
||||
|
||||
#define BOOL char
|
||||
|
||||
#ifndef FALSE
|
||||
#define TRUE (1)
|
||||
#define FALSE (0)
|
||||
#endif
|
||||
|
||||
typedef uint_fast64_t Bitboard;
|
||||
typedef int Move;
|
||||
|
||||
#define MAX_BOOK_ENTRY_LEN (300)
|
||||
#define MAX_PLYS_PER_GAME (1024)
|
||||
#define MAX_FEN_LEN (100)
|
||||
// #define MAX_BRANCHING_FACTOR (218) /* R6R/3Q4/1Q4Q1/4Q3/2Q4Q/Q4Q2/pp1Q4/kBNN1KB1 w - - 0 1 3Q4/1Q4Q1/4Q3/2Q4R/Q4Q2/3Q4/1Q4Rp/1K1BBNNk w - - 0 1 */
|
||||
#define MAX_BRANCHING_FACTOR (100) // okalachev
|
||||
#define MAX_ATTACKING_PIECES (12)
|
||||
|
||||
#define DEFAULT_AI_DEPTH (3)
|
||||
|
||||
typedef struct {
|
||||
Bitboard whiteKing;
|
||||
Bitboard whiteQueens;
|
||||
Bitboard whiteRooks;
|
||||
Bitboard whiteKnights;
|
||||
Bitboard whiteBishops;
|
||||
Bitboard whitePawns;
|
||||
|
||||
Bitboard blackKing;
|
||||
Bitboard blackQueens;
|
||||
Bitboard blackRooks;
|
||||
Bitboard blackKnights;
|
||||
Bitboard blackBishops;
|
||||
Bitboard blackPawns;
|
||||
} Board;
|
||||
|
||||
typedef struct {
|
||||
Board board;
|
||||
char toMove;
|
||||
char epSquare;
|
||||
char castlingRights;
|
||||
unsigned int halfmoveClock;
|
||||
unsigned int fullmoveNumber;
|
||||
} Position;
|
||||
|
||||
typedef struct {
|
||||
Position position;
|
||||
|
||||
unsigned int moveListLen;
|
||||
Move moveList[MAX_PLYS_PER_GAME];
|
||||
char positionHistory[MAX_PLYS_PER_GAME][MAX_FEN_LEN];
|
||||
} Game;
|
||||
|
||||
typedef struct {
|
||||
Move move;
|
||||
int score;
|
||||
} Node;
|
||||
|
||||
typedef struct {
|
||||
int depth;
|
||||
Position pos;
|
||||
int* alpha;
|
||||
int* beta;
|
||||
BOOL verbose;
|
||||
} ThreadInfo;
|
||||
|
||||
extern char FILES[8];
|
||||
extern char RANKS[8];
|
||||
|
||||
extern Bitboard FILES_BB[8];
|
||||
extern Bitboard RANKS_BB[8];
|
||||
|
||||
extern char INITIAL_FEN[];
|
||||
extern Board INITIAL_BOARD;
|
||||
extern int PIECE_VALUES[];
|
||||
|
||||
#define DOUBLED_PAWN_PENALTY (10)
|
||||
#define ISOLATED_PAWN_PENALTY (20)
|
||||
#define BACKWARDS_PAWN_PENALTY (8)
|
||||
#define PASSED_PAWN_BONUS (20)
|
||||
#define ROOK_SEMI_OPEN_FILE_BONUS (10)
|
||||
#define ROOK_OPEN_FILE_BONUS (15)
|
||||
#define ROOK_ON_SEVENTH_BONUS (20)
|
||||
|
||||
extern int PAWN_BONUS[];
|
||||
extern int KNIGHT_BONUS[];
|
||||
extern int BISHOP_BONUS[];
|
||||
extern int KING_BONUS[];
|
||||
extern int KING_ENDGAME_BONUS[];
|
||||
extern int FLIP_VERTICAL[];
|
||||
|
||||
void getInitialGame(Game* game);
|
||||
void getFenGame(Game* game, char fen[]);
|
||||
void insertPiece(Board* board, Bitboard position, char pieceCode);
|
||||
int loadFen(Position* position, char fen[]);
|
||||
int toFen(char* fen, Position* position);
|
||||
int toMinFen(char* fen, Position* position);
|
||||
void getMovelistGame(Game* game, char moves[]);
|
||||
|
||||
// ========= UTILITY =========
|
||||
|
||||
BOOL fromInitial(Game* game);
|
||||
Bitboard index2bb(int index);
|
||||
int str2index(char* str);
|
||||
Bitboard str2bb(char* str);
|
||||
BOOL isSet(Bitboard bb, int index);
|
||||
Bitboard lsb(Bitboard bb);
|
||||
Bitboard msb(Bitboard bb);
|
||||
int bb2index(Bitboard bb);
|
||||
char* movelist2str(Game* game);
|
||||
Move getLastMove(Game* game);
|
||||
BOOL startsWith(const char* str, const char* pre);
|
||||
int countBookOccurrences(Game* game);
|
||||
Move getBookMove(Game* game);
|
||||
char getFile(int position);
|
||||
char getRank(int position);
|
||||
Move generateMove(int leavingSquare, int arrivingSquare);
|
||||
int getFrom(Move move);
|
||||
int getTo(Move move);
|
||||
int char2piece(char pieceCode);
|
||||
int bb2piece(Bitboard position, Board* board);
|
||||
char bb2char(Bitboard position, Board* board);
|
||||
char* bb2str(Bitboard position, Board* board);
|
||||
void printBitboard(Bitboard bitboard);
|
||||
char getPieceChar(Bitboard position, Board* board);
|
||||
void printBoard(Board* board);
|
||||
void printGame(Game* game);
|
||||
Bitboard not(Bitboard bb);
|
||||
char opponent(char color);
|
||||
int countBits(Bitboard bb);
|
||||
void sortNodes(Node* sortedNodes, Node* nodes, int len, char color);
|
||||
void printMove(Move move);
|
||||
void printFullMove(Move move, Board* board);
|
||||
void printLegalMoves(Position* position);
|
||||
void printNode(Node node);
|
||||
void getTimestamp(char* timestamp);
|
||||
void dumpContent(Game* game);
|
||||
void dumpPGN(Game* game, char color, BOOL hasAI);
|
||||
void move2str(char* str, Game* game, int moveNumber);
|
||||
BOOL isAmbiguous(Position* posBefore, Move move);
|
||||
unsigned long hashPosition(Position* position);
|
||||
void writeToHashFile(Position* position, int evaluation, int depth);
|
||||
|
||||
// ====== BOARD FILTERS ======
|
||||
|
||||
Bitboard getColoredPieces(Board* board, char color);
|
||||
Bitboard getEmptySquares(Board* board);
|
||||
Bitboard getOccupiedSquares(Board* board);
|
||||
Bitboard getTwinPieces(Bitboard position, Board* board);
|
||||
Bitboard fileFilter(Bitboard positions);
|
||||
Bitboard rankFilter(Bitboard positions);
|
||||
|
||||
// ======= DIRECTIONS ========
|
||||
|
||||
Bitboard east(Bitboard bb);
|
||||
Bitboard west(Bitboard bb);
|
||||
Bitboard north(Bitboard bb);
|
||||
Bitboard south(Bitboard bb);
|
||||
Bitboard NE(Bitboard bb);
|
||||
Bitboard NW(Bitboard bb);
|
||||
Bitboard SE(Bitboard bb);
|
||||
Bitboard SW(Bitboard bb);
|
||||
Bitboard WNW(Bitboard moving_piece);
|
||||
Bitboard ENE(Bitboard moving_piece);
|
||||
Bitboard NNW(Bitboard moving_piece);
|
||||
Bitboard NNE(Bitboard moving_piece);
|
||||
Bitboard ESE(Bitboard moving_piece);
|
||||
Bitboard WSW(Bitboard moving_piece);
|
||||
Bitboard SSE(Bitboard moving_piece);
|
||||
Bitboard SSW(Bitboard moving_piece);
|
||||
|
||||
// ========== PAWN ===========
|
||||
|
||||
Bitboard getPawns(Board* board);
|
||||
Bitboard pawnSimplePushes(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnDoublePushes(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnPushes(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnEastAttacks(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnWestAttacks(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnAttacks(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnSimpleCaptures(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard pawnEpCaptures(Bitboard moving_piece, Position* position, char color);
|
||||
Bitboard pawnCaptures(Bitboard moving_piece, Position* position, char color);
|
||||
Bitboard pawnMoves(Bitboard moving_piece, Position* position, char color);
|
||||
BOOL isDoublePush(int leaving, int arriving);
|
||||
char getEpSquare(int leaving);
|
||||
BOOL isDoubledPawn(Bitboard position, Board* board, char color);
|
||||
BOOL isIsolatedPawn(Bitboard position, Board* board, char color);
|
||||
BOOL isBackwardsPawn(Bitboard position, Board* board, char color);
|
||||
BOOL isPassedPawn(Bitboard position, Board* board, char color);
|
||||
BOOL isOpenFile(Bitboard position, Board* board);
|
||||
BOOL isSemiOpenFile(Bitboard position, Board* board);
|
||||
|
||||
// ========== KNIGHT =========
|
||||
|
||||
Bitboard getKnights(Board* board);
|
||||
Bitboard knightAttacks(Bitboard moving_piece);
|
||||
Bitboard knightMoves(Bitboard moving_piece, Board* board, char color);
|
||||
|
||||
// ========== KING ===========
|
||||
|
||||
Bitboard getKing(Board* board, char color);
|
||||
Bitboard kingAttacks(Bitboard moving_piece);
|
||||
Bitboard kingMoves(Bitboard moving_piece, Board* board, char color);
|
||||
BOOL canCastleKingside(Position* position, char color);
|
||||
BOOL canCastleQueenside(Position* position, char color);
|
||||
char removeCastlingRights(char original_rights, char removed_rights);
|
||||
|
||||
// ========== BISHOP =========
|
||||
|
||||
Bitboard getBishops(Board* board);
|
||||
Bitboard NE_ray(Bitboard bb);
|
||||
Bitboard SE_ray(Bitboard bb);
|
||||
Bitboard NW_ray(Bitboard bb);
|
||||
Bitboard SW_ray(Bitboard bb);
|
||||
Bitboard NE_attack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard NW_attack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard SE_attack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard SW_attack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard diagonalAttacks(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard antiDiagonalAttacks(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard bishopAttacks(Bitboard moving_pieces, Board* board, char color);
|
||||
Bitboard bishopMoves(Bitboard moving_piece, Board* board, char color);
|
||||
|
||||
// ========== ROOK ===========
|
||||
|
||||
Bitboard getRooks(Board* board);
|
||||
Bitboard northRay(Bitboard moving_pieces);
|
||||
Bitboard southRay(Bitboard moving_pieces);
|
||||
Bitboard eastRay(Bitboard moving_pieces);
|
||||
Bitboard westRay(Bitboard moving_pieces);
|
||||
Bitboard northAttack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard southAttack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard fileAttacks(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard eastAttack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard westAttack(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard rankAttacks(Bitboard single_piece, Board* board, char color);
|
||||
Bitboard rookAttacks(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard rookMoves(Bitboard moving_piece, Board* board, char color);
|
||||
|
||||
// ========== QUEEN ==========
|
||||
|
||||
Bitboard getQueens(Board* board);
|
||||
Bitboard queenAttacks(Bitboard moving_piece, Board* board, char color);
|
||||
Bitboard queenMoves(Bitboard moving_piece, Board* board, char color);
|
||||
|
||||
// ======== MAKE MOVE ========
|
||||
|
||||
void clearPositions(Board* board, Bitboard positions);
|
||||
void movePiece(Board* board, Move move);
|
||||
void updatePosition(Position* newPosition, Position* position, Move move);
|
||||
void makeMove(Game* game, Move move);
|
||||
void unmakeMove(Game* game);
|
||||
|
||||
// ======== MOVE GEN =========
|
||||
|
||||
Bitboard getMoves(Bitboard movingPiece, Position* position, char color);
|
||||
int pseudoLegalMoves(Move* moves, Position* position, char color);
|
||||
Bitboard getAttacks(Bitboard movingPiece, Board* board, char color);
|
||||
int countAttacks(Bitboard target, Board* board, char color);
|
||||
BOOL isAttacked(Bitboard target, Board* board, char color);
|
||||
BOOL isCheck(Board* board, char color);
|
||||
BOOL isLegalMove(Position* position, Move move);
|
||||
int legalMoves(Move* legalMoves, Position* position, char color);
|
||||
int legalMovesCount(Position* position, char color);
|
||||
int staticOrderLegalMoves(Move* orderedLegalMoves, Position* position, char color);
|
||||
int legalCaptures(Move* legalCaptures, Position* position, char color);
|
||||
|
||||
// ====== GAME CONTROL =======
|
||||
|
||||
BOOL isCheckmate(Position* position);
|
||||
BOOL isStalemate(Position* position);
|
||||
BOOL hasInsufficientMaterial(Board* board);
|
||||
BOOL isEndgame(Board* board);
|
||||
BOOL isOver75MovesRule(Position* position);
|
||||
BOOL hasGameEnded(Position* position);
|
||||
void printOutcome(Position* position);
|
||||
|
||||
// ========== EVAL ===========
|
||||
|
||||
int winScore(char color);
|
||||
int materialSum(Board* board, char color);
|
||||
int materialBalance(Board* board);
|
||||
int positionalBonus(Board* board, char color);
|
||||
int positionalBalance(Board* board);
|
||||
int endNodeEvaluation(Position* position);
|
||||
int staticEvaluation(Position* position);
|
||||
int getCaptureSequence(Move* captures, Position* position, int targetSquare);
|
||||
int staticExchangeEvaluation(Position* position, int targetSquare);
|
||||
int quiescenceEvaluation(Position* position);
|
||||
|
||||
// ========= SEARCH ==========
|
||||
|
||||
Node staticSearch(Position* position);
|
||||
Node quiescenceSearch(Position* position);
|
||||
Node alphaBeta(Position* position, char depth, int alpha, int beta);
|
||||
int alphaBetaNodes(Node* nodes, Position* position, char depth);
|
||||
Node iterativeDeepeningAlphaBeta(Position* position, char depth, int alpha, int beta, BOOL verbose);
|
||||
Node pIDAB(Position* position, char depth, int* p_alpha, int* p_beta);
|
||||
Node pIDABhashed(Position* position, char depth, int* p_alpha, int* p_beta);
|
||||
Move getRandomMove(Position* position);
|
||||
Move getAIMove(Game* game, int depth);
|
||||
Move parseMove(char* move);
|
||||
Move getPlayerMove();
|
||||
Move suggestMove(char fen[], int depth);
|
||||
|
||||
// Parallel processing currently only implemented for Windows
|
||||
#ifdef _WIN32
|
||||
DWORD WINAPI evaluatePositionThreadFunction(LPVOID lpParam);
|
||||
DWORD WINAPI evaluatePositionThreadFunctionHashed(LPVOID lpParam);
|
||||
Node idabThreaded(Position* position, int depth, BOOL verbose);
|
||||
Node idabThreadedBestFirst(Position* position, int depth, BOOL verbose);
|
||||
Node idabThreadedBestFirstHashed(Position* position, int depth, BOOL verbose);
|
||||
#endif
|
||||
|
||||
// ===== PLAY LOOP (TEXT) ====
|
||||
|
||||
void playTextWhite(int depth);
|
||||
void playTextBlack(int depth);
|
||||
void playTextAs(char color, int depth);
|
||||
void playTextRandomColor(int depth);
|
||||
|
||||
// ===========================
|
||||
|
||||
#endif /* FAST_CHESS_H_ */
|
||||
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="Dice",
|
||||
name="Dice [RM]",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="dice_app",
|
||||
cdefines=["APP_DICE"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=70,
|
||||
fap_icon="dice.png",
|
||||
fap_category="Games",
|
||||
)
|
||||
@@ -0,0 +1,581 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "furi_hal_random.h"
|
||||
#include <gui/elements.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include "applications/settings/desktop_settings/desktop_settings_app.h"
|
||||
#include <dolphin/helpers/dolphin_deed.h>
|
||||
|
||||
#define TAG "Dice Roller"
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
DolphinDeed getRandomDeed() {
|
||||
DolphinDeed returnGrp[14] = {1, 5, 8, 10, 12, 15, 17, 20, 21, 25, 26, 28, 29, 32};
|
||||
static bool rand_generator_inited = false;
|
||||
if(!rand_generator_inited) {
|
||||
srand(furi_get_tick());
|
||||
rand_generator_inited = true;
|
||||
}
|
||||
uint8_t diceRoll = (rand() % COUNT_OF(returnGrp)); // JUST TO GET IT GOING? AND FIX BUG
|
||||
diceRoll = (rand() % COUNT_OF(returnGrp));
|
||||
return returnGrp[diceRoll];
|
||||
}
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} PluginEvent;
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
FuriMessageQueue* event_queue;
|
||||
DesktopSettings* desktop_settings;
|
||||
FuriHalRtcDateTime datetime;
|
||||
uint8_t diceSelect;
|
||||
uint8_t diceQty;
|
||||
uint8_t diceRoll;
|
||||
uint8_t playerOneScore;
|
||||
uint8_t playerTwoScore;
|
||||
char rollTime[1][15];
|
||||
char diceType[1][11];
|
||||
char strings[5][45];
|
||||
char theScores[1][45];
|
||||
bool letsRoll;
|
||||
} DiceState;
|
||||
|
||||
static void dice_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
PluginEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void dice_render_callback(Canvas* const canvas, void* ctx) {
|
||||
DiceState* state = ctx;
|
||||
if(furi_mutex_acquire(state->mutex, 200) != FuriStatusOk) {
|
||||
// Can't obtain mutex, requeue render
|
||||
PluginEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(state->event_queue, &event, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(state->diceSelect < 220) {
|
||||
if(state->diceQty == 1) {
|
||||
elements_button_left(canvas, "x1");
|
||||
} else if(state->diceQty == 2) {
|
||||
elements_button_left(canvas, "x2");
|
||||
} else if(state->diceQty == 3) {
|
||||
elements_button_left(canvas, "x3");
|
||||
} else if(state->diceQty == 4) {
|
||||
elements_button_left(canvas, "x4");
|
||||
} else if(state->diceQty == 5) {
|
||||
elements_button_left(canvas, "x5");
|
||||
} else if(state->diceQty == 6) {
|
||||
elements_button_left(canvas, "x6");
|
||||
}
|
||||
}
|
||||
if(state->letsRoll) {
|
||||
furi_hal_rtc_get_datetime(&state->datetime);
|
||||
uint8_t hour = state->datetime.hour;
|
||||
char strAMPM[3];
|
||||
snprintf(strAMPM, sizeof(strAMPM), "%s", "AM");
|
||||
if(hour > 12) {
|
||||
hour -= 12;
|
||||
snprintf(strAMPM, sizeof(strAMPM), "%s", "PM");
|
||||
}
|
||||
snprintf(
|
||||
state->rollTime[0],
|
||||
sizeof(state->rollTime[0]),
|
||||
"%.2d:%.2d:%.2d %s",
|
||||
hour,
|
||||
state->datetime.minute,
|
||||
state->datetime.second,
|
||||
strAMPM);
|
||||
if(state->diceSelect == 229) {
|
||||
const char* eightBall[] = {
|
||||
"It is certain",
|
||||
"Without a doubt",
|
||||
"You may rely on it",
|
||||
"Yes definitely",
|
||||
"It is decidedly so",
|
||||
"As I see it, yes",
|
||||
"Most likely",
|
||||
"Yes",
|
||||
"Outlook good",
|
||||
"Signs point to yes",
|
||||
"Reply hazy try again",
|
||||
"Better not tell you now",
|
||||
"Ask again later",
|
||||
"Cannot predict now",
|
||||
"Concentrate and ask again",
|
||||
"Don't count on it",
|
||||
"Outlook not so good",
|
||||
"My sources say no",
|
||||
"Very doubtful",
|
||||
"My reply is no"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "8BALL");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(eightBall);
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%s", eightBall[d1_i]);
|
||||
} else if(state->diceSelect == 228) {
|
||||
const char* eightBall[] = {
|
||||
"I'd do it.",
|
||||
"Hell, yeah!",
|
||||
"You bet your life!",
|
||||
"What are you waiting for?",
|
||||
"You could do worse things.",
|
||||
"Sure, I won't tell.",
|
||||
"Yeah, you got this. Would I lie to you?",
|
||||
"Looks like fun to me. ",
|
||||
"Yeah, sure, why not?",
|
||||
"DO IT!!!",
|
||||
"Who's it gonna hurt?",
|
||||
"Can you blame someone else?",
|
||||
"Ask me again later.",
|
||||
"Maybe, maybe not, I can't tell right now. ",
|
||||
"Are you the betting type? ",
|
||||
"Don't blame me if you get caught.",
|
||||
"What have you got to lose?",
|
||||
"I wouldn't if I were you.",
|
||||
"My money's on the snowball.",
|
||||
"Oh Hell no!"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "Devil Ball");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(eightBall);
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%s", eightBall[d1_i]);
|
||||
} else if(state->diceSelect == 230) {
|
||||
const char* diceOne[] = {
|
||||
"Nibble",
|
||||
"Massage",
|
||||
"Touch",
|
||||
"Caress",
|
||||
"Pet",
|
||||
"Fondle",
|
||||
"Suck",
|
||||
"Lick",
|
||||
"Blow",
|
||||
"Kiss",
|
||||
"???"};
|
||||
const char* diceTwo[] = {
|
||||
"Navel",
|
||||
"Ears",
|
||||
"Lips",
|
||||
"Neck",
|
||||
"Hand",
|
||||
"Thigh",
|
||||
"Nipple",
|
||||
"Breasts",
|
||||
"???",
|
||||
"Genitals"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "SEX?");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(diceOne);
|
||||
uint8_t d2_i = rand() % COUNT_OF(diceTwo);
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%s %s",
|
||||
diceOne[d1_i],
|
||||
diceTwo[d2_i]);
|
||||
} else if(state->diceSelect == 231) {
|
||||
const char* deckOne[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H",
|
||||
"4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C",
|
||||
"6D", "6S", "7H", "7C", "7D", "7S", "8H", "8C", "8D",
|
||||
"8S", "9H", "9C", "9D", "9S", "10H", "10C", "10D", "10S",
|
||||
"JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH",
|
||||
"KC", "KD", "KS", "AH", "AC", "AD", "AS"};
|
||||
char* deckTwo[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H",
|
||||
"4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C",
|
||||
"6D", "6S", "7H", "7C", "7D", "7S", "8H", "8C", "8D",
|
||||
"8S", "9H", "9C", "9D", "9S", "10H", "10C", "10D", "10S",
|
||||
"JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH",
|
||||
"KC", "KD", "KS", "AH", "AC", "AD"}; // ONE LESS SINCE ONE WILL BE REMOVED
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "WAR!");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(deckOne);
|
||||
// INITIALIZE WITH PLACEHOLDERS TO AVOID MAYBE UNINITIALIZED ERROR
|
||||
for(uint8_t i = 0; i < COUNT_OF(deckOne); i++) {
|
||||
if(i < d1_i) {
|
||||
snprintf(deckTwo[i], 8, "%s", deckOne[i]);
|
||||
} else if(i > d1_i) {
|
||||
snprintf(deckTwo[i - 1], 8, "%s", deckOne[i]);
|
||||
}
|
||||
}
|
||||
uint8_t d2_i = rand() % COUNT_OF(deckTwo);
|
||||
if(d1_i > d2_i) {
|
||||
state->playerOneScore++;
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%s > %s",
|
||||
deckOne[d1_i],
|
||||
deckTwo[d2_i]);
|
||||
} else {
|
||||
state->playerTwoScore++;
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%s < %s",
|
||||
deckOne[d1_i],
|
||||
deckTwo[d2_i]);
|
||||
}
|
||||
} else if(state->diceSelect == 232) {
|
||||
const char* diceOne[] = {
|
||||
"You", "You choose", "Nobody", "Everyone", "Nose goes", "Player to your right"};
|
||||
const char* diceTwo[] = {
|
||||
"take a tiny toke",
|
||||
"just chill",
|
||||
"take 2 tokes",
|
||||
"take a huge hit",
|
||||
"bogart it",
|
||||
"take a puff"};
|
||||
const char* diceThree[] = {
|
||||
"while humming a tune",
|
||||
"with your eyes closed",
|
||||
"on your knees",
|
||||
"while holding your nose",
|
||||
"while spinning in a circle",
|
||||
"in slow motion"};
|
||||
const char* diceFour[] = {
|
||||
"twice",
|
||||
"then tell a joke",
|
||||
"then laugh as hard as you can",
|
||||
"with the player to your left",
|
||||
"then sing a song",
|
||||
"then do a dance"};
|
||||
state->diceRoll =
|
||||
((rand() % state->diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG
|
||||
snprintf(state->diceType[0], sizeof(state->diceType[0]), "%s", "WEED!");
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%s at %s",
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
uint8_t d1_i = rand() % COUNT_OF(diceOne);
|
||||
uint8_t d2_i = rand() % COUNT_OF(diceTwo);
|
||||
uint8_t d3_i = rand() % COUNT_OF(diceThree);
|
||||
uint8_t d4_i = rand() % COUNT_OF(diceFour);
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%s", diceOne[d1_i]);
|
||||
snprintf(state->strings[2], sizeof(state->strings[2]), "%s", diceTwo[d2_i]);
|
||||
snprintf(state->strings[3], sizeof(state->strings[3]), "%s", diceThree[d3_i]);
|
||||
snprintf(state->strings[4], sizeof(state->strings[4]), "%s", diceFour[d4_i]);
|
||||
} else {
|
||||
state->diceRoll = ((rand() % state->diceSelect) + 1);
|
||||
snprintf(
|
||||
state->diceType[0], sizeof(state->diceType[0]), "%s%d", "d", state->diceSelect);
|
||||
snprintf(
|
||||
state->strings[0],
|
||||
sizeof(state->strings[0]),
|
||||
"%d%s at %s",
|
||||
state->diceQty,
|
||||
state->diceType[0],
|
||||
state->rollTime[0]);
|
||||
if(state->diceSelect >= 20 && state->diceRoll == state->diceSelect)
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
if(state->diceSelect >= 20 && state->diceRoll == state->diceSelect - 1)
|
||||
DOLPHIN_DEED(getRandomDeed());
|
||||
if(state->diceQty == 1) {
|
||||
snprintf(state->strings[1], sizeof(state->strings[1]), "%d", state->diceRoll);
|
||||
} else if(state->diceQty == 2) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 3) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 4) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 5) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
} else if(state->diceQty == 6) {
|
||||
snprintf(
|
||||
state->strings[1],
|
||||
sizeof(state->strings[1]),
|
||||
"%d %d %d %d %d %d",
|
||||
state->diceRoll,
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1),
|
||||
((rand() % state->diceSelect) + 1));
|
||||
}
|
||||
}
|
||||
state->letsRoll = false;
|
||||
}
|
||||
furi_mutex_release(state->mutex);
|
||||
if(state->diceRoll != 0) {
|
||||
if(state->diceSelect == 232) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]);
|
||||
canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignCenter, state->strings[1]);
|
||||
canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignCenter, state->strings[2]);
|
||||
canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, state->strings[3]);
|
||||
canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignCenter, state->strings[4]);
|
||||
} else if(state->diceSelect == 228 || state->diceSelect == 229) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, state->strings[1]);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]);
|
||||
} else {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, state->strings[1]);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, state->strings[0]);
|
||||
}
|
||||
if(state->diceSelect == 231 &&
|
||||
!(state->playerOneScore == 0 && state->playerTwoScore == 0)) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
snprintf(
|
||||
state->theScores[0],
|
||||
sizeof(state->theScores[0]),
|
||||
"%d %d",
|
||||
state->playerOneScore,
|
||||
state->playerTwoScore);
|
||||
canvas_draw_str_aligned(canvas, 64, 34, AlignCenter, AlignCenter, state->theScores[0]);
|
||||
}
|
||||
}
|
||||
if(state->diceSelect == 229 || state->diceSelect == 228) {
|
||||
elements_button_center(canvas, "Shake");
|
||||
} else if(state->diceSelect == 231) {
|
||||
elements_button_center(canvas, "Draw");
|
||||
} else {
|
||||
elements_button_center(canvas, "Roll");
|
||||
}
|
||||
if(state->diceSelect == 2) {
|
||||
elements_button_right(canvas, "d2");
|
||||
} else if(state->diceSelect == 3) {
|
||||
elements_button_right(canvas, "d3");
|
||||
} else if(state->diceSelect == 4) {
|
||||
elements_button_right(canvas, "d4");
|
||||
} else if(state->diceSelect == 6) {
|
||||
elements_button_right(canvas, "d6");
|
||||
} else if(state->diceSelect == 8) {
|
||||
elements_button_right(canvas, "d8");
|
||||
} else if(state->diceSelect == 10) {
|
||||
elements_button_right(canvas, "d10");
|
||||
} else if(state->diceSelect == 12) {
|
||||
elements_button_right(canvas, "d12");
|
||||
} else if(state->diceSelect == 20) {
|
||||
elements_button_right(canvas, "d20");
|
||||
} else if(state->diceSelect == 59) {
|
||||
elements_button_right(canvas, "d59");
|
||||
} else if(state->diceSelect == 69) {
|
||||
elements_button_right(canvas, "d69");
|
||||
} else if(state->diceSelect == 100) {
|
||||
elements_button_right(canvas, "d100");
|
||||
} else if(state->diceSelect == 229) {
|
||||
elements_button_right(canvas, "8BALL");
|
||||
} else if(state->diceSelect == 228) {
|
||||
elements_button_right(canvas, "DBALL");
|
||||
} else if(state->diceSelect == 230) {
|
||||
elements_button_right(canvas, "SEX");
|
||||
} else if(state->diceSelect == 231) {
|
||||
elements_button_right(canvas, "WAR");
|
||||
} else if(state->diceSelect == 232) {
|
||||
elements_button_right(canvas, "WEED");
|
||||
}
|
||||
}
|
||||
|
||||
static void dice_state_init(DiceState* const state) {
|
||||
memset(state, 0, sizeof(DiceState));
|
||||
furi_hal_rtc_get_datetime(&state->datetime);
|
||||
state->diceSelect = 20;
|
||||
state->diceQty = 1;
|
||||
state->diceRoll = 0;
|
||||
state->playerOneScore = 0;
|
||||
state->playerTwoScore = 0;
|
||||
state->letsRoll = false;
|
||||
state->desktop_settings = malloc(sizeof(DesktopSettings));
|
||||
}
|
||||
|
||||
static void dice_tick(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
PluginEvent event = {.type = EventTypeTick};
|
||||
// It's OK to lose this event if system overloaded
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t dice_app(void* p) {
|
||||
UNUSED(p);
|
||||
DiceState* plugin_state = malloc(sizeof(DiceState));
|
||||
dice_state_init(plugin_state);
|
||||
plugin_state->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
|
||||
if(plugin_state->event_queue == NULL) {
|
||||
FURI_LOG_E(TAG, "cannot create event queue\n");
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
plugin_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
if(plugin_state->mutex == NULL) {
|
||||
FURI_LOG_E(TAG, "cannot create mutex\n");
|
||||
furi_message_queue_free(plugin_state->event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(dice_tick, FuriTimerTypePeriodic, plugin_state->event_queue);
|
||||
if(timer == NULL) {
|
||||
FURI_LOG_E(TAG, "cannot create timer\n");
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
furi_message_queue_free(plugin_state->event_queue);
|
||||
free(plugin_state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
DESKTOP_SETTINGS_LOAD(plugin_state->desktop_settings);
|
||||
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, dice_render_callback, plugin_state);
|
||||
view_port_input_callback_set(view_port, dice_input_callback, plugin_state->event_queue);
|
||||
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||
|
||||
// Main loop
|
||||
PluginEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(plugin_state->event_queue, &event, 100);
|
||||
if(event_status == FuriStatusOk) {
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypeShort || event.input.type == InputTypeRepeat) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
break;
|
||||
case InputKeyDown:
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(plugin_state->diceSelect == 2) {
|
||||
plugin_state->diceSelect = 3;
|
||||
} else if(plugin_state->diceSelect == 3) {
|
||||
plugin_state->diceSelect = 4;
|
||||
} else if(plugin_state->diceSelect == 4) {
|
||||
plugin_state->diceSelect = 6;
|
||||
} else if(plugin_state->diceSelect == 6) {
|
||||
plugin_state->diceSelect = 8;
|
||||
} else if(plugin_state->diceSelect == 8) {
|
||||
plugin_state->diceSelect = 10;
|
||||
} else if(plugin_state->diceSelect == 10) {
|
||||
plugin_state->diceSelect = 12;
|
||||
} else if(plugin_state->diceSelect == 12) {
|
||||
plugin_state->diceSelect = 20;
|
||||
} else if(plugin_state->diceSelect == 20) {
|
||||
plugin_state->diceSelect = 100;
|
||||
} else if(plugin_state->diceSelect == 100) {
|
||||
plugin_state->diceSelect = 230;
|
||||
} else if(plugin_state->diceSelect == 230) {
|
||||
plugin_state->playerOneScore = 0;
|
||||
plugin_state->playerTwoScore = 0;
|
||||
plugin_state->diceSelect = 231;
|
||||
} else if(plugin_state->diceSelect == 231) {
|
||||
plugin_state->diceSelect = 229;
|
||||
} else if(plugin_state->diceSelect == 229) {
|
||||
plugin_state->diceSelect = 228;
|
||||
} else if(plugin_state->diceSelect == 228) {
|
||||
plugin_state->diceSelect = 232;
|
||||
} else if(plugin_state->diceSelect == 232) {
|
||||
plugin_state->diceSelect = 59;
|
||||
} else if(plugin_state->diceSelect == 59) {
|
||||
plugin_state->diceSelect = 69;
|
||||
} else {
|
||||
plugin_state->diceSelect = 2;
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(plugin_state->diceQty <= 5) {
|
||||
plugin_state->diceQty = plugin_state->diceQty + 1;
|
||||
} else {
|
||||
plugin_state->diceQty = 1;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
plugin_state->letsRoll = true;
|
||||
break;
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
// furi_hal_rtc_get_datetime(&plugin_state->datetime);
|
||||
}
|
||||
view_port_update(view_port);
|
||||
furi_mutex_release(plugin_state->mutex);
|
||||
} else {
|
||||
// FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
}
|
||||
}
|
||||
// Cleanup
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(plugin_state->event_queue);
|
||||
furi_mutex_free(plugin_state->mutex);
|
||||
free(plugin_state->desktop_settings);
|
||||
free(plugin_state);
|
||||
return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 207 B |
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Flipper Zero DnD Dice
|
||||
|
||||
<div style="text-align:center"><img src="sources/flipper-screen.png"/></div>
|
||||
<br />
|
||||
|
||||
**DnD Dice** is a dice rolling application for your **Flipper Zero**.
|
||||
|
||||
Dice types: Coin, d4, d6, d8, d10, d12, d20, d100
|
||||
|
||||
## Screenshots
|
||||
|
||||
<div style="text-align:center"><img src="sources/main-screen.png"/></div>
|
||||
<br />
|
||||
<div style="text-align:center"><img src="sources/roll-screen.png"/></div>
|
||||
|
||||
## Compiling
|
||||
|
||||
1. Clone the [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) repository or another firmware that you use (for example [unleashed-firmware](https://github.com/DarkFlippers/unleashed-firmware)).
|
||||
2. Create a symbolic link in `applications_user` named **dice**, pointing to this repository.
|
||||
3. Compile by command `./fbt fap_dice_dnd_app`
|
||||
4. Copy `build/f7-firmware-D/.extapps/dice_dnd_app.fap` to **apps/Games** on the SD card or by [qFlipper](https://flipperzero.one/update) app.
|
||||
@@ -0,0 +1,13 @@
|
||||
App(
|
||||
appid="DND_Dice_app",
|
||||
name="DnD Dice [Ka3u6y6a]",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="dice_dnd_app",
|
||||
cdefines=["APP_DICE"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
order=90,
|
||||
fap_icon="icon.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets",
|
||||
)
|
||||
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 535 B |
|
After Width: | Height: | Size: 204 B |
|
After Width: | Height: | Size: 546 B |
|
After Width: | Height: | Size: 530 B |
|
After Width: | Height: | Size: 546 B |
|
After Width: | Height: | Size: 535 B |
|
After Width: | Height: | Size: 628 B |
|
After Width: | Height: | Size: 618 B |
|
After Width: | Height: | Size: 630 B |
|
After Width: | Height: | Size: 635 B |
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 531 B |
|
After Width: | Height: | Size: 516 B |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 514 B |
|
After Width: | Height: | Size: 517 B |
|
After Width: | Height: | Size: 523 B |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 523 B |
|
After Width: | Height: | Size: 508 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 376 B |
|
After Width: | Height: | Size: 490 B |
|
After Width: | Height: | Size: 369 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 483 B |
|
After Width: | Height: | Size: 478 B |
|
After Width: | Height: | Size: 467 B |
|
After Width: | Height: | Size: 457 B |
|
After Width: | Height: | Size: 482 B |
|
After Width: | Height: | Size: 444 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 102 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 189 B |
|
After Width: | Height: | Size: 102 B |
|
After Width: | Height: | Size: 182 B |
|
After Width: | Height: | Size: 180 B |
|
After Width: | Height: | Size: 279 B |
@@ -0,0 +1,159 @@
|
||||
#include <gui/icon.h>
|
||||
#include "DND_Dice_app_icons.h"
|
||||
|
||||
#define TAG "DiceApp"
|
||||
|
||||
#define DICE_TYPES 8
|
||||
|
||||
#define MAX_DICE_COUNT 10
|
||||
#define MAX_COIN_FRAMES 9
|
||||
#define MAX_DICE_FRAMES 4
|
||||
|
||||
#define DICE_X 45
|
||||
#define DICE_Y 6
|
||||
#define DICE_Y_T 0
|
||||
|
||||
#define DICE_GAP 44
|
||||
|
||||
#define RESULT_BORDER_X 44
|
||||
#define RESULT_OFFSET 20
|
||||
|
||||
#define SWIPE_DIST 11
|
||||
|
||||
const Icon* coin_heads_start[] = {&I_coin_1, &I_coin_2};
|
||||
const Icon* coin_heads_end[] = {&I_coin_7, &I_coin_1};
|
||||
const Icon* coin_tails_start[] = {&I_coin_5, &I_coin_6};
|
||||
const Icon* coin_tails_end[] = {&I_coin_4, &I_coin_5};
|
||||
const Icon* coin_frames[] = {
|
||||
&I_coin_1,
|
||||
&I_coin_2,
|
||||
&I_coin_3,
|
||||
&I_coin_4,
|
||||
&I_coin_5,
|
||||
&I_coin_6,
|
||||
&I_coin_3,
|
||||
&I_coin_7,
|
||||
&I_coin_1,
|
||||
};
|
||||
|
||||
const int8_t result_frame_pos_y[] = {-30, -20, -10, 0};
|
||||
const Icon* dice_frames[] = {
|
||||
&I_d4_1, &I_d4_2, &I_d4_3, &I_d4_1, // d4
|
||||
&I_d6_1, &I_d6_2, &I_d6_3, &I_d6_4, // d6
|
||||
&I_d8_1, &I_d8_2, &I_d8_3, &I_d8_4, // d8
|
||||
&I_d10_1, &I_d10_2, &I_d10_3, &I_d10_4, // d10
|
||||
&I_d12_1, &I_d12_2, &I_d12_3, &I_d12_4, // d12
|
||||
&I_d20_1, &I_d20_2, &I_d20_3, &I_d20_4, // d20
|
||||
&I_d100_1, &I_d100_2, &I_d100_3, &I_d100_4, // d100
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t type;
|
||||
int x;
|
||||
int y;
|
||||
char* name;
|
||||
} Dice;
|
||||
|
||||
const uint8_t screen_pos[] = {};
|
||||
|
||||
static const Dice dice_types[] = {
|
||||
{2, 0, 0, "Coin"},
|
||||
{4, 0, 0, "d4"},
|
||||
{6, 0, 0, "d6"},
|
||||
{8, 0, 0, "d8"},
|
||||
{10, 0, 0, "d10"},
|
||||
{12, 0, 0, "d12"},
|
||||
{20, 0, 0, "d20"},
|
||||
{100, 0, 0, "d100"},
|
||||
};
|
||||
|
||||
typedef enum { EventTypeTick, EventTypeKey } EventType;
|
||||
typedef enum {
|
||||
SelectState,
|
||||
SwipeLeftState,
|
||||
SwipeRightState,
|
||||
AnimState,
|
||||
AnimResultState,
|
||||
ResultState
|
||||
} AppState;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} AppEvent;
|
||||
|
||||
typedef struct {
|
||||
AppState app_state;
|
||||
uint16_t roll_result;
|
||||
uint8_t rolled_dices[MAX_DICE_COUNT];
|
||||
uint8_t anim_frame;
|
||||
uint8_t dice_index;
|
||||
uint8_t dice_count;
|
||||
int8_t result_pos;
|
||||
Dice dices[DICE_TYPES];
|
||||
} State;
|
||||
|
||||
void init(State* const state) {
|
||||
state->app_state = SelectState;
|
||||
state->roll_result = 0;
|
||||
state->dice_index = 0;
|
||||
state->anim_frame = 0;
|
||||
state->dice_count = 1;
|
||||
|
||||
for(uint8_t i = 0; i < DICE_TYPES; i++) {
|
||||
state->dices[i] = dice_types[i];
|
||||
state->dices[i].x = DICE_X + (i * DICE_GAP);
|
||||
state->dices[i].y = i == 0 ? DICE_Y_T : DICE_Y;
|
||||
}
|
||||
}
|
||||
|
||||
void coin_set_start(uint16_t type) {
|
||||
if(type == 1) {
|
||||
coin_frames[0] = coin_heads_start[0];
|
||||
coin_frames[1] = coin_heads_start[1];
|
||||
} else {
|
||||
coin_frames[0] = coin_tails_start[0];
|
||||
coin_frames[1] = coin_tails_start[1];
|
||||
}
|
||||
}
|
||||
|
||||
void coin_set_end(uint16_t type) {
|
||||
if(type == 1) {
|
||||
coin_frames[MAX_COIN_FRAMES - 2] = coin_heads_end[0];
|
||||
coin_frames[MAX_COIN_FRAMES - 1] = coin_heads_end[1];
|
||||
} else {
|
||||
coin_frames[MAX_COIN_FRAMES - 2] = coin_tails_end[0];
|
||||
coin_frames[MAX_COIN_FRAMES - 1] = coin_tails_end[1];
|
||||
}
|
||||
}
|
||||
|
||||
bool isResultVisible(AppState state, uint8_t dice_index) {
|
||||
return (state == ResultState || state == AnimResultState) && dice_index != 0;
|
||||
}
|
||||
|
||||
bool isDiceNameVisible(AppState state) {
|
||||
return state != SwipeLeftState && state != SwipeRightState;
|
||||
}
|
||||
|
||||
bool isDiceButtonsVisible(AppState state) {
|
||||
return isDiceNameVisible(state) && state != AnimResultState && state != ResultState &&
|
||||
state != AnimState;
|
||||
}
|
||||
|
||||
bool isOneDice(uint8_t dice_index) {
|
||||
return dice_index == 0 || dice_index == 7;
|
||||
}
|
||||
|
||||
bool isDiceSettingsDisabled(AppState state, uint8_t dice_index) {
|
||||
return isOneDice(dice_index) || state == ResultState || state == AnimResultState ||
|
||||
state == AnimState;
|
||||
}
|
||||
|
||||
bool isAnimState(AppState state) {
|
||||
return state == SwipeLeftState || state == SwipeRightState || state == AnimResultState ||
|
||||
state == AnimState;
|
||||
}
|
||||
|
||||
bool isMenuState(AppState state) {
|
||||
return state == SwipeLeftState || state == SwipeRightState || state == SelectState;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
#include <furi.h>
|
||||
#include <input/input.h>
|
||||
#include <gui/gui.h>
|
||||
#include "constants.h"
|
||||
|
||||
const Icon* draw_dice_frame;
|
||||
|
||||
static void update(State* const state) {
|
||||
if(state->app_state == SwipeLeftState) {
|
||||
for(uint8_t i = 0; i < DICE_TYPES; i++) {
|
||||
state->dices[i].x -= SWIPE_DIST;
|
||||
state->dices[i].y = DICE_Y;
|
||||
}
|
||||
|
||||
if(state->dices[state->dice_index].x == DICE_X) {
|
||||
state->app_state = SelectState;
|
||||
state->dices[state->dice_index].y = DICE_Y_T;
|
||||
}
|
||||
|
||||
} else if(state->app_state == SwipeRightState) {
|
||||
for(uint8_t i = 0; i < DICE_TYPES; i++) {
|
||||
state->dices[i].x += SWIPE_DIST;
|
||||
state->dices[i].y = DICE_Y;
|
||||
}
|
||||
|
||||
if(state->dices[state->dice_index].x == DICE_X) {
|
||||
state->app_state = SelectState;
|
||||
state->dices[state->dice_index].y = DICE_Y_T;
|
||||
}
|
||||
} else if(state->app_state == AnimState) {
|
||||
state->anim_frame += 1;
|
||||
|
||||
if(state->dice_index == 0) {
|
||||
if(state->anim_frame == 3) coin_set_start(state->roll_result); // change coin anim
|
||||
|
||||
if(state->anim_frame >= MAX_COIN_FRAMES) {
|
||||
state->anim_frame = 0;
|
||||
state->app_state = AnimResultState;
|
||||
}
|
||||
} else {
|
||||
if(state->anim_frame >= MAX_DICE_FRAMES) {
|
||||
state->anim_frame = 0;
|
||||
state->app_state = AnimResultState;
|
||||
}
|
||||
}
|
||||
} else if(state->app_state == AnimResultState) {
|
||||
if(state->dice_index == 0) { // no extra animations for coin
|
||||
state->anim_frame = 0;
|
||||
state->app_state = ResultState;
|
||||
return;
|
||||
}
|
||||
|
||||
state->result_pos = result_frame_pos_y[state->anim_frame];
|
||||
state->anim_frame += 1;
|
||||
|
||||
// end animation
|
||||
if(state->result_pos == 0) {
|
||||
state->anim_frame = 0;
|
||||
state->app_state = ResultState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void roll(State* const state) {
|
||||
state->roll_result = 0;
|
||||
state->result_pos = result_frame_pos_y[0];
|
||||
|
||||
for(uint8_t i = 0; i < MAX_DICE_COUNT; i++) {
|
||||
if(i < state->dice_count) {
|
||||
state->rolled_dices[i] = (rand() % dice_types[state->dice_index].type) + 1;
|
||||
state->roll_result += state->rolled_dices[i];
|
||||
} else {
|
||||
state->rolled_dices[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(state->dice_index == 0) coin_set_end(state->roll_result); // change coin anim
|
||||
|
||||
state->app_state = AnimState;
|
||||
}
|
||||
|
||||
static void draw_ui(const State* state, Canvas* canvas) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
FuriString* count = furi_string_alloc();
|
||||
furi_string_printf(count, "%01d", state->dice_count);
|
||||
|
||||
// dice name
|
||||
if(isDiceNameVisible(state->app_state)) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 63, 50, AlignCenter, AlignBottom, dice_types[state->dice_index].name);
|
||||
}
|
||||
// dice arrow buttons
|
||||
if(isDiceButtonsVisible(state->app_state)) {
|
||||
if(state->dice_index > 0) canvas_draw_icon(canvas, 45, 44, &I_ui_button_left);
|
||||
if(state->dice_index < DICE_TYPES - 1)
|
||||
canvas_draw_icon(canvas, 78, 44, &I_ui_button_right);
|
||||
}
|
||||
|
||||
// dice count settings
|
||||
if(isDiceSettingsDisabled(state->app_state, state->dice_index))
|
||||
canvas_draw_icon(canvas, 48, 51, &I_ui_count_1);
|
||||
else
|
||||
canvas_draw_icon(canvas, 48, 51, &I_ui_count);
|
||||
canvas_draw_str_aligned(canvas, 58, 61, AlignCenter, AlignBottom, furi_string_get_cstr(count));
|
||||
|
||||
// buttons
|
||||
if(isAnimState(state->app_state) == false) canvas_draw_icon(canvas, 92, 54, &I_ui_button_roll);
|
||||
|
||||
if(state->app_state != AnimResultState && state->app_state != ResultState) {
|
||||
canvas_draw_icon(canvas, 0, 54, &I_ui_button_exit);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 54, &I_ui_button_back);
|
||||
}
|
||||
|
||||
furi_string_free(count);
|
||||
}
|
||||
|
||||
static void draw_dice(const State* state, Canvas* canvas) {
|
||||
if(isMenuState(state->app_state) == false) { // draw only selected dice
|
||||
if(state->dice_index == 0) { // coin
|
||||
draw_dice_frame = coin_frames[state->anim_frame];
|
||||
} else { // dices
|
||||
draw_dice_frame =
|
||||
dice_frames[(state->dice_index - 1) * MAX_DICE_FRAMES + state->anim_frame];
|
||||
}
|
||||
|
||||
canvas_draw_icon(
|
||||
canvas,
|
||||
state->dices[state->dice_index].x,
|
||||
state->dices[state->dice_index].y,
|
||||
draw_dice_frame);
|
||||
return;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < DICE_TYPES; i++) {
|
||||
if(state->app_state == ResultState && state->dice_index == i && state->dice_index != 0)
|
||||
continue; // draw results except coin
|
||||
if(state->dices[i].x > 128 || state->dices[i].x < -35) continue; // outside the screen
|
||||
|
||||
if(i == 0) { // coin
|
||||
draw_dice_frame = coin_frames[0];
|
||||
} else { // dices
|
||||
draw_dice_frame = dice_frames[(i - 1) * MAX_DICE_FRAMES];
|
||||
}
|
||||
|
||||
canvas_draw_icon(canvas, state->dices[i].x, state->dices[i].y, draw_dice_frame);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_results(const State* state, Canvas* canvas) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
FuriString* sum = furi_string_alloc();
|
||||
furi_string_printf(sum, "%01d", state->roll_result);
|
||||
|
||||
// ui frame
|
||||
if(state->app_state == AnimResultState)
|
||||
canvas_draw_icon(canvas, RESULT_BORDER_X, state->result_pos, &I_ui_result_border);
|
||||
else
|
||||
canvas_draw_icon(
|
||||
canvas, RESULT_BORDER_X, result_frame_pos_y[MAX_DICE_FRAMES - 1], &I_ui_result_border);
|
||||
|
||||
// result text
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
64,
|
||||
state->result_pos + RESULT_OFFSET,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
furi_string_get_cstr(sum));
|
||||
|
||||
if(state->app_state == ResultState && isOneDice(state->dice_index) == false) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
FuriString* dices = furi_string_alloc();
|
||||
for(uint8_t i = 0; i < state->dice_count; i++) {
|
||||
furi_string_cat_printf(dices, "%01d", state->rolled_dices[i]);
|
||||
|
||||
if(i != state->dice_count - 1) furi_string_cat_printf(dices, "%s", ", ");
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 63, 37, AlignCenter, AlignCenter, furi_string_get_cstr(dices));
|
||||
furi_string_free(dices);
|
||||
}
|
||||
|
||||
furi_string_free(sum);
|
||||
}
|
||||
|
||||
static void draw_callback(Canvas* canvas, void* ctx) {
|
||||
const State* state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||
if(state == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
draw_ui(state, canvas);
|
||||
|
||||
if(isResultVisible(state->app_state, state->dice_index)) {
|
||||
draw_results(state, canvas);
|
||||
} else {
|
||||
draw_dice(state, canvas);
|
||||
}
|
||||
|
||||
release_mutex((ValueMutex*)ctx, state);
|
||||
}
|
||||
|
||||
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
AppEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
AppEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
int32_t dice_dnd_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
|
||||
|
||||
FURI_LOG_E(TAG, ">>> Started...\r\n");
|
||||
State* state = malloc(sizeof(State));
|
||||
init(state);
|
||||
|
||||
ValueMutex state_mutex;
|
||||
if(!init_mutex(&state_mutex, state, sizeof(State))) {
|
||||
FURI_LOG_E(TAG, "cannot create mutex\r\n");
|
||||
free(state);
|
||||
return 255;
|
||||
}
|
||||
|
||||
// Set callbacks
|
||||
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);
|
||||
|
||||
FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency() * 0.2);
|
||||
|
||||
// Create GUI, register view port
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
AppEvent event;
|
||||
for(bool processing = true; processing;) {
|
||||
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||
State* state = (State*)acquire_mutex_block(&state_mutex);
|
||||
|
||||
if(event_status == FuriStatusOk) {
|
||||
// timer evetn
|
||||
if(event.type == EventTypeTick) {
|
||||
update(state);
|
||||
}
|
||||
// button events
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
// dice type
|
||||
if(isDiceButtonsVisible(state->app_state)) {
|
||||
if(event.input.key == InputKeyRight) {
|
||||
if(state->dice_index < DICE_TYPES - 1) {
|
||||
state->dice_index += 1;
|
||||
state->app_state = SwipeLeftState;
|
||||
}
|
||||
} else if(event.input.key == InputKeyLeft) {
|
||||
if(state->dice_index > 0) {
|
||||
state->dice_index -= 1;
|
||||
state->app_state = SwipeRightState;
|
||||
}
|
||||
}
|
||||
|
||||
if(isOneDice(state->dice_index)) state->dice_count = 1;
|
||||
}
|
||||
// dice count
|
||||
if(isDiceSettingsDisabled(state->app_state, state->dice_index) == false &&
|
||||
isAnimState(state->app_state) == false) {
|
||||
if(event.input.key == InputKeyUp) {
|
||||
if(state->dice_index != 0) {
|
||||
state->dice_count += 1;
|
||||
if(state->dice_count > MAX_DICE_COUNT) {
|
||||
state->dice_count = MAX_DICE_COUNT;
|
||||
}
|
||||
}
|
||||
} else if(event.input.key == InputKeyDown) {
|
||||
state->dice_count -= 1;
|
||||
if(state->dice_count < 1) {
|
||||
state->dice_count = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// roll
|
||||
if(event.input.key == InputKeyOk && isAnimState(state->app_state) == false) {
|
||||
roll(state);
|
||||
}
|
||||
// back to dice select state or quit from app
|
||||
if(event.input.key == InputKeyBack) {
|
||||
if(state->app_state == ResultState ||
|
||||
state->app_state == AnimResultState) {
|
||||
state->anim_frame = 0;
|
||||
state->app_state = SelectState;
|
||||
} else {
|
||||
processing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
release_mutex(&state_mutex, state);
|
||||
}
|
||||
|
||||
// Clear
|
||||
free(state);
|
||||
furi_timer_free(timer);
|
||||
furi_message_queue_free(event_queue);
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(view_port);
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 128 B |