Code Monkey home page Code Monkey logo

factorio-bot's Introduction

Factorio Bot

This project tries to be a fair bot/AI for the game Factorio (version 0.16.51).

"Fair" means that the bot only uses information that is available to a human player. Better planning algorithms or map memory is fine, but being able to "see" yet-undiscovered parts of the map, or "looking through the fog of war" is not considered "fair".

The project is in a very early stage, in fact, it's not much more than a proof-of-concept that this can actually work.

Features right now:

  • we can read out the map / buildings on the map
  • most buildings are drawn, if you zoom in far enough
  • we can plan walking paths
  • we can actually walk :)
  • place entities
  • mine entities
  • craft recipes
  • inventory updates
  • action scheduler / task concept

Screenshots

map overview

zoomed in

Setup

Things are a bit fiddly right now. If you are on Linux, then there are various helper scripts for you:

./prepare.sh will unpack three factorio installations (server and two clients) and install the lua-part of the bot.

./launch.sh --start/--stop/--run {server,Nayru,Farore,offline} will start/stop the corresponding instance. --run will start, wait for ^C and then stop. offline is special in that it launches a single-player game that can only be used together with --bot-offline.

./launch.sh --bot will start the bot. You can also use --bot-offline for a read-only version that only operates on the data file, without the need of a running server instance.

If you're not on Linux, or if stuff doesn't work, read the internals below.

Usage

Once the bot is running, you will see a map. Scroll with drag-and-drop, zoom with mousewheel.

You can plan paths by first left-clicking the starting point, then right-clicking the destination. Your character's location (which should be the starting point) is denoted as a red dot on the map.

A path will show up in the map, and your Factorio player will start moving.

The p button switches between visualisation of the ore types and the ore-patch ids. s gives info about the current scheduler state, r recalculates the scheduler, t and y insert a high- or low-priority wood chopping task, respectively. i gives additional info about the entity under the cursor.

For a quick demo, press (in that order):

  • r: the bot will walk and start chopping wood in the center of the map
  • t, r: the bot will interrupt what it was doing and chop wood in the north west. (After finishing, it will return to the center chopping task)
  • y, r: the bot will not interrupt what it was doing and once finished, will start a low priority wood chopping task in the south east.

Bugs / Limitations

Walking will appear really "jumpy" in the Factorio client which is being "remote-controlled". A second player, following the remote-controlled one, will not see anything jumpy.

Path planning will hang for a long time if no path can be found.

Building / Requirements

The bot is written in C++, using the C++17 standard. Dependencies are Boost and FLTK-1.3.4 for the GUI. (On Ubuntu, run: apt-get install libboost-dev libfltk1.3-dev to get them)

The build system used is GNU Make. So just type make all and you should™ be done.

This will create two executables: rcon-client with the obvious job, and bot, which is the bot program.

Internals

How the communication between the bot and a factorio game instance works in detail is explained in this document.

Also, there's a detailed description of the task scheduler.

License

Copyright (c) 2017, 2018 Florian Jung ([email protected])

factorio-bot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License, version 3, as published by the Free Software Foundation.

factorio-bot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with factorio-bot. If not, see http://www.gnu.org/licenses/.

Exceptions

Note that some portions of the codebase are -- where explicitly noted in the source files -- additionally licensed under the terms of the MIT license.

See also

Related to this is my production flow optimizer project.

factorio-bot's People

Contributors

windfisch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

factorio-bot's Issues

coal refill manager

whenever the fuel is empty on some machines, a high-priority refill job should be scheduled

Inventory bookkeeping problems

When running af187e6 and adding an iron rig, then a coal rig, refilling coal and adding another iron rig, the bot fails to keep track of its inventory.
It thought that it had 14 iron-plate, but in fact had 0.

CraftingActions get deleted too early, no claim possible

When a CraftingAction has finished, it is deleted immediately in main.cpp. One frame later, the Inventory update arrives and can't find the owner. Therefore, it cannot add the appropriate claim, the crafted item becomes free-for-all.

Possible fix: let the Task store a pointer to these CraftingActions. Or maintain a actionid -> owner_id mapping, without the weak_ptr<PrimitiveAction> in between

Task::auto_craft_from() must depend on the item allocation

Any Task must carry a list of basic items that are permitted to be collected. (Later the scheduler should dynamically decide this, but not yet).

Task::auto_craft_from() must be called from within the Scheduler, after updating the item allocation.

Failing to join the game: too slow

the writeout_pictures() function in the lua mod takes too much time while reconstructing data.raw. This results in the client being unable to connect to the game, because it is too slow.

Workaround: click "reconnect".

Fill slack time with appropriate tasks

When a high prio task is scheduled with an ETA large enough to do some work, but too short to finish any work, then currently the bot will just idle.

