Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Override player input with machine learning models #17407

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
59cf1fb
Add dummy game ai subsystem
MatPoliquin Jun 23, 2024
2fb1459
First working prototype of a machine learning model that can override…
MatPoliquin Jun 27, 2024
a21e9b3
Update README.md
MatPoliquin Jun 27, 2024
40cb006
Update README.md
MatPoliquin Jun 27, 2024
7d7524e
Fix loading path on Windows
MatPoliquin Jul 4, 2024
3dd87f5
Change ai override to player 2
MatPoliquin Jul 5, 2024
5477094
Added quick menu show game ai option
MatPoliquin Jul 7, 2024
6953b2a
Implemented Quick Menu entry for Game AI options
MatPoliquin Jul 8, 2024
a9eb210
Redirect debug logs to retroarch log system + properly support player…
MatPoliquin Jul 9, 2024
eeaf118
Added support to use framebuffer as input to the AI
MatPoliquin Jul 12, 2024
8b4cf7c
Added pixel format parameter to API
MatPoliquin Jul 14, 2024
718d737
Fix game name
MatPoliquin Jul 19, 2024
022017a
code clean-up of game_ai.cpp
MatPoliquin Jul 19, 2024
f866b6a
Update README.md - Windows Build
MatPoliquin Jul 28, 2024
ee29257
Update README.md
MatPoliquin Jul 28, 2024
7120fa6
Update README.md
MatPoliquin Jan 9, 2025
69e94b7
Merge branch 'libretro:master' into master
MatPoliquin Jan 13, 2025
1bded6b
Update README.md
MatPoliquin Jan 13, 2025
58f9732
Merge branch 'libretro:master' into master
MatPoliquin Jan 13, 2025
b54c65b
Update config.params.sh
MatPoliquin Jan 13, 2025
8b71d8e
Fix compile error in menu_displaylist.c
MatPoliquin Jan 14, 2025
684eefa
Add missing #define in menu_cbs_title.c
MatPoliquin Jan 14, 2025
6841795
Added new game_ai entry in griffin_cpp
MatPoliquin Jan 14, 2025
9e1ca05
Remove GAME_AI entry in msg_hash_us.c
MatPoliquin Jan 14, 2025
ae0a264
Fix compile error in menu_displaylist.h
MatPoliquin Jan 14, 2025
59d0d11
Removed GAME AI references from README.md
MatPoliquin Jan 14, 2025
93b3aac
Fixes coding style + add GameAI lib API header
MatPoliquin Jan 14, 2025
ed40ac3
Convert comment to legacy + remove unused code
MatPoliquin Jan 14, 2025
b868b11
Additional coding style fixes to game_ai.cpp
MatPoliquin Jan 14, 2025
913c4ad
Fix identation issues in game_ai.cpp
MatPoliquin Jan 14, 2025
6c35f84
Removed some debug code in game_ai.cpp
MatPoliquin Jan 14, 2025
aeeb622
Add game_ai_lib in deps
MatPoliquin Jan 14, 2025
65aaa37
Replace assert with retro_assert
MatPoliquin Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile.common
Original file line number Diff line number Diff line change
Expand Up @@ -2648,4 +2648,9 @@ ifeq ($(HAVE_ODROIDGO2), 1)
gfx/drivers/oga_gfx.o
endif

ifeq ($(HAVE_GAME_AI),1)
DEFINES += -DHAVE_GAME_AI
OBJ += ai/game_ai.o
MatPoliquin marked this conversation as resolved.
Show resolved Hide resolved
endif

##################################
26 changes: 26 additions & 0 deletions ai/GameAILibAPI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <bitset>
#include <string>
#include <filesystem>
#include <vector>
#include <queue>

typedef void (*debug_log_t)(int level, const char *fmt, ...);

#define GAMEAI_MAX_BUTTONS 16

class GameAI {
public:
virtual void Init(void * ram_ptr, int ram_size) {};
virtual void Think(bool buttons[GAMEAI_MAX_BUTTONS], int player, const void *frame_data, unsigned int frame_width, unsigned int frame_height, unsigned int frame_pitch, unsigned int pixel_format) {};
void SetShowDebug(const bool show){ this->showDebug = show; };
void SetDebugLog(debug_log_t func){debugLogFunc = func;};

private:
bool showDebug;
debug_log_t debugLogFunc;
};


typedef GameAI * (*creategameai_t)(const char *);
224 changes: 224 additions & 0 deletions ai/game_ai.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#include "game_ai.h"
#include <stdio.h>
#include <retro_assert.h>
#include <bitset>
#include <iostream>
#include <string>
#include <stdarg.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif

#define GAME_AI_MAX_PLAYERS 2

