From 61e922399d54659ca311f4d5c521c2f1dd1331bc Mon Sep 17 00:00:00 2001 From: RogueMaster Date: Fri, 16 Sep 2022 20:16:27 -0400 Subject: [PATCH] more apps --- .../plugins/calculator/application.fam | 12 + applications/plugins/calculator/calc.png | Bin 0 -> 1206 bytes applications/plugins/calculator/calculator.c | 453 +++ applications/plugins/calculator/tinyexpr.c | 661 ++++ applications/plugins/calculator/tinyexpr.h | 87 + applications/plugins/chess/application.fam | 12 + applications/plugins/chess/chess_app.c | 682 ++++ applications/plugins/chess/fast_chess.c | 2978 +++++++++++++++++ applications/plugins/chess/fast_chess.h | 388 +++ applications/plugins/chip8/application.fam | 12 + applications/plugins/chip8/chip8.c | 203 ++ applications/plugins/chip8/chip8.h | 45 + applications/plugins/chip8/chip8_app.c | 89 + applications/plugins/chip8/chip8_app.h | 11 + applications/plugins/chip8/chip8_app_i.h | 33 + .../chip8/emulator_core/flipper_chip.c | 319 ++ .../chip8/emulator_core/flipper_chip.h | 74 + .../chip8/emulator_core/flipper_fonts.h | 30 + .../plugins/chip8/scenes/chip8_scene.c | 26 + .../plugins/chip8/scenes/chip8_scene.h | 25 + .../plugins/chip8/scenes/chip8_scene_config.h | 2 + .../chip8/scenes/chip8_scene_file_select.c | 45 + .../plugins/chip8/scenes/chip8_scene_work.c | 90 + applications/plugins/chip8/views/chip8_view.c | 207 ++ applications/plugins/chip8/views/chip8_view.h | 29 + .../plugins/mouse_jiggler/application.fam | 12 + .../plugins/mouse_jiggler/mouse_jiggler.c | 141 + applications/plugins/paint/application.fam | 10 + applications/plugins/paint/paint.c | 148 + 29 files changed, 6824 insertions(+) create mode 100644 applications/plugins/calculator/application.fam create mode 100644 applications/plugins/calculator/calc.png create mode 100644 applications/plugins/calculator/calculator.c create mode 100644 applications/plugins/calculator/tinyexpr.c create mode 100644 applications/plugins/calculator/tinyexpr.h create mode 100644 applications/plugins/chess/application.fam create mode 100644 applications/plugins/chess/chess_app.c create mode 100644 applications/plugins/chess/fast_chess.c create mode 100644 applications/plugins/chess/fast_chess.h create mode 100644 applications/plugins/chip8/application.fam create mode 100644 applications/plugins/chip8/chip8.c create mode 100644 applications/plugins/chip8/chip8.h create mode 100644 applications/plugins/chip8/chip8_app.c create mode 100644 applications/plugins/chip8/chip8_app.h create mode 100644 applications/plugins/chip8/chip8_app_i.h create mode 100644 applications/plugins/chip8/emulator_core/flipper_chip.c create mode 100644 applications/plugins/chip8/emulator_core/flipper_chip.h create mode 100644 applications/plugins/chip8/emulator_core/flipper_fonts.h create mode 100644 applications/plugins/chip8/scenes/chip8_scene.c create mode 100644 applications/plugins/chip8/scenes/chip8_scene.h create mode 100644 applications/plugins/chip8/scenes/chip8_scene_config.h create mode 100644 applications/plugins/chip8/scenes/chip8_scene_file_select.c create mode 100644 applications/plugins/chip8/scenes/chip8_scene_work.c create mode 100644 applications/plugins/chip8/views/chip8_view.c create mode 100644 applications/plugins/chip8/views/chip8_view.h create mode 100644 applications/plugins/mouse_jiggler/application.fam create mode 100644 applications/plugins/mouse_jiggler/mouse_jiggler.c create mode 100644 applications/plugins/paint/application.fam create mode 100644 applications/plugins/paint/paint.c diff --git a/applications/plugins/calculator/application.fam b/applications/plugins/calculator/application.fam new file mode 100644 index 000000000..0fe98bc7b --- /dev/null +++ b/applications/plugins/calculator/application.fam @@ -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", +) diff --git a/applications/plugins/calculator/calc.png b/applications/plugins/calculator/calc.png new file mode 100644 index 0000000000000000000000000000000000000000..838d7b964feaf466887daedbf8cfaa0966ee6d4b GIT binary patch literal 1206 zcmV;n1WEgeP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&1WQRoK~#8N?VRnA z<1h$@lPzG|xB{<$n=LbKTmv~?Cu9hR5R$CKv3X{g_(NC-zY<#i*z@(zuj}c^{JxyN z?uUov{C&zKT>oYgQVAfHK%^3gR05GoAW{iLDuGBP5UB(rl|U>?IR9L3CkOq75>EF& zrLRZ*h2{5z{GLG5BC7++?PFR2}r&mr2p*dm@!U z6qRr!poFXrR2|OrbL=D#kLhq!uqG@@=w11F9r<+sR6^~qNMuS{D?L81#{Hgv0YGNb zS_ajd0Lu^8*jkAA6qNu+Yo1xxEM&(Vh_(`?WJmjM#Z&?U80pX(U=%A6Jfz$?B!|JCu5jB>-BI&odZPuOYc6{IS_YzX`ud#-Ibxpv-rM1!< z+$-69b$A6(LViymQVFbt-nqMlg$<1QrxMoQF@7SOt`3l@v?yL%>t0m}EuCvPc(xYM z9`jsU^ckhL{7j9jgx0_%BZB}s*wO$>=>9x^R|zehYDNdaz^~UAJ`w-vYr@DN0Q}e& z&zZs0>cE5VO~5F!e=mWdHKFGXNMC7DbX%OaBvZHd60zwWqYS$Ae9Ir#y!N^+n7Ss6 zfMD5L@Y-AVa>q#I_XOgxCfrVsODbW@)gi)K{;7nl4y1;x4nZaGylDYGmbEbA+d@?d zBb{Th(iR2%_WHKsRtFA*e=Rsk`o5LCQXgPVf6G^5wKbK1n^vb$;H#|`oMzNNin}JD zQ|`DmfLQ@Y(8<%L!J1IrEMY*U`#f0l;VKbcn;HHQ;6HVrfWV}+GU^{Cv`?5Bz!u0I zBau5s;;~P-ogSA|g12Lguttdj@Agk6WOX1l?7ccb?kxEdMJ2GawiZTw!Kil!nyI-oQCCanNGM*X9>N*L`F{?b|*^^daqeL^IGxnm@DuY?FtLViym zQVFbt-qjbS3w*l2s{}OShI=Ke12Mfie1ek9HK7)?)z+GmxX0J!vBdL_VEOa45?0$O zO3;JGaV@xaQLPSErX{8OItie34V0c=V=e#OC+x90;O%;*sa6NbtVe3_ksac=SF*GD zZ%wc=Wue=b2Kes#HQ0aZbU<*u0ltdOuL-Y6BG!b%{{i(_6K +#include +#include +#include +#include +#include +#include // Header-file for boolean data-type. +#include // Header-file for string functions. +#include "tinyexpr.h" // Header-file for the TinyExpr library. + +#include +#include + +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; +} diff --git a/applications/plugins/calculator/tinyexpr.c b/applications/plugins/calculator/tinyexpr.c new file mode 100644 index 000000000..bee3cfd44 --- /dev/null +++ b/applications/plugins/calculator/tinyexpr.c @@ -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 +#include +#include +#include +#include +#include + +#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) { + /* = | | {"(" ")"} | | "(" {"," } ")" | "(" ")" */ + 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) { + /* = {("-" | "+")} */ + 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) { + /* = {"^" } */ + 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) { + /* = {"^" } */ + 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) { + /* = {("*" | "/" | "%") } */ + 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) { + /* = {("+" | "-") } */ + 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) { + /* = {"," } */ + 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); +} diff --git a/applications/plugins/calculator/tinyexpr.h b/applications/plugins/calculator/tinyexpr.h new file mode 100644 index 000000000..c2cbe1a30 --- /dev/null +++ b/applications/plugins/calculator/tinyexpr.h @@ -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*/ diff --git a/applications/plugins/chess/application.fam b/applications/plugins/chess/application.fam new file mode 100644 index 000000000..f13cb1539 --- /dev/null +++ b/applications/plugins/chess/application.fam @@ -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", +) diff --git a/applications/plugins/chess/chess_app.c b/applications/plugins/chess/chess_app.c new file mode 100644 index 000000000..65b2df528 --- /dev/null +++ b/applications/plugins/chess/chess_app.c @@ -0,0 +1,682 @@ +#include +#include +#include +#include +#include + +#include +#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; +} diff --git a/applications/plugins/chess/fast_chess.c b/applications/plugins/chess/fast_chess.c new file mode 100644 index 000000000..c09b912b0 --- /dev/null +++ b/applications/plugins/chess/fast_chess.c @@ -0,0 +1,2978 @@ +/* + ============================================================================ + Name : fast-chess.c + Author : Frederico Jordan + Version : + Copyright : Copyright (c) 2016 Frederico Jordan + Description : Simple chess game! + ============================================================================ + */ + +#include +#include +#include +#include +#include +#include + +#include "fast_chess.h" + +char FILES[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; +char RANKS[8] = {'1', '2', '3', '4', '5', '6', '7', '8'}; + +Bitboard FILES_BB[8] = {FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H}; +Bitboard RANKS_BB[8] = {RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8}; + +char INITIAL_FEN[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +Board EMPTY_BOARD = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +Board INITIAL_BOARD = { + FILE_E & RANK_1, // whiteKing; + FILE_D& RANK_1, // whiteQueens; + (FILE_A | FILE_H) & RANK_1, // whiteRooks; + (FILE_B | FILE_G) & RANK_1, // whiteKnights; + (FILE_C | FILE_F) & RANK_1, // whiteBishops; + RANK_2, // whitePawns; + + FILE_E& RANK_8, // blackKing; + FILE_D& RANK_8, // blackQueens; + (FILE_A | FILE_H) & RANK_8, // blackRooks; + (FILE_B | FILE_G) & RANK_8, // blackKnights; + (FILE_C | FILE_F) & RANK_8, // blackBishops; + RANK_7, // blackPawns; +}; + +int PIECE_VALUES[] = { + 0, // EMPTY + 100, // PAWN + 300, // KNIGHT + 300, // BISHOP + 500, // ROOK + 900, // QUEEN + 42000 // KING +}; + +int PAWN_BONUS[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -40, -40, 0, 0, 0, + 1, 2, 3, -10, -10, 3, 2, 1, 2, 4, 6, 8, 8, 6, 4, 2, + 3, 6, 9, 12, 12, 9, 6, 3, 4, 8, 12, 16, 16, 12, 8, 4, + 5, 10, 15, 20, 20, 15, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0}; + +int KNIGHT_BONUS[] = {-10, -30, -10, -10, -10, -10, -30, -10, -10, 0, 0, 0, 0, + 0, 0, -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, + 5, 10, 10, 5, 0, -10, -10, 0, 5, 10, 10, 5, 0, + -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, 0, 0, + 0, 0, 0, -10, -10, -10, -10, -10, -10, -10, -10, -10}; + +int BISHOP_BONUS[] = {-10, -10, -20, -10, -10, -20, -10, -10, -10, 0, 0, 0, 0, + 0, 0, -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, + 5, 10, 10, 5, 0, -10, -10, 0, 5, 10, 10, 5, 0, + -10, -10, 0, 5, 5, 5, 5, 0, -10, -10, 0, 0, 0, + 0, 0, 0, -10, -10, -10, -10, -10, -10, -10, -10, -10}; + +int KING_BONUS[] = {0, 20, 40, -20, 0, -20, 40, 20, -20, -20, -20, -20, -20, + -20, -20, -20, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40, -40}; + +int KING_ENDGAME_BONUS[] = {0, 10, 20, 30, 30, 20, 10, 0, 10, 20, 30, 40, 40, 30, 20, 10, + 20, 30, 40, 50, 50, 40, 30, 20, 30, 40, 50, 60, 60, 50, 40, 30, + 30, 40, 50, 60, 60, 50, 40, 30, 20, 30, 40, 50, 50, 40, 30, 20, + 10, 20, 30, 40, 40, 30, 20, 10, 0, 10, 20, 30, 30, 20, 10, 0}; + +int FLIP_VERTICAL[] = {56, 57, 58, 59, 60, 61, 62, 63, 48, 49, 50, 51, 52, 53, 54, 55, + 40, 41, 42, 43, 44, 45, 46, 47, 32, 33, 34, 35, 36, 37, 38, 39, + 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, + 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7}; + +void getInitialGame(Game* game) { + game->position.board = INITIAL_BOARD; + game->position.toMove = WHITE; + game->position.epSquare = -1; + game->position.castlingRights = CASTLE_KINGSIDE_WHITE | CASTLE_QUEENSIDE_WHITE | + CASTLE_KINGSIDE_BLACK | CASTLE_QUEENSIDE_BLACK; + game->position.halfmoveClock = 0; + game->position.fullmoveNumber = 1; + + game->moveListLen = 0; + memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int)); + memset(game->positionHistory, 0, MAX_PLYS_PER_GAME * MAX_FEN_LEN * sizeof(char)); + memcpy(game->positionHistory[0], INITIAL_FEN, sizeof(INITIAL_FEN)); +} + +void getFenGame(Game* game, char fen[]) { + int fenLen = loadFen(&(game->position), fen); + + game->moveListLen = 0; + memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int)); + memset(game->positionHistory, 0, MAX_PLYS_PER_GAME * MAX_FEN_LEN * sizeof(char)); + memcpy(game->positionHistory[0], fen, fenLen); +} + +void insertPiece(Board* board, Bitboard position, char pieceCode) { + switch(pieceCode) { + case 'P': + board->whitePawns |= position; + break; + case 'N': + board->whiteKnights |= position; + break; + case 'B': + board->whiteBishops |= position; + break; + case 'R': + board->whiteRooks |= position; + break; + case 'Q': + board->whiteQueens |= position; + break; + case 'K': + board->whiteKing |= position; + break; + + case 'p': + board->blackPawns |= position; + break; + case 'n': + board->blackKnights |= position; + break; + case 'b': + board->blackBishops |= position; + break; + case 'r': + board->blackRooks |= position; + break; + case 'q': + board->blackQueens |= position; + break; + case 'k': + board->blackKing |= position; + break; + } +} + +int loadFen(Position* position, char fen[]) { + // ===== BOARD ===== + position->board = EMPTY_BOARD; + + int rank = 7; + int boardPos = rank * 8; + char* charPos = fen; + + char pieceCode = *(charPos); + + while(pieceCode != ' ') { + if(pieceCode == '/') { + rank--; + boardPos = rank * 8; + } else if(isdigit(pieceCode)) { + int emptySquares = atoi(charPos); + boardPos += emptySquares; + } else { + insertPiece(&(position->board), index2bb(boardPos++), pieceCode); + } + + pieceCode = *(++charPos); + } + + // ===== TO MOVE ===== + char* nextFenField = strchr(fen, ' ') + 1; + + if(*nextFenField == 'b') { + position->toMove = BLACK; + } else { + position->toMove = WHITE; + } + + // ===== CASTLING RIGHTS ===== + nextFenField = strchr(nextFenField, ' ') + 1; + + position->castlingRights = 0; + if(strchr(nextFenField, 'K')) position->castlingRights |= CASTLE_KINGSIDE_WHITE; + if(strchr(nextFenField, 'Q')) position->castlingRights |= CASTLE_QUEENSIDE_WHITE; + if(strchr(nextFenField, 'k')) position->castlingRights |= CASTLE_KINGSIDE_BLACK; + if(strchr(nextFenField, 'q')) position->castlingRights |= CASTLE_QUEENSIDE_BLACK; + + // ===== EN PASSANT ===== + nextFenField = strchr(nextFenField, ' ') + 1; + + if(*nextFenField == '-') { + position->epSquare = -1; + } else { + position->epSquare = str2index(nextFenField); + } + + // ===== HALF MOVE CLOCK ===== + if(!strchr(nextFenField, ' ')) { + position->halfmoveClock = 0; + position->fullmoveNumber = 1; + return 1 + nextFenField - fen; + } + nextFenField = strchr(nextFenField, ' ') + 1; + + position->halfmoveClock = atoi(nextFenField); + + // ===== FULL MOVE NUMBER ===== + if(!strchr(nextFenField, ' ')) { + position->fullmoveNumber = 1; + return 1 + nextFenField - fen; + } + nextFenField = strchr(nextFenField, ' ') + 1; + + position->fullmoveNumber = atoi(nextFenField); + + return 1 + nextFenField - fen; +} + +int toFen(char* fen, Position* position) { + int charCount = toMinFen(fen, position); + fen[charCount - 1] = ' '; + + // ===== HALF MOVE CLOCK ===== + snprintf(&fen[charCount++], sizeof(&fen[charCount++]), "%d", position->halfmoveClock); + if(position->halfmoveClock >= 10) { + charCount++; + if(position->halfmoveClock >= 100) { + charCount++; + } + } + fen[charCount++] = ' '; + + // ===== FULL MOVE NUMBER ===== + snprintf(&fen[charCount++], sizeof(&fen[charCount++]), "%d", position->fullmoveNumber); + if(position->fullmoveNumber >= 10) { + charCount++; + if(position->fullmoveNumber >= 100) { + charCount++; + } + } + fen[charCount++] = '\0'; + + return charCount; +} + +int toMinFen(char* fen, Position* position) { + int charCount = 0; + + // ===== BOARD ===== + int rank = 7; + int file = 0; + int* emptyCount = 0; + + Bitboard empties = getEmptySquares(&(position->board)); + Bitboard bb; + + while(rank >= 0) { + bb = index2bb(8 * rank + file); + + if(bb & empties) { + emptyCount++; + } else { + if(emptyCount != 0) { + snprintf(&fen[charCount++], 2, "%d", emptyCount); + emptyCount = 0; + } + fen[charCount++] = bb2char(bb, &(position->board)); + } + + file++; + if(file > 7) { + if(emptyCount != 0) { + snprintf(&fen[charCount++], 2, "%d", emptyCount); + emptyCount = 0; + } + file = 0; + rank--; + fen[charCount++] = '/'; + } + } + fen[charCount - 1] = ' '; + + // ===== TO MOVE ===== + if(position->toMove == BLACK) { + fen[charCount++] = 'b'; + } else { + fen[charCount++] = 'w'; + } + fen[charCount++] = ' '; + + // ===== CASTLING RIGHTS ===== + if(position->castlingRights == 0) { + fen[charCount++] = '-'; + } else { + if(position->castlingRights & CASTLE_KINGSIDE_WHITE) { + fen[charCount++] = 'K'; + } + if(position->castlingRights & CASTLE_QUEENSIDE_WHITE) { + fen[charCount++] = 'Q'; + } + if(position->castlingRights & CASTLE_KINGSIDE_BLACK) { + fen[charCount++] = 'k'; + } + if(position->castlingRights & CASTLE_QUEENSIDE_BLACK) { + fen[charCount++] = 'q'; + } + } + fen[charCount++] = ' '; + + // ===== EN PASSANT ===== + if(position->epSquare == -1) { + fen[charCount++] = '-'; + } else { + fen[charCount++] = getFile(position->epSquare); + fen[charCount++] = getRank(position->epSquare); + } + fen[charCount++] = '\0'; + + return charCount; +} + +void getMovelistGame(Game* game, char moves[]) { + getInitialGame(game); + + for(int i = 0; i < strlen(moves) - 3; i += 5) { + makeMove(game, parseMove(&moves[i])); + if(moves[i + 5] == ' ') i++; // FIXME Queening + } +} + +// ========= UTILITY ========= + +BOOL fromInitial(Game* game) { + if(strcmp(game->positionHistory[0], INITIAL_FEN) == 0) + return TRUE; + else + return FALSE; +} + +Bitboard index2bb(int index) { + Bitboard bb = 1; + return bb << index; +} + +int str2index(char* str) { + int i, file_num = 0, rank_num = 0; + for(i = 0; i < 8; i++) { + if(str[0] == FILES[i]) file_num = i; + if(str[1] == RANKS[i]) rank_num = i; + } + return 8 * rank_num + file_num; +} + +Bitboard str2bb(char* str) { + return index2bb(str2index(str)); +} + +BOOL isSet(Bitboard bb, int index) { + if(bb & index2bb(index)) + return TRUE; + else + return FALSE; +} + +Bitboard lsb(Bitboard bb) { + int i; + for(i = 0; i < NUM_SQUARES; i++) { + Bitboard bit = index2bb(i); + if(bb & bit) return bit; + } + return 0; +} + +Bitboard msb(Bitboard bb) { + int i; + for(i = 0; i < NUM_SQUARES; i++) { + Bitboard bit = index2bb(63 - i); + if(bb & bit) return bit; + } + return 0; +} + +int bb2index(Bitboard bb) { + int i; + for(i = 0; i < NUM_SQUARES; i++) { + Bitboard bit = index2bb(i); + if(bb & bit) return i; + } + return -1; +} + +char* movelist2str(Game* game) { + char* movestr = NULL; + + if(game->moveListLen == 0) { + movestr = (char*)malloc(sizeof(char)); + movestr[0] = 0; + return movestr; + } + + movestr = (char*)malloc(5 * game->moveListLen); + + int i; + for(i = 0; i < game->moveListLen; i++) { + int leaving = getFrom(game->moveList[i]); + int arriving = getTo(game->moveList[i]); + movestr[5 * i] = getFile(leaving); + movestr[5 * i + 1] = getRank(leaving); + movestr[5 * i + 2] = getFile(arriving); + movestr[5 * i + 3] = getRank(arriving); + movestr[5 * i + 4] = ' '; + } + + movestr[5 * game->moveListLen - 1] = 0; + + return movestr; +} + +Move getLastMove(Game* game) { + if(game->moveListLen == 0) + return 0; + else + return game->moveList[game->moveListLen - 1]; +} + +BOOL startsWith(const char* str, const char* pre) { + size_t lenpre = strlen(pre), lenstr = strlen(str); + + if(lenpre > lenstr) return FALSE; + + return strncmp(pre, str, lenpre) == 0 ? TRUE : FALSE; +} + +int countBookOccurrences(Game* game) { + FILE* fp = fopen("book.txt", "r"); + + if(fp == NULL) return 0; + + char* moveList = movelist2str(game); + char* line = (char*)malloc(sizeof(char) * MAX_BOOK_ENTRY_LEN); + int charPos = 0, occurrences = 0; + + while(TRUE) { + char ch = getc(fp); + line[charPos++] = ch; + + if(ch == '\n' || ch == EOF) { + line[charPos - 1] = '\0'; + + if(startsWith(line, moveList) && strlen(line) > strlen(moveList) + 4) { + occurrences++; + } + + if(ch == EOF) break; + + charPos = 0; + } + } + + fclose(fp); + free(line); + free(moveList); + + return occurrences; +} + +Move getBookMove(Game* game) { + Move move = 0; + int moveNum = rand() % countBookOccurrences(game); + + FILE* fp = fopen("book.txt", "r"); + + if(fp == NULL) return 0; + + char* moveList = movelist2str(game); + char* line = (char*)malloc(sizeof(char) * MAX_BOOK_ENTRY_LEN); + int charPos = 0, occurrences = 0; + + while(TRUE) { + char ch = getc(fp); + line[charPos++] = ch; + + if(ch == '\n') { + line[charPos] = '\0'; + + if(startsWith(line, moveList)) { + if(occurrences == moveNum) { + int ind = game->moveListLen * 5; + move = parseMove(&line[ind]); + break; + } + occurrences++; + } + + charPos = 0; + } + } + + fclose(fp); + free(line); + free(moveList); + + return move; +} + +char getFile(int position) { + int file = position % 8; + return FILES[file]; +} + +char getRank(int position) { + int rank = (int)(position / 8); + return RANKS[rank]; +} + +Move generateMove(int leavingSquare, int arrivingSquare) { + int leaving = (leavingSquare << 8); + int arriving = arrivingSquare; + return (Move)(leaving + arriving); +} + +int getFrom(Move move) { + return (move >> 8) & 0xFF; +} + +int getTo(Move move) { + return move & 0xFF; +} + +int bb2piece(Bitboard position, Board* board) { + if(position & board->whitePawns) return WHITE | PAWN; + if(position & board->whiteKnights) return WHITE | KNIGHT; + if(position & board->whiteBishops) return WHITE | BISHOP; + if(position & board->whiteRooks) return WHITE | ROOK; + if(position & board->whiteQueens) return WHITE | QUEEN; + if(position & board->whiteKing) return WHITE | KING; + if(position & board->blackPawns) return BLACK | PAWN; + if(position & board->blackKnights) return BLACK | KNIGHT; + if(position & board->blackBishops) return BLACK | BISHOP; + if(position & board->blackRooks) return BLACK | ROOK; + if(position & board->blackQueens) return BLACK | QUEEN; + if(position & board->blackKing) return BLACK | KING; + + return EMPTY; +} + +char bb2char(Bitboard position, Board* board) { + if(position & board->whitePawns) return 'P'; + if(position & board->whiteKnights) return 'N'; + if(position & board->whiteBishops) return 'B'; + if(position & board->whiteRooks) return 'R'; + if(position & board->whiteQueens) return 'Q'; + if(position & board->whiteKing) return 'K'; + if(position & board->blackPawns) return 'p'; + if(position & board->blackKnights) return 'n'; + if(position & board->blackBishops) return 'b'; + if(position & board->blackRooks) return 'r'; + if(position & board->blackQueens) return 'q'; + if(position & board->blackKing) return 'k'; + + return '?'; +} + +char* bb2str(Bitboard position, Board* board) { + if((position & board->whitePawns) | (position & board->blackPawns)) return "Pawn"; + if((position & board->whiteKnights) | (position & board->blackKnights)) return "Knight"; + if((position & board->whiteBishops) | (position & board->blackBishops)) return "Bishop"; + if((position & board->whiteRooks) | (position & board->blackRooks)) return "Rook"; + if((position & board->whiteQueens) | (position & board->blackQueens)) return "Queen"; + if((position & board->whiteKing) | (position & board->blackKing)) return "King"; + + return "?"; +} + +void printBitboard(Bitboard bitboard) { + int rank, file; + + printf("\n"); + for(rank = 0; rank < 8; rank++) { + printf("%d", 8 - rank); + for(file = 0; file < 8; file++) { + if(bitboard >> (file + (7 - rank) * 8) & 1) { + printf(" #"); + } else { + printf(" ."); + } + } + printf("\n"); + } + printf(" a b c d e f g h\n"); + fflush(stdout); +} + +char getPieceChar(Bitboard position, Board* board) { + if(position & board->whiteKing) return 'K'; + if(position & board->whiteQueens) return 'Q'; + if(position & board->whiteRooks) return 'R'; + if(position & board->whiteKnights) return 'N'; + if(position & board->whiteBishops) return 'B'; + if(position & board->whitePawns) return 'P'; + if(position & board->blackKing) return 'k'; + if(position & board->blackQueens) return 'q'; + if(position & board->blackRooks) return 'r'; + if(position & board->blackKnights) return 'n'; + if(position & board->blackBishops) return 'b'; + if(position & board->blackPawns) return 'p'; + return '.'; +} + +void printBoard(Board* board) { + int rank, file; + + printf("\n"); + for(rank = 0; rank < 8; rank++) { + printf("%d", 8 - rank); + for(file = 0; file < 8; file++) { + printf(" %c", getPieceChar((FILES_BB[file] & RANKS_BB[7 - rank]), board)); + } + printf("\n"); + } + printf(" a b c d e f g h\n"); + + fflush(stdout); +} + +void printGame(Game* game) { + // printf("Game -> %p (%lu)", game, sizeof(*game)); + // printBoard(&(game->position.board)); + // printf("board -> %p (%lu)\n", &(game->position.board), sizeof(game->position.board)); + // printf("toMove = %d -> %p (%lu)\n", game->position.toMove, &game->position.toMove, sizeof(game->position.toMove)); + // printf("ep = %d -> %p (%lu)\n", game->position.epSquare, &game->position.epSquare, sizeof(game->position.epSquare)); + // printf("castle rights = %d -> %p (%lu)\n", game->position.castlingRights, &game->position.castlingRights, sizeof(game->position.castlingRights)); + // printf("half clock = %d -> %p (%lu)\n", game->position.halfmoveClock, &game->position.halfmoveClock, sizeof(game->position.halfmoveClock)); + // printf("full num = %d -> %p (%lu)\n", game->position.fullmoveNumber, &game->position.fullmoveNumber, sizeof(game->position.fullmoveNumber)); + + // printf("moveListLen = %d -> %p (%lu)\n", game->moveListLen, &game->moveListLen, sizeof(game->moveListLen)); + // printf("moveList -> %p (%lu)\n", game->moveList, sizeof(game->moveList)); + // printf("positionHistory -> %p (%lu)\n", game->positionHistory, sizeof(game->positionHistory)); + // fflush(stdout); +} + +Bitboard not(Bitboard bb) { + return ~bb & ALL_SQUARES; +} + +char opponent(char color) { + switch(color) { + case WHITE: + return BLACK; + case BLACK: + return WHITE; + } + return -1; +} + +int countBits(Bitboard bb) { + int bitCount = 0; + Bitboard position = 1; + + for(int i = 0; i < NUM_SQUARES; i++) { + if(position & bb) { + bitCount++; + } + position = position << 1; + } + + return bitCount; +} + +void sortNodes(Node* sortedNodes, Node* nodes, int len, char color) { + Node nodeBuffer[len]; + + int i, j; + BOOL sorted; + for(i = 0; i < len; i++) { + sorted = FALSE; + + for(j = 0; j < i; j++) { + if((color == WHITE && nodes[i].score > sortedNodes[j].score) || + (color == BLACK && nodes[i].score < sortedNodes[j].score)) { + sorted = TRUE; + memcpy(nodeBuffer, &sortedNodes[j], (i - j) * sizeof(Node)); + memcpy(&sortedNodes[j + 1], nodeBuffer, (i - j) * sizeof(Node)); + sortedNodes[j] = nodes[i]; + break; + } + } + + if(sorted == FALSE) { + sortedNodes[i] = nodes[i]; + } + } +} + +void printMove(Move move) { + printf( + "%c%c to %c%c", + getFile(getFrom(move)), + getRank(getFrom(move)), + getFile(getTo(move)), + getRank(getTo(move))); +} + +void printFullMove(Move move, Board* board) { + Bitboard leavingBB = index2bb(getFrom(move)); + printf("%s from ", bb2str(leavingBB, board)); + printMove(move); +} + +void printLegalMoves(Position* position) { + int i; + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + for(i = 0; i < moveCount; i++) { + printf("%2d. ", i + 1); + printFullMove(moves[i], &(position->board)); + // printMove(moves[i]); + printf("\n"); + } + fflush(stdout); +} + +void printNode(Node node) { + printMove(node.move); + printf(": %d", node.score); +} + +void getTimestamp(char* timestamp) { + time_t timer; + struct tm* tm_info; + + time(&timer); + tm_info = localtime(&timer); + + strftime(timestamp, 20, "%Y-%m-%d_%H.%M.%S", tm_info); +} + +void dumpContent(Game* game) { + char* movelist = movelist2str(game); + + char filename[50]; + sprintf(filename, "chess_game_"); + getTimestamp(&filename[strlen(filename)]); + sprintf(&filename[strlen(filename)], ".txt"); + + FILE* file = fopen(filename, "w+"); + + fprintf(file, "movelist = %s\nposition history:\n", movelist); + + int i; + for(i = 0; i < game->moveListLen + 1; i++) fprintf(file, "%s\n", game->positionHistory[i]); + + free(movelist); + fclose(file); + + printf("Dumped game content to: %s\n", filename); + fflush(stdout); +} + +void dumpPGN(Game* game, char color, BOOL hasAI) { + char filename[50]; + sprintf(filename, "chess_game_"); + getTimestamp(&filename[strlen(filename)]); + sprintf(&filename[strlen(filename)], ".pgn"); + + FILE* file = fopen(filename, "w+"); + + char date[12]; + time_t timer; + struct tm* tm_info; + time(&timer); + tm_info = localtime(&timer); + strftime(date, 11, "%Y.%m.%d", tm_info); + + fprintf(file, "[Event \"Casual Game\"]\n"); + fprintf(file, "[Site \"?\"]\n"); + fprintf(file, "[Date \"%s\"]\n", date); + fprintf(file, "[Round \"-\"]\n"); + + if(hasAI) { + if(color == WHITE) { + fprintf(file, "[White \"%s\"]\n", HUMAN_NAME); + fprintf(file, "[Black \"%s\"]\n", ENGINE_NAME); + } else { + fprintf(file, "[White \"%s\"]\n", ENGINE_NAME); + fprintf(file, "[Black \"%s\"]\n", HUMAN_NAME); + } + } else { + fprintf(file, "[White \"Unknown Human Player\"]\n"); + fprintf(file, "[Black \"Unknown Human Player\"]\n"); + } + + if(hasGameEnded(&game->position)) { + if(endNodeEvaluation(&game->position) == winScore(WHITE)) { + fprintf(file, "[Result \"1-0\"]\n"); + } else if(endNodeEvaluation(&game->position) == winScore(BLACK)) { + fprintf(file, "[Result \"0-1\"]\n"); + } else if(endNodeEvaluation(&game->position) == 0) { + fprintf(file, "[Result \"1/2-1/2\"]\n"); + } + } else { + fprintf(file, "[Result \"*\"]\n"); + } + + if(strcmp(game->positionHistory[0], INITIAL_FEN) == 0) { + fprintf(file, "[Variant \"Standard\"]\n"); + } else { + fprintf(file, "[Variant \"From Position\"]\n"); + fprintf(file, "[FEN \"%s\"]\n", game->positionHistory[0]); + } + + fprintf(file, "[PlyCount \"%d\"]\n\n", game->moveListLen); + + int i; + char ply[8]; + for(i = 0; i < game->moveListLen; i++) { + if(i % 2 == 0) fprintf(file, "%d. ", 1 + (i / 2)); + move2str(ply, game, i); + fprintf(file, "%s ", ply); + } + + fclose(file); + + printf("Dumped game pgn to: %s\n", filename); + fflush(stdout); +} + +void move2str(char* str, Game* game, int moveNumber) { + Position posBefore, posAfter; + loadFen(&posBefore, game->positionHistory[moveNumber]); + loadFen(&posAfter, game->positionHistory[moveNumber + 1]); + Move move = game->moveList[moveNumber]; + int leavingSquare = getFrom(move); + int arrivingSquare = getTo(move); + + Bitboard leavingBB = index2bb(leavingSquare); + Bitboard arrivingBB = index2bb(arrivingSquare); + + int length = 0; + if((leavingBB & (posBefore.board.whiteKing | posBefore.board.blackKing)) && + abs(leavingSquare - arrivingSquare) == 2) { // if castling + if(arrivingBB & FILE_G) { + sprintf(str, "O-O"); + length += 3; + } else if(arrivingBB & FILE_C) { + sprintf(str, "O-O-O"); + length += 5; + } + } else { // if not castling + if(leavingBB & (posBefore.board.whitePawns | posBefore.board.blackPawns)) { + if(arrivingBB & getOccupiedSquares(&(posBefore.board))) { + str[length++] = getFile(leavingSquare); + } + } else { + str[length++] = bb2char(leavingBB, &(posBefore.board)); + } + + if(isAmbiguous(&posBefore, move)) { + if(countBits(getTwinPieces(leavingBB, &(posBefore.board)) & fileFilter(leavingBB)) == + 1) { + str[length++] = getFile(leavingSquare); + } else { + str[length++] = getRank(leavingSquare); + } + } + + if(bb2piece(arrivingBB, &(posBefore.board)) != EMPTY) { + str[length++] = 'x'; + } + + str[length++] = getFile(arrivingSquare); + str[length++] = getRank(arrivingSquare); + } + + if(isCheckmate(&posAfter)) { + str[length++] = '#'; + } else if(isCheck(&(posAfter.board), posAfter.toMove)) { + str[length++] = '+'; + } + + str[length++] = 0; +} + +BOOL isAmbiguous(Position* position, Move move) { + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + Bitboard targetBB = index2bb(getTo(move)); + Bitboard attackerBB = index2bb(getFrom(move)); + int attackerPiece = bb2piece(attackerBB, &(position->board)); + + int attackerCount = 0; + for(int i = 0; i < moveCount; i++) { + Bitboard tgtBB = index2bb(getTo(moves[i])); + Bitboard atkBB = index2bb(getFrom(moves[i])); + + if(attackerPiece == bb2piece(atkBB, &(position->board)) && (targetBB & tgtBB)) { + attackerCount++; + } + } + + return attackerCount > 1; +} + +unsigned long hashPosition(Position* position) { + char fen[MAX_FEN_LEN]; + toMinFen(fen, position); + + unsigned long hash = 5381; + int c, i = 0; + + while((c = fen[i++])) { + hash = ((hash << 5) + hash) + c; + } + + return hash; +} + +void writeToHashFile(Position* position, int evaluation, int depth) { + FILE* fp = fopen("hashfile", "a"); + + if(fp == NULL) return; + + char fen[MAX_FEN_LEN]; + toMinFen(fen, position); + + fprintf(fp, "%08lx %d %d %s\n", hashPosition(position), depth, evaluation, fen); + fclose(fp); +} + +// ====== BOARD FILTERS ====== + +Bitboard getColoredPieces(Board* board, char color) { + if(color == WHITE) { + return board->whiteKing | board->whiteQueens | board->whiteRooks | board->whiteKnights | + board->whiteBishops | board->whitePawns; + } else { + return board->blackKing | board->blackQueens | board->blackRooks | board->blackKnights | + board->blackBishops | board->blackPawns; + } +} + +Bitboard getEmptySquares(Board* board) { + return not(getOccupiedSquares(board)); +} + +Bitboard getOccupiedSquares(Board* board) { + return getColoredPieces(board, WHITE) | getColoredPieces(board, BLACK); +} + +Bitboard getTwinPieces(Bitboard position, Board* board) { + if(position & board->whiteKing) return board->whiteKing; + if(position & board->whiteQueens) return board->whiteQueens; + if(position & board->whiteRooks) return board->whiteRooks; + if(position & board->whiteKnights) return board->whiteKnights; + if(position & board->whiteBishops) return board->whiteBishops; + if(position & board->whitePawns) return board->whitePawns; + + if(position & board->blackKing) return board->blackKing; + if(position & board->blackQueens) return board->blackQueens; + if(position & board->blackRooks) return board->blackRooks; + if(position & board->blackKnights) return board->blackKnights; + if(position & board->blackBishops) return board->blackBishops; + if(position & board->blackPawns) return board->blackPawns; + + return position; +} + +Bitboard fileFilter(Bitboard positions) { + Bitboard filter = 0; + int i; + + for(i = 0; i < 8; i++) + if(positions & FILES_BB[i]) filter |= FILES_BB[i]; + return filter; +} + +Bitboard rankFilter(Bitboard positions) { + Bitboard filter = 0; + int i; + + for(i = 0; i < 8; i++) + if(positions & RANKS_BB[i]) filter |= RANKS_BB[i]; + return filter; +} + +// ======= DIRECTIONS ======== + +Bitboard east(Bitboard bb) { + return (bb << 1) & 0xfefefefefefefefe; // not(FILE_A) +} + +Bitboard west(Bitboard bb) { + return (bb >> 1) & 0x7f7f7f7f7f7f7f7f; // not(FILE_H) +} + +Bitboard north(Bitboard bb) { + return (bb << 8) & 0xffffffffffffff00; // not(RANK_1) +} + +Bitboard south(Bitboard bb) { + return (bb >> 8) & 0x00ffffffffffffff; // not(RANK_8) +} + +Bitboard NE(Bitboard bb) { + return (bb << 9) & 0xfefefefefefefe00; // not(RANK_1 | FILE_A) +} + +Bitboard NW(Bitboard bb) { + return (bb << 7) & 0x7f7f7f7f7f7f7f00; // not(RANK_1 | FILE_H) +} + +Bitboard SE(Bitboard bb) { + return (bb >> 7) & 0x00fefefefefefefe; // not(RANK_8 | FILE_A) +} + +Bitboard SW(Bitboard bb) { + return (bb >> 9) & 0x007f7f7f7f7f7f7f; // not(RANK_8 | FILE_H) +} + +Bitboard WNW(Bitboard moving_piece) { + return (moving_piece << 6) & 0x3f3f3f3f3f3f3f00; // not(RANK_1 | FILE_G | FILE_H) +} + +Bitboard ENE(Bitboard moving_piece) { + return (moving_piece << 10) & 0xfcfcfcfcfcfcfc00; // not(RANK_1 | FILE_A | FILE_B) +} + +Bitboard NNW(Bitboard moving_piece) { + return (moving_piece << 15) & 0x7f7f7f7f7f7f0000; // not(RANK_1 | RANK_2 | FILE_H) +} + +Bitboard NNE(Bitboard moving_piece) { + return (moving_piece << 17) & 0xfefefefefefe0000; // not(RANK_1 | RANK_2 | FILE_A) +} + +Bitboard ESE(Bitboard moving_piece) { + return (moving_piece >> 6) & 0x00fcfcfcfcfcfcfc; // not(RANK_8| FILE_A | FILE_B) +} + +Bitboard WSW(Bitboard moving_piece) { + return (moving_piece >> 10) & 0x003f3f3f3f3f3f3f; // not(RANK_8 | FILE_G | FILE_H) +} + +Bitboard SSE(Bitboard moving_piece) { + return (moving_piece >> 15) & 0x0000fefefefefefe; // not(RANK_7 | RANK_8 | FILE_A) +} + +Bitboard SSW(Bitboard moving_piece) { + return (moving_piece >> 17) & 0x00007f7f7f7f7f7f; // not(RANK_7 | RANK_8 | FILE_H) +} + +// ========== PAWN =========== + +Bitboard getPawns(Board* board) { + return board->whitePawns | board->blackPawns; +} + +Bitboard pawnSimplePushes(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return north(moving_piece) & getEmptySquares(board); + case BLACK: + return south(moving_piece) & getEmptySquares(board); + } + return 0; +} + +Bitboard pawnDoublePushes(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return north(pawnSimplePushes(moving_piece, board, color)) & + (getEmptySquares(board) & RANK_4); + case BLACK: + return south(pawnSimplePushes(moving_piece, board, color)) & + (getEmptySquares(board) & RANK_5); + } + return 0; +} + +Bitboard pawnPushes(Bitboard moving_piece, Board* board, char color) { + return pawnSimplePushes(moving_piece, board, color) | + pawnDoublePushes(moving_piece, board, color); +} + +Bitboard pawnEastAttacks(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return NE(moving_piece); + case BLACK: + return SE(moving_piece); + } + return 0; +} + +Bitboard pawnWestAttacks(Bitboard moving_piece, Board* board, char color) { + switch(color) { + case WHITE: + return NW(moving_piece); + case BLACK: + return SW(moving_piece); + } + return 0; +} + +Bitboard pawnAttacks(Bitboard moving_piece, Board* board, char color) { + return pawnEastAttacks(moving_piece, board, color) | + pawnWestAttacks(moving_piece, board, color); +} + +Bitboard pawnSimpleCaptures(Bitboard moving_piece, Board* board, char color) { + return pawnAttacks(moving_piece, board, color) & getColoredPieces(board, opponent(color)); +} + +Bitboard pawnEpCaptures(Bitboard moving_piece, Position* position, char color) { + if(position->epSquare == -1) return 0; + + Bitboard valid_ep_square = 0; + + switch(color) { + case WHITE: + valid_ep_square = index2bb(position->epSquare) & RANK_6; + break; + case BLACK: + valid_ep_square = index2bb(position->epSquare) & RANK_3; + break; + } + + return pawnAttacks(moving_piece, &(position->board), color) & valid_ep_square; +} + +Bitboard pawnCaptures(Bitboard moving_piece, Position* position, char color) { + return pawnSimpleCaptures(moving_piece, &(position->board), color) | + pawnEpCaptures(moving_piece, position, color); +} + +Bitboard pawnMoves(Bitboard moving_piece, Position* position, char color) { + return pawnPushes(moving_piece, &(position->board), color) | + pawnCaptures(moving_piece, position, color); +} + +BOOL isDoublePush(int leaving, int arriving) { + if((index2bb(leaving) & RANK_2) && (index2bb(arriving) & RANK_4)) return TRUE; + if((index2bb(leaving) & RANK_7) && (index2bb(arriving) & RANK_5)) return TRUE; + return FALSE; +} + +char getEpSquare(int leaving) { + if(index2bb(leaving) & RANK_2) return leaving + 8; + if(index2bb(leaving) & RANK_7) return leaving - 8; + return -1; +} + +BOOL isDoubledPawn(Bitboard position, Board* board, char color) { + if(color == WHITE) { + if(countBits(board->whitePawns & fileFilter(position)) > 1) return TRUE; + } else { + if(countBits(board->blackPawns & fileFilter(position)) > 1) return TRUE; + } + + return FALSE; +} + +BOOL isIsolatedPawn(Bitboard position, Board* board, char color) { + Bitboard sideFiles = fileFilter(east(position) | west(position)); + + if(color == WHITE) { + if(countBits(board->whitePawns & sideFiles) == 0) return TRUE; + } else { + if(countBits(board->blackPawns & sideFiles) == 0) return TRUE; + } + + return FALSE; +} + +BOOL isBackwardsPawn(Bitboard position, Board* board, char color) { + Bitboard squaresFilter = east(position) | west(position); + + if(color == BLACK) { + squaresFilter |= northRay(squaresFilter); + if(countBits(board->blackPawns & squaresFilter) == 0) return TRUE; + } else { + squaresFilter |= southRay(squaresFilter); + if(countBits(board->whitePawns & squaresFilter) == 0) return TRUE; + } + + return FALSE; +} + +BOOL isPassedPawn(Bitboard position, Board* board, char color) { + Bitboard squaresFilter = 0; + + if(color == BLACK) { + squaresFilter |= southRay(east(position)) | southRay(west(position)) | southRay(position); + if(countBits(board->whitePawns & squaresFilter) == 0) return TRUE; + } else { + squaresFilter |= northRay(east(position)) | northRay(west(position)) | northRay(position); + if(countBits(board->blackPawns & squaresFilter) == 0) return TRUE; + } + + return FALSE; +} + +BOOL isOpenFile(Bitboard position, Board* board) { + if(countBits(getPawns(board) & fileFilter(position)) == 0) return TRUE; + return FALSE; +} + +BOOL isSemiOpenFile(Bitboard position, Board* board) { + if(countBits(getPawns(board) & fileFilter(position)) == 1) return TRUE; + return FALSE; +} + +// ========== KNIGHT ========= + +Bitboard getKnights(Board* board) { + return board->whiteKnights | board->blackKnights; +} + +Bitboard knightAttacks(Bitboard moving_piece) { + return NNE(moving_piece) | ENE(moving_piece) | NNW(moving_piece) | WNW(moving_piece) | + SSE(moving_piece) | ESE(moving_piece) | SSW(moving_piece) | WSW(moving_piece); +} + +Bitboard knightMoves(Bitboard moving_piece, Board* board, char color) { + return knightAttacks(moving_piece) & not(getColoredPieces(board, color)); +} + +Bitboard knightFill(Bitboard moving_piece, int jumps) { + Bitboard fill = moving_piece; + int i; + for(i = 0; i < jumps; i++) { + fill |= knightAttacks(fill); + } + return fill; +} + +int knightDistance(Bitboard leaving_square, Bitboard arriving_square) { + Bitboard fill = leaving_square; + int dist = 0; + + while((fill & arriving_square) == 0) { + dist++; + fill |= knightAttacks(fill); + } + return dist; +} + +// ========== KING =========== + +Bitboard getKing(Board* board, char color) { + if(color == WHITE) { + return board->whiteKing; + } else { + return board->blackKing; + } +} + +Bitboard kingAttacks(Bitboard moving_piece) { + Bitboard kingAtks = moving_piece | east(moving_piece) | west(moving_piece); + kingAtks |= north(kingAtks) | south(kingAtks); + return kingAtks & not(moving_piece); +} + +Bitboard kingMoves(Bitboard moving_piece, Board* board, char color) { + return kingAttacks(moving_piece) & not(getColoredPieces(board, color)); +} + +BOOL canCastleKingside(Position* position, char color) { + Bitboard emptyPositions = getEmptySquares(&(position->board)); + + switch(color) { + case WHITE: + if((position->castlingRights & CASTLE_KINGSIDE_WHITE) && + (FILE_E & RANK_1 & position->board.whiteKing) && (FILE_F & RANK_1 & emptyPositions) && + (FILE_G & RANK_1 & emptyPositions) && (FILE_H & RANK_1 & position->board.whiteRooks) && + (!isAttacked((FILE_E & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_F & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_G & RANK_1), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + + case BLACK: + if((position->castlingRights & CASTLE_KINGSIDE_BLACK) && + (FILE_E & RANK_8 & position->board.blackKing) && (FILE_F & RANK_8 & emptyPositions) && + (FILE_G & RANK_8 & emptyPositions) && (FILE_H & RANK_8 & position->board.blackRooks) && + (!isAttacked((FILE_E & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_F & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_G & RANK_8), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + } + return FALSE; +} + +BOOL canCastleQueenside(Position* position, char color) { + Bitboard emptyPositions = getEmptySquares(&(position->board)); + + switch(color) { + case WHITE: + if((position->castlingRights & CASTLE_QUEENSIDE_WHITE) && + (FILE_A & RANK_1 & position->board.whiteRooks) && (FILE_B & RANK_1 & emptyPositions) && + (FILE_C & RANK_1 & emptyPositions) && (FILE_D & RANK_1 & emptyPositions) && + (FILE_E & RANK_1 & position->board.whiteKing) && + (!isAttacked((FILE_C & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_D & RANK_1), &(position->board), opponent(color))) && + (!isAttacked((FILE_E & RANK_1), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + + case BLACK: + if((position->castlingRights & CASTLE_QUEENSIDE_BLACK) && + (FILE_A & RANK_8 & position->board.blackRooks) && (FILE_B & RANK_8 & emptyPositions) && + (FILE_C & RANK_8 & emptyPositions) && (FILE_D & RANK_8 & emptyPositions) && + (FILE_E & RANK_8 & position->board.blackKing) && + (!isAttacked((FILE_C & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_D & RANK_8), &(position->board), opponent(color))) && + (!isAttacked((FILE_E & RANK_8), &(position->board), opponent(color)))) + return TRUE; + else + return FALSE; + } + return FALSE; +} + +char removeCastlingRights(char original_rights, char removed_rights) { + return (char)(original_rights & ~(removed_rights)); +} + +// ========== BISHOP ========= + +Bitboard getBishops(Board* board) { + return board->whiteBishops | board->blackBishops; +} + +Bitboard NE_ray(Bitboard bb) { + int i; + Bitboard ray = NE(bb); + + for(i = 0; i < 6; i++) { + ray |= NE(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard SE_ray(Bitboard bb) { + int i; + Bitboard ray = SE(bb); + + for(i = 0; i < 6; i++) { + ray |= SE(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard NW_ray(Bitboard bb) { + int i; + Bitboard ray = NW(bb); + + for(i = 0; i < 6; i++) { + ray |= NW(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard SW_ray(Bitboard bb) { + int i; + Bitboard ray = SW(bb); + + for(i = 0; i < 6; i++) { + ray |= SW(ray); + } + + return ray & ALL_SQUARES; +} + +Bitboard NE_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(NE_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return NE_ray(single_piece) ^ NE_ray(blocker); + } else { + return NE_ray(single_piece); + } +} + +Bitboard NW_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(NW_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return NW_ray(single_piece) ^ NW_ray(blocker); + } else { + return NW_ray(single_piece); + } +} + +Bitboard SE_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(SE_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return SE_ray(single_piece) ^ SE_ray(blocker); + } else { + return SE_ray(single_piece); + } +} + +Bitboard SW_attack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(SW_ray(single_piece) & getOccupiedSquares(board)); + if(blocker) { + return SW_ray(single_piece) ^ SW_ray(blocker); + } else { + return SW_ray(single_piece); + } +} + +Bitboard diagonalAttacks(Bitboard single_piece, Board* board, char color) { + return NE_attack(single_piece, board, color) | SW_attack(single_piece, board, color); +} + +Bitboard antiDiagonalAttacks(Bitboard single_piece, Board* board, char color) { + return NW_attack(single_piece, board, color) | SE_attack(single_piece, board, color); +} + +Bitboard bishopAttacks(Bitboard moving_pieces, Board* board, char color) { + return diagonalAttacks(moving_pieces, board, color) | + antiDiagonalAttacks(moving_pieces, board, color); +} + +Bitboard bishopMoves(Bitboard moving_piece, Board* board, char color) { + return bishopAttacks(moving_piece, board, color) & not(getColoredPieces(board, color)); +} + +// ========== ROOK =========== + +Bitboard getRooks(Board* board) { + return board->whiteRooks | board->blackRooks; +} + +Bitboard northRay(Bitboard moving_pieces) { + Bitboard ray_atks = north(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= north(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard southRay(Bitboard moving_pieces) { + Bitboard ray_atks = south(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= south(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard eastRay(Bitboard moving_pieces) { + Bitboard ray_atks = east(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= east(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard westRay(Bitboard moving_pieces) { + Bitboard ray_atks = west(moving_pieces); + + int i; + for(i = 0; i < 6; i++) { + ray_atks |= west(ray_atks); + } + + return ray_atks & ALL_SQUARES; +} + +Bitboard northAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(northRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return northRay(single_piece) ^ northRay(blocker); + else + return northRay(single_piece); +} + +Bitboard southAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(southRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return southRay(single_piece) ^ southRay(blocker); + else + return southRay(single_piece); +} + +Bitboard fileAttacks(Bitboard single_piece, Board* board, char color) { + return northAttack(single_piece, board, color) | southAttack(single_piece, board, color); +} + +Bitboard eastAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = lsb(eastRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return eastRay(single_piece) ^ eastRay(blocker); + else + return eastRay(single_piece); +} + +Bitboard westAttack(Bitboard single_piece, Board* board, char color) { + Bitboard blocker = msb(westRay(single_piece) & getOccupiedSquares(board)); + + if(blocker) + return westRay(single_piece) ^ westRay(blocker); + else + return westRay(single_piece); +} + +Bitboard rankAttacks(Bitboard single_piece, Board* board, char color) { + return eastAttack(single_piece, board, color) | westAttack(single_piece, board, color); +} + +Bitboard rookAttacks(Bitboard moving_piece, Board* board, char color) { + return fileAttacks(moving_piece, board, color) | rankAttacks(moving_piece, board, color); +} + +Bitboard rookMoves(Bitboard moving_piece, Board* board, char color) { + return rookAttacks(moving_piece, board, color) & not(getColoredPieces(board, color)); +} + +// ========== QUEEN ========== + +Bitboard getQueens(Board* board) { + return board->whiteQueens | board->blackQueens; +} + +Bitboard queenAttacks(Bitboard moving_piece, Board* board, char color) { + return rookAttacks(moving_piece, board, color) | bishopAttacks(moving_piece, board, color); +} + +Bitboard queenMoves(Bitboard moving_piece, Board* board, char color) { + return rookMoves(moving_piece, board, color) | bishopMoves(moving_piece, board, color); +} + +// ======== MAKE MOVE ======== + +void clearPositions(Board* board, Bitboard positions) { + board->whiteKing = board->whiteKing & not(positions); + board->whiteQueens = board->whiteQueens & not(positions); + board->whiteRooks = board->whiteRooks & not(positions); + board->whiteKnights = board->whiteKnights & not(positions); + board->whiteBishops = board->whiteBishops & not(positions); + board->whitePawns = board->whitePawns & not(positions); + board->blackKing = board->blackKing & not(positions); + board->blackQueens = board->blackQueens & not(positions); + board->blackRooks = board->blackRooks & not(positions); + board->blackKnights = board->blackKnights & not(positions); + board->blackBishops = board->blackBishops & not(positions); + board->blackPawns = board->blackPawns & not(positions); +} + +void movePiece(Board* board, Move move) { + Bitboard leaving = index2bb(getFrom(move)); + Bitboard arriving = index2bb(getTo(move)); + + if(leaving & board->whiteKing) { + clearPositions(board, arriving | leaving); + board->whiteKing = arriving; + } else if(leaving & board->whiteQueens) { + clearPositions(board, arriving | leaving); + board->whiteQueens = board->whiteQueens | arriving; + } else if(leaving & board->whiteRooks) { + clearPositions(board, arriving | leaving); + board->whiteRooks = board->whiteRooks | arriving; + } else if(leaving & board->whiteKnights) { + clearPositions(board, arriving | leaving); + board->whiteKnights = board->whiteKnights | arriving; + } else if(leaving & board->whiteBishops) { + clearPositions(board, arriving | leaving); + board->whiteBishops = board->whiteBishops | arriving; + } else if(leaving & board->whitePawns) { + clearPositions(board, arriving | leaving); + board->whitePawns = board->whitePawns | arriving; + } else if(leaving & board->blackKing) { + clearPositions(board, arriving | leaving); + board->blackKing = arriving; + } else if(leaving & board->blackQueens) { + clearPositions(board, arriving | leaving); + board->blackQueens = board->blackQueens | arriving; + } else if(leaving & board->blackRooks) { + clearPositions(board, arriving | leaving); + board->blackRooks = board->blackRooks | arriving; + } else if(leaving & board->blackKnights) { + clearPositions(board, arriving | leaving); + board->blackKnights = board->blackKnights | arriving; + } else if(leaving & board->blackBishops) { + clearPositions(board, arriving | leaving); + board->blackBishops = board->blackBishops | arriving; + } else if(leaving & board->blackPawns) { + clearPositions(board, arriving | leaving); + board->blackPawns = board->blackPawns | arriving; + } +} + +void updatePosition(Position* newPosition, Position* position, Move move) { + memcpy(newPosition, position, sizeof(Position)); + int leavingSquare = getFrom(move); + int arrivingSquare = getTo(move); + Bitboard leavingBB = index2bb(leavingSquare); + Bitboard arrivingBB = index2bb(arrivingSquare); + + // ===== MOVE PIECE ===== + movePiece(&(newPosition->board), move); + + // ===== TO MOVE ===== + newPosition->toMove = opponent(position->toMove); + + // ===== MOVE COUNTS ===== + newPosition->halfmoveClock += 1; + if(position->toMove == BLACK) { + newPosition->fullmoveNumber += 1; + } + + if(arrivingBB & getOccupiedSquares(&(position->board))) { + newPosition->halfmoveClock = 0; + } + + // ===== PAWNS ===== + newPosition->epSquare = -1; + if((leavingBB & position->board.whitePawns) | (leavingBB & position->board.blackPawns)) { + newPosition->halfmoveClock = 0; + + if(arrivingSquare == position->epSquare) { + if(index2bb(position->epSquare) & RANK_3) { + clearPositions(&(newPosition->board), index2bb((int)(position->epSquare + 8))); + } + + if(index2bb(position->epSquare) & RANK_6) { + clearPositions(&(newPosition->board), index2bb((int)(position->epSquare - 8))); + } + } + + if(isDoublePush(leavingSquare, arrivingSquare)) { + newPosition->epSquare = getEpSquare(leavingSquare); + } + + if(arrivingBB & (RANK_1 | RANK_8)) { + clearPositions(&(newPosition->board), arrivingBB); + + if(position->toMove == WHITE) { + newPosition->board.whiteQueens = newPosition->board.whiteQueens | arrivingBB; + } else { + newPosition->board.blackQueens = newPosition->board.blackQueens | arrivingBB; + } + } + } + + // ===== CASTLING ===== + if(leavingSquare == str2index("a1")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_QUEENSIDE_WHITE); + } else if(leavingSquare == str2index("h1")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_KINGSIDE_WHITE); + } else if(leavingSquare == str2index("a8")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_QUEENSIDE_BLACK); + } else if(leavingSquare == str2index("h8")) { + newPosition->castlingRights = + removeCastlingRights(newPosition->castlingRights, CASTLE_KINGSIDE_BLACK); + } + + if(leavingBB & position->board.whiteKing) { + newPosition->castlingRights = removeCastlingRights( + newPosition->castlingRights, (CASTLE_KINGSIDE_WHITE | CASTLE_QUEENSIDE_WHITE)); + if(leavingSquare == str2index("e1")) { + if(arrivingSquare == str2index("g1")) + movePiece(&(newPosition->board), generateMove(str2index("h1"), str2index("f1"))); + + if(arrivingSquare == str2index("c1")) + movePiece(&(newPosition->board), generateMove(str2index("a1"), str2index("d1"))); + } + } else if(leavingBB & position->board.blackKing) { + newPosition->castlingRights = removeCastlingRights( + newPosition->castlingRights, CASTLE_KINGSIDE_BLACK | CASTLE_QUEENSIDE_BLACK); + if(leavingSquare == str2index("e8")) { + if(arrivingSquare == str2index("g8")) + movePiece(&(newPosition->board), generateMove(str2index("h8"), str2index("f8"))); + + if(arrivingSquare == str2index("c8")) + movePiece(&(newPosition->board), generateMove(str2index("a8"), str2index("d8"))); + } + } +} + +void makeMove(Game* game, Move move) { + Position newPosition; + updatePosition(&newPosition, &(game->position), move); + memcpy(&(game->position), &newPosition, sizeof(Position)); + + game->moveListLen += 1; + + // ===== MOVE LIST ===== + game->moveList[game->moveListLen - 1] = move; + + // ===== POSITION HISTORY ===== + toFen(game->positionHistory[game->moveListLen], &game->position); +} + +void unmakeMove(Game* game) { + Position newPosition; + if(game->moveListLen >= 1) { + loadFen(&newPosition, game->positionHistory[game->moveListLen - 1]); + memcpy(&(game->position), &newPosition, sizeof(Position)); + + game->moveList[game->moveListLen - 1] = 0; + memset(game->positionHistory[game->moveListLen], 0, MAX_FEN_LEN * sizeof(char)); + + game->moveListLen -= 1; + } else { // return to initial game + loadFen(&newPosition, game->positionHistory[0]); + memcpy(&(game->position), &newPosition, sizeof(Position)); + + game->moveListLen = 0; + memset(game->moveList, 0, MAX_PLYS_PER_GAME * sizeof(int)); + memset(&game->positionHistory[1], 0, (MAX_PLYS_PER_GAME - 1) * MAX_FEN_LEN * sizeof(char)); + } +} + +// ======== MOVE GEN ========= + +Bitboard getMoves(Bitboard movingPiece, Position* position, char color) { + if((movingPiece & position->board.whitePawns) | (movingPiece & position->board.blackPawns)) + return pawnMoves(movingPiece, position, color); + if((movingPiece & position->board.whiteKnights) | (movingPiece & position->board.blackKnights)) + return knightMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteBishops) | (movingPiece & position->board.blackBishops)) + return bishopMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteRooks) | (movingPiece & position->board.blackRooks)) + return rookMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteQueens) | (movingPiece & position->board.blackQueens)) + return queenMoves(movingPiece, &(position->board), color); + if((movingPiece & position->board.whiteKing) | (movingPiece & position->board.blackKing)) + return kingMoves(movingPiece, &(position->board), color); + + return 0; +} + +int pseudoLegalMoves(Move* moves, Position* position, char color) { + int leavingSquare, arrivingSquare, moveCount = 0; + Bitboard leavingBB = 1; + Bitboard attackers = getColoredPieces(&(position->board), color); + + for(leavingSquare = 0; leavingSquare < NUM_SQUARES; leavingSquare++) { + if(leavingBB & attackers) { + Bitboard targets = getMoves(leavingBB, position, color); + + for(arrivingSquare = 0; arrivingSquare < NUM_SQUARES; arrivingSquare++) { + if(isSet(targets, arrivingSquare)) { + moves[moveCount++] = generateMove(leavingSquare, arrivingSquare); + } + } + + if((leavingBB & position->board.whiteKing) | (leavingBB & position->board.blackKing)) { + if(canCastleKingside(position, color)) { + moves[moveCount++] = generateMove(leavingSquare, leavingSquare + 2); + } + if(canCastleQueenside(position, color)) { + moves[moveCount++] = generateMove(leavingSquare, leavingSquare - 2); + } + } + } + leavingBB <<= 1; + } + + return moveCount; +} + +Bitboard getAttacks(Bitboard movingPiece, Board* board, char color) { + if((movingPiece & board->whitePawns) | (movingPiece & board->blackPawns)) + return pawnAttacks(movingPiece, board, color); + if((movingPiece & board->whiteKnights) | (movingPiece & board->blackKnights)) + return knightAttacks(movingPiece); + if((movingPiece & board->whiteBishops) | (movingPiece & board->blackBishops)) + return bishopAttacks(movingPiece, board, color); + if((movingPiece & board->whiteRooks) | (movingPiece & board->blackRooks)) + return rookAttacks(movingPiece, board, color); + if((movingPiece & board->whiteQueens) | (movingPiece & board->blackQueens)) + return queenAttacks(movingPiece, board, color); + if((movingPiece & board->whiteKing) | (movingPiece & board->blackKing)) + return kingAttacks(movingPiece); + + return 0; +} + +int countAttacks(Bitboard target, Board* board, char color) { + int i, attackCount = 0; + + Bitboard attackers = getColoredPieces(board, color); + Bitboard position = 1; + + for(i = 0; i < NUM_SQUARES; i++) { + if(position & attackers) { + if(getAttacks(position, board, color) & target) { + attackCount += 1; + } + } + position = position << 1; + } + + return attackCount; +} + +BOOL isAttacked(Bitboard target, Board* board, char color) { + int i = 0; + + Bitboard attackers = getColoredPieces(board, color); + Bitboard position = 1; + + for(i = 0; i < NUM_SQUARES; i++) { + if(position & attackers) { + if(getAttacks(position, board, color) & target) { + return TRUE; + } + } + position = position << 1; + } + + return FALSE; +} + +BOOL isCheck(Board* board, char color) { + return isAttacked(getKing(board, color), board, opponent(color)); +} + +BOOL isLegalMove(Position* position, Move move) { + Position newPosition; + updatePosition(&newPosition, position, move); + if(isCheck(&(newPosition.board), position->toMove)) return FALSE; + return TRUE; +} + +int legalMoves(Move* legalMoves, Position* position, char color) { + int i, legalCount = 0; + + Move pseudoMoves[MAX_BRANCHING_FACTOR]; + int pseudoCount = pseudoLegalMoves(pseudoMoves, position, color); + + for(i = 0; i < pseudoCount; i++) { + if(isLegalMove(position, pseudoMoves[i])) { + legalMoves[legalCount++] = pseudoMoves[i]; + } + } + + return legalCount; +} + +int legalMovesCount(Position* position, char color) { + int i, legalCount = 0; + + Move pseudoMoves[MAX_BRANCHING_FACTOR]; + int pseudoCount = pseudoLegalMoves(pseudoMoves, position, color); + + for(i = 0; i < pseudoCount; i++) { + if(isLegalMove(position, pseudoMoves[i])) { + legalCount++; + } + } + + return legalCount; +} + +int staticOrderLegalMoves(Move* orderedLegalMoves, Position* position, char color) { + Move moves[MAX_BRANCHING_FACTOR]; + int legalCount = legalMoves(moves, position, color); + + Position newPosition; + Node nodes[legalCount], orderedNodes[legalCount]; + + int i; + for(i = 0; i < legalCount; i++) { + updatePosition(&newPosition, position, moves[i]); + nodes[i] = (Node){.move = moves[i], .score = staticEvaluation(&newPosition)}; + } + + sortNodes(orderedNodes, nodes, legalCount, color); + + for(i = 0; i < legalCount; i++) { + orderedLegalMoves[i] = orderedNodes[i].move; + } + + return legalCount; +} + +int legalCaptures(Move* legalCaptures, Position* position, char color) { + int i, captureCount = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int legalCount = legalMoves(moves, position, color); + + for(i = 0; i < legalCount; i++) { + int arrivingSquare = getTo(moves[i]); + if(index2bb(arrivingSquare) & getColoredPieces(&(position->board), opponent(color))) { + legalCaptures[captureCount++] = moves[i]; + } + } + + return captureCount; +} + +// ====== GAME CONTROL ======= + +BOOL isCheckmate(Position* position) { + if(isCheck(&(position->board), position->toMove) && + legalMovesCount(position, position->toMove) == 0) + return TRUE; + else + return FALSE; +} + +BOOL isStalemate(Position* position) { + if(!isCheck(&(position->board), position->toMove) && + legalMovesCount(position, position->toMove) == 0) + return TRUE; + else + return FALSE; +} + +BOOL hasInsufficientMaterial(Board* board) { + int pieceCount = countBits(getOccupiedSquares(board)); + + if(pieceCount <= 3) { + if(pieceCount == 2 || getKnights(board) != 0 || getBishops(board) != 0) return TRUE; + } + + return FALSE; +} + +BOOL isEndgame(Board* board) { + if(countBits(getOccupiedSquares(board)) <= ENDGAME_PIECE_COUNT) return TRUE; + return FALSE; +} + +BOOL isOver75MovesRule(Position* position) { + if(position->halfmoveClock >= 150) + return TRUE; + else + return FALSE; +} + +BOOL hasGameEnded(Position* position) { + if(isCheckmate(position) || isStalemate(position) || + hasInsufficientMaterial(&(position->board)) || isOver75MovesRule(position)) + return TRUE; + else + return FALSE; +} + +void printOutcome(Position* position) { + if(isCheckmate(position) && position->toMove == BLACK) printf("WHITE wins!\n"); + if(isCheckmate(position) && position->toMove == WHITE) printf("BLACK wins!\n"); + if(isStalemate(position)) printf("Draw by stalemate!\n"); + if(hasInsufficientMaterial(&(position->board))) printf("Draw by insufficient material!\n"); + if(isOver75MovesRule(position)) printf("Draw by 75 move rule!\n"); + fflush(stdout); +} + +// ========== EVAL =========== + +int winScore(char color) { + if(color == WHITE) return 10 * PIECE_VALUES[KING]; + if(color == BLACK) return -10 * PIECE_VALUES[KING]; + return 0; +} + +int materialSum(Board* board, char color) { + if(color == WHITE) { + return PIECE_VALUES[PAWN] * countBits(board->whitePawns) + + PIECE_VALUES[KNIGHT] * countBits(board->whiteKnights) + + PIECE_VALUES[BISHOP] * countBits(board->whiteBishops) + + PIECE_VALUES[ROOK] * countBits(board->whiteRooks) + + PIECE_VALUES[QUEEN] * countBits(board->whiteQueens); + } else { + return PIECE_VALUES[PAWN] * countBits(board->blackPawns) + + PIECE_VALUES[KNIGHT] * countBits(board->blackKnights) + + PIECE_VALUES[BISHOP] * countBits(board->blackBishops) + + PIECE_VALUES[ROOK] * countBits(board->blackRooks) + + PIECE_VALUES[QUEEN] * countBits(board->blackQueens); + } +} + +int materialBalance(Board* board) { + return materialSum(board, WHITE) - materialSum(board, BLACK); +} + +int positionalBonus(Board* board, char color) { + int bonus = 0; + Bitboard positionBB; + Bitboard coloredPieces = getColoredPieces(board, color); + + int i; + for(i = 0; i < NUM_SQUARES; i++) { + positionBB = index2bb(i); + + if(positionBB & coloredPieces) { + if((positionBB & board->whitePawns) | (positionBB & board->blackPawns)) { + if(color == WHITE) { + bonus += PAWN_BONUS[i]; + } else { + bonus += PAWN_BONUS[FLIP_VERTICAL[i]]; + } + + if(isDoubledPawn(positionBB, board, color)) { + bonus -= DOUBLED_PAWN_PENALTY / 2; + } + if(isPassedPawn(positionBB, board, color)) { + bonus += PASSED_PAWN_BONUS; + } + + if(isIsolatedPawn(positionBB, board, color)) { + bonus -= ISOLATED_PAWN_PENALTY; + } else if(isBackwardsPawn(positionBB, board, color)) { + bonus -= BACKWARDS_PAWN_PENALTY; + } + } else if((positionBB & board->whiteKnights) | (positionBB & board->blackKnights)) { + if(color == WHITE) { + bonus += KNIGHT_BONUS[i]; + } else { + bonus += KNIGHT_BONUS[FLIP_VERTICAL[i]]; + } + } else if((positionBB & board->whiteBishops) | (positionBB & board->blackBishops)) { + if(color == WHITE) { + bonus += BISHOP_BONUS[i]; + } else { + bonus += BISHOP_BONUS[FLIP_VERTICAL[i]]; + } + } else if((positionBB & board->whiteRooks) | (positionBB & board->blackRooks)) { + if(isOpenFile(positionBB, board)) { + bonus += ROOK_OPEN_FILE_BONUS; + } else if(isSemiOpenFile(positionBB, board)) { + bonus += ROOK_SEMI_OPEN_FILE_BONUS; + } + + if(color == WHITE) { + if(positionBB & RANK_7) { + bonus += ROOK_ON_SEVENTH_BONUS; + } + } else { + if(positionBB & RANK_2) { + bonus += ROOK_ON_SEVENTH_BONUS; + } + } + } else if((positionBB & board->whiteKing) | (positionBB & board->blackKing)) { + if(isEndgame(board)) { + if(color == WHITE) { + bonus += KING_ENDGAME_BONUS[i]; + } else { + bonus += KING_ENDGAME_BONUS[FLIP_VERTICAL[i]]; + } + } else { + if(color == WHITE) { + bonus += KING_BONUS[i]; + } else { + bonus += KING_BONUS[FLIP_VERTICAL[i]]; + } + } + } + } + } + + return bonus; +} + +int positionalBalance(Board* board) { + return positionalBonus(board, WHITE) - positionalBonus(board, BLACK); +} + +int endNodeEvaluation(Position* position) { + if(isCheckmate(position)) { + return winScore(opponent(position->toMove)); + } + if(isStalemate(position) || hasInsufficientMaterial(&(position->board)) || + isOver75MovesRule(position)) { + return 0; + } + return 0; +} + +int staticEvaluation(Position* position) { + if(hasGameEnded(position)) + return endNodeEvaluation(position); + else + return materialBalance(&(position->board)) + positionalBalance(&(position->board)); +} + +int getPieceValue(Bitboard position, Board* board) { + if((position & board->whitePawns) | (position & board->blackPawns)) return PIECE_VALUES[PAWN]; + if((position & board->whiteKnights) | (position & board->blackKnights)) + return PIECE_VALUES[KNIGHT]; + if((position & board->whiteBishops) | (position & board->blackBishops)) + return PIECE_VALUES[BISHOP]; + if((position & board->whiteRooks) | (position & board->blackRooks)) return PIECE_VALUES[ROOK]; + if((position & board->whiteQueens) | (position & board->blackQueens)) + return PIECE_VALUES[QUEEN]; + if((position & board->whiteKing) | (position & board->blackKing)) return PIECE_VALUES[KING]; + return 0; +} + +int getCaptureSequence(Move* captures, Position* position, int targetSquare) { + Move allCaptures[MAX_BRANCHING_FACTOR], targetCaptures[MAX_ATTACKING_PIECES]; + int captureCount = legalCaptures(allCaptures, position, position->toMove); + int i, j, targetCount = 0; + + for(i = 0; i < captureCount; i++) { + if(getTo(allCaptures[i]) == targetSquare) { + targetCaptures[targetCount++] = allCaptures[i]; + } + } + + Move captureBuffer[targetCount]; + + BOOL sorted; + for(i = 0; i < targetCount; i++) { + sorted = FALSE; + int pieceValue = getPieceValue(index2bb(getFrom(targetCaptures[i])), &(position->board)); + + for(j = 0; j < i; j++) { + int sortedPieceValue = + getPieceValue(index2bb(getFrom(captures[j])), &(position->board)); + + if(pieceValue < sortedPieceValue) { + sorted = TRUE; + memcpy(captureBuffer, &captures[j], (i - j) * sizeof(Move)); + memcpy(&captures[j + 1], captureBuffer, (i - j) * sizeof(Move)); + captures[j] = targetCaptures[i]; + break; + } + } + + if(sorted == FALSE) { + captures[i] = targetCaptures[i]; + } + } + + return targetCount; +} + +int staticExchangeEvaluation(Position* position, int targetSquare) { + Move captures[MAX_ATTACKING_PIECES]; + int attackCount = getCaptureSequence(captures, position, targetSquare); + int value = 0; + + if(attackCount > 0) { + Position newPosition; + updatePosition(&newPosition, position, captures[0]); + int pieceValue = getPieceValue(index2bb(targetSquare), &(position->board)); + value = pieceValue - staticExchangeEvaluation(&newPosition, targetSquare); + } + + return value > 0 ? value : 0; +} + +int quiescenceEvaluation(Position* position) { + int staticScore = staticEvaluation(position); + + if(hasGameEnded(position)) return staticScore; + + Move captures[MAX_BRANCHING_FACTOR]; + int captureCount = legalCaptures(captures, position, position->toMove); + + if(captureCount == 0) { + return staticScore; + } else { + Position newPosition; + int i, bestScore = staticScore; + + for(i = 0; i < captureCount; i++) { + if(staticExchangeEvaluation(position, getTo(captures[i])) <= 0) continue; + + updatePosition(&newPosition, position, captures[i]); + int score = quiescenceEvaluation(&newPosition); + + if((position->toMove == WHITE && score > bestScore) || + (position->toMove == BLACK && score < bestScore)) { + bestScore = score; + } + } + + return bestScore; + } +} + +// ========= SEARCH ========== + +Node staticSearch(Position* position) { + int bestScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX; + Move bestMove = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + int score = staticEvaluation(&newPosition); + + if(score == winScore(position->toMove)) { + return (Node){.move = moves[i], .score = score}; + } + + if((position->toMove == WHITE && score > bestScore) || + (position->toMove == BLACK && score < bestScore)) { + bestScore = score; + bestMove = moves[i]; + } + } + + return (Node){.move = bestMove, .score = bestScore}; +} + +Node quiescenceSearch(Position* position) { + int bestScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX; + Move bestMove = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + int score = quiescenceEvaluation(&newPosition); + + if(score == winScore(position->toMove)) { + return (Node){.move = moves[i], .score = score}; + } + + if((position->toMove == WHITE && score > bestScore) || + (position->toMove == BLACK && score < bestScore)) { + bestScore = score; + bestMove = moves[i]; + } + } + + return (Node){.move = bestMove, .score = bestScore}; +} + +Node alphaBeta(Position* position, char depth, int alpha, int beta) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth == 1) return staticSearch(position); + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) return staticNode; + + Move bestMove = 0; + + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = staticOrderLegalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + + int score = alphaBeta(&newPosition, depth - 1, alpha, beta).score; + + if(score == winScore(position->toMove)) { + return (Node){.move = moves[i], .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = moves[i]; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = moves[i]; + } + + if(alpha > beta) { + break; + } + } + + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +int alphaBetaNodes(Node* sortedNodes, Position* position, char depth) { + Node nodes[MAX_BRANCHING_FACTOR]; + Move moves[MAX_BRANCHING_FACTOR]; + int moveCount = legalMoves(moves, position, position->toMove); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, moves[i]); + + nodes[i].move = moves[i]; + nodes[i].score = depth > 1 ? + alphaBeta(&newPosition, depth - 1, INT32_MIN, INT32_MAX).score : + staticEvaluation(&newPosition); + } + + sortNodes(sortedNodes, nodes, moveCount, position->toMove); + + return moveCount; +} + +Node iterativeDeepeningAlphaBeta(Position* position, char depth, int alpha, int beta, BOOL verbose) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth == 1) return quiescenceSearch(position); + // return staticSearch(position); + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) return staticNode; + + Move bestMove = 0; + + if(verbose) { + printf("Ordering moves...\n"); + fflush(stdout); + } + + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + Position newPosition; + int i; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, nodes[i].move); + + if(verbose) { + printf("(Move %2d/%d) ", i + 1, moveCount); + printFullMove(nodes[i].move, &(position->board)); + printf(" = "); + fflush(stdout); + } + + int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score; + + if(verbose) { + printf("%.2f\n", score / 100.00); + fflush(stdout); + } + + if(score == winScore(position->toMove)) { + return (Node){.move = nodes[i].move, .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = nodes[i].move; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = nodes[i].move; + } + + if(alpha > beta) { + break; + } + } + + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +Node pIDAB(Position* position, char depth, int* p_alpha, int* p_beta) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth == 1) return quiescenceSearch(position); + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) return staticNode; + + Move bestMove = 0; + + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + Position newPosition; + int i; + int alpha = *p_alpha; + int beta = *p_beta; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, nodes[i].move); + + int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score; + + if(score == winScore(position->toMove)) { + return (Node){.move = nodes[i].move, .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = nodes[i].move; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = nodes[i].move; + } + + if(alpha > beta || alpha > *p_beta || *p_alpha > beta) { + break; + } + } + + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +Node pIDABhashed(Position* position, char depth, int* p_alpha, int* p_beta) { + if(hasGameEnded(position)) { + int score = endNodeEvaluation(position); + writeToHashFile(position, score, 0); + return (Node){.score = score}; + } + + if(depth <= 1) { + Node quie = quiescenceSearch(position); + writeToHashFile(position, quie.score, depth); + return quie; + } + + // Mate in 1 + Node staticNode = staticSearch(position); + if(staticNode.score == winScore(position->toMove)) { + writeToHashFile(position, staticNode.score, 1); + return staticNode; + } + + Move bestMove = 0; + + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + Position newPosition; + int i; + int alpha = *p_alpha; + int beta = *p_beta; + for(i = 0; i < moveCount; i++) { + updatePosition(&newPosition, position, nodes[i].move); + + int score = iterativeDeepeningAlphaBeta(&newPosition, depth - 1, alpha, beta, FALSE).score; + writeToHashFile(&newPosition, score, depth - 1); + + if(score == winScore(position->toMove)) { + return (Node){.move = nodes[i].move, .score = score}; + } + + if(position->toMove == WHITE && score > alpha) { + alpha = score; + bestMove = nodes[i].move; + } else if(position->toMove == BLACK && score < beta) { + beta = score; + bestMove = nodes[i].move; + } + + if(alpha > beta || alpha > *p_beta || *p_alpha > beta) { + break; + } + } + + writeToHashFile(position, position->toMove == WHITE ? alpha : beta, depth); + return (Node){.move = bestMove, .score = position->toMove == WHITE ? alpha : beta}; +} + +// Parallel processing currently only implemented for Windows +#ifdef _WIN32 + +DWORD WINAPI evaluatePositionThreadFunction(LPVOID lpParam) { + ThreadInfo* tInfo = (ThreadInfo*)lpParam; + Position* pos = &tInfo->pos; + + Node node = pIDAB(pos, tInfo->depth, tInfo->alpha, tInfo->beta); + + if(pos->toMove == BLACK && node.score > *tInfo->alpha) { + *tInfo->alpha = node.score; + } else if(pos->toMove == WHITE && node.score < *tInfo->beta) { + *tInfo->beta = node.score; + } + + if(tInfo->verbose) { + printf("-"); + fflush(stdout); + } + + return node.score; +} + +DWORD WINAPI evaluatePositionThreadFunctionHashed(LPVOID lpParam) { + ThreadInfo* tInfo = (ThreadInfo*)lpParam; + Position* pos = &tInfo->pos; + + Node node = pIDABhashed(pos, tInfo->depth, tInfo->alpha, tInfo->beta); + + if(pos->toMove == BLACK && node.score > *tInfo->alpha) { + *tInfo->alpha = node.score; + } else if(pos->toMove == WHITE && node.score < *tInfo->beta) { + *tInfo->beta = node.score; + } + + if(tInfo->verbose) { + printf("-"); + fflush(stdout); + } + + return node.score; +} + +Node idabThreaded(Position* position, int depth, BOOL verbose) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth <= 1) return quiescenceSearch(position); + + int i; + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + if(moveCount == 1) { + return nodes[0]; + } + + if(verbose) { + printf("Analyzing %d possible moves with base depth %d:\n[", moveCount, depth); + for(i = 0; i < moveCount; i++) printf(" "); + printf("]\r["); + fflush(stdout); + } + + HANDLE threadHandles[MAX_BRANCHING_FACTOR]; + ThreadInfo threadInfo[MAX_BRANCHING_FACTOR]; + int alpha = INT32_MIN; + int beta = INT32_MAX; + + for(i = 0; i < moveCount; i++) { + threadInfo[i].depth = depth - 1; + updatePosition(&threadInfo[i].pos, position, nodes[i].move); + threadInfo[i].alpha = α + threadInfo[i].beta = β + threadInfo[i].verbose = verbose; + + threadHandles[i] = + CreateThread(NULL, 0, evaluatePositionThreadFunction, (LPVOID)&threadInfo[i], 0, NULL); + + if(threadHandles[i] == NULL) { + // printf("Error launching process on move #%d!\n", i); + printf("!"); + fflush(stdout); + } + } + + WaitForMultipleObjects((DWORD)moveCount, threadHandles, TRUE, INFINITE); + if(verbose) { + printf("]\n"); + fflush(stdout); + } + + Move bestMove = 0; + int bestMoveScore = position->toMove == WHITE ? INT32_MIN : INT32_MAX; + long unsigned int retVal; + int score; + for(i = 0; i < moveCount; i++) { + GetExitCodeThread(threadHandles[i], &retVal); + score = (int)retVal; + + if((position->toMove == WHITE && score > bestMoveScore) || + (position->toMove == BLACK && score < bestMoveScore)) { + bestMove = nodes[i].move; + bestMoveScore = score; + } + + if(CloseHandle(threadHandles[i]) == 0) { + // printf("Error on closing thread #%d!\n", i); + printf("x"); + fflush(stdout); + } + } + + return (Node){.move = bestMove, .score = bestMoveScore}; +} + +Node idabThreadedBestFirst(Position* position, int depth, BOOL verbose) { + if(hasGameEnded(position)) return (Node){.score = endNodeEvaluation(position)}; + + if(depth <= 1) return quiescenceSearch(position); + + int i; + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + if(moveCount == 1) { + return nodes[0]; + } + + Position firstPos; + updatePosition(&firstPos, position, nodes[0].move); + Node firstReply = idabThreaded(&firstPos, depth - 1, FALSE); + + if(firstReply.score == winScore(position->toMove)) { + if(verbose) { + printf("Playing checkmate move: "); + printFullMove(nodes[0].move, position->board); + printf(".\n"); + } + return (Node){.move = nodes[0].move, .score = firstReply.score}; + } + + if(verbose) { + printf("Move "); + printFullMove(nodes[0].move, position->board); + printf(" had score of %+.2f.\n", firstReply.score / 100.0); + printf( + "Analyzing other %d possible moves with minimum depth of %d plies:\n[", + moveCount - 1, + depth); + for(i = 0; i < moveCount - 1; i++) printf(" "); + printf("]\r["); + fflush(stdout); + } + + HANDLE threadHandles[MAX_BRANCHING_FACTOR]; + ThreadInfo threadInfo[MAX_BRANCHING_FACTOR]; + int alpha = INT32_MIN; + int beta = INT32_MAX; + + if(position->toMove == WHITE) { + alpha = firstReply.score; + } else { + beta = firstReply.score; + } + + for(i = 0; i < moveCount - 1; i++) { + threadInfo[i].depth = depth - 1; + updatePosition(&threadInfo[i].pos, position, nodes[i + 1].move); + threadInfo[i].alpha = α + threadInfo[i].beta = β + threadInfo[i].verbose = verbose; + + threadHandles[i] = + CreateThread(NULL, 0, evaluatePositionThreadFunction, (LPVOID)&threadInfo[i], 0, NULL); + + if(threadHandles[i] == NULL) { + // printf("Error launching process on move #%d!\n", i); + printf("!"); + fflush(stdout); + } + } + + WaitForMultipleObjects((DWORD)moveCount - 1, threadHandles, TRUE, INFINITE); + if(verbose) { + printf("] Done!\n"); + fflush(stdout); + } + + Move bestMove = nodes[0].move; + int bestMoveScore = firstReply.score; + long unsigned int retVal; + int score; + for(i = 0; i < moveCount - 1; i++) { + GetExitCodeThread(threadHandles[i], &retVal); + score = (int)retVal; + + if((position->toMove == WHITE && score > bestMoveScore) || + (position->toMove == BLACK && score < bestMoveScore)) { + bestMove = nodes[i + 1].move; + bestMoveScore = score; + } + + if(CloseHandle(threadHandles[i]) == 0) { + // printf("Error on closing thread #%d!\n", i); + printf("x"); + fflush(stdout); + } + } + + return (Node){.move = bestMove, .score = bestMoveScore}; +} + +Node idabThreadedBestFirstHashed(Position* position, int depth, BOOL verbose) { + if(hasGameEnded(position)) { + int score = endNodeEvaluation(position); + writeToHashFile(position, score, 0); + return (Node){.score = score}; + } + + if(depth <= 1) { + Node quie = quiescenceSearch(position); + writeToHashFile(position, quie.score, depth); + return quie; + } + + int i; + Node nodes[MAX_BRANCHING_FACTOR]; + int moveCount = alphaBetaNodes(nodes, position, depth - 1); + + if(moveCount == 1) { + return nodes[0]; + } + + Position firstPos; + updatePosition(&firstPos, position, nodes[0].move); + Node firstReply = idabThreaded(&firstPos, depth - 1, FALSE); + + if(firstReply.score == winScore(position->toMove)) { + if(verbose) { + printf("Playing checkmate move: "); + printFullMove(nodes[0].move, position->board); + printf(".\n"); + } + writeToHashFile(position, firstReply.score, depth); + return (Node){.move = nodes[0].move, .score = firstReply.score}; + } + + if(verbose) { + printf("Move "); + printFullMove(nodes[0].move, position->board); + printf(" had score of %+.2f.\n", firstReply.score / 100.0); + printf( + "Analyzing other %d possible moves with minimum depth of %d plies:\n[", + moveCount - 1, + depth); + for(i = 0; i < moveCount - 1; i++) printf(" "); + printf("]\r["); + fflush(stdout); + } + + HANDLE threadHandles[MAX_BRANCHING_FACTOR]; + ThreadInfo threadInfo[MAX_BRANCHING_FACTOR]; + int alpha = INT32_MIN; + int beta = INT32_MAX; + + if(position->toMove == WHITE) { + alpha = firstReply.score; + } else { + beta = firstReply.score; + } + + for(i = 0; i < moveCount - 1; i++) { + threadInfo[i].depth = depth - 1; + updatePosition(&threadInfo[i].pos, position, nodes[i + 1].move); + threadInfo[i].alpha = α + threadInfo[i].beta = β + threadInfo[i].verbose = verbose; + + threadHandles[i] = CreateThread( + NULL, 0, evaluatePositionThreadFunctionHashed, (LPVOID)&threadInfo[i], 0, NULL); + + if(threadHandles[i] == NULL) { + // printf("Error launching process on move #%d!\n", i); + printf("!"); + fflush(stdout); + } + } + + WaitForMultipleObjects((DWORD)moveCount - 1, threadHandles, TRUE, INFINITE); + if(verbose) { + printf("] Done!\n"); + fflush(stdout); + } + + Move bestMove = nodes[0].move; + int bestMoveScore = firstReply.score; + long unsigned int retVal; + int score; + for(i = 0; i < moveCount - 1; i++) { + GetExitCodeThread(threadHandles[i], &retVal); + score = (int)retVal; + + writeToHashFile(&threadInfo[i].pos, score, depth - 1); + + if((position->toMove == WHITE && score > bestMoveScore) || + (position->toMove == BLACK && score < bestMoveScore)) { + bestMove = nodes[i + 1].move; + bestMoveScore = score; + } + + if(CloseHandle(threadHandles[i]) == 0) { + // printf("Error on closing thread #%d!\n", i); + printf("x"); + fflush(stdout); + } + } + + writeToHashFile(position, bestMoveScore, depth); + return (Node){.move = bestMove, .score = bestMoveScore}; +} + +#endif /* _WIN32 */ + +Move getRandomMove(Position* position) { + Move moves[MAX_BRANCHING_FACTOR]; + int totalMoves = legalMoves(moves, position, position->toMove); + int chosenMove = rand() % totalMoves; + return moves[chosenMove]; +} + +Move getAIMove(Game* game, int depth) { + printf("--- AI ---\n"); + fflush(stdout); + + if(fromInitial(game) && countBookOccurrences(game) > 0) { + printf("There are %d available book continuations.\n", countBookOccurrences(game)); + fflush(stdout); + Move bookMove = getBookMove(game); + printf("CHOSEN book move: "); + printFullMove(bookMove, &(game->position.board)); + printf(".\n"); + fflush(stdout); + return bookMove; + } + + time_t startTime, endTime; + startTime = time(NULL); + + // Move move = getRandomMove(&game->position); + // Move move = simpleEvaluation(&game->position).move; + // Move move = minimax(&game->position, AI_DEPTH).move; + // Node node = alphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE); + // Node node = iterativeDeepeningAlphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE); + // Node node = idabThreaded(&game->position, depth, TRUE); + // Node node = idabThreadedBestFirst(&game->position, depth, TRUE); + // Node node = idabThreadedBestFirstHashed(&game->position, depth, TRUE); + +#ifdef _WIN32 + Node node = idabThreadedBestFirst(&game->position, depth, TRUE); +#else + Node node = iterativeDeepeningAlphaBeta(&game->position, depth, INT32_MIN, INT32_MAX, TRUE); +#endif + + endTime = time(NULL); + + printf("CHOSEN move: "); + printFullMove(node.move, &(game->position.board)); + printf( + " in %d seconds [%+.2f, %+.2f]\n", + (int)(endTime - startTime), + staticEvaluation(&game->position) / 100.0, + node.score / 100.0); + fflush(stdout); + + return node.move; +} + +Move parseMove(char* move) { + int pos1 = str2index(&move[0]); + int pos2 = str2index(&move[2]); + return generateMove(pos1, pos2); +} + +Move getPlayerMove() { + char input[100]; + fgets(input, 100, stdin); + return parseMove(input); +} + +Move suggestMove(char fen[], int depth) { + Game game; + getFenGame(&game, fen); + return getAIMove(&game, depth); +} + +// ===== PLAY LOOP (TEXT) ==== + +void playTextWhite(int depth) { + printf("Playing as WHITE!\n"); + fflush(stdout); + + Game game; + getInitialGame(&game); + + while(TRUE) { + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getPlayerMove()); + + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getAIMove(&game, depth)); + } + printOutcome(&game.position); +} + +void playTextBlack(int depth) { + printf("Playing as BLACK!\n"); + fflush(stdout); + + Game game; + getInitialGame(&game); + + while(TRUE) { + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getAIMove(&game, depth)); + + printBoard(&(game.position.board)); + if(hasGameEnded(&game.position)) break; + + makeMove(&game, getPlayerMove()); + } + printOutcome(&game.position); +} + +void playTextAs(char color, int depth) { + if(color == WHITE) playTextWhite(depth); + if(color == BLACK) playTextBlack(depth); +} + +void playTextRandomColor(int depth) { + char colors[] = {WHITE, BLACK}; + char color = colors[rand() % 2]; + playTextAs(color, depth); +} + +// =========================== + +/* +int main(int argc, char *argv[]) { + srand(time(NULL)); + + int opt; + int FEN_MODE = 0, MOVES_MODE = 1, mode = FEN_MODE; + int depth = DEFAULT_AI_DEPTH; + + while ((opt = getopt(argc, argv, "fmd:v")) != -1) { + switch (opt) { + case 'f': mode = FEN_MODE; break; + case 'm': mode = MOVES_MODE; break; + case 'd': depth = atoi(optarg); break; + case 'v': printf(ENGINE_VERSION); exit(0); + default: + fprintf(stderr, "Usage: %s [-d depth] [-f fen] [-m move_list]\n", argv[0]); + exit(EXIT_FAILURE); + } + } + + Game game; + if (argc > optind) { + if (mode == FEN_MODE) { + getFenGame(&game, argv[optind]); + } else if (mode == MOVES_MODE) { + getMovelistGame(&game, argv[optind]); + } + } else { + getInitialGame(&game); + } + + Move move; + if ( mode == MOVES_MODE && countBookOccurrences(&game) > 0 ) { + move = getBookMove(&game); + } else { + Node node = iterativeDeepeningAlphaBeta(&(game.position), (char) depth, INT32_MIN, INT32_MAX, FALSE); + move = node.move; + } + + printf("%c%c%c%c", getFile(getFrom(move)), getRank(getFrom(move)), getFile(getTo(move)), getRank(getTo(move))); + + return EXIT_SUCCESS; +} +// */ diff --git a/applications/plugins/chess/fast_chess.h b/applications/plugins/chess/fast_chess.h new file mode 100644 index 000000000..b56a32cb2 --- /dev/null +++ b/applications/plugins/chess/fast_chess.h @@ -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 +#endif + +#include + +#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_ */ \ No newline at end of file diff --git a/applications/plugins/chip8/application.fam b/applications/plugins/chip8/application.fam new file mode 100644 index 000000000..1c28209fb --- /dev/null +++ b/applications/plugins/chip8/application.fam @@ -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", +) \ No newline at end of file diff --git a/applications/plugins/chip8/chip8.c b/applications/plugins/chip8/chip8.c new file mode 100644 index 000000000..51d7247a2 --- /dev/null +++ b/applications/plugins/chip8/chip8.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#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"); +} diff --git a/applications/plugins/chip8/chip8.h b/applications/plugins/chip8/chip8.h new file mode 100644 index 000000000..5a6470592 --- /dev/null +++ b/applications/plugins/chip8/chip8.h @@ -0,0 +1,45 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#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 diff --git a/applications/plugins/chip8/chip8_app.c b/applications/plugins/chip8/chip8_app.c new file mode 100644 index 000000000..1b4d06646 --- /dev/null +++ b/applications/plugins/chip8/chip8_app.c @@ -0,0 +1,89 @@ +#include "chip8_app_i.h" +#include + +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; +} \ No newline at end of file diff --git a/applications/plugins/chip8/chip8_app.h b/applications/plugins/chip8/chip8_app.h new file mode 100644 index 000000000..0d5720388 --- /dev/null +++ b/applications/plugins/chip8/chip8_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Chip8App Chip8App; + +#ifdef __cplusplus +} +#endif diff --git a/applications/plugins/chip8/chip8_app_i.h b/applications/plugins/chip8/chip8_app_i.h new file mode 100644 index 000000000..78bd6f50f --- /dev/null +++ b/applications/plugins/chip8/chip8_app_i.h @@ -0,0 +1,33 @@ +#pragma once + +#include "chip8_app.h" +#include "scenes/chip8_scene.h" +#include "chip8.h" + +#include +#include +#include +#include +#include +#include +#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; diff --git a/applications/plugins/chip8/emulator_core/flipper_chip.c b/applications/plugins/chip8/emulator_core/flipper_chip.c new file mode 100644 index 000000000..4b438cac2 --- /dev/null +++ b/applications/plugins/chip8/emulator_core/flipper_chip.c @@ -0,0 +1,319 @@ +// +// Created by dwdraugr on 24.11.2021. +// + +#include "flipper_chip.h" +#include "flipper_fonts.h" +#include +#include +#include +#include + +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); +} diff --git a/applications/plugins/chip8/emulator_core/flipper_chip.h b/applications/plugins/chip8/emulator_core/flipper_chip.h new file mode 100644 index 000000000..09c2ed298 --- /dev/null +++ b/applications/plugins/chip8/emulator_core/flipper_chip.h @@ -0,0 +1,74 @@ +// +// Created by dwdraugr on 24.11.2021. +// + +#ifndef FLIPPER_CHIP_FLIPPER_CHIP_H +#define FLIPPER_CHIP_FLIPPER_CHIP_H + +#include +#include +#include + +#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 diff --git a/applications/plugins/chip8/emulator_core/flipper_fonts.h b/applications/plugins/chip8/emulator_core/flipper_fonts.h new file mode 100644 index 000000000..f1bcec1b9 --- /dev/null +++ b/applications/plugins/chip8/emulator_core/flipper_fonts.h @@ -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 diff --git a/applications/plugins/chip8/scenes/chip8_scene.c b/applications/plugins/chip8/scenes/chip8_scene.c new file mode 100644 index 000000000..34488c888 --- /dev/null +++ b/applications/plugins/chip8/scenes/chip8_scene.c @@ -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, +}; diff --git a/applications/plugins/chip8/scenes/chip8_scene.h b/applications/plugins/chip8/scenes/chip8_scene.h new file mode 100644 index 000000000..19fd40c62 --- /dev/null +++ b/applications/plugins/chip8/scenes/chip8_scene.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#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 diff --git a/applications/plugins/chip8/scenes/chip8_scene_config.h b/applications/plugins/chip8/scenes/chip8_scene_config.h new file mode 100644 index 000000000..593249a87 --- /dev/null +++ b/applications/plugins/chip8/scenes/chip8_scene_config.h @@ -0,0 +1,2 @@ +ADD_SCENE(chip8, file_select, FileSelect) +ADD_SCENE(chip8, work, Work) \ No newline at end of file diff --git a/applications/plugins/chip8/scenes/chip8_scene_file_select.c b/applications/plugins/chip8/scenes/chip8_scene_file_select.c new file mode 100644 index 000000000..380524da9 --- /dev/null +++ b/applications/plugins/chip8/scenes/chip8_scene_file_select.c @@ -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); +} diff --git a/applications/plugins/chip8/scenes/chip8_scene_work.c b/applications/plugins/chip8/scenes/chip8_scene_work.c new file mode 100644 index 000000000..6f9c6e5e2 --- /dev/null +++ b/applications/plugins/chip8/scenes/chip8_scene_work.c @@ -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); +} diff --git a/applications/plugins/chip8/views/chip8_view.c b/applications/plugins/chip8/views/chip8_view.c new file mode 100644 index 000000000..aceeed9e8 --- /dev/null +++ b/applications/plugins/chip8/views/chip8_view.c @@ -0,0 +1,207 @@ +#include +#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; + }); +} diff --git a/applications/plugins/chip8/views/chip8_view.h b/applications/plugins/chip8/views/chip8_view.h new file mode 100644 index 000000000..339261634 --- /dev/null +++ b/applications/plugins/chip8/views/chip8_view.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#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); diff --git a/applications/plugins/mouse_jiggler/application.fam b/applications/plugins/mouse_jiggler/application.fam new file mode 100644 index 000000000..3baeab3c2 --- /dev/null +++ b/applications/plugins/mouse_jiggler/application.fam @@ -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", +) \ No newline at end of file diff --git a/applications/plugins/mouse_jiggler/mouse_jiggler.c b/applications/plugins/mouse_jiggler/mouse_jiggler.c new file mode 100644 index 000000000..868082eea --- /dev/null +++ b/applications/plugins/mouse_jiggler/mouse_jiggler.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +#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; +} diff --git a/applications/plugins/paint/application.fam b/applications/plugins/paint/application.fam new file mode 100644 index 000000000..74e0a1cd2 --- /dev/null +++ b/applications/plugins/paint/application.fam @@ -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, +) \ No newline at end of file diff --git a/applications/plugins/paint/paint.c b/applications/plugins/paint/paint.c new file mode 100644 index 000000000..5cfe85155 --- /dev/null +++ b/applications/plugins/paint/paint.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include // 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; +}