From 690fa9354c55198b9162c1785905401366b3ef7d Mon Sep 17 00:00:00 2001 From: Dpbm Date: Mon, 13 May 2024 14:25:49 -0300 Subject: [PATCH] added ai play screen --- CMakeLists.txt | 2 +- game/players/ai_player.cpp | 28 +++++++-- game/players/ai_player.h | 5 +- game/screens/ai_screen.h | 2 +- game/screens/ai_screen_play.cpp | 100 +++++++++++++++++++++++++++++--- game/screens/ai_screen_play.h | 18 +++++- genetic/population.cpp | 11 +++- machine/layer.cpp | 2 +- machine/machine.cpp | 4 ++ machine/machine.h | 1 + main.cpp | 3 - 11 files changed, 149 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9748b3d..f076620 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ add_library(board ${BOARD_FILE}) add_library(screens ${SCREENS_FILES}) add_library(players ${PLAYERS_FILES}) target_link_libraries(players machine matrix genetic helpers ${SDL2_LIBRARIES}) -target_link_libraries(screens players board helpers genetic ${SDL2_LIBRARIES} SDL2_ttf nfd) +target_link_libraries(screens players board helpers genetic matrix ${SDL2_LIBRARIES} SDL2_ttf nfd) target_link_libraries(board helpers players) target_link_libraries(snake PRIVATE helpers board screens players ${SDL2_LIBRARIES} SDL2_ttf) diff --git a/game/players/ai_player.cpp b/game/players/ai_player.cpp index f90099b..d9d30ad 100644 --- a/game/players/ai_player.cpp +++ b/game/players/ai_player.cpp @@ -1,16 +1,20 @@ #include #include #include +#include #include "ai_player.h" #include "../../machine/weights.h" #include "../../genetic/chromosome.h" #include "../../helpers/utils.h" +#include "../../matrix/matrix.h" #include "player.h" +using std::to_string; using std::stringstream; using Machine::Weights; using Genetic::Chromosome; using Utils::vec2; +using Matrices::Matrix; namespace Players{ AIPlayer::AIPlayer(uint8_t board_w, uint8_t board_h) : Player(board_w, board_h){ @@ -31,9 +35,7 @@ namespace Players{ } void AIPlayer::setup_nn(){ - this->input_layer->set_values(this->input_data); - - this->nn->add_layer(this->input_layer); + this->nn->add_layer(2); this->nn->add_layer(4); this->nn->add_layer(4); @@ -79,8 +81,9 @@ namespace Players{ int16_t fx = food.x; int16_t fy = food.y; - this->input_data->update_value(0, 0, (double)(px-fx)/w); - this->input_data->update_value(0, 1, (double)(py-fy)/h); + Matrix* input = this->nn->get_input_layer()->get_values(); + input->update_value(0, 0, (double)(px-fx)/w); + input->update_value(0, 1, (double)(py-fy)/h); } @@ -90,7 +93,6 @@ namespace Players{ void AIPlayer::update_dir(){ Matrix* output = this->nn->get_output_layer()->get_values(); - size_t new_dir = 0; double biggest = 0; for(size_t i = 0; i < 4; i++){ @@ -116,6 +118,20 @@ namespace Players{ filename << ".wg"; this->nn->save_weights(filename.str()); } + + void AIPlayer::save_weights(uint32_t gen){ + time_t now = time(0); + string time = asctime(localtime(&now)); + //remove the $\n at the end of the string + time.pop_back(); + + stringstream filename; + filename << time; + filename << "gen-"; + filename << to_string(gen); + filename << ".wg"; + this->nn->save_weights(filename.str()); + } AIPlayer::~AIPlayer(){ delete this->chromosome; diff --git a/game/players/ai_player.h b/game/players/ai_player.h index 0676c83..2f79099 100644 --- a/game/players/ai_player.h +++ b/game/players/ai_player.h @@ -11,7 +11,6 @@ using Genetic::Chromosome; using Matrices::Matrix; using Machine::NN; -using Machine::Layer; using Utils::vec2; namespace Players{ @@ -23,6 +22,8 @@ namespace Players{ ~AIPlayer(); void save_weights(); + void save_weights(uint32_t gen); + void load_genes_into_weights(); void update_input_data(const vec2& food, uint16_t w, uint16_t h); void compute_next_dir(); @@ -32,8 +33,6 @@ namespace Players{ private: Chromosome* chromosome = nullptr; NN* nn = new NN; - Layer* input_layer = new Layer(2, true); - Matrix* input_data = new Matrix(2, 1); void setup_nn(); void setup_chromosome(); diff --git a/game/screens/ai_screen.h b/game/screens/ai_screen.h index a937a7c..41128bf 100644 --- a/game/screens/ai_screen.h +++ b/game/screens/ai_screen.h @@ -29,7 +29,7 @@ namespace Screens{ uint8_t board_w = 43; uint8_t board_h = 30; - uint8_t gen_time = 30; //in seconds + uint8_t gen_time = 40; //in seconds uint32_t control_tick = 0; Population population{3000, board_w, board_h, 20}; diff --git a/game/screens/ai_screen_play.cpp b/game/screens/ai_screen_play.cpp index 323e2d5..c282a69 100644 --- a/game/screens/ai_screen_play.cpp +++ b/game/screens/ai_screen_play.cpp @@ -5,11 +5,15 @@ #include #include #include +#include #include "../../nativefiledialog-extended/src/include/nfd.h" #include "screens.h" +#include "start_screen.h" #include "ai_screen_play.h" #include "../../helpers/utils.h" +#include "../../helpers/constants.h" +using std::to_string; using std::cout; using std::endl; using std::size_t; @@ -19,39 +23,119 @@ namespace Screens { AIPlayScreen::AIPlayScreen(SDL_Renderer* render) : Screen(render){ if(NFD_Init() != NFD_OKAY){ - cout << "Failed on load NFD" << endl; + cout << "Failed on load NFD " << NFD_GetError() << endl; + exit(1); + } + + if(!this->font){ + cout << "Failed on getting font!" << TTF_GetError() << endl; exit(1); } - nfdchar_t *outPath; - nfdresult_t result = NFD_OpenDialog(&outPath, NULL, 0, NULL); + nfdresult_t result = NFD_OpenDialog(&this->nn_path, NULL, 0, NULL); if(result == NFD_OKAY){ - this->player = new AIPlayer(this->board_w, this->board_h, parse_nn(outPath)); + this->player = new AIPlayer(this->board_w, this->board_h, parse_nn(this->nn_path)); this->board.add_player(this->player); - NFD_FreePath(outPath); - return; + }else{ + cout << "You must select a weights file!" << endl; + exit(1); } - cout << "You must select a weights file!" << endl; - exit(1); + + SDL_Surface* score_text_surface = TTF_RenderText_Solid(this->font, "AI Score", this->text_color); + this->score_text_texture = SDL_CreateTextureFromSurface(render, score_text_surface); + this->score_text_shape = SDL_Rect{20, 20, score_text_surface->w, score_text_surface->h}; + SDL_FreeSurface(score_text_surface); + + if(this->score_text_texture == nullptr){ + cout << "Failed on creating score text texture!" << SDL_GetError() << endl; + exit(1); + } + + this->left_padding = 10 * SQUARE_SIDE; } void AIPlayScreen::execute(bool& game_loop){ + bool won = this->player->get_score() >= this->max_score; + this->finished_game = won || this->player->is_dead(); + if(this->finished_game){ + SDL_Surface* game_over_surface = TTF_RenderText_Solid(this->title_font, won ? "AI Wins!!!" : "Game Over", this->text_color); + SDL_Texture* game_over_texture = SDL_CreateTextureFromSurface(this->render, game_over_surface); + SDL_Rect game_over_shape = SDL_Rect{(WIDTH/2)-(game_over_surface->w/2), (HEIGHT/2)-(game_over_surface->h), game_over_surface->w, game_over_surface->h}; + SDL_FreeSurface(game_over_surface); + + SDL_Surface* reset_surface = TTF_RenderText_Solid(this->font, "Press 'r' to reset", this->text_color); + SDL_Texture* reset_texture = SDL_CreateTextureFromSurface(this->render, reset_surface); + SDL_Rect reset_shape = SDL_Rect{(WIDTH/2)-(reset_surface->w/2), (HEIGHT/2)+(reset_surface->h)+20, reset_surface->w, reset_surface->h}; + SDL_FreeSurface(reset_surface); + + SDL_Surface* back_surface = TTF_RenderText_Solid(this->font, "Press 'g' to back to the start screen", this->text_color); + SDL_Texture* back_texture = SDL_CreateTextureFromSurface(this->render, back_surface); + SDL_Rect back_shape = SDL_Rect{(WIDTH/2)-(back_surface->w/2), (HEIGHT/2)+(back_surface->h)+50, back_surface->w, back_surface->h}; + SDL_FreeSurface(back_surface); + + SDL_SetRenderDrawColor(this->render, 0, 0, 0, 255); + SDL_RenderCopy(this->render, game_over_texture, NULL, &game_over_shape); + SDL_RenderCopy(this->render, reset_texture, NULL, &reset_shape); + SDL_RenderCopy(this->render, back_texture, NULL, &back_shape); + SDL_DestroyTexture(game_over_texture); + SDL_DestroyTexture(back_texture); + SDL_DestroyTexture(reset_texture); + return; + } + + this->player->update_input_data(this->board.get_food(), this->board_w, this->board_h); + this->player->compute_next_dir(); + this->player->update_dir(); + this->board.update_player_pos(); this->render_board(&this->board); + SDL_SetRenderDrawColor(this->render, 0, 0, 0, 255); + SDL_RenderCopy(this->render, this->score_text_texture, NULL, &this->score_text_shape); + + if(this->score_texture != nullptr) + SDL_DestroyTexture(this->score_texture); + SDL_Surface* score_surface = TTF_RenderText_Solid(this->font, to_string(this->player->get_score()).c_str(), this->text_color); + this->score_texture = SDL_CreateTextureFromSurface(this->render, score_surface); + this->score_shape = SDL_Rect{20, 60, score_surface->w, score_surface->h}; + SDL_FreeSurface(score_surface); + + SDL_RenderCopy(this->render, this->score_texture, NULL, &this->score_shape); } Screen* AIPlayScreen::key_event(const SDL_Keycode& key){ + switch (key) { + case SDLK_g: + if(this->finished_game) + return new StartScreen(this->render); + + case SDLK_r: + if(this->finished_game) + this->reset(); + + default: + break; + } return nullptr; } void AIPlayScreen::close_event(){ } + void AIPlayScreen::reset(){ + this->finished_game = false; + delete this->player; + this->player = new AIPlayer{this->board_w, this->board_h, parse_nn(this->nn_path)}; + this->board.add_player(this->player); + this->board.random_food(); + } AIPlayScreen::~AIPlayScreen(){ delete this->player; NFD_Quit(); + NFD_FreePath(this->nn_path); + SDL_DestroyTexture(this->score_texture); + SDL_DestroyTexture(this->score_text_texture); } } diff --git a/game/screens/ai_screen_play.h b/game/screens/ai_screen_play.h index f030d77..a3e2829 100644 --- a/game/screens/ai_screen_play.h +++ b/game/screens/ai_screen_play.h @@ -6,6 +6,7 @@ #include "screens.h" #include "../board.h" #include "../players/ai_player.h" +#include "../../nativefiledialog-extended/src/include/nfd.h" using Screens::Screen; using Game::Board; @@ -21,12 +22,25 @@ namespace Screens{ void close_event(); private: - uint8_t board_w = 43; + uint8_t board_w = 45; uint8_t board_h = 30; Board board{board_w, board_h}; - + + bool finished_game = false; + uint16_t max_score = 1000; AIPlayer* player = nullptr; + nfdchar_t* nn_path = nullptr; + + TTF_Font* font = TTF_OpenFont("./assets/pressstart.ttf", 20); + TTF_Font* title_font = TTF_OpenFont("./assets/pressstart.ttf", 40); + + SDL_Rect score_text_shape; + SDL_Rect score_shape; + SDL_Texture* score_text_texture = nullptr; + SDL_Texture* score_texture = nullptr; + + void reset(); }; }; diff --git a/genetic/population.cpp b/genetic/population.cpp index 9ec0fb5..da02795 100644 --- a/genetic/population.cpp +++ b/genetic/population.cpp @@ -161,7 +161,10 @@ namespace Genetic{ void Population::next_gen(){ cout << "gen: " << this->gen << endl; cout << "best fit: " << this->best_fitness << endl; - cout << "best score: " << this->best_score << endl << endl; + cout << "best score: " << this->best_score << endl; + + cout << "saving weights..." << endl << endl; + this->get_best_individual()->player->save_weights(this->gen); this->gen++; this->best_score = 0; @@ -170,17 +173,20 @@ namespace Genetic{ Individual** parents = this->select_parents(); Chromosome* offspring = this->generate_offspring(parents[0]->player->get_chromossome(), parents[1]->player->get_chromossome()); delete parents; + cout << "no problem on delete parents..." << endl; Gene* offspring_genes = offspring->get_genes(); uint64_t offspring_ch_size = offspring->get_size(); this->clear(); + cout << "no problem on delete pointers" << endl; this->individuals.clear(); + cout << "no problem on clear individuals" << endl; this->food_positions.clear(); + cout << "no problem on clear food" << endl; this->generate_food_positions(); vec2 first_food_pos = this->food_positions.at(0); - this->individuals.clear(); for(size_t i = 0; i < this->total_ind; i++){ Individual* ind = new Individual; ind->board = new Board(board_w, board_h); @@ -204,6 +210,7 @@ namespace Genetic{ this->individuals.push_back(ind); } + cout << "no problem at the end of the function" << endl << endl; } Individual** Population::select_parents(){ diff --git a/machine/layer.cpp b/machine/layer.cpp index 3d77063..d15d7f1 100644 --- a/machine/layer.cpp +++ b/machine/layer.cpp @@ -49,7 +49,7 @@ namespace Machine{ delete this->values; this->values = values; } - + void Layer::set_activation_function(void(*activation)(Matrix*)){ if(this->input) throw invalid_argument("Input layer must not have a activation function!"); diff --git a/machine/machine.cpp b/machine/machine.cpp index 01a02b7..07ccd2f 100644 --- a/machine/machine.cpp +++ b/machine/machine.cpp @@ -146,4 +146,8 @@ namespace Machine { Layer* NN::get_output_layer(){ return this->layers.at(this->total_layers-1); } + + Layer* NN::get_input_layer(){ + return this->layers.at(0); + } } diff --git a/machine/machine.h b/machine/machine.h index 47f6da8..976b3c4 100644 --- a/machine/machine.h +++ b/machine/machine.h @@ -26,6 +26,7 @@ namespace Machine { ~NN(); void feedforward(); Layer* get_output_layer(); + Layer* get_input_layer(); void load_weights(vector new_weights); private: diff --git a/main.cpp b/main.cpp index bb06a58..79beace 100644 --- a/main.cpp +++ b/main.cpp @@ -17,9 +17,6 @@ using Screens::Screen; using Screens::StartScreen; int main(){ - setlocale(LC_ALL, "en_US.UTF-8"); - setlocale(LC_NUMERIC, "en_US.UTF-8"); - if(SDL_Init(SDL_INIT_EVERYTHING) < 0){ cout << "SDL_Init Error: " << SDL_GetError() << endl; return 1;