├── .example.env ├── .github ├── scripts │ └── process-results.js └── workflows │ ├── deploy-leaderboard.yml │ └── process-results.yml ├── .gitignore ├── BUILD.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── PUBLISHING.md ├── README.md ├── agents ├── __init__.py ├── agent_abc.py ├── backtracking_agent.py ├── backtracking_system.py ├── basic_agent.py ├── utils │ ├── __init__.py │ ├── formatters │ │ ├── __init__.py │ │ ├── conversation_formatter_abc.py │ │ ├── recursive_formatter.py │ │ └── recursive_report_formatter.py │ ├── llm_factory.py │ ├── llm_utils.py │ ├── parse_response.py │ └── python_parser.py └── visual_agent.py ├── clean.sh ├── cluster ├── docker-compose-linux.yml ├── docker-compose.yml ├── docker │ ├── !README.md │ ├── Dockerfile │ ├── build.sh │ ├── build_alternative_installation_location.sh │ ├── config │ │ ├── config.ini │ │ ├── docker-entrypoint.sh │ │ ├── docker-update-mods.sh │ │ ├── factorio.pem │ │ ├── map-gen-settings.json │ │ ├── map-settings.json │ │ ├── mod-list.json │ │ ├── rconpw │ │ ├── scenario.sh │ │ ├── scenario2map.sh │ │ ├── server-settings.json │ │ └── update-mods.sh │ ├── install-docker.sh │ ├── main.py │ ├── mods │ │ ├── headless-player_0.1.0 │ │ │ ├── data.lua │ │ │ ├── description.json │ │ │ ├── graphics │ │ │ │ └── paperclip.png │ │ │ ├── info.json │ │ │ └── locale │ │ │ │ └── en │ │ │ │ └── en.cfg │ │ ├── mod-list.json │ │ └── stdlib_1.4.6 │ │ │ ├── LICENSE │ │ │ ├── changelog.txt │ │ │ ├── info.json │ │ │ ├── readme.md │ │ │ ├── stdlib-docs │ │ │ ├── classes │ │ │ │ ├── Data.Category.html │ │ │ │ ├── Data.Entity.html │ │ │ │ ├── Data.Fluid.html │ │ │ │ ├── Data.Item.html │ │ │ │ ├── Data.Recipe.html │ │ │ │ ├── Data.Technology.html │ │ │ │ ├── Data.html │ │ │ │ ├── LinkedList.html │ │ │ │ ├── Vendor.Enumerable.html │ │ │ │ ├── string_array.html │ │ │ │ └── unique_array.html │ │ │ ├── examples │ │ │ │ └── event.lua.html │ │ │ ├── index.html │ │ │ ├── ldoc.css │ │ │ ├── modules │ │ │ │ ├── Area.Area.html │ │ │ │ ├── Area.Chunk.html │ │ │ │ ├── Area.Direction.html │ │ │ │ ├── Area.Orientation.html │ │ │ │ ├── Area.Position.html │ │ │ │ ├── Area.Surface.html │ │ │ │ ├── Area.Tile.html │ │ │ │ ├── Core.html │ │ │ │ ├── Data.Pipes.html │ │ │ │ ├── Data.Sprites.html │ │ │ │ ├── Data.Util.html │ │ │ │ ├── Entity.Entity.html │ │ │ │ ├── Entity.Inventory.html │ │ │ │ ├── Entity.Resource.html │ │ │ │ ├── Event.Changes.html │ │ │ │ ├── Event.Event.html │ │ │ │ ├── Event.Filters.html │ │ │ │ ├── Event.Force.html │ │ │ │ ├── Event.Gui.html │ │ │ │ ├── Event.Player.html │ │ │ │ ├── Event.Surface.html │ │ │ │ ├── Event.Trains.html │ │ │ │ ├── Game.html │ │ │ │ ├── Misc.Config.html │ │ │ │ ├── Misc.Logger.html │ │ │ │ ├── Misc.Migrate.html │ │ │ │ ├── Misc.Queue.html │ │ │ │ ├── Utils.Color.html │ │ │ │ ├── Utils.Globals.html │ │ │ │ ├── Utils.Is.html │ │ │ │ ├── Utils.Iter.html │ │ │ │ ├── Utils.math.html │ │ │ │ ├── Utils.string.html │ │ │ │ ├── Utils.table.html │ │ │ │ ├── defines.anticolor.html │ │ │ │ ├── defines.color.html │ │ │ │ ├── defines.lightcolor.html │ │ │ │ ├── defines.time.html │ │ │ │ └── vendor.version.html │ │ │ ├── scripts │ │ │ │ ├── Developer.html │ │ │ │ └── quickstart.html │ │ │ ├── spectre-icons.min.css │ │ │ ├── spectre.min.css │ │ │ └── topics │ │ │ │ ├── LICENSE.html │ │ │ │ ├── changelog.txt.html │ │ │ │ ├── contributing.md.html │ │ │ │ ├── readme.md.html │ │ │ │ └── style-guide.md.html │ │ │ ├── stdlib │ │ │ ├── area │ │ │ │ ├── area.lua │ │ │ │ ├── chunk.lua │ │ │ │ ├── direction.lua │ │ │ │ ├── orientation.lua │ │ │ │ ├── position.lua │ │ │ │ ├── surface.lua │ │ │ │ └── tile.lua │ │ │ ├── config.lua │ │ │ ├── core.lua │ │ │ ├── data │ │ │ │ ├── category.lua │ │ │ │ ├── data.lua │ │ │ │ ├── developer │ │ │ │ │ └── developer.lua │ │ │ │ ├── entity.lua │ │ │ │ ├── fluid.lua │ │ │ │ ├── item.lua │ │ │ │ ├── modules │ │ │ │ │ ├── class.yuml │ │ │ │ │ ├── groups.lua │ │ │ │ │ ├── notes.txt │ │ │ │ │ ├── pipes.lua │ │ │ │ │ ├── product-data.lua │ │ │ │ │ ├── products.lua │ │ │ │ │ ├── recipe-test.lua │ │ │ │ │ ├── recipe.lua │ │ │ │ │ ├── recipe.txt │ │ │ │ │ ├── sprites.lua │ │ │ │ │ └── util.lua │ │ │ │ ├── recipe.lua │ │ │ │ └── technology.lua │ │ │ ├── entity │ │ │ │ ├── entity.lua │ │ │ │ ├── inventory.lua │ │ │ │ └── resource.lua │ │ │ ├── event │ │ │ │ ├── changes.lua │ │ │ │ ├── event.lua │ │ │ │ ├── force.lua │ │ │ │ ├── gui.lua │ │ │ │ ├── modules │ │ │ │ │ ├── dump_event_data.lua │ │ │ │ │ ├── event_filters.lua │ │ │ │ │ └── merge_data.lua │ │ │ │ ├── player.lua │ │ │ │ ├── surface.lua │ │ │ │ └── trains.lua │ │ │ ├── game.lua │ │ │ ├── misc │ │ │ │ ├── config.lua │ │ │ │ ├── logger.lua │ │ │ │ ├── migrate.lua │ │ │ │ └── queue.lua │ │ │ ├── scripts │ │ │ │ ├── interface.lua │ │ │ │ └── quickstart.lua │ │ │ ├── utils │ │ │ │ ├── classes │ │ │ │ │ ├── linked_list.lua │ │ │ │ │ ├── string_array.lua │ │ │ │ │ └── unique_array.lua │ │ │ │ ├── color.lua │ │ │ │ ├── defines │ │ │ │ │ ├── anticolor.lua │ │ │ │ │ ├── color.lua │ │ │ │ │ ├── color_list.lua │ │ │ │ │ ├── lightcolor.lua │ │ │ │ │ └── time.lua │ │ │ │ ├── globals.lua │ │ │ │ ├── is.lua │ │ │ │ ├── iter.lua │ │ │ │ ├── math.lua │ │ │ │ ├── string.lua │ │ │ │ ├── table.lua │ │ │ │ └── type.lua │ │ │ └── vendor │ │ │ │ ├── beholder.lua │ │ │ │ ├── bump.lua │ │ │ │ ├── cron.lua │ │ │ │ ├── enumerable.lua │ │ │ │ ├── inspect.lua │ │ │ │ ├── md5.lua │ │ │ │ ├── memoize.lua │ │ │ │ ├── middleclass.lua │ │ │ │ ├── mm.lua │ │ │ │ ├── pulsar.lua │ │ │ │ ├── semver.lua │ │ │ │ ├── serpent.lua │ │ │ │ ├── stateful.lua │ │ │ │ └── version.lua │ │ │ └── thumbnail.png │ ├── probe.sh │ ├── requirements.txt │ ├── run.sh │ ├── run_local.sh │ └── setup_docker_repo.sh ├── local │ ├── !README.md │ ├── assets │ │ ├── connect_button.png │ │ ├── connect_to_address_button.png │ │ ├── ip_field.png │ │ ├── multiplayer_button.png │ │ └── quit_button.png │ ├── cluster_ips.py │ ├── create_docker_compose_config.py │ ├── docker-compose-1-example.yml │ ├── factorio_server_login.py │ ├── requirements.txt │ └── run-envs.sh ├── remote │ ├── !README.md │ ├── cluster.cloudformation.yaml │ ├── cluster_ips.py │ └── factorio_server_login.py └── scenarios │ ├── default_lab_scenario │ ├── blueprint.zip │ ├── control.lua │ ├── control2.lua │ ├── description.json │ ├── freeplay.lua │ ├── info.json │ ├── locale │ │ └── en │ │ │ ├── freeplay.cfg │ │ │ └── level-01-locale.cfg │ ├── paperclips.lua │ ├── planet1.png │ └── script.dat │ ├── default_lab_scenario_small │ ├── blueprint.zip │ ├── control.lua │ ├── control2.lua │ ├── description.json │ ├── freeplay.lua │ ├── info.json │ ├── locale │ │ └── en │ │ │ ├── freeplay.cfg │ │ │ └── level-01-locale.cfg │ ├── paperclips.lua │ ├── planet1.png │ └── script.dat │ └── open_world │ └── README.md ├── data ├── __init__.py ├── blueprints_to_policies │ ├── README.md │ ├── __init__.py │ ├── blueprint_analyzer.py │ ├── blueprint_analyzer_with_connect.py │ ├── blueprint_analyzer_with_place_next_to.py │ ├── blueprint_metadata_generator.py │ ├── blueprint_refactor.py │ ├── blueprints │ │ └── example │ │ │ └── miner_cycle.json │ ├── factorio_school.py │ ├── loop_generator.py │ ├── models │ │ ├── __init__.py │ │ └── blueprint_entity.py │ ├── parse_blueprints.py │ ├── processing_state.py │ ├── schema.json │ ├── test_template.jinja2 │ ├── trajectory_generator.py │ └── util.py ├── plans │ ├── .example.env │ ├── README.md │ ├── iron_plate_factory_objectives.json │ ├── objective_tree_generator.py │ ├── plan_crawler.py │ ├── plan_parser.py │ ├── prompts.py │ └── research_automation.json ├── prompts │ ├── bottoms_up_prompts │ │ ├── finetuning_prompts │ │ │ ├── executor_plan │ │ │ │ ├── system_prompt.md │ │ │ │ └── user_prompt.md │ │ │ ├── step_generator │ │ │ │ ├── system_prompt.md │ │ │ │ └── user_prompt.md │ │ │ ├── step_judge │ │ │ │ ├── system_prompt.md │ │ │ │ └── user_prompt.md │ │ │ ├── step_supervised │ │ │ │ ├── system_prompt.md │ │ │ │ └── user_prompt.md │ │ │ ├── system_message.md │ │ │ ├── system_message_policy.md │ │ │ ├── system_message_policy_refined.md │ │ │ ├── system_message_policy_self_gen.md │ │ │ └── user_message_step_filler.md │ │ ├── implementation_generation │ │ │ ├── system_message.md │ │ │ └── user_message.md │ │ ├── implementation_reflection │ │ │ ├── system_message.md │ │ │ ├── system_message_old.md │ │ │ └── user_message.md │ │ ├── objective_generation │ │ │ ├── examples │ │ │ │ └── create_automatic_copper_mine │ │ │ │ │ ├── input.md │ │ │ │ │ └── output.md │ │ │ ├── system_message.md │ │ │ └── user_message.md │ │ └── skill_generator │ │ │ ├── system_message.md │ │ │ └── user_message.md │ ├── finetuning_prompts │ │ ├── system_message_policy.md │ │ ├── system_message_policy_synth.md │ │ ├── system_message_steps.md │ │ ├── user_message.md │ │ └── user_message_synth.md │ ├── outcome_test │ │ ├── system_message.md │ │ └── user_message.md │ ├── planning │ │ ├── system_message.md │ │ └── user_message.md │ ├── postprocessing │ │ ├── backfill_objectives │ │ │ ├── examples │ │ │ │ ├── connect_things │ │ │ │ │ ├── details.json │ │ │ │ │ └── snippet.py │ │ │ │ └── craft_furnace │ │ │ │ │ ├── details.json │ │ │ │ │ └── snippet.py │ │ │ ├── system_prompt.md │ │ │ └── user_message.md │ │ └── backfill_plans │ │ │ ├── examples │ │ │ ├── craft_with_chest │ │ │ │ ├── details.json │ │ │ │ └── snippet.py │ │ │ └── create_burner_mine │ │ │ │ ├── details.json │ │ │ │ └── snippet.py │ │ │ ├── recipes.md │ │ │ ├── system_prompt.md │ │ │ └── user_message.md │ ├── prompts_for_notebook │ │ ├── finetuning_prompts │ │ │ ├── system_message_step_filler.md │ │ │ └── user_message_step_filler.md │ │ ├── planning_examples │ │ │ └── rag_functions │ │ │ │ ├── Create_electric_coal_mine │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── assembling_machine │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── connect_chest_to_existing_furnaces │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── create_automatic_copper_mine copy │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ └── create_automatic_copper_mine │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ ├── recipes.md │ │ ├── system_message_correct.md │ │ ├── system_message_correct_filler.md │ │ ├── system_message_correct_old_backup.md │ │ ├── system_message_planning.md │ │ ├── system_message_planning_substeps.md │ │ ├── system_message_step.md │ │ ├── system_message_step_filler.md │ │ ├── system_message_step_old_backup.md │ │ ├── user_message_correct.md │ │ ├── user_message_correct_filler.md │ │ ├── user_message_planning.md │ │ ├── user_message_step.md │ │ └── user_message_step_filler.md │ ├── prompts_for_rag │ │ ├── planning_examples │ │ │ └── rag_functions │ │ │ │ ├── Connect and power electric mining drill │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── Craft 1 burner-mining-drill from scratch │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── Craft 1 electric-mining-drill with fixed inventory │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── Smelt_items │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── Smelt_items_with_furnace │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ ├── automate_copper_transport_to_chest │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ │ │ └── automate_drill_to_chest │ │ │ │ ├── input.md │ │ │ │ └── plan.md │ │ ├── recipes.md │ │ ├── system_message_correct.md │ │ ├── system_message_planning.md │ │ ├── system_message_policy.md │ │ ├── user_message.md │ │ ├── user_message_correct.md │ │ └── user_message_planning.md │ ├── self_correct_script_finetune │ │ ├── system_message.md │ │ └── user_message.md │ ├── self_correct_script_outcome │ │ ├── system_message.md │ │ └── user_message.md │ ├── self_correct_script_process │ │ ├── system_message.md │ │ └── user_message.md │ ├── steps_to_function │ │ ├── examples │ │ │ └── example_1_output.md │ │ ├── system_message.md │ │ └── user_message.md │ └── steps_to_script │ │ ├── system_message.md │ │ └── user_message.md ├── recipes │ ├── main.py │ └── recipes.lua ├── run_trace.py ├── screenshots_from_run.py ├── screenshots_to_mp4.py └── scripts │ ├── count.lua │ ├── count_entities.lua │ ├── count_items.lua │ ├── create_character.lua │ ├── day_time.lua │ ├── get_associated_characters.lua │ ├── get_view.lua │ ├── init.lua │ ├── init │ ├── clear_map.lua │ ├── count_all.lua │ ├── create_character.lua │ ├── create_force.lua │ ├── create_ore.lua │ ├── delete_rocks.lua │ ├── enact_truce.lua │ ├── give_item.lua │ ├── mine.lua │ ├── pollute.lua │ ├── random_map.lua │ ├── remove_cliffs.lua │ ├── reset.lua │ ├── revoke_truce.lua │ └── walk.lua │ ├── inventory.lua │ ├── players.lua │ ├── players1.lua │ ├── position.lua │ ├── print.lua │ ├── reveal_map.lua │ ├── stats.lua │ └── teleport.lua ├── docs ├── assets │ ├── documents │ │ └── paper.pdf │ ├── images │ │ ├── actions.png │ │ ├── figure_1.png │ │ ├── figure_2.png │ │ ├── figure_4.png │ │ ├── figure_5.png │ │ ├── figure_6.png │ │ ├── icons │ │ │ ├── electronic-circuit.png │ │ │ ├── iron-gear-wheel.png │ │ │ ├── iron-plate.png │ │ │ ├── petroleum-gas.png │ │ │ ├── plastic-bar.png │ │ │ ├── steel-plate.png │ │ │ └── sulfur.png │ │ ├── multiagent-thumb.png │ │ ├── repl.png │ │ ├── table_1.png │ │ ├── team │ │ │ ├── akbir.png │ │ │ ├── jack.png │ │ │ └── mart.png │ │ ├── vision-original.png │ │ └── vision.png │ └── videos │ │ ├── automation-science-h264.mp4 │ │ ├── compressed_1891-cropped-h264.mp4 │ │ ├── compressed_1897-cropped-h264.mp4 │ │ ├── compressed_1897-cropped.webp │ │ ├── compressed_2213-cropped-h264.mp4 │ │ ├── compressed_527-cropped-h264.mp4 │ │ ├── compressed_527-cropped.webp │ │ ├── compressed_720-cropped-h264.mp4 │ │ ├── compressed_761-cropped-h264.mp4 │ │ ├── compressed_767-cropped-h264.mp4 │ │ ├── compressed_803-cropped-h264.mp4 │ │ ├── compressed_804-cropped-h264.mp4 │ │ ├── electronic-circuits-h264.mp4 │ │ └── multiagent-thumb-h264.mp4 ├── favicon.ico ├── index.html ├── leaderboard │ ├── asset-manifest.json │ ├── combined-results.json │ ├── index.html │ ├── processed │ │ └── combined-results.json │ └── static │ │ ├── css │ │ ├── main.06c39b3c.css │ │ ├── main.06c39b3c.css.map │ │ └── main.31d6cfe0.css │ │ └── js │ │ ├── main.83539a1a.js │ │ ├── main.83539a1a.js.LICENSE.txt │ │ ├── main.83539a1a.js.map │ │ ├── main.a914cc1f.js │ │ ├── main.a914cc1f.js.LICENSE.txt │ │ ├── main.a914cc1f.js.map │ │ ├── main.be301726.js │ │ ├── main.be301726.js.LICENSE.txt │ │ ├── main.be301726.js.map │ │ ├── main.c2a531a7.js │ │ ├── main.c2a531a7.js.LICENSE.txt │ │ └── main.c2a531a7.js.map ├── release.0.2.0.html └── static │ ├── css │ ├── academicons.min.css │ ├── bulma-carousel.min.css │ ├── bulma-slider.min.css │ ├── bulma.min.css │ └── fontawesome.all.min.css │ └── js │ ├── bulma-carousel.min.js │ ├── bulma-slider.min.js │ ├── fontawesome.all.min.js │ └── index.js ├── env ├── __init__.py ├── src │ ├── a2a_instance.py │ ├── a2a_namespace.py │ ├── entities.py │ ├── exceptions │ │ ├── hinting_name_error.py │ │ └── insufficient_score_exception.py │ ├── game_types.py │ ├── gym │ │ ├── __init__.py │ │ ├── factorio_environment.py │ │ ├── observation_state.py │ │ ├── readme.md │ │ ├── test_utils.py │ │ ├── utils.py │ │ └── vocabulary.py │ ├── instance.py │ ├── lib │ │ ├── README.md │ │ ├── alerts.lua │ │ ├── build_checkerboard.lua │ │ ├── checksum.lua │ │ ├── clear_entities.lua │ │ ├── clear_inventory.lua │ │ ├── connection_points.lua │ │ ├── enemies.lua │ │ ├── initialise.lua │ │ ├── initialise_inventory.lua │ │ ├── priority_queue.lua │ │ ├── production_score.lua │ │ ├── recipe_fluid_connection_mappings.lua │ │ ├── reset.lua │ │ ├── reset_position.lua │ │ ├── serialize.lua │ │ └── util.lua │ ├── lua_manager.py │ ├── models │ │ ├── __init__.py │ │ ├── achievements.py │ │ ├── camera.py │ │ ├── conversation.py │ │ ├── game_state.py │ │ ├── generation_parameters.py │ │ ├── message.py │ │ ├── program.py │ │ ├── research_state.py │ │ ├── serializable_function.py │ │ └── technology_state.py │ ├── namespace.py │ ├── protocols │ │ └── a2a │ │ │ ├── handler.py │ │ │ └── server.py │ ├── rcon │ │ ├── PKG-INFO │ │ ├── README.md │ │ ├── __init__.py │ │ ├── factorio_rcon │ │ │ ├── __init__.py │ │ │ └── factorio_rcon.py │ │ ├── factorio_rcon_py.egg-info │ │ │ ├── PKG-INFO │ │ │ ├── SOURCES.txt │ │ │ ├── dependency_links.txt │ │ │ ├── requires.txt │ │ │ └── top_level.txt │ │ ├── setup.cfg │ │ └── setup.py │ ├── requirements.txt │ ├── tools │ │ ├── admin │ │ │ ├── clear_collision_boxes │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── clear_entities │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── extend_collision_boxes │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_factory_centroid │ │ │ │ ├── client.py │ │ │ │ ├── server.lua │ │ │ │ └── server2.lua │ │ │ ├── get_messages │ │ │ │ └── server.lua │ │ │ ├── get_path │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_production_stats │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── inspect_entities │ │ │ │ ├── deprecated │ │ │ │ └── server.lua │ │ │ ├── load_blueprint │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── load_entity_state │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── load_research_state │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── message_utils.py │ │ │ ├── observe_all │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── production_stats │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── regenerate_resources │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── render │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ ├── layers │ │ │ │ │ ├── connection_layers_renderer.py │ │ │ │ │ ├── entities_layer_renderer.py │ │ │ │ │ ├── grid_layer_renderer.py │ │ │ │ │ ├── layer_renderer.py │ │ │ │ │ ├── marker_layers_renderer.py │ │ │ │ │ ├── natural_layer_renderer.py │ │ │ │ │ ├── resource_layer_renderer.py │ │ │ │ │ └── water_layer_renderer.py │ │ │ │ ├── rendered_image.py │ │ │ │ ├── renderer.py │ │ │ │ ├── server.lua │ │ │ │ └── utils │ │ │ │ │ ├── colour_manager.py │ │ │ │ │ ├── connection_renderer.py │ │ │ │ │ ├── electricity_renderer.py │ │ │ │ │ ├── entity_categoriser.py │ │ │ │ │ ├── image_calculator.py │ │ │ │ │ ├── legend_renderer.py │ │ │ │ │ ├── natural_renderer.py │ │ │ │ │ ├── render_config.py │ │ │ │ │ ├── shape_renderer.py │ │ │ │ │ └── tile_renderer.py │ │ │ ├── render_message │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── request_path │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── save_blueprint │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── save_entity_state │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ └── save_research_state │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ ├── agent.md │ │ ├── agent │ │ │ ├── can_place_entity │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── connect_entities │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ ├── groupable_entities.py │ │ │ │ ├── path_result.py │ │ │ │ ├── resolver.py │ │ │ │ ├── resolvers │ │ │ │ │ ├── fluid_connection_resolver.py │ │ │ │ │ ├── power_connection_resolver.py │ │ │ │ │ └── transport_connection_resolver.py │ │ │ │ └── server.lua │ │ │ ├── craft_item │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── extract_item │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_connection_amount │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_entities │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_entity │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_prototype_recipe │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_research_progress │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── get_resource_patch │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── harvest_resource │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── insert_item │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── inspect_inventory │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── launch_rocket │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── move_to │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── nearest │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── nearest_buildable │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── pickup_entity │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── place_entity │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── place_entity_next_to │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── print │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── rotate_entity │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── score │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── send_message │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── set_entity_recipe │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── set_research │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ │ ├── shift_entity │ │ │ │ └── client.py │ │ │ └── sleep │ │ │ │ ├── agent.md │ │ │ │ ├── client.py │ │ │ │ └── server.lua │ │ ├── controller.py │ │ ├── init.py │ │ └── tool.py │ ├── transaction.py │ └── utils │ │ ├── achievements.py │ │ ├── bcolors.py │ │ ├── controller_loader │ │ ├── call_info.py │ │ ├── code_analyzer.py │ │ ├── manual_generator.py │ │ ├── module_loader.py │ │ ├── schema_generator.py │ │ ├── system_prompt_generator.py │ │ └── type_definition_processor.py │ │ ├── parse_llama_to_gpt.py │ │ ├── profits.py │ │ └── rcon.py └── tests │ ├── __init__.py │ ├── actions │ ├── __init__.py │ ├── _test_inspect_entities.py │ ├── _test_shift_entity.py │ ├── test_can_place.py │ ├── test_connect_entities_dry_run.py │ ├── test_craft.py │ ├── test_extract_item.py │ ├── test_get_entities.py │ ├── test_get_entity.py │ ├── test_get_prototype_recipe.py │ ├── test_get_research_progress.py │ ├── test_get_resource_patch.py │ ├── test_harvest_resource.py │ ├── test_insert_item.py │ ├── test_inspect_inventory.py │ ├── test_move_to.py │ ├── test_nearest.py │ ├── test_nearest_buildable.py │ ├── test_pickup_entity.py │ ├── test_place.py │ ├── test_place_entity_next_to.py │ ├── test_place_next_to_and_rotate.py │ ├── test_print.py │ ├── test_render.py │ ├── test_request_path.py │ ├── test_rotate.py │ ├── test_score.py │ ├── test_set_entity_recipe.py │ ├── test_set_research.py │ └── test_sleep.py │ ├── benchmarks │ ├── __init__.py │ ├── benchmark_api.py │ ├── benchmark_interpreter.py │ ├── test_demo_video.py │ ├── test_elapsed_ticks.py │ └── test_script_caching.py │ ├── blueprints │ ├── __init__.py │ ├── test_blueprint_based_policies.py │ └── test_save_load.py │ ├── complex │ ├── __init__.py │ ├── test_edge_cases.py │ └── test_slow.py │ ├── conftest.py │ ├── connect │ ├── __init__.py │ ├── test_connect_pipes.py │ ├── test_connect_poles.py │ ├── test_connect_transport_belts.py │ ├── test_connect_underground_belts.py │ ├── test_connect_underground_pipes.py │ ├── test_connect_walls.py │ └── test_fluid_handlers.py │ ├── entities │ ├── test_assemblers.py │ ├── test_drill.py │ ├── test_fluid_processors.py │ ├── test_inserters.py │ ├── test_nuclear_reactor.py │ ├── test_power_generators.py │ └── test_rockets.py │ ├── eval │ ├── __init__.py │ ├── _test_recursive_formatter_functional.py │ ├── samplers │ │ ├── __init__.py │ │ ├── test_kld_mean_sampler.py │ │ ├── test_python_parser.py │ │ └── test_weighted_reward_sampler.py │ ├── test_achievements.py │ ├── test_conversation_formatter.py │ ├── test_game_state.py │ ├── test_mcts_chunker.py │ ├── test_production_divergence.py │ ├── test_profits.py │ ├── test_python_parser.py │ ├── test_recursive_formatter.py │ └── test_save_load_python_namespace.py │ ├── functional │ ├── __init__.py │ ├── test_auto_fueling_iron_smelting_factory.py │ ├── test_auto_refilling_coal.py │ ├── test_chemical_plants.py │ ├── test_defence.py │ ├── test_electricity_unit.py │ ├── test_factory_unit.py │ ├── test_full_oil_chain.py │ ├── test_multi_drill_multi_furnace.py │ ├── test_multiple_drills_multiple_furnaces.py │ ├── test_objectives.py │ ├── test_research.py │ └── test_small_iron_factory.py │ ├── multiagent │ ├── test_actions_multiagent.py │ ├── test_agent_instructions.py │ └── test_messages.py │ ├── status │ ├── __init__.py │ ├── test_pipes_status.py │ ├── test_poles_status.py │ └── test_pump_status.py │ ├── test_eval.py │ ├── test_functions.py │ ├── test_production_stats.py │ ├── test_rcon_utils.py │ ├── test_variables.py │ └── test_warnings.py ├── eval ├── __init__.py ├── evaluator.py ├── open │ ├── MANUAL.md │ ├── MANUAL_short.md │ ├── __init__.py │ ├── beam │ │ ├── README.md │ │ ├── __init__.py │ │ ├── beam_search.py │ │ ├── beam_search_milestones.py │ │ ├── run.py │ │ ├── run_milestones.py │ │ └── run_parallel.py │ ├── db_client.py │ ├── independent_runs │ │ ├── __init__.py │ │ ├── multiagent │ │ │ ├── claude_lab_distrust.json │ │ │ ├── claude_lab_free.json │ │ │ └── claude_lab_impostor.json │ │ ├── run.py │ │ ├── run_a2a.py │ │ ├── run_config.json │ │ ├── run_config_example_lab_play.json │ │ ├── run_config_example_open_play.json │ │ ├── run_vision.py │ │ ├── simple_evaluator.py │ │ ├── trajectory_runner.py │ │ └── value_calculator.py │ ├── mcts │ │ ├── __init__.py │ │ ├── __main__chunked.py │ │ ├── __main__objective_mcts.py │ │ ├── __main__planning_agent.py │ │ ├── blueprint_scenario_sampler.py │ │ ├── blueprints_to_programs.py │ │ ├── chunked_mcts.py │ │ ├── grouped_logger.py │ │ ├── instance_group.py │ │ ├── logger.py │ │ ├── main.py │ │ ├── mcts.py │ │ ├── mcts_factory.py │ │ ├── objective_mcts.py │ │ ├── parallel_mcts.py │ │ ├── parallel_mcts_config.py │ │ ├── parallel_planning_mcts.py │ │ ├── parallel_supervised_config.py │ │ ├── plan_generator_1.py │ │ ├── planning_mcts.py │ │ ├── planning_models.py │ │ ├── readme.md │ │ ├── requirements.txt │ │ ├── results.py │ │ ├── samplers │ │ │ ├── __init__.py │ │ │ ├── beam_sampler.py │ │ │ ├── db_sampler.py │ │ │ ├── dynamic_reward_weighted_sampler.py │ │ │ ├── kld_achievement_sampler.py │ │ │ └── objective_sampler.py │ │ └── supervised_task_executor_abc.py │ └── plots │ │ ├── __init__.py │ │ ├── run_results.py │ │ ├── run_visualiser.py │ │ ├── simple_progression_analyser.py │ │ ├── simple_run_visualiser.py │ │ ├── simple_run_visualiser_with_ticks.py │ │ └── visualise_production_figures.py └── tasks │ ├── __init__.py │ ├── default_task.py │ ├── task_abc.py │ ├── task_definitions │ ├── automation_science_pack_throughput_16.json │ ├── crude_oil_throughput_16.json │ ├── electronic_circuit_throughput_16.json │ ├── inserter_throughput_16.json │ ├── iron_gear_wheel_throughput_16.json │ ├── iron_gear_wheel_throughput_unbounded.json │ ├── iron_ore_throughput_16.json │ ├── iron_plate_throughput_16.json │ ├── logistics_science_pack_throughput_16.json │ ├── military_science_pack_throughput_16.json │ ├── multiagent │ │ ├── iron_plate_throughput_distrust.json │ │ ├── iron_plate_throughput_free.json │ │ └── iron_plate_throughput_impostor.json │ ├── open_play.json │ ├── petroleum_gas_throughput_16.json │ ├── plastic_bar_throughput_16.json │ ├── steel_plate_throughput_16.json │ ├── stone_wall_throughput_16.json │ └── sulfur_throughput_16.json │ ├── task_factory.py │ ├── throughput_task.py │ └── unbounded_throughput_task.py ├── leaderboard ├── build │ ├── asset-manifest.json │ ├── combined-results.json │ ├── index.html │ └── static │ │ ├── css │ │ ├── main.06c39b3c.css │ │ └── main.06c39b3c.css.map │ │ └── js │ │ ├── main.28a2068e.js │ │ ├── main.28a2068e.js.LICENSE.txt │ │ └── main.28a2068e.js.map ├── package-lock.json ├── package.json ├── processed │ └── combined-results.json ├── public │ ├── combined-results.json │ └── index.html ├── readme.md ├── results │ ├── claude-3-5-sonnet.json │ ├── deepseek-chat.json │ ├── gemini-2.json │ ├── gpt-4o-mini.json │ ├── gpt-4o.json │ └── llama-3.3-70b.json └── src │ ├── App.js │ ├── components │ └── Leaderboard.jsx │ ├── index.css │ └── index.js ├── prepare_build.py ├── pyproject.toml ├── pytest.ini ├── pyvenv.cfg ├── run.py ├── server.py ├── server ├── CLAUDE.md ├── README.md ├── __init__.py ├── init.py ├── models.py ├── prompts.py ├── repository.py ├── resources.py ├── state.py ├── tools.py ├── tutorial.md ├── unix_tools.py └── version_control.py ├── setup.cfg ├── setup.py ├── uv.lock └── validate_installation.py /.example.env: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=XXX 2 | DEEPSEEK_API_KEY=XXX 3 | DB_PASSWORD=XXX 4 | ANTHROPIC_API_KEY=XXX 5 | NEPTUNE_API_TOKEN=XXX 6 | CLUSTER_NAME=XXX 7 | GEMINI_API_KEY=XXX 8 | TOGETHER_API_KEY=XXX 9 | DEEPSEEK_API_KEY=XXX 10 | OPEN_ROUTER_API_KEY=XXX 11 | 12 | # SQLite configuration (recommended for beginners) 13 | SQLITE_DB_FILE=XXX 14 | 15 | # If using a Postgres DB 16 | SKILLS_DB_HOST=XXX 17 | SKILLS_DB_PORT=XXX 18 | SKILLS_DB_NAME=XXX 19 | SKILLS_DB_USER=XXX 20 | SKILLS_DB_PASSWORD=XXX -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Factorio Learning Environment Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | ### License Notes 24 | 25 | - The MIT License is a permissive license that allows for reuse with minimal restrictions 26 | - This license applies to the Factorio Learning Environment code and documentation 27 | - Factorio game assets and code are not covered by this license 28 | - Contributors should ensure they have the right to license their contributions under MIT 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | 2 | include README.md 3 | include LICENSE 4 | recursive-include factorio_learning_environment * 5 | global-exclude __pycache__ 6 | global-exclude *.py[cod] 7 | global-exclude *.so 8 | global-exclude .DS_Store 9 | -------------------------------------------------------------------------------- /agents/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/agents/utils/__init__.py -------------------------------------------------------------------------------- /agents/utils/formatters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/agents/utils/formatters/__init__.py -------------------------------------------------------------------------------- /agents/utils/parse_response.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from agents import Python, Policy, PolicyMeta 4 | from agents.utils.python_parser import PythonParser 5 | 6 | 7 | def parse_response(response) -> Optional[Policy]: 8 | if hasattr(response, 'choices'): 9 | choice = response.choices[0] 10 | input_tokens = response.usage.prompt_tokens if hasattr(response, 'usage') else 0 11 | output_tokens = response.usage.completion_tokens if hasattr(response, 'usage') else 0 12 | total_tokens = input_tokens + output_tokens 13 | else: 14 | choice = response.content[0] 15 | input_tokens = response.usage.input_tokens if hasattr(response, 'usage') else 0 16 | output_tokens = response.usage.output_tokens if hasattr(response, 'usage') else 0 17 | total_tokens = input_tokens + output_tokens 18 | 19 | try: 20 | code, text_response = PythonParser.extract_code(choice) 21 | except Exception as e: 22 | print(f"Failed to extract code from choice: {str(e)}") 23 | return None 24 | 25 | if not code: 26 | return None 27 | 28 | policy = Policy(code=code, 29 | meta=PolicyMeta(output_tokens=output_tokens, input_tokens=input_tokens,total_tokens=total_tokens, text_response=text_response)) 30 | return policy -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Clean script to remove build artifacts before creating a new package 3 | 4 | echo "Cleaning build artifacts..." 5 | 6 | # Remove build directories 7 | rm -rf build/ 8 | rm -rf dist/ 9 | rm -rf *.egg-info/ 10 | rm -rf factorio_learning_environment/ 11 | 12 | # Remove any __pycache__ directories 13 | find . -type d -name "__pycache__" -exec rm -rf {} + 14 | 15 | echo "Clean complete. You can now build the package with:" 16 | echo "python -m build" -------------------------------------------------------------------------------- /cluster/docker-compose-linux.yml: -------------------------------------------------------------------------------- 1 | services: 2 | factorio: 3 | image: 'factorio:latest' 4 | platform: linux/amd64 5 | environment: 6 | - SAVES=/opt/factorio/saves 7 | - CONFIG=/opt/factorio/config 8 | - MODS=/opt/factorio/mods 9 | - SCENARIOS=/opt/factorio/scenarios 10 | - PORT=34197 11 | - RCON_PORT=27015 12 | volumes: 13 | - type: bind 14 | source: ./scenarios/default_lab_scenario 15 | target: /opt/factorio/scenarios/default_lab_scenario 16 | ports: 17 | - '34198:34197/udp' 18 | - '27015:27015/tcp' 19 | restart: unless-stopped 20 | user: factorio 21 | #entrypoint: [ "bash", "/scenario.sh", "default_lab_scenario" ] 22 | entrypoint: [] 23 | command: > 24 | /opt/factorio/bin/x64/factorio 25 | --start-server-load-scenario default_lab_scenario 26 | --port 34197 27 | --server-settings /opt/factorio/config/server-settings.json 28 | --map-gen-settings /opt/factorio/config/map-gen-settings.json 29 | --map-settings /opt/factorio/config/map-settings.json 30 | --server-banlist /opt/factorio/config/server-banlist.json 31 | --rcon-port 27015 32 | --rcon-password "" 33 | --server-whitelist /opt/factorio/config/server-whitelist.json 34 | --use-server-whitelist 35 | --server-adminlist /opt/factorio/config/server-adminlist.json 36 | --mod-directory /opt/factorio/mods 37 | deploy: 38 | resources: 39 | limits: 40 | cpus: '1' 41 | memory: 512m 42 | -------------------------------------------------------------------------------- /cluster/docker-compose.yml: -------------------------------------------------------------------------------- 1 | volumes: 2 | scenarios: 3 | services: 4 | factorio_1: 5 | image: 'factoriotools/factorio:1.1.110' 6 | volumes: 7 | - type: bind 8 | source: ./scenarios/default_lab_scenario 9 | target: /opt/factorio/scenarios/default_lab_scenario 10 | ports: 11 | - '34198:34197/udp' 12 | - '27016:27015/tcp' 13 | restart: always 14 | entrypoint: ["sh", "/scenario.sh", "default_lab"] 15 | deploy: 16 | resources: 17 | limits: 18 | cpus: '1' 19 | memory: 512m -------------------------------------------------------------------------------- /cluster/docker/!README.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | To build the Headless Factorio docker image and upload the mods to your local Factorio installation: 4 | ``` 5 | sh factorio/run.sh 6 | ``` 7 | 8 | If you don't care about mods, you can simply run: 9 | ``` 10 | docker build -t factorio . 11 | ``` 12 | 13 | and then 14 | ``` 15 | docker run -d \ 16 | -p 34197:34197/udp \ 17 | -p 27015:27015/tcp \ 18 | factorio 19 | ``` 20 | 21 | Note: You should provide your own location for installation files. 22 | 23 | -------------------------------------------------------------------------------- /cluster/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 factoriotools/factorio:1.1.110 2 | #--platform=linux/amd64/v2 3 | 4 | COPY config /opt/factorio/config 5 | COPY config /factorio/config 6 | COPY mods /opt/factorio/mods 7 | COPY config/scenario.sh /opt/factorio/scenario.sh 8 | 9 | 10 | # Open ports: 11 | # - '34197:34197/udp' 12 | # - '27015:27015/tcp' 13 | 14 | # Expose ports 15 | EXPOSE 34197/udp 16 | EXPOSE 27015/tcp 17 | 18 | 19 | RUN chmod +x /opt/factorio/scenario.sh # Fix the chmod path 20 | ENTRYPOINT ["/opt/factorio/scenario.sh"] -------------------------------------------------------------------------------- /cluster/docker/build.sh: -------------------------------------------------------------------------------- 1 | docker build -t factorio . 2 | 3 | cd mods 4 | tar -czf headless-player_0.1.0.tgz headless-player_0.1.0 5 | tar -czf stdlib_1.4.6.tgz stdlib_1.4.6 6 | 7 | mv stdlib_1.4.6.tgz ~/Library/Application\ Support/Factorio/mods/stdlib_1.4.6.tgz 8 | mv headless-player_0.1.0.tgz ~/Library/Application\ Support/Factorio/mods/headless-player_0.1.0.tgz 9 | 10 | rm -rf ~/Library/Application\ Support/Factorio/mods/stdlib_1.4.6 11 | rm -rf ~/Library/Application\ Support/Factorio/mods/headless-player_0.1.0 12 | 13 | cp -rf stdlib_1.4.6 ~/Library/Application\ Support/Factorio/mods/stdlib_1.4.6 14 | cp -rf headless-player_0.1.0 ~/Library/Application\ Support/Factorio/mods/headless-player_0.1.0 15 | 16 | cd .. 17 | 18 | cp config/mod-list.json ~/Library/Application\ Support/Factorio/mods/mod-list.json -------------------------------------------------------------------------------- /cluster/docker/build_alternative_installation_location.sh: -------------------------------------------------------------------------------- 1 | # This has has different mod location 2 | docker build -t factorio . --platform linux/amd64 3 | 4 | cd mods 5 | tar -czf headless-player_0.1.0.tgz headless-player_0.1.0 6 | tar -czf stdlib_1.4.6.tgz stdlib_1.4.6 7 | 8 | mv stdlib_1.4.6.tgz ~/Applications/Factorio.app/Contents/Resources/mods/stdlib_1.4.6.tgz 9 | mv headless-player_0.1.0.tgz ~/Applications/Factorio.app/Contents/Resources/mods/headless-player_0.1.0.tgz 10 | 11 | # unzip 12 | # cd ~/Applications/Factorio.app/Contents/Resources/mods 13 | 14 | 15 | # remove old files 16 | rm -rf ~/Applications/Factorio.app/Contents/Resources/mods/stdlib_1.4.6 17 | rm -rf ~/Applications/Factorio.app/Contents/Resources/mods/headless-player_0.1.0 18 | 19 | # untar files 20 | #tar -xzf ~/Applications/Factorio.app/Contents/Resources/mods/stdlib_1.4.6.tgz 21 | #tar -xzf ~/Applications/Factorio.app/Contents/Resources/mods/headless-player_0.1.0.tgz 22 | 23 | # move files to mods directory 24 | cp -rf stdlib_1.4.6 ~/Applications/Factorio.app/Contents/Resources/mods/stdlib_1.4.6 25 | cp -rf headless-player_0.1.0 ~/Applications/Factorio.app/Contents/Resources/mods/headless-player_0.1.0 26 | 27 | #rm -rf ~/Applications/Factorio.app/Contents/Resources/mods/stdlib_1.4.6.tgz 28 | #rm -rf ~/Applications/Factorio.app/Contents/Resources/mods/headless-player_0.1.0.tgz 29 | 30 | cd .. 31 | 32 | cp config/mod-list.json ~/Applications/Factorio.app/Contents/Resources/mods/mod-list.json -------------------------------------------------------------------------------- /cluster/docker/config/docker-update-mods.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eou pipefail 3 | 4 | if [[ -f /run/secrets/username ]]; then 5 | USERNAME=$(cat /run/secrets/username) 6 | fi 7 | 8 | if [[ -f /run/secrets/token ]]; then 9 | TOKEN=$(cat /run/secrets/token) 10 | fi 11 | 12 | if [[ -z ${USERNAME:-} ]]; then 13 | USERNAME="$(jq -j ".username" "$CONFIG/server-settings.json")" 14 | fi 15 | 16 | if [[ -z ${TOKEN:-} ]]; then 17 | TOKEN="$(jq -j ".token" "$CONFIG/server-settings.json")" 18 | fi 19 | 20 | if [[ -z ${USERNAME:-} ]]; then 21 | echo "You need to provide your Factorio username to update mods." 22 | fi 23 | 24 | if [[ -z ${TOKEN:-} ]]; then 25 | echo "You need to provide your Factorio token to update mods." 26 | fi 27 | 28 | ./update-mods.sh "$VERSION" "$MODS" "$USERNAME" "$TOKEN" 29 | -------------------------------------------------------------------------------- /cluster/docker/config/mod-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "mods": 3 | [ 4 | { 5 | "name": "base", 6 | "enabled": true 7 | }, 8 | { 9 | "name": "stdlib", 10 | "enabled": true 11 | }, 12 | { 13 | "name": "headless-player", 14 | "enabled": true 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /cluster/docker/config/rconpw: -------------------------------------------------------------------------------- 1 | factorio -------------------------------------------------------------------------------- /cluster/docker/config/scenario2map.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eoux pipefail 3 | 4 | if [[ -z ${1:-} ]]; then 5 | echo "No argument supplied" 6 | fi 7 | 8 | SERVER_SCENARIO="$1" 9 | mkdir -p "$SAVES" 10 | mkdir -p "$CONFIG" 11 | mkdir -p "$MODS" 12 | mkdir -p "$SCENARIOS" 13 | 14 | if [[ ! -f $CONFIG/server-settings.json ]]; then 15 | cp /opt/factorio/data/server-settings.example.json "$CONFIG/server-settings.json" 16 | fi 17 | 18 | if [[ ! -f $CONFIG/map-gen-settings.json ]]; then 19 | cp /opt/factorio/data/map-gen-settings.example.json "$CONFIG/map-gen-settings.json" 20 | fi 21 | 22 | if [[ ! -f $CONFIG/map-settings.json ]]; then 23 | cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json" 24 | fi 25 | 26 | exec /opt/factorio/bin/x64/factorio \ 27 | --scenario2map "$SERVER_SCENARIO" 28 | -------------------------------------------------------------------------------- /cluster/docker/install-docker.sh: -------------------------------------------------------------------------------- 1 | #ssh -i "factorio.pem" ec2-user@ec2-18-133-239-115.eu-west-2.compute.amazonaws.com 2 | 3 | # Setup 4 | sudo yum update -y 5 | sudo amazon-linux-extras install docker -y 6 | sudo yum install docker -y 7 | sudo service docker start 8 | sudo usermod -a -G docker ec2-user 9 | 10 | # Docker 11 | sudo mkdir -p /opt/factorio 12 | sudo chown 845:845 /opt/factorio 13 | sudo docker run -d \ 14 | -p 34197:34197/udp \ 15 | -p 27015:27015/tcp \ 16 | -v /opt/factorio:/factorio \ 17 | --name factorio \ 18 | --restart=always \ 19 | factoriotools/factorio 20 | 21 | sudo docker logs factorio 22 | sudo docker stop factorio 23 | 24 | nano /opt/factorio/config/server-settings.json -------------------------------------------------------------------------------- /cluster/docker/main.py: -------------------------------------------------------------------------------- 1 | import docker 2 | client = docker.from_env() 3 | 4 | container = client.containers.run('factorio', {'34197/udp': 34197, '27015/tcp': 27000}, platform='linux/amd64' 5 | ,detach=True) 6 | 7 | print(container.logs()) 8 | 9 | -------------------------------------------------------------------------------- /cluster/docker/mods/headless-player_0.1.0/data.lua: -------------------------------------------------------------------------------- 1 | --item.lua 2 | 3 | local paperclip = table.deepcopy(data.raw["item"]["iron-gear-wheel"]) 4 | paperclip.name = "paperclip" 5 | paperclip.type = "item" 6 | paperclip.stack_size = 1000 7 | paperclip.icons = { 8 | { 9 | icon = "__headless-player__/graphics/paperclip.png", 10 | icon_size = 128 11 | }, 12 | } 13 | 14 | local recipe = table.deepcopy(data.raw["recipe"]["iron-gear-wheel"]) 15 | recipe.name = "recipe-paperclip" 16 | recipe.enabled = true 17 | recipe.icons = { 18 | { 19 | icon = "__headless-player__/graphics/paperclip.png", 20 | icon_size = 128 21 | } 22 | } 23 | recipe.ingredients = {{"iron-plate",2}} 24 | recipe.result = "paperclip" 25 | 26 | data:extend{paperclip,recipe} -------------------------------------------------------------------------------- /cluster/docker/mods/headless-player_0.1.0/description.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": "a", 3 | "multiplayer-compatible": true, 4 | "is-main-game": true 5 | } -------------------------------------------------------------------------------- /cluster/docker/mods/headless-player_0.1.0/graphics/paperclip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/docker/mods/headless-player_0.1.0/graphics/paperclip.png -------------------------------------------------------------------------------- /cluster/docker/mods/headless-player_0.1.0/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "headless-player", 3 | "version": "0.1.0", 4 | "title": "Headless Player", 5 | "author": "Jack Hopkins", 6 | "factorio_version": "1.1", 7 | "dependencies": ["base >= 1.1", "stdlib"], 8 | "description": "This mod a paperclip." 9 | } -------------------------------------------------------------------------------- /cluster/docker/mods/headless-player_0.1.0/locale/en/en.cfg: -------------------------------------------------------------------------------- 1 | [item-name] 2 | paperclip=Paperclip 3 | 4 | [item-description] 5 | paperclip=The object of our desire 6 | 7 | [recipe-name] 8 | recipe-paperclip=Paperclip (Recipe) -------------------------------------------------------------------------------- /cluster/docker/mods/mod-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "mods": 3 | [ 4 | 5 | { 6 | "name": "base", 7 | "enabled": true 8 | }, 9 | 10 | { 11 | "name": "headless-player", 12 | "enabled": true 13 | }, 14 | 15 | { 16 | "name": "stdlib", 17 | "enabled": true 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Afforess 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stdlib", 3 | "version": "1.4.6", 4 | "factorio_version": "1.1", 5 | "title": "Factorio Standard Library", 6 | "author": "Afforess", 7 | "contact": "", 8 | "dependencies": [ 9 | "base >= 1.1.0" 10 | ], 11 | "homepage": "https://github.com/Afforess/Factorio-Stdlib", 12 | "description": "The Factorio Standard Library is a project to bring Factorio modders high-quality, commonly-required utilities and tools.", 13 | "package": { 14 | "ignore": [ 15 | "wiki/**", 16 | "spec/**", 17 | "tools/**", 18 | "doc/**", 19 | "faketorio/**" 20 | ], 21 | "scripts": { 22 | "datestamp": ".publish/datestamp.sh", 23 | "prepackage": ".publish/prepackage.sh", 24 | "version": ".publish/version.sh", 25 | "prepublish": ".publish/prepublish.sh", 26 | "publish": ".publish/publish.sh" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/config.lua: -------------------------------------------------------------------------------- 1 | return {} 2 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/category.lua: -------------------------------------------------------------------------------- 1 | --- Category 2 | -- @classmod Data.Category 3 | 4 | local Category = { 5 | __class = 'Category', 6 | __index = require('__stdlib__/stdlib/data/data'), 7 | __call = require('__stdlib__/stdlib/data/data').__call 8 | } 9 | setmetatable(Category, Category) 10 | 11 | Category.category_types = { 12 | ['ammo-category'] = true, 13 | ['equipment-category'] = true, 14 | ['fuel-category'] = true, 15 | ['recipe-category'] = true, 16 | ['module-category'] = true, 17 | ['rail-category'] = true, 18 | ['resource-category'] = true 19 | } 20 | 21 | function Category:create() 22 | return self 23 | end 24 | 25 | function Category:add() 26 | return self 27 | end 28 | 29 | function Category:remove() 30 | return self 31 | end 32 | 33 | function Category:replace(a, b) 34 | if self:valid('category') then 35 | self:remove(a) 36 | self:add(b) 37 | end 38 | return self 39 | end 40 | 41 | return Category 42 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/entity.lua: -------------------------------------------------------------------------------- 1 | --- Entity class 2 | -- @classmod Data.Entity 3 | 4 | local Data = require('__stdlib__/stdlib/data/data') 5 | 6 | local Entity = { 7 | __class = 'Entity', 8 | __index = Data, 9 | __call = Data.__call 10 | } 11 | setmetatable(Entity, Entity) 12 | 13 | function Entity:get_minable_item() 14 | local Item = require('__stdlib__/stdlib/data/item') 15 | if self:is_valid() then 16 | local m = self.minable 17 | return Item(m and (m.result or (m.results and (m.results[1] or m.results.name))), nil, self.options) 18 | end 19 | return Item() 20 | end 21 | 22 | function Entity:is_player_placeable() 23 | if self:is_valid() then 24 | return self:Flags():any('player-creation', 'placeable-player') 25 | end 26 | return false 27 | end 28 | 29 | function Entity:change_lab_inputs(name, add) 30 | if self:is_valid('lab') then 31 | Entity.Unique_Array.set(self.inputs) 32 | if add then 33 | self.inputs:add(name) 34 | else 35 | self.inputs:remove(name) 36 | end 37 | else 38 | log('Entity is not a lab.' .. _G.data_traceback()) 39 | end 40 | return self 41 | end 42 | 43 | return Entity 44 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/fluid.lua: -------------------------------------------------------------------------------- 1 | --- Fluid 2 | -- @classmod Data.Fluid 3 | 4 | local Data = require('__stdlib__/stdlib/data/data') 5 | 6 | local Fluid = { 7 | __class = 'Fluid', 8 | __index = Data, 9 | } 10 | 11 | function Fluid:__call(fluid) 12 | return self:get(fluid, 'fluid') 13 | end 14 | setmetatable(Fluid, Fluid) 15 | 16 | return Fluid 17 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/item.lua: -------------------------------------------------------------------------------- 1 | --- Item 2 | -- @classmod Data.Item 3 | 4 | local Data = require('__stdlib__/stdlib/data/data') 5 | local Table = require('__stdlib__/stdlib/utils/table') 6 | 7 | local Item = { 8 | __class = 'Item', 9 | __index = Data, 10 | __call = Data.__call 11 | } 12 | setmetatable(Item, Item) 13 | 14 | local function make_table(params) 15 | if not params then 16 | return Table.keys(data.raw.lab) 17 | else 18 | return type(params) == 'table' and params or {params} 19 | end 20 | end 21 | 22 | local function change_inputs(name, lab_names, add) 23 | lab_names = make_table(lab_names) 24 | local Entity = require('__stdlib__/stdlib/data/entity') 25 | for _, lab_name in pairs(lab_names) do 26 | Entity(lab_name, 'lab'):change_lab_inputs(name, add) 27 | end 28 | end 29 | 30 | function Item:add_to_labs(lab_names) 31 | if self:is_valid() then 32 | change_inputs(self.name, lab_names, true) 33 | end 34 | return self 35 | end 36 | 37 | function Item:remove_from_labs(lab_names) 38 | if self:is_valid() then 39 | change_inputs(self.name, lab_names, false) 40 | end 41 | return self 42 | end 43 | 44 | return Item 45 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/modules/class.yuml: -------------------------------------------------------------------------------- 1 | // {type:class} 2 | // {direction:topDown} 3 | [Data{bg:red}]->[Recipe] 4 | [Recipe]->[Raw] 5 | [Ingredients] 6 | [Results] 7 | [Products]^[Ingredients] 8 | [Products]^[Results] 9 | [Recipe]->[Ingredients] 10 | [Recipe]->[Results] 11 | [Parent]<->[note: Get the damn parent] 12 | [Ingredients|parent;child] 13 | //[Ingredients]->[Parent] 14 | [Results]->[Parent] 15 | [Parent]->[Recipe] 16 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/modules/recipe-test.lua: -------------------------------------------------------------------------------- 1 | -- luacheck: ignore Data test Recipe RecipeOld recipe recipeOld 2 | local Data = require('__stdlib__/stdlib/data/data') 3 | local RecipeOld = require('__stdlib__/stdlib/data/recipe') 4 | local Recipe = require('__stdlib__/stdlib/data/modules/recipe') 5 | 6 | 7 | local recipeOld = RecipeOld('stone-furnace') 8 | local recipe = Recipe('stone-furnace') 9 | 10 | print(recipe:Ingredients():log()) 11 | 12 | --recipe:log() 13 | --recipe:Ingredients()():Results()():log() 14 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/data/modules/recipe.lua: -------------------------------------------------------------------------------- 1 | local Recipe = { 2 | __class = 'Recipe', 3 | __index = require('stdlib/data/data'), 4 | __call = function(self, recipe) return self:get(recipe, 'recipe') end 5 | } 6 | setmetatable(Recipe, Recipe) 7 | 8 | -- luacheck: ignore 9 | 10 | local Is = require('stdlib/utils/is') 11 | local Item = require('stdlib/data/item') 12 | 13 | -- function Recipe:__call(recipe) 14 | -- local new = self:get(recipe, 'recipe') 15 | -- rawset(new, 'is_difficult', new.normal ~= nil) 16 | -- return new 17 | -- end 18 | 19 | local Products = require('stdlib/data/modules/products') 20 | 21 | function Recipe:Ingredients() 22 | return Products(self, 'ingredients') 23 | end 24 | Recipe.Ing = Recipe.Ingredients 25 | 26 | function Recipe:Results() 27 | return Products(self, 'results') 28 | end 29 | 30 | return Recipe 31 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/event/modules/merge_data.lua: -------------------------------------------------------------------------------- 1 | local table = require('__stdlib__/stdlib/utils/table') 2 | 3 | local function merge_additional_data(additional_data_array, data) 4 | for _, new_data in pairs(additional_data_array) do 5 | if type(new_data) == 'table' then 6 | table.merge(data, table.deepcopy(new_data)) 7 | elseif type(new_data) == 'function' then 8 | local new_data_func_result = new_data(data.index) 9 | if type(new_data_func_result) == 'table' then 10 | table.merge(data, new_data_func_result) 11 | else 12 | error('additional data function did not return a table') 13 | end 14 | else 15 | error('additional data present but is not a function or table') 16 | end 17 | end 18 | return data 19 | end 20 | 21 | return merge_additional_data 22 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/misc/migrate.lua: -------------------------------------------------------------------------------- 1 | --- Migration helper functions 2 | -- @module Misc.Migrate 3 | 4 | local Migrate = { 5 | __class = 'Migrate', 6 | __index = require('__stdlib__/stdlib/core') 7 | } 8 | setmetatable(Migrate, Migrate) 9 | 10 | local Is = require('__stdlib__/stdlib/utils/is') 11 | 12 | --- Migrate a dictionary of recipe -> tech names 13 | -- @tparam dictionary dictionary 14 | function Migrate.Recipes(dictionary) 15 | Is.Assert.Table(dictionary, 'dictionary of recipes->technology not found') 16 | for _, force in pairs(game.forces) do 17 | for recipe, tech in pairs(dictionary) do 18 | if force.technologies[tech] and force.technologies[tech].researched then 19 | if force.recipes[recipe] then 20 | force.recipes[recipe].enabled = true 21 | end 22 | end 23 | end 24 | end 25 | end 26 | 27 | function Migrate.all_recipes() 28 | for _, force in pairs(game.forces) do 29 | for _, tech in pairs(force.technologies) do 30 | if tech.researched then 31 | for _, unlock in pairs(tech.effects or {}) do 32 | if unlock.type == 'unlock-recipe' then 33 | force.recipes[unlock.recipe].enabled = true 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | 41 | return Migrate 42 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/utils/defines/time.lua: -------------------------------------------------------------------------------- 1 | --- A defines module for retrieving the number of ticks in 1 unit of time. 2 | -- Extends the Factorio defines table. 3 | -- @module defines.time 4 | 5 | -- defines table is automatically required in all mod loading stages. 6 | 7 | local SECOND = 60 8 | local MINUTE = SECOND * 60 9 | local HOUR = MINUTE * 60 10 | local DAY = HOUR * 24 11 | local WEEK = DAY * 7 12 | local MONTH = DAY * 30 13 | local YEAR = DAY * 365 14 | 15 | --- Returns the number of ticks in a second, minute, hour, day, week, month, or year. 16 | -- @table time 17 | -- @usage local ten_seconds = defines.time.second * 10 18 | local time = { 19 | second = SECOND, -- the number of Factorio ticks in a second 20 | minute = MINUTE, -- the number of Factorio ticks in a second 21 | hour = HOUR, -- the number of Factorio ticks in an hour 22 | day = DAY, -- the number of Factorio ticks in an day 23 | week = WEEK, -- the number of Factorio ticks in a week 24 | month = MONTH, -- the number of Factorio ticks in a month (30 days) 25 | year = YEAR -- the number of Factorio ticks in a year (365 days) 26 | } 27 | 28 | _G.defines = _G.defines or {} 29 | _G.defines.time = time 30 | 31 | return time 32 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/stdlib/utils/type.lua: -------------------------------------------------------------------------------- 1 | local type = type 2 | 3 | local Type = { 4 | Table = function(param) return type(param) == 'table' end, 5 | Function = function(param) return type(param) == 'function' or type((getmetatable(param) or {}).__call) == 'function' end, 6 | Nil = function(param) return param == nil end, 7 | String = function(param) return type(param) == 'string' end, 8 | Number = function(param) return type(param) == 'number' end, 9 | } 10 | 11 | return Type 12 | -------------------------------------------------------------------------------- /cluster/docker/mods/stdlib_1.4.6/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/docker/mods/stdlib_1.4.6/thumbnail.png -------------------------------------------------------------------------------- /cluster/docker/probe.sh: -------------------------------------------------------------------------------- 1 | lines=$(echo | lsof -i -P -n | grep $1 | wc -c) 2 | 3 | if [[ $lines -gt 0 ]]; then 4 | echo 'Running on' $1; 5 | exit; 6 | fi; 7 | 8 | echo 'Dead'; 9 | exit 1; -------------------------------------------------------------------------------- /cluster/docker/requirements.txt: -------------------------------------------------------------------------------- 1 | docker -------------------------------------------------------------------------------- /cluster/docker/run.sh: -------------------------------------------------------------------------------- 1 | docker build -t factorio . 2 | 3 | cd mods 4 | tar -czf headless-player_0.1.0.tgz headless-player_0.1.0 5 | tar -czf stdlib_1.4.6.tgz stdlib_1.4.6 6 | 7 | mv stdlib_1.4.6.tgz ~/Library/Application\ Support/Factorio/mods/stdlib_1.4.6.tgz 8 | mv headless-player_0.1.0.tgz ~/Library/Application\ Support/Factorio/mods/headless-player_0.1.0.tgz 9 | 10 | rm -rf ~/Library/Application\ Support/Factorio/mods/stdlib_1.4.6 11 | rm -rf ~/Library/Application\ Support/Factorio/mods/headless-player_0.1.0 12 | 13 | cp -rf stdlib_1.4.6 ~/Library/Application\ Support/Factorio/mods/stdlib_1.4.6 14 | cp -rf headless-player_0.1.0 ~/Library/Application\ Support/Factorio/mods/headless-player_0.1.0 15 | 16 | cd .. 17 | 18 | cp config/mod-list.json ~/Library/Application\ Support/Factorio/mods/mod-list.json 19 | 20 | docker run -d \ 21 | -p 34197:34197/udp \ 22 | -p 27015:27015/tcp \ 23 | factorio -------------------------------------------------------------------------------- /cluster/docker/run_local.sh: -------------------------------------------------------------------------------- 1 | docker run -d \ 2 | -p 34197:34197/udp \ 3 | -p 27015:27015/tcp \ 4 | --name=factorio \ 5 | --restart=always \ 6 | -it factorio -------------------------------------------------------------------------------- /cluster/docker/setup_docker_repo.sh: -------------------------------------------------------------------------------- 1 | aws ecr get-login-password --region eu-west-2 | docker login --username AWS --password-stdin 216370203482.dkr.ecr.eu-west-2.amazonaws.com 2 | aws ecr create-repository --repository-name factorio 3 | 4 | #public.ecr.aws/y5j3i3w6/raveler 5 | 6 | #docker login --username AWS --password-stdin public.ecr.aws/y5j3i3w6/paperclip_client:latest 7 | #docker tag hello-world public.ecr.aws/y5j3i3w6/paperclip_client:latest 8 | #docker push public.ecr.aws/y5j3i3w6/paperclip_client 9 | 10 | #docker tag hello-world 216370203482.dkr.ecr.eu-west-2.amazonaws.com/hello-world 11 | #docker push 216370203482.dkr.ecr.eu-west-2.amazonaws.com/hello-world #public.ecr.aws/y5j3i3w6/paperclip_client 12 | 13 | #216370203482.dkr.ecr.eu-west-2.amazonaws.com/hello-world -------------------------------------------------------------------------------- /cluster/local/assets/connect_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/local/assets/connect_button.png -------------------------------------------------------------------------------- /cluster/local/assets/connect_to_address_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/local/assets/connect_to_address_button.png -------------------------------------------------------------------------------- /cluster/local/assets/ip_field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/local/assets/ip_field.png -------------------------------------------------------------------------------- /cluster/local/assets/multiplayer_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/local/assets/multiplayer_button.png -------------------------------------------------------------------------------- /cluster/local/assets/quit_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/local/assets/quit_button.png -------------------------------------------------------------------------------- /cluster/local/requirements.txt: -------------------------------------------------------------------------------- 1 | # opencv-python-headless>=4.8.0 2 | opencv-python 3 | numpy>=1.24.0 4 | pyautogui>=0.9.54 5 | pillow>=10.0.0 6 | psutil>=5.9.0 7 | python-dotenv>=1.0.0 -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/blueprint.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/scenarios/default_lab_scenario/blueprint.zip -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/control2.lua: -------------------------------------------------------------------------------- 1 | local handler = require("event_handler") 2 | handler.add_lib(require("freeplay")) 3 | --handler.add_lib(require("silo-script")) 4 | --handler.add_lib(require("paperclips")) 5 | --require("story") 6 | --require "story" 7 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/description.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": "a", 3 | "multiplayer-compatible": true, 4 | "is-main-game": true 5 | } 6 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/info.json: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/locale/en/freeplay.cfg: -------------------------------------------------------------------------------- 1 | msg-intro=This is the Factorio Freeplay. Your task is to launch a rocket into space. You will need to research advanced technologies in order to unlock the rocket silo. Start small, work your way up with automation, and don't forget to protect yourself from the natives. 2 | scenario-name=Freeplay 3 | description=Your task is to launch a rocket into space. Start from nothing, work your way up with automation, and don't forget to protect yourself from the natives.\n[font=default-bold]This is the intended way of playing Factorio.[/font] 4 | 5 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/planet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/scenarios/default_lab_scenario/planet1.png -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario/script.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/scenarios/default_lab_scenario/script.dat -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/blueprint.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/scenarios/default_lab_scenario_small/blueprint.zip -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/control2.lua: -------------------------------------------------------------------------------- 1 | local handler = require("event_handler") 2 | handler.add_lib(require("freeplay")) 3 | --handler.add_lib(require("silo-script")) 4 | --handler.add_lib(require("paperclips")) 5 | --require("story") 6 | --require "story" 7 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/description.json: -------------------------------------------------------------------------------- 1 | { 2 | "order": "a", 3 | "multiplayer-compatible": true, 4 | "is-main-game": true 5 | } 6 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/info.json: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/locale/en/freeplay.cfg: -------------------------------------------------------------------------------- 1 | msg-intro=This is the Factorio Freeplay. Your task is to launch a rocket into space. You will need to research advanced technologies in order to unlock the rocket silo. Start small, work your way up with automation, and don't forget to protect yourself from the natives. 2 | scenario-name=Freeplay 3 | description=Your task is to launch a rocket into space. Start from nothing, work your way up with automation, and don't forget to protect yourself from the natives.\n[font=default-bold]This is the intended way of playing Factorio.[/font] 4 | 5 | -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/planet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/scenarios/default_lab_scenario_small/planet1.png -------------------------------------------------------------------------------- /cluster/scenarios/default_lab_scenario_small/script.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/cluster/scenarios/default_lab_scenario_small/script.dat -------------------------------------------------------------------------------- /cluster/scenarios/open_world/README.md: -------------------------------------------------------------------------------- 1 | This remains empty, as it will cause the map to generate with the default seed -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/data/__init__.py -------------------------------------------------------------------------------- /data/blueprints_to_policies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/data/blueprints_to_policies/__init__.py -------------------------------------------------------------------------------- /data/blueprints_to_policies/blueprints/example/miner_cycle.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": [ 3 | { 4 | "entity_number": 1, 5 | "name": "electric-mining-drill", 6 | "position": { 7 | "x": 0.5, 8 | "y": 5.5 9 | }, 10 | "direction": 2 11 | }, 12 | { 13 | "entity_number": 2, 14 | "name": "electric-mining-drill", 15 | "position": { 16 | "x": 3.5, 17 | "y": 5.5 18 | }, 19 | "direction": 4 20 | }, 21 | { 22 | "entity_number": 3, 23 | "name": "electric-mining-drill", 24 | "position": { 25 | "x": 0.5, 26 | "y": 8.5 27 | } 28 | }, 29 | { 30 | "entity_number": 4, 31 | "name": "electric-mining-drill", 32 | "position": { 33 | "x": 3.5, 34 | "y": 8.5 35 | }, 36 | "direction": 6 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /data/blueprints_to_policies/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/data/blueprints_to_policies/models/__init__.py -------------------------------------------------------------------------------- /data/blueprints_to_policies/models/blueprint_entity.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, List, Optional 3 | 4 | 5 | @dataclass 6 | class BlueprintEntity: 7 | entity_number: int 8 | name: str 9 | position: Dict[str, float] 10 | direction: int = 0 11 | items: Dict[str, int] = None 12 | type: str = None 13 | neighbours: List[int] = None 14 | input_priority: str = None 15 | recipe: Optional[str] = None 16 | output_priority: str = None 17 | control_behavior: Dict = None 18 | connections: Dict = None 19 | filter: Dict = None -------------------------------------------------------------------------------- /data/blueprints_to_policies/schema.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/data/blueprints_to_policies/schema.json -------------------------------------------------------------------------------- /data/blueprints_to_policies/test_template.jinja2: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from factorio_entities import Position 4 | from factorio_instance import Direction, FactorioInstance, BoundingBox 5 | from factorio_types import Prototype, Resource 6 | 7 | @pytest.fixture() 8 | def game(): 9 | instance = FactorioInstance(address='localhost', 10 | bounding_box=200, 11 | tcp_port=27015, 12 | cache_scripts=True, 13 | fast=True, 14 | inventory={ 15 | {% for item, quantity in inventory.items() %} 16 | '{{ item }}': {{ quantity }}, 17 | {%- endfor %} 18 | }) 19 | instance.reset() 20 | yield instance 21 | 22 | 23 | def test_{{ test_name }}(game): 24 | """ 25 | {{ test_description }} 26 | :param game: 27 | :return: 28 | """ 29 | {{ test_content }} -------------------------------------------------------------------------------- /data/blueprints_to_policies/util.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | total = 0 4 | exec_directory = os.getcwd() 5 | refactor_dir = exec_directory + '/refactor' 6 | for root, dirs, files in os.walk(refactor_dir): 7 | total += len(files) 8 | 9 | pass -------------------------------------------------------------------------------- /data/plans/.example.env: -------------------------------------------------------------------------------- 1 | SERPER_KEYS="your_serpapi_api_keys delimited by ," 2 | OPENAI_API_KEY="your_openai_api_key" -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/finetuning_prompts/executor_plan/user_prompt.md: -------------------------------------------------------------------------------- 1 | Your starting inventory is {{}}. Your initial mining setup is: There are no entities on the map. Bring out the list of entities required for the objective 2 | {task} 3 | 4 | First plan step by step and bring out a thorough list of entities you need to craft to achieve this objective, do not forget required fuel. Then create the python script to achieve the task. The list of entities should incldue all the entities that you need to craft to achieve this objective 5 | -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/finetuning_prompts/step_supervised/user_prompt.md: -------------------------------------------------------------------------------- 1 | Your starting inventory is {starting_inventory}. Your initial mining setup is: {mining_setup}. Create a python script that achieves the following task 2 | {task} 3 | 4 | First bring out a thorough step-by-step plan how you can achieve this task and then create the one python script to achieve the full task. 5 | For your plan, follow this structure: 6 | 1) What entities are needed for the task 7 | 2) What entities do we have on the map, in different entity inventories or in our inventory 8 | 3) What entities are we missing for the task 9 | 4) Execution -- Taking into account 1,2 and 3, what steps do we need to take to successfully carry out the task 10 | -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/finetuning_prompts/system_message_policy.md: -------------------------------------------------------------------------------- 1 | You are an AI agent exploring the world of Factotio. 2 | 3 | You create Python scripts to interact with the game and to achieve a objective. Invent appropriate objectives, and write Python to achieve them. The script you write will define steps that are required to be carried out to successfully achieve the objective. Before each step, you first think what is the next step using comments. You then write the code to carry out this step. Ensure the step succeeds by writing assert statements. Observe the game entities and reason about how they can fit together to generate flows of resources. 4 | 5 | You have access to the following Game API for use in your Python code: 6 | {schema} -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/finetuning_prompts/user_message_step_filler.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the next step to achieve this objective. Analyse the objective, the steps taken thus far, game logs, mining setup and inventory to come up with an accurate plan and code for the next step 2 | 3 | USER INPUT 4 | 5 | Here is the main objective 6 | {objective} 7 | 8 | Here are the steps taken thus far 9 | {steps_taken} 10 | 11 | Here are the logs thus far in the game up until the current step you need to fill 12 | {print_trace} 13 | 14 | Here is the inventory at the current step 15 | {inventory} 16 | 17 | Here is the current mining setup 18 | {mining_setup} 19 | 20 | {query} -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/implementation_generation/user_message.md: -------------------------------------------------------------------------------- 1 | Here is the reference objective and its implementation 2 | REFERENCE OBJECTIVE 3 | 4 | objective 5 | {ground_truth_objective} 6 | 7 | mining_setup 8 | {reference_mining_setup} 9 | 10 | python implementation 11 | {implementation} 12 | 13 | INPUT OBJECTIVE 14 | 15 | objective 16 | {input_objective} 17 | 18 | mining_setup 19 | {input_mining_setup} 20 | 21 | Generate the python implementation for the input objective -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/implementation_reflection/user_message.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the user objective. Analyse the script that has been run and their error messages and use them to create a working script. Here is the reference implementation that works and that you should follow to see how to correctly use the API 2 | 3 | REFERENCE INPUT 4 | {reference_objective} 5 | 6 | REFERENCE MINING SETUP 7 | {mining_setup} 8 | 9 | REFERENCE IMPLEMENTATION 10 | {reference_implementation} 11 | 12 | USER INPUT 13 | 14 | GAME_LOG 15 | {game_log} 16 | 17 | Mining setup 18 | {mining_setup} 19 | 20 | USER OBJECTIVE 21 | {objective} 22 | 23 | ATTEMPT WITH ERROR 24 | {script_with_error} 25 | 26 | ERROR MESSAGE 27 | {error_message} 28 | 29 | MINING SETUP DURING ERROR 30 | {mining_setup_during_error} 31 | 32 | -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/objective_generation/examples/create_automatic_copper_mine/input.md: -------------------------------------------------------------------------------- 1 | INPUT OBJECTIVE 2 | Create an automatic burner mine for copper plates to a chest -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/objective_generation/examples/create_automatic_copper_mine/output.md: -------------------------------------------------------------------------------- 1 | Here are similar output objectives 2 | 3 | ###START OF OBJECTIVES 4 | We need a burner mining setup that mines iron ore to a chest 5 | Create a mine with 2 burner mines that mines copper to 2 chests 6 | Create a automatic mining setup that sends stone to 3 furnaces 7 | Create a mine with 3 burner mining drills that transport stone to 2 furnaces 8 | ###END OF OBJECTIVES -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/objective_generation/user_message.md: -------------------------------------------------------------------------------- 1 | EXAMPLES 2 | {examples} 3 | 4 | USER INPUT 5 | 6 | The ground truth objective 7 | {user_input} -------------------------------------------------------------------------------- /data/prompts/bottoms_up_prompts/skill_generator/user_message.md: -------------------------------------------------------------------------------- 1 | Here is the reference objective and its implementation 2 | REFERENCE OBJECTIVE 3 | 4 | objective 5 | {ground_truth_objective} 6 | 7 | mining_setup 8 | {reference_mining_setup} 9 | 10 | python implementation 11 | {implementation} 12 | 13 | Generate a new and similar objective and the python implementation for the objective -------------------------------------------------------------------------------- /data/prompts/finetuning_prompts/user_message_synth.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python function to achieve the user input objective given using the factorio API. 2 | 3 | # RECIPES 4 | Here are also all the recipes of entities available in the game to craft 5 | {recipes} 6 | 7 | # PARENT FUNCTION 8 | Here is the parent function where the synthesis of the current input was called 9 | {parent_function} 10 | 11 | # USER INPUT 12 | Create a function for this objective 13 | {user_input} -------------------------------------------------------------------------------- /data/prompts/outcome_test/system_message.md: -------------------------------------------------------------------------------- 1 | You are an AI agent creating Python scripts to test whether an outcome was achieved in the Factorio game. 2 | You have the following api schema available to you {api_schema} 3 | Only write in python in ``` blocks. 4 | You are given the objective that will be tried to achieve. You must write a test to check whether this outcome has been achieved 5 | IMPORTANT 6 | Make sure it is a script and not a function. it must be a script that can be directly run and not a function definition 7 | The most common way how to write a test checking the outcome is to use assert statements. For instance, if the objective was to craft 5 iron plates, you can check the inventory using the api and assert that the inventory has 5 iron plates inside 8 | Make sure the tests have informative error messages when they fail -------------------------------------------------------------------------------- /data/prompts/outcome_test/user_message.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to test whether the objective was achieved. Here is an example 2 | 3 | EXAMPLE INPUT 4 | The objective is to craft 5 copper plates. 5 | 6 | EXAMPLE OUTPUT 7 | ```python 8 | number_of_copper_plates = inspect_inventory()[Prototype.CopperPlate] 9 | assert number_of_copper_plates == 5, f"Inventory does not have 5 copper plates, it has {{number_of_copper_plates}}" 10 | ``` 11 | 12 | USER INPUT 13 | The objective is '{objective}' -------------------------------------------------------------------------------- /data/prompts/planning/system_message.md: -------------------------------------------------------------------------------- 1 | You are an AI assistant creating a plan for a Factorio game objective. 2 | Please describe in natural language how will you achieve the user objective that is given. Your plan will have a SUMMARY part, where you summarise the required steps, all the required crafting components and actions, and a STEP part, where you lay out the steps that need to be done 3 | You have access to the game's api which is as follows 4 | {api_schema} 5 | Using the game's api and resources, including their crafting recipes, generate a comprehensive plan how to solve this objective. 6 | Be very careful to not overlook any potential required components in any recipes when planning. Pay additional care for stone furnaces as they can be both used for smelting things and as crafting components. Once you place a stone furnace, you cannot use it as a component for another recipe. In those cases you need to craft multiple stone furnaces 7 | You also have access to inventory. If possible, use materials from inventory to achieve the goals -------------------------------------------------------------------------------- /data/prompts/planning/user_message.md: -------------------------------------------------------------------------------- 1 | Generate a plan how to achieve the user specified objective. Below is one example 2 | 3 | EXAMPLES 4 | INPUT 5 | Craft copper plates 6 | Inventory: "{{}}" 7 | OUTPUT 8 | 9 | SUMMARY 10 | In general to craft copper plates, we need to craft a furnace, mine copper ore and smelt copper ore in the furnace. We need 1 copper ore for one copper plate. As we have no materials in inventory, we need to craft everything from scratch 11 | 12 | STEPS 13 | To smelt copper plates the plan is as follows 14 | 1) As the copper plate requires stone furnace to smelt, first we need to mine 5 stone and 15 | 2) Craft stone furnace 16 | 3) Mine coal for stone furnace 17 | 4) Move to copper ore 18 | 5) Place down stone furnace 19 | 6) Mine copper ore 20 | 7) Place coal and copper ore to stone furnace 21 | 8) Smelt copper ore for copper plates 22 | 23 | USER INPUT 24 | {objective} 25 | Inventory: {inventory} 26 | 27 | -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_objectives/examples/connect_things/details.json: -------------------------------------------------------------------------------- 1 | {"name": "create_a_stone_mining_drill_that_feeds_into_a_fueled_furnace", 2 | "description": "Sets up an automated stone mining and smelting station by placing and fueling a burner mining drill, stone furnace, and burner inserter in the correct configuration near a stone patch", 3 | "dependencies":["'burner-inserter': 1", "'burner-mining-drill': 1", "'stone-furnace': 1", "'coal': 15"], 4 | "objective": "Create a burner mine that puts stone into a furnace for smelting."} -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_objectives/examples/craft_furnace/details.json: -------------------------------------------------------------------------------- 1 | {"name": "craft_stone_furnace", 2 | "description": "Tests gathering stone resources and crafting a stone furnace. Verifies the player can locate stone, harvest the required quantity, craft a furnace, and confirm the crafted item appears in inventory.", 3 | "dependencies":[], 4 | "objective": "Get a stone furnace"} -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_objectives/examples/craft_furnace/snippet.py: -------------------------------------------------------------------------------- 1 | # Get stone for furnace 2 | stone_pos = nearest(Resource.Stone) 3 | move_to(stone_pos) 4 | harvest_resource(stone_pos, quantity=5) 5 | 6 | # Verify we got stone 7 | inventory = inspect_inventory() 8 | assert inventory.get(Prototype.Stone) >= 5, "Failed to get enough stone" 9 | 10 | # Craft stone furnace 11 | craft_item(Prototype.StoneFurnace, 1) 12 | 13 | # Verify we have stone furnace 14 | inventory = inspect_inventory() 15 | assert inventory.get(Prototype.StoneFurnace) >= 1, "Failed to craft stone furnace" -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_objectives/system_prompt.md: -------------------------------------------------------------------------------- 1 | You are an AI assistant given a implementation of a factorio objective using the python factorio API, the general description of the implementation and the name of the implementation. Your goal is to analyse the implementation, the name and description and output a objective for this implementation. The objective is something that can be give to someone and from that objective they should create this implementation. Some examples of objectives are "Get one burner mining drill", "Create an automated burner mining mine that transports iron to a chest" etc. 2 | 3 | Keep the objectives short and concise. At the same time make them detailed, for instance "Create iron plates for one boiler" is a better objective than "Create iron plates", as the latter is too vague and probably does not give enough detail to match the given implementation to this exact objective. You are also given examples of implementations and correct objectives, use them as style examples. 4 | 5 | First think step by step regarding what objective is suitable for the implementation. Then bring out the objective by putting the objective between 2 #OBJECTIVE tags for instance 6 | #OBJECTIVE 7 | Create iron plates for one boiler 8 | #OBJECTIVE 9 | Only use two #OBJECTIVE tags in your answer and they should exactly be before and after the generated objective. The output is parsed automatically so do not use the #OBJECTIVE tags anywhere else -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_objectives/user_message.md: -------------------------------------------------------------------------------- 1 | Here are some examples of inputs and output objectives 2 | 3 | EXAMPLES 4 | {examples} 5 | 6 | USER INPUT 7 | 8 | DESCRIPTION 9 | {description} 10 | 11 | INVENTORY REQUIREMENTS 12 | {dependencies} 13 | 14 | NAME 15 | {name} 16 | 17 | IMPLEMENTATION 18 | {implementation} -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_plans/examples/create_burner_mine/details.json: -------------------------------------------------------------------------------- 1 | {"plan": "Here's a thorough plan to create an automated copper mining setup using a burner mining drill, a wooden chest, a burner inserter, and transport belts.\nWe have all the necessary components in our inventory, so we don't need to craft anything.\nThe plan will involve placing the drill on a copper patch and placing the chest at a distance. We then need to set up the inserter next to the chest that places items into the chest, rotate the inserter as the inserter needs to put items into the chest. Then we need to connect the drill drop position with the chest inserter pickup position.\nFinally, we'll need to fuel the drill and inserter, wait some time and then check if the chest has copper ore in it.", 2 | "starting_inventory": { 3 | "burner-mining-drill": 3, 4 | "stone-furnace": 9, 5 | "transport-belt": 100, 6 | "burner-inserter": 5, 7 | "wooden-chest": 1, 8 | "coal": 10 9 | }, 10 | "mining_setup":"There are no entities on the map", 11 | "objective": "We need create an automated copper burner mine that mines copper ore to a chest further away from it"} -------------------------------------------------------------------------------- /data/prompts/postprocessing/backfill_plans/user_message.md: -------------------------------------------------------------------------------- 1 | Here are some examples of inputs and output plans 2 | 3 | EXAMPLES 4 | {examples} 5 | 6 | USER INPUT 7 | 8 | OBJECTIVE 9 | {objective} 10 | 11 | INVENTORY 12 | {inventory} 13 | 14 | MINING SETUP 15 | {mining_setup} 16 | 17 | IMPLEMENTATION 18 | {implementation} -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/finetuning_prompts/user_message_step_filler.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the next step to achieve this objective. Analyse the objective, the steps taken thus far, game logs, mining setup and inventory to come up with an accurate plan and code for the next step 2 | 3 | USER INPUT 4 | 5 | Here is the main objective 6 | {objective} 7 | 8 | Here are the steps taken thus far 9 | {steps_taken} 10 | 11 | Here are the logs thus far in the game up until the current step you need to fill 12 | {print_trace} 13 | 14 | Here is the inventory at the current step 15 | {inventory} 16 | 17 | Here is the current mining setup 18 | {mining_setup} 19 | 20 | {query} -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/Create_electric_coal_mine/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to place and power a electric mining drill on a coal patch 2 | Mining setup: There are no entities on the map 3 | Inventory: { 4 | 'electric-mining-drill': 1, 5 | 'boiler': 1, 6 | 'pipe': 15, 7 | 'steam-engine': 1, 8 | "offshore-pump": 1, 9 | 'small-electric-pole': 10, 10 | "coal": 5 11 | } -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/assembling_machine/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to use an assembling machine to craft iron gear wheels 2 | Mining setup: There is a powered assembling machine on the map 3 | Inventory: {"iron-plate": 50} -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/assembling_machine/plan.md: -------------------------------------------------------------------------------- 1 | Plan Analysis: 2 | This objective requires a assembling machine and iron plates. Powered assembling machine is on the map and e haveiron paltesin our inventory so we have everything we need. We first need to set the recipe of the assembling machine to iron gear wheels and then insert iron plates. After some while we can confirm if it worked by seeing if the assembling machine has iron gear wheels in it 3 | 4 | ###START OF PLAN 5 | STEP 1: Set recipe. Set the recipe of assembling machine to iron gear wheels 6 | STEP 2: insert resources. Insert the iron plates into the assembling machine 7 | STEP 3: Confirm success. Inspect inventory of assembling machine and checked that it has iron gear wheels 8 | ###END OF PLAN -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/connect_chest_to_existing_furnaces/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to place a chest where smelt items from all furnaces on the map are stored 2 | Mining setup: The following entities are on the map and can be used: [Furnace(fuel={'coal': 4}, name='stone-furnace', position=Position(x=-12.0, y=-12.0), direction=, energy=1600.0, type='furnace', dimensions=Dimensions(width=1.3984375, height=1.3984375), tile_dimensions=TileDimensions(tile_width=2.0, tile_height=2.0), prototype=)>, health=200.0, warnings=['no ingredients to smelt'], status=, furnace_source={}, furnace_result={'iron-plate': 5})] 3 | Inventory: { 4 | 'wooden-chest': 1, 5 | 'burner-inserter': 2, 6 | "transport-belt": 15, 7 | "coal": 10 8 | } -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/create_automatic_copper_mine copy/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to create a mining setup that mines copper ore and puts it into a chest further away from the drill 2 | Mining setup: There are no entities on the map 3 | Inventory: {"burner-mining-drill":1, "burner-inserter": 1, "wooden-chest": 1, "transport-belt":15} -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/create_automatic_copper_mine copy/plan.md: -------------------------------------------------------------------------------- 1 | Plan Analysis: 2 | To solve this objective, we need a burner mining drill, a burner inserter, a wooden chest and transport belts (atleast 10). We have all the required entities in our inventory so we don't need to craft anything. 3 | 4 | We must place the burner minng drill onto a copper patch, place a chest further away, place an inserter next to the chest and connect the drill with the inserter. We can check the final mine by looking if the chest has any copper ore in it 5 | 6 | ###START OF PLAN 7 | STEP 1: Place the drill and the chest. We need to place the drill on the copper and a chest further away from it. 8 | STEP 2: Connect drill to chest. We also need to check if the construction worked by looking if the chest has copper in it. To connect the drill we need to achieve the following substeps 9 | - First put a inserter next to the chest and rotate the inserter to put items into the chest 10 | - Connect the drills drop position with the chest inserters pickup position with transport belts. We do not need to put a inserter next to the drill asthe drill directly drops items on the ground and onto the transport belt. 11 | - Wait 30 seconds and check if the chest has copper ore in it 12 | ###END OF PLAN -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/create_automatic_copper_mine/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to craft a burner mining drill, a burner inserter, a wooden chest and transport belts (atleast 10) 2 | Mining setup: There are no entities on the map 3 | Inventory: {} -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/planning_examples/rag_functions/create_automatic_copper_mine/plan.md: -------------------------------------------------------------------------------- 1 | Plan Analysis: 2 | Firstly we must craft the entities as there are no resources or entities in our inventory. For that we must first print out recipes, gather resources and craft all the required entities. 3 | 4 | ###START OF PLAN 5 | STEP 1: Print recipes. We need to craft a burner mining drill, a burner inserter, a wooden chest and some transport belts (atleast 10). We must print the recipes of all the items we need to craft 6 | STEP 2: Gather resources. We need to gather the resources needed to craft the items required. As the recipes involve smelting ore into plates, we also need to gather coal and stone to craft a furnace. We also need to gather coal to fuel the burner drill on the copper ore and the chest inserter. Therefore we need to gather: 7 | - Enough coal to fuel the inserter and furnace 8 | - Enough copper and iron for crafting the required entities 9 | - Enough stone for 2 stone furnaces, one for crafting the drill and one for smelting the plates 10 | STEP 3: Smelt plates. We need to smelt the plates using a stone furnace. We also need the craft the stone furnace. The substeps are then 11 | - Craft a stone furnace for smelting, place it down and fuel it 12 | - Smelt the plates using the stone furnace 13 | STEP 4: Craft the entities. We need to craft the following entities 14 | - Burner mining drill 15 | - Burner inserter 16 | - Wooden chest 17 | - Transport belts (atleast 10) 18 | ###END OF PLAN -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/user_message_correct.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the objective given. Analyse the scripts that have been run and their error messages and use them to create a working script. Here are examples of scripts that work and that you should follow to see how to correctly use the API 2 | 3 | examples of working scripts 4 | {working_examples} 5 | 6 | USER INPUT 7 | {objective} 8 | 9 | LATEST FULL SCRIPT RUN 10 | {last_executed_policy} 11 | 12 | SCRIPT PART WITH ERROR 13 | {script_with_error} 14 | 15 | ERROR MESSAGE 16 | {error_message} 17 | 18 | GAME_LOG 19 | {game_log} 20 | 21 | OUTPUT THE WHOLE POLICY WITH ALL FUNCTION DEFINITIONS -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/user_message_correct_filler.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the objective given. Analyse the script that has been run and their error messages and use them to create a working script. Here are examples of scripts that work and that you should follow to see how to correctly use the API 2 | 3 | examples of working scripts 4 | {working_examples} 5 | 6 | FULL SCRIPT WITH THE PLACEHOLDERS 7 | {last_executed_policy} 8 | 9 | GAME_LOG 10 | {game_log} 11 | 12 | INVENTORY 13 | {inventory} 14 | 15 | Mining setup 16 | {mining_setup} 17 | 18 | STEP YOU NEED TO FILL OUT 19 | {objective} 20 | 21 | ATTEMPT WITH ERROR 22 | {script_with_error} 23 | 24 | ERROR MESSAGE 25 | {error_message} 26 | 27 | -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/user_message_planning.md: -------------------------------------------------------------------------------- 1 | Create a plan that will be used to create a function that achieves the objective taking into account the recipes, the current game mine setup and inventory. Below are examples of successful plans that have implemented some objectives. 2 | 3 | EXAMPLE INPUTS AND PLANS 4 | {examples} 5 | 6 | USER INPUT 7 | {user_input} 8 | 9 | PLANNING OUTPUT -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/user_message_step.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the step given. Analyse the full plan, game outputs and actions to come up with a accurate python step. Below are also some examples of successful policies implementing similar steps 2 | 3 | EXAMPLES 4 | {examples} 5 | 6 | Important: Follow similar style to examples if possible. They are successful so you can use similar methods and approaches 7 | USER INPUT 8 | 9 | Here are the actions taken thus far towards the objective 10 | {action_trace} 11 | 12 | Here are the outputs thus far in the game 13 | {print_trace} 14 | 15 | Here is the full plan 16 | {full_plan} 17 | 18 | {user_input} -------------------------------------------------------------------------------- /data/prompts/prompts_for_notebook/user_message_step_filler.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the step given. Analyse the FULL SCRIPT, game logs and inventory to come up with an accurate code for the placeholder. Below are also some examples of successful policies implementing similar steps 2 | 3 | EXAMPLES 4 | {examples} 5 | 6 | Important: Follow similar style to examples if possible. They are successful so you can use similar methods and approaches 7 | USER INPUT 8 | 9 | Here is the FULL SCRIPT 10 | {full_script} 11 | 12 | Here are the logs thus far in the game up until the current step you need to fill 13 | {print_trace} 14 | 15 | Here is the inventory at the current step 16 | {inventory} 17 | 18 | Here is the current mining setup 19 | {mining_setup} 20 | 21 | The current step you need to output the code for 22 | {objective} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Connect and power electric mining drill/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to place a electric mining drill on copper ore and create a steam energy setup to power it. 2 | Mining setup: There are no entities on the map 3 | Inventory: {{ 4 | "iron-plate": 20, 5 | "coal": 20, 6 | "copper-plate": 20, 7 | "stone-furnace": 3 8 | }} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Connect and power electric mining drill/plan.md: -------------------------------------------------------------------------------- 1 | To achieve this objective we need to have a offshore pump, boiler, steam engine and the electric mining drill. We laso need pipes and electic power pokles for connections. We have all of them in our inventory 2 | Therefore first we need to mine place the offshore pump on a water source. Then we need to place the boiler next to the water pump and a steam engine next to the boiler. Then we need to connect all of them with pipes 3 | We then need to check if the steam engine is receiving electricity. 4 | if it is, we finally need to place the mining drill onto a copper patch and connect the drill with the steam engine with the electric poles -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Craft 1 burner-mining-drill from scratch/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to craft one burner mining drill from scratch as we have no items in our inventory. 2 | Mining setup: Currently there are no entities on the map 3 | Inventory: {{}} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Craft 1 burner-mining-drill from scratch/plan.md: -------------------------------------------------------------------------------- 1 | To achieve this objective we first need to mine all the raw resources and craft everything as we don't have any resources in the inventory. 2 | We need to mine enough iron ore for the plates and gear wheels, mine enough stone for the furnaces, and enough coal for the furnaces. 3 | In total for crafting one burner mining drill we need to 3 iron gear wheel (2 iron plates each), 3 iron plates and one stone furnace. This means we need atleast 9 iron plates. Therefore we need to mine atleast 9 iron ore, 5 coal and as we need 2 stone furnaces, one for the burner mining drill recipe and one for smelting ores, we need 2*5 = 10 stone. 4 | Taking into account the buffer we will mine atleast 20 iron ore, 10 coal and 15 stone 5 | Then we need to smelt iron plates from the ore. For that we need to put down a stone furnace, insert coal and iron ore and wait until all plates have been smelt. 6 | After that we can can craft out burner mining drill -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Craft 1 electric-mining-drill with fixed inventory/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to craft one electric mining drill 2 | Mining setup: There are no entities on the map 3 | Inventory: {{ 4 | "iron-plate": 20, 5 | "coal": 20, 6 | "copper-plate": 20, 7 | "stone-furnace": 3 8 | }} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Craft 1 electric-mining-drill with fixed inventory/plan.md: -------------------------------------------------------------------------------- 1 | To achieve this objective we need to get crafting materials for the electric mining drill. We need 3 electronic circuits, 5 iron gear wheels and 10 iron plates. To craft the gear wheels, we will need 10 iron plates. To craft the circuits we will need 3 iron plates. In total then we will need 23 iron plates but we only have 20. 2 | Therefore first we need to mine some additional iron ore to turn into iron plates. We should mine 10 more iron ore to ensure we have all the required resources. 3 | Then we need to smelt iron plates from the ore. For that we need to put down a stone furnace that we have in our inventory, insert coal and iron ore and wait until all plates have been smelt. 4 | After that we can can craft the electric mining drill -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Smelt_items/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to smelt iron ores into plates with a furnace 2 | Mining setup: We have a furnace on the map that we can use to smelt iron ores 3 | Inventory: We have enough iron and coal in the inventory to smelt the iron plates 4 | :param input_coal (int): The number of coal to insert into the furnace 5 | :param input_iron_ore (int): The number of iron ore to insert into the furnace 6 | :param furnace (Prototype.StoneFurnace): The furnace entity to use for smelting 7 | :param output_iron_plate (int): The number of iron plates to extract from the furnace 8 | :return: None as the iron plates will be in inventory -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Smelt_items/plan.md: -------------------------------------------------------------------------------- 1 | To achieve this objective we first need to check if we have enough iron and coal in the inventory to smelt the iron plates. We can check using the input_coal input_iron_ore variables. 2 | Then we will insert the input coal and iron ore into the furnace from the inventory. We will use the input furnace variable 3 | We will then wait and extract the iron plates 4 | Finally we will assert that we have enough iron plates using the output_iron_plate variable -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Smelt_items_with_furnace/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to smelt 10 iron ores into plates with a furnace 2 | Mining setup: We have a furnace on the map 3 | Inventory: {} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/Smelt_items_with_furnace/plan.md: -------------------------------------------------------------------------------- 1 | To achieve this objective we first need to mine 10 iron ores to make sure we have enough iron 2 | We then need to find a furnace that has enough coal in (5). If no furnace has enough coal, we need to mine coal as well. We can do this by inspecting the stone furnace entities and looking at their contents for coal 3 | We will then need to smelt the iron ores into plates. We will move to a stone furnace and insert the iron ore and wait until they are smelted 4 | Finally we will assert that we have enough iron plates in our inventory -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/automate_copper_transport_to_chest/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to create a mining setup that mines copper ore, smelts it and puts it into a chest further away from the drill 2 | Mining setup: There are no entities on the map 3 | Inventory: {inventory} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/automate_copper_transport_to_chest/plan.md: -------------------------------------------------------------------------------- 1 | To solve this objective, we need a burner mining drill, a burner inserter, a wooden chest and transport belts. 2 | First step - We first need to place down the drill that we have in our inventory at a copper patch and fuel it. We need to first move to the coal, then place the drill and then insert fuel to the drill. 3 | Second step - Then we need to put down the wooden chest. We currently do not have a wooden chest in our inventory. We then need to mine wood, craft the chest and place down the chest away from the drill. We first need to move to the chest position as we can't place items far away and then place the chest 4 | Third step - We then need to put down an inserter that puts items to the chest. We have an inserter in our inventory so we don't need to craft it. 5 | Fourth step - We also need to rotate the inserter to face the chest as by default it takes from the chest. We then also need to fuel the inserter. 6 | Last step - We need to connect the inserter and the drill with transport belts. We have transport belts in our inventory. We need to use the drop position of the drill and the pickup position of the inserter. -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/automate_drill_to_chest/input.md: -------------------------------------------------------------------------------- 1 | Objective: We need to connect a drill to a chest with an burner inserter at the given direction 2 | Mining setup: We have a drill and a chest entity on the map 3 | Inventory: We have the burner inserter and transport belts in our inventory 4 | :param chest: The chest entity where the output of the drill needs to go 5 | :param drill: The drill entity that produces output for the chest 6 | :param direction: The direction where the burner inserter should be placed 7 | :return burner_inserter: The burner inserter entity that inserts items into the chest -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/planning_examples/rag_functions/automate_drill_to_chest/plan.md: -------------------------------------------------------------------------------- 1 | To solve this objective, we first need to place down the burner inserter next to the chest at the given direction. We have an inserter in our inventory so we don't need to craft it. We need to place it next to the chest input variable. 2 | We also need to rotate the inserter to face the chest as by default it takes from the chest. We then also need to fuel the inserter. 3 | Then we need to connect the chest inserter and the input drill variable with transport belts. We need to use the drop position of the drill and the pickup position of the inserter 4 | Finally we need to return the inserter object -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/user_message.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python script to achieve the objective given. Below are examples of successful policies implementing similar skills. 2 | 3 | EXAMPLES 4 | {examples} 5 | 6 | Here are also all the recipes of entities available in the game to craft 7 | {recipes} 8 | 9 | Important: Follow similar style to examples if possible. They are successful so you can use similar methods and approaches 10 | USER INPUT 11 | {user_input} -------------------------------------------------------------------------------- /data/prompts/prompts_for_rag/user_message_planning.md: -------------------------------------------------------------------------------- 1 | Create a plan that will be used to create a function that achieves the objective taking into account the recipes, the current game mine setup, variables and inventory. Below are examples of successful plans that have implemented some skills. 2 | 3 | EXAMPLE INPUTS AND PLANS 4 | {examples} 5 | 6 | USER INPUT 7 | {user_input} -------------------------------------------------------------------------------- /data/prompts/steps_to_function/examples/example_1_output.md: -------------------------------------------------------------------------------- 1 | # Check if we have enough copper plates in the inventory 2 | inventory = inspect_inventory() 3 | copper_plates = inventory.get(Prototype.CopperPlate, 0) 4 | 5 | # Calculate the number of copper plates needed (2 cables per plate) 6 | plates_needed = quantity // 2 + (1 if quantity % 2 != 0 else 0) 7 | 8 | if copper_plates < plates_needed: 9 | print(f"Not enough copper plates. Have {copper_plates}, need {plates_needed}") 10 | return False 11 | 12 | # Craft the copper cables 13 | cables_crafted = 0 14 | while cables_crafted < quantity: 15 | craft_success = craft_item(Prototype.CopperCable, 2) 16 | if not craft_success: 17 | print(f"Failed to craft copper cables. Crafted {cables_crafted} out of {quantity}") 18 | raise RuntimeError(f"Failed to craft copper cables. Crafted {cables_crafted} out of {quantity}") 19 | cables_crafted += 2 20 | 21 | print(f"Successfully crafted {cables_crafted} copper cables") -------------------------------------------------------------------------------- /data/prompts/steps_to_function/system_message.md: -------------------------------------------------------------------------------- 1 | You are an AI agent creating Python policy functions to achieve Factorio game objectives. 2 | The function should be named '{function_name}'. 3 | You have the following api schema available to you {api_schema} 4 | Only write in python in ``` blocks. 5 | Ensure the function raises an uncaught exception if something goes wrong at runtime. 6 | Do not use try-catch as it will hide the error message. 7 | Include appropriate function parameters with type annotations, instead of constants and magic numbers. 8 | Import: `from factorio_instance import *` -------------------------------------------------------------------------------- /data/prompts/steps_to_function/user_message.md: -------------------------------------------------------------------------------- 1 | Use the API to write a Python policy function to achieve the objective: '{objective}'. Here are the steps to achieve the objective: 2 | {steps} -------------------------------------------------------------------------------- /data/scripts/count.lua: -------------------------------------------------------------------------------- 1 | rcon.print(game.players[arg1].force.item_production_statistics.get_input_count('arg2')) 2 | rcon.print(game.players[arg1].force.item_production_statistics.get_output_count('arg2')) -------------------------------------------------------------------------------- /data/scripts/count_entities.lua: -------------------------------------------------------------------------------- 1 | local player = game.players["arg1"] 2 | local entity="arg2" 3 | 4 | local surface=player.surface 5 | local count=0 6 | local out=0 7 | for key, ent in pairs(surface.find_entities_filtered({force=player.force})) do 8 | if string.find(ent.name,entity) then 9 | count=count+1 10 | end 11 | out=out+1 12 | end 13 | rcon.print(out) 14 | rcon.print(count) -------------------------------------------------------------------------------- /data/scripts/count_items.lua: -------------------------------------------------------------------------------- 1 | local i = 0 2 | for c in game.players[1].surface.get_chunks() do 3 | i= i + game.players[1].surface.count_entities_filtered({area={{c.x * 32, c.y * 32}, {c.x * 32 + 32, c.y * 32 + 32}}, name="arg1"}) 4 | end 5 | rcon.print(i) -------------------------------------------------------------------------------- /data/scripts/create_character.lua: -------------------------------------------------------------------------------- 1 | local player = game.players[arg1] 2 | local surface=player.surface 3 | 4 | surface.create_entity({name = "character", position = {player.position.x + 5, player.position.y}, force = game.forces.player}) 5 | rcon.print("hi", #surface.find_entities_filtered({force=player.force, name="character"})) -------------------------------------------------------------------------------- /data/scripts/day_time.lua: -------------------------------------------------------------------------------- 1 | local player = game.players["arg1"] 2 | player.surface.always_day=True -------------------------------------------------------------------------------- /data/scripts/get_associated_characters.lua: -------------------------------------------------------------------------------- 1 | local player = game.players["arg1"] 2 | rcon.print(player.get_associated_characters()) -------------------------------------------------------------------------------- /data/scripts/get_view.lua: -------------------------------------------------------------------------------- 1 | rcon.print(game.players[arg1].surface.find_entities_filtered{position = {0, 0}, radius = 10}) 2 | rcon.print(game.players[arg1].surface.find_tiles_filtered{position = {0, 0}, radius = 10}) -------------------------------------------------------------------------------- /data/scripts/init.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/data/scripts/init.lua -------------------------------------------------------------------------------- /data/scripts/init/clear_map.lua: -------------------------------------------------------------------------------- 1 | local player = game.players[arg1] 2 | local surface=player.surface 3 | 4 | for _, entity in ipairs(player.surface.find_entities_filtered{ area={{player.position.x-arg2, 5 | player.position.y-arg2}, {player.position.x+arg2, player.position.y+arg2}}, type="resource"}) do 6 | entity.destroy() 7 | end -------------------------------------------------------------------------------- /data/scripts/init/count_all.lua: -------------------------------------------------------------------------------- 1 | game.players[arg1].print_entity_statistics() 2 | game.players[arg1].print_lua_object_statistics() 3 | 4 | rcon.print(game.players[arg1].print_entity_statistics()) 5 | rcon.print(game.players[arg1].print_lua_object_statistics()) -------------------------------------------------------------------------------- /data/scripts/init/create_character.lua: -------------------------------------------------------------------------------- 1 | -- Initialize the player's inventory 2 | local function init_player_inventory(player) 3 | player.clear_items_inside() 4 | player.insert{name="iron-plate", count=8} 5 | player.insert{name="pistol", count=1} 6 | player.insert{name="firearm-magazine", count=10} 7 | player.insert{name="burner-mining-drill", count = 1} 8 | player.insert{name="stone-furnace", count = 1} 9 | end 10 | 11 | local function init_player(player) 12 | local char_entity = player.surface.create_entity({name="player", position={0,0}, force=player.force}) 13 | player.character = char_entity 14 | player.surface.always_day = true 15 | player.game_view_settings.update_entity_selection = false 16 | player.game_view_settings.show_entity_info = true 17 | player.game_view_settings.show_controller_gui = true 18 | init_player_inventory(player) 19 | end 20 | 21 | init_player(game.players[arg1]) -------------------------------------------------------------------------------- /data/scripts/init/create_force.lua: -------------------------------------------------------------------------------- 1 | game.create_force("Name") -------------------------------------------------------------------------------- /data/scripts/init/create_ore.lua: -------------------------------------------------------------------------------- 1 | local player = game.players[arg1] 2 | local surface = player.surface; 3 | for y=-arg2, arg2 do 4 | for x=-arg2, arg2 do 5 | surface.create_entity({name="arg4", amount=arg3, position={player.position.x+x, player.position.y+y}}) 6 | end 7 | end -------------------------------------------------------------------------------- /data/scripts/init/delete_rocks.lua: -------------------------------------------------------------------------------- 1 | local surface = game.player.surface 2 | for c in surface.get_chunks() do 3 | for key, entity in pairs(surface.find_entities_filtered({area={{c.x * 32, c.y * 32}, {c.x * 32 + 32, c.y * 32 + 32}}, name = "ORE/OBJECT"})) do 4 | entity.destroy() 5 | end 6 | end -------------------------------------------------------------------------------- /data/scripts/init/enact_truce.lua: -------------------------------------------------------------------------------- 1 | game.player.surface.peaceful_mode=TRUE -------------------------------------------------------------------------------- /data/scripts/init/give_item.lua: -------------------------------------------------------------------------------- 1 | local player = game.players[arg1] 2 | --rcon.print(player.get_main_inventory.clear()) 3 | --rcon.print(player.get_main_inventory().get_insertable_count()) 4 | rcon.print(player.get_main_inventory().insert{name="arg2", count=100}) 5 | --rcon.print(player.force.item_production_statistics.get_input_count('arg2')) 6 | 7 | --player.get_main_inventory().insert({name="arg2", count=arg3}) 8 | --rcon.print(player.get_main_inventory().get_item_count("arg2")) 9 | --rcon.print(dump(player.get_main_inventory().get_contents())) -------------------------------------------------------------------------------- /data/scripts/init/mine.lua: -------------------------------------------------------------------------------- 1 | game.players[arg1].mining_state = {mining = true, position = defines.direction.arg2} -------------------------------------------------------------------------------- /data/scripts/init/pollute.lua: -------------------------------------------------------------------------------- 1 | game.player.surface.pollute(game.player.position, 1000000) -------------------------------------------------------------------------------- /data/scripts/init/random_map.lua: -------------------------------------------------------------------------------- 1 | local player = game.players[arg1] 2 | local surface=player.surface 3 | local ore=nil 4 | local size=arg2 5 | local density=arg3 6 | for y=-size, size do 7 | for x=-size, size do 8 | a=(size+1-math.abs(x))*10 9 | b=(size+1-math.abs(y))*10 10 | if aFactorio LeaderboardBack to Documentation
-------------------------------------------------------------------------------- /docs/leaderboard/static/js/main.83539a1a.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! decimal.js-light v2.5.1 https://github.com/MikeMcl/decimal.js-light/LICENCE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-is.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react-jsx-runtime.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** 44 | * @license React 45 | * scheduler.production.min.js 46 | * 47 | * Copyright (c) Facebook, Inc. and its affiliates. 48 | * 49 | * This source code is licensed under the MIT license found in the 50 | * LICENSE file in the root directory of this source tree. 51 | */ 52 | -------------------------------------------------------------------------------- /docs/leaderboard/static/js/main.a914cc1f.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! decimal.js-light v2.5.1 https://github.com/MikeMcl/decimal.js-light/LICENCE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-is.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react-jsx-runtime.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** 44 | * @license React 45 | * scheduler.production.min.js 46 | * 47 | * Copyright (c) Facebook, Inc. and its affiliates. 48 | * 49 | * This source code is licensed under the MIT license found in the 50 | * LICENSE file in the root directory of this source tree. 51 | */ 52 | -------------------------------------------------------------------------------- /docs/leaderboard/static/js/main.be301726.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! decimal.js-light v2.5.1 https://github.com/MikeMcl/decimal.js-light/LICENCE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-is.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react-jsx-runtime.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** 44 | * @license React 45 | * scheduler.production.min.js 46 | * 47 | * Copyright (c) Facebook, Inc. and its affiliates. 48 | * 49 | * This source code is licensed under the MIT license found in the 50 | * LICENSE file in the root directory of this source tree. 51 | */ 52 | -------------------------------------------------------------------------------- /docs/leaderboard/static/js/main.c2a531a7.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! decimal.js-light v2.5.1 https://github.com/MikeMcl/decimal.js-light/LICENCE */ 2 | 3 | /** 4 | * @license React 5 | * react-dom.production.min.js 6 | * 7 | * Copyright (c) Facebook, Inc. and its affiliates. 8 | * 9 | * This source code is licensed under the MIT license found in the 10 | * LICENSE file in the root directory of this source tree. 11 | */ 12 | 13 | /** 14 | * @license React 15 | * react-is.production.min.js 16 | * 17 | * Copyright (c) Facebook, Inc. and its affiliates. 18 | * 19 | * This source code is licensed under the MIT license found in the 20 | * LICENSE file in the root directory of this source tree. 21 | */ 22 | 23 | /** 24 | * @license React 25 | * react-jsx-runtime.production.min.js 26 | * 27 | * Copyright (c) Facebook, Inc. and its affiliates. 28 | * 29 | * This source code is licensed under the MIT license found in the 30 | * LICENSE file in the root directory of this source tree. 31 | */ 32 | 33 | /** 34 | * @license React 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | 43 | /** 44 | * @license React 45 | * scheduler.production.min.js 46 | * 47 | * Copyright (c) Facebook, Inc. and its affiliates. 48 | * 49 | * This source code is licensed under the MIT license found in the 50 | * LICENSE file in the root directory of this source tree. 51 | */ 52 | -------------------------------------------------------------------------------- /env/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/__init__.py -------------------------------------------------------------------------------- /env/src/exceptions/insufficient_score_exception.py: -------------------------------------------------------------------------------- 1 | class InsufficientScoreException(Exception): 2 | pass -------------------------------------------------------------------------------- /env/src/gym/__init__.py: -------------------------------------------------------------------------------- 1 | # from gym.envs.registration import register 2 | # 3 | # register( 4 | # id='Factorio-v0', 5 | # entry_point='envs:FactorioEnv', 6 | # max_episode_steps=300, 7 | # ) -------------------------------------------------------------------------------- /env/src/gym/readme.md: -------------------------------------------------------------------------------- 1 | This is where the old implementation of the Gym API lives. 2 | 3 | The idea is: 4 | - Use the 'observe_all' lua script to pull an observation tensor 5 | - Define an action tensor based on the actions + parameterisations. 6 | - Hook into OAI gym to train a model 7 | 8 | This is not finished, and was somewhat superceded by the focus on LLM coding models. I'm leaving it here as an example. -------------------------------------------------------------------------------- /env/src/lib/README.md: -------------------------------------------------------------------------------- 1 | Miscellaneous scripts used in FactorioInstance. -------------------------------------------------------------------------------- /env/src/lib/build_checkerboard.lua: -------------------------------------------------------------------------------- 1 | local player = global.agent_characters[arg1] 2 | local surface = player.surface 3 | 4 | surface.build_checkerboard({{-100, -100}, {100, 100}}) -------------------------------------------------------------------------------- /env/src/lib/checksum.lua: -------------------------------------------------------------------------------- 1 | if not global.__lua_script_checksums then 2 | global.__lua_script_checksums = {} 3 | end 4 | 5 | global.get_lua_script_checksums = function() 6 | return game.table_to_json(global.__lua_script_checksums) 7 | end 8 | 9 | global.set_lua_script_checksum = function(name, checksum) 10 | global.__lua_script_checksums[name] = checksum 11 | end 12 | 13 | global.clear_lua_script_checksums = function() 14 | global.__lua_script_checksums = {} 15 | end -------------------------------------------------------------------------------- /env/src/lib/clear_inventory.lua: -------------------------------------------------------------------------------- 1 | global.agent_characters[arg1].clear_items_inside() 2 | rcon.print(1) -------------------------------------------------------------------------------- /env/src/lib/enemies.lua: -------------------------------------------------------------------------------- 1 | game.forces["enemy"].kill_all_units() -- Removes all biters 2 | game.map_settings.enemy_expansion.enabled = false -- Stops biters from expanding 3 | game.map_settings.enemy_evolution.enabled = false -- Stops biters from evolving 4 | local surface = game.surfaces[1] 5 | for _, entity in pairs(surface.find_entities_filtered({type="unit-spawner"})) do 6 | entity.destroy() 7 | end 8 | -------------------------------------------------------------------------------- /env/src/lib/initialise_inventory.lua: -------------------------------------------------------------------------------- 1 | global.actions.initialise_inventory = function(player_index, item_names_and_counts_json) 2 | local player = global.agent_characters[player_index] 3 | local item_names_and_counts = game.json_to_table(item_names_and_counts_json) 4 | 5 | -- Loop through the entity names and insert them into the player's inventory 6 | for item, count in pairs(item_names_and_counts) do 7 | player.get_main_inventory().insert{name=item, count=count} 8 | end 9 | end -------------------------------------------------------------------------------- /env/src/lib/reset.lua: -------------------------------------------------------------------------------- 1 | game.reset_game_state() -------------------------------------------------------------------------------- /env/src/lib/reset_position.lua: -------------------------------------------------------------------------------- 1 | global.agent_characters[arg1].teleport({arg2, arg3}) 2 | rcon.print(1) -------------------------------------------------------------------------------- /env/src/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/src/models/__init__.py -------------------------------------------------------------------------------- /env/src/models/camera.py: -------------------------------------------------------------------------------- 1 | from env.src.entities import Position, BoundingBox 2 | from pydantic import BaseModel, Field 3 | 4 | class Camera(BaseModel): 5 | centroid: Position 6 | raw_centroid: Position 7 | entity_count: int 8 | bounds: BoundingBox 9 | zoom: float 10 | position: Position -------------------------------------------------------------------------------- /env/src/models/conversation.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from env.src.models.message import Message 6 | 7 | 8 | class Conversation(BaseModel): 9 | """Tracks dialogue between LLM and Factorio""" 10 | messages: List[Message] = Field(default_factory=list) 11 | 12 | @classmethod 13 | def parse_raw(cls, data: Dict[str, Any]) -> 'Conversation': 14 | messages = [Message(**msg) if isinstance(msg, dict) else msg 15 | for msg in data['messages']] 16 | return cls(messages=messages) 17 | 18 | def add_result(self, program: str, response: str, **kwargs): 19 | """Add program execution result to conversation""" 20 | self.messages.append(Message(role="assistant", content=program, metadata=kwargs)) 21 | self.messages.append(Message(role="user", content=response, metadata=kwargs)) 22 | 23 | 24 | -------------------------------------------------------------------------------- /env/src/models/generation_parameters.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, List 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class GenerationParameters(BaseModel): 7 | model: str 8 | n: int = 1 9 | temperature: float = 0.5 10 | max_tokens: int = 2048 11 | logit_bias: Optional[Dict[str, float]] = None 12 | stop_sequences: Optional[List] = None 13 | presence_penalty: Optional[float] = 0 14 | frequency_penalty: Optional[float] = 0 15 | -------------------------------------------------------------------------------- /env/src/models/message.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class Message(BaseModel): 7 | role: str 8 | content: str 9 | metadata: Dict[str, Any] = Field(default_factory=dict) 10 | -------------------------------------------------------------------------------- /env/src/models/research_state.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, List, Optional 3 | 4 | from env.src.models.technology_state import TechnologyState 5 | 6 | 7 | @dataclass 8 | class ResearchState: 9 | """Complete research state including all technologies and current research""" 10 | technologies: Dict[str, TechnologyState] 11 | current_research: Optional[str] 12 | research_progress: float 13 | research_queue: List[str] 14 | progress: Dict 15 | 16 | -------------------------------------------------------------------------------- /env/src/models/technology_state.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, List 3 | 4 | 5 | @dataclass 6 | class TechnologyState: 7 | """Represents the state of a technology""" 8 | name: str 9 | researched: bool 10 | enabled: bool 11 | level: int 12 | research_unit_count: int 13 | research_unit_energy: float 14 | prerequisites: List[str] 15 | ingredients: List[Dict[str, int]] 16 | -------------------------------------------------------------------------------- /env/src/rcon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/src/rcon/__init__.py -------------------------------------------------------------------------------- /env/src/rcon/factorio_rcon/__init__.py: -------------------------------------------------------------------------------- 1 | from .factorio_rcon import (PACKET_PARSER, RCONClient, AsyncRCONClient, 2 | RCONBaseError, ClientBusy, InvalidPassword, 3 | InvalidResponse, RCONNetworkError, RCONNotConnected, 4 | RCONClosed, RCONConnectError, RCONReceiveError, 5 | RCONSendError) 6 | -------------------------------------------------------------------------------- /env/src/rcon/factorio_rcon_py.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | factorio_rcon/__init__.py 4 | factorio_rcon/factorio_rcon.py 5 | factorio_rcon_py.egg-info/PKG-INFO 6 | factorio_rcon_py.egg-info/SOURCES.txt 7 | factorio_rcon_py.egg-info/dependency_links.txt 8 | factorio_rcon_py.egg-info/requires.txt 9 | factorio_rcon_py.egg-info/top_level.txt -------------------------------------------------------------------------------- /env/src/rcon/factorio_rcon_py.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /env/src/rcon/factorio_rcon_py.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | construct=2.8.12 2 | -------------------------------------------------------------------------------- /env/src/rcon/factorio_rcon_py.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | factorio_rcon 2 | -------------------------------------------------------------------------------- /env/src/rcon/setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 4 | 5 | -------------------------------------------------------------------------------- /env/src/rcon/setup.py: -------------------------------------------------------------------------------- 1 | """Setup information""" 2 | import setuptools 3 | 4 | with open("README.md", "r") as fh: 5 | LONG_DESCRIPTION = fh.read() 6 | 7 | setuptools.setup( 8 | name="factorio-rcon-py", 9 | version="1.2.1", 10 | author="mark9064", 11 | description="A simple factorio RCON client", 12 | long_description=LONG_DESCRIPTION, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/mark9064/factorio-rcon-py", 15 | packages=setuptools.find_packages(), 16 | install_requires=["construct"], 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 20 | "Operating System :: OS Independent", 21 | "Development Status :: 4 - Beta", 22 | "Natural Language :: English" 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /env/src/requirements.txt: -------------------------------------------------------------------------------- 1 | factorio-rcon-py==1.2.1 2 | python-dotenv==1.0.1 3 | imageio 4 | anyio 5 | pygame 6 | neptune 7 | pyopenssl==24.0.0 8 | rich 9 | construct 10 | pydantic 11 | openai 12 | anthropic 13 | scipy 14 | scikit-image 15 | pyautogui 16 | lupa 17 | numpy 18 | pillow 19 | psycopg2 20 | backoff 21 | tenacity 22 | pytest-asyncio 23 | fastapi>=0.68.0 24 | uvicorn>=0.15.0 25 | aiohttp>=3.8.0 26 | pydantic>=1.8.0 27 | a2a-sdk 28 | # For slpp, use the git version since it's likely more up to date: 29 | git+https://github.com/SirAnthony/slpp -------------------------------------------------------------------------------- /env/src/tools/admin/clear_collision_boxes/client.py: -------------------------------------------------------------------------------- 1 | from env.src.tools.tool import Tool 2 | 3 | 4 | class ClearCollisionBoxes(Tool): 5 | 6 | def __init__(self, connection, game_state): 7 | super().__init__(connection, game_state) 8 | 9 | def __call__(self) -> bool: 10 | """ 11 | Removes all pipe insulation 12 | """ 13 | response, elapsed = self.execute(self.player_index) 14 | return True -------------------------------------------------------------------------------- /env/src/tools/admin/clear_collision_boxes/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.clear_collision_boxes = function(player_index) 2 | local player = global.agent_characters[player_index] 3 | if not player then return end 4 | 5 | -- Clean up temporary entities 6 | local clearance_entities = global.clearance_entities[player_index] 7 | if not clearance_entities then return end 8 | 9 | -- Define search area around player (500 tile radius) 10 | local search_area = { 11 | left_top = { 12 | x = player.position.x - 500, 13 | y = player.position.y - 500 14 | }, 15 | right_bottom = { 16 | x = player.position.x + 500, 17 | y = player.position.y + 500 18 | } 19 | } 20 | 21 | -- Find all simple-entity-with-owner entities in the area 22 | local entities = player.surface.find_entities_filtered{ 23 | type = "simple-entity-with-owner", 24 | area = search_area 25 | } 26 | 27 | -- Destroy found entities 28 | local count = 0 29 | for _, entity in pairs(entities) do 30 | if entity.valid then 31 | entity.destroy() 32 | count = count + 1 33 | end 34 | end 35 | 36 | -- Destroy all created entities 37 | for _, entity in pairs(clearance_entities) do 38 | if entity.valid then 39 | entity.destroy() 40 | end 41 | end 42 | end -------------------------------------------------------------------------------- /env/src/tools/admin/clear_entities/client.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from env.src.tools.init import Init 4 | 5 | 6 | class ClearEntities(Init): 7 | 8 | def __init__(self, connection, game_state): 9 | super().__init__(connection, game_state) 10 | 11 | def __call__(self, *args, **kwargs): 12 | response, time_elapsed = self.execute(self.player_index) 13 | return response 14 | -------------------------------------------------------------------------------- /env/src/tools/admin/extend_collision_boxes/client.py: -------------------------------------------------------------------------------- 1 | from env.src.entities import Position 2 | from env.src.tools.tool import Tool 3 | 4 | 5 | class ExtendCollisionBoxes(Tool): 6 | 7 | def __init__(self, connection, game_state): 8 | super().__init__(connection, game_state) 9 | 10 | def __call__(self, source_position: Position, target_position: Position) -> bool: 11 | """ 12 | Add an insulative buffer of invisible objects around all pipes within the bounding box. 13 | This is necessary when making other pipe connections, as adjacency can inadvertently cause different 14 | pipe groups to merge 15 | """ 16 | response, elapsed = self.execute(self.player_index, 17 | source_position.x, 18 | source_position.y, 19 | target_position.x, 20 | target_position.y) 21 | return True -------------------------------------------------------------------------------- /env/src/tools/admin/get_messages/server.lua: -------------------------------------------------------------------------------- 1 | -- Left blank intentionally - this functionality is handled by the A2A protocol -------------------------------------------------------------------------------- /env/src/tools/admin/get_path/server.lua: -------------------------------------------------------------------------------- 1 | -- Function to get the path as a JSON object 2 | global.actions.get_path = function(request_id) 3 | local request_data = global.path_requests[request_id] 4 | if not request_data then 5 | return game.table_to_json({status = "\"invalid_request\""}) 6 | end 7 | 8 | if request_data == "pending" then 9 | return game.table_to_json({status = "\"pending\""}) 10 | end 11 | 12 | local path = global.paths[request_id] 13 | if not path then 14 | return game.table_to_json({status = "\"not_found\""}) 15 | end 16 | 17 | if path == "busy" then 18 | return game.table_to_json({status = "\"busy\""}) 19 | elseif path == "not_found" then 20 | return game.table_to_json({status = "\"not_found\""}) 21 | else 22 | local waypoints = {} 23 | for _, waypoint in ipairs(path) do 24 | table.insert(waypoints, { 25 | x = waypoint.position.x, 26 | y = waypoint.position.y 27 | }) 28 | end 29 | -- create a beam bounding box at the start and end of the path 30 | local start = path[1].position 31 | local finish = path[#path].position 32 | --create_beam_bounding_box(player, surface, 1, {x = start.x - 0.5, y = start.y - 0.5}, {x = finish.x + 0.5, y = finish.y + 0.5}) 33 | return game.table_to_json({ 34 | status = "\"success\"", 35 | waypoints = waypoints 36 | }) 37 | end 38 | end -------------------------------------------------------------------------------- /env/src/tools/admin/get_production_stats/client.py: -------------------------------------------------------------------------------- 1 | from env.src.tools.tool import Tool 2 | 3 | 4 | class GetProductionStats(Tool): 5 | 6 | def __init__(self, connection, game_state): 7 | super().__init__(connection, game_state) 8 | self.name = "production_stats" 9 | self.game_state = game_state 10 | 11 | def __call__(self, *args, **kwargs): 12 | response, execution_time = self.execute(self.player_index, *args, **kwargs) 13 | return response 14 | -------------------------------------------------------------------------------- /env/src/tools/admin/inspect_entities/deprecated: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/src/tools/admin/inspect_entities/deprecated -------------------------------------------------------------------------------- /env/src/tools/admin/load_blueprint/client.py: -------------------------------------------------------------------------------- 1 | from env.src.entities import Position 2 | from env.src.tools.tool import Tool 3 | 4 | 5 | class LoadBlueprint(Tool): 6 | 7 | def __init__(self, *args): 8 | super().__init__(*args) 9 | 10 | def __call__(self, blueprint: str, position: Position) -> bool: 11 | """ 12 | Loads a blueprint into the game. 13 | :param blueprint: Name of the blueprint to load 14 | :return: True if successful, False otherwise 15 | """ 16 | 17 | assert isinstance(blueprint, str) 18 | 19 | result, _ = self.execute(self.player_index, blueprint, position.x, position.y) 20 | 21 | if result == 0: 22 | return True 23 | return False -------------------------------------------------------------------------------- /env/src/tools/admin/load_entity_state/client.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import zlib 4 | from typing import Union, List, Dict 5 | 6 | from env.src.tools.tool import Tool 7 | 8 | 9 | class LoadEntityState(Tool): 10 | 11 | def __init__(self, *args): 12 | super().__init__(*args) 13 | 14 | def __call__(self, entities: Union[str, List[Dict]], decompress=False) -> bool: 15 | """ 16 | Loads the entity state back into the game. 17 | :param entities: Either a list of un-serialized dictionaries or a string containing Base64 encoded JSON data representing the entities to load. 18 | :return: True if successful, False otherwise 19 | """ 20 | 21 | if isinstance(entities, str): 22 | entities = base64.b64decode(entities) 23 | if decompress: 24 | entities = zlib.decompress(entities) 25 | else: 26 | entities = json.dumps(entities) 27 | 28 | result, _ = self.execute(self.player_index, entities) 29 | 30 | return result -------------------------------------------------------------------------------- /env/src/tools/admin/regenerate_resources/client.py: -------------------------------------------------------------------------------- 1 | from env.src.tools.tool import Tool 2 | 3 | 4 | class RegenerateResources(Tool): 5 | 6 | def __init__(self, *args): 7 | super().__init__(*args) 8 | 9 | def __call__(self) -> bool: 10 | """ 11 | Fills up all resources on the map back to full 12 | """ 13 | self.execute(self.player_index) 14 | 15 | return True -------------------------------------------------------------------------------- /env/src/tools/admin/regenerate_resources/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.regenerate_resources = function(player_index) 2 | local player = global.agent_characters[player_index] 3 | local surface = player.surface 4 | for _, ore in pairs(surface.find_entities_filtered({type="resource"})) do 5 | -- skip oil 6 | if ore.name ~= "crude-oil" then 7 | ore.amount = 10000 8 | else 9 | ore.amount = 300000 10 | end 11 | end 12 | player.force.reset() 13 | end 14 | 15 | global.actions.regenerate_resources2 = function(player_index) 16 | local player = global.agent_characters[player_index] 17 | 18 | local surface = player.surface 19 | for _, e in pairs(surface.find_entities_filtered{type="resource"}) do 20 | if e.prototype.infinite_resource then 21 | e.amount = e.initial_amount 22 | else 23 | e.destroy() 24 | end 25 | end 26 | local non_infinites = {} 27 | for resource, prototype in pairs(game.get_filtered_entity_prototypes{{filter="type", type="resource"}}) do 28 | if not prototype.infinite_resource then 29 | table.insert(non_infinites, resource) 30 | end 31 | end 32 | surface.regenerate_entity(non_infinites) 33 | for _, e in pairs(surface.find_entities_filtered{type="mining-drill"}) do 34 | e.update_connections() 35 | end 36 | return 1 37 | end -------------------------------------------------------------------------------- /env/src/tools/admin/render/layers/layer_renderer.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Dict, Any, Callable, List, Optional 3 | from PIL import ImageDraw 4 | 5 | from env.src.entities import Position 6 | from env.src.tools.admin.render.utils.render_config import RenderConfig 7 | 8 | 9 | class LayerRenderer(ABC): 10 | """ 11 | Abstract base class for all layer renderers. 12 | Each layer type should implement a concrete subclass of this. 13 | """ 14 | 15 | def __init__(self, config: RenderConfig): 16 | """ 17 | Initialize the layer renderer with configuration. 18 | 19 | Args: 20 | config: The render configuration 21 | """ 22 | self.config = config 23 | 24 | @abstractmethod 25 | def render(self, draw: ImageDraw.ImageDraw, 26 | game_to_img_func: Callable, 27 | boundaries: Dict[str, float], 28 | **kwargs) -> None: 29 | """ 30 | Render this layer to the provided image. 31 | 32 | Args: 33 | draw: The ImageDraw object to draw on 34 | game_to_img_func: Function to convert game coordinates to image coordinates 35 | boundaries: Dictionary with min_x, max_x, min_y, max_y values 36 | **kwargs: Additional arguments needed for rendering this layer 37 | """ 38 | pass 39 | 40 | @property 41 | @abstractmethod 42 | def layer_name(self) -> str: 43 | """Return the name of this layer""" 44 | pass -------------------------------------------------------------------------------- /env/src/tools/admin/render/rendered_image.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import io 3 | from PIL import Image, ImageDraw, ImageFont 4 | 5 | class RenderedImage: 6 | """Wrapper for rendered images with display capabilities""" 7 | 8 | def __init__(self, image: Image.Image): 9 | self.image = image 10 | 11 | def show(self): 12 | """Display the image (works in IDEs)""" 13 | self.image.show() 14 | 15 | def save(self, path: str): 16 | """Save the image to a file""" 17 | self.image.save(path) 18 | 19 | def to_base64(self): 20 | """Convert image to base64 string for embedding in HTML/Markdown""" 21 | buffer = io.BytesIO() 22 | self.image.save(buffer, format="PNG") 23 | return base64.b64encode(buffer.getvalue()).decode() 24 | 25 | def _repr_png_(self): 26 | """Support for Jupyter notebook display""" 27 | buffer = io.BytesIO() 28 | self.image.save(buffer, format="PNG") 29 | return buffer.getvalue() -------------------------------------------------------------------------------- /env/src/tools/admin/render_message/client.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional 2 | from env.src.tools.tool import Tool 3 | 4 | 5 | class RenderMessage(Tool): 6 | def __init__(self, connection, game_state): 7 | super().__init__(connection, game_state) 8 | self.name = "render_message" 9 | self.game_state = game_state 10 | self.load() 11 | 12 | def __call__(self, message: str) -> bool: 13 | """ 14 | Render a message above the agent's head. 15 | :param message: The message text to display 16 | :return: True if successful, raises exception if not 17 | """ 18 | try: 19 | # Execute the server command with positional arguments 20 | response = self.execute(self.player_index, message) 21 | 22 | if isinstance(response, str): 23 | raise Exception(f"Could not render message: {response}") 24 | 25 | return True 26 | 27 | except Exception as e: 28 | raise Exception(f"Error rendering message: {str(e)}") 29 | -------------------------------------------------------------------------------- /env/src/tools/admin/render_message/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.render_message = function(player_index, message) 2 | -- Get color based on player index 3 | local color = {r = 1, g = 1, b = 1} -- Default white 4 | if player_index == 1 then 5 | color = {r = 0.6, g = 1, b = 0.6} -- Light green 6 | message = "Agent 1: " .. message 7 | elseif player_index == 2 then 8 | color = {r = 0.6, g = 0.6, b = 1} -- Light blue 9 | message = "Agent 2: " .. message 10 | elseif player_index == 3 then 11 | color = {r = 1, g = 0.6, b = 0.6} -- Light red 12 | message = "Agent 3: " .. message 13 | end 14 | 15 | -- Print message with color 16 | game.print(message, color) 17 | return true 18 | end 19 | -------------------------------------------------------------------------------- /env/src/tools/admin/request_path/client.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from env.src.entities import Position 4 | from env.src.tools.tool import Tool 5 | 6 | 7 | class RequestPath(Tool): 8 | 9 | def __init__(self, connection, game_state): 10 | super().__init__(connection, game_state) 11 | 12 | def __call__(self, start: Position, finish: Position, max_attempts: int = 10, allow_paths_through_own_entities=False, radius=0.5, entity_size=1, resolution=0) -> int: 13 | """ 14 | Asynchronously request a path from start to finish from the game. 15 | """ 16 | assert isinstance(start, Position), "Start position must be a Position object" 17 | assert isinstance(finish, Position), "Finish position must be a Position object" 18 | 19 | try: 20 | start_x, start_y = self.get_position(start) 21 | goal_x, goal_y = finish.x, finish.y 22 | 23 | response, elapsed = self.execute(self.player_index, start_x, start_y, goal_x, goal_y, radius, allow_paths_through_own_entities, entity_size, resolution) 24 | 25 | if response is None or response == {} or isinstance(response, str): 26 | raise Exception("Could not request path (request_path)", response) 27 | 28 | path_handle = int(response) 29 | 30 | return path_handle 31 | 32 | except Exception as e: 33 | raise Exception(f"Could not get path from {start} to {finish}", e) -------------------------------------------------------------------------------- /env/src/tools/admin/save_blueprint/client.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from env.src.entities import Position 4 | from env.src.tools.tool import Tool 5 | 6 | 7 | class SaveBlueprint(Tool): 8 | 9 | def __init__(self, *args): 10 | super().__init__(*args) 11 | 12 | def __call__(self) -> Tuple[str, Position]: 13 | """ 14 | Saves the current player entities on the map into a blueprint string 15 | :return: Blueprint and offset to blueprint from the origin. 16 | """ 17 | result, _ = self.execute(self.player_index) 18 | 19 | blueprint = result['blueprint'] 20 | offset = Position(x=result['center_x'], y=result['center_y']) 21 | return blueprint, offset 22 | -------------------------------------------------------------------------------- /env/src/tools/agent/connect_entities/path_result.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Union 2 | 3 | 4 | class PathResult: 5 | """Encapsulates the result of a path finding operation""" 6 | 7 | def __init__(self, response: Union[Dict, str]): 8 | self.raw_response = response 9 | self.is_success = isinstance(response, dict) 10 | self.error_message = response if isinstance(response, str) else None 11 | self.entities = response.get('entities', {}) if self.is_success else {} 12 | self.connected = response.get('connected', False) if self.is_success else False 13 | 14 | @property 15 | def required_entities(self) -> int: 16 | return self.raw_response.get('number_of_entities', 0) if self.is_success else 0 -------------------------------------------------------------------------------- /env/src/tools/agent/connect_entities/resolver.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum, auto 3 | from typing import Union, List, Optional, Protocol, Tuple 4 | 5 | from env.src.entities import Position, Entity 6 | 7 | 8 | @dataclass 9 | class ConnectionPoint: 10 | position: Position 11 | entity: Optional[Entity] = None 12 | 13 | class ConnectionType(Enum): 14 | FLUID = auto() 15 | TRANSPORT = auto() 16 | POWER = auto() 17 | WALL = auto() 18 | 19 | class PositionResolver(Protocol): 20 | def resolve(self, source: Union[Position, Entity], target: Union[Position, Entity]) -> Tuple[Position, Position]: 21 | pass 22 | 23 | class Resolver(): 24 | def __init__(self, get_entities): 25 | self.get_entities = get_entities 26 | 27 | def _is_blocked(self, pos: Position) -> bool: 28 | entities = self.get_entities(position=pos, radius=0.5) 29 | return bool(entities) 30 | 31 | def resolve(self, source: Union[Position, Entity], 32 | target: Union[Position, Entity]) -> List[Tuple[Position, Position]]: 33 | 34 | source_pos, target_pos = None, None 35 | if isinstance(source, Position): 36 | source_pos = source 37 | else: 38 | source_pos = source.position 39 | if isinstance(target, Position): 40 | target_pos = target 41 | else: 42 | target_pos = target.position 43 | return [(source_pos, target_pos)] -------------------------------------------------------------------------------- /env/src/tools/agent/get_connection_amount/client.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import Tuple, List, Union 3 | 4 | from env.src.entities import Entity, Position, EntityGroup 5 | from env.src.game_types import Prototype 6 | from env.src.tools.agent.connect_entities.client import ConnectEntities 7 | from env.src.tools.tool import Tool 8 | 9 | 10 | class GetConnectionAmount(Tool): 11 | 12 | def __init__(self, connection, game_state): 13 | self.game_state = game_state 14 | super().__init__(connection, game_state) 15 | self.connect_entities = ConnectEntities(connection, game_state) 16 | 17 | 18 | def __call__(self, 19 | source: Union[Position, Entity, EntityGroup], 20 | target: Union[Position, Entity, EntityGroup], 21 | connection_type: Prototype = Prototype.Pipe 22 | ) -> int: 23 | """ 24 | Calculate the number of connecting entities needed to connect two entities, positions or groups. 25 | :param source: First entity or position 26 | :param target: Second entity or position 27 | :param connection_type: a Pipe, TransportBelt or ElectricPole 28 | :return: A integer representing how many entities are required to connect the source and target entities 29 | """ 30 | 31 | connect_output = self.connect_entities(source, target, connection_type, dry_run=True) 32 | return connect_output["number_of_entities_required"] 33 | -------------------------------------------------------------------------------- /env/src/tools/agent/get_connection_amount/server.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/src/tools/agent/get_connection_amount/server.lua -------------------------------------------------------------------------------- /env/src/tools/agent/get_entities/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.get_entities = function(player_index, radius, entity_names_json, position_x, position_y) 2 | local player = global.agent_characters[player_index] 3 | local position 4 | if position_x and position_y then 5 | position = {x = tonumber(position_x), y = tonumber(position_y)} 6 | else 7 | position = player.position 8 | end 9 | 10 | radius = tonumber(radius) or 5 11 | local entity_names = game.json_to_table(entity_names_json) or {} 12 | local area = { 13 | {position.x - radius, position.y - radius}, 14 | {position.x + radius, position.y + radius} 15 | } 16 | 17 | local filter = {} 18 | if entity_names and #entity_names > 0 then 19 | filter = {name = entity_names} 20 | end 21 | 22 | local entities 23 | if #entity_names > 0 then 24 | entities = player.surface.find_entities_filtered{area = area, force = player.force, filter=filter} 25 | else 26 | entities = player.surface.find_entities_filtered{area = area, force = player.force} 27 | end 28 | 29 | local result = {} 30 | for _, entity in ipairs(entities) do 31 | if entity.name ~= 'character' then 32 | local serialized = global.utils.serialize_entity(entity) 33 | table.insert(result, serialized) 34 | end 35 | end 36 | return dump(result) 37 | end -------------------------------------------------------------------------------- /env/src/tools/agent/get_prototype_recipe/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.get_prototype_recipe = function(player_index, recipe_name) 2 | local player = global.agent_characters[player_index] 3 | local recipe = player.force.recipes[recipe_name] 4 | if not recipe then 5 | return "recipe doesnt exist" 6 | end 7 | local serialized = global.utils.serialize_recipe(recipe) 8 | return serialized 9 | end 10 | 11 | -------------------------------------------------------------------------------- /env/src/tools/agent/launch_rocket/client.py: -------------------------------------------------------------------------------- 1 | from typing import Union, cast 2 | 3 | from env.src.entities import Position, RocketSilo 4 | from env.src.game_types import Prototype 5 | from env.src.tools.agent.get_entity.client import GetEntity 6 | from env.src.tools.tool import Tool 7 | 8 | 9 | class LaunchRocket(Tool): 10 | 11 | def __init__(self, connection, game_state): 12 | super().__init__(connection, game_state) 13 | self.get_entity = GetEntity(connection, game_state) 14 | 15 | 16 | def __call__(self, silo: Union[Position, RocketSilo]) -> RocketSilo: 17 | """ 18 | Launch a rocket. 19 | :param silo: Rocket silo 20 | :return: Your final position 21 | """ 22 | 23 | if isinstance(silo, Position): 24 | position = silo 25 | else: 26 | position = silo.position 27 | 28 | try: 29 | response, _ = self.execute(self.player_index, position.x, position.y) 30 | return cast(Prototype.RocketSilo, self.get_entity(Prototype.RocketSilo, position)) 31 | except Exception as e: 32 | raise Exception(f"Cannot launch rocket. {e}") 33 | -------------------------------------------------------------------------------- /env/src/tools/agent/launch_rocket/server.lua: -------------------------------------------------------------------------------- 1 | -- Function to find a rocket silo at the given position 2 | local function find_rocket_silo(surface, position) 3 | local silo = surface.find_entities_filtered{ 4 | name = "rocket-silo", 5 | position = position, 6 | limit = 1, 7 | radius=1000 8 | } 9 | return silo[1] 10 | end 11 | 12 | -- Function to check if the silo has a rocket ready to launch 13 | local function is_rocket_ready(silo) 14 | if not silo then return false end 15 | if not silo.valid then return false end 16 | 17 | -- Check if rocket is ready for launch 18 | return silo.rocket_silo_status == defines.rocket_silo_status.rocket_ready 19 | end 20 | 21 | -- Function to launch rocket from specified position 22 | global.actions.launch_rocket = function(x, y) 23 | -- Get the current game surface 24 | local surface = game.surfaces[1] 25 | local position = {x=x, y=y} 26 | -- Find rocket silo at the given position 27 | local silo = find_rocket_silo(surface, position) 28 | 29 | if not silo then 30 | game.print("No rocket silo found at specified position") 31 | return false 32 | end 33 | 34 | -- Check if silo has a rocket ready 35 | if not is_rocket_ready(silo) then 36 | game.print("Rocket is not ready for launch") 37 | return false 38 | end 39 | 40 | -- Launch the rocket 41 | silo.launch_rocket() 42 | game.print("Rocket launched successfully!") 43 | return true 44 | end -------------------------------------------------------------------------------- /env/src/tools/agent/move_to/agent.md: -------------------------------------------------------------------------------- 1 | # move_to 2 | 3 | The `move_to` tool allows you to navigate to specific positions in the Factorio world. This guide explains how to use it effectively. 4 | 5 | ## Basic Usage 6 | 7 | ```python 8 | move_to(position: Position) -> Position 9 | ``` 10 | 11 | The function returns your final Position after moving. 12 | 13 | ### Parameters 14 | 15 | - `position`: Target Position to move to 16 | 17 | ### Examples 18 | 19 | ```python 20 | # Simple movement 21 | new_pos = move_to(Position(x=10, y=10)) 22 | 23 | # Move to resource 24 | coal_pos = nearest(Resource.Coal) 25 | move_to(coal_pos) 26 | ``` 27 | ## Movement Patterns 28 | 29 | ### 1. Resource Navigation 30 | ```python 31 | move_to(nearest(IronOre)) 32 | ``` 33 | 34 | ### 2. Move before placing 35 | always need to move to the position where you need to place the entity 36 | ```python 37 | move_to(Position(x = 0, y = 0)) 38 | chest = place_entity(Prototypw.WoodenChest, position = Position(x = 0, y = 0)) 39 | ``` 40 | 41 | ## Troubleshooting 42 | 43 | 1. "Cannot move" 44 | - Verify destination is reachable (i.e not water) 45 | - Ensure coordinates are valid -------------------------------------------------------------------------------- /env/src/tools/agent/print/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.print = function(message) 2 | message = dump(message) 3 | return '"'..message..'"' 4 | end -------------------------------------------------------------------------------- /env/src/tools/agent/score/client.py: -------------------------------------------------------------------------------- 1 | from env.src.tools.tool import Tool 2 | 3 | 4 | class Reward(Tool): 5 | 6 | def __init__(self, connection, game_state): 7 | super().__init__(connection, game_state) 8 | self.name = "score" 9 | self.game_state = game_state 10 | self.load() 11 | 12 | def __call__(self, *args, **kwargs): 13 | response, execution_time = self.execute(*args, **kwargs) 14 | if self.game_state.instance.initial_score: 15 | response['player'] -= self.game_state.instance.initial_score 16 | 17 | if 'goal' in response: 18 | goal = response['goal'] 19 | else: 20 | goal = "" 21 | 22 | if isinstance(response, str): 23 | raise Exception(f"Could not get player score", response) 24 | 25 | return response['player'], goal 26 | 27 | 28 | # if __name__ == "__main__": 29 | # score = Reward("connection", 0) 30 | # score.load() 31 | # pass 32 | -------------------------------------------------------------------------------- /env/src/tools/agent/send_message/server.lua: -------------------------------------------------------------------------------- 1 | -- Left blank intentionally - this functionality is handled by the A2A protocol -------------------------------------------------------------------------------- /env/src/tools/agent/sleep/agent.md: -------------------------------------------------------------------------------- 1 | # sleep 2 | 3 | ## Overview 4 | The `sleep` tool provides a way to pause execution for a specified duration. It's particularly useful when waiting for game actions to complete, such as waiting for items to be crafted or resources to be gathered. 5 | 6 | ## Function Signature 7 | ```python 8 | def sleep(seconds: int) -> bool 9 | ``` 10 | 11 | ### Parameters 12 | - `seconds`: Number of seconds to pause execution (integer) 13 | 14 | ### Returns 15 | - `bool`: True if sleep completed successfully 16 | 17 | ## Usage Example 18 | ```python 19 | # Wait for 10 seconds 20 | game.sleep(10) 21 | 22 | # Wait for furnace to smelt items 23 | furnace = place_entity(Prototype.StoneFurnace, position=Position(0, 0)) 24 | insert_item(Prototype.IronOre, furnace, 10) 25 | sleep(15) # Wait for smelting to complete 26 | ``` 27 | 28 | ## Key Behaviors 29 | - Adapts to game speed settings automatically 30 | - Uses efficient polling to minimize resource usage 31 | 32 | ## Notes 33 | - Sleep duration is relative to game speed 34 | - Should be used sparingly and only when necessary 35 | - Useful for synchronizing automated processes -------------------------------------------------------------------------------- /env/src/tools/agent/sleep/client.py: -------------------------------------------------------------------------------- 1 | 2 | from time import sleep 3 | 4 | from env.src.tools.tool import Tool 5 | 6 | 7 | class Sleep(Tool): 8 | 9 | def __init__(self, connection, game_state): 10 | super().__init__(connection, game_state) 11 | 12 | def __call__(self, seconds: int) -> bool: 13 | """ 14 | Sleep for up to 15 seconds before continuing. Useful for waiting for actions to complete. 15 | :param seconds: Number of seconds to sleep. 16 | :return: True if sleep was successful. 17 | """ 18 | # Get initial tick 19 | ticks_elapsed = 0 20 | start_tick, _ = self.execute(-1) 21 | target_ticks = seconds * 60 # Convert seconds to ticks (60 ticks = 1 second) 22 | 23 | while True: 24 | current_tick, _ = self.execute(ticks_elapsed) 25 | ticks_elapsed = current_tick - start_tick 26 | 27 | if ticks_elapsed >= target_ticks: 28 | return True 29 | 30 | # Sleep for a small interval to prevent excessive polling 31 | # Using 0.05 seconds (50ms) as a reasonable polling interval 32 | sleep(0.05) 33 | -------------------------------------------------------------------------------- /env/src/tools/agent/sleep/server.lua: -------------------------------------------------------------------------------- 1 | global.actions.sleep = function(ticks_elapsed) 2 | if ticks_elapsed > 0 then 3 | global.elapsed_ticks = global.elapsed_ticks + ticks_elapsed 4 | end 5 | return game.tick 6 | end -------------------------------------------------------------------------------- /env/src/tools/init.py: -------------------------------------------------------------------------------- 1 | from env.src.tools.controller import Controller 2 | 3 | class Init(Controller): 4 | 5 | def __init__(self, lua_script_manager: 'FactorioLuaScriptManager', game_state: 'FactorioInstance', *args, **kwargs): 6 | super().__init__(lua_script_manager, game_state) 7 | self.load() 8 | 9 | def load(self): 10 | self.lua_script_manager.load_init_into_game(self.name) -------------------------------------------------------------------------------- /env/src/transaction.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple, Any 2 | 3 | 4 | class FactorioTransaction: 5 | def __init__(self): 6 | self.commands: List[Tuple[str, List[Any], bool]] = [] # (command, parameters, is_raw) 7 | 8 | def add_command(self, command: str, *parameters, raw=False): 9 | self.commands.append((command, list(parameters), raw)) 10 | 11 | def clear(self): 12 | self.commands.clear() 13 | 14 | def get_commands(self): 15 | return self.commands -------------------------------------------------------------------------------- /env/src/utils/bcolors.py: -------------------------------------------------------------------------------- 1 | class bcolors: 2 | HEADER = '\033[95m' 3 | OKBLUE = '\033[94m' 4 | OKCYAN = '\033[96m' 5 | OKGREEN = '\033[92m' 6 | WARNING = '\033[93m' 7 | FAIL = '\033[91m' 8 | ENDC = '\033[0m' 9 | BOLD = '\033[1m' 10 | UNDERLINE = '\033[4m' -------------------------------------------------------------------------------- /env/src/utils/controller_loader/call_info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class CallInfo: 6 | """Contains information about a class's __call__ method.""" 7 | input_types: str 8 | output_type: str 9 | docstring: str 10 | signature: str 11 | -------------------------------------------------------------------------------- /env/src/utils/controller_loader/manual_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class ManualGenerator: 5 | """Generates manual from agent.md files in a directory.""" 6 | 7 | @staticmethod 8 | def generate_manual(folder_path) -> str: 9 | """Generate schema from all Python files in the folder.""" 10 | if "agent/" not in folder_path: 11 | agent_tool_path = os.path.join(folder_path, "agent") 12 | else: 13 | agent_tool_path = folder_path 14 | 15 | # get all the folders in tool_paths 16 | tool_folders = [f for f in os.listdir(agent_tool_path) if os.path.isdir(os.path.join(agent_tool_path, f))] 17 | manual = "" 18 | for folder in tool_folders: 19 | # check if it has a agent.md file 20 | agent_path = os.path.join(agent_tool_path, folder, "agent.md") 21 | if os.path.exists(agent_path): 22 | with open(agent_path, "r") as f: 23 | manual += f.read() 24 | manual += "\n\n" 25 | else: 26 | continue 27 | # read in the agent.md in master_tool_path 28 | with open(os.path.join(folder_path, "agent.md"), "r") as f: 29 | manual += f"## General useful patterns\n" 30 | manual += f.read() 31 | return manual 32 | -------------------------------------------------------------------------------- /env/src/utils/controller_loader/module_loader.py: -------------------------------------------------------------------------------- 1 | import importlib.util 2 | from typing import Optional, Any 3 | 4 | 5 | class ModuleLoader: 6 | """Handles loading Python modules from file paths.""" 7 | 8 | @staticmethod 9 | def from_path(path: str) -> Optional[Any]: 10 | """Load and return a module from the given path.""" 11 | spec = importlib.util.spec_from_file_location("temp_module", path) 12 | if not spec or not spec.loader: 13 | return None 14 | module = importlib.util.module_from_spec(spec) 15 | spec.loader.exec_module(module) 16 | return module -------------------------------------------------------------------------------- /env/src/utils/controller_loader/type_definition_processor.py: -------------------------------------------------------------------------------- 1 | class TypeDefinitionProcessor: 2 | """Processes Python type definition files.""" 3 | 4 | @staticmethod 5 | def load_and_clean_definitions(file_path: str) -> str: 6 | """Load and clean type definitions from a file.""" 7 | with open(file_path, 'r') as file: 8 | content = file.read() 9 | 10 | # Filter out imports and comments 11 | lines = [ 12 | line for line in content.split("\n") 13 | if not (line.startswith(("import", "from")) or line.lstrip().startswith('#')) 14 | ] 15 | 16 | cleaned_content = "\n".join(lines) 17 | cleaned_content = cleaned_content.replace("\n\n\n", "\n").replace("\n\n", "\n").strip() 18 | 19 | # Extract from Prototype class onwards 20 | prototype_index = cleaned_content.find("class RecipeName(enum.Enum)") 21 | return cleaned_content[prototype_index:] if prototype_index >= 0 else cleaned_content -------------------------------------------------------------------------------- /env/src/utils/parse_llama_to_gpt.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def parse_and_format(data): 5 | 6 | # Extract needed data 7 | messages = data.get("input", []) 8 | 9 | # Include output in the messages list if it exists 10 | if "output" in data: 11 | messages.append(data["output"]) 12 | 13 | # Transform the messages into the desired output format 14 | output_messages = { 15 | "messages": [ 16 | {"role": msg["role"], "content": msg["message"].replace("Recent History: \n", "")} 17 | for msg in messages 18 | ] 19 | } 20 | 21 | # Convert back to JSON string format for each message set 22 | return json.dumps(output_messages) 23 | 24 | if __name__ == "__main__": 25 | # Read the JSON string 26 | 27 | # Call the function to parse and format the JSONL string from /Users/jackhopkins/PycharmProjects/PaperclipMaximiser/src/llama_events.jsonl 28 | 29 | # Get lines from the file 30 | file = open("//llama_events.jsonl") 31 | out_file = open("//gpt_events.jsonl", "w") 32 | 33 | #json_obj = json.loads(file.read()) 34 | lines = file.readlines() 35 | 36 | # Parse each line 37 | for line in lines: 38 | # Parse the JSON string into a dictionary 39 | data = json.loads(line) 40 | output_json = parse_and_format(data) 41 | 42 | print(output_json) 43 | 44 | # Append to the jsonl file 45 | out_file.write(output_json + "\n") 46 | 47 | # Close the file 48 | file.close() -------------------------------------------------------------------------------- /env/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/__init__.py -------------------------------------------------------------------------------- /env/tests/actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/actions/__init__.py -------------------------------------------------------------------------------- /env/tests/actions/_test_shift_entity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from scipy.optimize import direct 3 | 4 | from entities import Position, Direction 5 | from game_types import Prototype, Resource 6 | 7 | 8 | @pytest.fixture() 9 | def game(instance): 10 | instance.reset() 11 | yield instance.namespace 12 | instance.reset() 13 | 14 | def test_shift_entity(game): 15 | """ 16 | Place a boiler at (0, 0) 17 | :param game: 18 | :return: 19 | """ 20 | #boilers_in_inventory = game.inspect_inventory()[Prototype.Pipe] 21 | entity = game.place_entity(Prototype.StoneFurnace, position=Position(x=5, y=0)) 22 | 23 | entity = game.shift_entity(entity, Direction.RIGHT, distance=10) 24 | assert entity -------------------------------------------------------------------------------- /env/tests/actions/test_get_prototype_recipe.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.game_types import Prototype 4 | 5 | @pytest.fixture() 6 | def game(instance): 7 | instance.initial_inventory = {'assembling-machine-1': 1} 8 | instance.reset() 9 | yield instance.namespace 10 | instance.reset() 11 | 12 | def test_get_recipe(game): 13 | 14 | recipe = game.get_prototype_recipe(Prototype.IronGearWheel) 15 | 16 | assert recipe.ingredients[0].name == 'iron-plate' 17 | assert recipe.ingredients[0].count == 2 -------------------------------------------------------------------------------- /env/tests/actions/test_get_research_progress.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.instance import FactorioInstance 4 | from env.src.game_types import Technology 5 | from cluster.local.cluster_ips import get_local_container_ips 6 | 7 | @pytest.fixture() 8 | def game(instance): 9 | #game.initial_inventory = {'assembling-machine-1': 1} 10 | ips, udp_ports, tcp_ports = get_local_container_ips() 11 | instance = FactorioInstance(address='localhost', 12 | bounding_box=200, 13 | tcp_port=tcp_ports[-1],#27019, 14 | all_technologies_researched=False, 15 | fast=True, 16 | inventory={}) 17 | instance.reset() 18 | yield instance.namespace 19 | instance.reset() 20 | 21 | def test_get_research_progress_automation(game): 22 | ingredients = game.get_research_progress(Technology.Automation) 23 | assert ingredients[0].count == 10 24 | 25 | def test_get_research_progress_none_fail(game): 26 | try: 27 | ingredients = game.get_research_progress() 28 | except: 29 | assert True 30 | return 31 | 32 | assert False, "Need to set research before calling get_research_progress() without an argument" 33 | 34 | 35 | def test_get_research_progress_none(game): 36 | ingredients1 = game.set_research(Technology.Automation) 37 | ingredients2 = game.get_research_progress() 38 | 39 | assert len(ingredients1) == len(ingredients2) -------------------------------------------------------------------------------- /env/tests/actions/test_nearest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.entities import Position 4 | from env.src.game_types import Resource 5 | 6 | @pytest.fixture() 7 | def game(instance): 8 | instance.reset() 9 | yield instance.namespace 10 | instance.reset() 11 | 12 | def test_nearest_resource(game): 13 | """ 14 | Test distance to the nearest coal resource. 15 | :param game: 16 | :return: 17 | """ 18 | coal: Position = game.nearest(Resource.Coal) 19 | assert coal.y == -0.5 20 | assert coal.x == 15.5 21 | 22 | def test_move_to_nearest(game): 23 | """ 24 | Test that when the player moves to the nearest water resource, the nearest water resource remains the same. 25 | :param game: 26 | :return: 27 | """ 28 | water: Position = game.nearest(Resource.Water) 29 | game.move_to(water) 30 | assert abs(water.x - game.nearest(Resource.Water).x) <= 1 31 | 32 | -------------------------------------------------------------------------------- /env/tests/actions/test_print.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.entities import Position 4 | from env.src.instance import Direction 5 | from env.src.game_types import Prototype, Resource 6 | 7 | 8 | @pytest.fixture() 9 | def game(instance): 10 | instance.reset() 11 | yield instance.namespace 12 | instance.reset() 13 | 14 | def test_print_tuple(game): 15 | """ 16 | Print a tuple 17 | """ 18 | r = game.print("Hello", "World", (1, 2, 3)) 19 | 20 | assert r == "Hello\tWorld\t(1, 2, 3)" -------------------------------------------------------------------------------- /env/tests/actions/test_render.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.entities import Position, Layer 4 | from env.src.game_types import Prototype, Resource 5 | 6 | 7 | @pytest.fixture() 8 | def game(instance): 9 | instance.initial_inventory = {'iron-chest': 1, 10 | 'small-electric-pole': 20, 11 | 'iron-plate': 10, 12 | 'assembling-machine-1': 1, 13 | 'pipe-to-ground': 10, 14 | 'pipe': 30, 'transport-belt': 50, 'underground-belt': 30} 15 | instance.reset() 16 | yield instance.namespace 17 | instance.reset() 18 | 19 | def test_basic_render(game): 20 | chest = game.place_entity(Prototype.IronChest, position=Position(x=0, y=0)) 21 | belts = game.connect_entities(Position(x=0, y=-2), Position(x=15, y=5), {Prototype.Pipe, Prototype.UndergroundPipe}) 22 | poles = game.connect_entities(Position(x=0, y=-10), Position(x=15, y=-10), { Prototype.SmallElectricPole }) 23 | image = game._render(position=Position(x=0, y=5), layers=Layer.ALL) 24 | image.show() 25 | pass -------------------------------------------------------------------------------- /env/tests/actions/test_request_path.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.entities import Position 4 | from env.src.game_types import Prototype 5 | 6 | @pytest.fixture() 7 | def game(instance): 8 | instance.reset() 9 | yield instance.namespace 10 | instance.reset() 11 | 12 | def test_path(game): 13 | """ 14 | Get a path from (0, 0) to (10, 0) 15 | :param game: 16 | :return: 17 | """ 18 | path = game._request_path(Position(x=0, y=0), Position(x=10, y=0)) 19 | 20 | assert path -------------------------------------------------------------------------------- /env/tests/actions/test_score.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.game_types import Prototype 4 | 5 | @pytest.fixture() 6 | def game(instance): 7 | instance.reset() 8 | yield instance.namespace 9 | 10 | def test_get_score(game): 11 | score, _ = game.score() 12 | assert isinstance(score, int) 13 | -------------------------------------------------------------------------------- /env/tests/actions/test_set_entity_recipe.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.entities import Position 4 | from env.src.game_types import Prototype 5 | 6 | @pytest.fixture() 7 | def game(instance): 8 | game.initial_inventory = {'assembling-machine-1': 1} 9 | instance.reset() 10 | yield instance.namespace 11 | instance.reset() 12 | 13 | def test_set_entity_recipe(game): 14 | # Place an assembling machine 15 | assembling_machine = game.place_entity(Prototype.AssemblingMachine1, position=Position(x=0, y=0)) 16 | 17 | # Set a recipe for the assembling machine 18 | assembling_machine = game.set_entity_recipe(assembling_machine, Prototype.IronGearWheel) 19 | 20 | # Assert that the recipe of the assembling machine has been updated 21 | prototype_name, _ = Prototype.IronGearWheel.value 22 | 23 | assert assembling_machine.recipe == prototype_name -------------------------------------------------------------------------------- /env/tests/actions/test_set_research.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.instance import FactorioInstance 4 | from env.src.game_types import Technology 5 | from cluster.local.cluster_ips import get_local_container_ips 6 | 7 | @pytest.fixture() 8 | def game(instance): 9 | #game.initial_inventory = {'assembling-machine-1': 1} 10 | #from gym import FactorioInstance 11 | ips, udp_ports, tcp_ports = get_local_container_ips() 12 | instance = FactorioInstance(address='localhost', 13 | bounding_box=200, 14 | tcp_port=tcp_ports[-1],#27019, 15 | all_technologies_researched=False, 16 | fast=True, 17 | inventory={}) 18 | instance.reset() 19 | yield instance.namespace 20 | instance.reset() 21 | 22 | def test_set_research(game): 23 | ingredients = game.set_research(Technology.Automation) 24 | assert ingredients[0].count == 10 25 | 26 | def test_fail_to_research_locked_technology(game): 27 | try: 28 | game.set_research(Technology.Automation2) 29 | except Exception as e: 30 | assert True 31 | return 32 | assert False, "Was able to research locked technology. Expected exception." -------------------------------------------------------------------------------- /env/tests/actions/test_sleep.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | 5 | from env.src.entities import Position 6 | from env.src.instance import Direction 7 | from env.src.game_types import Prototype, Resource 8 | 9 | 10 | @pytest.fixture() 11 | def game(instance): 12 | instance.reset() 13 | yield instance.namespace 14 | instance.reset() 15 | 16 | def test_sleep(game): 17 | for i in range(10): 18 | game.instance.speed(i) 19 | speed = game.speed 20 | start_time = time.time() 21 | game.sleep(10) 22 | end_time = time.time() 23 | elapsed_seconds = end_time-start_time 24 | assert elapsed_seconds*speed - 10 < 0.05, f"Sleep function did not work as expected for speed {i}" -------------------------------------------------------------------------------- /env/tests/benchmarks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/benchmarks/__init__.py -------------------------------------------------------------------------------- /env/tests/blueprints/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/blueprints/__init__.py -------------------------------------------------------------------------------- /env/tests/complex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/complex/__init__.py -------------------------------------------------------------------------------- /env/tests/complex/test_slow.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from time import sleep 3 | from entities import Position, ResourcePatch 4 | from instance import Direction, FactorioInstance 5 | from game_types import Prototype, Resource 6 | 7 | 8 | @pytest.fixture() 9 | def game(): 10 | instance = FactorioInstance(address='localhost', 11 | bounding_box=200, 12 | tcp_port=27000, 13 | fast=True, 14 | inventory={}) 15 | instance.speed(1) 16 | instance.reset() 17 | yield instance.namespace 18 | 19 | 20 | def test_slow_harvest(game): 21 | """Test placement of entities at the edge of the map and in tight spaces.""" 22 | # Place entity at map edge 23 | coal_position = game.nearest(Resource.Coal) 24 | game.move_to(coal_position) 25 | harvested = game.harvest_resource(coal_position, 20) 26 | inventory = game.inspect_inventory() 27 | 28 | # Run the tests 29 | if __name__ == "__main__": 30 | pytest.main([__file__]) -------------------------------------------------------------------------------- /env/tests/connect/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/connect/__init__.py -------------------------------------------------------------------------------- /env/tests/connect/test_connect_walls.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from env.src.entities import Position 4 | from env.src.game_types import Prototype 5 | 6 | 7 | @pytest.fixture() 8 | def game(instance): 9 | instance.initial_inventory = { 10 | **instance.initial_inventory, 11 | 'stone-wall': 100, 12 | } 13 | instance.reset() 14 | yield instance.namespace 15 | #instance.reset() 16 | 17 | 18 | def test_connect_wall_line(game): 19 | start_position = Position(x=0, y=0) 20 | end_position = Position(x=5, y=0) 21 | 22 | wall = game.connect_entities(start_position, end_position, connection_type=Prototype.StoneWall) 23 | assert len(wall.entities) == 6 24 | 25 | 26 | -------------------------------------------------------------------------------- /env/tests/entities/test_drill.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from entities import Direction, EntityStatus, Position 3 | from game_types import Prototype, Resource 4 | 5 | 6 | @pytest.fixture() 7 | def game(instance): 8 | instance.initial_inventory = { 9 | **instance.initial_inventory, 10 | 'solar-panel': 3, 11 | 'small-electric-pole': 4, 12 | 'burner-mining-drill':1, 13 | 'long-handed-inserter': 2, 14 | 'filter-inserter': 2, 15 | 'stack-inserter': 2, 16 | 'wooden-chest': 2, 17 | 'iron-chest': 4, 18 | 'steel-chest': 4, 19 | 'coal': 50, 20 | 'iron-plate': 100, 21 | 'copper-plate': 100, 22 | 'electronic-circuit': 100 23 | } 24 | instance.speed(10) 25 | instance.reset() 26 | yield instance.namespace 27 | 28 | 29 | 30 | def test_drill_warnings(game): 31 | """Test long-handed inserter's ability to move items between chests""" 32 | game.move_to(game.nearest(Resource.IronOre)) 33 | 34 | drill = game.place_entity(Prototype.BurnerMiningDrill, position=game.nearest(Resource.IronOre), direction=Direction.UP) 35 | game.insert_item(Prototype.Coal, drill, 10) 36 | game.place_entity(Prototype.WoodenChest, position=drill.drop_position, direction=Direction.UP) 37 | game.sleep(5) 38 | 39 | drill = game.get_entities({Prototype.BurnerMiningDrill})[0] 40 | assert not drill.warnings 41 | 42 | 43 | -------------------------------------------------------------------------------- /env/tests/entities/test_power_generators.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from entities import Direction, EntityStatus 4 | from game_types import Prototype 5 | 6 | 7 | @pytest.fixture() 8 | def game(instance): 9 | instance.initial_inventory = { 10 | **instance.initial_inventory, 11 | 'solar-panel': 3, 12 | 'accumulator': 3, 13 | 'steam-engine': 3, 14 | 'small-electric-pole': 4 15 | } 16 | instance.speed(10) 17 | instance.reset() 18 | yield instance.namespace 19 | 20 | 21 | def test_solar_panel_charge_accumulator(game): 22 | solar_panel = game.place_entity(Prototype.SolarPanel) 23 | pole = game.place_entity_next_to(Prototype.SmallElectricPole, solar_panel.position, Direction.UP) 24 | game.place_entity_next_to(Prototype.Accumulator, pole.position, Direction.UP) 25 | game.sleep(1) 26 | accumulator = game.get_entities({Prototype.Accumulator})[0] 27 | assert accumulator.status == EntityStatus.CHARGING 28 | 29 | -------------------------------------------------------------------------------- /env/tests/eval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/eval/__init__.py -------------------------------------------------------------------------------- /env/tests/eval/samplers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/eval/samplers/__init__.py -------------------------------------------------------------------------------- /env/tests/eval/samplers/test_weighted_reward_sampler.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import Mock 3 | 4 | from eval.open.mcts.samplers.dynamic_reward_weighted_sampler import DynamicRewardWeightedSampler 5 | 6 | 7 | class TestWeightedRewardSampler(unittest.TestCase): 8 | def setUp(self): 9 | self.db_client = Mock() 10 | self.sampler = DynamicRewardWeightedSampler( 11 | db_client=self.db_client, 12 | max_conversation_length=5, 13 | maximum_lookback=2, 14 | ) 15 | 16 | async def test_sample_parent_with_lookback(self): 17 | 18 | depths = [] 19 | # Test sampling with single result 20 | for _ in range(100): 21 | program = await self.sampler.sample_parent(version=312) 22 | depths.append(program.depth) 23 | 24 | max_depth = max(depths) 25 | min_depth = min(depths) 26 | 27 | print(max_depth, min_depth) 28 | self.assertEqual(True, False) 29 | self.assertEqual(max_depth, 26) 30 | self.assertEqual(min_depth, 24) 31 | 32 | 33 | if __name__ == '__main__': 34 | unittest.main() -------------------------------------------------------------------------------- /env/tests/functional/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/functional/__init__.py -------------------------------------------------------------------------------- /env/tests/status/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/env/tests/status/__init__.py -------------------------------------------------------------------------------- /env/tests/status/test_pipes_status.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import pytest 4 | 5 | from env.src.entities import Direction, Position, EntityStatus 6 | from game_types import Resource, Prototype 7 | 8 | 9 | @pytest.fixture() 10 | def game(instance): 11 | instance.initial_inventory = { 12 | **instance.initial_inventory, 13 | 'stone-furnace': 10, 14 | 'burner-inserter': 50, 15 | 'offshore-pump': 4, 16 | 'pipe': 100, 17 | 'small-electric-pole': 50, 18 | 'transport-belt': 200, 19 | 'coal': 100, 20 | 'wooden-chest': 1, 21 | 'assembling-machine-1': 10, 22 | 'boiler': 3, 23 | 'steam-engine': 3 24 | } 25 | instance.reset() 26 | yield instance.namespace 27 | instance.reset() 28 | 29 | 30 | 31 | def test_not_connected_pipes_is_not_connected(game): 32 | pipes1 = game.connect_entities(Position(x=0, y=0), Position(x=5, y=0), connection_type=Prototype.Pipe) 33 | assert pipes1.status == EntityStatus.EMPTY 34 | 35 | pipes2 = game.connect_entities(Position(x=7, y=0), Position(x=12, y=0), connection_type=Prototype.Pipe) 36 | assert pipes2.status == EntityStatus.EMPTY 37 | -------------------------------------------------------------------------------- /env/tests/test_functions.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from time import sleep 3 | from entities import Position, ResourcePatch 4 | from instance import Direction 5 | from game_types import Prototype, Resource 6 | 7 | 8 | @pytest.fixture() 9 | def game(instance): 10 | instance.reset() 11 | yield instance.namespace 12 | 13 | def test_syntax_error(game): 14 | functions = \ 15 | """ 16 | def func_1(arg): 17 | print("a") 18 | assert 1 = 2 19 | 20 | func_1(6) 21 | """ 22 | _, _, result = game.instance.eval(functions) 23 | 24 | assert result == 'Error: invalid syntax (, line 4): assert 1 = 2' 25 | 26 | def test_assertion_exception(game): 27 | functions = \ 28 | """ 29 | def func_1(arg1: Dict) -> str: 30 | \"\"\"this is a func\"\"\" 31 | print("a") 32 | assert 1 == 2 33 | 34 | def func_2(): 35 | func_1({}) 36 | 37 | func_2() 38 | """ 39 | _, _, result = game.instance.eval(functions) 40 | 41 | funcs = game.get_functions() 42 | 43 | description = "# Your utility functions" + "\n\n".join([str(f) for f in funcs]) 44 | 45 | assert result == '0: mart' 46 | 47 | def test_function_with_entity_annotation(game): 48 | functions = \ 49 | """ 50 | def func_1(arg1: Entity) -> str: 51 | \"\"\"this is a func\"\"\" 52 | print("a") 53 | assert 1 == 2 54 | 55 | def func_2(): 56 | func_1({}) 57 | 58 | func_2() 59 | """ 60 | _, _, result = game.instance.eval(functions) 61 | 62 | assert result == '0: mart' -------------------------------------------------------------------------------- /env/tests/test_rcon_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from utils.rcon import _lua2python 4 | 5 | 6 | @pytest.fixture() 7 | def game(instance): 8 | instance.reset() 9 | yield instance.namespace 10 | 11 | def test_lua_2_python(): 12 | lua_response = '{ ["a"] = false,["b"] = ["string global"],}' 13 | command = 'pcall(global.actions.move_to,1,11.5,20)' 14 | response, timing = _lua2python(command, lua_response) 15 | 16 | assert response == {'a': False, 'b': 'string global', 2: ']'} -------------------------------------------------------------------------------- /env/tests/test_variables.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from time import sleep 3 | from entities import Position, ResourcePatch 4 | from instance import Direction 5 | from game_types import Prototype, Resource 6 | 7 | 8 | @pytest.fixture() 9 | def game(instance): 10 | instance.initial_inventory = { 11 | 'stone-furnace': 10, 12 | 'burner-mining-drill': 10, 13 | 'electric-mining-drill': 5, 14 | 'transport-belt': 200, 15 | 'underground-belt': 20, 16 | 'splitter': 10, 17 | 'burner-inserter': 50, 18 | 'fast-inserter': 20, 19 | 'pipe': 100, 20 | 'pipe-to-ground': 20, 21 | 'offshore-pump': 5, 22 | 'boiler': 5, 23 | 'steam-engine': 10, 24 | 'small-electric-pole': 50, 25 | 'medium-electric-pole': 20, 26 | 'assembling-machine-1': 10, 27 | 'iron-chest': 20, 28 | 'coal': 500, 29 | 'iron-plate': 200, 30 | 'copper-plate': 200, 31 | } 32 | instance.reset() 33 | yield instance.namespace 34 | 35 | def test_variables(game): 36 | 37 | game.instance.eval_with_error("fizz='mart'") 38 | _, _, result = game.instance.eval_with_error("print(fizz)") 39 | 40 | assert result == "1: ('mart',)" 41 | 42 | def test_print(game): 43 | _, _, result = game.instance.eval_with_error("print('hello')") 44 | 45 | assert result == "1: ('hello',)" -------------------------------------------------------------------------------- /eval/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/__init__.py -------------------------------------------------------------------------------- /eval/open/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/open/__init__.py -------------------------------------------------------------------------------- /eval/open/beam/README.md: -------------------------------------------------------------------------------- 1 | This folder contains the logic to run a fixed-compute search against the environment using various models. 2 | 3 | Unlike MCTS, which is variable compute and open-ended, this is useful to directly compare models against one another. -------------------------------------------------------------------------------- /eval/open/beam/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/open/beam/__init__.py -------------------------------------------------------------------------------- /eval/open/independent_runs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/open/independent_runs/__init__.py -------------------------------------------------------------------------------- /eval/open/independent_runs/multiagent/claude_lab_distrust.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "task": "multiagent/iron_plate_throughput_distrust.json", 4 | "model": "claude-3-5-sonnet-latest", 5 | "num_agents": 2 6 | } 7 | ] 8 | 9 | -------------------------------------------------------------------------------- /eval/open/independent_runs/multiagent/claude_lab_free.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "task": "multiagent/iron_plate_throughput_free.json", 4 | "model": "claude-3-5-sonnet-latest", 5 | "num_agents": 2 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /eval/open/independent_runs/multiagent/claude_lab_impostor.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "task": "multiagent/iron_plate_throughput_impostor.json", 4 | "model": "claude-3-5-sonnet-latest", 5 | "num_agents": 2 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /eval/open/independent_runs/run_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"task": "iron_gear_wheel_throughput_16.json", 3 | "model": "gpt-4o-mini-2024-07-18", 4 | "version": 768}, 5 | {"task": "plastic_bar_throughput_16.json", 6 | "model": "gpt-4o-mini-2024-07-18", 7 | "exit_on_task_success": false}, 8 | {"task": "open_play.json", 9 | "model": "gpt-4o-mini-2024-07-18"} 10 | ] -------------------------------------------------------------------------------- /eval/open/independent_runs/run_config_example_lab_play.json: -------------------------------------------------------------------------------- 1 | [{"task": "iron_ore_throughput_16.json", "model": "gpt-4o"}] 2 | -------------------------------------------------------------------------------- /eval/open/independent_runs/run_config_example_open_play.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"task": "open_play.json", 3 | "model": "gpt-4o"} 4 | ] -------------------------------------------------------------------------------- /eval/open/mcts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/open/mcts/__init__.py -------------------------------------------------------------------------------- /eval/open/mcts/instance_group.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | from eval.evaluator import Evaluator 5 | from eval.open.mcts.mcts import MCTS 6 | 7 | 8 | @dataclass 9 | class InstanceGroup: 10 | """Represents a group of instances for parallel MCTS execution""" 11 | group_id: int 12 | mcts: MCTS 13 | evaluator: Evaluator 14 | active_instances: List['FactorioInstance'] 15 | #holdout_instance: 'FactorioInstance' 16 | 17 | @property 18 | def total_instances(self) -> int: 19 | return len(self.active_instances) + 1 # Including holdout -------------------------------------------------------------------------------- /eval/open/mcts/parallel_mcts_config.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Type 2 | 3 | from models.game_state import GameState 4 | from eval.open.mcts.mcts import MCTS 5 | from eval.open.mcts.samplers.db_sampler import DBSampler 6 | 7 | 8 | class ParallelMCTSConfig: 9 | """Configuration for ParallelMCTS""" 10 | def __init__(self, 11 | n_parallel: int, 12 | system_prompt: str, 13 | initial_state: GameState, 14 | mcts_class: Type[MCTS], 15 | sampler: DBSampler, 16 | mcts_kwargs: Dict[str, Any] = None, 17 | **kwargs): 18 | self.n_parallel = n_parallel 19 | self.system_prompt = system_prompt 20 | self.initial_state = initial_state 21 | self.mcts_class = mcts_class 22 | self.sampler = sampler 23 | self.mcts_kwargs = mcts_kwargs or {} 24 | 25 | for key, value in kwargs.items(): 26 | setattr(self, key, value) 27 | -------------------------------------------------------------------------------- /eval/open/mcts/parallel_supervised_config.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from models.game_state import GameState 4 | 5 | 6 | class SupervisedExecutorConfig: 7 | """Configuration for ParallelMCTS""" 8 | def __init__(self, 9 | n_parallel: int, 10 | model_to_evaluate: str, 11 | supervised_kwargs: Dict[str, Any] = None, 12 | initial_state: GameState = None): 13 | self.n_parallel = n_parallel 14 | self.model_to_evaluate = model_to_evaluate 15 | self.supervised_kwargs = supervised_kwargs or {} 16 | self.initial_state = initial_state 17 | 18 | def _to_dict(self) -> Dict[str, Any]: 19 | return { 20 | "n_parallel": self.n_parallel, 21 | "model_to_evaluate": self.model_to_evaluate, 22 | "supervised_kwargs": self.supervised_kwargs, 23 | "initial_state": self.initial_state.to_raw() if self.initial_state else None 24 | } 25 | -------------------------------------------------------------------------------- /eval/open/mcts/requirements.txt: -------------------------------------------------------------------------------- 1 | questionary -------------------------------------------------------------------------------- /eval/open/mcts/samplers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/open/mcts/samplers/__init__.py -------------------------------------------------------------------------------- /eval/open/plots/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/open/plots/__init__.py -------------------------------------------------------------------------------- /eval/tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JackHopkins/factorio-learning-environment/7023dbdb34e469803add4f5067739fb341cd5b44/eval/tasks/__init__.py -------------------------------------------------------------------------------- /eval/tasks/default_task.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List, Union, Optional 2 | from env.src.instance import FactorioInstance 3 | from eval.tasks.task_abc import TaskABC 4 | from agents import TaskResponse 5 | 6 | 7 | class DefaultTask(TaskABC): 8 | def __init__(self, trajectory_length, goal_description: str, task_key: str, agent_instructions: Optional[List[str]] = None): 9 | super().__init__(trajectory_length, starting_inventory = {}, 10 | goal_description=goal_description, 11 | task_key = task_key, 12 | all_technology_reserached=False, 13 | agent_instructions = agent_instructions) 14 | self.starting_game_state = None 15 | 16 | 17 | def verify(self, score: float, instance: FactorioInstance, step_statistics: Dict) -> TaskResponse: 18 | return TaskResponse(success = False, 19 | meta = {}) 20 | 21 | def _to_dict(self) -> Dict[str, Any]: 22 | return { 23 | "goal_description": self.goal_description, 24 | "trajectory_length": self.trajectory_length, 25 | "starting_inventory": self.starting_inventory, 26 | "initial_state": self.starting_game_state.to_raw() if self.starting_game_state else None, 27 | } 28 | 29 | def setup_instance(self, instance): 30 | """Code to provision the task environment""" 31 | pass -------------------------------------------------------------------------------- /eval/tasks/task_definitions/automation_science_pack_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { "task_type": "throughput", 3 | "config": 4 | { 5 | "goal_description":"Create an automatic automation-science-pack factory that produces 16 automation-science-packs per 60 ingame seconds.", 6 | "throughput_entity":"automation-science-pack", 7 | "quota":16, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "automation_science_pack_throughput_16" 12 | } 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/crude_oil_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | { 6 | "goal_description":"Create an automatic crude oil factory that produces 250 crude oil per 60 ingame seconds.", 7 | "throughput_entity":"crude-oil", 8 | "quota":250, 9 | "trajectory_length": 128, 10 | "holdout_wait_period": 60, 11 | "pre_holdout_wait_period": 60, 12 | "task_key": "crude_oil_throughput_16" 13 | } 14 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/electronic_circuit_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { "task_type": "throughput", 3 | "config": 4 | { 5 | "goal_description":"Create an automatic electronic-circuit factory that produces 16 electronic-circuits per 60 ingame seconds.", 6 | "throughput_entity":"electronic-circuit", 7 | "quota":16, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "electronic_circuit_throughput_16" 12 | } 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/inserter_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | { 6 | "goal_description":"Create an automatic inserter factory that produces 16 inserter per 60 ingame seconds.", 7 | "throughput_entity":"inserter", 8 | "quota":16, 9 | "trajectory_length": 128, 10 | "holdout_wait_period": 60, 11 | "pre_holdout_wait_period": 60, 12 | "task_key": "inserter_throughput_16" 13 | } 14 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/iron_gear_wheel_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | {"goal_description":"Create an automatic iron gear wheel factory that produces 16 iron gear wheel per 60 ingame seconds", 6 | "throughput_entity":"iron-gear-wheel", 7 | "quota":16, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "iron_gear_wheel_throughput_16"} 12 | 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/iron_gear_wheel_throughput_unbounded.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "unbounded_throughput", 4 | "config": 5 | {"goal_description":"Create an automatic iron gear wheel factory.", 6 | "throughput_entity":"iron-gear-wheel", 7 | "trajectory_length": 16, 8 | "holdout_wait_period": 60, 9 | "pre_holdout_wait_period": 60, 10 | "task_key": "iron_gear_wheel_throughput_unbounded_16_steps_show_steps_true", 11 | "show_number_of_steps_left_in_prompt": true} 12 | 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/iron_ore_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": { 5 | "goal_description":"Create an automatic iron ore factory that produces 16 iron ore per 60 ingame seconds.", 6 | "throughput_entity":"iron-ore", 7 | "quota":16, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "iron_ore_throughput_16" 12 | } 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/iron_plate_throughput_16.json: -------------------------------------------------------------------------------- 1 | { 2 | "task_type": "throughput", 3 | "config": { 4 | "goal_description":"Create an automatic iron plate factory that produces 16 iron plate per 60 ingame seconds.", 5 | "throughput_entity":"iron-plate", 6 | "quota":16, 7 | "trajectory_length": 128, 8 | "holdout_wait_period": 60, 9 | "pre_holdout_wait_period": 60, 10 | "task_key": "iron_plate_throughput_16" 11 | } 12 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/logistics_science_pack_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { "task_type": "throughput", 3 | "config": 4 | { 5 | "goal_description":"Create an automatic logistic-science-pack factory that produces 16 logistic-science-packs per 60 ingame seconds.", 6 | "throughput_entity":"logistic-science-pack", 7 | "quota":16, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "logistic_science_pack_throughput_16" 12 | } 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/military_science_pack_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { "task_type": "throughput", 3 | "config": 4 | { 5 | "goal_description":"Create an automatic military-science-pack factory that produces 16 military-science-packs per 60 ingame seconds.", 6 | "throughput_entity":"military-science-pack", 7 | "quota":16, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "military_science_pack_throughput_16" 12 | } 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/open_play.json: -------------------------------------------------------------------------------- 1 | 2 | { "task_type": "default", 3 | "config": { 4 | "goal_description":"- Build the biggest possible factory\n- Maximise automation, efficiency and scale", 5 | "trajectory_length": 5000, 6 | "task_key": "open_play" 7 | } 8 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/petroleum_gas_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config":{ 5 | "goal_description":"Create an automatic petroleum-gas factory that produces 250 petroleum-gas per 60 ingame seconds.", 6 | "throughput_entity":"petroleum-gas", 7 | "quota":250, 8 | "trajectory_length": 128, 9 | "holdout_wait_period": 60, 10 | "pre_holdout_wait_period": 60, 11 | "task_key": "petroleum_gas_throughput_16" 12 | } 13 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/plastic_bar_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | { 6 | "goal_description":"Create an automatic plastic bar factory that produces 16 plastic bar per 60 ingame seconds.", 7 | "throughput_entity":"plastic-bar", 8 | "quota":16, 9 | "trajectory_length": 128, 10 | "holdout_wait_period": 60, 11 | "pre_holdout_wait_period": 60, 12 | "task_key": "plastic_bar_throughput_16" 13 | } 14 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/steel_plate_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | { 6 | "goal_description":"Create an automatic steel plate factory that produces 16 steel plates per 60 ingame seconds.", 7 | "throughput_entity":"steel-plate", 8 | "quota":16, 9 | "trajectory_length": 128, 10 | "holdout_wait_period": 60, 11 | "pre_holdout_wait_period": 60, 12 | "task_key": "steel_plate_throughput_16" 13 | } 14 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/stone_wall_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | { 6 | "goal_description":"Create an automatic stone wall factory that produces 16 stone wall per 60 ingame seconds.", 7 | "throughput_entity":"stone-wall", 8 | "quota":16, 9 | "trajectory_length": 128, 10 | "holdout_wait_period": 60, 11 | "pre_holdout_wait_period": 60, 12 | "task_key": "stone_wall_throughput_16" 13 | } 14 | } -------------------------------------------------------------------------------- /eval/tasks/task_definitions/sulfur_throughput_16.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "task_type": "throughput", 4 | "config": 5 | { 6 | "goal_description":"Create an automatic sulfur factory that produces 16 sulfur per 60 ingame seconds.", 7 | "throughput_entity":"sulfur", 8 | "quota":16, 9 | "trajectory_length": 128, 10 | "holdout_wait_period": 60, 11 | "pre_holdout_wait_period": 60, 12 | "task_key": "sulfur_throughput_16" 13 | } 14 | } -------------------------------------------------------------------------------- /eval/tasks/task_factory.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List 2 | from env.src.entities import Inventory, Entity 3 | from env.src.instance import FactorioInstance 4 | from eval.tasks.throughput_task import ThroughputTask 5 | from eval.tasks.default_task import DefaultTask 6 | from eval.tasks.task_abc import TaskABC 7 | from eval.tasks.unbounded_throughput_task import UnboundedThroughputTask 8 | from pathlib import Path 9 | import os 10 | 11 | TASK_FOLDER = Path(os.path.dirname(__file__), "task_definitions") 12 | import json 13 | 14 | class TaskFactory: 15 | def __init__(self): 16 | pass 17 | 18 | @staticmethod 19 | def create_task(task_path) -> TaskABC: 20 | task_path = Path(TASK_FOLDER, task_path) 21 | with open(task_path, 'r') as f: 22 | input_json = json.load(f) 23 | 24 | task_type_mapping = { 25 | "throughput": ThroughputTask, 26 | "default": DefaultTask, 27 | "unbounded_throughput": UnboundedThroughputTask 28 | } 29 | task_type = input_json["task_type"] 30 | task_config = input_json["config"] 31 | if task_type in task_type_mapping: 32 | task_class = task_type_mapping[task_type] 33 | return task_class(**task_config) 34 | else: 35 | raise ValueError(f"Task key {task_type} not recognized") -------------------------------------------------------------------------------- /leaderboard/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "./static/css/main.06c39b3c.css", 4 | "main.js": "./static/js/main.28a2068e.js", 5 | "index.html": "./index.html", 6 | "main.06c39b3c.css.map": "./static/css/main.06c39b3c.css.map", 7 | "main.28a2068e.js.map": "./static/js/main.28a2068e.js.map" 8 | }, 9 | "entrypoints": [ 10 | "static/css/main.06c39b3c.css", 11 | "static/js/main.28a2068e.js" 12 | ] 13 | } -------------------------------------------------------------------------------- /leaderboard/build/index.html: -------------------------------------------------------------------------------- 1 | Factorio LeaderboardBack to Documentation
-------------------------------------------------------------------------------- /leaderboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "factorio-leaderboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "./", 6 | "dependencies": { 7 | "react": "^18.2.0", 8 | "react-dom": "^18.2.0", 9 | "react-scripts": "5.0.1", 10 | "recharts": "^2.5.0" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": [ 20 | "react-app" 21 | ] 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | }, 35 | "devDependencies": { 36 | "gh-pages": "^5.0.0" 37 | } 38 | } -------------------------------------------------------------------------------- /leaderboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Factorio Leaderboard 7 | 8 | 9 | Back to Documentation 10 |
11 | 12 | -------------------------------------------------------------------------------- /leaderboard/results/claude-3-5-sonnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Claude 3.5-Sonnet", 3 | "productionScore": 293206, 4 | "milestones": 30, 5 | "automationMilestones": 13, 6 | "labTasksSuccessRate": 21.9, 7 | "mostComplexItem": "plastic-bar", 8 | "submittedBy": "JackHopkins", 9 | "submissionDate": "2025-03-06" 10 | } -------------------------------------------------------------------------------- /leaderboard/results/deepseek-chat.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Deepseek-v3", 3 | "productionScore": 48585, 4 | "milestones": 22, 5 | "automationMilestones": 7, 6 | "labTasksSuccessRate": 15.1, 7 | "mostComplexItem": "plastic-bar", 8 | "submittedBy": "JackHopkins", 9 | "submissionDate": "2025-03-07" 10 | } -------------------------------------------------------------------------------- /leaderboard/results/gemini-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gemini-2-Flash", 3 | "productionScore": 115782, 4 | "milestones": 20, 5 | "automationMilestones": 6, 6 | "labTasksSuccessRate": 13.0, 7 | "mostComplexItem": "iron-gear-wheel", 8 | "submittedBy": "JackHopkins", 9 | "submissionDate": "2025-03-07" 10 | } -------------------------------------------------------------------------------- /leaderboard/results/gpt-4o-mini.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GPT4o-Mini", 3 | "productionScore": 26756, 4 | "milestones": 14, 5 | "automationMilestones": 4, 6 | "labTasksSuccessRate": 4.2, 7 | "mostComplexItem": "iron-plate", 8 | "submittedBy": "JackHopkins", 9 | "submissionDate": "2025-03-07" 10 | } -------------------------------------------------------------------------------- /leaderboard/results/gpt-4o.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GPT4o", 3 | "productionScore": 87599, 4 | "milestones": 30, 5 | "automationMilestones": 9, 6 | "labTasksSuccessRate": 16.6, 7 | "mostComplexItem": "plastic-bar", 8 | "submittedBy": "JackHopkins", 9 | "submissionDate": "2025-03-07" 10 | } -------------------------------------------------------------------------------- /leaderboard/results/llama-3.3-70b.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Llama-3.3-70b", 3 | "productionScore": 54998, 4 | "milestones": 16, 5 | "automationMilestones": 4, 6 | "labTasksSuccessRate": 5.2, 7 | "mostComplexItem": "iron-plate", 8 | "submittedBy": "JackHopkins", 9 | "submissionDate": "2025-03-07" 10 | } -------------------------------------------------------------------------------- /leaderboard/src/App.js: -------------------------------------------------------------------------------- 1 | // src/App.js 2 | import React from 'react'; 3 | import Leaderboard from './components/Leaderboard'; 4 | 5 | function App() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | export default App; -------------------------------------------------------------------------------- /leaderboard/src/index.js: -------------------------------------------------------------------------------- 1 | // src/index.js 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom/client'; 4 | import './index.css'; 5 | import App from './App'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | pythonpath = env/src 3 | testpaths = env/src/tests 4 | python_files = test_*.py -------------------------------------------------------------------------------- /pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = /Users/jackhopkins/miniforge3/envs/default/bin 2 | include-system-site-packages = false 3 | version = 3.9.4 4 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | # Add the necessary paths to sys.path 5 | root_dir = os.path.dirname(os.path.abspath(__file__)) 6 | env_src_dir = os.path.join(root_dir, 'env', 'src') 7 | # Add paths to sys.path if they're not already there 8 | if root_dir not in sys.path: 9 | sys.path.insert(0, root_dir) 10 | if env_src_dir not in sys.path: 11 | sys.path.insert(0, env_src_dir) 12 | # Now import and run the actual script 13 | from eval.open.independent_runs.run import main 14 | if __name__ == "__main__": 15 | main() -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # Factorio MCP Server 2 | from server import mcp 3 | import server.tools 4 | import server.resources 5 | import server.prompts 6 | import server.unix_tools 7 | import server.version_control 8 | 9 | # Command-line interface for the MCP server 10 | if __name__ == "__main__": 11 | import sys 12 | import argparse 13 | 14 | parser = argparse.ArgumentParser(description="Factorio Learning Environment MCP Server") 15 | parser.add_argument("--transport", default="stdio", choices=["stdio", "sse"], 16 | help="Transport mechanism to use (stdio or sse)") 17 | parser.add_argument("--host", default="127.0.0.1", help="Host for SSE server (if using sse transport)") 18 | parser.add_argument("--port", type=int, default=3000, help="Port for SSE server (if using sse transport)") 19 | 20 | args = parser.parse_args() 21 | 22 | if args.transport == "stdio": 23 | print("Starting Factorio MCP server with stdio transport") 24 | mcp.run(transport='stdio') 25 | elif args.transport == "sse": 26 | print(f"Starting Factorio MCP server with SSE transport on {args.host}:{args.port}") 27 | mcp.run(transport='sse', host=args.host, port=args.port) 28 | else: 29 | print(f"Unknown transport: {args.transport}") 30 | sys.exit(1) -------------------------------------------------------------------------------- /server/models.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Dict, List, Optional, Union, Any 3 | 4 | @dataclass 5 | class FactorioServer: 6 | """Represents a Factorio server instance""" 7 | address: str 8 | tcp_port: int 9 | instance_id: int 10 | name: str = None 11 | connected: bool = False 12 | last_checked: float = 0 13 | is_active: bool = False 14 | system_response: str = None 15 | 16 | 17 | @dataclass 18 | class Recipe: 19 | """Represents a crafting recipe in Factorio""" 20 | name: str 21 | ingredients: List[Dict[str, Union[str, int]]] 22 | results: List[Dict[str, Union[str, int]]] 23 | energy_required: float 24 | 25 | 26 | @dataclass 27 | class ResourcePatch: 28 | """Represents a resource patch in the Factorio world""" 29 | name: str 30 | position: Dict[str, float] 31 | amount: int 32 | size: Dict[str, float] -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | 2 | [metadata] 3 | name = factorio-learning-environment 4 | version = attr: factorio_learning_environment.__version__ 5 | 6 | [options] 7 | packages = find: 8 | include_package_data = True 9 | zip_safe = False 10 | python_requires = >=3.10 11 | 12 | [options.entry_points] 13 | console_scripts = 14 | fle = factorio_learning_environment.run:main 15 | --------------------------------------------------------------------------------