mirror of
https://github.com/JackHopkins/factorio-learning-environment.git
synced 2025-09-06 21:48:51 +00:00
Fixed a connection points bug with storage tanks. Added 2 videos for the readme.
This commit is contained in:
92
README.md
92
README.md
@@ -13,8 +13,19 @@ both settings that models still lack strong spatial reasoning. In lab-play, we f
|
||||
exhibit promising short-horizon skills, yet are unable to operate effectively in constrained environments, reflecting limitations in error analysis. In open-play, while LLMs discover automation strategies that improve growth (e.g electric-powered drilling), they fail to achieve complex
|
||||
automation (e.g electronic-circuit manufacturing).
|
||||
|
||||
<img src="docs/assets/videos/claude-500.webp" width="364" height="364" controls></video>
|
||||
<img src="docs/assets/videos/4o-mini-1500.webp" width="364" height="364" controls></video>
|
||||
|
||||
|
||||
## Table of Contents
|
||||
- Getting Started
|
||||
- Environment
|
||||
- Agents
|
||||
- Tools
|
||||
- Repository Structure
|
||||
- Contribution
|
||||
- License
|
||||
|
||||
## Getting Started
|
||||
|
||||
Download the repository and install dependencies:
|
||||
@@ -64,13 +75,86 @@ To do this, open the Factorio client, navigate to 'Multiplayer' and enter the UD
|
||||
|
||||
If you are running multiple servers (via docker compose), you will need to activate each one.
|
||||
|
||||
## Data
|
||||
## Environment
|
||||
|
||||
We provide a dataset of 50,000 trajectories of gameplay. These trajectories were generated by running an agent on the server and recording its actions.
|
||||
|
||||
The dataset can be downloaded [here]().
|
||||
## Agents
|
||||
|
||||
To generate your own dataset, you should perform an MCTS run by following the instructions [here](environment/src/datasetgen/mcts/readme.md)
|
||||
### Tools
|
||||
|
||||
Agents have access to an API of 27 tools, which can be found at `env/src/tools/agent`.
|
||||
|
||||
## Repository
|
||||
|
||||
```
|
||||
factorio-learning-environment/
|
||||
├── cluster/
|
||||
│ ├── docker/
|
||||
│ │ ├── config/
|
||||
│ │ └── mods/
|
||||
│ ├── local/
|
||||
│ │ └── assets/
|
||||
│ ├── remote/
|
||||
│ └── scenarios/
|
||||
│ ├── default_lab_scenario/
|
||||
│ └── open_world/
|
||||
├── data/
|
||||
│ ├── blueprints_to_policies/
|
||||
│ ├── icons/
|
||||
│ ├── plans/
|
||||
│ ├── prompts/
|
||||
│ ├── recipes/
|
||||
│ ├── screenshots/
|
||||
│ └── scripts/
|
||||
├── docs/
|
||||
│ ├── assets/
|
||||
│ │ └── images/
|
||||
│ └── static/
|
||||
│ ├── css/
|
||||
│ └── js/
|
||||
├── env/
|
||||
│ ├── src/
|
||||
│ │ ├── exceptions/
|
||||
│ │ ├── gym/
|
||||
│ │ ├── lib/
|
||||
│ │ ├── models/
|
||||
│ │ ├── rcon/
|
||||
│ │ ├── tools/
|
||||
│ │ └── utils/
|
||||
│ └── tests
|
||||
│ ├── actions/
|
||||
│ ├── benchmarks/
|
||||
│ ├── blueprints/
|
||||
│ ├── complex/
|
||||
│ ├── connect/
|
||||
│ ├── entities/
|
||||
│ ├── functional/
|
||||
│ ├── mcts/
|
||||
│ └── status/
|
||||
└── eval/
|
||||
├── open/
|
||||
│ ├── auto_curriculum/
|
||||
│ ├── beam/
|
||||
│ ├── independent_runs/
|
||||
│ ├── mcts/
|
||||
│ ├── model/
|
||||
│ └── plots
|
||||
└── tasks
|
||||
└── supervised_results
|
||||
|
||||
```
|
||||
|
||||
|
||||
[//]: # (## Data)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (We provide a dataset of 50,000 trajectories of gameplay. These trajectories were generated by running an agent on the server and recording its actions.)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (The dataset can be downloaded [here]().)
|
||||
|
||||
[//]: # ()
|
||||
[//]: # (To generate your own dataset, you should perform an MCTS run by following the instructions [here](environment/src/datasetgen/mcts/readme.md))
|
||||
|
||||
|
||||
[//]: # (## Evaluate Agent)
|
||||
|
Before Width: | Height: | Size: 494 KiB After Width: | Height: | Size: 494 KiB |
BIN
docs/assets/videos/4o-mini-1500.webp
Normal file
BIN
docs/assets/videos/4o-mini-1500.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 944 KiB |
BIN
docs/assets/videos/claude-500.webp
Normal file
BIN
docs/assets/videos/claude-500.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
3
env/src/game_types.py
vendored
3
env/src/game_types.py
vendored
@@ -42,8 +42,6 @@ class RecipeName(enum.Enum):
|
||||
|
||||
class PrototypeMetaclass(enum.EnumMeta):
|
||||
|
||||
def __repr__(self):
|
||||
return self
|
||||
def __getattr__(cls, name):
|
||||
try:
|
||||
attr = super().__getattr__(name)
|
||||
@@ -142,6 +140,7 @@ class Prototype(enum.Enum, metaclass=PrototypeMetaclass):
|
||||
SulfuricAcid = "sulfuric-acid", None
|
||||
Uranium235 = "uranium-235", None
|
||||
Uranium238 = "uranium-238", None
|
||||
Concrete = "concrete", None
|
||||
|
||||
Lubricant = "lubricant", None
|
||||
AdvancedOilProcessing = "advanced-oil-processing", None # These are recipes, not prototypes.
|
||||
|
3
env/src/lib/connection_points.lua
vendored
3
env/src/lib/connection_points.lua
vendored
@@ -12,7 +12,8 @@ global.utils.get_storage_tank_connection_points = function(entity)
|
||||
|
||||
-- Note: entity.direction is in Factorio's 8-way direction system (0-7)
|
||||
-- We need to handle both orientations (0/2 and 1/3)
|
||||
if entity.direction == 1 or entity.direction == 3 then
|
||||
game.print("en "..entity.direction)
|
||||
if entity.direction == 1 or entity.direction == 2 then
|
||||
-- TopRight/BottomLeft connections
|
||||
table.insert(connection_points, {x = x + 1, y = y - 2}) -- Top right - Top
|
||||
table.insert(connection_points, {x = x + 2, y = y - 1}) -- Top right - Right
|
||||
|
21
env/src/lib/serialize.lua
vendored
21
env/src/lib/serialize.lua
vendored
@@ -626,6 +626,16 @@ function get_entity_direction(entity, direction)
|
||||
else
|
||||
return defines.direction.west
|
||||
end
|
||||
elseif prototype.type == "storage-tank" then
|
||||
if direction == 0 then
|
||||
return defines.direction.north
|
||||
elseif direction == 1 then
|
||||
return defines.direction.east -- Only 2 directions
|
||||
elseif direction == 2 then
|
||||
return defines.direction.south -- Only 2 directions
|
||||
else
|
||||
return defines.direction.west -- Only 2 directions
|
||||
end
|
||||
else
|
||||
return direction
|
||||
end
|
||||
@@ -1493,19 +1503,19 @@ global.utils.serialize_entity = function(entity)
|
||||
serialized.connection_points = {}
|
||||
|
||||
-- Assembling machine connection points are similar to chemical plants
|
||||
if direction == defines.direction.north then
|
||||
if entity.direction == defines.direction.north then
|
||||
table.insert(serialized.connection_points,
|
||||
{x = x, y = y - 2
|
||||
})
|
||||
elseif direction == defines.direction.south then
|
||||
elseif entity.direction == defines.direction.south then
|
||||
table.insert(serialized.connection_points,
|
||||
{x = x, y = y + 2
|
||||
})
|
||||
elseif direction == defines.direction.east then
|
||||
elseif entity.direction == defines.direction.east then
|
||||
table.insert(serialized.connection_points,
|
||||
{x = x + 2, y
|
||||
{x = x + 2, y = y
|
||||
})
|
||||
elseif direction == defines.direction.west then
|
||||
elseif entity.direction == defines.direction.west then
|
||||
table.insert(serialized.connection_points,
|
||||
{x = x - 2, y = y
|
||||
})
|
||||
@@ -1514,6 +1524,7 @@ global.utils.serialize_entity = function(entity)
|
||||
-- Filter out any invalid connection points
|
||||
local filtered_connection_points = {}
|
||||
for _, point in ipairs(serialized.connection_points) do
|
||||
game.print(serpent.line(point))
|
||||
if global.utils.is_valid_connection_point(game.surfaces[1], point) then
|
||||
table.insert(filtered_connection_points, point)
|
||||
end
|
||||
|
12
env/src/tools/agent.md
vendored
12
env/src/tools/agent.md
vendored
@@ -377,14 +377,4 @@ def extend_resource_network(game, existing_belt_end, new_consumers):
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
### 3. Power Grid Expansion
|
||||
```python
|
||||
def expand_power_grid(game, power_source, new_machines):
|
||||
for machine in new_machines:
|
||||
connection = game.connect_entities(power_source,
|
||||
machine,
|
||||
Prototype.SmallElectricPole)
|
||||
if not connection:
|
||||
print(f"Failed to connect power to machine at {machine.
|
||||
```
|
43
env/src/utils/rcon.py
vendored
43
env/src/utils/rcon.py
vendored
@@ -121,49 +121,6 @@ def _remove_numerical_keys(dictionary):
|
||||
pruned = parts
|
||||
return pruned
|
||||
|
||||
# def _lua2python(command, response, *parameters, trace=False, start=0):
|
||||
# if trace:
|
||||
# print(command, parameters, response)
|
||||
# if response:
|
||||
# if trace:
|
||||
# print(f"success: {command}")
|
||||
# end = timer()
|
||||
#
|
||||
# if response[0] != '{':
|
||||
#
|
||||
# splitted = response.split("\n")[-1]
|
||||
#
|
||||
# if "[string" in splitted:
|
||||
# a, b = splitted.split("[string")
|
||||
# splitted = a + '[\"' + b.replace('"', '!!')
|
||||
# # remove trailing ',} '
|
||||
# splitted = re.sub(r',\s*}\s*$', '', splitted) + "\"]}"
|
||||
#
|
||||
# output = lua.decode(splitted)
|
||||
# else:
|
||||
# output = lua.decode(response)
|
||||
#
|
||||
# ##output = luadata.unserialize(splitted[-1], encoding="utf-8", multival=False)
|
||||
#
|
||||
# if trace:
|
||||
# print("{hbar}\nCOMMAND: {command}\nPARAMETERS: {parameters}\n\n{response}\n\nOUTPUT:{output}"
|
||||
# .format(hbar="-" * 100, command=command, parameters=parameters, response=response, output=output))
|
||||
#
|
||||
# # remove numerical keys
|
||||
# if isinstance(output, dict) and 'b' in output:
|
||||
# pruned = _remove_numerical_keys(output['b'])
|
||||
# output['b'] = pruned
|
||||
# # Only the last transmission is considered the output - the rest are just messages
|
||||
# return output, (end - start)
|
||||
# else:
|
||||
# if trace:
|
||||
# print(f"failure: {command} \t")
|
||||
# end = timer()
|
||||
#
|
||||
# try:
|
||||
# return lua.decode(response), (end - start)
|
||||
# except Exception as e:
|
||||
# return None, (end - start)
|
||||
|
||||
class LuaConversionError(Exception):
|
||||
"""Custom exception for Lua conversion errors"""
|
||||
|
19
env/tests/entities/test_assemblers.py
vendored
19
env/tests/entities/test_assemblers.py
vendored
@@ -12,9 +12,10 @@ def game(instance):
|
||||
'accumulator': 3,
|
||||
'steam-engine': 3,
|
||||
'small-electric-pole': 4,
|
||||
'assembling-machine-2': 1,
|
||||
'assembling-machine-2': 2,
|
||||
'offshore-pump': 1,
|
||||
'pipe': 100
|
||||
'pipe': 100,
|
||||
'storage-tank': 4,
|
||||
}
|
||||
instance.speed(10)
|
||||
instance.reset()
|
||||
@@ -37,4 +38,18 @@ def test_solar_panel_charge_accumulator(game):
|
||||
print( f"Connected ass_machine to water {ass_machine.position} with {group}")
|
||||
|
||||
|
||||
def test_assembler_2_connect_to_storage(game):
|
||||
|
||||
for direction in [Direction.UP, Direction.LEFT, Direction.RIGHT, Direction.DOWN]:
|
||||
assembly_pos = Position(x=-37, y=-16.5)
|
||||
game.move_to(assembly_pos)
|
||||
ass_machine = game.place_entity(Prototype.AssemblingMachine2, position=assembly_pos, direction=Direction.LEFT)
|
||||
game.set_entity_recipe(entity=ass_machine, prototype=Prototype.Concrete)
|
||||
ass_machine = game.rotate_entity(ass_machine, direction)
|
||||
|
||||
tank_pos = Position(x=-37, y=-6.5)
|
||||
game.move_to(tank_pos)
|
||||
tank = game.place_entity(Prototype.StorageTank, position=tank_pos, direction=direction)
|
||||
print(f"Placed storage tank at {tank.position}")
|
||||
game.connect_entities(tank, ass_machine, Prototype.Pipe)
|
||||
game.instance.reset()
|
||||
|
@@ -1,2 +0,0 @@
|
||||
sudo scp -i factorio.pem server-settings.json ec2-user@ec2-18-133-239-115.eu-west-2.compute.amazonaws.com:/opt/factorio/config/server-settings.json
|
||||
sudo ssh -i factorio.pem ec2-user@ec2-18-133-239-115.eu-west-2.compute.amazonaws.com 'docker stop factorio; docker start factorio'
|
Reference in New Issue
Block a user