It would be better to pick any runnable lower-prio task (that's based on goals, not actions) and execute that to fill the gap. Or to run a resource collector.

Ideally, we pick that task that minimizes the walking durations.

(No crafting can be done, because that high-prio task will occupy the crafting queue until it turns runnable)

need to distinguish between the planned and the actual facility level

When adding lots of facility upgrade tasks, and then submitting a higher-priority coal refiller in between, then the coal refiller will try to refill coal for all planned entities. But it should only refill the actual entities.

Possible fix: register a callback upon task completion to keep track of the actual entities on the map.

Generic facility planning

Should plan a facility from an input list and a desired output list. (e.g. "give me 4/sec green circuits using only iron and copper plates" or "give me 13/sec iron plate using iron ore")

Asynchronous pre-planning of routes in the background

WalkTo actions (or anything involving route planning, actually) currently plan their routes on start(), introducing a long delay if the distance is large.

Instead, these actions should be able to plan ahead, changing things as follows:

  1. WalkTo actions store their route instead of generating it on-the-fly.
  2. Their route is initially pre-populated with a naive straight line. It is intended to replaced very soon by the first update_route(Pos start) call.
  3. They offer an update_route() method that's intended to be executed from a different thread.
  4. update_route() will atomically / mutex-locked update the previous route, so at any point in time, there's a route that's more-or-less-valid.
  5. If at the begin of update_route()'s execution it is discovered that the action is already running, then route calculation takes the current player's position as its starting point.
  6. If at the end of update_route()'s execution it is discovered that the action is already running, the player is "pulled onto the route" with another short pathfinding call.

The pre-planning thread will iterate over all WalkTo's in tasks and over the implicit WalkTo's in between the tasks, and pre-plan them up to a limited time/waylength in the future.
Also, upon start() of a WalkTo action, it is replanned again, to account for last-minute changes.

Smarter items collector algorithm

This boils down to the Travelling Purchaser Problem. At least a very basic heuristic for this would still be better than the current algorithm, which just enumerates all chests around (0,0) or the player's position.

separate game driver, basic controlling logic and strategy

FactorioIO should be a pure callback-driven game driver.

The "basic" logic like Inventory and position tracking, map building, resource patch management and action management (including path planning) should be moved up a layer into one or more separate modules.
E.g. actions should auto-extrapolate the inventory, lag hiding should be performed here.

The scheduler resides another layer up, using actions for crafting, walking and adding "item claims".
Problem: in order to perform item claims, either the basic logic + lua mod need to know about these attributions -> they depend on "Task", or the scheduler needs to maintain these claims separately from the actual inventory -> difficult to avoid race conditions: the inventory update and the claim must be done in the same tick, before (or both after) executing the strategy tick()s

The strategy managers read from the world map and submit tasks to the scheduler

Task dependencies

Tasks should depend on other tasks. The scheduler should respect this.

Image loading cannot deal with multi-file images

Factorio 0.16 has some images that are described as something.png in the data.raw structure, but the actual files are split in something-1.png, something-2.png etc.

The current behaviour is to ignore these files completely. It might be good to actually handle this.

spurious crash in pathfinding

Only occurs sometimes, not really reproducible. Addresses did not resolve to source code lines.

Scheduler::recalculate()
=================================================================
==2832==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffdb4e53b58 at pc 0x563ae0c41ea5 bp 0x7ffdb4e52ff0 sp 0x7ffdb4e52fe8
READ of size 8 at 0x7ffdb4e53b58 thread T0
    #0 0x563ae0c41ea4  (/home/flo/factorio-bot/bot+0x86fea4)
    #1 0x563ae0c487d1  (/home/flo/factorio-bot/bot+0x8767d1)
    #2 0x563ae0c3b598  (/home/flo/factorio-bot/bot+0x869598)
    #3 0x563ae0c36c56  (/home/flo/factorio-bot/bot+0x864c56)
    #4 0x563ae0c3611d  (/home/flo/factorio-bot/bot+0x86411d)
    #5 0x563ae0dd380b  (/home/flo/factorio-bot/bot+0xa0180b)
    #6 0x563ae0dad69c  (/home/flo/factorio-bot/bot+0x9db69c)
    #7 0x563ae0d99d99  (/home/flo/factorio-bot/bot+0x9c7d99)
    #8 0x563ae0d97d4a  (/home/flo/factorio-bot/bot+0x9c5d4a)
    #9 0x563ae0f172df  (/home/flo/factorio-bot/bot+0xb452df)
    #10 0x7f440e62f222  (/usr/lib/libc.so.6+0x24222)
    #11 0x563ae09facbd  (/home/flo/factorio-bot/bot+0x628cbd)

Address 0x7ffdb4e53b58 is located in stack of thread T0 at offset 888 in frame
    #0 0x563ae0c3626f  (/home/flo/factorio-bot/bot+0x86426f)

  This frame has 38 object(s):
    [32, 40) 'point.i'
    [64, 72) 'ref.tmp8.i'
    [96, 480) 'log' (line 88)
    [544, 576) 'ref.tmp' (line 88)
    [608, 609) 'ref.tmp1' (line 88)
    [624, 640) 'view_area' (line 94)
    [656, 760) 'view' (line 97)
    [800, 808) 'ref.tmp43' (line 97)
    [832, 872) 'openlist' (line 102) <== Memory access at offset 888 overflows this variable
    [912, 913) 'ref.tmp56' (line 102) <== Memory access at offset 888 underflows this variable
    [928, 984) 'needs_cleanup' (line 104)
    [1024, 1040) 'ref.tmp69' (line 106)
    [1056, 1064) 'ref.tmp94' (line 107)
    [1088, 1104) 'current' (line 112)
    [1120, 1136) 'agg.tmp125'
    [1152, 1168) 'agg.tmp135'
    [1184, 1192) 'p' (line 123)
    [1216, 1256) 'agg.tmp176'
    [1296, 1336) 'agg.tmp179'
    [1376, 1416) '__begin3' (line 136)
    [1456, 1496) '__end3' (line 136)
    [1536, 1544) 'pos197' (line 136)
    [1568, 1600) 'ref.tmp205' (line 136)
    [1632, 1696) 'steps' (line 147)
    [1728, 1736) 'successor' (line 152)
    [1760, 1768) 'ref.tmp625' (line 184)
    [1792, 1800) 'ref.tmp646' (line 187)
    [1824, 1840) 'ref.tmp647' (line 187)
    [1856, 1864) 'ref.tmp675' (line 191)
    [1888, 1904) 'ref.tmp690' (line 193)
    [1920, 1936) 'ref.tmp706' (line 197)
    [1952, 1960) 'ref.tmp725' (line 198)
    [1984, 2024) '__begin1' (line 206)
    [2064, 2104) '__end1' (line 206)
    [2144, 2152) 'ref.tmp784' (line 208)
    [2176, 2184) 'ref.tmp838' (line 213)
    [2208, 2216) 'ref.tmp841' (line 213)
    [2240, 2256) 'ref.tmp842' (line 213)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/flo/factorio-bot/bot+0x86fea4) 
