JaffarPlus
High-performance best-first search optimizer for tool-assisted speedruns
Loading...
Searching...
No Matches
game.hpp
Go to the documentation of this file.
1#pragma once
2
9#include "emulator.hpp"
10#include "rule.hpp"
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>
17#include <map>
18#include <memory>
19#include <set>
20#include <unordered_set>
21#include <utility>
22#include <vector>
23
24namespace jaffarPlus
25{
26
38class Game
39{
40public:
43 {
44 normal = 0,
45 win = 1,
46 fail = 2
47 };
48
55 Game(std::unique_ptr<Emulator> emulator, const nlohmann::json& config) : _emulator(std::move(emulator)), _gameConfigRemaining(config)
56 {
57 // Getting emulator name (for runtime use)
58 _gameName = jaffarCommon::json::popString(_gameConfigRemaining, "Game Name");
59
60 // Parsing frame rate
61 _frameRate = jaffarCommon::json::popNumber<float>(_gameConfigRemaining, "Frame Rate");
62
63 // Parsing whether to bypass emulator state load/saving
64 _bypassEmulatorState = jaffarCommon::json::popBoolean(_gameConfigRemaining, "Bypass Emulator State");
65
66 // Marking printable properties
67 const auto& printProperties = jaffarCommon::json::popArray<std::string>(_gameConfigRemaining, "Print Properties");
68 for (const auto& property : printProperties) _printablePropertyNames.push_back(property);
69
70 // Parsing hashable game properties
71 const auto& hashProperties = jaffarCommon::json::popArray<std::string>(_gameConfigRemaining, "Hash Properties");
72 for (const auto& property : hashProperties) _hashablePropertyNames.push_back(property);
73
74 // Storing rules JSON for later parsing. Consumed as a whole here; the rule-array element keys
75 // (Conditions/Actions/Satisfies/...) are still parsed leniently by parseRules() below.
76 _rulesJs = jaffarCommon::json::popArray<nlohmann::json>(_gameConfigRemaining, "Rules");
77 };
78
84 std::string getRegisteredPropertyNames() const
85 {
86 std::string names;
87 for (const auto& entry : _propertyMap) names += (names.empty() ? "" : ", ") + entry.second->getName();
88 return names;
89 }
90
101 {
102 if (_isInitialized == true) JAFFAR_THROW_LOGIC("This game instance was already initialized");
103
104 // Initializing emulator, if not already initialized
105 if (_emulator->isInitialized() == false) _emulator->initialize();
106
107 // Getting game-specific properties
109
110 // Registering printable properties
111 for (const auto& property : _printablePropertyNames)
112 {
113 // Getting property name hash
114 const auto propertyHash = jaffarCommon::hash::hashString(property);
115
116 // Checking the property is registered
117 if (_propertyMap.contains(propertyHash) == false)
118 JAFFAR_THROW_LOGIC("Property '%s' is not registered in this game. Registered properties: %s\n", property.c_str(), getRegisteredPropertyNames().c_str());
119
120 // If so, add its pointer to the print property vector
121 _propertyPrintVector.push_back(_propertyMap.at(propertyHash).get());
122 }
123
124 // Registering hashable properties
125 for (const auto& property : _hashablePropertyNames)
126 {
127 // Getting property name hash
128 const auto propertyHash = jaffarCommon::hash::hashString(property);
129
130 // Checking the property is registered
131 if (_propertyMap.contains(propertyHash) == false)
132 JAFFAR_THROW_LOGIC("Property '%s' is not registered in this game. Registered properties: %s\n", property.c_str(), getRegisteredPropertyNames().c_str());
133
134 // If so, add its pointer to the print property vector
135 _propertyHashVector.push_back(_propertyMap.at(propertyHash).get());
136 }
137
138 // Now parsing rules
140
141 // Update internals pre initialization (first state update)
143
144 // Calling game-specific initializer
146
147 // Update internals post initialization
149
150 // Set this as initialized
151 _isInitialized = true;
152 }
153
154 Game() = delete;
155 virtual ~Game() = default;
156
164 __INLINE__ void advanceState(const InputSet::inputIndex_t input)
165 {
166 // Calling the pre-update hook
168
169 // Update save solution last rule id
171
172 // Performing the requested input
173 advanceStateImpl(input);
174
175 // Calling the post-update hook
177 }
178
187 __INLINE__ void serializeState(jaffarCommon::serializer::Base& serializer) const
188 {
189 // Serializing internal emulator state
190 if (_bypassEmulatorState == false) _emulator->serializeState(serializer);
191
192 // Storage for game-specific data
193 serializeStateImpl(serializer);
194
195 // Serializing reward
196 serializer.push(&_reward, sizeof(_reward));
197
198 // Serializing checkpoint level
199 serializer.push(&_checkpointLevel, sizeof(_checkpointLevel));
200
201 // Serializing the previous last rule id that activated a save solution
203
204 // Serializing the current last rule id that activated a save solution
206
207 // Serializing state type
208 serializer.push(&_stateType, sizeof(_stateType));
209
210 // Serializing rule states
211 serializer.push(_rulesStatus.data(), _rulesStatus.size());
212 }
213
224 __INLINE__ void deserializeState(jaffarCommon::deserializer::Base& deserializer)
225 {
226 // Calling the pre-update hook
228
229 // Storage for the internal emulator state
230 if (_bypassEmulatorState == false) _emulator->deserializeState(deserializer);
231
232 // Storage for game-specific data
233 deserializeStateImpl(deserializer);
234
235 // Calling the post-update hook
237
238 // Deserializing reward
239 deserializer.pop(&_reward, sizeof(_reward));
240
241 // Deserializing checkpoint level
242 deserializer.pop(&_checkpointLevel, sizeof(_checkpointLevel));
243
244 // Deserializing the previous last rule id that activated a save solution
246
247 // Deserializing the last rule id that activated a save solution
249
250 // Deserializing state type
251 deserializer.pop(&_stateType, sizeof(_stateType));
252
253 // Calling the pre-rule update hook
255
256 // Deserializing rules status
257 deserializer.pop(_rulesStatus.data(), _rulesStatus.size());
258
259 // Running game specific rule actions
261
262 // Calling the post-rule update hook
264 }
265
273 __INLINE__ void computeHash(MetroHash128& hashEngine) const
274 {
275 // Processing hashable game properties
276 for (const auto& p : _propertyHashVector) hashEngine.Update(p->getPointer(), p->getSize());
277
278 // Processing any additional game-specific hash
279 computeAdditionalHashing(hashEngine);
280 }
281
288 void printInfo() const
289 {
290 // Getting maximum printable property name, for formatting purposes
291 const size_t separatorSize = 4;
292 size_t maximumNameSize = 0;
293 for (const auto& p : _propertyPrintVector) maximumNameSize = std::max(maximumNameSize, p->getName().size());
294
295 // Printing game state
296 jaffarCommon::logger::log("[J+] + Game State Type: ");
297 if (_stateType == stateType_t::normal) jaffarCommon::logger::log("Normal");
298 if (_stateType == stateType_t::win) jaffarCommon::logger::log("Win");
299 if (_stateType == stateType_t::fail) jaffarCommon::logger::log("Fail");
300 jaffarCommon::logger::log("\n");
301
302 // Printing game state
303 jaffarCommon::logger::log("[J+] + Game State Reward: %f\n", _reward);
304
305 // Printing rule status
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");
309
310 // Printing game properties defined in the script file
311 jaffarCommon::logger::log("[J+] + Game Properties: \n");
312 for (const auto& p : _propertyPrintVector)
313 {
314 // Getting property name
315 const auto& name = p->getName();
316
317 // Printing property name first
318 jaffarCommon::logger::log("[J+] + '%s':", name.c_str());
319
320 // Calculating separation spaces for this property
321 const auto propertySeparatorSize = separatorSize + maximumNameSize - name.size();
322
323 // Printing separator spaces
324 for (size_t i = 0; i < propertySeparatorSize; i++) jaffarCommon::logger::log(" ");
325
326 // Then printing separator spaces
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>());
335 if (p->getDatatype() == Property::datatype_t::dt_float32) jaffarCommon::logger::log("%f (0x%X)\n", p->getValue<float>(), p->getValue<uint32_t>());
336 if (p->getDatatype() == Property::datatype_t::dt_float64) jaffarCommon::logger::log("%f (0x%lX)\n", p->getValue<double>(), p->getValue<uint64_t>());
337 if (p->getDatatype() == Property::datatype_t::dt_bool) jaffarCommon::logger::log("%1u\n", p->getValue<bool>());
338 }
339
340 // Printing game-specific stuff now
342 }
343
351 __INLINE__ void evaluateRules()
352 {
353 // Calling the pre-update hook
355
356 // Second, check which unsatisfied rules have been satisfied now
357 for (auto& rule : _rules)
358 {
359 // Getting rule index
360 const auto ruleIdx = rule->getIndex();
361
362 // Evaluate rule only if it's not yet satisfied
363 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == false)
364 {
365 // Checking if conditions are met
366 bool isSatisfied = rule->evaluate();
367
368 // If it's achieved, update its status and run its actions
369 if (isSatisfied) satisfyRule(*rule);
370 }
371 }
372
373 // Running game-specific rule actions
375
376 // Calling the pre-update hook
378 }
379
384 {
385 // First, checking if the rules have been satisfied
386 for (auto& rule : _rules)
387 {
388 // Getting rule index
389 const auto ruleIdx = rule->getIndex();
390
391 // Run ations only if rule is satisfied
392 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == true)
393 for (const auto& action : rule->getActions()) action();
394 }
395 }
396
405 __INLINE__ void updateGameStateType()
406 {
407 // Clearing game state type before we evaluate satisfied rules
409
410 // Clearing checkpoint level and tolerance
412
413 // Second, we run the specified actions for the satisfied rules in label order
414 for (auto& rule : _rules)
415 {
416 // Getting rule index
417 const auto ruleIdx = rule->getIndex();
418
419 // Run actions
420 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == true)
421 {
422 // Modify game state, depending on rule type
423
424 // Evaluate checkpoint rule and store tolerance if specified
425 if (rule->isCheckpointRule())
426 {
428 _checkpointTolerance = rule->getCheckpointTolerance();
429 }
430
431 // Evaluate save state rule and path if specified -- only if the current rule label is greater than the last rule to activate this
432 if (rule->isSaveSolutionRule() && (ssize_t)ruleIdx > _saveSolutionCurrentLastRuleIdx) _saveSolutionCurrentLastRuleId = ruleIdx;
433
434 // Winning in the same rule superseeds checkpoint, and failing superseed everything
435 if (rule->isWinRule()) _stateType = stateType_t::win;
436 if (rule->isFailRule()) _stateType = stateType_t::fail;
437 }
438 }
439 }
440
447 __INLINE__ void updateReward()
448 {
449 // First, we resetting reward to zero
450 _reward = 0.0;
451
452 // Second, we get the reward from every satisfied rule
453 for (auto& rule : _rules)
454 {
455 // Getting rule index
456 const auto ruleIdx = rule->getIndex();
457
458 // Run actions
459 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == true)
460 {
461 // Getting reward from satisfied rule
462 const auto ruleReward = rule->getReward();
463
464 // Adding it to the state reward
465 _reward += ruleReward;
466 }
467 }
468
469 // Adding any game-specific rewards
471 }
472
485 std::unique_ptr<Condition> parseCondition(const nlohmann::json& conditionJs)
486 {
487 // Parsing operator name
488 const auto& opName = jaffarCommon::json::getString(conditionJs, "Op");
489
490 // Getting operator type from its name
491 const auto opType = Condition::getOperatorType(opName);
492
493 // Parsing first operand (property name)
494 const auto& property1Name = jaffarCommon::json::getString(conditionJs, "Property");
495
496 // Getting property name hash, for indexing
497 const auto property1NameHash = jaffarCommon::hash::hashString(property1Name);
498
499 // Making sure the requested property exists in the property map
500 if (_propertyMap.contains(property1NameHash) == false) JAFFAR_THROW_LOGIC("[ERROR] Property '%s' has not been declared.\n", property1Name.c_str());
501
502 // Getting property object
503 const auto property1 = _propertyMap[property1NameHash].get();
504
505 // Getting property data type
506 auto datatype1 = property1->getDatatype();
507
508 // Parsing second operand (number)
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");
512
513 // If value is a number, take it as immediate
514 if (conditionJs["Value"].is_number())
515 {
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>());
520
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>());
525
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>());
528 }
529
530 // If value is a boolean, take it as immediate
531 if (conditionJs["Value"].is_boolean())
532 {
533 if (datatype1 == Property::datatype_t::dt_bool) return std::make_unique<_vCondition<bool>>(opType, property1, nullptr, 0, conditionJs["Value"].get<bool>());
534 }
535
536 // If value is a string, take value as property number 2
537 if (conditionJs["Value"].is_string())
538 {
539 // Parsing second operand (property name)
540 const auto& property2Name = jaffarCommon::json::getString(conditionJs, "Value");
541
542 // Getting property name hash, for indexing
543 const auto property2NameHash = jaffarCommon::hash::hashString(property2Name);
544
545 // Making sure the requested property exists in the property map
546 if (_propertyMap.contains(property2NameHash) == false) JAFFAR_THROW_LOGIC("[ERROR] Property '%s' has not been declared.\n", property2Name.c_str());
547
548 // Getting property object
549 const auto property2 = _propertyMap[property2NameHash].get();
550
551 if (datatype1 == Property::datatype_t::dt_uint8) return std::make_unique<_vCondition<uint8_t>>(opType, property1, property2, 0, 0);
552 if (datatype1 == Property::datatype_t::dt_uint16) return std::make_unique<_vCondition<uint16_t>>(opType, property1, property2, 0, 0);
553 if (datatype1 == Property::datatype_t::dt_uint32) return std::make_unique<_vCondition<uint32_t>>(opType, property1, property2, 0, 0);
554 if (datatype1 == Property::datatype_t::dt_uint64) return std::make_unique<_vCondition<uint64_t>>(opType, property1, property2, 0, 0);
555
556 if (datatype1 == Property::datatype_t::dt_int8) return std::make_unique<_vCondition<int8_t>>(opType, property1, property2, 0, 0);
557 if (datatype1 == Property::datatype_t::dt_int16) return std::make_unique<_vCondition<int16_t>>(opType, property1, property2, 0, 0);
558 if (datatype1 == Property::datatype_t::dt_int32) return std::make_unique<_vCondition<int32_t>>(opType, property1, property2, 0, 0);
559 if (datatype1 == Property::datatype_t::dt_int64) return std::make_unique<_vCondition<int64_t>>(opType, property1, property2, 0, 0);
560
561 if (datatype1 == Property::datatype_t::dt_float32) return std::make_unique<_vCondition<float>>(opType, property1, property2, 0, 0);
562 if (datatype1 == Property::datatype_t::dt_float64) return std::make_unique<_vCondition<double>>(opType, property1, property2, 0, 0);
563 if (datatype1 == Property::datatype_t::dt_bool) return std::make_unique<_vCondition<bool>>(opType, property1, property2, 0, 0);
564 }
565
566 JAFFAR_THROW_LOGIC("[ERROR] Rule contains an invalid 'Value' key.\n", conditionJs["Value"].dump().c_str());
567 }
568
570 __INLINE__ Emulator* getEmulator() const { return _emulator.get(); }
571
578 static std::unique_ptr<Game> getGame(const nlohmann::json& emulatorConfig, const nlohmann::json& gameConfig);
579
581 __INLINE__ float getFrameRate() const { return _frameRate; }
582
584 __INLINE__ float getReward() const { return _reward; }
585
590 virtual __INLINE__ std::string getTraceLine() const { return ""; }
591
598 virtual __INLINE__ float getFloorReward() const { return _reward; }
599
601 __INLINE__ stateType_t getStateType() const { return _stateType; }
602
604 __INLINE__ size_t getCheckpointLevel() const { return _checkpointLevel; }
605
607 __INLINE__ size_t getCheckpointTolerance() const { return _checkpointTolerance; }
608
614
617
620
625 __INLINE__ const std::string getSaveSolutionPath() const { return isSaveSolution() ? _rules[_saveSolutionCurrentLastRuleId]->getSaveSolutionPath() : ""; }
626
628 __INLINE__ std::string getName() const { return _gameName; }
629
631 __INLINE__ bool isInitialized() const { return _isInitialized; }
632
638 virtual jaffarCommon::hash::hash_t getStateInputHash() { return jaffarCommon::hash::hash_t(); };
639
645 virtual __INLINE__ void getAdditionalAllowedInputs(std::vector<InputSet::inputIndex_t>& allowedInputSet) {}
646
652 virtual __INLINE__ std::set<std::string> getAllPossibleInputs() { return {}; }
653
655 virtual void playerPrintCommands() const {}
661 virtual bool playerParseCommand(const int command) { return false; }
662
668 virtual jaffarCommon::hash::hash_t getDirectStateHash() const { return jaffarCommon::hash::hash_t(); }
669
670protected:
681 void finalizeGameConfig() { jaffarCommon::json::checkEmpty(_gameConfigRemaining, "Game Configuration"); }
682
691 void* registerGameProperty(const std::string& name, void* const pointer, const Property::datatype_t dataType, const Property::endianness_t endianness)
692 {
693 // Creating property
694 auto property = std::make_unique<Property>(name, pointer, dataType, endianness);
695
696 // Getting property name hash as key
697 const auto propertyNameHash = property->getNameHash();
698
699 // Adding property to the map for later reference
700 _propertyMap[propertyNameHash] = std::move(property);
701
702 // Return the pointer proper (this is just sugar to make the use of this function more compact)
703 return pointer;
704 }
705
716 void parseRules(const nlohmann::json& rulesJson)
717 {
718 // Reset the rules container
719 _rules.clear();
720 _rulesStatus.clear();
721
722 // Evaluate each rule
723 for (size_t idx = 0; idx < rulesJson.size(); idx++)
724 {
725 // Getting specific rule json object
726 const auto& ruleJs = rulesJson[idx];
727
728 // Check if rule is a key/value object
729 if (ruleJs.is_object() == false) JAFFAR_THROW_LOGIC("Passed rule is not a JSON object. Dump: \n %s", ruleJs.dump(2).c_str());
730
731 // Getting rule label
732 auto label = jaffarCommon::json::getNumber<Rule::label_t>(ruleJs, "Label");
733
734 // Creating new rule with the given label
735 auto rule = std::make_unique<Rule>(idx, label);
736
737 // Parsing json into a rule class
738 parseRule(*rule, ruleJs);
739
740 // Adding new rule to the collection
741 _rules.push_back(std::move(rule));
742 }
743
744 // Checking all cross references are correct
745 for (const auto& rule : _rules)
746 for (const auto& label : rule->getSatisfyRuleLabels())
747 {
748 bool subRuleFound = false;
749 for (const auto& subRule : _rules)
750 if (subRule->getLabel() == label)
751 {
752 rule->addSatisfyRule(subRule.get());
753 subRuleFound = true;
754 }
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());
756 }
757
758 // Create rule status vector
759 _rulesStatus.resize(jaffarCommon::bitwise::getByteStorageForBitCount(_rules.size()));
760
761 // Clearing the status vector evaluation
762 for (size_t i = 0; i < _rules.size(); i++) jaffarCommon::bitwise::setBitValue(_rulesStatus.data(), i, false);
763 }
764
775 void parseRule(Rule& rule, const nlohmann::json& ruleJs)
776 {
777 // Getting rule condition array
778 const auto& conditions = jaffarCommon::json::getArray<nlohmann::json>(ruleJs, "Conditions");
779
780 // Getting rule action array
781 const auto& actions = jaffarCommon::json::getArray<nlohmann::json>(ruleJs, "Actions");
782
783 // Parsing satisfies vector
784 const auto& satisfiesVectorJs = jaffarCommon::json::getArray<nlohmann::json>(ruleJs, "Satisfies");
785
786 // Parsing rule conditions
787 for (const auto& condition : conditions) rule.addCondition(parseCondition(condition));
788
789 // Parsing rule actions
790 for (const auto& action : actions) parseRuleAction(rule, action);
791
792 // Parsing satisfies vector
793 for (const auto& s : satisfiesVectorJs)
794 {
795 // Check for correct format
796 if (s.is_number() == false) JAFFAR_THROW_LOGIC("Wrong format provided in 'Satisfies' array in rule '%s'\n", ruleJs.dump(2).c_str());
797
798 // Adding the satisfies label
799 rule.addSatisfyRuleLabel(s.get<Rule::label_t>());
800 }
801 }
802
814 void parseRuleAction(Rule& rule, const nlohmann::json& actionJs)
815 {
816 // Getting action type
817 std::string actionType = jaffarCommon::json::getString(actionJs, "Type");
818
819 // Running the action, depending on the type
820 bool recognizedActionType = false;
821
822 if (actionType == "Add Reward")
823 {
824 rule.setReward(jaffarCommon::json::getNumber<float>(actionJs, "Value"));
825 recognizedActionType = true;
826 }
827
828 // Storing fail state
829 if (actionType == "Trigger Fail")
830 {
831 rule.setFailRule(true);
832 recognizedActionType = true;
833 }
834
835 // Storing win state
836 if (actionType == "Trigger Win")
837 {
838 rule.setWinRule(true);
839 recognizedActionType = true;
840 }
841
842 // Storing checkpoint flags
843 if (actionType == "Trigger Checkpoint")
844 {
845 rule.setCheckpointRule(true);
846 rule.setCheckpointTolerance(jaffarCommon::json::getNumber<size_t>(actionJs, "Tolerance"));
847 recognizedActionType = true;
848 }
849
850 // Storing save state flags
851 if (actionType == "Trigger Save Solution")
852 {
853 rule.setSaveSolutionRule(true);
854 rule.setSaveSolutionPath(jaffarCommon::json::getString(actionJs, "Path"));
855 recognizedActionType = true;
856 }
857
858 // If not recognized yet, it must be a game specific action
859 if (recognizedActionType == false) recognizedActionType = parseRuleActionImpl(rule, actionType, actionJs);
860
861 // If not recognized at all, then fail
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());
866 }
867
875 __INLINE__ void satisfyRule(Rule& rule)
876 {
877 // Recursively run actions for the yet unsatisfied rules that are satisfied by this one and mark them as satisfied
878 for (const auto subRule : rule.getSatisfyRules())
879 {
880 // Getting index from the subrule
881 auto subRuleIdx = subRule->getIndex();
882
883 // Only activate it if it hasn't been activated before
884 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), subRuleIdx) == false) satisfyRule(*subRule);
885 }
886
887 // Getting rule index
888 const auto ruleIdx = rule.getIndex();
889
890 // Setting status to satisfied
891 jaffarCommon::bitwise::setBitValue(_rulesStatus.data(), ruleIdx, true);
892 }
893
898 virtual void initializeImpl() {};
899
903 virtual void registerGameProperties() = 0;
904
909 virtual void serializeStateImpl(jaffarCommon::serializer::Base& serializer) const = 0;
910
915 virtual void deserializeStateImpl(jaffarCommon::deserializer::Base& deserializer) = 0;
916
921 virtual float calculateGameSpecificReward() const = 0;
922
927 virtual void computeAdditionalHashing(MetroHash128& hashEngine) const = 0;
928
932 virtual void printInfoImpl() const = 0;
933
938 virtual void advanceStateImpl(const InputSet::inputIndex_t input) = 0;
939
947 virtual bool parseRuleActionImpl(Rule& rule, const std::string& actionType, const nlohmann::json& actionJs) = 0;
948
950 virtual __INLINE__ void stateUpdatePreHook() {};
952 virtual __INLINE__ void stateUpdatePostHook() {};
954 virtual __INLINE__ void ruleUpdatePreHook() {};
956 virtual __INLINE__ void ruleUpdatePostHook() {};
957
960
961 float _reward = 0.0;
962
963 size_t _checkpointLevel = 0;
964
966
970
973
974 const std::unique_ptr<Emulator> _emulator;
975
976 std::vector<std::unique_ptr<Rule>> _rules;
977
978 std::vector<uint8_t> _rulesStatus;
979
980 std::vector<std::string> _printablePropertyNames;
981
982 std::vector<std::string> _hashablePropertyNames;
983
984 std::vector<const Property*> _propertyHashVector;
985
986 std::vector<const Property*> _propertyPrintVector;
987
988 std::map<jaffarCommon::hash::hash_t, std::unique_ptr<Property>> _propertyMap;
989
991
993
994 std::string _gameName;
995
996 nlohmann::json _rulesJs;
997
999 nlohmann::json _gameConfigRemaining;
1000
1001 bool _isInitialized = false;
1002};
1003
1004} // namespace jaffarPlus
static operator_t getOperatorType(const std::string &operation)
Maps a configuration operator string to its operator_t value.
Definition condition.hpp:59
Abstract base for an emulation core.
Definition emulator.hpp:40
Abstract base class for a JaffarPlus game.
Definition game.hpp:39
float _frameRate
Frame rate to play the game with, required for correct playback.
Definition game.hpp:990
size_t _checkpointLevel
Current state's checkpoint level.
Definition game.hpp:963
virtual bool playerParseCommand(const int command)
Handles a game-specific player command.
Definition game.hpp:661
virtual void ruleUpdatePostHook()
Optional hook run after rule evaluation/restoration. Base does nothing.
Definition game.hpp:956
const std::unique_ptr< Emulator > _emulator
Underlying emulator instance.
Definition game.hpp:974
std::map< jaffarCommon::hash::hash_t, std::unique_ptr< Property > > _propertyMap
All registered properties, indexed by name hash.
Definition game.hpp:988
virtual float calculateGameSpecificReward() const =0
Computes the game-specific contribution to the state reward.
size_t _checkpointTolerance
Tolerance recorded for checkpoint states.
Definition game.hpp:965
std::vector< const Property * > _propertyHashVector
Properties used to hash/distinguish states, ordered.
Definition game.hpp:984
void parseRule(Rule &rule, const nlohmann::json &ruleJs)
Parses a single rule's conditions, actions and "Satisfies" labels into a Rule.
Definition game.hpp:775
std::vector< std::string > _printablePropertyNames
Parsed property names configured to be printed.
Definition game.hpp:980
size_t getCheckpointTolerance() const
Returns the current state's checkpoint tolerance.
Definition game.hpp:607
bool isSaveSolution() const
Indicates whether the current state should trigger a save solution.
Definition game.hpp:613
virtual void initializeImpl()
Game-specific initialization hook, called during initialize.
Definition game.hpp:898
void advanceState(const InputSet::inputIndex_t input)
Advances the game state by applying a single input.
Definition game.hpp:164
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.
Definition game.hpp:55
virtual void ruleUpdatePreHook()
Optional hook run before rule evaluation/restoration. Base does nothing.
Definition game.hpp:954
Emulator * getEmulator() const
Returns a pointer to the internal emulator.
Definition game.hpp:570
std::vector< uint8_t > _rulesStatus
Bit vector indicating whether each rule has been satisfied.
Definition game.hpp:978
void parseRules(const nlohmann::json &rulesJson)
Parses the full rule array, builds the rule objects and resolves cross-references.
Definition game.hpp:716
stateType_t _stateType
Current game state type. Initialized to normal because it is read (printInfo) and serialized for the ...
Definition game.hpp:959
bool _bypassEmulatorState
When true, the game handles state save/load entirely, bypassing the emulator.
Definition game.hpp:992
virtual void stateUpdatePreHook()
Optional hook run before a state update (advance/deserialize). Base does nothing.
Definition game.hpp:950
void satisfyRule(Rule &rule)
Marks a rule as satisfied, recursively satisfying the rules it satisfies first.
Definition game.hpp:875
ssize_t _saveSolutionCurrentLastRuleId
Current last rule index that activated a save solution; save state activates only when a rule id is b...
Definition game.hpp:972
void serializeState(jaffarCommon::serializer::Base &serializer) const
Serializes the full game state.
Definition game.hpp:187
ssize_t getSaveSolutionCurrentLastRuleIdx() const
Returns the current last rule index that set a save solution.
Definition game.hpp:619
std::string getRegisteredPropertyNames() const
Returns a comma-separated list of the property names registered for this game.
Definition game.hpp:84
virtual float getFloorReward() const
Reward used for the Reference Reward Floor comparison: the un-biased progress reward,...
Definition game.hpp:598
std::string _gameName
Game name (for runtime use).
Definition game.hpp:994
virtual void playerPrintCommands() const
Prints the game's player-specific commands, if any.
Definition game.hpp:655
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.
Definition game.hpp:447
size_t getCheckpointLevel() const
Returns the current state's checkpoint level.
Definition game.hpp:604
void evaluateRules()
Evaluates the rule set against the current state.
Definition game.hpp:351
virtual void stateUpdatePostHook()
Optional hook run after a state update (advance/deserialize). Base does nothing.
Definition game.hpp:952
void finalizeGameConfig()
Asserts that every key in the game configuration has been recognized.
Definition game.hpp:681
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.
Definition game.hpp:691
virtual std::set< std::string > getAllPossibleInputs()
Reports all possible inputs the game might require.
Definition game.hpp:652
void deserializeState(jaffarCommon::deserializer::Base &deserializer)
Restores the full game state previously written by serializeState.
Definition game.hpp:224
bool _isInitialized
Whether the game has been initialized.
Definition game.hpp:1001
std::unique_ptr< Condition > parseCondition(const nlohmann::json &conditionJs)
Parses a single rule condition from JSON into a typed Condition.
Definition game.hpp:485
float getFrameRate() const
Returns the configured frame rate.
Definition game.hpp:581
void printInfo() const
Prints the current game state to the logger.
Definition game.hpp:288
std::vector< std::string > _hashablePropertyNames
Parsed property names configured to be hashed.
Definition game.hpp:982
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.
Definition game.hpp:645
virtual jaffarCommon::hash::hash_t getStateInputHash()
Returns a hash identifying the current state for new-input discovery.
Definition game.hpp:638
float getReward() const
Returns the current state's reward.
Definition game.hpp:584
void updateGameStateType()
Recomputes the state type and checkpoint level from the satisfied rules.
Definition game.hpp:405
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.
Definition game.hpp:668
stateType_t getStateType() const
Returns the current state type (normal, win or fail).
Definition game.hpp:601
void computeHash(MetroHash128 &hashEngine) const
Updates a hash engine with the current state's distinguishing data.
Definition game.hpp:273
void initialize()
Initializes the game: emulator, properties, rules and the first state update.
Definition game.hpp:100
float _reward
Current game state reward.
Definition game.hpp:961
nlohmann::json _gameConfigRemaining
Mutable working copy of the game config; recognized keys are popped, leftovers are unrecognized....
Definition game.hpp:999
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.
Definition game.hpp:43
@ normal
No win or fail rule is currently satisfied.
Definition game.hpp:44
@ fail
A fail rule is currently satisfied.
Definition game.hpp:46
@ win
A win rule is currently satisfied.
Definition game.hpp:45
nlohmann::json _rulesJs
Temporary storage of the rules JSON for delayed parsing.
Definition game.hpp:996
std::vector< const Property * > _propertyPrintVector
Properties printed for game information, ordered.
Definition game.hpp:986
std::string getName() const
Returns the game name used at runtime.
Definition game.hpp:628
const std::string getSaveSolutionPath() const
Returns the save path of the rule that activated the current save solution.
Definition game.hpp:625
virtual std::string getTraceLine() const
One line of a per-step trace for player --dumpTrace (space-separated coordinates the game wants to re...
Definition game.hpp:590
void parseRuleAction(Rule &rule, const nlohmann::json &actionJs)
Parses a single rule action from JSON and applies it to the rule.
Definition game.hpp:814
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.
Definition game.hpp:383
bool isInitialized() const
Returns whether the game has been initialized.
Definition game.hpp:631
ssize_t _saveSolutionCurrentLastRuleIdx
Previous last rule index that activated a save solution (preserved to mark the state where it changes...
Definition game.hpp:969
std::vector< std::unique_ptr< Rule > > _rules
Game script rules, kept in a vector to preserve ordering.
Definition game.hpp:976
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.
Definition game.hpp:616
size_t inputIndex_t
Type used to index an input.
Definition inputSet.hpp:29
endianness_t
The byte order of the value stored at the property's memory address.
Definition property.hpp:45
datatype_t
The interpretation of the bytes at the property's memory address.
Definition property.hpp:29
@ dt_int32
Signed 32-bit integer (config datatype "INT32").
Definition property.hpp:36
@ dt_int8
Signed 8-bit integer (config datatype "INT8").
Definition property.hpp:34
@ dt_float32
Single precision float, 32-bit (config datatype "FLOAT32").
Definition property.hpp:39
@ dt_uint64
Unsigned 64-bit integer (config datatype "UINT64").
Definition property.hpp:33
@ dt_uint16
Unsigned 16-bit integer (config datatype "UINT16").
Definition property.hpp:31
@ dt_uint8
Unsigned 8-bit integer (config datatype "UINT8").
Definition property.hpp:30
@ dt_int16
Signed 16-bit integer (config datatype "INT16").
Definition property.hpp:35
@ dt_uint32
Unsigned 32-bit integer (config datatype "UINT32").
Definition property.hpp:32
@ dt_bool
Boolean stored in a single byte (config datatype "BOOL").
Definition property.hpp:38
@ dt_float64
Double precision float, 64-bit (config datatype "FLOAT64").
Definition property.hpp:40
@ dt_int64
Signed 64-bit integer (config datatype "INT64").
Definition property.hpp:37
A labelled collection of conditions with associated reward, actions and outcome flags.
Definition rule.hpp:27
label_t getLabel() const
Returns the rule's identifying label.
Definition rule.hpp:76
void setWinRule(const bool isWinRule)
Sets whether this rule is a win rule.
Definition rule.hpp:55
void setFailRule(const bool isFailRule)
Sets whether this rule is a fail rule.
Definition rule.hpp:57
size_t getIndex() const
Returns the rule's internal sequential index.
Definition rule.hpp:96
void addSatisfyRuleLabel(const label_t satisfyRuleLabel)
Adds the label of another rule considered satisfied alongside this one.
Definition rule.hpp:71
const std::vector< Rule * > & getSatisfyRules() const
Returns pointers to the rules satisfied alongside this one.
Definition rule.hpp:94
void setCheckpointTolerance(const size_t checkPointTolerance)
Sets the checkpoint tolerance for this rule.
Definition rule.hpp:61
void setReward(const float reward)
Sets the reward granted when this rule is satisfied.
Definition rule.hpp:53
size_t label_t
Type used to identify a rule by label.
Definition rule.hpp:30
void setSaveSolutionRule(const bool isSaveSolutionRule)
Sets whether this rule triggers saving the solution.
Definition rule.hpp:63
void addCondition(std::unique_ptr< Condition > condition)
Adds a condition that must hold for this rule to be satisfied.
Definition rule.hpp:69
void setCheckpointRule(const bool isCheckpointRule)
Sets whether this rule is a checkpoint rule.
Definition rule.hpp:59
void setSaveSolutionPath(const std::string &saveSolutionPath)
Sets the path where the solution is saved.
Definition rule.hpp:65
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,...