External AI control

Discussion of all aspects of the game engine, including development of new and existing features.

Moderators: Forum Moderators, Developers

Post Reply
mattsc
Posts: 1166
Joined: October 13th, 2010, 6:14 pm
Location: Hidden on the hex behind Fred

External AI control

Post by mattsc »

The question has come up a few times whether it is possible to have an external mechanism (such as a machine learning framework) interact with Wesnoth without having to change the C++ code. In other words, is it possible to receive the game state from Wesnoth and pass a move back to it, which is then executed by the AI, with the new game state being returned afterward and so on? While no direct interface mechanism is provided by the Wesnoth engine for this, it is nevertheless possible to achieve this via scripts that receive output sent to stdout by a Lua candidate action (CA) and modify files that are read by another Lua CA. It's admittedly a pretty hacky way to do this, but it does work and is quite simple.

I put together a very quick and dirty proof-of-concept example and added it to the AI-demos add-on. It can be accessed by starting the campaign from the 'Campaigns' menu item, or from the command line with wesnoth -tmanual_ai (or via the test scenarios hotkey). Note that this has not been uploaded to the add-ons server yet (some other parts of AI-demos are currently not releasable), but you can get it from the AI-demos github repository. The commit adding the example is here.

In brief, there are two Lua CAs. The first writes some information about units to stdout, which can then be read by, for example, an external script. This example only outputs a small subset of the game state information, but extending that is trivial. The second CA continuously reads a file and moves a unit according to that file's content, if it changes. Again, this is only an example of a simple move, but it can easily be extended to attacks, recruiting etc. Also, I have not built in any sanity checks, whether the move is actually possible or points to a valid unit or anything like that. As I said, this is meant as a quick proof-of-concept example only.

One more comment: Passing the information back to Wesnoth can probably be done in a "nicer" way by using the --unsafe-scripts command line option (or via changes to the C++ code), but I was really only interested in testing if this can be done using only Lua CAs directly. If somebody has a more elegant solution, please let us know.

I've included the files needed below with very brief explanations of what they do, just so that they are documented on the forum also.
The Files
add-ons/AI-demos/scenarios/manual_ai.cfg: The scenario file. Really the only important line here is the one that includes the AI config file. Note that, in the add-on, the switchboard.cfg file was also modified, but that has nothing to do with the AI, it just provides another way of starting the scenario.

Code: Select all

#textdomain wesnoth-AI-demos

#ifdef TEST
[test]
#else
# wmlindent: start ignoring
[scenario]
# wmlindent: stop ignoring
#endif
    id=manual_ai
    name=_"Manual AI"
    next_scenario=aid_switchboard

    map_data="{multiplayer/maps/2p_The_Freelands.map}"

    {DEFAULT_SCHEDULE}
    random_start_time=yes
    turns=50
    victory_when_enemies_defeated=yes

    [side]
        side=1
        controller=human
        id=Fred
        name=Fred
        type=Orcish Warrior
        persistent=no
        facing=sw

        team_name=Fred
        user_team_name=_"Fred"

        gold=100
        village_gold=2
    [/side]

    [side]
        side=2
        controller=ai
        id=challenger
        type=Orcish Warrior
        persistent=no

        team_name=challenger
        user_team_name=_"Fred's Challenger"
        save_id="Fred's Challenger"

        recruit=Goblin Spearman, Naga Fighter, Orcish Archer, Orcish Assassin, Orcish Grunt, Troll Whelp, Wolf Rider
        gold=100
        village_gold=2
        [village]
            x=26
            y=22
        [/village]

        {~add-ons/AI-demos/ais/ai_manual.cfg}
    [/side]

    [event]
        name=start

        [message]
            id=Fred
            caption=_"Manual External AI Control Testing"
            message=_"This is a proof-of-concept scenario demonstrating how one can control units on an AI side with input external to Wesnoth (via a text editor in this case). This method can be used, for example, by a machine learning framework.
<i> </i>
End the side 1 turn and move the side 2 leader (id = challenger) by editing (and saving!) file '~/add-ons/AI-demos/lua/manual_input.lua'. Note that there are no checks built in whether the move is actually meaningful or possible, this is really just a bare-bones proof of concept.
<i> </i>
This scenario can also be started from the commandline as a test scenario with 'wesnoth -d -tmanual_ai' or by using the test scenario hotkey in the title screen." # wmllint: no spellcheck
        [/message]

        [objectives]
            summary=_"Testing of manual interface to AI"
        [/objectives]
    [/event]

#ifndef TEST
[/scenario]
#else
# wmlindent: start ignoring
[/test]
# wmlindent: stop ignoring
#endif
add-ons/AI-demos/ais/ai_manual.cfg: The AI setup code. This could also be added directly in manual_ai.cfg, of course.

Code: Select all

