diff --git a/apps_source_code/reversi/application.fam b/apps_source_code/reversi/application.fam index 507d4b843b4..75af5c99a90 100644 --- a/apps_source_code/reversi/application.fam +++ b/apps_source_code/reversi/application.fam @@ -7,13 +7,13 @@ App( requires=[ "gui", ], - stack_size=1 * 1024, + stack_size=2 * 1024, order=90, fap_icon="game_reversi.png", fap_category="Games", fap_icon_assets_symbol="game_reversi", fap_author="@dimat", fap_weburl="https://github.com/zyuhel/flipperzero-racegame", - fap_version="1.2", + fap_version="1.3", fap_description="Reversi game, the game controls should be intuitive. Longs press on OK opens the menu to start a new game.", ) diff --git a/apps_source_code/reversi/game_reversi.c b/apps_source_code/reversi/game_reversi.c index f2efed8851b..bd7f0a278fa 100644 --- a/apps_source_code/reversi/game_reversi.c +++ b/apps_source_code/reversi/game_reversi.c @@ -12,8 +12,7 @@ #define FRAME_TOP 3 #define FRAME_CELL_SIZE 7 -#define SAVING_DIRECTORY "/ext/apps/Games" -#define SAVING_FILENAME SAVING_DIRECTORY "/game_reversi.save" +#define SAVING_FILENAME APP_DATA_PATH("reversi.save") typedef enum { AppScreenGame, AppScreenMenu } AppScreen; @@ -188,12 +187,6 @@ bool load_game(GameState* game_state) { void save_game(const GameState* game_state) { Storage* storage = furi_record_open(RECORD_STORAGE); - if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) { - if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) { - return; - } - } - File* file = storage_file_alloc(storage); if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { storage_file_write(file, game_state, sizeof(GameState)); @@ -330,7 +323,7 @@ int32_t game_reversi_app() { furi_mutex_acquire(app_state.mutex, FuriWaitForever); app_state.selected_menu_item = 0; app_state.screen = AppScreenMenu; - + furi_mutex_release(app_state.mutex); view_port_update(view_port); continue; diff --git a/apps_source_code/reversi/reversi.c b/apps_source_code/reversi/reversi.c index 46c1929cf22..725ae576b74 100644 --- a/apps_source_code/reversi/reversi.c +++ b/apps_source_code/reversi/reversi.c @@ -5,6 +5,16 @@ // Psst! Most of this file was written with Copilot +const int weights[BOARD_SIZE][BOARD_SIZE] = { + {100, -10, 10, 10, 10, 10, -10, 100}, + {-10, -20, -5, -5, -5, -5, -20, -10}, + {10, -5, 5, 1, 1, 5, -5, 10}, + {10, -5, 1, 0, 0, 1, -5, 10}, + {10, -5, 1, 0, 0, 1, -5, 10}, + {10, -5, 5, 1, 1, 5, -5, 10}, + {-10, -20, -5, -5, -5, -5, -20, -10}, + {100, -10, 10, 10, 10, 10, -10, 100}}; + // Check if the move is legal by checking if it results in any opponent pieces being captured bool is_legal_move(int8_t board[BOARD_SIZE][BOARD_SIZE], int row, int col, int player) { if(board[row][col] != 0) return false; @@ -53,52 +63,56 @@ bool has_legal_moves(int8_t board[BOARD_SIZE][BOARD_SIZE], int8_t player_color) return false; } -// Calculate the heuristic value of the current board. This function can -// be replaced with a more complex evaluation function that takes into -// account factors such as mobility, piece square tables, etc. -int heuristic(int8_t board[BOARD_SIZE][BOARD_SIZE]) { - int white = 0, black = 0; +int evaluate_board(int8_t board[BOARD_SIZE][BOARD_SIZE], int player) { + int score = 0; for(int i = 0; i < BOARD_SIZE; i++) { for(int j = 0; j < BOARD_SIZE; j++) { - if(board[i][j] == 1) white++; - if(board[i][j] == -1) black++; + if(board[i][j] == player) { + score += weights[i][j]; + } else if(board[i][j] == -player) { + score -= weights[i][j]; + } } } - return white - black; + return score; } // Make a move on the board and capture any opponent pieces -void make_move(GameState* state, int x, int y, int player) { - state->board[x][y] = player; +void make_move( + GameState* game_state, + int8_t board[BOARD_SIZE][BOARD_SIZE], + int x, + int y, + int player) { + board[x][y] = player; int opponent = -player; for(int i = -1; i <= 1; i++) { for(int j = -1; j <= 1; j++) { if(i == 0 && j == 0) continue; int r = x + i, c = y + j; - if(r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && - state->board[r][c] == opponent) { + if(r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] == opponent) { int k = 2; while(true) { r += i; c += j; if(r < 0 || r >= BOARD_SIZE || c < 0 || c >= BOARD_SIZE) break; - if(state->board[r][c] == player) { + if(board[r][c] == player) { r -= i; c -= j; while(r != x || c != y) { - state->board[r][c] = player; + board[r][c] = player; r -= i; c -= j; } break; } - if(state->board[r][c] == 0) break; + if(board[r][c] == 0) break; k++; } } } } - state->is_game_over = is_game_over(state->board); + game_state->is_game_over = is_game_over(game_state->board); } void init_game(GameState* state) { @@ -136,11 +150,68 @@ void human_move(GameState* game_state) { game_state->cursor_y, game_state->current_player)) { make_move( - game_state, game_state->cursor_x, game_state->cursor_y, game_state->current_player); + game_state, + game_state->board, + game_state->cursor_x, + game_state->cursor_y, + game_state->current_player); game_state->current_player = -game_state->current_player; } } +int minimax( + GameState* game_state, + int8_t board[BOARD_SIZE][BOARD_SIZE], + int depth, + bool is_maximizing, + int player, + int alpha, + int beta) { + if(depth == 0 || is_game_over(board)) { + return evaluate_board(board, player); + } + + if(is_maximizing) { + int max_eval = -1000000; + for(int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + if(is_legal_move(board, i, j, player)) { + int8_t temp_board[BOARD_SIZE][BOARD_SIZE]; + memcpy(temp_board, board, sizeof(temp_board)); + make_move(game_state, temp_board, i, j, player); + int eval = + minimax(game_state, temp_board, depth - 1, false, -player, alpha, beta); + max_eval = max(max_eval, eval); + alpha = max(alpha, eval); + if(beta <= alpha) { + break; + } + } + } + } + return max_eval; + } else { + int min_eval = 1000000; + for(int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + if(is_legal_move(board, i, j, -player)) { + int8_t temp_board[BOARD_SIZE][BOARD_SIZE]; + memcpy(temp_board, board, sizeof(temp_board)); + make_move(game_state, temp_board, i, j, -player); + int eval = + minimax(game_state, temp_board, depth - 1, true, player, alpha, beta); + min_eval = min(min_eval, eval); + beta = min(beta, eval); + if(beta <= alpha) { + break; + } + } + } + } + return min_eval; + } +} + void computer_move(GameState* game_state) { if(game_state->current_player == game_state->human_color) { return; @@ -148,19 +219,28 @@ void computer_move(GameState* game_state) { int best_row = -1, best_col = -1, best_score = -1000000; for(int i = 0; i < BOARD_SIZE; i++) { for(int j = 0; j < BOARD_SIZE; j++) { - if(!is_legal_move(game_state->board, i, j, game_state->current_player)) { - continue; - } - int score = heuristic(game_state->board); - if(score > best_score) { - best_score = score; - best_row = i; - best_col = j; + if(is_legal_move(game_state->board, i, j, game_state->current_player)) { + int8_t temp_board[BOARD_SIZE][BOARD_SIZE]; + memcpy(temp_board, game_state->board, sizeof(temp_board)); + make_move(game_state, temp_board, i, j, game_state->current_player); + int score = minimax( + game_state, + temp_board, + 3, + false, + -game_state->current_player, + -1000000, + 1000000); + if(score > best_score) { + best_score = score; + best_row = i; + best_col = j; + } } } } if(best_row != -1) { - make_move(game_state, best_row, best_col, game_state->current_player); + make_move(game_state, game_state->board, best_row, best_col, game_state->current_player); } if(has_legal_moves(game_state->board, game_state->human_color)) { game_state->current_player = -game_state->current_player; diff --git a/apps_source_code/reversi/reversi.h b/apps_source_code/reversi/reversi.h index ee45888384b..44aa7479c15 100644 --- a/apps_source_code/reversi/reversi.h +++ b/apps_source_code/reversi/reversi.h @@ -3,6 +3,11 @@ #include #include +#ifndef __cplusplus +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + #define BLACK 1 #define WHITE -1 #define BOARD_SIZE 8