├── Interface ├── output │ └── fitness.txt ├── problem │ └── matrix.txt ├── config.ini ├── input │ └── matrix.txt ├── constants.py └── interface.py ├── Mod └── interface_0.1.1 │ ├── README.md │ ├── control.lua │ ├── item.lua │ ├── info.json │ ├── data.lua │ ├── settings.lua │ └── metaconfig.lua └── README.md /Interface/output/fitness.txt: -------------------------------------------------------------------------------- 1 | matrix.txt_output_0: 0 2 | matrix.txt_input_0: 21 3 | -------------------------------------------------------------------------------- /Mod/interface_0.1.1/README.md: -------------------------------------------------------------------------------- 1 | Adds some commands to Factorio for interacting with external apps. 2 | 3 | The commands are: 4 | 5 | /pos 6 | - return the current position of the character -------------------------------------------------------------------------------- /Mod/interface_0.1.1/control.lua: -------------------------------------------------------------------------------- 1 | local version = 1 2 | 3 | -- test command to print a player's position 4 | commands.add_command("pos", "send stats", function(table) 5 | p = game.players[1].position 6 | rcon.print(p) 7 | game.players[1].print(p) 8 | end) 9 | -------------------------------------------------------------------------------- /Interface/problem/matrix.txt: -------------------------------------------------------------------------------- 1 | -1 -1 -1 -1 -1 -1 -1 -1 -1 2 | -1 0 0 0 0 0 0 0 -1 3 | -6 -8 0 0 0 0 0 0 -1 4 | -1 0 0 0 0 0 0 0 -1 5 | -1 0 0 0 0 0 0 -3 -6 6 | -1 0 0 0 0 0 0 0 -1 7 | -1 0 0 0 0 0 0 0 -1 8 | -1 0 0 0 0 0 0 0 -1 9 | -1 -1 -1 -1 -1 -1 -1 -1 -1 10 | -------------------------------------------------------------------------------- /Mod/interface_0.1.1/item.lua: -------------------------------------------------------------------------------- 1 | local my_inserter = table.deepcopy(data.raw["inserter"]["fast-inserter"]) 2 | -- to insert a new item based on fast inserter which needs no power 3 | my_inserter.name = "inputinserter" 4 | my_inserter.energy_source.type = "void" 5 | 6 | data:extend{my_inserter} 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mod/interface_0.1.1/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interface", 3 | "version": "0.1.1", 4 | "title": "Interface", 5 | "author": "eLeMeNOhPi", 6 | "factorio_version": "1.0", 7 | "dependencies": ["base >= 1.0"], 8 | "description": "Adds some commands to Factorio for interacting with external apps." 9 | } 10 | -------------------------------------------------------------------------------- /Interface/config.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | debug = false 3 | seed = 303 4 | inputfile = input/matrix.txt 5 | problemfile = problem/matrix.txt 6 | outputfile = output/fitness.txt 7 | inputs = 1|stone+500|iron-ore+350 2|wood+1000 8 | inputsdefaults = iron-ore|1000 9 | ip = 127.0.0.1 10 | port = 27015 11 | password = 123 12 | gamespeed = 1000000 13 | waittime = 0.1 14 | -------------------------------------------------------------------------------- /Interface/input/matrix.txt: -------------------------------------------------------------------------------- 1 | 4.000000000000000000e+00 4.000000000000000000e+00 0.000000000000000000e+00 1.000000000000000000e+00 4.000000000000000000e+00 2 | 1.000000000000000000e+00 3.000000000000000000e+00 4.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00 3 | 2.000000000000000000e+00 0.000000000000000000e+00 4.000000000000000000e+00 1.000000000000000000e+00 4.000000000000000000e+00 4 | 0.000000000000000000e+00 2.000000000000000000e+00 0.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00 5 | 2.000000000000000000e+00 2.000000000000000000e+00 2.000000000000000000e+00 1.000000000000000000e+00 1.000000000000000000e+00 6 | -------------------------------------------------------------------------------- /Mod/interface_0.1.1/data.lua: -------------------------------------------------------------------------------- 1 | require("item") 2 | 3 | -- Code for replacing the water in the map 4 | local noise = require("noise") -- From the core mod 5 | 6 | if settings.startup['ctg-enable'].value and settings.startup['ctg-remove-default-water'].value then 7 | -- Note sure what probability_expression does. Setting it to zero does not turn off water. 8 | local nowater = { 9 | probability_expression = noise.to_noise_expression(-math.huge) 10 | } 11 | 12 | local t = data.raw.tile 13 | t.water.autoplace = nowater 14 | t.deepwater.autoplace = nowater 15 | t['water-green'].autoplace = nowater 16 | t['deepwater-green'].autoplace = nowater 17 | end -------------------------------------------------------------------------------- /Mod/interface_0.1.1/settings.lua: -------------------------------------------------------------------------------- 1 | require("metaconfig") 2 | 3 | -- sets the defaults for terrein. This script works together with the other scripts 4 | 5 | local o = 0 6 | for i, setting in ipairs(meta.settings) do 7 | o = o + 1 8 | local s = { 9 | type = setting[2] .. '-setting', 10 | name = "ctg-" .. setting[1], 11 | setting_type = meta.setting_type, 12 | default_value = setting[3], 13 | order = string.format('ctg-%04d', i) 14 | } 15 | if setting[2] == 'string' and setting[4] ~= nil then 16 | s.allowed_values = setting[4] 17 | end 18 | if setting[2] == 'int' and setting[4] ~= nil then 19 | s.minimum_value = setting[4][1] 20 | s.maximum_value = setting[4][2] 21 | end 22 | data:extend{s} 23 | end 24 | 25 | data:extend{ 26 | { 27 | type = 'bool-setting', 28 | name = 'ctg-enable', 29 | setting_type = 'startup', 30 | default_value = true, 31 | order = 'ctg-s000' 32 | }, 33 | { 34 | type = 'bool-setting', 35 | name = 'ctg-remove-default-water', 36 | setting_type = 'startup', 37 | default_value = true, 38 | order = 'ctg-s001' 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Interface/constants.py: -------------------------------------------------------------------------------- 1 | MAP = { 2 | # input fast inserters (no power needed) 3 | -2 : "inputinserter_north", 4 | -3 : "inputinserter_east", 5 | -4 : "inputinserter_south", 6 | -5 : "inputinserter_west", 7 | # chest 8 | -6 : "steel-chest", 9 | # output fast inserters (no power needed) 10 | -7 : "inputinserter_north", 11 | -8 : "inputinserter_east", 12 | -9 : "inputinserter_south", 13 | -10 : "inputinserter_west", 14 | # obstacle 15 | -1 : "stone-wall", 16 | 17 | 18 | 19 | # normal transport belts 20 | 1 : "transport-belt_north", 21 | 2 : "transport-belt_east", 22 | 3 : "transport-belt_south", 23 | 4 : "transport-belt_west", 24 | # fast transport belts 25 | 5 : "fast-transport-belt_north", 26 | 6 : "fast-transport-belt_east", 27 | 7 : "fast-transport-belt_south", 28 | 8 : "fast-transport-belt_west", 29 | # express transport belts 30 | 9 : "express-transport-belt_north", 31 | 10 : "express-transport-belt_east", 32 | 11 : "express-transport-belt_south", 33 | 12 : "express-transport-belt_west", 34 | # normal spliters 35 | 13 : "splitter_north", 36 | 14 : "splitter_east", 37 | 15 : "splitter_south", 38 | 16 : "splitter_west", 39 | # fast spliters 40 | 17 : "fast-splitter_north", 41 | 18 : "fast-splitter_east", 42 | 19 : "fast-splitter_south", 43 | 20 : "fast-splitter_west", 44 | # express spliters 45 | 21 : "express-splitter_north", 46 | 22 : "express-splitter_east", 47 | 23 : "express-splitter_south", 48 | 24 : "express-splitter_west", 49 | # normal underground belts 50 | 25 : "underground-belt_north", 51 | 26 : "underground-belt_east", 52 | 27 : "underground-belt_south", 53 | 28 : "underground-belt_west", 54 | # fast underground belts 55 | 29 : "fast-underground-belt_north", 56 | 30 : "fast-underground-belt_east", 57 | 31 : "fast-underground-belt_south", 58 | 32 : "fast-underground-belt_west", 59 | # express underground belts 60 | 33 : "express-underground-belt_north", 61 | 34 : "express-underground-belt_east", 62 | 35 : "express-underground-belt_south", 63 | 36 : "express-underground-belt_west", 64 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FactorioBeltProblemGECCO 2 | 3 | This repository contains the Factorio interface first described in our pre-print: https://arxiv.org/abs/2102.04871 4 | 5 | ## Installation instructions for Docker (recommended) 6 | 7 | 1) Install docker ([link](https://www.docker.com/products/docker-desktop)). 8 | 2) Install your preferred image (such as goofball222's image ([link](https://hub.docker.com/r/goofball222/factorio/)). 9 | 3) In the docker-based directory, navigate to the /factorio/mods/ sub-directory. Place the interface_0.1.1 folder found here on Git (from within the /Mod/ folder) within the /factorio/mods/ folder. 10 | 4) Modify all required settings for your local machine. Some changes we considered include: 11 | 4.1) Ports for RCON 12 | 4.2) Ports for your docker container - though defaults should suffice 13 | 4.3) Auto-save interval 14 | 4.4) RCON.pwd 15 | 4.5) Server visibility 16 | 5) Restart docker / container. 17 | 6) The interface should now be working. 18 | 19 | If you're having issues with docker (we certainly had a few) pay close attention to IPs and ports made available within docker and make sure they align with those in RCON settings. We cannot assist with any docker related issues - so please only contact us with issues relating to the interface itself. 20 | 21 | ## Installation instructions for Factorio (recommended for testing purposes only) 22 | 23 | 1) Purchase Factorio (I recommend via the Factorio website, as more money goes to Wube this way - otherwise you can purchase it on Steam) 24 | 2) Download Factorio. 25 | 3) Navigate to the mods folder 26 | For Windows: C:\Users\user name\AppData\Roaming\Factorio\mods\ 27 | For Linux: ~/.factorio/mods 28 | For Mac OS X: ~/Library/Application Support/factorio/mods 29 | 4) Place the /interface_0.1.1/ folder into the mods folder. 30 | 5) Run Factorio, click on "Mods", and turn on interface_0.1.1 31 | 32 | ## Running 33 | 34 | 1) Write your optimizer in your preferred language, which can create *.txt files containing integer-encoded solutions (see FactorioBeltProblemGECCO/blob/main/Interface/input/matrix.txt for an example). 35 | 2) In the language of your choice, call the interface script (FactorioBeltProblemGECCO/Interface/interface.py) with the correct parameters (see interface.py's main() method for details). 36 | 3) Repeat #2 as many times as required with new input files and parameters. 37 | 38 | Of course running on docker is much faster in a graphic-less mode than the standard Factorio version, but it can be useful to see the solutions visually to troubleshoot the solutions your optimizer creates. 39 | 40 | Please get in touch with Dr. Ken Reid for any collaboration requests: ken@kenreid.co.uk 41 | -------------------------------------------------------------------------------- /Mod/interface_0.1.1/metaconfig.lua: -------------------------------------------------------------------------------- 1 | -- functions for custom terrein generation 2 | 3 | meta = { 4 | water_colors = {"blue", "green"}, 5 | setting_type = "runtime-global", 6 | -- List of pairs of name of preset and code to generate preset 7 | pattern_presets = { 8 | {"custom", nil}, 9 | {"spiral", "Union(Spiral(1.3, 0.4), Rectangle(-105, -2, 115, 2))"}, 10 | {"arithmetic spiral", "ArithmeticSpiral(50, 0.4)"}, 11 | {"rectilinear spiral", "Zoom(RectSpiral(), 50)"}, 12 | {"triple spiral", "AngularRepeat(Spiral(1.6, 0.5), 3)"}, 13 | {"crossing spirals", "Union(Spiral(1.4, 0.4), Spiral(1 / 1.6, 0.2))"}, 14 | {"natural archipelago", 15 | -- "NoiseCustom({exponent=1.5,noise={0.3,0.4,1,1,1.2,0.8,0.7,0.4,0.3,0.2},land_percent=0.13})"}, 16 | "Union(" .. 17 | "NoiseCustom({exponent=1.5,noise={0.3,0.4,1,1,1.2,0.8,0.7,0.4,0.3,0.2},land_percent=0.07})," .. 18 | "NoiseCustom({exponent=1.9,noise={1,1,1,1,1,1,0.7,0.4,0.3,0.2},land_percent=0.1," .. 19 | "start_on_land=false,start_on_beach=false}))"}, 20 | {"natural big islands", 21 | "NoiseCustom({exponent=2.3,noise={1,1,1,1,1,1,0.7,0.4,0.3,0.2},land_percent=0.2})"}, 22 | {"natural continents", 23 | "NoiseCustom({exponent=2.4,noise={1,1,1,1,1,1,1,0.6,0.3,0.2},land_percent=0.35})"}, 24 | {"natural half land", 25 | "NoiseCustom({exponent=2,noise={0.5,1,1,1,1,1,0.7,0.4,0.3,0.2},land_percent=0.5})"}, 26 | {"natural big lakes", 27 | "NoiseCustom({exponent=2.3,noise={0.5,0.8,1,1,1,1,0.7,0.4,0.3,0.2},land_percent=0.65})"}, 28 | {"natural medium lakes", 29 | "NoiseCustom({exponent=2.1,noise={0.3,0.6,1,1,1,1,0.7,0.4,0.3,0.2},land_percent=0.86})"}, 30 | {"natural small lakes", 31 | -- "NoiseCustom({exponent=1.8,noise={0.2,0.3,0.4,0.6,1,1,0.7,0.4,0.3,0.2},land_percent=0.96})"}, 32 | "NoiseCustom({exponent=1.5,noise={0.05,0.1,0.4,0.7,1,0.7,0.3,0.1},land_percent=0.92})"}, 33 | {"pink noise (good luck...)", "NoiseExponent({exponent=1,land_percent = 0.35})"}, 34 | {"radioactive", "Union(AngularRepeat(Halfplane(), 3), Circle(38))"}, 35 | {"comb", "Zoom(Comb(), 50)"}, 36 | {"cross", "Cross(50)"}, 37 | {"cross and circles", "Union(Cross(20), ConcentricBarcode(30, 60))"}, 38 | {"crossing bars", "Union(Barcode(nil, 10, 20), Barcode(nil, 20, 50))"}, 39 | {"grid", "Zoom(Grid(), 50)"}, 40 | {"skew grid", "Zoom(Affine(Grid(), 1, 1, 1, 0), 50)"}, 41 | {"distorted grid", "Distort(Zoom(Grid(), 30))"}, 42 | {"maze 1 (fibonacci)", "Tighten(Zoom(Maze1(), 50))"}, 43 | {"maze 2 (DLA)", "Tighten(Zoom(Maze2(), 50))"}, 44 | {"maze 3 (percolation)", "Tighten(Zoom(Maze3(0.6), 50))"}, 45 | {"polar maze 3", "Zoom(AngularRepeat(Maze3(), 3), 50)"}, 46 | {"bridged maze 3", "IslandifySquares(Maze3(), 50, 10, 4)"}, 47 | {"thin branching fractal", "Fractal(1.5, 40, 0.4)"}, 48 | {"mandelbrot", "Tile(Mandelbrot(300), 150, 315, -600, -315)"}, 49 | {"jigsaw islands", "Zoom(JigsawIslands(0.3), 40)"}, 50 | {"pink noise maze", 51 | "Intersection(Zoom(Maze2(), 50), NoiseExponent{exponent=1,land_percent=0.8})"}, 52 | {"tiny pot holes", "TP(nil, Zoom(Maze3(0.997), 2))"}, 53 | {"small pot holes", "TP(nil, Zoom(Maze3(0.994), 3))"} 54 | } 55 | -- void_pattern_presets = { 56 | -- {"tiny pot holes", "Maze3(0.997)"}, 57 | -- {"small pot holes", "Zoom(Maze3(0.994), 3)"}, 58 | -- {"custom", nil} 59 | -- } 60 | } 61 | 62 | function preset_by_name(name) 63 | for _, item in ipairs(meta.pattern_presets) do 64 | if item[1] == name then 65 | return item[2] 66 | end 67 | end 68 | return nil 69 | end 70 | 71 | local function map_first(xs) 72 | local result = {} 73 | for i, x in pairs(xs) do 74 | table.insert(result, x[1]) 75 | end 76 | return result 77 | end 78 | 79 | local function mk_bool(name, def) 80 | return {name, "bool", def} 81 | end 82 | local function mk_str(name, def) 83 | return {name, "string", def} 84 | end 85 | local function mk_dropdown(name, opts, default) 86 | if default == nil then 87 | return {name, "string", opts[1], opts} 88 | else 89 | return {name, "string", default, opts} 90 | end 91 | end 92 | local function mk_int(name, def, range) 93 | if range == nil then 94 | return {name, "int", def} 95 | else 96 | return {name, "int", def, range} 97 | end 98 | end 99 | 100 | meta.settings = { 101 | mk_dropdown("pattern-preset", map_first(meta.pattern_presets), "maze 2 (DLA)"), 102 | mk_str("pattern-custom", "(lua code goes here)"), 103 | 104 | mk_str("pattern-v1", "nil"), 105 | mk_str("pattern-v2", "nil"), 106 | mk_str("pattern-v3", "nil"), 107 | mk_str("pattern-v4", "nil"), 108 | mk_str("pattern-v5", "nil"), 109 | mk_str("pattern-v6", "nil"), 110 | mk_str("pattern-v7", "nil"), 111 | mk_str("pattern-v8", "nil"), 112 | 113 | mk_dropdown("water-color", meta.water_colors), 114 | mk_int("seed", 0, {0, 2 ^ 32}), 115 | 116 | mk_bool("initial-landfill", false), 117 | mk_bool("force-initial-water", false), 118 | mk_bool("big-scan", false), 119 | mk_bool("screenshot", false), 120 | mk_bool("screenshot-zoom", false) 121 | } 122 | -------------------------------------------------------------------------------- /Interface/interface.py: -------------------------------------------------------------------------------- 1 | import factorio_rcon # pip3 install factorio-rcon-py Library to use RCON for Factorio 2 | from datetime import datetime 3 | import numpy as np 4 | import _thread 5 | import time 6 | import random 7 | import configparser 8 | import sys 9 | import argparse 10 | import time 11 | from os import listdir 12 | from os.path import isfile, join 13 | import math 14 | 15 | # constants are defined here 16 | from constants import * 17 | 18 | global debug 19 | 20 | class Interface: 21 | # class constructor 22 | def __init__(self, ip, port, password, inputs, defaults): 23 | self.client = None 24 | self.ip = ip 25 | self.port = port 26 | self.password = password 27 | self.inputs = {} 28 | self.outputs = {} 29 | self.fitness = -1 30 | self.initialinputs = inputs 31 | self.default = defaults 32 | 33 | # connects to the Factorio Console. Needs a running multiplayer server. 34 | def connect(self): 35 | self.client = factorio_rcon.RCONClient(self.ip, self.port, self.password) # IP, Port, Password 36 | 37 | # removes all the entities in the surface 38 | def reset_surface(self): 39 | command = "/c game.surfaces[1].clear()" 40 | command += "; game.surfaces[1].daytime=0" 41 | self.send(command) 42 | 43 | # builds a solution matrix in game 44 | def build(self, matrix, filename, index=0): 45 | filename = filename.split("/") 46 | filename = filename[len(filename)-1] 47 | 48 | # going through the matrix 49 | for x, a in enumerate(matrix): 50 | for y, b in enumerate(a): 51 | if b == 0: 52 | continue 53 | else: 54 | entity = MAP[b] 55 | entity = entity.split("_") 56 | 57 | n = 10 # groups of 3 58 | 59 | # calculate the factorio world position for each entities of the matrix (e.g transport belts / underground belts / spliters etc) 60 | yprime = x+2 + index * (len(matrix) + 6) - n * (len(matrix) + 6) * math.floor(index/n) 61 | xprime = y+2 + math.floor(index/n)*(len(matrix[0]) + 6) 62 | 63 | # create the entities 64 | if len(entity) == 2: 65 | self.create_entity(name=entity[0], position=(xprime, yprime), filename=filename, direction=entity[1]) 66 | else: 67 | self.create_entity(name=entity[0], position=(xprime, yprime), filename=filename) 68 | 69 | # sends a message to rcon 70 | def send(self, message): 71 | return self.client.send_command("{}".format(message)) 72 | 73 | # builds a given problem matrix for each solution that needs to be tested 74 | def make_problem(self, matrix, files): 75 | # for each solution file 76 | for index, filename in enumerate(files): 77 | filename = filename.split("/") 78 | filename = filename[len(filename)-1] 79 | xl = len(matrix) 80 | yl = len(matrix[0]) 81 | if not filename in self.outputs.keys(): 82 | self.outputs[filename] = [] 83 | if not filename in self.inputs.keys(): 84 | self.inputs[filename] = [] 85 | 86 | # uses same principle as the build function 87 | for x, a in enumerate(matrix): 88 | for y, b in enumerate(a): 89 | n = 10 # groups of 3 90 | xprime = x + index * (xl + 2) - n * (xl + 2) * math.floor(index/n) 91 | yprime = y + math.floor(index/n)*(yl + 2) 92 | 93 | if b == 0: 94 | continue 95 | else: 96 | entity = MAP[b] 97 | entity = entity.split("_") 98 | 99 | if entity[0] == "steel-chest": 100 | if (x-1 >= 0 and matrix[x-1][y] == -4) or (y-1 >= 0 and matrix[x][y-1] == -3) or (x+1 < xl and matrix[x+1][y] == -2) or (y+1 < yl and matrix[x][y+1] == -5): 101 | self.inputs[filename].append((yprime, xprime)) 102 | else: 103 | self.outputs[filename].append((yprime, xprime)) 104 | # at this point we have x and y 105 | 106 | # check whether or not the entity has direction 107 | if len(entity) == 2: 108 | self.create_entity(name=entity[0], position=(yprime, xprime), filename=filename, direction=entity[1]) 109 | else: 110 | # no direction like a box for example or a steel-chest 111 | self.create_entity(name=entity[0], position=(yprime, xprime), filename=filename) 112 | 113 | # creates a box. can be full or empty 114 | def create_box(self, position, fill): 115 | x = position[0] 116 | y = position[1] 117 | command = "/c box = game.surfaces[1].create_entity{{name=\"steel-chest\", position={{{}, {}}}, force=game.forces.player}}".format(x, y) 118 | if fill: 119 | command += "; box.insert{name=\"iron-ore\", count=2000}" 120 | self.send(command) 121 | 122 | # creates an entity facing an arbitrary direction on a given position 123 | def create_entity(self, name, position, filename, direction=None): 124 | 125 | x = position[0] 126 | y = position[1] 127 | 128 | if name == "steel-chest" and position in self.inputs[filename]: 129 | 130 | command = "/c box = game.surfaces[1].create_entity{{name=\"steel-chest\", position={{{}, {}}}, force=game.forces.player}}".format(x, y) 131 | command += "; box.insert{name=\"iron-ore\", count=2000}" 132 | return self.client.send_command("{}".format(command)) 133 | 134 | if direction: 135 | command = "/c entity = game.surfaces[1].create_entity{{name=\"{}\", position={{{}, {}}}, direction=defines.direction.{}, force=game.forces.player}}".format(name, x, y, direction) 136 | else: 137 | command = "/c entity = game.surfaces[1].create_entity{{name=\"{}\", position={{{}, {}}}, force=game.forces.player}}".format(name, x, y) 138 | 139 | return self.client.send_command("{}".format(command)) 140 | 141 | # sets the game speed 142 | def set_game_speed(self, speed): 143 | self.send("/c game.speed={}".format(speed)) 144 | 145 | # Returns the number of materials in the input/output box whenever the function is called for all the output/input boxes. Evaluation is done in the optimizer 146 | def evaluate(self): 147 | output = "" 148 | for filename in self.outputs: 149 | for i, pos in enumerate(self.outputs[filename]): 150 | x = pos[0] 151 | y = pos[1] 152 | command = "/c output = game.surfaces[1].find_entities_filtered{{name='steel-chest', position={{{}, {}}}, radius=1}}[1]".format(x, y) 153 | command += "; inventory = output.get_output_inventory()" 154 | command += "; count = inventory.get_item_count()" 155 | command += "; rcon.print(count)" 156 | response = self.send(command) 157 | 158 | output += "{}_output_{}: {}\n".format(filename, i, response) 159 | if debug: 160 | print("evaluate()::response = " + response + "\n") 161 | 162 | for filename in self.inputs: 163 | for i, pos in enumerate(self.inputs[filename]): 164 | x = pos[0] 165 | y = pos[1] 166 | command = "/c input = game.surfaces[1].find_entities_filtered{{name='steel-chest', position={{{}, {}}}, radius=1}}[1]".format(x, y) 167 | command += "; inventory = input.get_output_inventory()" 168 | command += "; count = inventory.get_item_count()" 169 | command += "; rcon.print(count)" 170 | response = self.send(command) 171 | # print("response", response) 172 | 173 | response = 2000 - int(response) 174 | output += "{}_input_{}: {}\n".format(filename, i, response) 175 | if debug: 176 | print("evaluate()::response = " + repr(response) + "\n") 177 | 178 | self.fitness = output 179 | print(output) 180 | 181 | # saves the return value of evaluate (indirectly) in a given path (creates/truncates the file) 182 | def save_fitness(self, path): 183 | flag_save = True 184 | while flag_save: 185 | flag_save = False 186 | try: 187 | file = open(path, "w") 188 | file.write(str(self.fitness)) 189 | file.close() 190 | except: 191 | flag_save = True 192 | # print("Interface Save Error: Retrying") 193 | time.sleep(0.0001) 194 | 195 | 196 | # converts string to boolean 197 | def s2b(str): 198 | return str.lower() in ("yes", "true", "y", "1") 199 | 200 | # The main function 201 | def main(): 202 | # globals 203 | global debug 204 | 205 | # arg parser 206 | parser = argparse.ArgumentParser() 207 | parser.add_argument("-problem", help="path to the problem matrix file") 208 | parser.add_argument("-input", help="path to the input matrix file") 209 | parser.add_argument("-output", help="path to the output/fitness file") 210 | parser.add_argument("-config", help="path to the config file") 211 | parser.add_argument("-idir", help="path to the inputs directory") 212 | args = parser.parse_args() 213 | 214 | config_path = "config.ini" 215 | 216 | if args.config: 217 | config_path = args.config 218 | 219 | # connect to the config file 220 | config = configparser.ConfigParser() 221 | 222 | config.read(config_path) 223 | 224 | # start of configuration from config.ini 225 | debug = s2b(config['DEFAULT']['debug']) 226 | input_file = config['DEFAULT']['inputfile'] 227 | problem_file = config['DEFAULT']['problemfile'] 228 | output_file = config['DEFAULT']['outputfile'] 229 | ip = config['DEFAULT']['ip'] 230 | port = int(config['DEFAULT']['port']) 231 | password = config['DEFAULT']['password'] 232 | speed = config['DEFAULT']['gamespeed'] 233 | seed = config['DEFAULT']['seed'] 234 | wait_time = float(config['DEFAULT']['waittime']) 235 | inputsdefaults = config['DEFAULT']['inputsdefaults'] 236 | inputs = config['DEFAULT']['inputs'] 237 | 238 | # end of configuration from config.ini 239 | 240 | # begin configuration from terminal 241 | if args.input: 242 | input_file = args.input 243 | if args.output: 244 | output_file = args.output 245 | if args.problem: 246 | problem_file = args.problem 247 | 248 | inputDir = "" 249 | flag_multiInput = False 250 | if args.idir: 251 | inputDir = args.idir 252 | flag_multiInput = True 253 | 254 | random.seed(seed) 255 | 256 | # randomly generating a numpy matrix 257 | # input_matrix = np.random.randint(size, size=(n, n)) 258 | 259 | # how to save a numpy matrix 260 | # np.savetxt("input/matrix.txt", input_matrix) 261 | 262 | # loading a numpy matrix 263 | # print("\n\n\n\n {} \n\n\n\n".format(input_file)) 264 | 265 | if not flag_multiInput: 266 | # We only get one input. Go according to the previous single input single output approach 267 | input_matrix = np.loadtxt(input_file) 268 | problem_matrix = np.loadtxt(problem_file) 269 | 270 | xi = len(input_matrix) 271 | yi = len(input_matrix[0]) 272 | xp = len(problem_matrix) 273 | yp = len(problem_matrix[0]) 274 | if xi + 4 != xp or yi + 4 != yp: 275 | raise "ERROR: incompatible input/problem. input matrix size: {}x{} problem matrix size: {}x{}".format(xi, yi, xp, yp) 276 | 277 | if debug: 278 | print("\nmain()::input_matrix = \n" + str(input_matrix) + "\n") 279 | 280 | intf = Interface(ip=ip, port=port, password=password, inputs=inputs, defaults=inputsdefaults) 281 | intf.connect() 282 | time.sleep(0.0001) 283 | intf.set_game_speed(speed=speed) 284 | intf.reset_surface() 285 | intf.make_problem(matrix=problem_matrix, files=[input_file]) 286 | intf.build(matrix=input_matrix, filename=input_file) 287 | time.sleep(wait_time) 288 | intf.evaluate() 289 | intf.save_fitness(path=output_file) 290 | 291 | elif flag_multiInput: 292 | # We have multiple input files and therefore all should be tested concurrently 293 | 294 | problem_matrix = np.loadtxt(problem_file) 295 | 296 | # inputDir = r"C:\Users\iliya\OneDrive\Desktop\Work\Codes\QuickGP\factorio\inputs\\" 297 | 298 | onlyfiles = [f for f in listdir(inputDir) if isfile(join(inputDir, f))] 299 | 300 | intf = Interface(ip=ip, port=port, password=password, inputs=inputs, defaults=inputsdefaults) 301 | intf.connect() 302 | intf.set_game_speed(speed=speed) 303 | intf.reset_surface() 304 | intf.make_problem(matrix=problem_matrix, files=onlyfiles) 305 | for index, filename in enumerate(onlyfiles): 306 | 307 | flag_save = True 308 | while flag_save: 309 | flag_save = False 310 | try: 311 | input_matrix = np.loadtxt(inputDir + filename) 312 | except: 313 | # ToDo:: fix this for mac files. 314 | flag_save = True 315 | time.sleep(0.0001) 316 | 317 | xi = len(input_matrix) 318 | yi = len(input_matrix[0]) 319 | xp = len(problem_matrix) 320 | yp = len(problem_matrix[0]) 321 | if xi + 4 != xp or yi + 4 != yp: 322 | raise "ERROR: incompatible input/problem. input matrix size: {}x{} problem matrix size: {}x{}".format(xi, yi, xp, yp) 323 | 324 | if debug: 325 | print("\nmain()::input_matrix = \n" + str(input_matrix) + "\n") 326 | 327 | intf.build(matrix=input_matrix, filename=filename, index=index) 328 | 329 | time.sleep(wait_time) 330 | intf.evaluate() 331 | 332 | exit() 333 | 334 | # calls the main function 335 | if __name__ == '__main__': 336 | main() --------------------------------------------------------------------------------