#include "GameAILibAPI.h"

class GameAIManager {
public:
GameAIManager();

signed short int Input(unsigned int port, unsigned int device, unsigned int idx, unsigned int id, signed short int result);
void Init();
void Load(const char * name, void * ram_ptr, int ram_size, retro_log_printf_t log);
void Think(bool override_p1, bool override_p2, bool show_debug, const void *frame_data, unsigned int frame_width, unsigned int frame_height, unsigned int frame_pitch, unsigned int pixel_format);

private:
creategameai_t CreateGameAI;
GameAI * ga;
volatile void * g_ram_ptr;
volatile int g_ram_size;
volatile signed short int g_buttons_bits[GAME_AI_MAX_PLAYERS];
volatile int g_frameCount;
volatile char game_ai_lib_path[1024];
std::string g_game_name;

};

GameAIManager gameAIMgr;
retro_log_printf_t g_log;

//======================================================
// Helper functions
//======================================================
extern "C" void game_ai_debug_log(int level, const char *fmt, ...)
{
va_list vp;
va_start(vp, fmt);

if (g_log)
{
g_log((enum retro_log_level)level, fmt, vp);
}

va_end(vp);
}

void array_to_bits_16(volatile signed short & result, const bool b[16])
{
for (int bit = 0; bit <= 15; bit++)
{
result |= b[bit] ? (1 << bit) : 0;
}
}

//======================================================
// GameAIManager::GameAIManager
//======================================================
GameAIManager::GameAIManager()
{
CreateGameAI = nullptr;
ga = nullptr;
g_ram_ptr = nullptr;
g_ram_size = 0;
g_buttons_bits[0] = 0;
g_buttons_bits[1] = 0;
g_frameCount = 0;
game_ai_lib_path[0]='\0';
g_log = nullptr;
}

//======================================================
// GameAIManager::Input
//======================================================
signed short int GameAIManager::Input(unsigned int port, unsigned int device, unsigned int idx, unsigned int id, signed short int result)
{
if (ga == nullptr)
return 0;

if (port < GAME_AI_MAX_PLAYERS)
return g_buttons_bits[port];

return 0;
}

//======================================================
// GameAIManager::Init
//======================================================
void GameAIManager::Init()
{
if (CreateGameAI == nullptr)
{
#ifdef _WIN32
HINSTANCE hinstLib;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

hinstLib = LoadLibrary(TEXT("game_ai.dll"));
retro_assert(hinstLib);

char full_module_path[MAX_PATH];
DWORD dwLen = GetModuleFileNameA(hinstLib, static_cast<char*>(&full_module_path), MAX_PATH);

if (hinstLib != NULL)
{
CreateGameAI = (creategameai_t) GetProcAddress(hinstLib, "CreateGameAI");

retro_assert(CreateGameAI);
}
#else
void *myso = dlopen("libgame_ai.so", RTLD_NOW);
retro_assert(myso);

if(myso != NULL)
{
dlinfo(myso, RTLD_DI_ORIGIN, (void *) &game_ai_lib_path);

CreateGameAI = reinterpret_cast<creategameai_t>(dlsym(myso, "CreateGameAI"));
retro_assert(CreateGameAI);
}
#endif
}
}

//======================================================
// GameAIManager::Load
//======================================================
void GameAIManager::Load(const char *name, void *ram_ptr, int ram_size, retro_log_printf_t log)
{
g_game_name = name;

g_ram_ptr = ram_ptr;
g_ram_size = ram_size;

g_log = log;
}

//======================================================
// GameAIManager::Think
//======================================================
void GameAIManager::Think(bool override_p1, bool override_p2, bool show_debug, const void *frame_data, unsigned int frame_width, unsigned int frame_height, unsigned int frame_pitch, unsigned int pixel_format)
{
if (ga)
ga->SetShowDebug(show_debug);

if (ga == nullptr && g_ram_ptr != nullptr)
{
ga = CreateGameAI(g_game_name.c_str());
retro_assert(ga);

if (ga)
{
std::string data_path((char *)game_ai_lib_path);
data_path += "/data/";
data_path += g_game_name;

ga->Init((void *) g_ram_ptr, g_ram_size);

ga->SetDebugLog(game_ai_debug_log);
}
}

if (g_frameCount >= 3)
{
if (ga)
{
bool b[16] = {0};

g_buttons_bits[0]=0;
g_buttons_bits[1]=0;

if (override_p1)
{
ga->Think(b, 0, frame_data, frame_width, frame_height, frame_pitch, pixel_format);
array_to_bits_16(g_buttons_bits[0], b);
}

if (override_p2)
{
ga->Think(b, 1, frame_data, frame_width, frame_height, frame_pitch, pixel_format);
array_to_bits_16(g_buttons_bits[1], b);
}
}
g_frameCount=0;
}
else
{
g_frameCount++;
}
}

