├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── action.cpp
├── action.hpp
├── area.cpp
├── area.hpp
├── clock.hpp
├── constants.h
├── defines.cpp
├── defines.h
├── doc
├── factorio_comm.md
└── scheduler.md
├── entity.cpp
├── entity.h
├── eval
└── viewports
│ ├── README.md
│ ├── dumb_O2.patch
│ ├── dyn_O2.patch
│ ├── dyn_noopt.patch
│ ├── norm_O2.patch
│ ├── norm_noopt.patch
│ └── output1.txt.bz2
├── factorio_io.cpp
├── factorio_io.h
├── filter.sh
├── goal.cpp
├── goal.hpp
├── graphics_definitions.h
├── gui
├── gui.cpp
└── gui.h
├── inventory.cpp
├── inventory.hpp
├── item.h
├── item_storage.h
├── launch.sh
├── logging.cpp
├── logging.hpp
├── luamod
├── Windfisch_0.0.1
│ ├── control.lua
│ ├── data-final-fixes.lua
│ ├── data.lua
│ ├── info.json
│ └── prototypes
│ │ └── .emptydir
└── server-settings.json
├── main.cpp
├── mine_planning.cpp
├── mine_planning.h
├── mk_compile_commands_json.bash
├── multivariant.hpp
├── pathfinding.cpp
├── pathfinding.hpp
├── player.h
├── pos.hpp
├── prepare.sh
├── rcon-client.cpp
├── rcon.cpp
├── rcon.h
├── recipe.h
├── resource.hpp
├── safe_cast.hpp
├── sched.txt
├── scheduler.cpp
├── scheduler.hpp
├── screenshots
├── overview.png
└── zoomed.png
├── split.hpp
├── test
├── .gitignore
├── claim.cpp
├── scheduler.cpp
├── scheduler.run.desired
├── worldlist.cpp
└── worldlist.run.desired
├── unused.h
├── util.hpp
├── worldlist.hpp
└── worldmap.hpp
/.gitignore:
--------------------------------------------------------------------------------
1 | .dummy.mk
2 | depend
3 | *.d
4 | *.o
5 | core
6 | gmon.out
7 | rcon-client
8 | botconfig.conf
9 | *.swp
10 | config.mk
11 | bot
12 | compile_commands.json
13 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | include config.mk
2 |
3 | EXE=bot
4 | COMMONOBJECTS=factorio_io.o rcon.o area.o pathfinding.o defines.o action.o mine_planning.o inventory.o scheduler.o goal.o entity.o gui/gui.o logging.o # objects used for $(EXE)
5 |
6 | ALLTESTS=test/worldlist test/scheduler
7 |
8 | # all objects, including those for other targets (i.e. rcon-client)
9 | ALLOBJECTS=$(COMMONOBJECTS) main.o rcon-client.o $(addsuffix .o,$(ALLTESTS))
10 | DEBUG=1
11 |
12 |
13 | MODNAME=Windfisch_0.0.1
14 | MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes/.emptydir)
15 |
16 |
17 |
18 | DEBUGFLAGS = -O2 -g -D_GLIBCXX_DEBUG -fsanitize=undefined,address -fno-omit-frame-pointer
19 | FASTFLAGS = -O2 -g
20 | CXXFLAGS_BASE = -std=c++17
21 | CFLAGS_BASE = -std=c99
22 |
23 | GUIFLAGS = -O2
24 |
25 | COMPILER ?= GCC
26 | ifeq ($(COMPILER),GCC)
27 | CC=gcc
28 | CXX=g++
29 | WARNFLAGS=-Wall -Wextra -pedantic
30 | WARNFLAGS+=-Werror=return-type -Werror=implicit-fallthrough
31 | GUIFLAGS +=
32 | else
33 | CC=clang
34 | CXX=clang++
35 | WARNFLAGS=-Weverything -pedantic -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++98-c++11-compat -Wno-sign-conversion -Wno-sign-compare -Wno-padded -Wno-exit-time-destructors -Wno-global-constructors -Wno-weak-vtables -Wno-switch-enum -Wswitch -Wno-double-promotion -Wno-documentation-unknown-command -Wno-covered-switch-default
36 | WARNFLAGS+=-Werror=return-type -Werror=header-hygiene -Werror=implicit-fallthrough
37 | # -Wswitch-enum complains, even if there's a default: label. -Wswitch does not.
38 | GUIFLAGS +=
39 | endif
40 |
41 |
42 | DEBUG ?= 1
43 | ifeq ($(DEBUG),1)
44 | FLAGS += $(DEBUGFLAGS)
45 | else
46 | FLAGS += $(FASTFLAGS)
47 | endif
48 |
49 | FLAGS += $(WARNFLAGS)
50 | CFLAGS = $(shell fltk-config --cflags --use-images) $(CFLAGS_BASE) $(FLAGS)
51 | CXXFLAGS = $(shell fltk-config --cxxflags --use-images) $(CXXFLAGS_BASE) $(FLAGS)
52 | LINK=$(CXX)
53 | LINKFLAGS=$(CXXFLAGS)
54 | LIBS=$(shell fltk-config --ldflags --use-images)
55 |
56 | MODDESTS=$(MODSRCS:luamod/%=$(FACTORIODIR)/mods/%)
57 |
58 | # Pseudotargets
59 | .PHONY: all clean run info mod help test build_tests run_tests
60 |
61 | all: compile_commands.json $(EXE) rcon-client
62 |
63 | clean:
64 | rm -f $(EXE) $(ALLOBJECTS) $(ALLOBJECTS:.o=.d)
65 | distclean: clean
66 | rm -f depend compile_commands.json
67 |
68 | test: build_tests run_tests
69 | build_tests: $(ALLTESTS)
70 | run_tests: $(addsuffix .run,$(ALLTESTS))
71 |
72 | test/%.run: test/%
73 | @./$< > $@.out
74 | @if ! diff -u $@.desired $@.out; then echo "TEST $@ FAILED, see above"; false; else echo "TEST $@ PASSED"; true; fi
75 |
76 | test/worldlist: $(COMMONOBJECTS) test/worldlist.o
77 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
78 |
79 | test/scheduler: $(COMMONOBJECTS) test/scheduler.o scheduler.o
80 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
81 |
82 |
83 | help:
84 | @echo "Targets:"
85 | @echo " all -> build the program"
86 | @echo " run -> run the program, using the datafile in the factorio installation (see Configuration below)"
87 | @echo " Note: this will NOT install the mod or (re)create the datafile. Do this manually by"
88 | @echo " running 'make datafile' before."
89 | @echo " mod -> install the lua mod into the factorio installation (see Configuration below)"
90 | @echo " datafile -> update the lua mod and re-create its output datafile"
91 | @echo " clean -> all generated files (including depend. except config.mk)"
92 | @echo " info -> show an overview of the CFLAGS used"
93 | @echo " help -> show this help"
94 | @echo
95 | @echo "Configuration:"
96 | @echo " Edit the 'config.mk' file and adjust the factorio path."
97 | @echo " This path will be used by the 'run', 'mod' and 'outfile' targets"
98 |
99 |
100 | info:
101 | @echo DEBUGFLAGS = $(DEBUGFLAGS)
102 | @echo FASTFLAGS = $(FASTFLAGS)
103 | @echo DEBUG = $(DEBUG)
104 | @echo CXXFLAGS_BASE = $(CXXFLAGS_BASE)
105 | @echo CXXFLAGS = $(CXXFLAGS)
106 | @echo LDFLAGS= $(LDFLAGS)
107 |
108 | run: $(EXE)
109 | ./$(EXE)
110 |
111 | mod: $(MODDESTS)
112 |
113 | DATAFILE=$(FACTORIODIR)/script-output/output1.txt
114 | SAVEGAME=$(FACTORIODIR)/factorio-bot.zip
115 | SERVERSETTINGS=luamod/server-settings.json
116 |
117 | datafile: $(DATAFILE)
118 | echo 'made target $(DATAFILE)'
119 |
120 | rm_datafile:
121 | rm -f $(DATAFILE)
122 | savegame: $(SAVEGAME)
123 |
124 | $(SAVEGAME):
125 | $(FACTORIODIR)/bin/x64/factorio --create $@
126 |
127 | .PRECIOUS: $(DATAFILE)
128 |
129 | $(DATAFILE): $(MODDESTS) $(SAVEGAME) $(SERVERSETTINGS)
130 | rm -f $(DATAFILE)
131 | cp $(SAVEGAME) $(SAVEGAME:.zip=.tmp.zip)
132 | ( sleep 5; ./rcon-client localhost 1234 trivial_password '/c remote.call("windfisch","whoami","server")'; sleep 1; ./rcon-client localhost 1234 trivial_password '/c remote.call("windfisch","whoami","server")' ) &
133 | -$(FACTORIODIR)/bin/x64/factorio --start-server $(SAVEGAME:.zip=.tmp.zip) --server-settings $(SERVERSETTINGS) --rcon-port 1234 --rcon-password trivial_password
134 | echo
135 | echo made $(DATAFILE)
136 |
137 | # Build system
138 |
139 | .PRECIOUS: config.mk
140 | config.mk:
141 | echo 'FACTORIODIR=/path/to/factorio' > $@
142 | echo 'COMPILER=GCC' >> $@
143 | echo '# possible values: GCC, clang' >> $@
144 |
145 | $(FACTORIODIR)/mods/$(MODNAME)/%: luamod/$(MODNAME)/%
146 | mkdir -p $(dir $@)
147 | cp -a $< $@
148 |
149 |
150 |
151 | include depend
152 |
153 | depend: $(ALLOBJECTS:.o=.d)
154 | cat $^ > $@
155 |
156 | %.d: %.cpp
157 | $(CXX) $(CXXFLAGS) -MM -MT $(@:.d=.o) $< -o $@
158 |
159 | gui/%.d: gui/%.cpp
160 | $(CXX) $(CXXFLAGS) -MM -MT $(@:.d=.o) $(GUIFLAGS) $< -o $@
161 |
162 | gui/%.o: gui/%.cpp
163 | $(CXX) $(CXXFLAGS) $(GUIFLAGS) -c $< -o $@
164 |
165 | %.o: %.cpp
166 | $(CXX) $(CXXFLAGS) -c $< -o $@
167 |
168 |
169 | $(EXE): $(COMMONOBJECTS) main.o
170 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
171 | rcon-client: rcon-client.o rcon.o logging.o
172 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
173 |
174 | compile_commands.json:
175 | bash mk_compile_commands_json.bash > $@
176 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Factorio Bot
2 | ============
3 |
4 | This project tries to be a **fair** bot/AI for the game
5 | [Factorio](https://www.factorio.com) (version 0.16.51).
6 |
7 | "Fair" means that the bot only uses information that is available to a human
8 | player. Better planning algorithms or map memory is fine, but being able to
9 | "see" yet-undiscovered parts of the map, or "looking through the fog of war"
10 | is not considered "fair".
11 |
12 | The project is in a **very early** stage, in fact, it's not much more than a
13 | proof-of-concept that this can actually work.
14 |
15 | Features right now:
16 |
17 | * we can read out the map / buildings on the map
18 | * most buildings are drawn, if you zoom in far enough
19 | * we can plan walking paths
20 | * we can actually walk :)
21 | * place entities
22 | * mine entities
23 | * craft recipes
24 | * inventory updates
25 | * action scheduler / task concept
26 |
27 |
28 | Screenshots
29 | -----------
30 |
31 | 
32 |
33 | 
34 |
35 |
36 | Setup
37 | ------
38 |
39 | Things are a bit fiddly right now. If you are on Linux, then there are various
40 | helper scripts for you:
41 |
42 | `./prepare.sh` will unpack three factorio installations (server and two
43 | clients) and install the lua-part of the bot.
44 |
45 | `./launch.sh --start/--stop/--run {server,Nayru,Farore,offline}` will start/stop the
46 | corresponding instance. `--run` will start, wait for `^C` and then stop. `offline` is
47 | special in that it launches a single-player game that can only be used together with
48 | `--bot-offline`.
49 |
50 | `./launch.sh --bot` will start the bot. You can also use `--bot-offline` for a
51 | read-only version that only operates on the data file, without the need of a
52 | running server instance.
53 |
54 | If you're not on Linux, or if stuff doesn't work, read the internals below.
55 |
56 |
57 | Usage
58 | -----
59 |
60 | Once the bot is running, you will see a map. Scroll with drag-and-drop, zoom
61 | with mousewheel.
62 |
63 | You can plan paths by first left-clicking the starting point, then
64 | right-clicking the destination. Your character's location (which should
65 | be the starting point) is denoted as a red dot on the map.
66 |
67 | A path will show up in the map, and your Factorio player will start moving.
68 |
69 | The `p` button switches between visualisation of the ore types and the ore-patch
70 | ids. `s` gives info about the current scheduler state, `r` recalculates the
71 | scheduler, `t` and `y` insert a high- or low-priority wood chopping task,
72 | respectively. `i` gives additional info about the entity under the cursor.
73 |
74 | For a quick demo, press (in that order):
75 |
76 | - `r`: the bot will walk and start chopping wood in the center of the map
77 | - `t`, `r`: the bot will interrupt what it was doing and chop wood in the
78 | north west. (After finishing, it will return to the center chopping task)
79 | - `y`, `r`: the bot will not interrupt what it was doing and once finished,
80 | will start a low priority wood chopping task in the south east.
81 |
82 | Bugs / Limitations
83 | ------------------
84 |
85 | Walking will appear really "jumpy" in the Factorio client which is being
86 | "remote-controlled". A second player, following the remote-controlled one,
87 | will not see anything jumpy.
88 |
89 | Path planning will hang for a long time if no path can be found.
90 |
91 | Building / Requirements
92 | -----------------------
93 |
94 | The bot is written in C++, using the C++17 standard. Dependencies are
95 | [Boost](http://boost.org) and [FLTK](http://fltk.org)-1.3.4 for the GUI. (On Ubuntu,
96 | run: `apt-get install libboost-dev libfltk1.3-dev` to get them)
97 |
98 | The build system used is GNU Make. So just type `make all` and you should™ be
99 | done.
100 |
101 | This will create two executables: `rcon-client` with the obvious job, and
102 | `bot`, which is the bot program.
103 |
104 | Internals
105 | ---------
106 |
107 | How the communication between the bot and a factorio game instance works in detail
108 | is explained [in this document](doc/factorio_comm.md).
109 |
110 | Also, there's a detailed description of the [task scheduler](doc/scheduler.md).
111 |
112 |
113 | License
114 | =======
115 |
116 | Copyright (c) 2017, 2018 Florian Jung (flo@windfisch.org)
117 |
118 | *factorio-bot* is free software: you can redistribute it and/or
119 | modify it under the terms of the **GNU General Public License,
120 | version 3**, as published by the Free Software Foundation.
121 |
122 | *factorio-bot* is distributed in the hope that it will be useful,
123 | but WITHOUT ANY WARRANTY; without even the implied warranty of
124 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
125 | GNU General Public License for more details.
126 |
127 | You should have received a [copy of the GNU General Public License](LICENSE)
128 | along with factorio-bot. If not, see .
129 |
130 | Exceptions
131 | ----------
132 |
133 | Note that some portions of the codebase are -- where explicitly noted in the
134 | source files -- additionally licensed under the terms of the MIT license.
135 |
136 |
137 |
138 | See also
139 | ========
140 |
141 | Related to this is my [production flow optimizer project](https://github.com/Windfisch/production-flow).
142 |
--------------------------------------------------------------------------------
/action.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #include "action.hpp"
20 | #include "pathfinding.hpp"
21 | #include "constants.h"
22 | #include
23 | #include
24 | #include "factorio_io.h"
25 | #include
26 | #include "logging.hpp"
27 |
28 | using namespace std;
29 |
30 | namespace action {
31 | Registry registry;
32 |
33 | void Registry::start_action(const std::shared_ptr& action)
34 | {
35 | Logger log("action_registry");
36 | if (auto primitive_action = std::dynamic_pointer_cast(action))
37 | {
38 | log << "inserting action #" << primitive_action->id << " into the registry" << endl;
39 | (*this)[primitive_action->id] = primitive_action;
40 | }
41 | action->start();
42 | }
43 |
44 | void Registry::cleanup()
45 | {
46 | for (iterator iter = begin(); iter != end();)
47 | {
48 | if (iter->second.expired())
49 | iter = erase(iter);
50 | else
51 | ++iter;
52 | }
53 | }
54 |
55 | shared_ptr Registry::get(int id)
56 | {
57 | auto iter = find(id);
58 | if (iter == end())
59 | return nullptr;
60 | else
61 | return iter->second.lock();
62 | }
63 |
64 | int PrimitiveAction::id_counter;
65 |
66 | void PrimitiveAction::start()
67 | {
68 | Logger log("action");
69 |
70 | execute_impl();
71 | item_balance_t negative_balance;
72 | item_balance_t balance = inventory_balance_on_launch();
73 | std::copy_if (balance.begin(), balance.end(), std::inserter(negative_balance, negative_balance.end()), [](auto elem){return elem.second < 0;} );
74 |
75 | game->players[player].inventory.apply(negative_balance, owner);
76 | log << "Launched action " << str() << ". Extrapolated inventory is now" << endl;
77 | game->players[player].inventory.dump();
78 | }
79 |
80 | void WalkTo::start()
81 | {
82 | std::vector waypoints = a_star(game->players[player].position.to_int(), destination, game->walk_map, allowed_distance, min_distance);
83 | subactions.push_back(unique_ptr(new WalkWaypoints(game,player,nullopt, waypoints)));
84 |
85 | registry.start_action(subactions[0]);
86 | }
87 |
88 | void WalkWaypoints::execute_impl()
89 | {
90 | Logger log("verbose");
91 | log << "WalkWaypoints from " << waypoints[0].str() << " to " << waypoints.back().str() << endl;
92 | game->set_waypoints(id, player, waypoints);
93 | }
94 |
95 | void MineObject::execute_impl()
96 | {
97 | game->set_mining_target(id, player, obj);
98 | }
99 |
100 | void MineObject::abort()
101 | {
102 | game->unset_mining_target(player);
103 | finished = true;
104 | }
105 |
106 | void CraftRecipe::execute_impl()
107 | {
108 | game->start_crafting(id, player, recipe->name, count);
109 | }
110 |
111 | void PlaceEntity::execute_impl()
112 | {
113 | game->place_entity(player, item->name, entity.pos, entity.direction);
114 | game->register_pending_entity(game->get_tick() + 60 /* FIXME MAGIC NUMBER */, entity);
115 | finished = true;
116 | }
117 |
118 | void PutToInventory::execute_impl()
119 | {
120 | game->insert_to_inventory(player, item->name, amount, entity, inventory_type);
121 | finished = true;
122 | }
123 |
124 | void TakeFromInventory::execute_impl()
125 | {
126 | game->remove_from_inventory(player, item->name, amount, entity, inventory_type);
127 | finished = true;
128 | }
129 |
130 | std::pair WalkTo::walk_result(Pos current_position) const
131 | {
132 | // TODO FIXME: if we've a detailed path, return that one
133 | return pair(destination.center(), chrono::milliseconds( int(1000*(current_position - destination.center()).len() / WALKING_SPEED) ));
134 | }
135 |
136 | std::pair WalkWaypoints::walk_result(Pos current_position) const
137 | {
138 | if (waypoints.empty())
139 | return pair(current_position, Clock::duration(0));
140 |
141 | float seconds = 0.f;
142 | for (size_t i = 0; i < waypoints.size() - 1; i++)
143 | {
144 | const Pos& from = waypoints[i];
145 | const Pos& to = waypoints[i+1];
146 | seconds += float((from-to).len() / WALKING_SPEED);
147 | }
148 |
149 | return pair(waypoints.back(), chrono::milliseconds(int(1000*seconds)));
150 | }
151 |
152 | item_balance_t MineObject::inventory_balance() const
153 | {
154 | return obj.proto->mine_results;
155 | }
156 |
157 | item_balance_t CompoundAction::inventory_balance() const
158 | {
159 | item_balance_t result;
160 | for (const auto& action : subactions)
161 | {
162 | for (const auto& [item,amount] : action->inventory_balance())
163 | result[item] += amount;
164 | }
165 | return result;
166 | }
167 |
168 | item_balance_t CraftRecipe::inventory_balance_on_launch() const
169 | {
170 | item_balance_t result;
171 | for (const auto& ingredient : recipe->ingredients)
172 | result[ingredient.item] -= ingredient.amount;
173 | return result;
174 | }
175 |
176 | string CompoundAction::str() const
177 | {
178 | string result = "CompoundAction{";
179 | for (size_t i=0; istr();
184 | }
185 | result+="}";
186 | return result;
187 | }
188 |
189 | string WalkTo::str() const
190 | {
191 | return "WalkTo("+destination.str()+"±"+to_string(allowed_distance)+")";
192 | }
193 |
194 | string WalkWaypoints::str() const
195 | {
196 | return "WalkWaypoints(" + join_string(waypoints, [](Pos p) { return p.str(); }) + ")";
197 | }
198 |
199 | string MineObject::str() const
200 | {
201 | return "MineObject(" + obj.str() + ")";
202 | }
203 |
204 | string PlaceEntity::str() const
205 | {
206 | return "PlaceEntity(" + item->name + " -> " + entity.str() + ")";
207 | }
208 |
209 | string PutToInventory::str() const
210 | {
211 | return "PutToInventory(" + to_string(amount) + "x " + item->name + " -> " + entity.str() + ")";
212 | }
213 |
214 | string TakeFromInventory::str() const
215 | {
216 | return "TakeFromInventory(" + to_string(amount) + "x " + item->name + " <- " + entity.str() + ")";
217 | }
218 |
219 | string CraftRecipe::str() const
220 | {
221 | return "CraftRecipe(" + to_string(count) + "x " + recipe->name + ")";
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/action.hpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #pragma once
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include "pos.hpp"
28 | #include "entity.h"
29 | #include "resource.hpp"
30 | #include "unused.h"
31 | #include "defines.h"
32 | #include "item.h"
33 | #include "clock.hpp"
34 |
35 | class FactorioGame;
36 |
37 | namespace action
38 | {
39 | struct PrimitiveAction;
40 | struct ActionBase;
41 |
42 | using action_id_t = int;
43 |
44 | struct Registry : public std::unordered_map>
45 | {
46 | void start_action(const std::shared_ptr& action);
47 | void cleanup();
48 | std::shared_ptr get(action_id_t id);
49 | };
50 | extern Registry registry; // FIXME this should be in FactorioGame
51 |
52 | struct ActionBase
53 | {
54 | friend struct Registry;
55 |
56 | virtual bool is_finished() const = 0;
57 | private: virtual void start() = 0; public: // FIXME ugly
58 | virtual void tick() = 0;
59 | virtual void abort() {}
60 | virtual void on_mined_item(std::string type, int count) { UNUSED(type); UNUSED(count); }
61 | virtual ~ActionBase() = default;
62 | virtual std::string str() const = 0;
63 |
64 | virtual std::optional first_pos() const { return std::nullopt; }
65 |
66 | /** returns the position of the player after having executed this action, and the time required
67 | * for doing so, under the assumption that we're at current_position now. */
68 | virtual std::pair walk_result(Pos current_position) const = 0;
69 |
70 | /** Inventory balance after having fully executed this action.
71 | * returns a map of (item, balance) pairs, where a positive balance signifies
72 | * receiving this item, negative means consuming the item */
73 | virtual item_balance_t inventory_balance() const = 0;
74 |
75 | /** Inventory balance when launching this action. E.g. for CraftAction,
76 | * this returns negative balance for the ingredients, but does not report
77 | * the products yet (because they won't be added immediately at launch)
78 | *
79 | * `tick` needs to be the timestamp of the inventory to extrapolate. */
80 | virtual item_balance_t extrapolate_inventory(int tick) const = 0;
81 | };
82 |
83 | struct CompoundAction : public ActionBase
84 | {
85 | std::vector< std::shared_ptr > subactions;
86 | size_t current_subaction = 0;
87 |
88 | CompoundAction() {}
89 |
90 | std::string str() const;
91 |
92 | std::optional first_pos() const
93 | {
94 | for (const auto& action : subactions)
95 | {
96 | if (std::optional pos = action->first_pos(); pos.has_value())
97 | return pos;
98 | }
99 | return std::nullopt;
100 | }
101 |
102 | void tick()
103 | {
104 | if (!is_finished())
105 | {
106 | subactions[current_subaction]->tick();
107 |
108 | if (subactions[current_subaction]->is_finished())
109 | {
110 | current_subaction++;
111 |
112 | if (!is_finished())
113 | registry.start_action(subactions[current_subaction]);
114 | }
115 | }
116 | }
117 |
118 | bool is_finished() const
119 | {
120 | return subactions.size() == current_subaction;
121 | }
122 |
123 | private: void start() // FIXME ugly
124 |
125 | {
126 | if (!subactions.empty())
127 | registry.start_action(subactions[0]);
128 | }
129 | public:
130 |
131 | // dispatch events
132 | void on_mined_item(std::string type, int count)
133 | {
134 | // FIXME shouldn't this be subaction[current_subaction] only?
135 | for (auto& subaction : subactions)
136 | subaction->on_mined_item(type, count);
137 | }
138 |
139 | std::pair walk_result(Pos current_position) const
140 | {
141 | Pos pos = current_position;
142 | Clock::duration sum(0);
143 | for (const auto& action : subactions)
144 | {
145 | auto [pos_after, duration] = action->walk_result(pos);
146 | assert(duration.count() >= 0);
147 | pos = pos_after;
148 | sum += duration;
149 | assert(sum.count() >= 0);
150 | }
151 | return std::pair(pos,sum);
152 | }
153 |
154 | item_balance_t inventory_balance() const;
155 | item_balance_t extrapolate_inventory(int tick) const
156 | {
157 | if (is_finished())
158 | return {};
159 | else
160 | return subactions[current_subaction]->extrapolate_inventory(tick);
161 | }
162 | };
163 |
164 | struct WalkTo : public CompoundAction
165 | {
166 | Area_f destination;
167 | double allowed_distance;
168 | double min_distance;
169 |
170 | FactorioGame* game;
171 | int player;
172 |
173 | std::string str() const;
174 |
175 | std::optional first_pos() const
176 | {
177 | return destination.center();
178 | }
179 |
180 | WalkTo(FactorioGame* game_, int player_, Area_f destination_, double allowed_distance_ = 1., double min_distance_ = 0.) { game = game_; player = player_; destination = destination_; allowed_distance = allowed_distance_; min_distance = min_distance_; }
181 | [[deprecated]] WalkTo(FactorioGame* game_, int player_, Pos destination_, double allowed_distance_ = 1.) : WalkTo(game_, player_, Area_f(destination_,destination_), allowed_distance_) {}
182 | private: void start(); public: // FIXME ugly
183 |
184 |
185 | std::pair walk_result(Pos current_position) const; // TODO FIXME implement
186 | };
187 |
188 | // TODO: primitive action list
189 |
190 | struct PrimitiveAction : public ActionBase
191 | {
192 | int player;
193 | FactorioGame* game;
194 | std::optional owner;
195 |
196 | static action_id_t id_counter;
197 | bool finished = false;
198 |
199 | action_id_t id;
200 | PrimitiveAction(FactorioGame* game_, int player_, std::optional owner_) { game = game_; player = player_; id = id_counter++; owner = owner_;}
201 |
202 | bool is_finished() const
203 | {
204 | return finished;
205 | }
206 |
207 | private: void start(); public: // FIXME ugly
208 |
209 |
210 | void tick() {}
211 |
212 | int confirmed_tick = 0; // TODO FIXME set properly!
213 |
214 | virtual item_balance_t inventory_balance_on_launch() const = 0;
215 |
216 | item_balance_t extrapolate_inventory(int tick) const
217 | {
218 | if (confirmed_tick && tick >= confirmed_tick)
219 | return {};
220 | else
221 | return inventory_balance_on_launch();
222 | }
223 |
224 | private: virtual void execute_impl() = 0;
225 | };
226 |
227 | struct WalkWaypoints : public PrimitiveAction
228 | {
229 | WalkWaypoints(FactorioGame* game, int player, std::optional owner, const std::vector& waypoints_) : PrimitiveAction(game,player,owner), waypoints(waypoints_) {}
230 | std::vector waypoints;
231 |
232 | std::string str() const;
233 |
234 | std::optional first_pos() const
235 | {
236 | if (waypoints.empty()) return std::nullopt;
237 | else return waypoints[0];
238 | }
239 |
240 | item_balance_t inventory_balance() const { return {}; }
241 | item_balance_t inventory_balance_on_launch() const { return {}; }
242 |
243 | std::pair walk_result(Pos current_position) const;
244 |
245 | private: void execute_impl();
246 | };
247 |
248 | struct MineObject : public PrimitiveAction // TODO amount (default: 0, means "mine it until gone")
249 | {
250 | MineObject(FactorioGame* game, int player, std::optional owner, Entity obj_) : PrimitiveAction(game,player,owner), obj(obj_) {}
251 | Entity obj;
252 |
253 | std::string str() const;
254 |
255 | item_balance_t inventory_balance() const;
256 | item_balance_t inventory_balance_on_launch() const { return {}; }
257 |
258 | std::pair walk_result(Pos current_position) const
259 | {
260 | return std::pair(current_position, std::chrono::seconds(1));
261 | }
262 |
263 | private: void execute_impl();
264 | private: void abort();
265 | };
266 |
267 | struct PlaceEntity : public PrimitiveAction
268 | {
269 | PlaceEntity(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, const Entity& entity_) : PrimitiveAction(game,player,owner), entity(entity_), item(item_) {}
270 | [[deprecated]] PlaceEntity(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, Pos_f pos_, dir4_t direction_=NORTH) : PrimitiveAction(game,player,owner), entity(pos_, item_->place_result, direction_), item(item_) {}
271 |
272 | Entity entity;
273 | const ItemPrototype* item;
274 |
275 | std::string str() const;
276 |
277 | item_balance_t inventory_balance() const { return {{item, -1}}; }
278 | item_balance_t inventory_balance_on_launch() const { return inventory_balance(); }
279 |
280 | std::pair walk_result(Pos current_position) const
281 | {
282 | return std::pair(current_position, std::chrono::seconds(1));
283 | }
284 |
285 | private: void execute_impl();
286 | };
287 |
288 | struct PutToInventory : public PrimitiveAction
289 | {
290 | const ItemPrototype* item;
291 | int amount;
292 | Entity entity;
293 | inventory_t inventory_type;
294 |
295 | std::string str() const;
296 |
297 | PutToInventory(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, int amount_, Entity entity_, inventory_t inventory_type_) :
298 | PrimitiveAction(game,player,owner), item(item_), amount(amount_), entity(entity_), inventory_type(inventory_type_) { }
299 |
300 | item_balance_t inventory_balance() const { return {{item, -amount}}; }
301 | item_balance_t inventory_balance_on_launch() const { return inventory_balance(); }
302 |
303 | std::pair walk_result(Pos current_position) const
304 | {
305 | return std::pair(current_position, std::chrono::seconds(1));
306 | }
307 | private: void execute_impl();
308 | };
309 |
310 | struct TakeFromInventory : public PrimitiveAction
311 | {
312 | const ItemPrototype* item;
313 | int amount;
314 | Entity entity;
315 | inventory_t inventory_type;
316 |
317 | TakeFromInventory(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, int amount_, Entity entity_, inventory_t inventory_type_) :
318 | PrimitiveAction(game,player,owner), item(item_), amount(amount_), entity(entity_), inventory_type(inventory_type_) { }
319 |
320 | std::string str() const;
321 |
322 | item_balance_t inventory_balance() const { return {{item, amount}}; }
323 | item_balance_t inventory_balance_on_launch() const { return inventory_balance(); }
324 |
325 | std::pair walk_result(Pos current_position) const
326 | {
327 | return std::pair(current_position, std::chrono::seconds(1));
328 | }
329 | private: void execute_impl();
330 | };
331 |
332 | struct CraftRecipe : public PrimitiveAction
333 | {
334 | CraftRecipe(FactorioGame* game, int player, std::optional owner, const Recipe* recipe_, int count_=1) : PrimitiveAction(game,player,owner), recipe(recipe_), count(count_) {}
335 |
336 | const Recipe* recipe;
337 | int count;
338 |
339 | std::string str() const;
340 |
341 | item_balance_t inventory_balance() const { throw std::runtime_error("not implemented"); }
342 | item_balance_t inventory_balance_on_launch() const;
343 |
344 | std::pair walk_result(Pos) const
345 | {
346 | throw std::runtime_error("not implemented");
347 | }
348 | private: void execute_impl();
349 | };
350 | } // namespace action
351 |
--------------------------------------------------------------------------------
/area.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #include
20 | #include
21 | #include "area.hpp"
22 |
23 | using std::string;
24 | using std::sscanf;
25 | using std::min;
26 | using std::abs;
27 |
28 | template <>
29 | Area_::Area_(string str)
30 | {
31 | sscanf(str.c_str(), "%i,%i;%i,%i", &left_top.x, &left_top.y, &right_bottom.x, &right_bottom.y);
32 | }
33 |
34 | template <>
35 | Area_::Area_(string str)
36 | {
37 | sscanf(str.c_str(), "%lf,%lf;%lf,%lf", &left_top.x, &left_top.y, &right_bottom.x, &right_bottom.y);
38 | }
39 |
40 | template
41 | string Area_::str() const
42 | {
43 | return left_top.str() + " -- " + right_bottom.str();
44 | }
45 |
46 | double distance(Pos_f a, Area_f b)
47 | {
48 | if (b.contains(a)) return 0.;
49 |
50 | if (b.left_top.x <= a.x && a.x <= b.right_bottom.x)
51 | return min(abs(a.y - b.left_top.y), abs(a.y - b.right_bottom.y));
52 | if (b.left_top.y <= a.y && a.y <= b.right_bottom.y)
53 | return min(abs(a.x - b.left_top.x), abs(a.x - b.right_bottom.x));
54 |
55 | Pos_f right_top( b.right_bottom.x, b.left_top.y );
56 | Pos_f left_bottom( b.left_top.x, b.right_bottom.y );
57 | return min(
58 | min( (a-b.left_top).len(), (a-b.right_bottom).len() ),
59 | min( (a-right_top).len(), (a-left_bottom).len() ));
60 | }
61 |
62 | template struct Area_;
63 | template struct Area_;
64 |
--------------------------------------------------------------------------------
/area.hpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #pragma once
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include "pos.hpp"
25 | #include "defines.h"
26 | #include
27 | #include
28 |
29 | template struct Area_
30 | {
31 | Pos_ left_top;
32 | Pos_ right_bottom;
33 |
34 | Area_() {}
35 | Area_(T x1, T y1, T x2, T y2) : left_top(x1,y1), right_bottom(x2,y2) {}
36 | Area_(const Pos_& lt, const Pos_& rb) : left_top(lt), right_bottom(rb) {}
37 | Area_(const std::vector>& positions)
38 | {
39 | assert(!positions.empty());
40 | // calculate the bounding box
41 | left_top = positions[0];
42 | right_bottom = positions[0];
43 | for (const auto& p : positions)
44 | {
45 | if (p.x < left_top.x)
46 | left_top.x = p.x;
47 | if (p.x >= right_bottom.x)
48 | right_bottom.x = p.x+1; // FIXME what to do with floats here :/?
49 | if (p.y < left_top.y)
50 | left_top.y = p.y;
51 | if (p.y >= right_bottom.y)
52 | right_bottom.y = p.y+1;
53 | }
54 | }
55 | Area_(std::string str);
56 | template Area_(const Area_& other) : left_top(other.left_top), right_bottom(other.right_bottom) {}
57 |
58 | std::string str() const;
59 |
60 | bool operator==(const Area_& other) const { return this->left_top==other.left_top && this->right_bottom==other.right_bottom; }
61 |
62 | bool empty() const { return !(left_top <= right_bottom); }
63 |
64 | void normalize()
65 | {
66 | if (left_top.x > right_bottom.x)
67 | std::swap(left_top.x, right_bottom.x);
68 | if (left_top.y > right_bottom.y)
69 | std::swap(left_top.y, right_bottom.y);
70 | }
71 |
72 | Pos_ center() const { return (left_top + right_bottom) / 2; }
73 |
74 | bool contains(const Pos_& p) const { return left_top <= p && p < right_bottom; }
75 | bool contains_x(T x) const { return left_top.x <= x && x < right_bottom.x; }
76 | bool contains_y(T y) const { return left_top.y <= y && y < right_bottom.y; }
77 |
78 | T diameter() const { return std::max(right_bottom.x - left_top.x, right_bottom.y - left_top.y); }
79 | T radius_around(Pos_ p) const
80 | {
81 | Pos_ rel_left_top = left_top - p;
82 | Pos_ rel_right_bottom = right_bottom - p;
83 | return std::max( std::max(rel_left_top.x, rel_left_top.y), std::max(rel_right_bottom.x, rel_right_bottom.y) );
84 | }
85 |
86 | Pos_ size() const { return right_bottom - left_top; }
87 |
88 | template typename std::enable_if< std::is_floating_point::value, Area_ >::type
89 | /*Area_*/ outer() const
90 | {
91 | return Area_( Pos_(int(std::floor(left_top.x)), int(std::floor(left_top.y))),
92 | Pos_(int(std::ceil(right_bottom.x)), int(std::ceil(right_bottom.y))) );
93 | }
94 |
95 | [[nodiscard]] Area_ rotate(dir4_t rot) const // assumes that the box is originally in NORTH orientation. i.e., passing rot=NORTH will do nothing, passing EAST will rotate 90deg clockwise etc
96 | {
97 | assert(rot == NORTH || rot == EAST || rot == SOUTH || rot == WEST);
98 | switch (rot)
99 | {
100 | case NORTH: return *this;
101 | case EAST: return Area_( Pos_(-right_bottom.y, left_top.x), Pos_(-left_top.y, right_bottom.x) );
102 | case SOUTH: return Area_( Pos_(-right_bottom.x, -right_bottom.y), Pos_(-left_top.x, -left_top.y) );
103 | case WEST: return Area_( Pos_(left_top.y, -right_bottom.x), Pos_(right_bottom.y, -left_top.x) );
104 | }
105 | // can't be reached
106 | assert(false);
107 | }
108 |
109 | [[nodiscard]] Area_ shift(Pos_ offset) const { return Area_(left_top+offset, right_bottom+offset); }
110 | [[nodiscard]] Area_ expand(T radius) const { return Area_(left_top-Pos_(radius,radius), right_bottom+Pos_(radius,radius)); }
111 |
112 | template [[nodiscard]] typename std::enable_if< std::is_integral::value, Area_ >::type expand(Pos_ point) const
113 | {
114 | return expand(Area_(point, point+Pos_(1,1)));
115 | }
116 | [[nodiscard]] Area_ expand(Area_ other) const { return Area_( std::min(left_top.x, other.left_top.x), std::min(left_top.y, other.left_top.y), std::max(right_bottom.x, other.right_bottom.x), std::max(right_bottom.y, other.right_bottom.y) ); }
117 | [[nodiscard]] T radius() const { return std::max( std::max( left_top.x, left_top.y), std::max( right_bottom.x, right_bottom.y) ); }
118 | [[nodiscard]] Area_ intersect(Area_ other) const
119 | {
120 | return Area_(
121 | Pos_(
122 | std::max(this->left_top.x, other.left_top.x),
123 | std::max(this->left_top.y, other.left_top.y)
124 | ),
125 | Pos_(
126 | std::min(this->right_bottom.x, other.right_bottom.x),
127 | std::min(this->right_bottom.y, other.right_bottom.y)
128 | )
129 | );
130 | }
131 | [[nodiscard]] bool intersects(Area_ other) const { return !intersect(other).empty(); }
132 | };
133 |
134 | typedef Area_ Area;
135 | typedef Area_ Area_f;
136 |
137 | template <> Area_::Area_(std::string str);
138 | template <> Area_::Area_(std::string str);
139 | extern template struct Area_;
140 | extern template struct Area_;
141 |
142 | double distance(Pos_f a, Area_f b);
143 |
--------------------------------------------------------------------------------
/clock.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | using Clock = std::chrono::steady_clock;
5 |
--------------------------------------------------------------------------------
/constants.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | constexpr double WALKING_SPEED = 8.9021; // in tiles per second.
4 | // source: https://wiki.factorio.com/Talk:Exoskeleton#Movement_speed_experiments
5 |
6 |
--------------------------------------------------------------------------------
/defines.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #include "defines.h"
20 | #include
21 |
22 | using namespace std;
23 |
24 | string inventory_names[] = {
25 | "defines.inventory.fuel",
26 | "defines.inventory.burnt_result",
27 | "defines.inventory.chest",
28 | "defines.inventory.furnace_source",
29 | "defines.inventory.furnace_result",
30 | "defines.inventory.furnace_modules",
31 | "defines.inventory.player_quickbar",
32 | "defines.inventory.player_main",
33 | "defines.inventory.player_guns",
34 | "defines.inventory.player_ammo",
35 | "defines.inventory.player_armor",
36 | "defines.inventory.player_tools",
37 | "defines.inventory.player_vehicle",
38 | "defines.inventory.player_trash",
39 | "defines.inventory.god_quickbar",
40 | "defines.inventory.god_main",
41 | "defines.inventory.roboport_robot",
42 | "defines.inventory.roboport_material",
43 | "defines.inventory.robot_cargo",
44 | "defines.inventory.robot_repair",
45 | "defines.inventory.assembling_machine_input",
46 | "defines.inventory.assembling_machine_output",
47 | "defines.inventory.assembling_machine_modules",
48 | "defines.inventory.lab_input",
49 | "defines.inventory.lab_modules",
50 | "defines.inventory.mining_drill_modules",
51 | "defines.inventory.item_main",
52 | "defines.inventory.rocket_silo_rocket",
53 | "defines.inventory.rocket_silo_result",
54 | "defines.inventory.car_trunk",
55 | "defines.inventory.car_ammo",
56 | "defines.inventory.cargo_wagon",
57 | "defines.inventory.turret_ammo",
58 | "defines.inventory.beacon_modules"
59 | };
60 |
61 | static inventory_flag_t IN = {true, false};
62 | static inventory_flag_t OUT = {false, true};
63 | static inventory_flag_t INOUT = {true, true};
64 | static inventory_flag_t NONE = {false, false};
65 |
66 | inventory_flag_t inventory_flags[] = {
67 | IN, //"defines.inventory.fuel",
68 | OUT, //"defines.inventory.burnt_result",
69 | INOUT, //"defines.inventory.chest",
70 | IN, //"defines.inventory.furnace_source",
71 | OUT, //"defines.inventory.furnace_result",
72 | NONE, //"defines.inventory.furnace_modules",
73 | NONE, //"defines.inventory.player_quickbar",
74 | NONE, //"defines.inventory.player_main",
75 | NONE, //"defines.inventory.player_guns",
76 | NONE, //"defines.inventory.player_ammo",
77 | NONE, //"defines.inventory.player_armor",
78 | NONE, //"defines.inventory.player_tools",
79 | NONE, //"defines.inventory.player_vehicle",
80 | NONE, //"defines.inventory.player_trash",
81 | NONE, //"defines.inventory.god_quickbar",
82 | NONE, //"defines.inventory.god_main",
83 | NONE, //"defines.inventory.roboport_robot",
84 | NONE, //"defines.inventory.roboport_material",
85 | NONE, //"defines.inventory.robot_cargo",
86 | NONE, //"defines.inventory.robot_repair",
87 | IN, //"defines.inventory.assembling_machine_input",
88 | OUT, //"defines.inventory.assembling_machine_output",
89 | NONE, //"defines.inventory.assembling_machine_modules",
90 | IN, //"defines.inventory.lab_input",
91 | NONE, //"defines.inventory.lab_modules",
92 | NONE, //"defines.inventory.mining_drill_modules",
93 | NONE, //"defines.inventory.item_main",
94 | NONE, //"defines.inventory.rocket_silo_rocket",
95 | NONE, //"defines.inventory.rocket_silo_result",
96 | INOUT, //"defines.inventory.car_trunk",
97 | INOUT, //"defines.inventory.car_ammo",
98 | INOUT, //"defines.inventory.cargo_wagon",
99 | IN, //"defines.inventory.turret_ammo",
100 | NONE, //"defines.inventory.beacon_modules"
101 | };
102 |
103 | const unordered_map inventory_types {
104 | {"defines.inventory.fuel", INV_FUEL},
105 | {"defines.inventory.burnt_result", INV_BURNT_RESULT},
106 | {"defines.inventory.chest", INV_CHEST},
107 | {"defines.inventory.furnace_source", INV_FURNACE_SOURCE},
108 | {"defines.inventory.furnace_result", INV_FURNACE_RESULT},
109 | {"defines.inventory.furnace_modules", INV_FURNACE_MODULES},
110 | {"defines.inventory.player_quickbar", INV_PLAYER_QUICKBAR},
111 | {"defines.inventory.player_main", INV_PLAYER_MAIN},
112 | {"defines.inventory.player_guns", INV_PLAYER_GUNS},
113 | {"defines.inventory.player_ammo", INV_PLAYER_AMMO},
114 | {"defines.inventory.player_armor", INV_PLAYER_ARMOR},
115 | {"defines.inventory.player_tools", INV_PLAYER_TOOLS},
116 | {"defines.inventory.player_vehicle", INV_PLAYER_VEHICLE},
117 | {"defines.inventory.player_trash", INV_PLAYER_TRASH},
118 | {"defines.inventory.god_quickbar", INV_GOD_QUICKBAR},
119 | {"defines.inventory.god_main", INV_GOD_MAIN},
120 | {"defines.inventory.roboport_robot", INV_ROBOPORT_ROBOT},
121 | {"defines.inventory.roboport_material", INV_ROBOPORT_MATERIAL},
122 | {"defines.inventory.robot_cargo", INV_ROBOT_CARGO},
123 | {"defines.inventory.robot_repair", INV_ROBOT_REPAIR},
124 | {"defines.inventory.assembling_machine_input", INV_ASSEMBLING_MACHINE_INPUT},
125 | {"defines.inventory.assembling_machine_output", INV_ASSEMBLING_MACHINE_OUTPUT},
126 | {"defines.inventory.assembling_machine_modules", INV_ASSEMBLING_MACHINE_MODULES},
127 | {"defines.inventory.lab_input", INV_LAB_INPUT},
128 | {"defines.inventory.lab_modules", INV_LAB_MODULES},
129 | {"defines.inventory.mining_drill_modules", INV_MINING_DRILL_MODULES},
130 | {"defines.inventory.item_main", INV_ITEM_MAIN},
131 | {"defines.inventory.rocket_silo_rocket", INV_ROCKET_SILO_ROCKET},
132 | {"defines.inventory.rocket_silo_result", INV_ROCKET_SILO_RESULT},
133 | {"defines.inventory.car_trunk", INV_CAR_TRUNK},
134 | {"defines.inventory.car_ammo", INV_CAR_AMMO},
135 | {"defines.inventory.cargo_wagon", INV_CARGO_WAGON},
136 | {"defines.inventory.turret_ammo", INV_TURRET_AMMO},
137 | {"defines.inventory.beacon_modules", INV_BEACON_MODULES}
138 | };
139 |
--------------------------------------------------------------------------------
/defines.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #pragma once
20 |
21 | #include
22 | #include
23 |
24 | enum dir4_t
25 | {
26 | NORTH=0,
27 | EAST,
28 | SOUTH,
29 | WEST,
30 |
31 | TOP=NORTH,
32 | RIGHT=EAST,
33 | BOTTOM=SOUTH,
34 | LEFT=WEST
35 | };
36 |
37 | enum dir8_t
38 | {
39 | d8_NORTH=0,
40 | d8_NORTHEAST,
41 | d8_EAST,
42 | d8_SOUTHEAST,
43 | d8_SOUTH,
44 | d8_SOUTHWEST,
45 | d8_WEST,
46 | d8_NORTHWEST
47 | };
48 |
49 | enum inventory_t
50 | {
51 | INV_MIN = 0,
52 | INV_FUEL = 0,
53 | INV_BURNT_RESULT,
54 | INV_CHEST,
55 | INV_FURNACE_SOURCE,
56 | INV_FURNACE_RESULT,
57 | INV_FURNACE_MODULES,
58 | INV_PLAYER_QUICKBAR,
59 | INV_PLAYER_MAIN,
60 | INV_PLAYER_GUNS,
61 | INV_PLAYER_AMMO,
62 | INV_PLAYER_ARMOR,
63 | INV_PLAYER_TOOLS,
64 | INV_PLAYER_VEHICLE,
65 | INV_PLAYER_TRASH,
66 | INV_GOD_QUICKBAR,
67 | INV_GOD_MAIN,
68 | INV_ROBOPORT_ROBOT,
69 | INV_ROBOPORT_MATERIAL,
70 | INV_ROBOT_CARGO,
71 | INV_ROBOT_REPAIR,
72 | INV_ASSEMBLING_MACHINE_INPUT,
73 | INV_ASSEMBLING_MACHINE_OUTPUT,
74 | INV_ASSEMBLING_MACHINE_MODULES,
75 | INV_LAB_INPUT,
76 | INV_LAB_MODULES,
77 | INV_MINING_DRILL_MODULES,
78 | INV_ITEM_MAIN,
79 | INV_ROCKET_SILO_ROCKET,
80 | INV_ROCKET_SILO_RESULT,
81 | INV_CAR_TRUNK,
82 | INV_CAR_AMMO,
83 | INV_CARGO_WAGON,
84 | INV_TURRET_AMMO,
85 | INV_BEACON_MODULES,
86 | INV_MAX
87 | };
88 |
89 | struct inventory_flag_t
90 | {
91 | bool put:1; // we may put to the inventory
92 | bool take:1; // we may take from the inventory
93 | };
94 |
95 | extern inventory_flag_t inventory_flags[];
96 |
97 | extern std::string inventory_names[];
98 | extern const std::unordered_map inventory_types;
99 |
--------------------------------------------------------------------------------
/doc/factorio_comm.md:
--------------------------------------------------------------------------------
1 | Bridging between the Bot and Factorio
2 | =====================================
3 |
4 | Interacting with Factorio isn't too easy. Factorio is [moddable](http://lua-api.factorio.com)
5 | with [Lua](https://www.lua.org) (good), but due to how it's working
6 | internally, a lot of I/O functions are disabled (bad).
7 |
8 | We're basically forced to do the following:
9 |
10 | * have the Lua mod write out all information into a file under
11 | `factorio/script-output/`,
12 | * parse this file, do things, be artifically intelligent,
13 | * and then establish a
14 | [RCON](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol)
15 | connection to Factorio and issue our commands
16 |
17 | Additionally, gathering all map information is a quite CPU-intensive task, so
18 | we wish to do this only on the server (since the clients won't write out the
19 | datafile, there's no need for them to figure out what to write in the first
20 | place). This means that we have to do **different** behaviour on different
21 | Factorio nodes, which isn't intended in Factorio.
22 |
23 | The way we're doing this is: After starting of a server/client, we tell the
24 | game via RCON who has just joined. The nodes can then figure out whether it
25 | was themselves who just joined, and thus remember their own name. This
26 | finally enables us to writeout only on the server.
27 |
28 | The start up procedure is as follows:
29 |
30 | * start the server
31 | * the server will write "server" into its `script-output/players_connected.txt`
32 | * send `/c remote.call('windfisch','whoami','server')` via RCON
33 | * now the server knows that it is the server
34 | * start zero or more clients. the server will write "clientname" into
35 | `players_connected.txt`. Send `/c remote.call(...)` with the appropriate
36 | name.
37 | * start the bot (`./test`), telling it where the `script-output/output1.txt`
38 | file is.
39 | * the bot will read the file, show a GUI, and RCON your commands into the
40 | server.
41 |
42 | Note that the bot actually accepts the file **prefix**, i.e.
43 | `script-output/output`. (This is because, in a later version, we will rotate
44 | through multiple output files, in order to limit file size.)
45 |
46 |
--------------------------------------------------------------------------------
/doc/scheduler.md:
--------------------------------------------------------------------------------
1 | The Scheduler
2 | =============
3 |
4 | The scheduler is, together with the [goal manager](goal_manager.md), the core
5 | of the factorio-bot. It computes the order in which actions from the tasks
6 | are to be executed, manages the crafting queue and collects items as necessary.
7 |
8 | The scheduler is instanced *per player*, any inter-player-coordination must
9 | be implemented outside.
10 |
11 | Problem statement
12 | -----------------
13 |
14 | In factorio, there are two separate threads of activity: The *crafting* thread
15 | and the *walking* thread. Any tasks that have to be performed usually require
16 | both: E.g., building a new iron mine not only requires the bot to *walk* to the
17 | location and actually place the entities on the ground (both of which happen in
18 | the walking thread), but also to *craft* or *collect* the required items first.
19 | Collecting them also occupies the walking thread, while crafting them would be
20 | handled in the *crafting thread*.
21 |
22 | **(P1)** For an efficient operation, crafting and walking operations should be
23 | parallelized as much as possible. For example, if a high-priority mine-building
24 | task must craft a lot of mines first, it would be wasted time if all other task
25 | would wait for it to complete. Instead, the bot could perform a lower-priority
26 | task that only requires *walking/building*, efficiently parallelizing with the
27 | crafting process.
28 |
29 | **(P2)** However, the lower-priority task should not (significantly) delay the
30 | high priority task, which could happen e.g. if its start location was on the
31 | opposite side of the map.
32 |
33 | **(P3)** Additionally, a lower-priority task which could well fit in the idle
34 | gap should be able to execute, even if it needs one tiny craft to succeed. In
35 | this case, the a lower-priority task should be able to perform its crafts
36 | *before* a high priority task, if it doens't delay it for too long.
37 |
38 | Concepts
39 | --------
40 |
41 | The scheduler makes use of the following core concepts:
42 |
43 | - **Tasks**: A task consists of a list of *actions*, together with a *start*
44 | and *end location* and a list of *required items*. The scheduler must fulfill
45 | all requirements of the task, i.e. either collect or craft all required items,
46 | and move the player close enough to the task's start location. Every task has
47 | a *priority* attached to it.
48 | - **Tagged Inventory**/**claims**: Each item in the player's inventory can be
49 | *claimed* by a task, meaning that it's reserved for use solely by this task.
50 | A task may only use items it has claimed. **Unclaimed items** can not be used
51 | by a task, but can be claimed by any task.
52 | - **Crafting list**: Each task has a crafting list, which is initially empty
53 | and managed by the scheduler. For each required item of a task, the scheduler
54 | decides whether to claim it from unclaimed items in the inventory, to collect
55 | it or to craft it. In the latter case, it's added to the task's crafting list.
56 | - **Crafting order**: The scheduler keeps a list of which task may craft first.
57 | This is required to fulfill *(P3)*, allowing low-prio-but-short tasks to craft
58 | first.
59 |
60 | A task is either **runnable**, **eventually runnable** or **not runnable**.
61 | It's *runnable* iif it has all required items, and *eventually runnable* iif it
62 | will have all required items after all of its crafts, which will be eventually
63 | performed given the current crafting order. If it's not *eventually runnable*,
64 | a task is considered *not runnable*.
65 |
66 | Any *eventually runnable* task can be assigned a **crafting eta**. This is the
67 | time, given the current crafting order and assuming that all crafts in this
68 | crafting order will be performed, that it will take until this task becomes
69 | *runnable*.
70 |
71 | A task that is *not runnable* has **required items w.r.t. its crafting list**,
72 | which equates of the minimum item list that, after all of its crafts would be
73 | performed, can supply its *required items*.
74 |
75 | Implementation
76 | --------------
77 |
78 | ### Task selection
79 |
80 | We start with an empty **timetable**. A timetable is **valid**, iif all tasks
81 | in it can start no later than a small grace time after their scheduled start,
82 | considering task durations and the time spent from walking from one task to
83 | another. A timetable is **executable**, iif the first task it contains can
84 | start right now (or very soon, at least).
85 |
86 | All pending tasks, ordered by decreasing priority, are considered. If the
87 | considered task is **runnable** or **eventually runnable** (after possibly
88 | assigning unclaimed items to it), we try to insert it, starting at its
89 | **crafting eta**, into the timetable. If otherwise the considered task is **not
90 | runnable** (even after assignin unclaimed items to it), we unassign any
91 | previously claimed items, and then calculate a **crafting list** and a
92 | corresponding **collector tour** through the map, collecting all *required
93 | items w.r.t. the task's crafting list*. We then try to **donate** all
94 | *unclaimed items* to the collector tour. The tour cannot be nonempty at this
95 | point (otherwise the task would have been eventually runnable in the first
96 | place).
97 |
98 | **Donating** items to a collector tour tries to shorten a tour as much as
99 | possible by removing waypoints. The items that would have been collected at
100 | these waypoints are instead taken from the unclaimed items in the inventory.
101 | However, as few as possible items are claimed from the inventory. An example:
102 | The collector tour would visit the chest at location A, and take 300 iron from
103 | it. Then, it would visit the chest at B, and take 100 more iron, and 200 copper
104 | from there. We donate 340 iron and 170 copper. This is not sufficient to remove
105 | the stop at B. However, it's sufficient to remove the stop at A. We therefore
106 | remove stop A, and claim 300 iron to the tasks that would have been served by
107 | this stop. Afterwards, we have 40 unclaimed iron and 170 unclaimed copper in
108 | the inventory.
109 |
110 | We prefer to keep a **buffer of items** in our inventory. If that buffer saves
111 | us from having to walk somewhere to fetch items, we use it. If we must walk
112 | there anyway, we do not use the buffer. **TODO**: *That's dumb. Better use the
113 | buffer as far as possible, but then refill it there. Idea: a collector tour
114 | attempts to ensure that N items of a type are there *after* these tasks have
115 | finished. Unclaimed items are instantly assigned to tasks, in order of their
116 | priority. After the collector task has finished, our buffers are full again.*
117 |
118 | #### FIXME: maybe that's the solution:
119 | Whenever the task scheduler runs, there are no claimed items (because there are
120 | no currently running tasks).
121 |
122 | When a task can run with what we have in our inventory (without creating debt),
123 | it may do so. If not, then for each item that would create debt, we collect it,
124 | with an aftermath of N items (not zero). I.e. we collect more than we need, to
125 | re-fill our buffer. (Also, for items that would suffice, we check whether
126 | re-filling them would cause nearly-zero cost; if so, we refill them.)
127 |
128 | We then try to add lower-priority tasks to the collector task, if they don't
129 | extend its original length by more than 10%.
130 |
131 | Then, existing inventory is claimed to the tasks depending on their actual
132 | priority. (The same is true for items that get collected.)
133 | Yet crafts are scheduled in their order of the grocery-store-sorter.
134 |
135 | Donations can still occur. As far as the inventory is concerned, they're
136 | just handled like collects, i.e. they're assigned in the order of task
137 | priorities. If they can save us from visiting a station, then we update the
138 | path accordingly.
139 |
140 | #### end fixme
141 |
142 |
143 |
144 | If the
145 | resulting timetable would be valid, the insertion is performed. If the
146 | timetable has become **executable**, its first task is returned. Otherwise,
147 | the next pending task is considered.
148 |
149 | It can happen that no task can be selected.
150 |
151 |
--------------------------------------------------------------------------------
/entity.cpp:
--------------------------------------------------------------------------------
1 | #include "entity.h"
2 |
3 | double EntityPrototype::max_collision_box_size;
4 |
--------------------------------------------------------------------------------
/entity.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #pragma once
20 | #include "area.hpp"
21 | #include "pos.hpp"
22 | #include "multivariant.hpp"
23 | #include "defines.h"
24 | #include "inventory.hpp"
25 | #include
26 | #include
27 | #include
28 |
29 | struct ContainerData
30 | {
31 | MultiInventory inventories;
32 | bool fuel_is_output = false;
33 | };
34 |
35 | struct MachineData : public ContainerData {};
36 | struct MiningDrillData : public ContainerData {};
37 |
38 | using mvu = multivariant_utils>;
39 |
40 | struct ItemPrototype;
41 |
42 | struct EntityPrototype
43 | {
44 | static double max_collision_box_size;
45 |
46 | std::string name;
47 | std::string type;
48 | Area_f collision_box;
49 | bool collides_player;
50 | bool collides_object;
51 | bool mineable;
52 | std::vector< std::pair > mine_results_str;
53 | item_balance_t mine_results; // filled in later by resolve_item_references()
54 | size_t data_kind;
55 |
56 | EntityPrototype(const std::string& name_, const std::string& type_, const std::string& collision_str, const Area_f& collision_box_, bool mineable_, std::vector< std::pair > mine_results_str_) :
57 | name(name_),
58 | type(type_),
59 | collision_box(collision_box_),
60 | collides_player(collision_str.find('P') != std::string::npos),
61 | collides_object(collision_str.find('O') != std::string::npos),
62 | mineable(mineable_),
63 | mine_results_str(mine_results_str_),
64 | data_kind(mvu::invalid_index)
65 | {
66 | max_collision_box_size = std::max(max_collision_box_size, collision_box_.radius_around(Pos_f(0,0)));
67 |
68 | if (type_ == "container" || type_ == "logistic-container")
69 | data_kind = mvu::index();
70 | else if (type_ == "mining-drill")
71 | data_kind = mvu::index();
72 | else if (type_ == "assembling-machine")
73 | data_kind = mvu::index();
74 | else if (type_ == "furnace")
75 | data_kind = mvu::index();
76 | }
77 | };
78 |
79 | struct Entity
80 | {
81 | Pos_f get_pos() const { return pos; } // WorldList wants to call this.
82 | Area_f get_extent() const { return collision_box(); } // WorldList wants to call this.
83 | static double get_max_extent() { return EntityPrototype::max_collision_box_size; } // same
84 |
85 | Pos_f pos;
86 | const EntityPrototype* proto = nullptr;
87 | dir4_t direction = NORTH;
88 |
89 | std::string str() const { return proto->name+"@"+pos.str(); }
90 |
91 | refcount_base* data_ptr;
92 |
93 | //bool operator==(const Entity& that) const { return this->pos==that.pos && this->proto==that.proto && this->direction==that.direction && this->data_ptr == that.data_ptr; }
94 | constexpr bool mostly_equals(const Entity& that) const { return this->pos==that.pos && this->proto==that.proto; }
95 |
96 | struct mostly_equals_comparator
97 | {
98 | constexpr bool operator()(const Entity &lhs, const Entity &rhs) const
99 | {
100 | return lhs.mostly_equals(rhs);
101 | }
102 | };
103 |
104 | void takeover_data(Entity& that)
105 | {
106 | assert (this->proto == that.proto);
107 |
108 | release_data();
109 | this->data_ptr = that.data_ptr;
110 | that.data_ptr = nullptr;
111 | }
112 |
113 | Area_f collision_box() const { return proto->collision_box.rotate(direction).shift(pos); }
114 |
115 | template T& data()
116 | {
117 | return *mvu::get(proto->data_kind, data_ptr);
118 | }
119 | template T* data_or_null()
120 | {
121 | return mvu::get_or_null(proto->data_kind, data_ptr);
122 | }
123 | template const T& data() const
124 | {
125 | return *mvu::get(proto->data_kind, data_ptr);
126 | }
127 | template const T* data_or_null() const
128 | {
129 | return mvu::get_or_null(proto->data_kind, data_ptr);
130 | }
131 |
132 | void make_unique()
133 | {
134 | data_ptr = mvu::clone(proto->data_kind, data_ptr);
135 | }
136 |
137 | ~Entity()
138 | {
139 | release_data();
140 | }
141 |
142 |
143 | struct nullent_tag {};
144 |
145 | Entity(nullent_tag, const Pos_f& pos_ = Pos_f()) : pos(pos_), proto(nullptr), data_ptr(nullptr)
146 | {
147 | }
148 |
149 | Entity(const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH)
150 | : pos(pos_), proto(proto_), direction(direction_), data_ptr(mvu::make(proto_->data_kind))
151 | {
152 | if (data_ptr)
153 | data_ptr->refcount = 1;
154 | }
155 |
156 | Entity(const Entity& e)
157 | {
158 | pos = e.pos;
159 | proto = e.proto;
160 | direction = e.direction;
161 | data_ptr = e.data_ptr;
162 | if (data_ptr)
163 | data_ptr->refcount++;
164 | }
165 |
166 | Entity(Entity&& e) noexcept
167 | {
168 | pos = std::move(e.pos);
169 | proto = std::move(e.proto);
170 | direction = std::move(e.direction);
171 | data_ptr = e.data_ptr;
172 | e.data_ptr = nullptr;
173 | }
174 |
175 | Entity& operator=(const Entity& e)
176 | {
177 | release_data();
178 |
179 | pos = e.pos;
180 | proto = e.proto;
181 | direction = e.direction;
182 | data_ptr = e.data_ptr;
183 | if (data_ptr)
184 | data_ptr->refcount++;
185 |
186 | return *this;
187 | }
188 | Entity& operator=(Entity&& e)
189 | {
190 | release_data();
191 |
192 | pos = std::move(e.pos);
193 | proto = std::move(e.proto);
194 | direction = std::move(e.direction);
195 | data_ptr = e.data_ptr;
196 | e.data_ptr = nullptr;
197 |
198 | return *this;
199 | }
200 |
201 | private:
202 | void release_data()
203 | {
204 | if (data_ptr)
205 | {
206 | data_ptr->refcount--;
207 | if (data_ptr->refcount == 0)
208 | mvu::del(proto->data_kind, data_ptr);
209 | }
210 | }
211 | };
212 |
213 | struct DesiredEntity : public Entity
214 | {
215 | std::weak_ptr corresponding_actual_entity;
216 | std::shared_ptr get_actual();
217 |
218 | DesiredEntity(const Entity& ent) : Entity(ent) {}
219 | DesiredEntity(const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH)
220 | : Entity(pos_, proto_, direction_) {}
221 | };
222 |
223 | struct PlannedEntity : public DesiredEntity
224 | {
225 | int level; // at which facility-level to place this
226 |
227 | PlannedEntity(int level_, const Entity& ent) : DesiredEntity(ent), level(level_) {}
228 | PlannedEntity(int level_, const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH)
229 | : DesiredEntity(pos_, proto_, direction_), level(level_) {}
230 | PlannedEntity(const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH)
231 | : DesiredEntity(pos_, proto_, direction_), level(-1) {}
232 | };
233 |
234 | template Area_f bounding_box(const container_type& container)
235 | {
236 | bool first = true;
237 | Area_f result;
238 | for (const auto& thing : container)
239 | {
240 | if (first) result = Area_f(thing.pos, thing.pos);
241 | else result = result.expand(thing.pos);
242 | first=false;
243 | }
244 | return result;
245 | }
246 |
--------------------------------------------------------------------------------
/eval/viewports/README.md:
--------------------------------------------------------------------------------
1 | Viewport Evaluation
2 | ===================
3 |
4 | Question
5 | --------
6 |
7 | is `WorldMap::Viewport` worth the effort, or is `WorldMap::DumbViewport`
8 | good enough?
9 |
10 |
11 | Experiment
12 | ----------
13 |
14 | Try to find a path, measure time. Try dumb and normal viewport with and
15 | without compiler optimisation.
16 |
17 | Tested with `./launch.sh --bot-offline` with `output1.txt`
18 |
19 |
20 | Results
21 | -------
22 |
23 | based on 661e1e38d24f
24 |
25 | - Dumb + no optimisation = no patch: 17715ms
26 | - Normal + no opt = `norm_noopt.patch`: 10220ms
27 | - Dynamic + no opt = `dyn_noopt.patch`: 9932ms
28 |
29 | - Dumb + `-O2` = `dumb_o2.patch`: 1652ms
30 | - Normal + `-O2` = `norm_O2.patch`: 491ms
31 | - Dynamic + `-O2` = `dyn_O2.patch`: 489ms
32 |
33 |
34 | Conclusion
35 | ----------
36 |
37 | `Viewport` is worth the effort, and growing the viewport dynamically,
38 | on-demand is equally fast `:)`
39 |
--------------------------------------------------------------------------------
/eval/viewports/dumb_O2.patch:
--------------------------------------------------------------------------------
1 | diff --git a/Makefile b/Makefile
2 | index 6ff1b97..3a8a4b6 100644
3 | --- a/Makefile
4 | +++ b/Makefile
5 | @@ -13,7 +13,7 @@ MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes
6 |
7 |
8 |
9 | -DEBUGFLAGS = -g -D_GLIBCXX_DEBUG #-fsanitize=undefined,address
10 | +DEBUGFLAGS = -g -O2 -D_GLIBCXX_DEBUG #-fsanitize=undefined,address
11 | FASTFLAGS = -O2
12 | CXXFLAGS_BASE = -std=c++14
13 | CFLAGS_BASE = -std=c99
14 |
--------------------------------------------------------------------------------
/eval/viewports/dyn_O2.patch:
--------------------------------------------------------------------------------
1 | diff --git a/Makefile b/Makefile
2 | index 6ff1b97..3a8a4b6 100644
3 | --- a/Makefile
4 | +++ b/Makefile
5 | @@ -13,7 +13,7 @@ MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes
6 |
7 |
8 |
9 | -DEBUGFLAGS = -g -D_GLIBCXX_DEBUG #-fsanitize=undefined,address
10 | +DEBUGFLAGS = -g -O2 -D_GLIBCXX_DEBUG #-fsanitize=undefined,address
11 | FASTFLAGS = -O2
12 | CXXFLAGS_BASE = -std=c++14
13 | CFLAGS_BASE = -std=c99
14 | diff --git a/factorio_io.cpp b/factorio_io.cpp
15 | index ab13378..40c8840 100644
16 | --- a/factorio_io.cpp
17 | +++ b/factorio_io.cpp
18 | @@ -447,7 +447,7 @@ struct ResourcePatch
19 | }
20 | };
21 |
22 | -void FactorioGame::floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius)
23 | +void FactorioGame::floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius)
24 | {
25 | int id = next_free_resource_id++;
26 |
27 | diff --git a/factorio_io.h b/factorio_io.h
28 | index cfe4e75..0780b6f 100644
29 | --- a/factorio_io.h
30 | +++ b/factorio_io.h
31 | @@ -106,7 +106,7 @@ class FactorioGame
32 | void parse_players(const std::string& data);
33 | void parse_objects(const Area& area, const std::string& data);
34 |
35 | - void floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius);
36 | + void floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius);
37 | int next_free_resource_id = 1;
38 |
39 | public:
40 | diff --git a/pathfinding.cpp b/pathfinding.cpp
41 | index a993100..0d55e9d 100644
42 | --- a/pathfinding.cpp
43 | +++ b/pathfinding.cpp
44 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal)
45 |
46 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size)
47 | {
48 | - auto view = map.dumb_view(Pos(0,0));
49 | + auto view = map.view(Pos(-584,-274), Pos(306,302), Pos(0,0));
50 |
51 | assert(size<=1.);
52 | vector result;
53 | diff --git a/pos.hpp b/pos.hpp
54 | index c240297..c28f0b9 100644
55 | --- a/pos.hpp
56 | +++ b/pos.hpp
57 | @@ -46,6 +46,7 @@ struct Pos_
58 |
59 | static Pos_ tile_to_chunk(const Pos_& p) { return Pos_(chunkidx(p.x), chunkidx(p.y)); }
60 | static Pos_ tile_to_chunk_ceil(const Pos_& p) { return Pos_(chunkidx(p.x+31), chunkidx(p.y+31)); }
61 | + static Pos_ chunk_to_tile(const Pos_& p) { return Pos_(p.x*32, p.y*32); }
62 | };
63 |
64 | template <> inline Pos_ Pos_::operator/(int f) const { return Pos_(sane_div(x,f), sane_div(y,f)); }
65 | diff --git a/worldmap.hpp b/worldmap.hpp
66 | index 3145c32..5ed721f 100644
67 | --- a/worldmap.hpp
68 | +++ b/worldmap.hpp
69 | @@ -85,6 +85,11 @@ class WorldMap
70 | lefttop_chunk = Pos::tile_to_chunk(lefttop_tile);
71 | Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile);
72 |
73 | + // recalculate tile borders, because we can only deal with chunks. This means that
74 | + // the actually available area is larger than the requested area.
75 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk);
76 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk);
77 | +
78 | xlen_chunks = rightbot_chunk.x - lefttop_chunk.x;
79 | ylen_chunks = rightbot_chunk.y - lefttop_chunk.y;
80 |
81 | @@ -96,6 +101,48 @@ class WorldMap
82 | for (int y = 0; y < ylen_chunks; y++)
83 | chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y);
84 | }
85 | +
86 | + void extend_self(const Pos& new_lefttop_tile, const Pos& new_rightbot_tile)
87 | + {
88 | + Pos old_lefttop_chunk = lefttop_chunk;
89 | + int old_xlen_chunks = xlen_chunks;
90 | + int old_ylen_chunks = ylen_chunks;
91 | + std::vector old_chunkcache = std::move(chunkcache);
92 | +
93 | +
94 | +
95 | + lefttop_tile = new_lefttop_tile;
96 | + rightbot_tile = new_rightbot_tile;
97 | +
98 | + lefttop_chunk = Pos::tile_to_chunk(lefttop_tile);
99 | + Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile);
100 | +
101 | + // recalculate tile borders, because we can only deal with chunks. This means that
102 | + // the actually available area is larger than the requested area.
103 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk);
104 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk);
105 | +
106 | + xlen_chunks = rightbot_chunk.x - lefttop_chunk.x;
107 | + ylen_chunks = rightbot_chunk.y - lefttop_chunk.y;
108 | +
109 | + if (xlen_chunks <= 0 or ylen_chunks <= 0)
110 | + throw std::invalid_argument("invalid Viewport range");
111 | +
112 | + chunkcache.resize(xlen_chunks * ylen_chunks);
113 | + for (int x = 0; x < xlen_chunks; x++)
114 | + for (int y = 0; y < ylen_chunks; y++)
115 | + {
116 | + int oldx = x + lefttop_chunk.x - old_lefttop_chunk.x;
117 | + int oldy = y + lefttop_chunk.y - old_lefttop_chunk.y;
118 | +
119 | + if (0 <= oldx && oldx < old_xlen_chunks &&
120 | + 0 <= oldy && oldy < old_ylen_chunks)
121 | + chunkcache[x+y*xlen_chunks] = old_chunkcache[oldx+oldy*old_xlen_chunks];
122 | + else
123 | + chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y);
124 | + }
125 | + }
126 | +
127 | public:
128 | typedef typename std::conditional::type reftype;
129 |
130 | @@ -104,12 +151,21 @@ class WorldMap
131 | return Viewport_(parent, lefttop_tile_, rightbot_tile_, origin);
132 | }
133 |
134 | - reftype at(int x, int y) const
135 | + reftype at(int x, int y)
136 | {
137 | int tilex = x+origin.x;
138 | int tiley = y+origin.y;
139 | if (!((tilex >= lefttop_tile.x) && (tiley >= lefttop_tile.y) && (tilex < rightbot_tile.x) && (tiley < rightbot_tile.y)))
140 | - throw std::invalid_argument("invalid coordinate for Viewport");
141 | + {
142 | + Pos new_lefttop_tile(lefttop_tile);
143 | + Pos new_rightbot_tile(rightbot_tile);
144 | + if (tilex < lefttop_tile.x) new_lefttop_tile.x = tilex;
145 | + else if (tilex >= rightbot_tile.x) new_rightbot_tile.x = tilex+1;
146 | + if (tiley < lefttop_tile.y) new_lefttop_tile.y = tiley;
147 | + else if (tiley >= rightbot_tile.y) new_rightbot_tile.y = tiley+1;
148 | +
149 | + extend_self(new_lefttop_tile, new_rightbot_tile);
150 | + }
151 | int chunkx = chunkidx(tilex) - lefttop_chunk.x;
152 | int chunky = chunkidx(tiley) - lefttop_chunk.y;
153 | assert(chunkx >= 0);
154 | @@ -122,7 +178,7 @@ class WorldMap
155 |
156 | return (*chunkcache[chunkx+chunky*xlen_chunks])[relx][rely];
157 | }
158 | - reftype at(const Pos& pos) const { return at(pos.x, pos.y); }
159 | + reftype at(const Pos& pos) { return at(pos.x, pos.y); }
160 | };
161 |
162 |
163 |
--------------------------------------------------------------------------------
/eval/viewports/dyn_noopt.patch:
--------------------------------------------------------------------------------
1 | diff --git a/factorio_io.cpp b/factorio_io.cpp
2 | index ab13378..40c8840 100644
3 | --- a/factorio_io.cpp
4 | +++ b/factorio_io.cpp
5 | @@ -447,7 +447,7 @@ struct ResourcePatch
6 | }
7 | };
8 |
9 | -void FactorioGame::floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius)
10 | +void FactorioGame::floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius)
11 | {
12 | int id = next_free_resource_id++;
13 |
14 | diff --git a/factorio_io.h b/factorio_io.h
15 | index cfe4e75..0780b6f 100644
16 | --- a/factorio_io.h
17 | +++ b/factorio_io.h
18 | @@ -106,7 +106,7 @@ class FactorioGame
19 | void parse_players(const std::string& data);
20 | void parse_objects(const Area& area, const std::string& data);
21 |
22 | - void floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius);
23 | + void floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius);
24 | int next_free_resource_id = 1;
25 |
26 | public:
27 | diff --git a/pathfinding.cpp b/pathfinding.cpp
28 | index a993100..0d55e9d 100644
29 | --- a/pathfinding.cpp
30 | +++ b/pathfinding.cpp
31 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal)
32 |
33 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size)
34 | {
35 | - auto view = map.dumb_view(Pos(0,0));
36 | + auto view = map.view(Pos(-584,-274), Pos(306,302), Pos(0,0));
37 |
38 | assert(size<=1.);
39 | vector result;
40 | diff --git a/pos.hpp b/pos.hpp
41 | index c240297..c28f0b9 100644
42 | --- a/pos.hpp
43 | +++ b/pos.hpp
44 | @@ -46,6 +46,7 @@ struct Pos_
45 |
46 | static Pos_ tile_to_chunk(const Pos_& p) { return Pos_(chunkidx(p.x), chunkidx(p.y)); }
47 | static Pos_ tile_to_chunk_ceil(const Pos_& p) { return Pos_(chunkidx(p.x+31), chunkidx(p.y+31)); }
48 | + static Pos_ chunk_to_tile(const Pos_& p) { return Pos_(p.x*32, p.y*32); }
49 | };
50 |
51 | template <> inline Pos_ Pos_::operator/(int f) const { return Pos_(sane_div(x,f), sane_div(y,f)); }
52 | diff --git a/worldmap.hpp b/worldmap.hpp
53 | index 3145c32..5ed721f 100644
54 | --- a/worldmap.hpp
55 | +++ b/worldmap.hpp
56 | @@ -85,6 +85,11 @@ class WorldMap
57 | lefttop_chunk = Pos::tile_to_chunk(lefttop_tile);
58 | Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile);
59 |
60 | + // recalculate tile borders, because we can only deal with chunks. This means that
61 | + // the actually available area is larger than the requested area.
62 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk);
63 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk);
64 | +
65 | xlen_chunks = rightbot_chunk.x - lefttop_chunk.x;
66 | ylen_chunks = rightbot_chunk.y - lefttop_chunk.y;
67 |
68 | @@ -96,6 +101,48 @@ class WorldMap
69 | for (int y = 0; y < ylen_chunks; y++)
70 | chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y);
71 | }
72 | +
73 | + void extend_self(const Pos& new_lefttop_tile, const Pos& new_rightbot_tile)
74 | + {
75 | + Pos old_lefttop_chunk = lefttop_chunk;
76 | + int old_xlen_chunks = xlen_chunks;
77 | + int old_ylen_chunks = ylen_chunks;
78 | + std::vector old_chunkcache = std::move(chunkcache);
79 | +
80 | +
81 | +
82 | + lefttop_tile = new_lefttop_tile;
83 | + rightbot_tile = new_rightbot_tile;
84 | +
85 | + lefttop_chunk = Pos::tile_to_chunk(lefttop_tile);
86 | + Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile);
87 | +
88 | + // recalculate tile borders, because we can only deal with chunks. This means that
89 | + // the actually available area is larger than the requested area.
90 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk);
91 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk);
92 | +
93 | + xlen_chunks = rightbot_chunk.x - lefttop_chunk.x;
94 | + ylen_chunks = rightbot_chunk.y - lefttop_chunk.y;
95 | +
96 | + if (xlen_chunks <= 0 or ylen_chunks <= 0)
97 | + throw std::invalid_argument("invalid Viewport range");
98 | +
99 | + chunkcache.resize(xlen_chunks * ylen_chunks);
100 | + for (int x = 0; x < xlen_chunks; x++)
101 | + for (int y = 0; y < ylen_chunks; y++)
102 | + {
103 | + int oldx = x + lefttop_chunk.x - old_lefttop_chunk.x;
104 | + int oldy = y + lefttop_chunk.y - old_lefttop_chunk.y;
105 | +
106 | + if (0 <= oldx && oldx < old_xlen_chunks &&
107 | + 0 <= oldy && oldy < old_ylen_chunks)
108 | + chunkcache[x+y*xlen_chunks] = old_chunkcache[oldx+oldy*old_xlen_chunks];
109 | + else
110 | + chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y);
111 | + }
112 | + }
113 | +
114 | public:
115 | typedef typename std::conditional::type reftype;
116 |
117 | @@ -104,12 +151,21 @@ class WorldMap
118 | return Viewport_(parent, lefttop_tile_, rightbot_tile_, origin);
119 | }
120 |
121 | - reftype at(int x, int y) const
122 | + reftype at(int x, int y)
123 | {
124 | int tilex = x+origin.x;
125 | int tiley = y+origin.y;
126 | if (!((tilex >= lefttop_tile.x) && (tiley >= lefttop_tile.y) && (tilex < rightbot_tile.x) && (tiley < rightbot_tile.y)))
127 | - throw std::invalid_argument("invalid coordinate for Viewport");
128 | + {
129 | + Pos new_lefttop_tile(lefttop_tile);
130 | + Pos new_rightbot_tile(rightbot_tile);
131 | + if (tilex < lefttop_tile.x) new_lefttop_tile.x = tilex;
132 | + else if (tilex >= rightbot_tile.x) new_rightbot_tile.x = tilex+1;
133 | + if (tiley < lefttop_tile.y) new_lefttop_tile.y = tiley;
134 | + else if (tiley >= rightbot_tile.y) new_rightbot_tile.y = tiley+1;
135 | +
136 | + extend_self(new_lefttop_tile, new_rightbot_tile);
137 | + }
138 | int chunkx = chunkidx(tilex) - lefttop_chunk.x;
139 | int chunky = chunkidx(tiley) - lefttop_chunk.y;
140 | assert(chunkx >= 0);
141 | @@ -122,7 +178,7 @@ class WorldMap
142 |
143 | return (*chunkcache[chunkx+chunky*xlen_chunks])[relx][rely];
144 | }
145 | - reftype at(const Pos& pos) const { return at(pos.x, pos.y); }
146 | + reftype at(const Pos& pos) { return at(pos.x, pos.y); }
147 | };
148 |
149 |
150 |
--------------------------------------------------------------------------------
/eval/viewports/norm_O2.patch:
--------------------------------------------------------------------------------
1 | diff --git a/Makefile b/Makefile
2 | index 6ff1b97..3a8a4b6 100644
3 | --- a/Makefile
4 | +++ b/Makefile
5 | @@ -13,7 +13,7 @@ MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes
6 |
7 |
8 |
9 | -DEBUGFLAGS = -g -D_GLIBCXX_DEBUG #-fsanitize=undefined,address
10 | +DEBUGFLAGS = -g -O2 -D_GLIBCXX_DEBUG #-fsanitize=undefined,address
11 | FASTFLAGS = -O2
12 | CXXFLAGS_BASE = -std=c++14
13 | CFLAGS_BASE = -std=c99
14 | diff --git a/pathfinding.cpp b/pathfinding.cpp
15 | index a993100..355098a 100644
16 | --- a/pathfinding.cpp
17 | +++ b/pathfinding.cpp
18 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal)
19 |
20 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size)
21 | {
22 | - auto view = map.dumb_view(Pos(0,0));
23 | + auto view = map.view(Pos(-800,-500), Pos(500,500), Pos(0,0));
24 |
25 | assert(size<=1.);
26 | vector result;
27 |
--------------------------------------------------------------------------------
/eval/viewports/norm_noopt.patch:
--------------------------------------------------------------------------------
1 | diff --git a/pathfinding.cpp b/pathfinding.cpp
2 | index a993100..355098a 100644
3 | --- a/pathfinding.cpp
4 | +++ b/pathfinding.cpp
5 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal)
6 |
7 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size)
8 | {
9 | - auto view = map.dumb_view(Pos(0,0));
10 | + auto view = map.view(Pos(-800,-500), Pos(500,500), Pos(0,0));
11 |
12 | assert(size<=1.);
13 | vector result;
14 |
--------------------------------------------------------------------------------
/eval/viewports/output1.txt.bz2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Windfisch/factorio-bot/ccef051f0d7db5b6cf9160e2bc5ef418b4bceabc/eval/viewports/output1.txt.bz2
--------------------------------------------------------------------------------
/factorio_io.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #pragma once
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include "pathfinding.hpp"
29 | #include "worldmap.hpp"
30 | #include "pos.hpp"
31 | #include "area.hpp"
32 | #include "rcon.h"
33 | #include "player.h"
34 | #include "resource.hpp"
35 | #include "entity.h"
36 | #include "item.h"
37 | #include "recipe.h"
38 | #include "action.hpp"
39 | #include "worldlist.hpp"
40 | #include "defines.h"
41 | #include "graphics_definitions.h"
42 | #include "item_storage.h"
43 |
44 | class FactorioGame
45 | {
46 | private:
47 | Rcon rcon;
48 |
49 | std::string line_buffer;
50 |
51 | std::string factorio_file_prefix;
52 | std::ifstream factorio_file;
53 | int factorio_file_id = 1;
54 |
55 | void change_file_id(int new_id);
56 | std::string factorio_file_name();
57 | std::string remove_line_from_buffer();
58 |
59 | double max_entity_radius = 0.;
60 | std::unordered_map< std::string, std::unique_ptr > entity_prototypes;
61 | std::unordered_map< std::string, std::unique_ptr > item_prototypes;
62 | std::unordered_map< std::string, std::unique_ptr > recipes;
63 |
64 | public:
65 | WorldList actual_entities; // list of entities that are actually there per chunk
66 | WorldList desired_entities; // list of entities that we expect to be there per chunk
67 |
68 | // the GraphicsDefinition has either one or four entries: north east south west.
69 | std::unordered_map< std::string, std::vector > graphics_definitions;
70 |
71 | private:
72 | /* desired_entities and actual_entities deviate because of the following reasons:
73 | * - trees are irrelevant for desired_entities and thus omitted
74 | * - an entity was scheduled to be built, but is still awaiting its construction
75 | * (walking there takes some time)
76 | * - an entity was destroyed by a biter attack
77 | * - an entity was reconfigured by another player
78 | */
79 |
80 | void parse_graphics(const std::string& data);
81 | void parse_tiles(const Area& area, const std::string& data);
82 | void parse_resources(const Area& area, const std::string& data);
83 | void parse_entity_prototypes(const std::string& data);
84 | void parse_item_prototypes(const std::string& data);
85 | void parse_recipes(const std::string& data);
86 | void parse_action_completed(const std::string& data);
87 | void parse_players(const std::string& data);
88 | void parse_objects(const Area& area, const std::string& data);
89 | void parse_item_containers(const std::string& data);
90 | void update_walkmap(const Area& area);
91 | void parse_mined_item(const std::string& data);
92 | void parse_inventory_changed(const std::string& data);
93 |
94 | void resolve_references_to_items();
95 |
96 | /** flood-fills the resource patch at (x,y), but only the part with a patch_id being
97 | * NOT_YET_ASSIGNED, generating a new ResourcePatch from it. Stops at already-assigned
98 | * entries of the same resource type, or at different types. Any adjacent same-type
99 | * patches ("neighbors") and the newly generated are merged together. The largest neighbor
100 | * is preserved and extended, while all other neighbors and the newly generated (temporary,
101 | * in that case) patch are deleted.
102 | * precondition: view.at(x,y).patch_id == NOT_YET_ASSIGNED
103 | * precondition: view is by at least radius larger than the NOT_YET_ASSIGNED entries inside it
104 | * postcondition: view.at(x,y)'s and adjacent coordinates' patch_id fields are changed from
105 | * NOT_YET_ASSIGNED to an actual value.
106 | */
107 | void floodfill_resources(WorldMap::Viewport& view, const Area& area /* FIXME remove the area parameter */, int x, int y, int radius);
108 | int next_free_resource_id = 1;
109 | int last_tick = 0;
110 |
111 |
112 | struct best_before_entity_t
113 | {
114 | int last_valid_tick;
115 | Entity entity;
116 | };
117 | std::vector pending_entities;
118 |
119 | /** changes the type of the resource-field 'entry' to new_type (which can be NONE, in which case
120 | * `entity` will be ignored). The resulting patch_id will always be NOT_YET_ASSIGNED.
121 | * The previous patch (if entry.type was not NONE) will have the `position` removed, and
122 | * will be removed from the patch list, if it would be empty after the operation.
123 | */
124 | void update_resource_field(Resource& entry, Resource::type_t new_type, Pos position, Entity entity);
125 |
126 | public:
127 | FactorioGame(std::string prefix);
128 | void rcon_connect(std::string host, int port, std::string password);
129 | void rcon_call(std::string func, std::string args);
130 | void rcon_call(std::string func, int player_id, std::string args);
131 | void rcon_call(std::string func, int action_id, int player_id, std::string args);
132 | std::string read_packet();
133 |
134 | /** parses a packet. returns true if this results in a consistent gamestate (i.e., on "tick" messages) */
135 | bool parse_packet(const std::string& data);
136 | int get_tick() { return last_tick; }
137 | void register_pending_entity(int tick, const Entity& ent) { pending_entities.push_back({tick,ent}); }
138 |
139 | // never use these functions directly, use player actions instead
140 | void set_waypoints(int action_id, int player_id, const std::vector& waypoints);
141 | void set_mining_target(int action_id, int player_id, Entity target);
142 | void unset_mining_target(int player_id);
143 | void start_crafting(int action_id, int player_id, std::string recipe, int count=1);
144 | void place_entity(int player_id, std::string item_name, Pos_f pos, dir4_t direction);
145 | void insert_to_inventory(int player_id, std::string item_name, int amount, Entity entity, inventory_t inventory);
146 | void remove_from_inventory(int player_id, std::string item_name, int amount, Entity entity, inventory_t inventory);
147 |
148 | [[deprecated("set player actions instead")]]
149 | void walk_to(int id, const Pos& dest);
150 |
151 | WorldMap walk_map;
152 | WorldMap resource_map;
153 | std::set< std::shared_ptr > resource_patches;
154 |
155 | void resource_bookkeeping(const Area& area, WorldMap::Viewport resview);
156 | void assert_resource_consistency() const; // only for debugging purposes
157 |
158 | std::vector players;
159 |
160 | const EntityPrototype& get_entity_prototype(std::string name) const { return *entity_prototypes.at(name); }
161 | const ItemPrototype& get_item_prototype(std::string name) const { return *item_prototypes.at(name); }
162 | const Recipe* get_recipe(std::string name) const { return recipes.at(name).get(); }
163 |
164 | /** returns the best recipe for crafting the item */
165 | const Recipe* get_recipe_for(const ItemPrototype*) const;
166 | /** returns a list of all recipes that produce this item */
167 | const std::vector get_recipes_for(const ItemPrototype*) const;
168 | /** returns an item that has the entity as place_result */
169 | const ItemPrototype* get_item_for(const EntityPrototype*) const;
170 | };
171 |
--------------------------------------------------------------------------------
/filter.sh:
--------------------------------------------------------------------------------
1 | tee log.txt | grep --line-buffered -ve '^\S*\(dump\|detail\|floodfill_resources\|pathfinding\|verbose\|action_registry\|core\.objects\|calculate_schedule\)\S*:' | sed 's/^\S*: //'
2 |
--------------------------------------------------------------------------------
/goal.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #include "goal.hpp"
20 | #include "factorio_io.h"
21 | #include "util.hpp"
22 | #include "logging.hpp"
23 |
24 | #include
25 |
26 | using boost::make_iterator_range;
27 |
28 | using namespace std;
29 |
30 | const int RESOURCE_REACH = 2; // max distance for mining a tree, ore field or simple-entity
31 | const int REACH = 4; // max distance for mining a player-built entity // FIXME magic constant. move to common header
32 | const int SAFETY_DISTANCE = 2; // minimum distance for building a player-built entity
33 |
34 | static_assert(REACH > SAFETY_DISTANCE);
35 |
36 | namespace goal
37 | {
38 |
39 | bool GoalList::all_fulfilled(FactorioGame* game) const
40 | {
41 | for (const auto& goal : (*this))
42 | if (!goal->fulfilled(game))
43 | return false;
44 |
45 | return true;
46 | }
47 |
48 | vector> GoalList::calculate_actions(FactorioGame* game, int player, std::optional owner) const
49 | {
50 | vector> result;
51 |
52 | for (const auto& goal : (*this))
53 | {
54 | auto actions = goal->calculate_actions(game, player, owner);
55 | result.insert(std::end(result),
56 | std::make_move_iterator(std::begin(actions)),
57 | std::make_move_iterator(std::end(actions)));
58 | }
59 |
60 | return result;
61 | }
62 |
63 | bool PlaceEntity::fulfilled(FactorioGame* game) const
64 | {
65 | return game->actual_entities.search_or_null(entity) != nullptr;
66 | }
67 | vector> PlaceEntity::_calculate_actions(FactorioGame* game, int player, std::optional owner) const
68 | {
69 | const float CLEAR_MARGIN = 0.1f; // FIXME magic number
70 | vector> result;
71 |
72 | // clear the area from trees and rocks first
73 | Area_f area_to_clear = entity.collision_box().expand(CLEAR_MARGIN);
74 | for (const Entity& offending_entity : game->actual_entities.overlap_range(area_to_clear))
75 | if (offending_entity.proto->type == "tree" || offending_entity.proto->type == "simple-entity")
76 | {
77 | result.push_back( make_unique(game, player, offending_entity.pos, RESOURCE_REACH) );
78 | result.push_back( make_unique(game, player, nullopt, offending_entity) );
79 | }
80 |
81 | // TODO FIXME: ensure that the player isn't in the way.
82 | result.push_back( make_unique(game, player, entity.collision_box(), REACH, SAFETY_DISTANCE) );
83 | result.push_back( make_unique(game, player, owner, game->get_item_for(entity.proto), entity) );
84 | return result;
85 | }
86 |
87 | bool RemoveEntity::fulfilled(FactorioGame* game) const
88 | {
89 | return game->actual_entities.search_or_null(entity) == nullptr;
90 | }
91 | vector> RemoveEntity::_calculate_actions(FactorioGame* game, int player, std::optional owner) const
92 | {
93 | vector> result;
94 |
95 | bool is_resource = entity.proto->type == "tree" || entity.proto->type == "simple-entity" || entity.proto->type == "resource";
96 |
97 | result.push_back( make_unique(game, player, entity.pos, is_resource ? RESOURCE_REACH : REACH) );
98 | result.push_back( make_unique(game, player, owner, entity) );
99 | return result;
100 | }
101 |
102 | bool InventoryPredicate::fulfilled(FactorioGame* game) const
103 | {
104 | Entity* ent = game->actual_entities.search_or_null(entity);
105 | const MultiInventory& actual_inventory = ent ? ent->data().inventories : MultiInventory();
106 | if (type == POSITIVE)
107 | {
108 | for (auto [item, amount] : desired_inventory)
109 | if (actual_inventory.get_or(item, inventory_type, 0) < amount)
110 | return false;
111 | return true;
112 | }
113 | else
114 | {
115 | for (const auto& [key, amount] : make_iterator_range(actual_inventory.inv_begin(inventory_type), actual_inventory.inv_end(inventory_type)))
116 | {
117 | if (get_or(desired_inventory, key.item) > amount)
118 | return false;
119 | }
120 | return true;
121 | }
122 | }
123 | vector> InventoryPredicate::_calculate_actions(FactorioGame* game, int player, std::optional owner) const
124 | {
125 | vector> result;
126 | result.push_back( make_unique(game, player, entity.pos) );
127 |
128 | Entity* ent = game->actual_entities.search_or_null(entity);
129 | const MultiInventory& actual_inventory = ent ? ent->data().inventories : MultiInventory();
130 |
131 | if (type == POSITIVE)
132 | {
133 | for (auto [item, desired_amount] : desired_inventory)
134 | {
135 | auto actual_amount = actual_inventory.get_or(item, inventory_type, 0);
136 | if (actual_amount < desired_amount)
137 | result.push_back(make_unique(game, player, owner, item, desired_amount-actual_amount, entity, inventory_type));
138 | }
139 | }
140 | else
141 | {
142 | for (const auto& [key, actual_amount] : make_iterator_range(actual_inventory.inv_begin(inventory_type), actual_inventory.inv_end(inventory_type)))
143 | {
144 | auto desired_amount = get_or(desired_inventory, key.item);
145 | if (actual_amount > desired_amount)
146 | result.push_back(make_unique(game, player, owner, key.item, actual_amount-desired_amount, *ent, inventory_type));
147 | }
148 | }
149 |
150 | return result;
151 | }
152 |
153 | string PlaceEntity::str() const
154 | {
155 | return "PlaceEntity(" + entity.str() + ")";
156 | }
157 |
158 | string RemoveEntity::str() const
159 | {
160 | return "RemoveEntity(" + entity.str() + ")";
161 | }
162 |
163 | string InventoryPredicate::str() const
164 | {
165 | string result = "InventoryPredicate(" + entity.str() + "[" + inventory_names[inventory_type] + "] -> at ";
166 | result += (type == POSITIVE ? "least " : "most ");
167 | result += desired_inventory.str() + ")";
168 | return result;
169 | }
170 |
171 | void GoalList::dump() const
172 | {
173 | Logger log("goallist_dump");
174 | log << "GoalList:" << endl;
175 | for (const auto& goal : (*this))
176 | log << "\t" << goal->str() << endl;
177 | }
178 | void GoalList::dump(FactorioGame* game) const
179 | {
180 | Logger log("goallist_dump");
181 | log << "GoalList:" << endl;
182 | for (const auto& goal : (*this))
183 | log << "\t" << goal->str(game) << endl;
184 | }
185 |
186 | }
187 |
--------------------------------------------------------------------------------
/goal.hpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2017, 2018 Florian Jung
3 | *
4 | * This file is part of factorio-bot.
5 | *
6 | * factorio-bot is free software: you can redistribute it and/or
7 | * modify it under the terms of the GNU General Public License,
8 | * version 3, as published by the Free Software Foundation.
9 | *
10 | * factorio-bot is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with factorio-bot. If not, see .
17 | */
18 |
19 | #pragma once
20 |
21 | #include "entity.h"
22 | #include "action.hpp"
23 |
24 | #include
25 | #include
26 | #include
27 |
28 | namespace goal
29 | {
30 |
31 | /** Interface for all immediate goals. An immediate goal is a predicate on the "relevant" game state, which consists of
32 | * the map's state (entities, their settings and inventories) and the players' special inventories, such as the axe.
33 | *
34 | * These goals can (and will) be reordered at will.
35 | */
36 | struct GoalInterface
37 | {
38 | /** calculates the actions required to fulfill this goal.
39 | *
40 | * returns the empty list if fulfilled(FactorioGame* game)==true.
41 | */
42 | virtual std::vector> calculate_actions(FactorioGame* game, int player, std::optional owner) const
43 | {
44 | if (!fulfilled(game))
45 | return _calculate_actions(game, player, owner);
46 | else
47 | return {};
48 | }
49 |
50 | /** internal helper to calculate the actions required to fulfill this goal.
51 | * Its result will be ignored, if fulfilled(FactorioGame* game)==true. */
52 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const = 0;
53 | virtual bool fulfilled(FactorioGame* game) const = 0;
54 | virtual std::string str(FactorioGame* game) const
55 | {
56 | return (fulfilled(game) ? "[x] " : "[ ] ") + str();
57 | }
58 | virtual std::string str() const = 0;
59 | virtual ~GoalInterface() = default;
60 | };
61 |
62 | struct PlaceEntity : public GoalInterface
63 | {
64 | Entity entity;
65 |
66 | PlaceEntity(Entity e) : entity(e) {}
67 |
68 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const;
69 | virtual bool fulfilled(FactorioGame* game) const;
70 | std::string str() const;
71 | };
72 |
73 | struct RemoveEntity : public GoalInterface
74 | {
75 | Entity entity;
76 |
77 | RemoveEntity(Entity e) : entity(e) {}
78 |
79 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional