Skip to content

Commit

Permalink
Huge refactor of Lua AI engine
Browse files Browse the repository at this point in the history
This commit potentially breaks any Lua AI customization, except for external Lua candidate actions.
In practice, though, Lua aspects and goals will probably continue to work for the most part.

- The ai table now has a read_only attribute.
  If true, functions that change the game state will be missing from the table.
  The read_only attribute is false in CA execution and in stages.
  It is true everywhere else.
- Every Lua AI component now supports a [args] subtag.
  The contents of this tag are passed as parameters to the component code.
  This data is immutable; components cannot alter its contents.
  (External Lua candidate actions do not receive this data.)
- Accessing the persistent engine data is now supported in all Lua components.

When calling a Lua component, the Lua engine now passes two parameters:
1. The contents of the [args] tag in the specific component.
2. The contents of the [data] tag in the Lua [engine].

The return value of the [engine] code, if any, is stored for later used.
It will be passed as the third parameter to any other Lua component.
This data can be changed, but will not be saved.
The default engine does not return any such data.
  • Loading branch information
CelticMinstrel authored and mattsc committed Mar 22, 2016
1 parent 56a9917 commit f8f5557
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 126 deletions.
22 changes: 5 additions & 17 deletions data/ai/lua/dummy_engine_lua.lua
Expand Up @@ -2,21 +2,9 @@
-- This is the engine used by the Lua AI when no engine is
-- defined specifically in the [side] tag

return {
get_ai = function(ai)
local my_ai = {}
-- This provides a cache level for the move map functions,
-- making them a bit easier to use
local ai_stdlib = wesnoth.require('ai/lua/stdlib.lua')
ai_stdlib.init(ai)

local ai_stdlib = wesnoth.require('ai/lua/stdlib.lua')
ai_stdlib.init(ai)

-- Make the ai table available to the eval/exec functions
function my_ai:get_ai()
return ai
end

-- Make the persistent data table available to the eval/exec functions
my_ai.data = {}

return my_ai
end
}
-- No special state is returned by the default engine
11 changes: 11 additions & 0 deletions src/ai/composite/aspect.cpp
Expand Up @@ -148,4 +148,15 @@ known_aspect::~known_aspect()
{
}

std::string lua_aspect_visitor::quote_string(const std::string& s)
{
if (s.find_first_of('"') == std::string::npos) {
return '"' + s + '"';
} else if (s.find_first_of("'") == std::string::npos) {
return "'" + s + "'";
} else {
return "[=====[" + s + "]=====]";
}
}

} //end of namespace ai
37 changes: 22 additions & 15 deletions src/ai/composite/aspect.hpp
Expand Up @@ -26,6 +26,7 @@
#include "scripting/game_lua_kernel.hpp"

#include "log.hpp"
#include "util.hpp"

#include <boost/bind.hpp>
#include <boost/pointer_cast.hpp>
Expand Down Expand Up @@ -394,57 +395,63 @@ class standard_aspect : public typesafe_aspect<T> {
std::string turns_;

};

class lua_aspect_visitor : public boost::static_visitor<std::string> {
static std::string quote_string(const std::string& s);
public:
std::string operator()(bool b) const {return b ? "true" : "false";}
std::string operator()(int i) const {return quote_string(str_cast(i));}
std::string operator()(unsigned long long i) const {return quote_string(str_cast(i));}
std::string operator()(double i) const {return quote_string(str_cast(i));}
std::string operator()(const std::string& s) const {return quote_string(s);}
std::string operator()(const t_string& s) const {return quote_string(s.str());}
std::string operator()(boost::blank) const {return "nil";}
};

template<typename T>
class lua_aspect : public typesafe_aspect<T>
{
public:
lua_aspect(readonly_context &context, const config &cfg, const std::string &id, boost::shared_ptr<lua_ai_context>& l_ctx)
: typesafe_aspect<T>(context, cfg, id)
, handler_(), code_()
, handler_(), code_(), params_(cfg.child_or_empty("args"))
{
std::string value;
if (cfg.has_attribute("value"))
{
value = cfg["value"].str();
if (value == "yes") /** @todo for Nephro or Crab: get rid of this workaround */
{
value = "true";
}
value = "return " + value;
code_ = "return " + cfg["value"].apply_visitor(lua_aspect_visitor());
}
else if (cfg.has_attribute("code"))
{
value = cfg["code"].str();
code_ = cfg["code"].str();
}
else
{
// error
return;
}
code_ = value;
handler_ = boost::shared_ptr<lua_ai_action_handler>(resources::lua_kernel->create_lua_ai_action_handler(value.c_str(), *l_ctx));
handler_ = boost::shared_ptr<lua_ai_action_handler>(resources::lua_kernel->create_lua_ai_action_handler(code_.c_str(), *l_ctx));
}

void recalculate() const
{
this->valid_lua_ = true;
boost::shared_ptr< lua_object<T> > l_obj = boost::shared_ptr< lua_object<T> >(new lua_object<T>());
config c = config();
handler_->handle(c, true, l_obj);
this->value_lua_ = l_obj;
handler_->handle(params_, true, this->value_lua_);
}

config to_config() const
{
config cfg = aspect::to_config();
cfg["code"] = code_;
if (!params_.empty()) {
cfg.add_child("args", params_);
}
return cfg;
}

private:
boost::shared_ptr<lua_ai_action_handler> handler_;
std::string code_;
const config params_;
};