//======================================================
// Interface to RA
//======================================================
extern "C" signed short int game_ai_input(unsigned int port, unsigned int device, unsigned int idx, unsigned int id, signed short int result)
{
return gameAIMgr.Input(port,device, idx, id, result);
}

extern "C" void game_ai_init()
{
gameAIMgr.Init();
}

extern "C" void game_ai_load(const char * name, void * ram_ptr, int ram_size, retro_log_printf_t log)
{
gameAIMgr.Load(name, ram_ptr, ram_size, log);
}

extern "C" void game_ai_think(bool override_p1, bool override_p2, bool show_debug, const void *frame_data, unsigned int frame_width, unsigned int frame_height, unsigned int frame_pitch, unsigned int pixel_format)
{
gameAIMgr.Think(override_p1, override_p2, show_debug, frame_data, frame_width, frame_height, frame_pitch, pixel_format);
}

15 changes: 15 additions & 0 deletions ai/game_ai.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once


#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

#include <libretro.h>

EXTERNC signed short int game_ai_input(unsigned int port, unsigned int device, unsigned int idx, unsigned int id, signed short int result);
EXTERNC void game_ai_init();
EXTERNC void game_ai_load(const char * name, void * ram_ptr, int ram_size, retro_log_printf_t log);
EXTERNC void game_ai_think(bool override_p1, bool override_p2, bool show_debug, const void *frame_data, unsigned int frame_width, unsigned int frame_height, unsigned int frame_pitch, unsigned int pixel_format);
4 changes: 4 additions & 0 deletions configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -2227,6 +2227,10 @@ static struct config_bool_setting *populate_settings_bool(
SETTING_BOOL("gcdwebserver_alert", &settings->bools.gcdwebserver_alert, true, true, false);
#endif

#ifdef HAVE_GAME_AI
SETTING_BOOL("quick_menu_show_game_ai", &settings->bools.quick_menu_show_game_ai, true, 1, false);
#endif

*size = count;

return tmp;
Expand Down
8 changes: 8 additions & 0 deletions configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,14 @@ typedef struct settings
#if defined(HAVE_COCOATOUCH)
bool gcdwebserver_alert;
#endif

#ifdef HAVE_GAME_AI
bool quick_menu_show_game_ai;
bool game_ai_override_p1;
bool game_ai_override_p2;
bool game_ai_show_debug;
#endif

} bools;

uint8_t flags;
Expand Down
8 changes: 8 additions & 0 deletions deps/game_ai_lib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
libtorch/
build/
CMakeFiles/
Debug/
libtorch/
win/
*.zip
.vscode/
25 changes: 25 additions & 0 deletions deps/game_ai_lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(custom_ops)

find_package(Torch REQUIRED)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})

add_executable(test test.cpp RetroModel.cpp)
target_link_libraries(test "${TORCH_LIBRARIES}" ${OpenCV_LIBS})

add_library(game_ai SHARED GameAILocal.cpp RetroModel.cpp games/NHL94GameAI.cpp games/NHL94GameData.cpp games/DefaultGameAI.cpp utils/data.cpp utils/memory.cpp utils/utils.cpp)

target_link_libraries(game_ai "${TORCH_LIBRARIES}" ${OpenCV_LIBS})

set_property(TARGET test PROPERTY CXX_STANDARD 17)
set_property(TARGET game_ai PROPERTY CXX_STANDARD 17)

if (MSVC)
file(GLOB TORCH_DLLS "${TORCH_INSTALL_PREFIX}/lib/*.dll")
add_custom_command(TARGET test
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${TORCH_DLLS}
$<TARGET_FILE_DIR:test>)
endif (MSVC)
26 changes: 26 additions & 0 deletions deps/game_ai_lib/GameAI.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <bitset>
#include <string>
#include <filesystem>
#include <vector>
#include <queue>

typedef void (*debug_log_t)(int level, const char *fmt, ...);

#define GAMEAI_MAX_BUTTONS 16

class GameAI {
public:
virtual void Init(void * ram_ptr, int ram_size) {};
virtual void Think(bool buttons[GAMEAI_MAX_BUTTONS], int player, const void *frame_data, unsigned int frame_width, unsigned int frame_height, unsigned int frame_pitch, unsigned int pixel_format) {};
void SetShowDebug(const bool show){ this->showDebug = show; };
void SetDebugLog(debug_log_t func){debugLogFunc = func;};

private:
bool showDebug;
debug_log_t debugLogFunc;
};


typedef GameAI * (*creategameai_t)(const char *);
Loading
Loading