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 __INLINE__ Property* findProperty(const std::string& propertyName)
85 {
86 const auto h = jaffarCommon::hash::hashString(propertyName);
87 if (_propertyMap.contains(h) == false) return nullptr;
88 return _propertyMap[h].get();
89 }
90
96 std::string getRegisteredPropertyNames() const
97 {
98 std::string names;
99 for (const auto& entry : _propertyMap) names += (names.empty() ? "" : ", ") + entry.second->getName();
100 return names;
101 }
102
113 {
114 if (_isInitialized == true) JAFFAR_THROW_LOGIC("This game instance was already initialized");
115
116 // Initializing emulator, if not already initialized
117 if (_emulator->isInitialized() == false) _emulator->initialize();
118
119 // Getting game-specific properties
121
122 // Registering printable properties
123 for (const auto& property : _printablePropertyNames)
124 {
125 // Getting property name hash
126 const auto propertyHash = jaffarCommon::hash::hashString(property);
127
128 // Checking the property is registered
129 if (_propertyMap.contains(propertyHash) == false)
130 JAFFAR_THROW_LOGIC("Property '%s' is not registered in this game. Registered properties: %s\n", property.c_str(), getRegisteredPropertyNames().c_str());
131
132 // If so, add its pointer to the print property vector
133 _propertyPrintVector.push_back(_propertyMap.at(propertyHash).get());
134 }
135
136 // Registering hashable properties
137 for (const auto& property : _hashablePropertyNames)
138 {
139 // Getting property name hash
140 const auto propertyHash = jaffarCommon::hash::hashString(property);
141
142 // Checking the property is registered
143 if (_propertyMap.contains(propertyHash) == false)
144 JAFFAR_THROW_LOGIC("Property '%s' is not registered in this game. Registered properties: %s\n", property.c_str(), getRegisteredPropertyNames().c_str());
145
146 // If so, add its pointer to the print property vector
147 _propertyHashVector.push_back(_propertyMap.at(propertyHash).get());
148 }
149
150 // Now parsing rules
152
153 // Update internals pre initialization (first state update)
155
156 // Calling game-specific initializer
158
159 // Update internals post initialization
161
162 // Set this as initialized
163 _isInitialized = true;
164 }
165
166 Game() = delete;
167 virtual ~Game() = default;
168
176 __INLINE__ void advanceState(const InputSet::inputIndex_t input)
177 {
178 // Calling the pre-update hook
180
181 // Update save solution last rule id
183
184 // Performing the requested input
185 advanceStateImpl(input);
186
187 // Calling the post-update hook
189 }
190
199 __INLINE__ void serializeState(jaffarCommon::serializer::Base& serializer) const
200 {
201 // Serializing internal emulator state
202 if (_bypassEmulatorState == false) _emulator->serializeState(serializer);
203
204 // Storage for game-specific data
205 serializeStateImpl(serializer);
206
207 // Serializing reward
208 serializer.push(&_reward, sizeof(_reward));
209
210 // Serializing checkpoint level
211 serializer.push(&_checkpointLevel, sizeof(_checkpointLevel));
212
213 // Serializing the previous last rule id that activated a save solution
215
216 // Serializing the current last rule id that activated a save solution
218
219 // Serializing state type
220 serializer.push(&_stateType, sizeof(_stateType));
221
222 // Serializing rule states
223 serializer.push(_rulesStatus.data(), _rulesStatus.size());
224 }
225
236 __INLINE__ void deserializeState(jaffarCommon::deserializer::Base& deserializer)
237 {
238 // Calling the pre-update hook
240
241 // Storage for the internal emulator state
242 if (_bypassEmulatorState == false) _emulator->deserializeState(deserializer);
243
244 // Storage for game-specific data
245 deserializeStateImpl(deserializer);
246
247 // Calling the post-update hook
249
250 // Deserializing reward
251 deserializer.pop(&_reward, sizeof(_reward));
252
253 // Deserializing checkpoint level
254 deserializer.pop(&_checkpointLevel, sizeof(_checkpointLevel));
255
256 // Deserializing the previous last rule id that activated a save solution
258
259 // Deserializing the last rule id that activated a save solution
261
262 // Deserializing state type
263 deserializer.pop(&_stateType, sizeof(_stateType));
264
265 // Calling the pre-rule update hook
267
268 // Deserializing rules status
269 deserializer.pop(_rulesStatus.data(), _rulesStatus.size());
270
271 // Running game specific rule actions
273
274 // Calling the post-rule update hook
276 }
277
285 __INLINE__ void computeHash(MetroHash128& hashEngine) const
286 {
287 // Processing hashable game properties
288 for (const auto& p : _propertyHashVector) hashEngine.Update(p->getPointer(), p->getSize());
289
290 // Processing any additional game-specific hash
291 computeAdditionalHashing(hashEngine);
292 }
293
300 void printInfo() const
301 {
302 // Getting maximum printable property name, for formatting purposes
303 const size_t separatorSize = 4;
304 size_t maximumNameSize = 0;
305 for (const auto& p : _propertyPrintVector) maximumNameSize = std::max(maximumNameSize, p->getName().size());
306
307 // Printing game state
308 jaffarCommon::logger::log("[J+] + Game State Type: ");
309 if (_stateType == stateType_t::normal) jaffarCommon::logger::log("Normal");
310 if (_stateType == stateType_t::win) jaffarCommon::logger::log("Win");
311 if (_stateType == stateType_t::fail) jaffarCommon::logger::log("Fail");
312 jaffarCommon::logger::log("\n");
313
314 // Printing game state
315 jaffarCommon::logger::log("[J+] + Game State Reward: %f\n", _reward);
316
317 // Printing rule status
318 jaffarCommon::logger::log("[J+] + Rule Status: ");
319 for (size_t i = 0; i < _rules.size(); i++) jaffarCommon::logger::log("%d", jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), i) ? 1 : 0);
320 jaffarCommon::logger::log("\n");
321
322 // Printing game properties defined in the script file
323 jaffarCommon::logger::log("[J+] + Game Properties: \n");
324 for (const auto& p : _propertyPrintVector)
325 {
326 // Getting property name
327 const auto& name = p->getName();
328
329 // Printing property name first
330 jaffarCommon::logger::log("[J+] + '%s':", name.c_str());
331
332 // Calculating separation spaces for this property
333 const auto propertySeparatorSize = separatorSize + maximumNameSize - name.size();
334
335 // Printing separator spaces
336 for (size_t i = 0; i < propertySeparatorSize; i++) jaffarCommon::logger::log(" ");
337
338 // Then printing separator spaces
339 if (p->getDatatype() == Property::datatype_t::dt_int8) jaffarCommon::logger::log("0x%02X (%03d)\n", p->getValue<int8_t>(), p->getValue<int8_t>());
340 if (p->getDatatype() == Property::datatype_t::dt_int16) jaffarCommon::logger::log("0x%04X (%05d)\n", p->getValue<int16_t>(), p->getValue<int16_t>());
341 if (p->getDatatype() == Property::datatype_t::dt_int32) jaffarCommon::logger::log("0x%08X (%10d)\n", p->getValue<int32_t>(), p->getValue<int32_t>());
342 if (p->getDatatype() == Property::datatype_t::dt_int64) jaffarCommon::logger::log("0x%16lX (%ld)\n", p->getValue<int64_t>(), p->getValue<int64_t>());
343 if (p->getDatatype() == Property::datatype_t::dt_uint8) jaffarCommon::logger::log("0x%02X (%03u)\n", p->getValue<uint8_t>(), p->getValue<uint8_t>());
344 if (p->getDatatype() == Property::datatype_t::dt_uint16) jaffarCommon::logger::log("0x%04X (%05u)\n", p->getValue<uint16_t>(), p->getValue<uint16_t>());
345 if (p->getDatatype() == Property::datatype_t::dt_uint32) jaffarCommon::logger::log("0x%08X (%10u)\n", p->getValue<uint32_t>(), p->getValue<uint32_t>());
346 if (p->getDatatype() == Property::datatype_t::dt_uint64) jaffarCommon::logger::log("0x%16lX (%lu)\n", p->getValue<uint64_t>(), p->getValue<uint64_t>());
347 if (p->getDatatype() == Property::datatype_t::dt_float32) jaffarCommon::logger::log("%f (0x%X)\n", p->getValue<float>(), p->getValue<uint32_t>());
348 if (p->getDatatype() == Property::datatype_t::dt_float64) jaffarCommon::logger::log("%f (0x%lX)\n", p->getValue<double>(), p->getValue<uint64_t>());
349 if (p->getDatatype() == Property::datatype_t::dt_bool) jaffarCommon::logger::log("%1u\n", p->getValue<bool>());
350 }
351
352 // Printing game-specific stuff now
354 }
355
363 __INLINE__ void evaluateRules()
364 {
365 // Calling the pre-update hook
367
368 // Second, check which unsatisfied rules have been satisfied now
369 for (auto& rule : _rules)
370 {
371 // Getting rule index
372 const auto ruleIdx = rule->getIndex();
373
374 // Evaluate rule only if it's not yet satisfied
375 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == false)
376 {
377 // Checking if conditions are met
378 bool isSatisfied = rule->evaluate();
379
380 // If it's achieved, update its status and run its actions
381 if (isSatisfied) satisfyRule(*rule);
382 }
383 }
384
385 // Running game-specific rule actions
387
388 // Calling the pre-update hook
390 }
391
396 {
397 // First, checking if the rules have been satisfied
398 for (auto& rule : _rules)
399 {
400 // Getting rule index
401 const auto ruleIdx = rule->getIndex();
402
403 // Run ations only if rule is satisfied
404 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == true)
405 for (const auto& action : rule->getActions()) action();
406 }
407 }
408
417 __INLINE__ void updateGameStateType()
418 {
419 // Clearing game state type before we evaluate satisfied rules
421
422 // Clearing checkpoint level and tolerance
424
425 // Second, we run the specified actions for the satisfied rules in label order
426 for (auto& rule : _rules)
427 {
428 // Getting rule index
429 const auto ruleIdx = rule->getIndex();
430
431 // Run actions
432 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == true)
433 {
434 // Modify game state, depending on rule type
435
436 // Evaluate checkpoint rule and store tolerance if specified
437 if (rule->isCheckpointRule())
438 {
440 _checkpointTolerance = rule->getCheckpointTolerance();
441 }
442
443 // Evaluate save state rule and path if specified -- only if the current rule label is greater than the last rule to activate this
444 if (rule->isSaveSolutionRule() && (ssize_t)ruleIdx > _saveSolutionCurrentLastRuleIdx) _saveSolutionCurrentLastRuleId = ruleIdx;
445
446 // Winning in the same rule superseeds checkpoint, and failing superseed everything
447 if (rule->isWinRule()) _stateType = stateType_t::win;
448 if (rule->isFailRule()) _stateType = stateType_t::fail;
449 }
450 }
451 }
452
459 __INLINE__ void updateReward()
460 {
461 // First, we resetting reward to zero
462 _reward = 0.0;
463
464 // Second, we get the reward from every satisfied rule
465 for (auto& rule : _rules)
466 {
467 // Getting rule index
468 const auto ruleIdx = rule->getIndex();
469
470 // Run actions
471 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), ruleIdx) == true)
472 {
473 // Getting reward from satisfied rule
474 const auto ruleReward = rule->getReward();
475
476 // Adding it to the state reward
477 _reward += ruleReward;
478 }
479 }
480
481 // Adding any game-specific rewards
483 }
484
497 std::unique_ptr<Condition> parseCondition(const nlohmann::json& conditionJs)
498 {
499 // Parsing operator name
500 const auto& opName = jaffarCommon::json::getString(conditionJs, "Op");
501
502 // Getting operator type from its name
503 const auto opType = Condition::getOperatorType(opName);
504
505 // Parsing first operand (property name)
506 const auto& property1Name = jaffarCommon::json::getString(conditionJs, "Property");
507
508 // Getting property name hash, for indexing
509 const auto property1NameHash = jaffarCommon::hash::hashString(property1Name);
510
511 // Making sure the requested property exists in the property map
512 if (_propertyMap.contains(property1NameHash) == false) JAFFAR_THROW_LOGIC("[ERROR] Property '%s' has not been declared.\n", property1Name.c_str());
513
514 // Getting property object
515 const auto property1 = _propertyMap[property1NameHash].get();
516
517 // Getting property data type
518 auto datatype1 = property1->getDatatype();
519
520 // Parsing second operand (number)
521 if (conditionJs.contains("Value") == false) JAFFAR_THROW_LOGIC("[ERROR] Rule condition missing 'Value' key.\n");
522 if (conditionJs["Value"].is_number() == false && conditionJs["Value"].is_string() == false && conditionJs["Value"].is_boolean() == false)
523 JAFFAR_THROW_LOGIC("[ERROR] Wrong format for 'Value' entry in rule condition. It must be a string or number");
524
525 // If value is a number, take it as immediate
526 if (conditionJs["Value"].is_number())
527 {
528 if (datatype1 == Property::datatype_t::dt_uint8) return std::make_unique<_vCondition<uint8_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<uint8_t>());
529 if (datatype1 == Property::datatype_t::dt_uint16) return std::make_unique<_vCondition<uint16_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<uint16_t>());
530 if (datatype1 == Property::datatype_t::dt_uint32) return std::make_unique<_vCondition<uint32_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<uint32_t>());
531 if (datatype1 == Property::datatype_t::dt_uint64) return std::make_unique<_vCondition<uint64_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<uint64_t>());
532
533 if (datatype1 == Property::datatype_t::dt_int8) return std::make_unique<_vCondition<int8_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<int8_t>());
534 if (datatype1 == Property::datatype_t::dt_int16) return std::make_unique<_vCondition<int16_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<int16_t>());
535 if (datatype1 == Property::datatype_t::dt_int32) return std::make_unique<_vCondition<int32_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<int32_t>());
536 if (datatype1 == Property::datatype_t::dt_int64) return std::make_unique<_vCondition<int64_t>>(opType, property1, nullptr, 0, conditionJs["Value"].get<int64_t>());
537
538 if (datatype1 == Property::datatype_t::dt_float32) return std::make_unique<_vCondition<float>>(opType, property1, nullptr, 0, conditionJs["Value"].get<float>());
539 if (datatype1 == Property::datatype_t::dt_float64) return std::make_unique<_vCondition<double>>(opType, property1, nullptr, 0, conditionJs["Value"].get<double>());
540 }
541
542 // If value is a boolean, take it as immediate
543 if (conditionJs["Value"].is_boolean())
544 {
545 if (datatype1 == Property::datatype_t::dt_bool) return std::make_unique<_vCondition<bool>>(opType, property1, nullptr, 0, conditionJs["Value"].get<bool>());
546 }
547
548 // If value is a string, take value as property number 2
549 if (conditionJs["Value"].is_string())
550 {
551 // Parsing second operand (property name)
552 const auto& property2Name = jaffarCommon::json::getString(conditionJs, "Value");
553
554 // Getting property name hash, for indexing
555 const auto property2NameHash = jaffarCommon::hash::hashString(property2Name);
556
557 // Making sure the requested property exists in the property map
558 if (_propertyMap.contains(property2NameHash) == false) JAFFAR_THROW_LOGIC("[ERROR] Property '%s' has not been declared.\n", property2Name.c_str());
559
560 // Getting property object
561 const auto property2 = _propertyMap[property2NameHash].get();
562
563 if (datatype1 == Property::datatype_t::dt_uint8) return std::make_unique<_vCondition<uint8_t>>(opType, property1, property2, 0, 0);
564 if (datatype1 == Property::datatype_t::dt_uint16) return std::make_unique<_vCondition<uint16_t>>(opType, property1, property2, 0, 0);
565 if (datatype1 == Property::datatype_t::dt_uint32) return std::make_unique<_vCondition<uint32_t>>(opType, property1, property2, 0, 0);
566 if (datatype1 == Property::datatype_t::dt_uint64) return std::make_unique<_vCondition<uint64_t>>(opType, property1, property2, 0, 0);
567
568 if (datatype1 == Property::datatype_t::dt_int8) return std::make_unique<_vCondition<int8_t>>(opType, property1, property2, 0, 0);
569 if (datatype1 == Property::datatype_t::dt_int16) return std::make_unique<_vCondition<int16_t>>(opType, property1, property2, 0, 0);
570 if (datatype1 == Property::datatype_t::dt_int32) return std::make_unique<_vCondition<int32_t>>(opType, property1, property2, 0, 0);
571 if (datatype1 == Property::datatype_t::dt_int64) return std::make_unique<_vCondition<int64_t>>(opType, property1, property2, 0, 0);
572
573 if (datatype1 == Property::datatype_t::dt_float32) return std::make_unique<_vCondition<float>>(opType, property1, property2, 0, 0);
574 if (datatype1 == Property::datatype_t::dt_float64) return std::make_unique<_vCondition<double>>(opType, property1, property2, 0, 0);
575 if (datatype1 == Property::datatype_t::dt_bool) return std::make_unique<_vCondition<bool>>(opType, property1, property2, 0, 0);
576 }
577
578 JAFFAR_THROW_LOGIC("[ERROR] Rule contains an invalid 'Value' key.\n", conditionJs["Value"].dump().c_str());
579 }
580
582 __INLINE__ Emulator* getEmulator() const { return _emulator.get(); }
583
590 static std::unique_ptr<Game> getGame(const nlohmann::json& emulatorConfig, const nlohmann::json& gameConfig);
591
593 __INLINE__ float getFrameRate() const { return _frameRate; }
594
596 __INLINE__ float getReward() const { return _reward; }
597
602 virtual __INLINE__ std::string getTraceLine() const { return ""; }
603
610 virtual __INLINE__ float getFloorReward() const { return _reward; }
611
614 virtual __INLINE__ InputSet::inputIndex_t getNullInputIndex() const { return 0; }
615
617 __INLINE__ stateType_t getStateType() const { return _stateType; }
618
620 __INLINE__ size_t getCheckpointLevel() const { return _checkpointLevel; }
621
623 __INLINE__ size_t getCheckpointTolerance() const { return _checkpointTolerance; }
624
630
633
636
641 __INLINE__ const std::string getSaveSolutionPath() const { return isSaveSolution() ? _rules[_saveSolutionCurrentLastRuleId]->getSaveSolutionPath() : ""; }
642
644 __INLINE__ std::string getName() const { return _gameName; }
645
647 __INLINE__ bool isInitialized() const { return _isInitialized; }
648
654 virtual jaffarCommon::hash::hash_t getStateInputHash() { return jaffarCommon::hash::hash_t(); };
655
661 virtual __INLINE__ void getAdditionalAllowedInputs(std::vector<InputSet::inputIndex_t>& allowedInputSet) {}
662
668 virtual __INLINE__ std::set<std::string> getAllPossibleInputs() { return {}; }
669
671 virtual void playerPrintCommands() const {}
677 virtual bool playerParseCommand(const int command) { return false; }
678
684 virtual jaffarCommon::hash::hash_t getDirectStateHash() const { return jaffarCommon::hash::hash_t(); }
685
686protected:
697 void finalizeGameConfig() { jaffarCommon::json::checkEmpty(_gameConfigRemaining, "Game Configuration"); }
698
707 void* registerGameProperty(const std::string& name, void* const pointer, const Property::datatype_t dataType, const Property::endianness_t endianness)
708 {
709 // Creating property
710 auto property = std::make_unique<Property>(name, pointer, dataType, endianness);
711
712 // Getting property name hash as key
713 const auto propertyNameHash = property->getNameHash();
714
715 // Adding property to the map for later reference
716 _propertyMap[propertyNameHash] = std::move(property);
717
718 // Return the pointer proper (this is just sugar to make the use of this function more compact)
719 return pointer;
720 }
721
732 void parseRules(const nlohmann::json& rulesJson)
733 {
734 // Reset the rules container
735 _rules.clear();
736 _rulesStatus.clear();
737
738 // Evaluate each rule
739 for (size_t idx = 0; idx < rulesJson.size(); idx++)
740 {
741 // Getting specific rule json object
742 const auto& ruleJs = rulesJson[idx];
743
744 // Check if rule is a key/value object
745 if (ruleJs.is_object() == false) JAFFAR_THROW_LOGIC("Passed rule is not a JSON object. Dump: \n %s", ruleJs.dump(2).c_str());
746
747 // Getting rule label
748 auto label = jaffarCommon::json::getNumber<Rule::label_t>(ruleJs, "Label");
749
750 // Creating new rule with the given label
751 auto rule = std::make_unique<Rule>(idx, label);
752
753 // Parsing json into a rule class
754 parseRule(*rule, ruleJs);
755
756 // Adding new rule to the collection
757 _rules.push_back(std::move(rule));
758 }
759
760 // Checking all cross references are correct
761 for (const auto& rule : _rules)
762 for (const auto& label : rule->getSatisfyRuleLabels())
763 {
764 bool subRuleFound = false;
765 for (const auto& subRule : _rules)
766 if (subRule->getLabel() == label)
767 {
768 rule->addSatisfyRule(subRule.get());
769 subRuleFound = true;
770 }
771 if (subRuleFound == false) JAFFAR_THROW_LOGIC("Rule label %u referenced by rule %u in the 'Satisfies' array does not exist.\n", label, rule->getIndex());
772 }
773
774 // Create rule status vector
775 _rulesStatus.resize(jaffarCommon::bitwise::getByteStorageForBitCount(_rules.size()));
776
777 // Clearing the status vector evaluation
778 for (size_t i = 0; i < _rules.size(); i++) jaffarCommon::bitwise::setBitValue(_rulesStatus.data(), i, false);
779 }
780
791 void parseRule(Rule& rule, const nlohmann::json& ruleJs)
792 {
793 // Getting rule condition array
794 const auto& conditions = jaffarCommon::json::getArray<nlohmann::json>(ruleJs, "Conditions");
795
796 // Getting rule action array
797 const auto& actions = jaffarCommon::json::getArray<nlohmann::json>(ruleJs, "Actions");
798
799 // Parsing satisfies vector
800 const auto& satisfiesVectorJs = jaffarCommon::json::getArray<nlohmann::json>(ruleJs, "Satisfies");
801
802 // Parsing rule conditions
803 for (const auto& condition : conditions) rule.addCondition(parseCondition(condition));
804
805 // Parsing rule actions
806 for (const auto& action : actions) parseRuleAction(rule, action);
807
808 // Parsing satisfies vector
809 for (const auto& s : satisfiesVectorJs)
810 {
811 // Check for correct format
812 if (s.is_number() == false) JAFFAR_THROW_LOGIC("Wrong format provided in 'Satisfies' array in rule '%s'\n", ruleJs.dump(2).c_str());
813
814 // Adding the satisfies label
815 rule.addSatisfyRuleLabel(s.get<Rule::label_t>());
816 }
817 }
818
830 void parseRuleAction(Rule& rule, const nlohmann::json& actionJs)
831 {
832 // Getting action type
833 std::string actionType = jaffarCommon::json::getString(actionJs, "Type");
834
835 // Running the action, depending on the type
836 bool recognizedActionType = false;
837
838 if (actionType == "Add Reward")
839 {
840 rule.setReward(jaffarCommon::json::getNumber<float>(actionJs, "Value"));
841 recognizedActionType = true;
842 }
843
844 // Storing fail state
845 if (actionType == "Trigger Fail")
846 {
847 rule.setFailRule(true);
848 recognizedActionType = true;
849 }
850
851 // Storing win state
852 if (actionType == "Trigger Win")
853 {
854 rule.setWinRule(true);
855 recognizedActionType = true;
856 }
857
858 // Storing checkpoint flags
859 if (actionType == "Trigger Checkpoint")
860 {
861 rule.setCheckpointRule(true);
862 rule.setCheckpointTolerance(jaffarCommon::json::getNumber<size_t>(actionJs, "Tolerance"));
863 recognizedActionType = true;
864 }
865
866 // Storing save state flags
867 if (actionType == "Trigger Save Solution")
868 {
869 rule.setSaveSolutionRule(true);
870 rule.setSaveSolutionPath(jaffarCommon::json::getString(actionJs, "Path"));
871 recognizedActionType = true;
872 }
873
874 // If not recognized yet, it must be a game specific action
875 if (recognizedActionType == false) recognizedActionType = parseRuleActionImpl(rule, actionType, actionJs);
876
877 // If not recognized at all, then fail
878 if (recognizedActionType == false)
879 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 "
880 "any game-specific actions)\n",
881 actionType.c_str(), rule.getLabel());
882 }
883
891 __INLINE__ void satisfyRule(Rule& rule)
892 {
893 // Recursively run actions for the yet unsatisfied rules that are satisfied by this one and mark them as satisfied
894 for (const auto subRule : rule.getSatisfyRules())
895 {
896 // Getting index from the subrule
897 auto subRuleIdx = subRule->getIndex();
898
899 // Only activate it if it hasn't been activated before
900 if (jaffarCommon::bitwise::getBitValue(_rulesStatus.data(), subRuleIdx) == false) satisfyRule(*subRule);
901 }
902
903 // Getting rule index
904 const auto ruleIdx = rule.getIndex();
905
906 // Setting status to satisfied
907 jaffarCommon::bitwise::setBitValue(_rulesStatus.data(), ruleIdx, true);
908 }
909
914 virtual void initializeImpl() {};
915
919 virtual void registerGameProperties() = 0;
920
925 virtual void serializeStateImpl(jaffarCommon::serializer::Base& serializer) const = 0;
926
931 virtual void deserializeStateImpl(jaffarCommon::deserializer::Base& deserializer) = 0;
932
937 virtual float calculateGameSpecificReward() const = 0;
938
943 virtual void computeAdditionalHashing(MetroHash128& hashEngine) const = 0;
944
948 virtual void printInfoImpl() const = 0;
949
954 virtual void advanceStateImpl(const InputSet::inputIndex_t input) = 0;
955
963 virtual bool parseRuleActionImpl(Rule& rule, const std::string& actionType, const nlohmann::json& actionJs) = 0;
964
966 virtual __INLINE__ void stateUpdatePreHook() {};
968 virtual __INLINE__ void stateUpdatePostHook() {};
970 virtual __INLINE__ void ruleUpdatePreHook() {};
972 virtual __INLINE__ void ruleUpdatePostHook() {};
973
976
977 float _reward = 0.0;
978
979 size_t _checkpointLevel = 0;
980
982
986
989
990 const std::unique_ptr<Emulator> _emulator;
991
992 std::vector<std::unique_ptr<Rule>> _rules;
993
994 std::vector<uint8_t> _rulesStatus;
995
996 std::vector<std::string> _printablePropertyNames;
997
998 std::vector<std::string> _hashablePropertyNames;
999
1000 std::vector<const Property*> _propertyHashVector;
1001
1002 std::vector<const Property*> _propertyPrintVector;
1003
1004 std::map<jaffarCommon::hash::hash_t, std::unique_ptr<Property>> _propertyMap;
1005
1007
1009
1010 std::string _gameName;
1011
1012 nlohmann::json _rulesJs;
1013
1015 nlohmann::json _gameConfigRemaining;
1016
1017 bool _isInitialized = false;
1018};
1019
1020} // 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:1006
size_t _checkpointLevel
Current state's checkpoint level.
Definition game.hpp:979
virtual bool playerParseCommand(const int command)
Handles a game-specific player command.
Definition game.hpp:677
virtual void ruleUpdatePostHook()
Optional hook run after rule evaluation/restoration. Base does nothing.
Definition game.hpp:972
const std::unique_ptr< Emulator > _emulator
Underlying emulator instance.
Definition game.hpp:990
std::map< jaffarCommon::hash::hash_t, std::unique_ptr< Property > > _propertyMap
All registered properties, indexed by name hash.
Definition game.hpp:1004
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:981
std::vector< const Property * > _propertyHashVector
Properties used to hash/distinguish states, ordered.
Definition game.hpp:1000
void parseRule(Rule &rule, const nlohmann::json &ruleJs)
Parses a single rule's conditions, actions and "Satisfies" labels into a Rule.
Definition game.hpp:791
std::vector< std::string > _printablePropertyNames
Parsed property names configured to be printed.
Definition game.hpp:996
size_t getCheckpointTolerance() const
Returns the current state's checkpoint tolerance.
Definition game.hpp:623
virtual InputSet::inputIndex_t getNullInputIndex() const
The input index representing "no buttons pressed" – used by the engine's Hash Lookahead (advance-with...
Definition game.hpp:614
bool isSaveSolution() const
Indicates whether the current state should trigger a save solution.
Definition game.hpp:629
virtual void initializeImpl()
Game-specific initialization hook, called during initialize.
Definition game.hpp:914
void advanceState(const InputSet::inputIndex_t input)
Advances the game state by applying a single input.
Definition game.hpp:176
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:970
Emulator * getEmulator() const
Returns a pointer to the internal emulator.
Definition game.hpp:582
std::vector< uint8_t > _rulesStatus
Bit vector indicating whether each rule has been satisfied.
Definition game.hpp:994
void parseRules(const nlohmann::json &rulesJson)
Parses the full rule array, builds the rule objects and resolves cross-references.
Definition game.hpp:732
stateType_t _stateType
Current game state type. Initialized to normal because it is read (printInfo) and serialized for the ...
Definition game.hpp:975
bool _bypassEmulatorState
When true, the game handles state save/load entirely, bypassing the emulator.
Definition game.hpp:1008
virtual void stateUpdatePreHook()
Optional hook run before a state update (advance/deserialize). Base does nothing.
Definition game.hpp:966
void satisfyRule(Rule &rule)
Marks a rule as satisfied, recursively satisfying the rules it satisfies first.
Definition game.hpp:891
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:988
void serializeState(jaffarCommon::serializer::Base &serializer) const
Serializes the full game state.
Definition game.hpp:199
ssize_t getSaveSolutionCurrentLastRuleIdx() const
Returns the current last rule index that set a save solution.
Definition game.hpp:635
std::string getRegisteredPropertyNames() const
Returns a comma-separated list of the property names registered for this game.
Definition game.hpp:96
virtual float getFloorReward() const
Reward used for the Reference Reward Floor comparison: the un-biased progress reward,...
Definition game.hpp:610
std::string _gameName
Game name (for runtime use).
Definition game.hpp:1010
virtual void playerPrintCommands() const
Prints the game's player-specific commands, if any.
Definition game.hpp:671
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:459
size_t getCheckpointLevel() const
Returns the current state's checkpoint level.
Definition game.hpp:620
Property * findProperty(const std::string &propertyName)
Finds a registered game property by name.
Definition game.hpp:84
void evaluateRules()
Evaluates the rule set against the current state.
Definition game.hpp:363
virtual void stateUpdatePostHook()
Optional hook run after a state update (advance/deserialize). Base does nothing.
Definition game.hpp:968
void finalizeGameConfig()
Asserts that every key in the game configuration has been recognized.
Definition game.hpp:697
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:707
virtual std::set< std::string > getAllPossibleInputs()
Reports all possible inputs the game might require.
Definition game.hpp:668
void deserializeState(jaffarCommon::deserializer::Base &deserializer)
Restores the full game state previously written by serializeState.
Definition game.hpp:236
bool _isInitialized
Whether the game has been initialized.
Definition game.hpp:1017
std::unique_ptr< Condition > parseCondition(const nlohmann::json &conditionJs)
Parses a single rule condition from JSON into a typed Condition.
Definition game.hpp:497
float getFrameRate() const
Returns the configured frame rate.
Definition game.hpp:593
void printInfo() const
Prints the current game state to the logger.
Definition game.hpp:300
std::vector< std::string > _hashablePropertyNames
Parsed property names configured to be hashed.
Definition game.hpp:998
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:661
virtual jaffarCommon::hash::hash_t getStateInputHash()
Returns a hash identifying the current state for new-input discovery.
Definition game.hpp:654
float getReward() const
Returns the current state's reward.
Definition game.hpp:596
void updateGameStateType()
Recomputes the state type and checkpoint level from the satisfied rules.
Definition game.hpp:417
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:684
stateType_t getStateType() const
Returns the current state type (normal, win or fail).
Definition game.hpp:617
void computeHash(MetroHash128 &hashEngine) const
Updates a hash engine with the current state's distinguishing data.
Definition game.hpp:285
void initialize()
Initializes the game: emulator, properties, rules and the first state update.
Definition game.hpp:112
float _reward
Current game state reward.
Definition game.hpp:977
nlohmann::json _gameConfigRemaining
Mutable working copy of the game config; recognized keys are popped, leftovers are unrecognized....
Definition game.hpp:1015
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:1012
std::vector< const Property * > _propertyPrintVector
Properties printed for game information, ordered.
Definition game.hpp:1002
std::string getName() const
Returns the game name used at runtime.
Definition game.hpp:644
const std::string getSaveSolutionPath() const
Returns the save path of the rule that activated the current save solution.
Definition game.hpp:641
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:602
void parseRuleAction(Rule &rule, const nlohmann::json &actionJs)
Parses a single rule action from JSON and applies it to the rule.
Definition game.hpp:830
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:395
bool isInitialized() const
Returns whether the game has been initialized.
Definition game.hpp:647
ssize_t _saveSolutionCurrentLastRuleIdx
Previous last rule index that activated a save solution (preserved to mark the state where it changes...
Definition game.hpp:985
std::vector< std::unique_ptr< Rule > > _rules
Game script rules, kept in a vector to preserve ordering.
Definition game.hpp:992
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:632
size_t inputIndex_t
Type used to index an input.
Definition inputSet.hpp:29
A named, typed reference to a value stored at a memory address.
Definition property.hpp:25
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,...