Files
Momentum-Firmware/applications/external/chess/views/flipchess_scene_1.c
2023-08-12 12:09:05 -06:00

727 lines
23 KiB
C

#include "../flipchess.h"
#include <furi.h>
// #include <furi_hal.h>
// #include <furi_hal_random.h>
#include <input/input.h>
#include <gui/elements.h>
//#include <dolphin/dolphin.h>
#include <string.h>
//#include "flipchess_icons.h"
#include "../helpers/flipchess_voice.h"
#include "../helpers/flipchess_haptic.h"
#define SCL_960_CASTLING 0 // setting to 1 compiles a 960 version of smolchess
#define XBOARD_DEBUG 0 // will create files with xboard communication
#define SCL_EVALUATION_FUNCTION SCL_boardEvaluateStatic
#define SCL_DEBUG_AI 0
#include "../chess/smallchesslib.h"
#define ENABLE_960 0 // setting to 1 enables 960 chess
#define MAX_TEXT_LEN 15 // 15 = max length of text
#define MAX_TEXT_BUF (MAX_TEXT_LEN + 1) // max length of text + null terminator
#define THREAD_WAIT_TIME 20 // time to wait for draw thread to finish
struct FlipChessScene1 {
View* view;
FlipChessScene1Callback callback;
void* context;
};
typedef struct {
uint8_t paramPlayerW;
uint8_t paramPlayerB;
uint8_t paramAnalyze; // depth of analysis
uint8_t paramMoves;
uint8_t paramInfo;
uint8_t paramFlipBoard;
uint8_t paramExit;
uint16_t paramStep;
char* paramFEN;
char* paramPGN;
int clockSeconds;
SCL_Game game;
SCL_Board startState;
#if ENABLE_960
int16_t random960PosNumber;
#endif
//uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];
uint8_t squareSelected;
uint8_t squareSelectedLast;
char* msg;
char* msg2;
char* msg3;
char moveString[MAX_TEXT_BUF];
char moveString2[MAX_TEXT_BUF];
char moveString3[MAX_TEXT_BUF];
uint8_t thinking;
SCL_SquareSet moveHighlight;
uint8_t squareFrom;
uint8_t squareTo;
uint8_t turnState;
} FlipChessScene1Model;
static uint8_t picture[SCL_BOARD_PICTURE_WIDTH * SCL_BOARD_PICTURE_WIDTH];
void flipchess_putImagePixel(uint8_t pixel, uint16_t index) {
picture[index] = pixel;
}
uint8_t flipchess_stringsEqual(const char* s1, const char* s2, int max) {
for(int i = 0; i < max; ++i) {
if(*s1 != *s2) return 0;
if(*s1 == 0) return 1;
s1++;
s2++;
}
return 1;
}
int16_t flipchess_makeAIMove(
SCL_Board board,
uint8_t* s0,
uint8_t* s1,
char* prom,
FlipChessScene1Model* model) {
uint8_t level = SCL_boardWhitesTurn(board) ? model->paramPlayerW : model->paramPlayerB;
uint8_t depth = (level > 0) ? level : 1;
uint8_t extraDepth = 3;
uint8_t endgameDepth = 1;
uint8_t randomness =
model->game.ply < 2 ? 1 : 0; /* in first moves increase randomness for different
openings */
uint8_t rs0, rs1;
SCL_gameGetRepetiotionMove(&(model->game), &rs0, &rs1);
if(model->clockSeconds >= 0) // when using clock, choose AI params accordingly
{
if(model->clockSeconds <= 5) {
depth = 1;
extraDepth = 2;
endgameDepth = 0;
} else if(model->clockSeconds < 15) {
depth = 2;
extraDepth = 2;
} else if(model->clockSeconds < 100) {
depth = 2;
} else if(model->clockSeconds < 5 * 60) {
depth = 3;
} else {
depth = 3;
extraDepth = 4;
}
}
return SCL_getAIMove(
board,
depth,
extraDepth,
endgameDepth,
SCL_boardEvaluateStatic,
SCL_randomBetter,
randomness,
rs0,
rs1,
s0,
s1,
prom);
}
bool flipchess_isPlayerTurn(FlipChessScene1Model* model) {
return (SCL_boardWhitesTurn(model->game.board) && model->paramPlayerW == 0) ||
(!SCL_boardWhitesTurn(model->game.board) && model->paramPlayerB == 0);
}
void flipchess_shiftMessages(FlipChessScene1Model* model) {
// shift messages
model->msg3 = model->msg2;
model->msg2 = model->msg;
strncpy(model->moveString3, model->moveString2, MAX_TEXT_LEN);
strncpy(model->moveString2, model->moveString, MAX_TEXT_LEN);
}
void flipchess_drawBoard(FlipChessScene1Model* model) {
// draw chess board
SCL_drawBoard(
model->game.board,
flipchess_putImagePixel,
model->squareSelected,
model->moveHighlight,
model->paramFlipBoard);
}
uint8_t flipchess_saveState(FlipChess* app, FlipChessScene1Model* model) {
for(uint8_t i = 0; i < SCL_FEN_MAX_LENGTH; i++) {
app->import_game_text[i] = '\0';
}
const uint8_t res = SCL_boardToFEN(model->game.board, app->import_game_text);
if(res > 0) {
app->import_game = 1;
}
return res;
}
uint8_t flipchess_turn(FlipChessScene1Model* model) {
// 0: none, 1: player, 2: AI, 3: undo
uint8_t moveType = FlipChessStatusNone;
// if(model->paramInfo) {
// if(model->random960PosNumber >= 0)
// printf("960 random position number: %d\n", model->random960PosNumber);
// printf("ply number: %d\n", model->game.ply);
// int16_t eval = SCL_boardEvaluateStatic(model->game.board);
// printf(
// "board static evaluation: %lf (%d)\n",
// ((double)eval) / ((double)SCL_VALUE_PAWN),
// eval);
// printf("board hash: %u\n", SCL_boardHash32(model->game.board));
// printf("phase: ");
// switch(SCL_boardEstimatePhase(model->game.board)) {
// case SCL_PHASE_OPENING:
// puts("opening");
// break;
// case SCL_PHASE_ENDGAME:
// puts("endgame");
// break;
// default:
// puts("midgame");
// break;
// }
// printf(
// "en passant: %d\n",
// ((model->game.board[SCL_BOARD_ENPASSANT_CASTLE_BYTE] & 0x0f) + 1) % 16);
// printf(
// "50 move rule count: %d\n", model->game.board[SCL_BOARD_MOVE_COUNT_BYTE]);
// if(model->paramFEN == NULL && model->paramPGN == NULL) {
// printf("PGN: ");
// SCL_printPGN(model->game.record, putCharacter, startState);
// putchar('\n');
// }
// }
if(model->game.state != SCL_GAME_STATE_PLAYING) {
model->paramExit = FlipChessStatusNone;
} else {
char movePromote = 'q';
if(flipchess_isPlayerTurn(model)) {
// if(stringsEqual(string, "undo", 5))
// moveType = FlipChessStatusMoveUndo;
// else if(stringsEqual(string, "quit", 5))
// break;
if(model->turnState == 0 && model->squareSelected != 255) {
model->squareFrom = model->squareSelected;
model->turnState = 1;
} else if(model->turnState == 1 && model->squareSelected != 255) {
model->squareTo = model->squareSelected;
model->turnState = 2;
model->squareSelectedLast = model->squareSelected;
//model->squareSelected = 255;
}
if(model->turnState == 1 && model->squareFrom != 255) {
if((model->game.board[model->squareFrom] != '.') &&
(SCL_pieceIsWhite(model->game.board[model->squareFrom]) ==
SCL_boardWhitesTurn(model->game.board))) {
SCL_boardGetMoves(model->game.board, model->squareFrom, model->moveHighlight);
}
} else if(model->turnState == 2) {
if(SCL_squareSetContains(model->moveHighlight, model->squareTo)) {
moveType = FlipChessStatusMovePlayer;
}
model->turnState = 0;
SCL_squareSetClear(model->moveHighlight);
}
} else {
model->squareSelected = 255;
flipchess_makeAIMove(
model->game.board, &(model->squareFrom), &(model->squareTo), &movePromote, model);
moveType = FlipChessStatusMoveAI;
model->turnState = 0;
}
if(moveType == FlipChessStatusMovePlayer || moveType == FlipChessStatusMoveAI) {
flipchess_shiftMessages(model);
SCL_moveToString(
model->game.board,
model->squareFrom,
model->squareTo,
movePromote,
model->moveString);
SCL_gameMakeMove(&(model->game), model->squareFrom, model->squareTo, movePromote);
SCL_squareSetClear(model->moveHighlight);
SCL_squareSetAdd(model->moveHighlight, model->squareFrom);
SCL_squareSetAdd(model->moveHighlight, model->squareTo);
} else if(moveType == FlipChessStatusMoveUndo) {
flipchess_shiftMessages(model);
if(model->paramPlayerW != 0 || model->paramPlayerB != 0)
SCL_gameUndoMove(&(model->game));
SCL_gameUndoMove(&(model->game));
SCL_squareSetClear(model->moveHighlight);
}
switch(model->game.state) {
case SCL_GAME_STATE_WHITE_WIN:
model->msg = "white wins";
model->paramExit = FlipChessStatusReturn;
break;
case SCL_GAME_STATE_BLACK_WIN:
model->msg = "black wins";
model->paramExit = FlipChessStatusReturn;
break;
case SCL_GAME_STATE_DRAW_STALEMATE:
model->msg = "stalemate";
model->paramExit = FlipChessStatusReturn;
break;
case SCL_GAME_STATE_DRAW_REPETITION:
model->msg = "draw-repetition";
model->paramExit = FlipChessStatusReturn;
break;
case SCL_GAME_STATE_DRAW_DEAD:
model->msg = "draw-dead pos.";
model->paramExit = FlipChessStatusReturn;
break;
case SCL_GAME_STATE_DRAW:
model->msg = "draw";
model->paramExit = FlipChessStatusReturn;
break;
case SCL_GAME_STATE_DRAW_50:
model->msg = "draw-50 moves";
model->paramExit = FlipChessStatusReturn;
break;
default:
if(model->game.ply > 0) {
const uint8_t whitesTurn = SCL_boardWhitesTurn(model->game.board);
if(SCL_boardCheck(model->game.board, whitesTurn)) {
model->msg = (whitesTurn ? "black: check!" : "white: check!");
} else {
model->msg = (whitesTurn ? "black played" : "white played");
}
uint8_t s0, s1;
char p;
SCL_recordGetMove(model->game.record, model->game.ply - 1, &s0, &s1, &p);
SCL_moveToString(model->game.board, s0, s1, p, model->moveString);
}
break;
model->paramExit = moveType;
}
}
model->thinking = 0;
return model->paramExit;
}
void flipchess_scene_1_set_callback(
FlipChessScene1* instance,
FlipChessScene1Callback callback,
void* context) {
furi_assert(instance);
furi_assert(callback);
instance->callback = callback;
instance->context = context;
}
void flipchess_scene_1_draw(Canvas* canvas, FlipChessScene1Model* model) {
//UNUSED(model);
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
//canvas_draw_icon(canvas, 0, 0, &I_FLIPR_128x64);
// Frame
canvas_draw_frame(canvas, 0, 0, 66, 64);
// Message
canvas_set_font(canvas, FontSecondary);
if(model->thinking) {
canvas_draw_str(canvas, 68, 10, "thinking...");
} else {
canvas_draw_str(canvas, 68, 10, model->msg);
}
canvas_draw_str(canvas, 68, 19, model->moveString);
canvas_draw_str(canvas, 68, 31, model->msg2);
canvas_draw_str(canvas, 68, 40, model->moveString2);
canvas_draw_str(canvas, 68, 52, model->msg3);
canvas_draw_str(canvas, 68, 61, model->moveString3);
// Board
for(uint16_t y = 0; y < SCL_BOARD_PICTURE_WIDTH; y++) {
for(uint16_t x = 0; x < SCL_BOARD_PICTURE_WIDTH; x++) {
if(!picture[x + (y * SCL_BOARD_PICTURE_WIDTH)]) {
canvas_draw_dot(canvas, x + 1, y);
}
}
}
}
static int flipchess_scene_1_model_init(
FlipChessScene1Model* const model,
const int white_mode,
const int black_mode,
char* import_game_text) {
model->paramPlayerW = white_mode;
model->paramPlayerB = black_mode;
model->paramAnalyze = 255; // depth of analysis
model->paramMoves = 0;
model->paramInfo = 1;
model->paramFlipBoard = 0;
model->paramExit = FlipChessStatusNone;
model->paramStep = 0;
model->paramFEN = import_game_text;
model->paramPGN = NULL;
model->clockSeconds = -1;
SCL_Board emptyStartState = SCL_BOARD_START_STATE;
memcpy(model->startState, &emptyStartState, sizeof(SCL_Board));
#if ENABLE_960
model->random960PosNumber = -1;
#endif
model->squareSelected = 255;
model->squareSelectedLast = 28; // start selector near middle
model->msg = "init";
model->moveString[0] = '\0';
model->msg2 = "";
model->moveString2[0] = '\0';
model->msg3 = "";
model->moveString3[0] = '\0';
model->thinking = 0;
SCL_SquareSet emptySquareSet = SCL_SQUARE_SET_EMPTY;
memcpy(model->moveHighlight, &emptySquareSet, sizeof(SCL_SquareSet));
model->squareFrom = 255;
model->squareTo = 255;
model->turnState = 0;
SCL_randomBetterSeed(furi_hal_random_get());
#if ENABLE_960
#if SCL_960_CASTLING
if(model->random960PosNumber < 0) model->random960PosNumber = SCL_randomBetter();
#endif
if(model->random960PosNumber >= 0) model->random960PosNumber %= 960;
#endif
if(model->paramFEN != NULL)
SCL_boardFromFEN(model->startState, model->paramFEN);
else if(model->paramPGN != NULL) {
SCL_Record record;
SCL_recordFromPGN(record, model->paramPGN);
SCL_boardInit(model->startState);
SCL_recordApply(record, model->startState, model->paramStep);
}
#if ENABLE_960
#if SCL_960_CASTLING
else
SCL_boardInit960(model->startState, model->random960PosNumber);
#endif
#endif
SCL_gameInit(&(model->game), model->startState);
if(model->paramAnalyze != 255) {
char p;
uint8_t move[] = {0, 0};
model->paramPlayerW = model->paramAnalyze;
model->paramPlayerB = model->paramAnalyze;
int16_t evaluation =
flipchess_makeAIMove(model->game.board, &(move[0]), &(move[1]), &p, model);
if(model->paramAnalyze == 0) evaluation = SCL_boardEvaluateStatic(model->game.board);
char moveStr[5];
moveStr[4] = 0;
SCL_squareToString(move[0], moveStr);
SCL_squareToString(move[1], moveStr + 2);
//printf("%lf (%d)\n", ((double)evaluation) / ((double)SCL_VALUE_PAWN), evaluation);
//puts(moveStr);
return evaluation;
}
if(model->paramMoves) {
char string[256];
for(int i = 0; i < 64; ++i)
if(model->game.board[i] != '.' &&
SCL_pieceIsWhite(model->game.board[i]) == SCL_boardWhitesTurn(model->game.board)) {
SCL_SquareSet possibleMoves = SCL_SQUARE_SET_EMPTY;
SCL_boardGetMoves(model->game.board, i, possibleMoves);
SCL_SQUARE_SET_ITERATE_BEGIN(possibleMoves)
SCL_moveToString(model->game.board, i, iteratedSquare, 'q', string);
//printf("%s ", string);
SCL_SQUARE_SET_ITERATE_END
}
return FlipChessStatusReturn;
}
model->msg = (SCL_boardWhitesTurn(model->game.board) ? "white to move" : "black to move");
// 0 = success
return FlipChessStatusNone;
}
bool flipchess_scene_1_input(InputEvent* event, void* context) {
furi_assert(context);
FlipChessScene1* instance = context;
FlipChess* app = instance->context;
if(event->type == InputTypeRelease) {
switch(event->key) {
case InputKeyBack:
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
if(model->turnState == 1) {
model->turnState = 0;
SCL_squareSetClear(model->moveHighlight);
flipchess_drawBoard(model);
} else {
instance->callback(FlipChessCustomEventScene1Back, instance->context);
}
},
true);
break;
case InputKeyRight:
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
model->squareSelected = model->squareSelectedLast;
} else {
model->squareSelected = (model->squareSelected + 1) % 64;
}
flipchess_drawBoard(model);
},
true);
break;
case InputKeyDown:
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
model->squareSelected = model->squareSelectedLast;
} else {
model->squareSelected = (model->squareSelected + 56) % 64;
}
flipchess_drawBoard(model);
},
true);
break;
case InputKeyLeft:
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
model->squareSelected = model->squareSelectedLast;
} else {
model->squareSelected = (model->squareSelected + 63) % 64;
}
flipchess_drawBoard(model);
},
true);
break;
case InputKeyUp:
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
if(model->squareSelectedLast != 255 && model->squareSelected == 255) {
model->squareSelected = model->squareSelectedLast;
} else {
model->squareSelected = (model->squareSelected + 8) % 64;
}
flipchess_drawBoard(model);
},
true);
break;
case InputKeyOk:
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
// if(model->paramExit == FlipChessStatusReturn) {
// instance->callback(FlipChessCustomEventScene1Back, instance->context);
// break;
// }
if(!flipchess_isPlayerTurn(model)) {
model->thinking = 1;
}
},
true);
furi_thread_flags_wait(0, FuriFlagWaitAny, THREAD_WAIT_TIME);
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
// first turn of round, probably player but could be AI
if(flipchess_turn(model) == FlipChessStatusReturn) {
if(app->sound == 1) flipchess_voice_a_strange_game();
flipchess_play_long_bump(app);
}
flipchess_saveState(app, model);
flipchess_drawBoard(model);
},
true);
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
if(!flipchess_isPlayerTurn(model)) {
model->thinking = 1;
}
},
true);
furi_thread_flags_wait(0, FuriFlagWaitAny, THREAD_WAIT_TIME);
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
// if player played, let AI play
if(!flipchess_isPlayerTurn(model)) {
if(flipchess_turn(model) == FlipChessStatusReturn) {
if(app->sound == 1) flipchess_voice_a_strange_game();
flipchess_play_long_bump(app);
}
flipchess_saveState(app, model);
flipchess_drawBoard(model);
}
},
true);
break;
case InputKeyMAX:
break;
}
}
return true;
}
void flipchess_scene_1_exit(void* context) {
furi_assert(context);
FlipChessScene1* instance = (FlipChessScene1*)context;
with_view_model(
instance->view, FlipChessScene1Model * model, { model->paramExit = 0; }, true);
}
void flipchess_scene_1_enter(void* context) {
furi_assert(context);
FlipChessScene1* instance = (FlipChessScene1*)context;
FlipChess* app = instance->context;
flipchess_play_happy_bump(app);
with_view_model(
instance->view,
FlipChessScene1Model * model,
{
// load imported game if applicable
char* import_game_text = NULL;
if(app->import_game == 1 && strlen(app->import_game_text) > 0) {
import_game_text = app->import_game_text;
} else {
if(app->sound == 1) flipchess_voice_how_about_chess();
}
int init = flipchess_scene_1_model_init(
model, app->white_mode, app->black_mode, import_game_text);
if(init == FlipChessStatusNone) {
// perform initial turn, sets up and lets white
// AI play if applicable
const uint8_t turn = flipchess_turn(model);
if(turn == FlipChessStatusReturn) {
init = turn;
} else {
flipchess_saveState(app, model);
flipchess_drawBoard(model);
}
}
// if return status, return from scene immediately
// if(init == FlipChessStatusReturn) {
// instance->callback(FlipChessCustomEventScene1Back, instance->context);
// }
},
true);
}
FlipChessScene1* flipchess_scene_1_alloc() {
FlipChessScene1* instance = malloc(sizeof(FlipChessScene1));
instance->view = view_alloc();
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(FlipChessScene1Model));
view_set_context(instance->view, instance); // furi_assert crashes in events without this
view_set_draw_callback(instance->view, (ViewDrawCallback)flipchess_scene_1_draw);
view_set_input_callback(instance->view, flipchess_scene_1_input);
view_set_enter_callback(instance->view, flipchess_scene_1_enter);
view_set_exit_callback(instance->view, flipchess_scene_1_exit);
return instance;
}
void flipchess_scene_1_free(FlipChessScene1* instance) {
furi_assert(instance);
with_view_model(
instance->view, FlipChessScene1Model * model, { UNUSED(model); }, true);
view_free(instance->view);
free(instance);
}
View* flipchess_scene_1_get_view(FlipChessScene1* instance) {
furi_assert(instance);
return instance->view;
}