Shadow bytes around the buggy address:
  0x1000369c2710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000369c2720: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000369c2730: 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2
  0x1000369c2740: f8 f8 f8 f8 f2 f2 f2 f2 f8 f2 00 00 f2 f2 00 00
  0x1000369c2750: 00 00 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2
=>0x1000369c2760: f8 f2 f2 f2 00 00 00 00 00 f2 f2[f2]f2 f2 f8 f2
  0x1000369c2770: 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f8 f8 f2 f2
  0x1000369c2780: f8 f2 f2 f2 00 00 f2 f2 00 00 f2 f2 00 00 f2 f2
  0x1000369c2790: f8 f2 f2 f2 00 00 00 00 00 f2 f2 f2 f2 f2 00 00
  0x1000369c27a0: 00 00 00 f2 f2 f2 f2 f2 f8 f8 f8 f8 f8 f2 f2 f2
  0x1000369c27b0: f2 f2 f8 f8 f8 f8 f8 f2 f2 f2 f2 f2 f8 f2 f2 f2
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==2832==ABORTING

crash when a task with only fulfilled goals is scheduled

bot: scheduler.cpp:46: void sched::Task::update_actions_from_goals(FactorioGame *, int): Assertion `first_pos.has_value()' failed.

Program received signal SIGABRT, Aborted.

Fulfilled tasks should be removed from the scheduler completely.

Resource collector task may be executed twice

The last item the resource collector task mines isn't added to the inventory immediately, but with one frame delay. But the scheduler recalculation occurs immediately, causing the item collector to be repeated.

Instead, actions should only be marked as completed once their effects came into effect.

WorldMap caching issue

WorldMap currently stores its Chunks column-wise in the memory. However, data is accessed usually row-wise (especially in the GUI drawing functions / update_imgbuf).
-> WorldMap should store data row-wise.

Experiments with GCC6, -O2 for GUI and -O1 for the rest have shown that the drawing speed is doubled.

Tasks start to execute even if they're not runnable yet

If a task is eventually runnable, it is scheduled with its crafting ETA. However currently, the first scheduled task will be executed, regardless of whether it's actually runnable yet.

The WalkTo-Action may already be executed, but the task itself must wait until it became runnable.

Itemcollector always removes from the CHEST inventory

When taking stuff from furnaces, it should be the product inventory!

Possible fixes:

  1. Make the bot aware of the different inventories, so the right one can be selected
  2. hard-code the inventory based on the entity type / item type (e.g.: wood from chest -> chest inventory. wood from furnace: fuel inventory. iron-plate from furnace: ???)
  3. Ignore the problem and make the decision in the lua mod.

Best would be 1.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.