RetroArch/deps/game_ai_lib/utils/data.cpp

406 lines
9.3 KiB
C++

// Adapted from OpenAI's retro source code:
// https://github.com/openai/retro
#include "data.h"
//#include "script.h"
#include "utils.h"
#ifdef ERROR
#undef ERROR
#endif
#include "json.hpp"
#include <fstream>
using namespace Retro;
using namespace std;
using nlohmann::json;
static string s_dataDirectory;
template<typename T>
T find(json::const_reference j, const string& key) {
const auto& iter = j.find(key);
if (iter == j.end()) {
return T();
}
try {
T t = *iter;
return t;
} catch (json::exception&) {
return T();
}
}
static void setActions(const vector<string>& buttonList, const vector<vector<vector<string>>>& actionsIn, map<int, set<int>>& actions) {
actions.clear();
for (const auto& outer : actionsIn) {
set<int> sublist;
int mask = 0;
for (const auto& middle : outer) {
int buttons = 0;
for (const auto& button : middle) {
const auto& iter = find(buttonList.begin(), buttonList.end(), button);
buttons |= 1 << (iter - buttonList.begin());
}
mask |= buttons;
sublist.insert(buttons);
}
actions.emplace(mask, move(sublist));
}
}
static unsigned filterAction(unsigned action, const map<int, set<int>>& actions) {
unsigned newAction = 0;
for (const auto& actionSet : actions) {
unsigned maskedAction = action & actionSet.first;
if (actionSet.second.find(maskedAction) != actionSet.second.end()) {
newAction |= maskedAction;
}
}
return newAction;
}
Variable::Variable(const DataType& type, size_t address, uint64_t mask)
: type(type)
, address(address)
, mask(mask) {
}
bool Variable::operator==(const Variable& other) const {
return type == other.type && address == other.address && mask == other.mask;
}
bool GameData::load(const string& filename) {
ifstream file(filename);
return load(&file);
}
bool GameData::load(istream* file) {
json manifest;
try {
*file >> manifest;
} catch (json::exception&) {
return false;
}
const auto& info = const_cast<const json&>(manifest).find("info");
if (info == manifest.cend()) {
return false;
}
unordered_map<std::string, Variable> oldVars;
oldVars.swap(m_vars);
for (auto var = info->cbegin(); var != info->cend(); ++var) {
if (var->find("address") == var->cend() || var->find("type") == var->cend()) {
oldVars.swap(m_vars);
return false;
}
string dtype = var->at("type");
if (dtype.size() < 3) {
continue;
}
try {
Variable v(dtype, var->at("address"), var->value("mask", UINT64_MAX));
setVariable(var.key(), v);
} catch (std::out_of_range) {
continue;
}
}
return true;
}
bool GameData::save(const string& filename) const {
ofstream file(filename);
return save(&file);
}
bool GameData::save(ostream* file) const {
json manifest;
json info;
for (const auto& var : m_vars) {
json jvar;
jvar["address"] = var.second.address;
jvar["type"] = var.second.type.type;
if (var.second.mask != UINT64_MAX) {
jvar["mask"] = var.second.mask;
}
info[var.first] = jvar;
}
manifest["info"] = info;
try {
file->width(2);
*file << manifest;
*file << endl;
} catch (json::exception&) {
return false;
}
return true;
}
string GameData::dataPath(const string& hint) {
if (s_dataDirectory.size()) {
return s_dataDirectory;
}
const char* envDir = getenv("RETRO_DATA_PATH");
if (envDir) {
s_dataDirectory = envDir;
} else {
s_dataDirectory = drillUp({ "retro/data", "data" }, ".", hint);
}
return s_dataDirectory;
}
void GameData::reset() {
restart();
m_lastMem.reset();
m_cloneMem.reset();
m_vars.clear();
//m_searches.clear();
m_searchOldMem.clear();
}
void GameData::restart() {
m_customVars.clear();
}
void GameData::updateRam() {
m_lastMem = move(m_cloneMem);
m_cloneMem.clone(m_mem);
}
void GameData::setTypes(const vector<DataType> types) {
m_types = vector<DataType>(types);
}
void GameData::setButtons(const vector<string>& buttons) {
m_buttons = buttons;
}
vector<string> GameData::buttons() const {
return m_buttons;
}
void GameData::setActions(const vector<vector<vector<string>>>& actions) {
::setActions(m_buttons, actions, m_actions);
}
map<int, set<int>> GameData::validActions() const {
return m_actions;
}
unsigned GameData::filterAction(unsigned action) const {
return ::filterAction(action, m_actions);
}
Datum GameData::lookupValue(const string& name) {
auto variant = m_customVars.find(name);
if (variant != m_customVars.end()) {
return Datum(variant->second.get());
}
auto v = m_vars.find(name);
if (v == m_vars.end()) {
throw invalid_argument(name);
}
return m_mem[v->second];
}
Variant GameData::lookupValue(const string& name) const {
auto variant = m_customVars.find(name);
if (variant != m_customVars.end()) {
return *variant->second;
}
auto v = m_vars.find(name);
if (v == m_vars.end()) {
throw invalid_argument(name);
}
return m_mem[v->second];
}
/*Datum GameData::lookupValue(const TypedSearchResult& result) {
return m_mem[Variable{ result.type, result.address }];
}
int64_t GameData::lookupValue(const TypedSearchResult& result) const {
return m_mem[Variable{ result.type, result.address }];
}*/
int64_t GameData::lookupDelta(const string& name) const {
const auto& v = m_vars.find(name);
if (v == m_vars.end()) {
return 0;
}
int64_t newVal = m_cloneMem[v->second];
if (!m_lastMem.ok()) {
return 0;
}
int64_t oldVal = m_lastMem[v->second];
return newVal - oldVal;
}
unordered_map<string, Datum> GameData::lookupAll() {
unordered_map<string, Datum> data;
for (auto var = m_vars.cbegin(); var != m_vars.cend(); ++var) {
try {
data.emplace(var->first, m_mem[var->second]);
} catch (...) {
}
}
for (auto var = m_customVars.cbegin(); var != m_customVars.cend(); ++var) {
data.emplace(var->first, var->second.get());
}
return data;
}
unordered_map<string, int64_t> GameData::lookupAll() const {
unordered_map<string, int64_t> data;
for (auto var = m_vars.cbegin(); var != m_vars.cend(); ++var) {
try {
data.emplace(var->first, m_mem[var->second]);
} catch (...) {
}
}
for (auto var = m_customVars.cbegin(); var != m_customVars.cend(); ++var) {
data.emplace(var->first, *var->second);
}
return data;
}
void GameData::setValue(const std::string& name, int64_t v) {
auto variant = m_customVars.find(name);
if (variant != m_customVars.end()) {
*variant->second = v;
return;
}
auto var = m_vars.find(name);
if (var != m_vars.end()) {
m_mem[var->second] = v;
return;
}
m_customVars.emplace(name, std::make_unique<Variant>(v));
}
void GameData::setValue(const std::string& name, const Variant& v) {
auto variant = m_customVars.find(name);
if (variant != m_customVars.end()) {
*variant->second = v;
return;
}
auto var = m_vars.find(name);
if (var != m_vars.end()) {
m_mem[var->second] = v;
return;
}
m_customVars.emplace(name, std::make_unique<Variant>(v));
}
Variable GameData::getVariable(const string& name) const {
const auto& v = m_vars.find(name);
if (v == m_vars.end()) {
throw invalid_argument(name);
}
return v->second;
}
void GameData::setVariable(const string& name, const Variable& var) {
removeVariable(name);
m_vars.emplace(name, var);
}
void GameData::removeVariable(const string& name) {
auto iter = m_vars.find(name);
if (iter != m_vars.end()) {
m_vars.erase(iter);
}
}
unordered_map<string, Variable> GameData::listVariables() const {
return m_vars;
}
size_t GameData::numVariables() const {
return m_vars.size();
}
/*void GameData::search(const std::string& name, int64_t value) {
if (m_searches.find(name) == m_searches.cend()) {
if (m_types.size()) {
m_searches.emplace(name, Search{ m_types });
} else {
m_searches.emplace(name, Search{});
}
}
Search* search = &m_searches[name];
search->search(m_mem, value);
m_searchOldMem[name].clone(m_mem);
}*/
/*void GameData::deltaSearch(const std::string& name, Operation op, int64_t reference) {
if (m_searches.find(name) == m_searches.cend()) {
if (m_types.size()) {
m_searches.emplace(name, Search{ m_types });
} else {
m_searches.emplace(name, Search{});
}
}
if (m_searchOldMem.find(name) == m_searchOldMem.cend()) {
m_searchOldMem[name].clone(m_mem);
}
Search* search = &m_searches[name];
search->delta(m_mem, m_searchOldMem[name], op, reference);
m_searchOldMem[name].clone(m_mem);
}*
size_t GameData::numSearches() const {
return m_searches.size();
}
vector<string> GameData::listSearches() const {
vector<string> names;
for (const auto& search : m_searches) {
names.emplace_back(search.first);
}
return names;
}
Search* GameData::getSearch(const string& name) {
auto iter = m_searches.find(name);
if (iter != m_searches.end()) {
return &iter->second;
}
return nullptr;
}
void GameData::removeSearch(const string& name) {
auto iter = m_searches.find(name);
if (iter != m_searches.end()) {
m_searches.erase(iter);
}
}*/
static const vector<pair<string, Operation>> s_ops{
make_pair("equal", Operation::EQUAL),
make_pair("negative-equal", Operation::NEGATIVE_EQUAL),
make_pair("not-equal", Operation::NOT_EQUAL),
make_pair("less-than", Operation::LESS_THAN),
make_pair("greater-than", Operation::GREATER_THAN),
make_pair("less-or-equal", Operation::LESS_OR_EQUAL),
make_pair("greater-or-equal", Operation::GREATER_OR_EQUAL),
make_pair("less-or-equal", Operation::LESS_OR_EQUAL),
make_pair("nonzero", Operation::NONZERO),
make_pair("zero", Operation::ZERO),
make_pair("negative", Operation::NEGATIVE),
make_pair("positive", Operation::POSITIVE),
make_pair("sign", Operation::SIGN)
};