flip_chess_app.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. #include <dolphin/dolphin.h>
  6. #include <furi_hal_random.h>
  7. #define SCL_960_CASTLING 0 // setting to 1 compiles a 960 version of smolchess
  8. #define XBOARD_DEBUG 0 // will create files with xboard communication
  9. #define SCL_EVALUATION_FUNCTION SCL_boardEvaluateStatic
  10. #define SCL_DEBUG_AI 0
  11. #include "smallchesslib.h"
  12. typedef struct {
  13. uint8_t status;
  14. FuriMutex* mutex;
  15. } ChessState;
  16. typedef enum {
  17. EventTypeTick,
  18. EventTypeKey,
  19. } EventType;
  20. typedef struct {
  21. EventType type;
  22. InputEvent input;
  23. } Event;
  24. uint8_t paramPlayerW = 0;
  25. uint8_t paramPlayerB = 0;
  26. // uint8_t paramBoard = 1;
  27. uint8_t paramAnalyze = 255; // depth of analysis
  28. uint8_t paramMoves = 0;
  29. //uint8_t paramXboard = 0;
  30. uint8_t paramInfo = 1;
  31. //uint8_t paramDraw = 1;
  32. uint8_t paramFlipBoard = 0;
  33. //uint8_t paramHelp = 0;
  34. uint8_t paramExit = 0;
  35. uint16_t paramStep = 0;
  36. char* paramFEN = NULL;
  37. char* paramPGN = NULL;
  38. //uint16_t paramRandom = 0;
  39. //uint8_t paramBlind = 0;
  40. int clockSeconds = -1;
  41. SCL_Game game;
  42. SCL_Board startState = SCL_BOARD_START_STATE;
  43. int16_t random960PosNumber = -1;
  44. uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];
  45. uint8_t selected = 255;
  46. char* msg = "Flip Chess";
  47. void putImagePixel(uint8_t pixel, uint16_t index) {
  48. picture[index] = pixel;
  49. }
  50. uint8_t stringsEqual(const char* s1, const char* s2, int max) {
  51. for(int i = 0; i < max; ++i) {
  52. if(*s1 != *s2) return 0;
  53. if(*s1 == 0) return 1;
  54. s1++;
  55. s2++;
  56. }
  57. return 1;
  58. }
  59. int16_t makeAIMove(SCL_Board board, uint8_t* s0, uint8_t* s1, char* prom) {
  60. uint8_t level = SCL_boardWhitesTurn(board) ? paramPlayerW : paramPlayerB;
  61. uint8_t depth = (level > 0) ? level : 1;
  62. uint8_t extraDepth = 3;
  63. uint8_t endgameDepth = 1;
  64. uint8_t randomness = game.ply < 2 ? 1 : 0; /* in first moves increase randomness for different
  65. openings */
  66. uint8_t rs0, rs1;
  67. SCL_gameGetRepetiotionMove(&game, &rs0, &rs1);
  68. if(clockSeconds >= 0) // when using clock, choose AI params accordingly
  69. {
  70. if(clockSeconds <= 5) {
  71. depth = 1;
  72. extraDepth = 2;
  73. endgameDepth = 0;
  74. } else if(clockSeconds < 15) {
  75. depth = 2;
  76. extraDepth = 2;
  77. } else if(clockSeconds < 100) {
  78. depth = 2;
  79. } else if(clockSeconds < 5 * 60) {
  80. depth = 3;
  81. } else {
  82. depth = 3;
  83. extraDepth = 4;
  84. }
  85. }
  86. return SCL_getAIMove(
  87. board,
  88. depth,
  89. extraDepth,
  90. endgameDepth,
  91. SCL_boardEvaluateStatic,
  92. SCL_randomBetter,
  93. randomness,
  94. rs0,
  95. rs1,
  96. s0,
  97. s1,
  98. prom);
  99. }
  100. void initGame() {
  101. SCL_randomBetterSeed(furi_hal_random_get());
  102. #if SCL_960_CASTLING
  103. if(random960PosNumber < 0) random960PosNumber = SCL_randomBetter();
  104. #endif
  105. if(random960PosNumber >= 0) random960PosNumber %= 960;
  106. if(paramFEN != NULL)
  107. SCL_boardFromFEN(startState, paramFEN);
  108. else if(paramPGN != NULL) {
  109. SCL_Record record;
  110. SCL_recordFromPGN(record, paramPGN);
  111. SCL_boardInit(startState);
  112. SCL_recordApply(record, startState, paramStep);
  113. }
  114. #if SCL_960_CASTLING
  115. else
  116. SCL_boardInit960(startState, random960PosNumber);
  117. #endif
  118. SCL_gameInit(&game, startState);
  119. if(paramAnalyze != 255) {
  120. char p;
  121. uint8_t move[] = {0, 0};
  122. paramPlayerW = paramAnalyze;
  123. paramPlayerB = paramAnalyze;
  124. int16_t evaluation = makeAIMove(game.board, &(move[0]), &(move[1]), &p);
  125. if(paramAnalyze == 0) evaluation = SCL_boardEvaluateStatic(game.board);
  126. char moveStr[5];
  127. moveStr[4] = 0;
  128. SCL_squareToString(move[0], moveStr);
  129. SCL_squareToString(move[1], moveStr + 2);
  130. //printf("%lf (%d)\n", ((double)evaluation) / ((double)SCL_VALUE_PAWN), evaluation);
  131. //puts(moveStr);
  132. return 0;
  133. }
  134. if(paramMoves) {
  135. char string[256];
  136. for(int i = 0; i < 64; ++i)
  137. if(game.board[i] != '.' &&
  138. SCL_pieceIsWhite(game.board[i]) == SCL_boardWhitesTurn(game.board)) {
  139. SCL_SquareSet possibleMoves = SCL_SQUARE_SET_EMPTY;
  140. SCL_boardGetMoves(game.board, i, possibleMoves);
  141. SCL_SQUARE_SET_ITERATE_BEGIN(possibleMoves)
  142. SCL_moveToString(game.board, i, iteratedSquare, 'q', string);
  143. //printf("%s ", string);
  144. SCL_SQUARE_SET_ITERATE_END
  145. }
  146. return 0;
  147. }
  148. }
  149. static void flip_chess_render_callback(Canvas* const canvas, void* ctx) {
  150. furi_assert(ctx);
  151. const ChessState* chess_state = ctx;
  152. furi_mutex_acquire(chess_state->mutex, FuriWaitForever);
  153. // Before the function is called, the state is set with the canvas_reset(canvas)
  154. // Frame
  155. canvas_draw_frame(canvas, 0, 0, 128, 64);
  156. // Message
  157. canvas_set_font(canvas, FontSecondary);
  158. canvas_draw_str(canvas, 66, 10, msg);
  159. // Board
  160. for(uint16_t y = 0; y < SCL_BOARD_PICTURE_WIDTH; y++) {
  161. for(uint16_t x = 0; x < SCL_BOARD_PICTURE_WIDTH; x++) {
  162. if(picture[x + (y * SCL_BOARD_PICTURE_WIDTH)]) {
  163. canvas_draw_dot(canvas, x, y);
  164. }
  165. }
  166. }
  167. furi_mutex_release(chess_state->mutex);
  168. }
  169. static void flip_chess_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  170. furi_assert(event_queue);
  171. Event event = {.type = EventTypeKey, .input = *input_event};
  172. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  173. }
  174. int32_t flip_chess_app(void* p) {
  175. UNUSED(p);
  176. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Event));
  177. dolphin_deed(DolphinDeedPluginStart);
  178. ChessState* chess_state = malloc(sizeof(ChessState));
  179. chess_state->status = 0;
  180. chess_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  181. if(!chess_state->mutex) {
  182. FURI_LOG_E("FlipChess", "cannot create mutex\r\n");
  183. furi_message_queue_free(event_queue);
  184. free(chess_state);
  185. return 255;
  186. }
  187. ViewPort* view_port = view_port_alloc();
  188. view_port_draw_callback_set(view_port, flip_chess_render_callback, chess_state);
  189. view_port_input_callback_set(view_port, flip_chess_input_callback, event_queue);
  190. // Open GUI and register view_port
  191. Gui* gui = furi_record_open(RECORD_GUI);
  192. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  193. Event event;
  194. furi_hal_random_init();
  195. initGame();
  196. //char string[256];
  197. SCL_SquareSet squareSet = SCL_SQUARE_SET_EMPTY;
  198. char moveString[16];
  199. moveString[0] = 0;
  200. SCL_SquareSet moveHighlight = SCL_SQUARE_SET_EMPTY;
  201. uint8_t squareFrom = 255;
  202. uint8_t squareTo = 255;
  203. SCL_drawBoard(game.board, putImagePixel, selected, squareSet, paramFlipBoard);
  204. for(bool processing = true; processing;) {
  205. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  206. furi_mutex_acquire(chess_state->mutex, FuriWaitForever);
  207. if(event_status == FuriStatusOk) {
  208. // press events
  209. if(event.type == EventTypeKey) {
  210. if(event.input.type == InputTypePress) {
  211. switch(event.input.key) {
  212. case InputKeyUp:
  213. selected = (selected + 8) % 64;
  214. SCL_drawBoard(
  215. game.board, putImagePixel, selected, squareSet, paramFlipBoard);
  216. break;
  217. case InputKeyDown:
  218. selected = (selected + 56) % 64;
  219. SCL_drawBoard(
  220. game.board, putImagePixel, selected, squareSet, paramFlipBoard);
  221. break;
  222. case InputKeyRight:
  223. selected = (selected + 1) % 64;
  224. SCL_drawBoard(
  225. game.board, putImagePixel, selected, squareSet, paramFlipBoard);
  226. break;
  227. case InputKeyLeft:
  228. selected = (selected + 63) % 64;
  229. SCL_drawBoard(
  230. game.board, putImagePixel, selected, squareSet, paramFlipBoard);
  231. break;
  232. case InputKeyOk: ;
  233. if (chess_state->status == 1) {
  234. squareFrom = selected;
  235. chess_state->status = 2;
  236. } else {
  237. squareTo = selected;
  238. chess_state->status = 1;
  239. }
  240. // // // // //
  241. // 0: none, 1: player, 2: AI, 3: undo
  242. uint8_t moveType = 0;
  243. //for(int i = 0; i < 40; ++i) putchar('\n');
  244. //putchar('\n');
  245. if(game.ply > 0) {
  246. msg =
  247. (SCL_boardWhitesTurn(game.board) ? "black played" :
  248. "white played");
  249. // printf(" played ");
  250. uint8_t s0, s1;
  251. char p;
  252. SCL_recordGetMove(game.record, game.ply - 1, &s0, &s1, &p);
  253. SCL_moveToString(game.board, s0, s1, p, moveString);
  254. msg = moveString;
  255. //printf("%s\n", moveString);
  256. }
  257. msg =
  258. (SCL_boardWhitesTurn(game.board) ? "white to move" : "black to move");
  259. //printf(" to move\n");
  260. // if(paramInfo) {
  261. // //putchar('\n');
  262. // if(random960PosNumber >= 0)
  263. // printf("960 random position number: %d\n", random960PosNumber);
  264. // printf("ply number: %d\n", game.ply);
  265. // //SCL_boardToFEN(game.board, string);
  266. // //printf("FEN: %s\n", string);
  267. // int16_t eval = SCL_boardEvaluateStatic(game.board);
  268. // printf(
  269. // "board static evaluation: %lf (%d)\n",
  270. // ((double)eval) / ((double)SCL_VALUE_PAWN),
  271. // eval);
  272. // printf("board hash: %u\n", SCL_boardHash32(game.board));
  273. // printf("phase: ");
  274. // switch(SCL_boardEstimatePhase(game.board)) {
  275. // case SCL_PHASE_OPENING:
  276. // puts("opening");
  277. // break;
  278. // case SCL_PHASE_ENDGAME:
  279. // puts("endgame");
  280. // break;
  281. // default:
  282. // puts("midgame");
  283. // break;
  284. // }
  285. // printf(
  286. // "en passant: %d\n",
  287. // ((game.board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f) + 1) % 16);
  288. // printf(
  289. // "50 move rule count: %d\n", game.board[SCL_BOARD_MOVE_COUNT_BYTE]);
  290. // // if(paramFEN == NULL && paramPGN == NULL) {
  291. // // //printf("PGN: ");
  292. // // //SCL_printPGN(game.record, putCharacter, startState);
  293. // // //putchar('\n');
  294. // // }
  295. // }
  296. if(game.state != SCL_GAME_STATE_PLAYING || paramExit) break;
  297. //uint8_t squareFrom = 0;
  298. //uint8_t squareTo = 0;
  299. char movePromote = 'q';
  300. if((SCL_boardWhitesTurn(game.board) && paramPlayerW == 0) ||
  301. (!SCL_boardWhitesTurn(game.board) && paramPlayerB == 0)) {
  302. // printf("\nmove: ");
  303. // scanf("%s", string);
  304. // char string[256];
  305. // if(stringsEqual(string, "undo", 5))
  306. // moveType = 3;
  307. // else if(stringsEqual(string, "quit", 5))
  308. // break;
  309. // else {
  310. //squareFrom = selected; //SCL_stringToSquare(string);
  311. //squareTo = selected; //SCL_stringToSquare(string + 2);
  312. //uint8_t r =
  313. // SCL_stringToMove(string, &squareFrom, &squareTo, &movePromote);
  314. if(squareFrom != 255) {
  315. if((game.board[squareFrom] != '.') &&
  316. (SCL_pieceIsWhite(game.board[squareFrom]) ==
  317. SCL_boardWhitesTurn(game.board))) {
  318. SCL_boardGetMoves(game.board, squareFrom, squareSet);
  319. if(SCL_squareSetContains(squareSet, squareTo)) {
  320. moveType = 1;
  321. }
  322. }
  323. }
  324. // }
  325. } else {
  326. makeAIMove(game.board, &squareFrom, &squareTo, &movePromote);
  327. moveType = 2;
  328. }
  329. if(moveType == 1 || moveType == 2) {
  330. SCL_moveToString(
  331. game.board, squareFrom, squareTo, movePromote, moveString);
  332. SCL_gameMakeMove(&game, squareFrom, squareTo, movePromote);
  333. SCL_squareSetClear(moveHighlight);
  334. SCL_squareSetAdd(moveHighlight, squareFrom);
  335. SCL_squareSetAdd(moveHighlight, squareTo);
  336. } else if(moveType == 3) {
  337. if(paramPlayerW != 0 || paramPlayerB != 0) SCL_gameUndoMove(&game);
  338. SCL_gameUndoMove(&game);
  339. SCL_squareSetClear(moveHighlight);
  340. }
  341. //putchar('\n');
  342. SCL_drawBoard(
  343. game.board, putImagePixel, selected, squareSet, paramFlipBoard);
  344. switch(game.state) {
  345. case SCL_GAME_STATE_WHITE_WIN:
  346. msg = "white wins";
  347. break;
  348. case SCL_GAME_STATE_BLACK_WIN:
  349. msg = "black wins";
  350. break;
  351. case SCL_GAME_STATE_DRAW_STALEMATE:
  352. msg = "draw (stalemate)";
  353. break;
  354. case SCL_GAME_STATE_DRAW_REPETITION:
  355. msg = "draw (repeated position)";
  356. break;
  357. case SCL_GAME_STATE_DRAW_DEAD:
  358. msg = "draw (dead position)";
  359. break;
  360. case SCL_GAME_STATE_DRAW:
  361. msg = "draw";
  362. break;
  363. case SCL_GAME_STATE_DRAW_50:
  364. msg = "draw (50 move rule)";
  365. break;
  366. default:
  367. //msg = "game over";
  368. break;
  369. // // // // //
  370. }
  371. break;
  372. case InputKeyBack:
  373. processing = false;
  374. break;
  375. default:
  376. break;
  377. }
  378. }
  379. }
  380. }
  381. view_port_update(view_port);
  382. furi_mutex_release(chess_state->mutex);
  383. }
  384. // Reset GPIO pins to default state
  385. //furi_hal_gpio_init(&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
  386. view_port_enabled_set(view_port, false);
  387. gui_remove_view_port(gui, view_port);
  388. furi_record_close(RECORD_GUI);
  389. view_port_free(view_port);
  390. furi_message_queue_free(event_queue);
  391. furi_mutex_free(chess_state->mutex);
  392. free(chess_state);
  393. return 0;
  394. }