mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-14 00:38:36 -07:00
more apps
This commit is contained in:
12
applications/plugins/calculator/application.fam
Normal file
12
applications/plugins/calculator/application.fam
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
App(
|
||||||
|
appid="APPS_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",
|
||||||
|
)
|
||||||
BIN
applications/plugins/calculator/calc.png
Normal file
BIN
applications/plugins/calculator/calc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
453
applications/plugins/calculator/calculator.c
Normal file
453
applications/plugins/calculator/calculator.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
661
applications/plugins/calculator/tinyexpr.c
Normal file
661
applications/plugins/calculator/tinyexpr.c
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
// 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.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.0 || r < 0.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 = strtod(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);
|
||||||
|
}
|
||||||
87
applications/plugins/calculator/tinyexpr.h
Normal file
87
applications/plugins/calculator/tinyexpr.h
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// 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*/
|
||||||
12
applications/plugins/chess/application.fam
Normal file
12
applications/plugins/chess/application.fam
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
App(
|
||||||
|
appid="INTAPP_Chess",
|
||||||
|
name="Chess",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="chess_app",
|
||||||
|
cdefines=["APP_CHESS"],
|
||||||
|
requires=["storage","gui"],
|
||||||
|
stack_size= 4 * 1024,
|
||||||
|
order=50,
|
||||||
|
fap_icon="chessIcon.png",
|
||||||
|
fap_category="Games",
|
||||||
|
)
|
||||||
682
applications/plugins/chess/chess_app.c
Normal file
682
applications/plugins/chess/chess_app.c
Normal file
@@ -0,0 +1,682 @@
|
|||||||
|
#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_hal_get_tick();
|
||||||
|
thinking = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t make_ai_move(void* 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_hal_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) {
|
||||||
|
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];
|
||||||
|
sprintf(str, "%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_hal_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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
2978
applications/plugins/chess/fast_chess.c
Normal file
2978
applications/plugins/chess/fast_chess.c
Normal file
File diff suppressed because it is too large
Load Diff
388
applications/plugins/chess/fast_chess.h
Normal file
388
applications/plugins/chess/fast_chess.h
Normal file
@@ -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_ */
|
||||||
12
applications/plugins/chip8/application.fam
Normal file
12
applications/plugins/chip8/application.fam
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
App(
|
||||||
|
appid="INTAPP_Chip8",
|
||||||
|
name="CHIP8 Emulator",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="chip8_app",
|
||||||
|
cdefines=["APP_CHIP8"],
|
||||||
|
requires=["gui"],
|
||||||
|
stack_size=4 * 1024,
|
||||||
|
order=60,
|
||||||
|
fap_icon="chip8Icon.png",
|
||||||
|
fap_category="Misc",
|
||||||
|
)
|
||||||
203
applications/plugins/chip8/chip8.c
Normal file
203
applications/plugins/chip8/chip8.c
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
#include <lib/toolbox/args.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include "chip8.h"
|
||||||
|
#include "emulator_core/flipper_chip.h"
|
||||||
|
|
||||||
|
#define TAG "Chip8Emulator"
|
||||||
|
#define WORKER_TAG TAG "Worker"
|
||||||
|
#define FILE_BUFFER_LEN 16
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WorkerEvtToggle = (1 << 1),
|
||||||
|
WorkerEvtEnd = (1 << 2),
|
||||||
|
} WorkerEvtFlags;
|
||||||
|
|
||||||
|
struct Chip8Emulator {
|
||||||
|
Chip8State st;
|
||||||
|
string_t file_path;
|
||||||
|
FuriThread* thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int32_t chip8_worker(void* context) {
|
||||||
|
Chip8Emulator* chip8 = context;
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Start furi record open");
|
||||||
|
Storage* furi_storage_record = furi_record_open(RECORD_STORAGE);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "furi record opened");
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Start storage file alloc");
|
||||||
|
File* rom_file = storage_file_alloc(furi_storage_record);
|
||||||
|
FURI_LOG_I(
|
||||||
|
WORKER_TAG, "Start storage file open, path = %s", string_get_cstr(chip8->file_path));
|
||||||
|
|
||||||
|
uint8_t* rom_data = malloc(4096);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "4096 array gotten");
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if(chip8->st.worker_state == WorkerStateBackPressed) {
|
||||||
|
FURI_LOG_I(WORKER_TAG, "WorkerStateBackPressed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chip8->st.worker_state == WorkerStateLoadingRom) {
|
||||||
|
bool is_file_opened = storage_file_open(
|
||||||
|
rom_file, string_get_cstr(chip8->file_path), FSAM_READ, FSOM_OPEN_EXISTING);
|
||||||
|
|
||||||
|
if(!is_file_opened) {
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Cannot open storage");
|
||||||
|
storage_file_close(rom_file);
|
||||||
|
storage_file_free(rom_file);
|
||||||
|
chip8->st.worker_state = WorkerStateRomLoadError;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "File was opened, try read this");
|
||||||
|
|
||||||
|
int rom_len = read_rom_data(rom_file, rom_data);
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Rom data finished reading");
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Load chip8 core data");
|
||||||
|
t_chip8_load_game(chip8->st.t_chip8_state, rom_data, rom_len);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "chip8 core data loaded");
|
||||||
|
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Wipe screen start");
|
||||||
|
for(int i = 0; i < CHIP8_SCREEN_H; i++) {
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Wipe screen line %d", i);
|
||||||
|
for(int j = 0; j < CHIP8_SCREEN_W; j++) {
|
||||||
|
chip8->st.t_chip8_state->screen[i][j] = 0;
|
||||||
|
}
|
||||||
|
furi_delay_ms(15);
|
||||||
|
}
|
||||||
|
FURI_LOG_I(WORKER_TAG, "Wipe screen end");
|
||||||
|
|
||||||
|
chip8->st.worker_state = WorkerStateRomLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chip8->st.worker_state == WorkerStateRomLoaded) {
|
||||||
|
if(chip8->st.t_chip8_state->go_render) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
t_chip8_execute_next_opcode(chip8->st.t_chip8_state);
|
||||||
|
FURI_LOG_I(
|
||||||
|
"chip8_executing",
|
||||||
|
"current: 0x%X next: 0x%X",
|
||||||
|
chip8->st.t_chip8_state->current_opcode,
|
||||||
|
chip8->st.t_chip8_state->next_opcode);
|
||||||
|
furi_delay_ms(2);
|
||||||
|
//t_chip8_tick(chip8->st.t_chip8_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I("CHIP8", "Prepare to ending app");
|
||||||
|
storage_file_close(rom_file);
|
||||||
|
storage_file_free(rom_file);
|
||||||
|
t_chip8_free_memory(chip8->st.t_chip8_state, free);
|
||||||
|
FURI_LOG_I("CHIP8", "End ending");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chip8Emulator* chip8_make_emulator(string_t file_path) {
|
||||||
|
furi_assert(file_path);
|
||||||
|
FURI_LOG_I("CHIP8", "make emulator, file_path=", string_get_cstr(file_path));
|
||||||
|
|
||||||
|
Chip8Emulator* chip8 = malloc(sizeof(Chip8Emulator));
|
||||||
|
string_init(chip8->file_path);
|
||||||
|
string_set(chip8->file_path, file_path);
|
||||||
|
chip8->st.worker_state = WorkerStateLoadingRom;
|
||||||
|
chip8->st.t_chip8_state = t_chip8_init(malloc);
|
||||||
|
|
||||||
|
// FURI_LOG_I(WORKER_TAG, "Start wipe screen");
|
||||||
|
// furi_delay_ms(1500);
|
||||||
|
// for (int i = 0; i < CHIP8_SCREEN_H; i++)
|
||||||
|
// {
|
||||||
|
// FURI_LOG_I(WORKER_TAG, "Start wipe line %d", i);
|
||||||
|
// for (int j = 0; j < CHIP8_SCREEN_W; j++)
|
||||||
|
// {
|
||||||
|
// chip8->st.t_chip8_state->screen[i][j] = 0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// FURI_LOG_I(WORKER_TAG, "End wipe screen");
|
||||||
|
|
||||||
|
chip8->thread = furi_thread_alloc();
|
||||||
|
furi_thread_set_name(chip8->thread, "Chip8Worker");
|
||||||
|
furi_thread_set_stack_size(chip8->thread, 4096);
|
||||||
|
furi_thread_set_context(chip8->thread, chip8);
|
||||||
|
furi_thread_set_callback(chip8->thread, chip8_worker);
|
||||||
|
|
||||||
|
furi_thread_start(chip8->thread);
|
||||||
|
return chip8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_close_emulator(Chip8Emulator* chip8) {
|
||||||
|
FURI_LOG_I("chip_8_close_emulator", "start");
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(chip8->thread), WorkerEvtEnd);
|
||||||
|
furi_thread_join(chip8->thread);
|
||||||
|
furi_thread_free(chip8->thread);
|
||||||
|
string_clear(chip8->file_path);
|
||||||
|
free(chip8);
|
||||||
|
FURI_LOG_I("chip_8_close_emulator", "end");
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_toggle(Chip8Emulator* chip8) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_thread_flags_set(furi_thread_get_id(chip8->thread), WorkerEvtToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Chip8State* chip8_get_state(Chip8Emulator* chip8) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
return &(chip8->st);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t read_rom_data(File* file, uint8_t* data) {
|
||||||
|
furi_assert(file);
|
||||||
|
furi_assert(data);
|
||||||
|
|
||||||
|
const uint8_t buffer_size = 32;
|
||||||
|
uint16_t file_pointer = 0;
|
||||||
|
uint8_t buff[buffer_size];
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
uint16_t bytes_were_read = storage_file_read(file, buff, buffer_size);
|
||||||
|
|
||||||
|
if(bytes_were_read == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint16_t i = 0; i < bytes_were_read; i++) {
|
||||||
|
data[file_pointer] = buff[i];
|
||||||
|
file_pointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_back_pressed(Chip8Emulator* chip8) {
|
||||||
|
chip8->st.worker_state = WorkerStateBackPressed;
|
||||||
|
chip8->st.t_chip8_state->go_render = true;
|
||||||
|
FURI_LOG_I(WORKER_TAG, "SET BACK PRESSED. EMULATION IS STOPPED");
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_up_pressed(Chip8Emulator* chip8) {
|
||||||
|
chip8->st.t_chip8_state->go_render = true;
|
||||||
|
t_chip8_set_input(chip8->st.t_chip8_state, k_1);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "UP PRESSED");
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_down_pressed(Chip8Emulator* chip8) {
|
||||||
|
chip8->st.t_chip8_state->go_render = true;
|
||||||
|
t_chip8_set_input(chip8->st.t_chip8_state, k_4);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "DOWN PRESSED");
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_release_keyboard(Chip8Emulator* chip8) {
|
||||||
|
chip8->st.t_chip8_state->go_render = true;
|
||||||
|
t_chip8_release_input(chip8->st.t_chip8_state);
|
||||||
|
FURI_LOG_I(WORKER_TAG, "chip8_release_keyboard Release input");
|
||||||
|
}
|
||||||
45
applications/plugins/chip8/chip8.h
Normal file
45
applications/plugins/chip8/chip8.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <m-string.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include "emulator_core/flipper_chip.h"
|
||||||
|
|
||||||
|
#define CHIP8_SCREEN_W 64
|
||||||
|
#define CHIP8_SCREEN_H 32
|
||||||
|
|
||||||
|
typedef struct Chip8Emulator Chip8Emulator;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WorkerStateLoadingRom,
|
||||||
|
WorkerStateRomLoaded,
|
||||||
|
WorkerStateRomLoadError,
|
||||||
|
WorkerStateBackPressed,
|
||||||
|
} WorkerState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
WorkerState worker_state;
|
||||||
|
t_chip8_state* t_chip8_state;
|
||||||
|
} Chip8State;
|
||||||
|
|
||||||
|
Chip8Emulator* chip8_make_emulator(string_t file_path);
|
||||||
|
|
||||||
|
void chip8_close_emulator(Chip8Emulator* chip8);
|
||||||
|
void chip8_set_back_pressed(Chip8Emulator* chip8);
|
||||||
|
void chip8_set_up_pressed(Chip8Emulator* chip8);
|
||||||
|
void chip8_set_down_pressed(Chip8Emulator* chip8);
|
||||||
|
void chip8_release_keyboard(Chip8Emulator* chip8);
|
||||||
|
|
||||||
|
Chip8State* chip8_get_state(Chip8Emulator* chip8);
|
||||||
|
|
||||||
|
void chip8_toggle(Chip8Emulator* chip8);
|
||||||
|
|
||||||
|
uint16_t read_rom_data(File* file, uint8_t* data);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
89
applications/plugins/chip8/chip8_app.c
Normal file
89
applications/plugins/chip8/chip8_app.c
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include "chip8_app_i.h"
|
||||||
|
#include <furi.h>
|
||||||
|
|
||||||
|
static bool chip8_app_custom_event_callback(void* context, uint32_t event) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool chip8_app_back_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
return scene_manager_handle_back_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void chip8_app_tick_event_callback(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
scene_manager_handle_tick_event(app->scene_manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t** chip8_backup_screen_alloc() {
|
||||||
|
FURI_LOG_I("chip8", "chip8_backup_screen_alloc start");
|
||||||
|
|
||||||
|
uint8_t** backup_screen = malloc(SCREEN_HEIGHT * sizeof(size_t));
|
||||||
|
for(int i = 0; i < SCREEN_HEIGHT; i++) {
|
||||||
|
backup_screen[i] = malloc(SCREEN_WIDTH * sizeof(uint8_t));
|
||||||
|
for(int j = 0; j < SCREEN_WIDTH; j++) {
|
||||||
|
backup_screen[i][j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FURI_LOG_I("chip8", "chip8_backup_screen_alloc end");
|
||||||
|
return backup_screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chip8App* chip8_app_alloc() {
|
||||||
|
Chip8App* app = malloc(sizeof(Chip8App));
|
||||||
|
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
app->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
app->scene_manager = scene_manager_alloc(&chip8_scene_handlers, app);
|
||||||
|
|
||||||
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||||
|
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||||
|
|
||||||
|
view_dispatcher_set_tick_event_callback(
|
||||||
|
app->view_dispatcher, chip8_app_tick_event_callback, 100);
|
||||||
|
view_dispatcher_set_custom_event_callback(
|
||||||
|
app->view_dispatcher, chip8_app_custom_event_callback);
|
||||||
|
view_dispatcher_set_navigation_event_callback(
|
||||||
|
app->view_dispatcher, chip8_app_back_event_callback);
|
||||||
|
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
app->chip8_view = chip8_alloc();
|
||||||
|
app->backup_screen = chip8_backup_screen_alloc();
|
||||||
|
view_dispatcher_add_view(app->view_dispatcher, Chip8WorkView, chip8_get_view(app->chip8_view));
|
||||||
|
|
||||||
|
scene_manager_next_scene(app->scene_manager, Chip8FileSelectView);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_app_free(Chip8App* app) {
|
||||||
|
FURI_LOG_I("CHIP8", "chip8_app_free started");
|
||||||
|
furi_assert(app);
|
||||||
|
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, Chip8FileSelectView);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, Chip8WorkView);
|
||||||
|
chip8_free(app->chip8_view);
|
||||||
|
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
scene_manager_free(app->scene_manager);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t chip8_app(void* p) {
|
||||||
|
Chip8App* chip8_app = chip8_app_alloc();
|
||||||
|
|
||||||
|
view_dispatcher_run(chip8_app->view_dispatcher);
|
||||||
|
chip8_app_free(chip8_app);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
11
applications/plugins/chip8/chip8_app.h
Normal file
11
applications/plugins/chip8/chip8_app.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct Chip8App Chip8App;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
33
applications/plugins/chip8/chip8_app_i.h
Normal file
33
applications/plugins/chip8/chip8_app_i.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "chip8_app.h"
|
||||||
|
#include "scenes/chip8_scene.h"
|
||||||
|
#include "chip8.h"
|
||||||
|
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
#include <gui/modules/variable_item_list.h>
|
||||||
|
#include "views/chip8_view.h"
|
||||||
|
|
||||||
|
#define CHIP8_APP_PATH_FOLDER "/any/chip8"
|
||||||
|
#define CHIP8_APP_EXTENSION ".ch8"
|
||||||
|
|
||||||
|
struct Chip8App {
|
||||||
|
Gui* gui;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
SceneManager* scene_manager;
|
||||||
|
DialogsApp* dialogs;
|
||||||
|
|
||||||
|
string_t file_name;
|
||||||
|
uint8_t** backup_screen;
|
||||||
|
Chip8View* chip8_view;
|
||||||
|
Chip8Emulator* chip8;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
Chip8FileSelectView,
|
||||||
|
Chip8WorkView,
|
||||||
|
} Chip8AppView;
|
||||||
319
applications/plugins/chip8/emulator_core/flipper_chip.c
Normal file
319
applications/plugins/chip8/emulator_core/flipper_chip.c
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
//
|
||||||
|
// Created by dwdraugr on 24.11.2021.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "flipper_chip.h"
|
||||||
|
#include "flipper_fonts.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static uint8_t randbyte();
|
||||||
|
static void draw_sprite(t_chip8_state* state, uint8_t x, uint8_t y, uint8_t n);
|
||||||
|
static void error_stop(t_chip8_state* state, uint16_t opcode);
|
||||||
|
|
||||||
|
t_chip8_state* t_chip8_init(void* (*system_malloc)(size_t)) {
|
||||||
|
t_chip8_state* state = system_malloc(sizeof(t_chip8_state));
|
||||||
|
|
||||||
|
state->PC = MEMORY_START_POSITION;
|
||||||
|
state->SP = 0;
|
||||||
|
state->I = 0;
|
||||||
|
state->delay_timer = 0;
|
||||||
|
state->sound_timer = 0;
|
||||||
|
state->go_render = false;
|
||||||
|
state->next_opcode = 0;
|
||||||
|
|
||||||
|
state->memory = system_malloc(MEMORY_SIZE * sizeof(uint8_t));
|
||||||
|
// memset(state->memory, 0, MEMORY_SIZE);
|
||||||
|
state->screen = system_malloc(SCREEN_HEIGHT * sizeof(size_t));
|
||||||
|
for(int i = 0; i < SCREEN_HEIGHT; i++) {
|
||||||
|
state->screen[i] = system_malloc(SCREEN_WIDTH * sizeof(uint8_t));
|
||||||
|
// memset(state->screen[i], 0, SCREEN_WIDTH);
|
||||||
|
}
|
||||||
|
state->V = system_malloc(CPU_REGISTER_NUMBER * sizeof(uint8_t));
|
||||||
|
// memset(state->V, 0, CPU_REGISTER_NUMBER);
|
||||||
|
state->stack = system_malloc(CPU_STACK_DEPTH * sizeof(uint16_t));
|
||||||
|
// memset(state->stack, 0, CPU_STACK_DEPTH * sizeof(short));
|
||||||
|
state->key = system_malloc(KEYS_NUMBER * sizeof(uint8_t));
|
||||||
|
// memset(state->key, 0, KEYS_NUMBER);
|
||||||
|
|
||||||
|
memcpy(state->memory, font_small, FONT_SMALL);
|
||||||
|
srand(time(NULL));
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool t_chip8_load_game(t_chip8_state* state, const uint8_t* rom, int rom_size) {
|
||||||
|
if(MEMORY_ROM_SIZE < rom_size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(&state->memory[MEMORY_START_POSITION], rom, rom_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void t_chip8_free_memory(t_chip8_state* state, void (*system_free)(void*)) {
|
||||||
|
system_free(state->memory);
|
||||||
|
for(int i = 0; i < SCREEN_HEIGHT; i++) {
|
||||||
|
system_free(state->screen[i]);
|
||||||
|
}
|
||||||
|
system_free(state->screen);
|
||||||
|
system_free(state->V);
|
||||||
|
system_free(state->key);
|
||||||
|
system_free(state->stack);
|
||||||
|
system_free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void t_chip8_execute_next_opcode(t_chip8_state* state) {
|
||||||
|
static bool isWaitInput = false;
|
||||||
|
static uint8_t register_number = 255;
|
||||||
|
|
||||||
|
uint16_t opcode = state->memory[state->PC] << 8 | state->memory[state->PC + 1];
|
||||||
|
uint8_t x = (opcode >> 8) & 0x000F;
|
||||||
|
uint8_t y = (opcode >> 4) & 0x000F;
|
||||||
|
uint8_t n = opcode & 0x000F;
|
||||||
|
uint8_t kk = opcode & 0x00FF;
|
||||||
|
uint16_t nnn = opcode & 0x0FFF;
|
||||||
|
|
||||||
|
// jump to input-wait opcode
|
||||||
|
if(isWaitInput) {
|
||||||
|
opcode = 0xF000;
|
||||||
|
kk = 0x0A;
|
||||||
|
x = register_number;
|
||||||
|
}
|
||||||
|
state->current_opcode = opcode & 0xF000;
|
||||||
|
switch(opcode & 0xF000) {
|
||||||
|
case 0x0000:
|
||||||
|
switch(kk) {
|
||||||
|
case 0x00E0:
|
||||||
|
for(int i = 0; i < SCREEN_HEIGHT; i++) {
|
||||||
|
for(int j = 0; j < SCREEN_WIDTH; j++) {
|
||||||
|
state->screen[i][j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x00EE:
|
||||||
|
state->PC = state->stack[--state->SP];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_stop(state, opcode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x1000:
|
||||||
|
state->PC = nnn;
|
||||||
|
break;
|
||||||
|
case 0x2000:
|
||||||
|
state->stack[state->SP++] = state->PC + 2;
|
||||||
|
state->PC = nnn;
|
||||||
|
break;
|
||||||
|
case 0x3000:
|
||||||
|
state->PC += (state->V[x] == kk) ? 4 : 2;
|
||||||
|
break;
|
||||||
|
case 0x4000:
|
||||||
|
state->PC += (state->V[x] != kk) ? 4 : 2;
|
||||||
|
break;
|
||||||
|
case 0x5000:
|
||||||
|
state->PC += (state->V[x] == state->V[y]) ? 4 : 2;
|
||||||
|
break;
|
||||||
|
case 0x6000:
|
||||||
|
state->V[x] = kk;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x7000:
|
||||||
|
state->V[x] += kk;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x8000:
|
||||||
|
switch(n) {
|
||||||
|
case 0x0:
|
||||||
|
state->V[x] = state->V[y];
|
||||||
|
break;
|
||||||
|
case 0x1:
|
||||||
|
state->V[x] |= state->V[y];
|
||||||
|
break;
|
||||||
|
case 0x2:
|
||||||
|
state->V[x] &= state->V[y];
|
||||||
|
break;
|
||||||
|
case 0x3:
|
||||||
|
state->V[x] ^= state->V[y];
|
||||||
|
break;
|
||||||
|
case 0x4:
|
||||||
|
state->V[0xF] = (int)state->V[x] + (int)state->V[y] ? 1 : 0;
|
||||||
|
state->V[x] += state->V[y];
|
||||||
|
break;
|
||||||
|
case 0x5:
|
||||||
|
state->V[0xF] = state->V[x] > state->V[y] ? 1 : 0;
|
||||||
|
state->V[x] -= state->V[y];
|
||||||
|
break;
|
||||||
|
case 0x6:
|
||||||
|
state->V[0xF] = state->V[x] & 0x1;
|
||||||
|
state->V[x] >>= 1;
|
||||||
|
break;
|
||||||
|
case 0x7:
|
||||||
|
state->V[0xF] = state->V[y] > state->V[x] ? 1 : 0;
|
||||||
|
state->V[x] = state->V[y] - state->V[x];
|
||||||
|
break;
|
||||||
|
case 0xE:
|
||||||
|
state->V[0xF] = (state->V[x] >> 7) & 0x1;
|
||||||
|
state->V[x] <<= 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_stop(state, opcode);
|
||||||
|
}
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x9000:
|
||||||
|
switch(n) {
|
||||||
|
case 0x0:
|
||||||
|
state->PC += state->V[x] != state->V[y] ? 4 : 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_stop(state, opcode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xA000:
|
||||||
|
state->I = nnn;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0xB000:
|
||||||
|
state->PC = nnn + state->V[0];
|
||||||
|
break;
|
||||||
|
case 0xC000:
|
||||||
|
state->V[x] = randbyte() & kk;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0xD000:
|
||||||
|
draw_sprite(state, state->V[x], state->V[y], n);
|
||||||
|
state->go_render = true;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0xE000:
|
||||||
|
switch(kk) {
|
||||||
|
case 0x9E:
|
||||||
|
state->PC += state->key[state->V[x]] ? 4 : 2;
|
||||||
|
break;
|
||||||
|
case 0xA1:
|
||||||
|
state->PC += !state->key[state->V[x]] ? 4 : 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_stop(state, opcode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0xF000:
|
||||||
|
switch(kk) {
|
||||||
|
case 0x07:
|
||||||
|
state->V[x] = state->delay_timer;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x0A:
|
||||||
|
for(int i = 0; i < KEYS_NUMBER; i++) {
|
||||||
|
if(state->key[i]) {
|
||||||
|
state->V[x] = i;
|
||||||
|
isWaitInput = false;
|
||||||
|
goto exit_input_wait;
|
||||||
|
}
|
||||||
|
isWaitInput = true;
|
||||||
|
register_number = x;
|
||||||
|
}
|
||||||
|
exit_input_wait:
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x15:
|
||||||
|
state->delay_timer = state->V[x];
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x18:
|
||||||
|
state->sound_timer = state->V[x];
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x1E:
|
||||||
|
state->V[0xF] = state->I + state->V[x] > 0xFFF ? 1 : 0;
|
||||||
|
state->I += state->V[x];
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x29:
|
||||||
|
state->I = FONT_BYTES_PER_CHAR * state->V[x];
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x33:
|
||||||
|
state->memory[state->I] = (state->V[x] % 1000) / 100;
|
||||||
|
state->memory[state->I + 1] = (state->V[x] % 100) / 10;
|
||||||
|
state->memory[state->I + 2] = state->V[x] % 10;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x55:
|
||||||
|
memcpy(state->memory, state->V, x);
|
||||||
|
state->I += x + 1;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
case 0x65:
|
||||||
|
for(int i = 0; i <= x; i++) {
|
||||||
|
state->V[i] = state->memory[state->I + i];
|
||||||
|
}
|
||||||
|
state->I += x + 1;
|
||||||
|
state->PC += 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_stop(state, opcode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error_stop(state, opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
state->next_opcode = state->memory[state->PC] << 8 | state->memory[state->PC + 1];
|
||||||
|
state->next_opcode &= 0xf000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void t_chip8_tick(t_chip8_state* state) {
|
||||||
|
if(state->delay_timer > 0) {
|
||||||
|
--state->delay_timer;
|
||||||
|
}
|
||||||
|
if(state->sound_timer > 0) {
|
||||||
|
--state->sound_timer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t** t_chip8_get_screen(t_chip8_state* state) {
|
||||||
|
return (uint8_t**)state->screen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void t_chip8_set_input(t_chip8_state* state, t_keys key) {
|
||||||
|
state->key[key] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void t_chip8_release_input(t_chip8_state* state) {
|
||||||
|
for(int i = 0; i < KEYS_NUMBER; i++) {
|
||||||
|
state->key[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t randbyte() {
|
||||||
|
return rand() % 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_sprite(t_chip8_state* state, uint8_t x, uint8_t y, uint8_t n) {
|
||||||
|
unsigned row = y, col = x;
|
||||||
|
unsigned byte_index;
|
||||||
|
unsigned bit_index;
|
||||||
|
|
||||||
|
state->V[0xF] = 0;
|
||||||
|
for(byte_index = 0; byte_index < n; byte_index++) {
|
||||||
|
uint8_t byte = state->memory[state->I + byte_index];
|
||||||
|
|
||||||
|
for(bit_index = 0; bit_index < 8; bit_index++) {
|
||||||
|
uint8_t bit = (byte >> bit_index) & 0x1;
|
||||||
|
|
||||||
|
uint8_t* pixel_pointer = &state->screen[(row + byte_index) % SCREEN_HEIGHT]
|
||||||
|
[(col + (7 - bit_index)) % SCREEN_WIDTH];
|
||||||
|
|
||||||
|
if(bit == 1 && *pixel_pointer == 1) state->V[0xF] = 1;
|
||||||
|
*pixel_pointer = *pixel_pointer ^ bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void error_stop(t_chip8_state* state, uint16_t opcode) {
|
||||||
|
exit(100);
|
||||||
|
}
|
||||||
74
applications/plugins/chip8/emulator_core/flipper_chip.h
Normal file
74
applications/plugins/chip8/emulator_core/flipper_chip.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// Created by dwdraugr on 24.11.2021.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FLIPPER_CHIP_FLIPPER_CHIP_H
|
||||||
|
#define FLIPPER_CHIP_FLIPPER_CHIP_H
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define CPU_REGISTER_NUMBER 0x10
|
||||||
|
#define CPU_STACK_DEPTH 0x10
|
||||||
|
|
||||||
|
#define MEMORY_SIZE 0x1000
|
||||||
|
#define MEMORY_START_POSITION 0x200
|
||||||
|
#define MEMORY_ROM_SIZE (MEMORY_SIZE - MEMORY_START_POSITION)
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 64
|
||||||
|
#define SCREEN_HEIGHT 32
|
||||||
|
#define SCREEN_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT)
|
||||||
|
|
||||||
|
#define KEYS_NUMBER 0x10
|
||||||
|
|
||||||
|
typedef struct s_flipper_state {
|
||||||
|
uint16_t I;
|
||||||
|
uint16_t PC;
|
||||||
|
uint8_t SP;
|
||||||
|
uint8_t* V; // CPU_REGISTER_NUMBER
|
||||||
|
uint16_t* stack; // CPU_STACK_DEPTH
|
||||||
|
uint8_t* memory; // MEMORY_SIZE
|
||||||
|
uint8_t delay_timer;
|
||||||
|
uint8_t sound_timer;
|
||||||
|
uint8_t** screen; // SCREEN_HEIGHT * SCREEN_WIDTH
|
||||||
|
uint8_t* key; // KEYS_NUMBER
|
||||||
|
bool go_render;
|
||||||
|
int current_opcode;
|
||||||
|
int next_opcode;
|
||||||
|
} t_chip8_state;
|
||||||
|
|
||||||
|
// keyboard layout
|
||||||
|
// 1 2 3 C
|
||||||
|
// 4 5 6 D
|
||||||
|
// 7 8 9 E
|
||||||
|
// A 0 B F
|
||||||
|
typedef enum e_keys {
|
||||||
|
k_x,
|
||||||
|
k_1,
|
||||||
|
k_2,
|
||||||
|
k_3,
|
||||||
|
k_q,
|
||||||
|
k_w,
|
||||||
|
k_e,
|
||||||
|
k_a,
|
||||||
|
k_s,
|
||||||
|
k_d,
|
||||||
|
k_z,
|
||||||
|
k_c,
|
||||||
|
k_4,
|
||||||
|
k_r,
|
||||||
|
k_f,
|
||||||
|
k_v,
|
||||||
|
} t_keys;
|
||||||
|
|
||||||
|
t_chip8_state* t_chip8_init(void* (*system_malloc)(size_t));
|
||||||
|
bool t_chip8_load_game(t_chip8_state* state, const uint8_t* rom, int rom_size);
|
||||||
|
void t_chip8_execute_next_opcode(t_chip8_state* state);
|
||||||
|
void t_chip8_tick(t_chip8_state* state);
|
||||||
|
uint8_t** t_chip8_get_screen(t_chip8_state* state);
|
||||||
|
void t_chip8_free_memory(t_chip8_state* state, void (*system_free)(void*));
|
||||||
|
void t_chip8_set_input(t_chip8_state* state, t_keys key);
|
||||||
|
void t_chip8_release_input(t_chip8_state* state);
|
||||||
|
|
||||||
|
#endif //FLIPPER_CHIP_FLIPPER_CHIP_H
|
||||||
30
applications/plugins/chip8/emulator_core/flipper_fonts.h
Normal file
30
applications/plugins/chip8/emulator_core/flipper_fonts.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
//
|
||||||
|
// Created by dwdraugr on 24.11.2021.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef FLIPPER_CHIP_FLIPPER_FONTS_H
|
||||||
|
#define FLIPPER_CHIP_FLIPPER_FONTS_H
|
||||||
|
|
||||||
|
#define FONT_BYTES_PER_CHAR 5
|
||||||
|
#define FONT_SMALL (16 * 5)
|
||||||
|
|
||||||
|
unsigned char font_small[FONT_SMALL] = {
|
||||||
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
||||||
|
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
||||||
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
||||||
|
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
||||||
|
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
||||||
|
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
||||||
|
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
||||||
|
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
||||||
|
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
||||||
|
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
||||||
|
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
||||||
|
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
||||||
|
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
||||||
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
||||||
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
||||||
|
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FLIPPER_CHIP_FLIPPER_FONTS_H
|
||||||
26
applications/plugins/chip8/scenes/chip8_scene.c
Normal file
26
applications/plugins/chip8/scenes/chip8_scene.c
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "chip8_scene.h"
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||||
|
void (*const chip8_scene_on_enter_handlers[])(void*) = {
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||||
|
bool (*const chip8_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||||
|
void (*const chip8_scene_on_exit_handlers[])(void* context) = {
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
};
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
const SceneManagerHandlers chip8_scene_handlers = {
|
||||||
|
.on_enter_handlers = chip8_scene_on_enter_handlers,
|
||||||
|
.on_event_handlers = chip8_scene_on_event_handlers,
|
||||||
|
.on_exit_handlers = chip8_scene_on_exit_handlers,
|
||||||
|
.scene_num = Chip8SceneNum,
|
||||||
|
};
|
||||||
25
applications/plugins/chip8/scenes/chip8_scene.h
Normal file
25
applications/plugins/chip8/scenes/chip8_scene.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/scene_manager.h>
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) Chip8Scene##id,
|
||||||
|
typedef enum {
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
Chip8SceneNum,
|
||||||
|
} Chip8Scene;
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
extern const SceneManagerHandlers chip8_scene_handlers;
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) \
|
||||||
|
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
|
|
||||||
|
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||||
|
#include "chip8_scene_config.h"
|
||||||
|
#undef ADD_SCENE
|
||||||
2
applications/plugins/chip8/scenes/chip8_scene_config.h
Normal file
2
applications/plugins/chip8/scenes/chip8_scene_config.h
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ADD_SCENE(chip8, file_select, FileSelect)
|
||||||
|
ADD_SCENE(chip8, work, Work)
|
||||||
45
applications/plugins/chip8/scenes/chip8_scene_file_select.c
Normal file
45
applications/plugins/chip8/scenes/chip8_scene_file_select.c
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "../chip8_app_i.h"
|
||||||
|
#include "furi_hal_power.h"
|
||||||
|
|
||||||
|
static bool chip8_file_select(Chip8App* chip8) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
string_init(chip8->file_name);
|
||||||
|
string_set_str(chip8->file_name, CHIP8_APP_PATH_FOLDER);
|
||||||
|
// string_set_str(file_path, chip8->file_name);
|
||||||
|
|
||||||
|
bool res = dialog_file_browser_show(
|
||||||
|
chip8->dialogs,
|
||||||
|
chip8->file_name,
|
||||||
|
chip8->file_name,
|
||||||
|
CHIP8_APP_EXTENSION,
|
||||||
|
true,
|
||||||
|
&I_unknown_10px,
|
||||||
|
false);
|
||||||
|
|
||||||
|
FURI_LOG_I(
|
||||||
|
"Chip8_file_browser_show", "chip8->file_name: %s", string_get_cstr(chip8->file_name));
|
||||||
|
FURI_LOG_I("Chip8_file_browser_show", "res: %d", res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_file_select_on_enter(void* context) {
|
||||||
|
Chip8App* chip8 = context;
|
||||||
|
|
||||||
|
if(chip8_file_select(chip8)) {
|
||||||
|
FURI_LOG_I(
|
||||||
|
"Chip8", "chip8_file_select, file_name = %s", string_get_cstr(chip8->file_name));
|
||||||
|
scene_manager_next_scene(chip8->scene_manager, Chip8WorkView);
|
||||||
|
} else {
|
||||||
|
view_dispatcher_stop(chip8->view_dispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool chip8_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
UNUSED(context);
|
||||||
|
UNUSED(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_file_select_on_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
}
|
||||||
90
applications/plugins/chip8/scenes/chip8_scene_work.c
Normal file
90
applications/plugins/chip8/scenes/chip8_scene_work.c
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include "../chip8_app_i.h"
|
||||||
|
#include "../views/chip8_view.h"
|
||||||
|
#include "furi_hal.h"
|
||||||
|
|
||||||
|
void chip8_scene_ok_callback(InputType type, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
view_dispatcher_send_custom_event(app->view_dispatcher, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_back_callback(Chip8View* view, InputType type, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
chip8_set_back_pressed(app->chip8);
|
||||||
|
chip8_set_state(view, chip8_get_state(app->chip8));
|
||||||
|
// view_dispatcher_send_custom_event(app->view_dispatcher, type);
|
||||||
|
view_dispatcher_stop(app->view_dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_up_callback(InputType type, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
chip8_set_up_pressed(app->chip8);
|
||||||
|
// view_dispatcher_send_custom_event(app->view_dispatcher, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_down_callback(InputType type, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
chip8_set_down_pressed(app->chip8);
|
||||||
|
// view_dispatcher_send_custom_event(app->view_dispatcher, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_release_callback(InputType type, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8App* app = context;
|
||||||
|
chip8_release_keyboard(app->chip8);
|
||||||
|
// view_dispatcher_send_custom_event(app->view_dispatcher, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool chip8_scene_work_on_event(void* context, SceneManagerEvent event) {
|
||||||
|
Chip8App* app = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
chip8_toggle(app->chip8);
|
||||||
|
consumed = true;
|
||||||
|
} else if(event.type == SceneManagerEventTypeTick) {
|
||||||
|
Chip8State* state = chip8_get_state(app->chip8);
|
||||||
|
|
||||||
|
chip8_set_state(app->chip8_view, state);
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_work_on_enter(void* context) {
|
||||||
|
Chip8App* app = context;
|
||||||
|
|
||||||
|
chip8_set_file_name(app->chip8_view, app->file_name);
|
||||||
|
|
||||||
|
string_t file_tmp;
|
||||||
|
string_init(file_tmp);
|
||||||
|
|
||||||
|
string_printf(file_tmp, "%s", string_get_cstr(app->file_name));
|
||||||
|
|
||||||
|
FURI_LOG_I("chip8_scene_work_on_enter", "file_name: %s", string_get_cstr(file_tmp));
|
||||||
|
|
||||||
|
FURI_LOG_I("chip8_scene_work_on_enter", "START SET BACKUP SCREEN");
|
||||||
|
chip8_set_backup_screen(app->chip8_view, app->backup_screen);
|
||||||
|
FURI_LOG_I("chip8_scene_work_on_enter", "END SET BACKUP SCREEN");
|
||||||
|
|
||||||
|
app->chip8 = chip8_make_emulator(file_tmp);
|
||||||
|
|
||||||
|
string_clear(file_tmp);
|
||||||
|
|
||||||
|
chip8_set_state(app->chip8_view, chip8_get_state(app->chip8));
|
||||||
|
|
||||||
|
chip8_set_ok_callback(app->chip8_view, chip8_scene_ok_callback, app);
|
||||||
|
chip8_set_back_callback(app->chip8_view, chip8_scene_back_callback, app);
|
||||||
|
chip8_set_up_callback(app->chip8_view, chip8_scene_up_callback, app);
|
||||||
|
chip8_set_down_callback(app->chip8_view, chip8_scene_down_callback, app);
|
||||||
|
chip8_set_release_callback(app->chip8_view, chip8_scene_release_callback, app);
|
||||||
|
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, Chip8WorkView);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_scene_work_on_exit(void* context) {
|
||||||
|
Chip8App* app = context;
|
||||||
|
chip8_close_emulator(app->chip8);
|
||||||
|
}
|
||||||
207
applications/plugins/chip8/views/chip8_view.c
Normal file
207
applications/plugins/chip8/views/chip8_view.c
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
#include <gui/elements.h>
|
||||||
|
#include "chip8_view.h"
|
||||||
|
#include "../chip8.h"
|
||||||
|
#include "../emulator_core/flipper_chip.h"
|
||||||
|
|
||||||
|
struct Chip8View {
|
||||||
|
View* view;
|
||||||
|
Chip8ViewCallback callback;
|
||||||
|
void* context;
|
||||||
|
Chip8ViewKeyBackCallback backCallback;
|
||||||
|
Chip8ViewKeyUpCallback upCallback;
|
||||||
|
Chip8ViewKeyDownCallback downCallback;
|
||||||
|
Chip8ViewReleaseCallback releaseCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
string_t file_name;
|
||||||
|
Chip8State state;
|
||||||
|
uint8_t** backup_screen;
|
||||||
|
} Chip8Model;
|
||||||
|
|
||||||
|
static void chip8_draw_callback(Canvas* canvas, void* _model) {
|
||||||
|
Chip8Model* model = _model;
|
||||||
|
|
||||||
|
if(model->state.worker_state == WorkerStateLoadingRom) {
|
||||||
|
canvas_draw_icon(canvas, 4, 22, &I_Clock_18x18);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model->state.worker_state == WorkerStateRomLoaded) {
|
||||||
|
while(!model->state.t_chip8_state->go_render) {
|
||||||
|
for(int y = 0; y < CHIP8_SCREEN_H; y++) {
|
||||||
|
for(int x = 0; x < CHIP8_SCREEN_W; x++) {
|
||||||
|
if(model->backup_screen[y][x] == 0) {
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
} else {
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
canvas_draw_box(canvas, x * 2, y * 2, 2, 2);
|
||||||
|
//canvas_draw_dot(canvas, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t** screen = t_chip8_get_screen(model->state.t_chip8_state);
|
||||||
|
|
||||||
|
for(int y = 0; y < CHIP8_SCREEN_H; y++) {
|
||||||
|
for(int x = 0; x < CHIP8_SCREEN_W; x++) {
|
||||||
|
if(screen[y][x] == 0) {
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
} else {
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
canvas_draw_box(canvas, x * 2, y * 2, 2, 2);
|
||||||
|
model->backup_screen[y][x] = screen[y][x];
|
||||||
|
//canvas_draw_dot(canvas, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model->state.t_chip8_state->go_render = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(model->state.worker_state == WorkerStateRomLoadError) {
|
||||||
|
canvas_draw_icon(canvas, 4, 22, &I_Error_18x18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool chip8_input_callback(InputEvent* event, void* context) {
|
||||||
|
FURI_LOG_I("Chip8", "received input");
|
||||||
|
furi_assert(context);
|
||||||
|
Chip8View* chip8 = context;
|
||||||
|
bool consumed = false;
|
||||||
|
|
||||||
|
if(event->type == InputTypeShort) {
|
||||||
|
if(event->key == InputKeyOk) {
|
||||||
|
consumed = true;
|
||||||
|
furi_assert(chip8->callback);
|
||||||
|
chip8->callback(InputTypeShort, chip8->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event->key == InputKeyBack) {
|
||||||
|
consumed = true;
|
||||||
|
furi_assert(chip8->callback);
|
||||||
|
chip8->backCallback(chip8, InputTypeShort, chip8->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event->key == InputKeyUp) {
|
||||||
|
consumed = true;
|
||||||
|
furi_assert(chip8->upCallback);
|
||||||
|
chip8->upCallback(InputTypeShort, chip8->context);
|
||||||
|
}
|
||||||
|
if(event->key == InputKeyDown) {
|
||||||
|
consumed = true;
|
||||||
|
furi_assert(chip8->downCallback);
|
||||||
|
chip8->downCallback(InputTypeShort, chip8->context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(event->type == InputTypeRelease) {
|
||||||
|
chip8->releaseCallback(InputTypeShort, chip8->context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chip8View* chip8_alloc() {
|
||||||
|
Chip8View* chip8 = malloc(sizeof(Chip8View));
|
||||||
|
|
||||||
|
chip8->view = view_alloc();
|
||||||
|
view_allocate_model(chip8->view, ViewModelTypeLocking, sizeof(Chip8Model));
|
||||||
|
view_set_context(chip8->view, chip8);
|
||||||
|
view_set_draw_callback(chip8->view, chip8_draw_callback);
|
||||||
|
view_set_input_callback(chip8->view, chip8_input_callback);
|
||||||
|
|
||||||
|
return chip8;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_free(Chip8View* chip8) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
view_free(chip8->view);
|
||||||
|
free(chip8);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* chip8_get_view(Chip8View* chip8) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
return chip8->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_ok_callback(Chip8View* chip8, Chip8ViewCallback callback, void* context) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_assert(callback);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
chip8->callback = callback;
|
||||||
|
chip8->context = context;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_back_callback(Chip8View* chip8, Chip8ViewKeyBackCallback callback, void* context) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_assert(callback);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
chip8->backCallback = callback;
|
||||||
|
chip8->context = context;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_up_callback(Chip8View* chip8, Chip8ViewKeyUpCallback callback, void* context) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_assert(callback);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
chip8->upCallback = callback;
|
||||||
|
chip8->context = context;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_down_callback(Chip8View* chip8, Chip8ViewKeyDownCallback callback, void* context) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_assert(callback);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
chip8->downCallback = callback;
|
||||||
|
chip8->context = context;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_file_name(Chip8View* chip8, string_t name) {
|
||||||
|
furi_assert(name);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
*model->file_name = *name;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_backup_screen(Chip8View* chip8, uint8_t** screen) {
|
||||||
|
furi_assert(screen);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
model->backup_screen = screen;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_state(Chip8View* chip8, Chip8State* st) {
|
||||||
|
furi_assert(st);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
memcpy(&(model->state), st, sizeof(Chip8State));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_set_release_callback(Chip8View* chip8, Chip8ViewReleaseCallback callback, void* context) {
|
||||||
|
furi_assert(chip8);
|
||||||
|
furi_assert(callback);
|
||||||
|
with_view_model(
|
||||||
|
chip8->view, (Chip8Model * model) {
|
||||||
|
chip8->releaseCallback = callback;
|
||||||
|
chip8->context = context;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
29
applications/plugins/chip8/views/chip8_view.h
Normal file
29
applications/plugins/chip8/views/chip8_view.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../chip8.h"
|
||||||
|
|
||||||
|
typedef struct Chip8View Chip8View;
|
||||||
|
typedef void (*Chip8ViewCallback)(InputType type, void* context);
|
||||||
|
typedef void (*Chip8ViewKeyBackCallback)(Chip8View* view, InputType type, void* context);
|
||||||
|
typedef void (*Chip8ViewKeyUpCallback)(InputType type, void* context);
|
||||||
|
typedef void (*Chip8ViewKeyDownCallback)(InputType type, void* context);
|
||||||
|
typedef void (*Chip8ViewReleaseCallback)(InputType type, void* context);
|
||||||
|
|
||||||
|
Chip8View* chip8_alloc();
|
||||||
|
|
||||||
|
void chip8_free(Chip8View* chip8);
|
||||||
|
|
||||||
|
View* chip8_get_view(Chip8View* chip8);
|
||||||
|
|
||||||
|
void chip8_set_ok_callback(Chip8View* chip8, Chip8ViewCallback callback, void* context);
|
||||||
|
void chip8_set_back_callback(Chip8View* chip8, Chip8ViewKeyBackCallback callback, void* context);
|
||||||
|
void chip8_set_up_callback(Chip8View* chip8, Chip8ViewKeyUpCallback callback, void* context);
|
||||||
|
void chip8_set_down_callback(Chip8View* chip8, Chip8ViewKeyDownCallback callback, void* context);
|
||||||
|
void chip8_set_release_callback(Chip8View* chip8, Chip8ViewReleaseCallback callback, void* context);
|
||||||
|
|
||||||
|
void chip8_set_backup_screen(Chip8View* chip8, uint8_t** screen);
|
||||||
|
|
||||||
|
void chip8_set_file_name(Chip8View* chip8, string_t name);
|
||||||
|
|
||||||
|
void chip8_set_state(Chip8View* chip8, Chip8State* st);
|
||||||
12
applications/plugins/mouse_jiggler/application.fam
Normal file
12
applications/plugins/mouse_jiggler/application.fam
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
App(
|
||||||
|
appid="APPS_MouseJiggler",
|
||||||
|
name="Mouse Jiggler",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="mouse_jiggler_app",
|
||||||
|
cdefines=["APP_MOUSE_JIGGLER"],
|
||||||
|
requires=["gui"],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
order=150,
|
||||||
|
fap_icon="mjigIcon.png",
|
||||||
|
fap_category="Misc",
|
||||||
|
)
|
||||||
141
applications/plugins/mouse_jiggler/mouse_jiggler.c
Normal file
141
applications/plugins/mouse_jiggler/mouse_jiggler.c
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <input/input.h>
|
||||||
|
|
||||||
|
#define MOUSE_MOVE_SHORT 5
|
||||||
|
#define MOUSE_MOVE_LONG 20
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeInput,
|
||||||
|
EventTypeKey,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
EventType type;
|
||||||
|
InputEvent input;
|
||||||
|
} UsbMouseEvent;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool running;
|
||||||
|
} MouseJigglerState;
|
||||||
|
|
||||||
|
static void mouse_jiggler_render_callback(Canvas* canvas, void* ctx) {
|
||||||
|
const MouseJigglerState* plugin_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||||
|
if(plugin_state == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
canvas_draw_str(canvas, 2, 12, "USB Mouse Jiggler");
|
||||||
|
if(!plugin_state->running) {
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str(canvas, 2, 27, " -> STOPPED");
|
||||||
|
canvas_draw_str(canvas, 2, 51, "Press [ok] to start");
|
||||||
|
canvas_draw_str(canvas, 2, 63, "Press [back] to exit");
|
||||||
|
} else {
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
canvas_draw_str(canvas, 2, 27, " -> RUNNING");
|
||||||
|
canvas_draw_str(canvas, 2, 51, "Press [back] to stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
release_mutex((ValueMutex*)ctx, plugin_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mouse_jiggler_input_callback(InputEvent* input_event, void* ctx) {
|
||||||
|
FuriMessageQueue* event_queue = ctx;
|
||||||
|
furi_assert(event_queue);
|
||||||
|
|
||||||
|
UsbMouseEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||||
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mouse_jiggler_state_init(MouseJigglerState* const plugin_state) {
|
||||||
|
plugin_state->running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t mouse_jiggler_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(UsbMouseEvent));
|
||||||
|
|
||||||
|
MouseJigglerState* plugin_state = malloc(sizeof(MouseJigglerState));
|
||||||
|
if(plugin_state == NULL) {
|
||||||
|
FURI_LOG_E("MouseJiggler", "MouseJigglerState: malloc error\r\n");
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
mouse_jiggler_state_init(plugin_state);
|
||||||
|
|
||||||
|
ValueMutex state_mutex;
|
||||||
|
if(!init_mutex(&state_mutex, plugin_state, sizeof(MouseJigglerState))) {
|
||||||
|
FURI_LOG_E("MouseJiggler", "cannot create mutex\r\n");
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
free(plugin_state);
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, mouse_jiggler_render_callback, &state_mutex);
|
||||||
|
view_port_input_callback_set(view_port, mouse_jiggler_input_callback, event_queue);
|
||||||
|
|
||||||
|
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
|
||||||
|
furi_hal_usb_set_config(&usb_hid, NULL);
|
||||||
|
|
||||||
|
// Open GUI and register view_port
|
||||||
|
Gui* gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
UsbMouseEvent event;
|
||||||
|
//bool status = 0;
|
||||||
|
|
||||||
|
for(bool processing = true; processing;) {
|
||||||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||||
|
|
||||||
|
MouseJigglerState* plugin_state = (MouseJigglerState*)acquire_mutex_block(&state_mutex);
|
||||||
|
|
||||||
|
if(event_status == FuriStatusOk) {
|
||||||
|
if(event.type == EventTypeKey) {
|
||||||
|
if(event.input.type == InputTypePress) {
|
||||||
|
switch(event.input.key) {
|
||||||
|
case InputKeyOk:
|
||||||
|
if(!plugin_state->running) {
|
||||||
|
plugin_state->running = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InputKeyBack:
|
||||||
|
if(!plugin_state->running) {
|
||||||
|
processing = false;
|
||||||
|
} else {
|
||||||
|
plugin_state->running = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(plugin_state->running) {
|
||||||
|
furi_hal_hid_mouse_move(MOUSE_MOVE_SHORT, 0);
|
||||||
|
furi_delay_ms(500);
|
||||||
|
furi_hal_hid_mouse_move(-MOUSE_MOVE_SHORT, 0);
|
||||||
|
furi_delay_ms(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
view_port_update(view_port);
|
||||||
|
release_mutex(&state_mutex, plugin_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_hal_usb_set_config(usb_mode_prev, NULL);
|
||||||
|
|
||||||
|
// remove & free all stuff created by app
|
||||||
|
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);
|
||||||
|
delete_mutex(&state_mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
applications/plugins/paint/application.fam
Normal file
10
applications/plugins/paint/application.fam
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
App(
|
||||||
|
appid="APPS_Paint",
|
||||||
|
name="Paint",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="paint_app",
|
||||||
|
cdefines=["APP_PAINT"],
|
||||||
|
requires=["gui"],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
order=175,
|
||||||
|
)
|
||||||
148
applications/plugins/paint/paint.c
Normal file
148
applications/plugins/paint/paint.c
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#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.
|
||||||
|
|
||||||
|
typedef struct selected_position {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} selected_position;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
selected_position selected;
|
||||||
|
bool board[32][16];
|
||||||
|
bool isDrawing;
|
||||||
|
} PaintData;
|
||||||
|
|
||||||
|
void paint_draw_callback(Canvas* canvas, void* ctx) {
|
||||||
|
const PaintData* paint_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||||
|
UNUSED(ctx);
|
||||||
|
canvas_clear(canvas);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
//draw the canvas(32x16) on screen(144x64) using 4x4 tiles
|
||||||
|
for(int y = 0; y < 16; y++) {
|
||||||
|
for(int x = 0; x < 32; x++) {
|
||||||
|
if(paint_state->board[x][y]) {
|
||||||
|
canvas_draw_box(canvas, x * 4, y * 4, 4, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//draw cursor as a 4x4 black box with a 2x2 white box inside
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
canvas_draw_box(canvas, paint_state->selected.x * 4, paint_state->selected.y * 4, 4, 4);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
canvas_draw_box(
|
||||||
|
canvas, paint_state->selected.x * 4 + 1, paint_state->selected.y * 4 + 1, 2, 2);
|
||||||
|
|
||||||
|
//release the mutex
|
||||||
|
release_mutex((ValueMutex*)ctx, paint_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint_input_callback(InputEvent* input_event, void* ctx) {
|
||||||
|
furi_assert(ctx);
|
||||||
|
FuriMessageQueue* event_queue = ctx;
|
||||||
|
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t paint_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||||
|
|
||||||
|
PaintData* paint_state = malloc(sizeof(PaintData));
|
||||||
|
ValueMutex paint_state_mutex;
|
||||||
|
if(!init_mutex(&paint_state_mutex, paint_state, sizeof(PaintData))) {
|
||||||
|
FURI_LOG_E("paint", "cannot create mutex\r\n");
|
||||||
|
free(paint_state);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure view port
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, paint_draw_callback, &paint_state_mutex);
|
||||||
|
view_port_input_callback_set(view_port, paint_input_callback, event_queue);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
//check the key pressed and change x and y accordingly
|
||||||
|
if(event.type == InputTypeShort) {
|
||||||
|
switch(event.key) {
|
||||||
|
case InputKeyUp:
|
||||||
|
paint_state->selected.y -= 1;
|
||||||
|
break;
|
||||||
|
case InputKeyDown:
|
||||||
|
paint_state->selected.y += 1;
|
||||||
|
break;
|
||||||
|
case InputKeyLeft:
|
||||||
|
paint_state->selected.x -= 1;
|
||||||
|
break;
|
||||||
|
case InputKeyRight:
|
||||||
|
paint_state->selected.x += 1;
|
||||||
|
break;
|
||||||
|
case InputKeyOk:
|
||||||
|
paint_state->board[paint_state->selected.x][paint_state->selected.y] =
|
||||||
|
!paint_state->board[paint_state->selected.x][paint_state->selected.y];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if cursor position is out of bounds and reset it to the closest position
|
||||||
|
if(paint_state->selected.x < 0) {
|
||||||
|
paint_state->selected.x = 0;
|
||||||
|
}
|
||||||
|
if(paint_state->selected.x > 31) {
|
||||||
|
paint_state->selected.x = 31;
|
||||||
|
}
|
||||||
|
if(paint_state->selected.y < 0) {
|
||||||
|
paint_state->selected.y = 0;
|
||||||
|
}
|
||||||
|
if(paint_state->selected.y > 15) {
|
||||||
|
paint_state->selected.y = 15;
|
||||||
|
}
|
||||||
|
if(paint_state->isDrawing == true) {
|
||||||
|
paint_state->board[paint_state->selected.x][paint_state->selected.y] = true;
|
||||||
|
}
|
||||||
|
view_port_update(view_port);
|
||||||
|
}
|
||||||
|
if(event.key == InputKeyBack && event.type == InputTypeLong) {
|
||||||
|
paint_state->board[1][1] = true;
|
||||||
|
for(int y = 0; y < 16; y++) {
|
||||||
|
for(int x = 0; x < 32; x++) {
|
||||||
|
paint_state->board[x][y] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view_port_update(view_port);
|
||||||
|
}
|
||||||
|
if(event.key == InputKeyOk && event.type == InputTypeLong) {
|
||||||
|
paint_state->isDrawing = !paint_state->isDrawing;
|
||||||
|
paint_state->board[paint_state->selected.x][paint_state->selected.y] = true;
|
||||||
|
view_port_update(view_port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gui_remove_view_port(gui, view_port);
|
||||||
|
view_port_free(view_port);
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
free(paint_state);
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user