11#include <jaffarCommon/bitwise.hpp>
12#include <jaffarCommon/deserializers/base.hpp>
13#include <jaffarCommon/hash.hpp>
14#include <jaffarCommon/json.hpp>
15#include <jaffarCommon/logger.hpp>
16#include <jaffarCommon/serializers/base.hpp>
20#include <unordered_set>
67 const auto& printProperties = jaffarCommon::json::popArray<std::string>(
_gameConfigRemaining,
"Print Properties");
71 const auto& hashProperties = jaffarCommon::json::popArray<std::string>(
_gameConfigRemaining,
"Hash Properties");
87 for (
const auto& entry :
_propertyMap) names += (names.empty() ?
"" :
", ") + entry.second->getName();
102 if (
_isInitialized ==
true) JAFFAR_THROW_LOGIC(
"This game instance was already initialized");
114 const auto propertyHash = jaffarCommon::hash::hashString(property);
118 JAFFAR_THROW_LOGIC(
"Property '%s' is not registered in this game. Registered properties: %s\n", property.c_str(),
getRegisteredPropertyNames().c_str());
128 const auto propertyHash = jaffarCommon::hash::hashString(property);
132 JAFFAR_THROW_LOGIC(
"Property '%s' is not registered in this game. Registered properties: %s\n", property.c_str(),
getRegisteredPropertyNames().c_str());
155 virtual ~Game() =
default;
276 for (
const auto& p :
_propertyHashVector) hashEngine.Update(p->getPointer(), p->getSize());
291 const size_t separatorSize = 4;
292 size_t maximumNameSize = 0;
293 for (
const auto& p :
_propertyPrintVector) maximumNameSize = std::max(maximumNameSize, p->getName().size());
296 jaffarCommon::logger::log(
"[J+] + Game State Type: ");
300 jaffarCommon::logger::log(
"\n");
303 jaffarCommon::logger::log(
"[J+] + Game State Reward: %f\n",
_reward);
306 jaffarCommon::logger::log(
"[J+] + Rule Status: ");
307 for (
size_t i = 0; i <
_rules.size(); i++) jaffarCommon::logger::log(
"%d", jaffarCommon::bitwise::getBitValue(
_rulesStatus.data(), i) ? 1 : 0);
308 jaffarCommon::logger::log(
"\n");
311 jaffarCommon::logger::log(
"[J+] + Game Properties: \n");
315 const auto& name = p->getName();
318 jaffarCommon::logger::log(
"[J+] + '%s':", name.c_str());
321 const auto propertySeparatorSize = separatorSize + maximumNameSize - name.size();
324 for (
size_t i = 0; i < propertySeparatorSize; i++) jaffarCommon::logger::log(
" ");
327 if (p->getDatatype() ==
Property::datatype_t::dt_int8) jaffarCommon::logger::log(
"0x%02X (%03d)\n", p->getValue<int8_t>(), p->getValue<int8_t>());
328 if (p->getDatatype() ==
Property::datatype_t::dt_int16) jaffarCommon::logger::log(
"0x%04X (%05d)\n", p->getValue<int16_t>(), p->getValue<int16_t>());
329 if (p->getDatatype() ==
Property::datatype_t::dt_int32) jaffarCommon::logger::log(
"0x%08X (%10d)\n", p->getValue<int32_t>(), p->getValue<int32_t>());
330 if (p->getDatatype() ==
Property::datatype_t::dt_int64) jaffarCommon::logger::log(
"0x%16lX (%ld)\n", p->getValue<int64_t>(), p->getValue<int64_t>());
331 if (p->getDatatype() ==
Property::datatype_t::dt_uint8) jaffarCommon::logger::log(
"0x%02X (%03u)\n", p->getValue<uint8_t>(), p->getValue<uint8_t>());
332 if (p->getDatatype() ==
Property::datatype_t::dt_uint16) jaffarCommon::logger::log(
"0x%04X (%05u)\n", p->getValue<uint16_t>(), p->getValue<uint16_t>());
333 if (p->getDatatype() ==
Property::datatype_t::dt_uint32) jaffarCommon::logger::log(
"0x%08X (%10u)\n", p->getValue<uint32_t>(), p->getValue<uint32_t>());
334 if (p->getDatatype() ==
Property::datatype_t::dt_uint64) jaffarCommon::logger::log(
"0x%16lX (%lu)\n", p->getValue<uint64_t>(), p->getValue<uint64_t>());
360 const auto ruleIdx = rule->getIndex();
363 if (jaffarCommon::bitwise::getBitValue(
_rulesStatus.data(), ruleIdx) ==
false)
366 bool isSatisfied = rule->evaluate();
389 const auto ruleIdx = rule->getIndex();
392 if (jaffarCommon::bitwise::getBitValue(
_rulesStatus.data(), ruleIdx) ==
true)
393 for (
const auto& action : rule->getActions()) action();
417 const auto ruleIdx = rule->getIndex();
420 if (jaffarCommon::bitwise::getBitValue(
_rulesStatus.data(), ruleIdx) ==
true)
425 if (rule->isCheckpointRule())
456 const auto ruleIdx = rule->getIndex();
459 if (jaffarCommon::bitwise::getBitValue(
_rulesStatus.data(), ruleIdx) ==
true)
462 const auto ruleReward = rule->getReward();
488 const auto& opName = jaffarCommon::json::getString(conditionJs,
"Op");
494 const auto& property1Name = jaffarCommon::json::getString(conditionJs,
"Property");
497 const auto property1NameHash = jaffarCommon::hash::hashString(property1Name);
500 if (
_propertyMap.contains(property1NameHash) ==
false) JAFFAR_THROW_LOGIC(
"[ERROR] Property '%s' has not been declared.\n", property1Name.c_str());
503 const auto property1 =
_propertyMap[property1NameHash].get();
506 auto datatype1 = property1->getDatatype();
509 if (conditionJs.contains(
"Value") ==
false) JAFFAR_THROW_LOGIC(
"[ERROR] Rule condition missing 'Value' key.\n");
510 if (conditionJs[
"Value"].is_number() ==
false && conditionJs[
"Value"].is_string() ==
false && conditionJs[
"Value"].is_boolean() ==
false)
511 JAFFAR_THROW_LOGIC(
"[ERROR] Wrong format for 'Value' entry in rule condition. It must be a string or number");
514 if (conditionJs[
"Value"].is_number())
516 if (datatype1 ==
Property::datatype_t::dt_uint8)
return std::make_unique<_vCondition<uint8_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<uint8_t>());
517 if (datatype1 ==
Property::datatype_t::dt_uint16)
return std::make_unique<_vCondition<uint16_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<uint16_t>());
518 if (datatype1 ==
Property::datatype_t::dt_uint32)
return std::make_unique<_vCondition<uint32_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<uint32_t>());
519 if (datatype1 ==
Property::datatype_t::dt_uint64)
return std::make_unique<_vCondition<uint64_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<uint64_t>());
521 if (datatype1 ==
Property::datatype_t::dt_int8)
return std::make_unique<_vCondition<int8_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<int8_t>());
522 if (datatype1 ==
Property::datatype_t::dt_int16)
return std::make_unique<_vCondition<int16_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<int16_t>());
523 if (datatype1 ==
Property::datatype_t::dt_int32)
return std::make_unique<_vCondition<int32_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<int32_t>());
524 if (datatype1 ==
Property::datatype_t::dt_int64)
return std::make_unique<_vCondition<int64_t>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<int64_t>());
526 if (datatype1 ==
Property::datatype_t::dt_float32)
return std::make_unique<_vCondition<float>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<
float>());
527 if (datatype1 ==
Property::datatype_t::dt_float64)
return std::make_unique<_vCondition<double>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<
double>());
531 if (conditionJs[
"Value"].is_boolean())
533 if (datatype1 ==
Property::datatype_t::dt_bool)
return std::make_unique<_vCondition<bool>>(opType, property1,
nullptr, 0, conditionJs[
"Value"].get<
bool>());
537 if (conditionJs[
"Value"].is_string())
540 const auto& property2Name = jaffarCommon::json::getString(conditionJs,
"Value");
543 const auto property2NameHash = jaffarCommon::hash::hashString(property2Name);
546 if (
_propertyMap.contains(property2NameHash) ==
false) JAFFAR_THROW_LOGIC(
"[ERROR] Property '%s' has not been declared.\n", property2Name.c_str());
549 const auto property2 =
_propertyMap[property2NameHash].get();
566 JAFFAR_THROW_LOGIC(
"[ERROR] Rule contains an invalid 'Value' key.\n", conditionJs[
"Value"].dump().c_str());
578 static std::unique_ptr<Game>
getGame(
const nlohmann::json& emulatorConfig,
const nlohmann::json& gameConfig);
638 virtual jaffarCommon::hash::hash_t
getStateInputHash() {
return jaffarCommon::hash::hash_t(); };
668 virtual jaffarCommon::hash::hash_t
getDirectStateHash()
const {
return jaffarCommon::hash::hash_t(); }
694 auto property = std::make_unique<Property>(name, pointer, dataType, endianness);
697 const auto propertyNameHash =
property->getNameHash();
723 for (
size_t idx = 0; idx < rulesJson.size(); idx++)
726 const auto& ruleJs = rulesJson[idx];
729 if (ruleJs.is_object() ==
false) JAFFAR_THROW_LOGIC(
"Passed rule is not a JSON object. Dump: \n %s", ruleJs.dump(2).c_str());
732 auto label = jaffarCommon::json::getNumber<Rule::label_t>(ruleJs,
"Label");
735 auto rule = std::make_unique<Rule>(idx, label);
741 _rules.push_back(std::move(rule));
745 for (
const auto& rule :
_rules)
746 for (
const auto& label : rule->getSatisfyRuleLabels())
748 bool subRuleFound =
false;
749 for (
const auto& subRule :
_rules)
750 if (subRule->getLabel() == label)
752 rule->addSatisfyRule(subRule.get());
755 if (subRuleFound ==
false) JAFFAR_THROW_LOGIC(
"Rule label %u referenced by rule %u in the 'Satisfies' array does not exist.\n", label, rule->getIndex());
762 for (
size_t i = 0; i <
_rules.size(); i++) jaffarCommon::bitwise::setBitValue(
_rulesStatus.data(), i,
false);
778 const auto& conditions = jaffarCommon::json::getArray<nlohmann::json>(ruleJs,
"Conditions");
781 const auto& actions = jaffarCommon::json::getArray<nlohmann::json>(ruleJs,
"Actions");
784 const auto& satisfiesVectorJs = jaffarCommon::json::getArray<nlohmann::json>(ruleJs,
"Satisfies");
793 for (
const auto& s : satisfiesVectorJs)
796 if (s.is_number() ==
false) JAFFAR_THROW_LOGIC(
"Wrong format provided in 'Satisfies' array in rule '%s'\n", ruleJs.dump(2).c_str());
817 std::string actionType = jaffarCommon::json::getString(actionJs,
"Type");
820 bool recognizedActionType =
false;
822 if (actionType ==
"Add Reward")
824 rule.
setReward(jaffarCommon::json::getNumber<float>(actionJs,
"Value"));
825 recognizedActionType =
true;
829 if (actionType ==
"Trigger Fail")
832 recognizedActionType =
true;
836 if (actionType ==
"Trigger Win")
839 recognizedActionType =
true;
843 if (actionType ==
"Trigger Checkpoint")
847 recognizedActionType =
true;
851 if (actionType ==
"Trigger Save Solution")
855 recognizedActionType =
true;
859 if (recognizedActionType ==
false) recognizedActionType =
parseRuleActionImpl(rule, actionType, actionJs);
862 if (recognizedActionType ==
false)
863 JAFFAR_THROW_LOGIC(
"[ERROR] Unrecognized action '%s' in rule %lu. Valid actions are: Add Reward, Trigger Fail, Trigger Win, Trigger Checkpoint, Trigger Save Solution (plus "
864 "any game-specific actions)\n",
865 actionType.c_str(), rule.
getLabel());
881 auto subRuleIdx = subRule->getIndex();
888 const auto ruleIdx = rule.
getIndex();
891 jaffarCommon::bitwise::setBitValue(
_rulesStatus.data(), ruleIdx,
true);
976 std::vector<std::unique_ptr<Rule>>
_rules;
988 std::map<jaffarCommon::hash::hash_t, std::unique_ptr<Property>>
_propertyMap;
static operator_t getOperatorType(const std::string &operation)
Maps a configuration operator string to its operator_t value.
Abstract base for an emulation core.
Abstract base class for a JaffarPlus game.
float _frameRate
Frame rate to play the game with, required for correct playback.
size_t _checkpointLevel
Current state's checkpoint level.
virtual bool playerParseCommand(const int command)
Handles a game-specific player command.
virtual void ruleUpdatePostHook()
Optional hook run after rule evaluation/restoration. Base does nothing.
const std::unique_ptr< Emulator > _emulator
Underlying emulator instance.
std::map< jaffarCommon::hash::hash_t, std::unique_ptr< Property > > _propertyMap
All registered properties, indexed by name hash.
virtual float calculateGameSpecificReward() const =0
Computes the game-specific contribution to the state reward.
size_t _checkpointTolerance
Tolerance recorded for checkpoint states.
std::vector< const Property * > _propertyHashVector
Properties used to hash/distinguish states, ordered.
void parseRule(Rule &rule, const nlohmann::json &ruleJs)
Parses a single rule's conditions, actions and "Satisfies" labels into a Rule.
std::vector< std::string > _printablePropertyNames
Parsed property names configured to be printed.
size_t getCheckpointTolerance() const
Returns the current state's checkpoint tolerance.
bool isSaveSolution() const
Indicates whether the current state should trigger a save solution.
virtual void initializeImpl()
Game-specific initialization hook, called during initialize.
void advanceState(const InputSet::inputIndex_t input)
Advances the game state by applying a single input.
static std::unique_ptr< Game > getGame(const nlohmann::json &emulatorConfig, const nlohmann::json &gameConfig)
Factory that constructs the concrete game matching the given configuration.
Game(std::unique_ptr< Emulator > emulator, const nlohmann::json &config)
Constructs a game from an already created emulator and a configuration object.
virtual void ruleUpdatePreHook()
Optional hook run before rule evaluation/restoration. Base does nothing.
Emulator * getEmulator() const
Returns a pointer to the internal emulator.
std::vector< uint8_t > _rulesStatus
Bit vector indicating whether each rule has been satisfied.
void parseRules(const nlohmann::json &rulesJson)
Parses the full rule array, builds the rule objects and resolves cross-references.
stateType_t _stateType
Current game state type. Initialized to normal because it is read (printInfo) and serialized for the ...
bool _bypassEmulatorState
When true, the game handles state save/load entirely, bypassing the emulator.
virtual void stateUpdatePreHook()
Optional hook run before a state update (advance/deserialize). Base does nothing.
void satisfyRule(Rule &rule)
Marks a rule as satisfied, recursively satisfying the rules it satisfies first.
ssize_t _saveSolutionCurrentLastRuleId
Current last rule index that activated a save solution; save state activates only when a rule id is b...
void serializeState(jaffarCommon::serializer::Base &serializer) const
Serializes the full game state.
ssize_t getSaveSolutionCurrentLastRuleIdx() const
Returns the current last rule index that set a save solution.
std::string getRegisteredPropertyNames() const
Returns a comma-separated list of the property names registered for this game.
virtual float getFloorReward() const
Reward used for the Reference Reward Floor comparison: the un-biased progress reward,...
std::string _gameName
Game name (for runtime use).
virtual void playerPrintCommands() const
Prints the game's player-specific commands, if any.
virtual void advanceStateImpl(const InputSet::inputIndex_t input)=0
Advances the game state by applying the given input.
void updateReward()
Recomputes the current state's reward from the satisfied rules.
size_t getCheckpointLevel() const
Returns the current state's checkpoint level.
void evaluateRules()
Evaluates the rule set against the current state.
virtual void stateUpdatePostHook()
Optional hook run after a state update (advance/deserialize). Base does nothing.
void finalizeGameConfig()
Asserts that every key in the game configuration has been recognized.
virtual void registerGameProperties()=0
Registers the game's properties (via registerGameProperty).
void * registerGameProperty(const std::string &name, void *const pointer, const Property::datatype_t dataType, const Property::endianness_t endianness)
Registers a game property so it can be referenced by name in rules and printing/hashing.
virtual std::set< std::string > getAllPossibleInputs()
Reports all possible inputs the game might require.
void deserializeState(jaffarCommon::deserializer::Base &deserializer)
Restores the full game state previously written by serializeState.
bool _isInitialized
Whether the game has been initialized.
std::unique_ptr< Condition > parseCondition(const nlohmann::json &conditionJs)
Parses a single rule condition from JSON into a typed Condition.
float getFrameRate() const
Returns the configured frame rate.
void printInfo() const
Prints the current game state to the logger.
std::vector< std::string > _hashablePropertyNames
Parsed property names configured to be hashed.
virtual bool parseRuleActionImpl(Rule &rule, const std::string &actionType, const nlohmann::json &actionJs)=0
Parses and applies a game-specific rule action.
virtual void getAdditionalAllowedInputs(std::vector< InputSet::inputIndex_t > &allowedInputSet)
Lets a game contribute additional allowed inputs based on game-specific decisions.
virtual jaffarCommon::hash::hash_t getStateInputHash()
Returns a hash identifying the current state for new-input discovery.
float getReward() const
Returns the current state's reward.
void updateGameStateType()
Recomputes the state type and checkpoint level from the satisfied rules.
virtual void computeAdditionalHashing(MetroHash128 &hashEngine) const =0
Adds game-specific data into the hash engine.
virtual jaffarCommon::hash::hash_t getDirectStateHash() const
Returns the state hash directly, without going through a hashing engine.
stateType_t getStateType() const
Returns the current state type (normal, win or fail).
void computeHash(MetroHash128 &hashEngine) const
Updates a hash engine with the current state's distinguishing data.
void initialize()
Initializes the game: emulator, properties, rules and the first state update.
float _reward
Current game state reward.
nlohmann::json _gameConfigRemaining
Mutable working copy of the game config; recognized keys are popped, leftovers are unrecognized....
virtual void deserializeStateImpl(jaffarCommon::deserializer::Base &deserializer)=0
Restores game-specific state previously written by serializeStateImpl.
stateType_t
Classification of the current game state, derived from the satisfied rules.
@ normal
No win or fail rule is currently satisfied.
@ fail
A fail rule is currently satisfied.
@ win
A win rule is currently satisfied.
nlohmann::json _rulesJs
Temporary storage of the rules JSON for delayed parsing.
std::vector< const Property * > _propertyPrintVector
Properties printed for game information, ordered.
std::string getName() const
Returns the game name used at runtime.
const std::string getSaveSolutionPath() const
Returns the save path of the rule that activated the current save solution.
virtual std::string getTraceLine() const
One line of a per-step trace for player --dumpTrace (space-separated coordinates the game wants to re...
void parseRuleAction(Rule &rule, const nlohmann::json &actionJs)
Parses a single rule action from JSON and applies it to the rule.
virtual void printInfoImpl() const =0
Prints game-specific state information to the logger.
virtual void serializeStateImpl(jaffarCommon::serializer::Base &serializer) const =0
Serializes game-specific state.
void runGameSpecificRuleActions()
Runs the registered actions of every currently satisfied rule.
bool isInitialized() const
Returns whether the game has been initialized.
ssize_t _saveSolutionCurrentLastRuleIdx
Previous last rule index that activated a save solution (preserved to mark the state where it changes...
std::vector< std::unique_ptr< Rule > > _rules
Game script rules, kept in a vector to preserve ordering.
Game()=delete
Default construction is disabled; a game requires an emulator and config.
ssize_t getSaveSolutionPrevLastRuleIdx() const
Returns the previous last rule index that set a save solution.
endianness_t
The byte order of the value stored at the property's memory address.
datatype_t
The interpretation of the bytes at the property's memory address.
@ dt_int32
Signed 32-bit integer (config datatype "INT32").
@ dt_int8
Signed 8-bit integer (config datatype "INT8").
@ dt_float32
Single precision float, 32-bit (config datatype "FLOAT32").
@ dt_uint64
Unsigned 64-bit integer (config datatype "UINT64").
@ dt_uint16
Unsigned 16-bit integer (config datatype "UINT16").
@ dt_uint8
Unsigned 8-bit integer (config datatype "UINT8").
@ dt_int16
Signed 16-bit integer (config datatype "INT16").
@ dt_uint32
Unsigned 32-bit integer (config datatype "UINT32").
@ dt_bool
Boolean stored in a single byte (config datatype "BOOL").
@ dt_float64
Double precision float, 64-bit (config datatype "FLOAT64").
@ dt_int64
Signed 64-bit integer (config datatype "INT64").
A labelled collection of conditions with associated reward, actions and outcome flags.
label_t getLabel() const
Returns the rule's identifying label.
void setWinRule(const bool isWinRule)
Sets whether this rule is a win rule.
void setFailRule(const bool isFailRule)
Sets whether this rule is a fail rule.
size_t getIndex() const
Returns the rule's internal sequential index.
void addSatisfyRuleLabel(const label_t satisfyRuleLabel)
Adds the label of another rule considered satisfied alongside this one.
const std::vector< Rule * > & getSatisfyRules() const
Returns pointers to the rules satisfied alongside this one.
void setCheckpointTolerance(const size_t checkPointTolerance)
Sets the checkpoint tolerance for this rule.
void setReward(const float reward)
Sets the reward granted when this rule is satisfied.
size_t label_t
Type used to identify a rule by label.
void setSaveSolutionRule(const bool isSaveSolutionRule)
Sets whether this rule triggers saving the solution.
void addCondition(std::unique_ptr< Condition > condition)
Adds a condition that must hold for this rule to be satisfied.
void setCheckpointRule(const bool isCheckpointRule)
Sets whether this rule is a checkpoint rule.
void setSaveSolutionPath(const std::string &saveSolutionPath)
Sets the path where the solution is saved.
Abstract emulator interface that concrete emulation cores implement, exposing state load/save,...
A rule evaluated by the engine: a labelled set of conditions that, when all satisfied,...