Add an 'Evaluate' RL endpoint, intended for dynamic game balancing

This adds a new endpoint to the RL interface for evaluating custom
JavaScript. When combined with the ability to create arbitrary
modifiers, this provides the required functionality for exploring
quantitative game balancing.

Patch By: irishninja
Fixes #5981

Differential Revision: https://code.wildfiregames.com/D3479
This was SVN commit r24962.
This commit is contained in:
wraitii
2021-02-28 12:16:32 +00:00
parent 2e2ef6f3fe
commit df18102e96
7 changed files with 137 additions and 15 deletions
@@ -0,0 +1,10 @@
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
let playerEnt = cmpPlayerManager.GetPlayerByID('1');
let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
cmpModifiersManager.AddModifiers("cheat/fastactions", {
"Cost/BuildTime": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }],
"ResourceGatherer/BaseSpeed": [{ "affects": [["Structure"], ["Unit"]], "multiply": 1000 }],
"Pack/Time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }],
"Upgrade/Time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }],
"ProductionQueue/TechCostMultiplier/time": [{ "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }]
}, playerEnt);
@@ -78,15 +78,17 @@ def test_walk():
def test_attack():
state = game.reset(config)
units = state.units(owner=1, type='cavalry')
unit = state.units(owner=1, type='cavalry')[0]
target = state.units(owner=2, type='female_citizen')[0]
initial_health = target.health()
initial_health_target = target.health()
initial_health_unit = unit.health()
state = game.step([zero_ad.actions.reveal_map()])
attack = zero_ad.actions.attack(units, target)
attack = zero_ad.actions.attack([unit], target)
state = game.step([attack])
while state.unit(target.id()).health() >= initial_health:
while (state.unit(target.id()).health() >= initial_health_target
) and (state.unit(unit.id()).health() >= initial_health_unit):
state = game.step()
def test_chat():
@@ -0,0 +1,46 @@
import zero_ad
import json
import math
from os import path
game = zero_ad.ZeroAD('http://localhost:6000')
scriptdir = path.dirname(path.realpath(__file__))
with open(path.join(scriptdir, '..', 'samples', 'arcadia.json'), 'r') as f:
config = f.read()
with open(path.join(scriptdir, 'fastactions.js'), 'r') as f:
fastactions = f.read()
def test_return_object():
state = game.reset(config)
result = game.evaluate('({"hello": "world"})')
assert type(result) is dict
assert result['hello'] == 'world'
def test_return_null():
result = game.evaluate('null')
assert result == None
def test_return_string():
state = game.reset(config)
result = game.evaluate('"cat"')
assert result == 'cat'
def test_fastactions():
state = game.reset(config)
game.evaluate(fastactions)
female_citizens = state.units(owner=1, type='female_citizen')
house_tpl = 'structures/spart/house'
house_count = len(state.units(owner=1, type=house_tpl))
x = 680
z = 640
build_house = zero_ad.actions.construct(female_citizens, house_tpl, x, z, autocontinue=True)
# Check that they start building the house
state = game.step([build_house])
step_count = 0
new_house = lambda _=None: state.units(owner=1, type=house_tpl)[0]
initial_health = new_house().health(ratio=True)
while new_house().health(ratio=True) == initial_health:
state = game.step()
assert new_house().health(ratio=True) >= 1.0
@@ -27,3 +27,7 @@ class RLAPI():
post_data = '\n'.join(names)
response = self.post('templates', post_data)
return zip(names, response.decode().split('\n'))
def evaluate(self, code):
response = self.post('evaluate', code)
return json.loads(response.decode())
@@ -25,6 +25,9 @@ class ZeroAD():
self.current_state = GameState(json.loads(state_json), self)
return self.current_state
def evaluate(self, code):
return self.api.evaluate(code)
def get_template(self, name):
return self.get_templates([name])[0]