#textdomain wesnoth-AI-demos

#ifndef AI_CA_GOTO
{core/macros/ai_candidate_actions.cfg}
#endif

[ai]
    id=manual_ai
    description=_"Multiplayer_AI^AI-demos: Manual AI"
    mp_rank=2
    [stage]
        id=main_loop
        name=ai_default_rca::candidate_action_evaluation_loop

        [candidate_action]
            engine=lua
            name=stats
            max_score=999990
            location="~add-ons/AI-demos/lua/ca_manual_stats.lua"
        [/candidate_action]
        [candidate_action]
            engine=lua
            name=manual_move
            max_score=999980
            location="~add-ons/AI-demos/lua/ca_manual_move.lua"
        [/candidate_action]
    [/stage]
[/ai]
add-ons/AI-demos/lua/ca_manual_stats.lua and add-ons/AI-demos/lua/ca_manual_move.lua: These are the two CAs. The former writes the game state output (just some very basic information about the units on the map in this example). The latter waits for the input and then executes the move. It also has a timeout built in, in case no input is received (in 10 seconds, in this example).

Code: Select all

----- CA: Stats at beginning of turn (max_score: 999990) -----
-- This will be blacklisted after first execution each turn

local ca_manual_stats = {}

function ca_manual_stats:evaluation()
    return 999990
end

function ca_manual_stats:execution(cfg, data)
    local tod = wesnoth.get_time_of_day()
    std_print('\n**** Manual AI *********************************************************')
    std_print('Beginning of Turn ' .. wesnoth.current.turn .. ' (' .. tod.name ..') stats')

    local units = wesnoth.get_units()
    for _,unit in ipairs(units) do
        std_print(string.format('%-25s %2d,%2d  %1d', unit.id, unit.x, unit.y, unit.side))
    end

    std_print('************************************************************************')

    local id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
    wml.variables.manual_ai_id = id
    wml.variables.manual_ai_x = x
    wml.variables.manual_ai_y = y
end

return ca_manual_stats
and

Code: Select all

----- CA: Manual move (max_score: 999980) -----

local ca_manual_move = {}

function ca_manual_move:evaluation()
    local start_time = wesnoth.get_time_stamp()
    local timeout_ms = 10000 -- milli-seconds
    --std_print('Start time:', start_time)

    local old_id = wml.variables.manual_ai_id
    local old_x = wml.variables.manual_ai_x
    local old_y = wml.variables.manual_ai_y
    local id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")

    while (id == old_id) and (x == old_x) and (y == old_y) and (wesnoth.get_time_stamp() < start_time + timeout_ms) do
        id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
    end

    if (id == old_id) and (x == old_x) and (y == old_y) then
        std_print('manual move CA has timed out')
        return 0
    else
        return 999980
    end
end

function ca_manual_move:execution(cfg, data)
    local id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
    std_print('move ' .. id .. ' --> ' .. x .. ',' .. y)

    local unit = wesnoth.get_unit(id)
    ai.move(unit, x, y)

    wml.variables.manual_ai_id = id
    wml.variables.manual_ai_x = x
    wml.variables.manual_ai_y = y
end

return ca_manual_move
add-ons/AI-demos/lua/manual_input.lua: This is the file to be modified by whatever external means one chooses. In this simple example, it contains the id and goal coordinates for the move of the unit.

Code: Select all

-- Edit the information below to make the AI move the unit
return 'challenger', 19, 21

User avatar
Pentarctagon
Project Manager
Posts: 4370
Joined: March 22nd, 2009, 10:50 pm
Location: Earth (occasionally)

Re: External AI control

Post by Pentarctagon »

Perhaps another potential option would be to write the gamestate out using PersistenceWML, convert it to JSON or XML with wmlparser3, and then use that.
99 little bugs in the code, 99 little bugs
take one down, patch it around
-2,147,483,648 little bugs in the code


User avatar
josteph
Developer
Posts: 741
Joined: August 19th, 2017, 6:58 pm

Re: External AI control

Post by josteph »

Something like this could be the core of a "Wesnoth in ncurses" implementation, too. Instead of the external program being a machine learning app, it would be a console app that lets an actual human player perform the next move.

User avatar
crowvine
Posts: 1
Joined: April 28th, 2020, 9:40 pm

Re: External AI control

Post by crowvine »

Howdy! I just came to the forum looking for something like this. I would love to experiment with AI design using the Wesnoth engine. The other game I am looking at is the much clunkier Pioneers (http://pio.sourceforge.net/) game, which is attractive because of its standalone pioneersai (http://pio.sourceforge.net/manual/ch04.html), which connects to a persistent server over the network, and could therefore be more easily replaced by a custom AI.

I will look more closely at your LUA Candidate Actions. It's not something with which I am familiar, but if it's faster than HTTP, all the better!

Post Reply