0ad/source/tools/rlclient/python/zero_ad/environment.py
wraitii df18102e96 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.
2021-02-28 12:16:32 +00:00

116 lines
3.4 KiB
Python

from .api import RLAPI
import json
import math
from xml.etree import ElementTree
from itertools import cycle
class ZeroAD():
def __init__(self, uri='http://localhost:6000'):
self.api = RLAPI(uri)
self.current_state = None
self.cache = {}
self.player_id = 1
def step(self, actions=[], player=None):
player_ids = cycle([self.player_id]) if player is None else cycle(player)
cmds = zip(player_ids, actions)
cmds = ((player, action) for (player, action) in cmds if action is not None)
state_json = self.api.step(cmds)
self.current_state = GameState(json.loads(state_json), self)
return self.current_state
def reset(self, config='', save_replay=False, player_id=1):
state_json = self.api.reset(config, player_id, save_replay)
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]
def get_templates(self, names):
templates = self.api.get_templates(names)
return [ (name, EntityTemplate(content)) for (name, content) in templates ]
def update_templates(self, types=[]):
all_types = list(set([unit.type() for unit in self.current_state.units()]))
all_types += types
template_pairs = self.get_templates(all_types)
self.cache = {}
for (name, tpl) in template_pairs:
self.cache[name] = tpl
return template_pairs
class GameState():
def __init__(self, data, game):
self.data = data
self.game = game
self.mapSize = self.data['mapSize']
def units(self, owner=None, type=None):
filter_fn = lambda e: (owner is None or e['owner'] == owner) and \
(type is None or type in e['template'])
return [ Entity(e, self.game) for e in self.data['entities'].values() if filter_fn(e) ]
def unit(self, id):
id = str(id)
return Entity(self.data['entities'][id], self.game) if id in self.data['entities'] else None
class Entity():
def __init__(self, data, game):
self.data = data
self.game = game
self.template = self.game.cache.get(self.type(), None)
def type(self):
return self.data['template']
def id(self):
return self.data['id']
def owner(self):
return self.data['owner']
def max_health(self):
template = self.get_template()
return float(template.get('Health/Max'))
def health(self, ratio=False):
if ratio:
return self.data['hitpoints']/self.max_health()
return self.data['hitpoints']
def position(self):
return self.data['position']
def get_template(self):
if self.template is None:
self.game.update_templates([self.type()])
self.template = self.game.cache[self.type()]
return self.template
class EntityTemplate():
def __init__(self, xml):
self.data = ElementTree.fromstring(f'<Entity>{xml}</Entity>')
def get(self, path):
node = self.data.find(path)
return node.text if node is not None else None
def set(self, path, value):
node = self.data.find(path)
if node:
node.text = str(value)
return node is not None
def __str__(self):
return ElementTree.tostring(self.data).decode('utf-8')