Expand Down
32 changes: 17 additions & 15 deletions src/ai/composite/engine_lua.cpp
Expand Up @@ -56,7 +56,7 @@ class lua_candidate_action_wrapper_base : public candidate_action {

public:
lua_candidate_action_wrapper_base( rca_context &context, const config &cfg)
: candidate_action(context, cfg),evaluation_action_handler_(),execution_action_handler_(),serialized_evaluation_state_()
: candidate_action(context, cfg),evaluation_action_handler_(),execution_action_handler_(),serialized_evaluation_state_(cfg.child_or_empty("args"))
{
// do nothing
}
Expand All @@ -65,8 +65,6 @@ class lua_candidate_action_wrapper_base : public candidate_action {

virtual double evaluate()
{
serialized_evaluation_state_ = config();

lua_int_obj l_obj = lua_int_obj(new lua_object<int>());

if (evaluation_action_handler_) {
Expand All @@ -82,15 +80,15 @@ class lua_candidate_action_wrapper_base : public candidate_action {


virtual void execute() {
lua_int_obj l_obj = lua_int_obj(new lua_object<int>());
if (execution_action_handler_) {
execution_action_handler_->handle(serialized_evaluation_state_, false, l_obj);
lua_object_ptr nil;
execution_action_handler_->handle(serialized_evaluation_state_, false, nil);
}
}

virtual config to_config() const {
config cfg = candidate_action::to_config();
cfg.add_child("state",serialized_evaluation_state_);
cfg.add_child("args",serialized_evaluation_state_);
return cfg;
}

Expand Down Expand Up @@ -155,9 +153,10 @@ class lua_candidate_action_wrapper_external : public lua_candidate_action_wrappe
std::string exec_parms_;

void generate_code(std::string& eval, std::string& exec) {
std::string code = "wesnoth.require(\"" + location_ + "\")";
eval = "return " + code + ":evaluation((...):get_ai(), {" + eval_parms_ + "}, (...))";
exec = code + ":execution((...):get_ai(), {" + exec_parms_ + "}, (...))";
std::string preamble = "local params, data, state = ...\n";
std::string load = "wesnoth.require(\"" + location_ + "\")";
eval = preamble + "return " + load + ":evaluation(ai, {" + eval_parms_ + "}, {data = data})";
exec = preamble + load + ":execution(ai, {" + exec_parms_ + "}, {data = data})";
}
};

Expand Down Expand Up @@ -197,7 +196,7 @@ class lua_sticky_candidate_action_wrapper : public lua_candidate_action_wrapper
class lua_stage_wrapper : public stage {
public:
lua_stage_wrapper( ai_context &context, const config &cfg, lua_ai_context &lua_ai_ctx )
: stage(context,cfg),action_handler_(),code_(cfg["code"]),serialized_evaluation_state_(cfg.child_or_empty("state"))
: stage(context,cfg),action_handler_(),code_(cfg["code"]),serialized_evaluation_state_(cfg.child_or_empty("args"))
{
action_handler_ = boost::shared_ptr<lua_ai_action_handler>(resources::lua_kernel->create_lua_ai_action_handler(code_.c_str(),lua_ai_ctx));
}
Expand All @@ -209,10 +208,10 @@ class lua_stage_wrapper : public stage {
virtual bool do_play_stage()
{
gamestate_observer gs_o;
lua_int_obj l_obj = lua_int_obj(new lua_object<int>());

if (action_handler_) {
action_handler_->handle(serialized_evaluation_state_, false, l_obj);
lua_object_ptr nil;
action_handler_->handle(serialized_evaluation_state_, false, nil);
}

return gs_o.is_gamestate_changed();
Expand All @@ -222,7 +221,7 @@ class lua_stage_wrapper : public stage {
{
config cfg = stage::to_config();
cfg["code"] = code_;
cfg.add_child("state",serialized_evaluation_state_);
cfg.add_child("args",serialized_evaluation_state_);
return cfg;
}
private:
Expand All @@ -244,9 +243,12 @@ engine_lua::engine_lua( readonly_context &context, const config &cfg )
{
name_ = "lua";
config data(cfg.child_or_empty("data"));
config args(cfg.child_or_empty("args"));

if (lua_ai_context_) { // The context might be NULL if the config contains errors
lua_ai_context_->set_persistent_data(data);
lua_ai_context_->set_arguments(args);
lua_ai_context_->update_state();
}
}

Expand All @@ -256,7 +258,7 @@ std::string engine_lua::get_engine_code(const config &cfg) const
return cfg["code"].str();
}
// If there is no engine defined we create a dummy engine
std::string code = "local ai = ... return wesnoth.require(\"ai/lua/dummy_engine_lua.lua\").get_ai(ai)";
std::string code = "wesnoth.require(\"ai/lua/dummy_engine_lua.lua\")";
return code;
}

Expand All @@ -273,7 +275,7 @@ void engine_lua::push_ai_table()
{
if (game_config::debug)
{
lua_ai_context_->load_and_inject_ai_table(this);
// TODO: Reimplement this somehow
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/ai/composite/goal.cpp
Expand Up @@ -350,7 +350,7 @@ void lua_goal::add_targets(std::back_insert_iterator< std::vector< target > > ta
{
boost::shared_ptr< lua_object< std::vector < target > > > l_obj
= boost::shared_ptr< lua_object< std::vector < target > > >(new lua_object< std::vector < target > >());
config c = config();
config c(cfg_.child_or_empty("args"));
handler_->handle(c, true, l_obj);
try {
std::vector < target > targets = *(l_obj->get());
Expand Down

0 comments on commit f8f5557

Please sign in to comment.