diff --git a/kits/julia/README.md b/kits/julia/README.md new file mode 100644 index 00000000..d1ad50b0 --- /dev/null +++ b/kits/julia/README.md @@ -0,0 +1,49 @@ +# Julia Kit + +This is the folder for the Julia kit. Please make sure to read the instructions as they are important regarding how you will write a bot and submit it to the competition servers. + +Make sure to check our [Discord](https://discord.gg/aWJt3UAcgn) or the [Kaggle forums](https://www.kaggle.com/c/lux-ai-2021/discussion) for announcements if there are any breaking changes. + +## Getting Started + +To get started, download the `simple` folder from this repository. + +Then navigate to that folder via command line e.g. `cd simple` or for windows `chdir simple`. + +Your main code will go into `bot/src/main/julia/XXX.jl` and you can use create other files to help you as well. You should leave `main.py` and the `lux` package alone in `bot/src/main/julia/`. Read the `Bot.jl` file to get an idea of how a bot is programmed and a feel for the Julia API. + +Make sure you have Julia v1.6 (something about competition server). + +To confirm your setup, in your bot folder run + +To then test run your bot, run + +``` +lux-ai-2021 run.sh run.sh --out=replay.json +``` + +which should produce no errors. + +To debug your bot locally start a game with `debug.sh` script (also increase the bot timeout): + +```lux-ai-2021 run.sh debug.sh --maxtime=9999999``` + +Then, connect the debugger to `localhost:5005`. Now you can use breakpoints and other debug features! + +If you find some bugs or unfixable errors, please let an admin know via Discord, the forums, or email us. + +## Developing + +Now that you have some code and you checked that your code works by trying to submit something, you are now ready to start programming your bot and having fun! + +If you haven't read it already, take a look at the [design specifications for the competition](https://lux-ai.org/specs-2021). This will go through the rules and objectives of the competition. + +All of our kits follow a common API through which you can use to access various functions and properties that will help you develop your strategy and bot. The markdown version is here: https://github.com/Lux-AI-Challenge/Lux-Design-2021/blob/master/kits/README.md + +## Submitting to Kaggle + +You can just create submission archive using `./pack.sh`. Upload `submission.tar.gz` to Kaggle "Submission" section. + +## FAQ + +As questions come up, this will be populated with frequently asked questions regarding the Julia kit. diff --git a/kits/julia/simple/lux/Project.toml b/kits/julia/simple/lux/Project.toml new file mode 100644 index 00000000..5870dc8c --- /dev/null +++ b/kits/julia/simple/lux/Project.toml @@ -0,0 +1,9 @@ +name = "LuxAI" +uuid = "2dcf1b2f-c367-41fa-9531-ffe19c618c81" +authors = ["José Bayoán Santiago Calderón "] +version = "0.1.0" + +[deps] +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" diff --git a/kits/julia/simple/lux/src/LuxAI.jl b/kits/julia/simple/lux/src/LuxAI.jl new file mode 100644 index 00000000..1302b975 --- /dev/null +++ b/kits/julia/simple/lux/src/LuxAI.jl @@ -0,0 +1,14 @@ +module LuxAI + +using JSON3: JSON3, StructType, Struct + +for file in ["annotate", "constants", "game_objects", "game"] + include(joinpath("lux", "$file.jl")) +end + +# function julia_main()::Cint +# # do something based on ARGS? +# return 0 # if things finished successfully +# end + +end diff --git a/kits/julia/simple/lux/src/annotate.jl b/kits/julia/simple/lux/src/annotate.jl new file mode 100644 index 00000000..d999b46e --- /dev/null +++ b/kits/julia/simple/lux/src/annotate.jl @@ -0,0 +1,37 @@ +""" + circle(x::Integer, y::Integer) :: String + +Returns the draw circle annotation action. +Will draw a unit sized circle on the visualizer at the current turn centered at the `Cell` at the given x, y coordinates. +""" +circle(x::Integer, y::Integer) = "dc $x $y" +""" + x(x::Integer, y::Integer) :: String + +Returns the draw X annotation action. +Will draw a unit sized X on the visualizer at the current turn centered at the `Cell` at the given x, y coordinates. +""" +x(x::Integer, y::Integer) = "dx $x $y" +""" + line(x1::Integer, y1::Integer, x2::Integer, y2::Integer) :: String + +Returns the draw line annotation action. +Will draw a line from the center of the `Cell` at (x1, y1) to the center of the `Cell` at (x2, y2). +""" +line(x1::Integer, y1::Integer, x2::Integer, y2::Integer) = "dl $x1 $y1 $x2 $y2" +""" + text(x::Integer, y::Integer, message::AbstractString, + fontsize::Integer = 16) :: String + +Returns the draw text annotation action. +Will write text on top of the tile at (x, y) with the particular message and fontsize. +""" +text(x::Integer, y::Integer, message::AbstractString, fontsize::Integer = 16) = + "dt $x $y '$message' $fontsize" +""" + sidetext(message::AbstractString) :: String + +Returns the draw side text annotation action. +Will write text that is displayed on that turn on the side of the visualizer. +""" +sidetext(message::AbstractString) = "dst '$message'" diff --git a/kits/julia/simple/lux/src/constants.jl b/kits/julia/simple/lux/src/constants.jl new file mode 100644 index 00000000..165ccd63 --- /dev/null +++ b/kits/julia/simple/lux/src/constants.jl @@ -0,0 +1,15 @@ +""" + GameConstants(jsonfile::AbstractString = joinpath(pkgdir(LuxAI), "src", "lux", "game_constants.json")) + +This will contain constants on all game parameters like the max turns, the light upkeep of CityTiles etc. +If there are any crucial changes to the starter kits, typically only this object will change. +""" +struct GameConstants + UNIT_TYPES :: NamedTuple{(:WORKER, :CART), NTuple{2, Int8}} + RESOURCE_TYPES :: NamedTuple{(:WOOD, :COAL, :URANIUM), NTuple{3, String}} + INPUTS :: NamedTuple{(:RESEARCH_POINTS, :RESOURCES, :UNITS, :CITY, :CITY_TILES, :ROADS, :DONE), NTuple{7, String}} + DIRECTIONS :: NamedTuple{(:NORTH, :WEST, :EAST, :SOUTH, :CENTER), NTuple{5, String}} + PARAMETERS :: NamedTuple{(:DAY_LENGTH, :NIGHT_LENGTH, :MAX_DAYS, :LIGHT_UPKEEP, :WOOD_GROWTH_RATE, :MAX_WOOD_AMOUNT, :CITY_BUILD_COST, :CITY_ADJACENCY_BONUS, :RESOURCE_CAPACITY, :WORKER_COLLECTION_RATE, :RESOURCE_TO_FUEL_RATE, :RESEARCH_REQUIREMENTS, :CITY_ACTION_COOLDOWN, :UNIT_ACTION_COOLDOWN, :MAX_ROAD, :MIN_ROAD, :CART_ROAD_DEVELOPMENT_RATE, :PILLAGE_RATE), Tuple{Int, Int, Int, NamedTuple{(:CITY, :WORKER, :CART), NTuple{3, Int}}, Float64, Float64, Int, Int, NamedTuple{(:WORKER, :CART), NTuple{2, Int}}, NamedTuple{(:WOOD, :COAL, :URANIUM), NTuple{3, Int}}, NamedTuple{(:WOOD, :COAL, :URANIUM), NTuple{3, Int}}, NamedTuple{(:COAL, :URANIUM), NTuple{2, Int}}, Int, NamedTuple{(:WORKER, :CART), NTuple{2, Int}}, Int, Int, Float64, Float64}} +end + +StructType(::Type{GameConstants}) = Struct() diff --git a/kits/julia/simple/lux/src/game.jl b/kits/julia/simple/lux/src/game.jl new file mode 100644 index 00000000..4cb5e5f8 --- /dev/null +++ b/kits/julia/simple/lux/src/game.jl @@ -0,0 +1,118 @@ +""" + Game(messages::AbstractDict, + configuration::GameConstants = JSON3.read(read(joinpath(pkgdir(LuxAI), "src", "lux", "game_constants.json"), String), + GameConstants)) + +Struct for the state of the game. +""" +struct Game + id :: Int + turn :: Ref{Int} + map :: GameMap + players :: NTuple{2, Player} + configuration :: GameConstants + function Game(observations::AbstractDict, + configuration::GameConstants = JSON3.read(read(joinpath(pkgdir(LuxAI), "src", "lux", "game_constants.json"), String), GameConstants)) + updates = observations["updates"] + id = parse(Int, updates[1]) + turn = Ref(observations["step"]) + # get some other necessary initial input + game_map = GameMap(parse(Int, match.(r"\d+", updates[2]).match)) + players = (Player(0), Player(1)) + new(id, turn, game_map, players, configuration) + end +end +""" + _end_turn(::Game) :: Nothing + +Prints the signal for end of turn. +""" +_end_turn(::Game) = print("D_FINISH") +""" + _reset_player_states!(obj::Game) :: Nothing + +Resets the state of the players after finishing their turn. +""" +function _reset_player_states!(obj::Game) + for player in obj.players + empty!(player.units) + empty!(player.cities) + end + nothing +end +""" + _update!(obj::Game, messages) + +Update state of the game. +""" +function _update!(obj::Game, observation::AbstractVector{<:AbstractString}) + for col in axes(obj.map.map, 2) + for row in axes(obj.map.map, 1) + obj.map.map[row, col] = Cell(Position(row - 1, col - 1)) + end + end + obj.turn.x += 1 + _reset_player_states!(obj) + for update in observation + if update == "D_DONE" + break + end + observation = observation["updates"] + update = observation[end - 8] + strs = split(update, " ") + input_identifier = strs[1] + if input_identifier == obj.configuration.INPUTS.RESEARCH_POINTS + team = parse(Int, strs[2]) + obj.players[team + 1].research_points = parse(Int, strs[3]) + elseif input_identifier == obj.configuration.INPUTS.RESOURCES + type = strs[2] + x = parse(Int, strs[3]) + y = parse(Int, strs[4]) + amt = parse(Float64, strs[5]) + pos = Position(x, y) + resource = Resource(type, amt) + obj.map.map[x + 1, y + 1] = Cell(pos, resource) + elseif input_identifier == obj.configuration.INPUTS.UNITS + unittype = parse(Int, strs[2]) + unittype = findfirst(isequal(unittype), values(obj.configuration.UNIT_TYPES)) + unittype = string(keys(obj.configuration.UNIT_TYPES)[unittype]) + team = parse(Int, strs[3]) + unitid = strs[4] + x = parse(Int, strs[5]) + y = parse(Int, strs[6]) + pos = Position(x, y) + cooldown = parse(Float64, strs[7]) + wood = parse(Int, strs[8]) + coal = parse(Int, strs[9]) + uranium = parse(Int, strs[10]) + cargo = Cargo(;wood, coal, uranium) + push!(obj.players[team + 1].units, Unit(unitid, team, pos, unittype, cooldown, cargo)) + elseif input_identifier == obj.configuration.INPUTS.CITY + team = parse(Int, strs[2]) + cityid = strs[3] + fuel = parse(Float64, strs[4]) + lightupkeep = parse(Float64, strs[5]) + obj.players[team + 1].cities[cityid] = City(cityid, team, fuel, lightupkeep) + elseif input_identifier == obj.configuration.INPUTS.CITY_TILES + team = parse(Int, strs[2]) + cityid = strs[3] + x = parse(Int, strs[4]) + y = parse(Int, strs[5]) + pos = Position(x, y) + cooldown = parse(Float64, strs[6]) + city = obj.players[team + 1].cities[cityid] + citytile = CityTile(cityid, team, pos, cooldown) + push!(city.citytiles, citytile) + cell = get_cell_by_pos(obj.map, pos) + cell.citytile = citytile + obj.players[team + 1].city_tile_count += 1 + elseif input_identifier == obj.configuration.INPUTS.ROADS + x = parse(Int, strs[2]) + y = parse(Int, strs[3]) + pos = Position(x, y) + road = parse(Float64, strs[4]) + cell = get_cell_by_pos(obj.map, pos) + cell.road = road + end + end +end diff --git a/kits/julia/simple/lux/src/game_constants.json b/kits/julia/simple/lux/src/game_constants.json new file mode 100644 index 00000000..cd6a026f --- /dev/null +++ b/kits/julia/simple/lux/src/game_constants.json @@ -0,0 +1,68 @@ +{ + "UNIT_TYPES": { + "WORKER": 0, + "CART": 1 + }, + "RESOURCE_TYPES": { + "WOOD": "wood", + "COAL": "coal", + "URANIUM": "uranium" + }, + "INPUTS": { + "RESEARCH_POINTS": "rp", + "RESOURCES": "r", + "UNITS": "u", + "CITY": "c", + "CITY_TILES": "ct", + "ROADS": "ccd", + "DONE": "D_DONE" + }, + "DIRECTIONS": { + "NORTH": "n", + "WEST": "w", + "EAST": "e", + "SOUTH": "s", + "CENTER": "c" + }, + "PARAMETERS": { + "DAY_LENGTH": 30, + "NIGHT_LENGTH": 10, + "MAX_DAYS": 360, + "LIGHT_UPKEEP": { + "CITY": 23, + "WORKER": 4, + "CART": 10 + }, + "WOOD_GROWTH_RATE": 1.025, + "MAX_WOOD_AMOUNT": 500, + "CITY_BUILD_COST": 100, + "CITY_ADJACENCY_BONUS": 5, + "RESOURCE_CAPACITY": { + "WORKER": 100, + "CART": 2000 + }, + "WORKER_COLLECTION_RATE": { + "WOOD": 20, + "COAL": 5, + "URANIUM": 2 + }, + "RESOURCE_TO_FUEL_RATE": { + "WOOD": 1, + "COAL": 10, + "URANIUM": 40 + }, + "RESEARCH_REQUIREMENTS": { + "COAL": 50, + "URANIUM": 200 + }, + "CITY_ACTION_COOLDOWN": 10, + "UNIT_ACTION_COOLDOWN": { + "CART": 3, + "WORKER": 2 + }, + "MAX_ROAD": 6, + "MIN_ROAD": 0, + "CART_ROAD_DEVELOPMENT_RATE": 0.75, + "PILLAGE_RATE": 0.5 + } +} diff --git a/kits/julia/simple/lux/src/game_objects.jl b/kits/julia/simple/lux/src/game_objects.jl new file mode 100644 index 00000000..984e8969 --- /dev/null +++ b/kits/julia/simple/lux/src/game_objects.jl @@ -0,0 +1,310 @@ +@enum Directions north south west east center +""" + Position(x::Integer, y::Integer) :: Position + +Position on the map. Starts with (0, 0) for the top left corner. +""" +struct Position + x :: Int + y :: Int +end +""" + is_adjacent(obj::Position, pos::Position) :: Bool + +Returns true if this Position is adjacent to pos. False otherwise. +""" +function is_adjacent(obj::Position, pos::Position) + Δx = obj.x - pos.x + Δy = obj.y - pos.y + Δx^2 + Δy^2 ≤ 2 +end +""" + equals(obj::Position, pos::Position) :: Bool + +Returns true if this Position is equal to the other pos object by checking x, y coordinates. False otherwise. +""" +equals(obj::Position, pos::Position) :: Bool = obj.x == pos.x && obj.y == pos.y +""" + translate(obj::Position, direction::Directions, units::Integer) :: Position + +Returns the Position equal to going in a direction units number of times from this Position. +""" +function translate(obj::Position, direction::Directions, units::Integer) + x = obj.x + y = obj.y + if direction == north + x -= units + elseif direction == south + x += units + elseif direction == west + y -= units + elseif direction == east + y += units + elseif direction == center + end + Position(x, y) +end +""" + distance_to(obj::Position, pos::Position) :: Float64 + +Returns the Manhattan (rectilinear) distance from this Position to pos, +""" +distance_to(obj::Position, pos::Position) = abs(obj.x - pos.x) + abs(obj.y - pos.y) +""" + direction_to(obj::Position, target_pos::Position) :: Directions + +Returns the direction that would move you closest to target_pos from this Position if you took a single step. In particular, will return DIRECTIONS.CENTER if this Position is equal to the target_pos. Note that this does not check for potential collisions with other units but serves as a basic pathfinding method. +""" +function direction_to(obj::Position, target_pos::Position) + Δx = obj.x - target_pos.x + Δy = obj.y - target_pos.y + if Δx == Δy == 0 + center + elseif abs(Δx) ≥ abs(Δy) + obj.x > target_pos.x ? west : east + else + obj.y < target_pos.y ? south : north + end +end +""" + CityTile(cityid::AbstractString, team::Integer, pos::Position, cooldown::Real) :: CityTile + +""" +struct CityTile + cityid :: String + team :: Int + pos :: Position + cooldown :: Float64 +end +""" + research(obj::CityTile) :: String + +Returns the research action. +""" +function research(obj::CityTile) + x, y = obj.pos + "r $x $y" +end +""" + build_worker(obj::CityTile) :: String + +Returns the build worker action. When applied and requirements are met, a worker will be built at the City. +""" +function build_worker(obj::CityTile) + x, y = obj.pos + "bw $x $y" +end +""" + build_cart(obj::CityTile) :: String + +Returns the build cart action. When applied and requirements are met, a cart will be built at the City. +""" +function build_cart(obj::CityTile) + x, y = obj.pos + "bc $x $y" +end +""" + Resource(type::AbstractString, amount::Integer) :: Resource + +""" +mutable struct Resource + type :: String + amount :: Int +end +""" + Cell(pos::Position, + resource::Union{Nothing, Resource} = nothing, + citytile::Union{Nothing, CityTile} = nothing, + road::Real = 0) + +""" +mutable struct Cell + pos :: Position + resource :: Union{Nothing, Resource} + citytile :: Union{Nothing, CityTile} + road :: Float64 + Cell(pos::Position, + resource::Union{Nothing, Resource} = nothing, + citytile::Union{Nothing, CityTile} = nothing, + road::Real = 0) = + new(pos, resource, citytile, road) +end +""" + has_resource(obj::Cell) :: Bool + +Returns true if this Cell has a non-depleted Resource, false otherwise. +""" +has_resource(obj::Cell) :: Bool = isa(obj.resource, Resource) && obj.resource.amount > 0 +""" + GameMap(dim::Integer) + +The map is organized such that the top left corner of the map is at (0, 0) and the bottom right is at (width - 1, height - 1). The map is always square. +""" +struct GameMap + map :: Matrix{Cell} + function GameMap(dim::Integer) :: GameMap + new([ Cell(Position(i, j)) for i in 0:dim - 1, j in 0:dim - 1]) + end +end +""" + get_cell_by_pos(obj::GameMap, pos::Position) :: Cell + +Returns the Cell at the given pos. +""" +get_cell_by_pos(obj::GameMap, pos::Position) :: Cell = get_cell(obj, pos.x, pos.y) +""" + get_cell(obj::GameMap, x::Integer, y::Integer) :: Cell + +Returns the Cell at the given pos. +""" +get_cell(obj::GameMap, x::Integer, y::Integer) :: Cell = obj.map[x + 1, y + 1] +""" + Cargo(;wood::Integer = 0, + coal::Integer = 0, + uranium::Integer = 0) :: Cargo +""" +struct Cargo + wood :: Int + coal :: Int + uranium :: Int + Cargo(;wood::Integer = 0, + coal::Integer = 0, + uranium::Integer = 0) = + new(wood, coal, uranium) +end +""" + City(cityid::AbstractString, teamid::Integer, + fuel::Real, lightupkeep::Real, citytiles::AbstractVector{CityTile} = CityTile[]) :: City + +""" +struct City + cityid :: String + team :: Int + fuel :: Float64 + lightupkeep :: Float64 + citytiles :: Vector{CityTile} + function City(cityid::AbstractString, teamid::Integer, + fuel::Real, lightupkeep::Real, + citytiles::AbstractVector{CityTile} = CityTile[]) + new(cityid, teamid, fuel, lightupkeep, citytiles) + end +end +""" + get_light_upkeep(obj::City) :: Float64 + +Returns the light upkeep per turn of the City. Fuel in the City is subtracted by the light upkeep each turn of night. +""" +get_light_upkeep(obj::City) :: Float64 = obj.lightupkeep +""" + Unit(id::AbstractString, team::Int, pos::Position, unit_type::AbstractString, + cooldown::Real, cargo::Cargo) :: Unit + +""" +struct Unit + id :: String + pos :: Position + team :: Int + cooldown :: Float64 + cargo :: Cargo + # Internal + unit_type :: String + function Unit(id::AbstractString, team::Int, pos::Position, unit_type::AbstractString, + cooldown::Real, cargo::Cargo) + new(id, pos, team, cooldown, cargo, unit_type) + end +end +""" + can_act(obj::Union{CityTile, Unit}) :: Bool + +Whether this City or Unit can perform an action this turn, which is when the Cooldown is less than 1. +""" +can_act(obj::Union{CityTile, Unit}) :: Bool = obj.cooldown < 1 +""" + get_cargo_space_left(obj::Unit, gameconstants::GameConstants = GAME_CONSTANTS) :: Int + +Returns the amount of space left in the cargo of this Unit. +Note that any Resource takes up the same space, e.g. 70 wood takes up as much space as 70 uranium, but 70 uranium would produce much more fuel than wood when deposited at a City. +""" +function get_cargo_space_left(obj::Unit, + gameconstants::GameConstants = GAME_CONSTANTS) :: Bool + space_used = obj.cargo.wood + obj.cargo.coal + obj.cargo.uranium + rc = gameconstants.PARAMETERS.RESOURCE_CAPACITY + space_capacity = rc[obj.unit_type] + space_capacity - space_used +end +""" + can_build(obj::Unit, gameconstants::GameConstants = GAME_CONSTANTS) :: Bool + +Returns true if the Unit can build a City on the tile it is on now. False otherwise. +Checks that the tile does not have a Resource over it still and the Unit has a Cooldown of less than 1. +""" +function can_build(obj::Unit, game_map::GameMap, + gameconstants::GameConstants = GAME_CONSTANTS) :: Bool + cell = get_cell_by_pos(game_map, obj.pos) + !has_resource(cell) && + can_act(obj) && + (obj.cargo.wood + obj.cargo.coal + obj.cargo.uranium) ≥ + gameconstants.PARAMETERS.CITY_BUILD_COST +end +""" + move(obj::Unit, dir::Directions, gameconstants::GameConstants = game.configuration) :: String + +Returns the move action. When applied, Unit will move in the specified direction by one Unit, provided there are no other units in the way or opposition cities. (Units can stack on top of each other however when over a friendly City). +""" +move(obj::Unit, dir::Directions, gameconstants::GameConstants = game.configuration) :: String = + "m $(obj.id) $(gameconstants[string(dir)])" +""" + transfer(obj::Unit, + dest_id::AbstractString, resourceType::AbstractString, amount::Integer) :: String + +Returns the transfer action. Will transfer from this Unit the selected Resource type by the desired amount to the Unit with id dest_id given that both units are adjacent at the start of the turn. (This means that a destination Unit can receive a transfer of resources by another Unit but also move away from that Unit) +""" +transfer(obj::Unit, dest_id::AbstractString, resourceType::AbstractString, amount::Integer) :: String = + "t $(obj.id) $dest_id $resourceType $amount" +""" + build_city(obj::Unit) :: String + +Returns the build City action. When applied, Unit will try to build a City right under itself provided it is an empty tile with no City or resources and the worker is carrying 100 units of resources. All resources are consumed if the city is succesfully built. +""" +build_city(obj::Unit) :: String = "bcity $(obj.id)" +""" + pillage(obj::Unit) :: String + +Returns the pillage action. When applied, Unit will pillage the tile it is currently on top of and remove 0.5 of the road level. +""" +pillage(obj::Unit) :: String = "p $(obj.id)" +""" + Player(team::Integer, + research_points::Integer = 0, + units::Vector{Unit} = Unit[], + cities::Dict{String, City} = Dict{String, City}()) + +This contains information on a particular player of a particular team. +""" +mutable struct Player + team :: Int + research_points :: Int + units :: Vector{Unit} + cities :: Dict{String, City} + city_tile_count :: Int + Player(team::Integer, + research_points::Integer = 0, + units::Vector{Unit} = Unit[], + cities::Dict{String, City} = Dict{String, City}(), + city_tile_count::Integer = 0) = + new(team, research_points, units, cities, city_tile_count) +end +""" + research_coal(obj::Player, gameconstants::GameConstants = GAME_CONSTANTS) :: Bool + +Whether or not this player's team has researched coal and can mine coal. +""" +research_coal(obj::Player, gameconstants::GameConstants = GAME_CONSTANTS) :: Bool = + obj.research_points ≥ gameconstants.PARAMETERS.RESEARCH_REQUIREMENTS.coal +""" + researched_uranium(obj::Player, gameconstants::GameConstants = GAME_CONSTANTS) :: Bool + +Whether or not this player's team has researched coal and can mine uranium. +""" +researched_uranium(obj::Player, gameconstants::GameConstants = GAME_CONSTANTS) :: Bool = + obj.research_points ≥ gameconstants.PARAMETERS.RESEARCH_REQUIREMENTS.uranium diff --git a/kits/julia/simple/main.jl b/kits/julia/simple/main.jl new file mode 100644 index 00000000..6507bce2 --- /dev/null +++ b/kits/julia/simple/main.jl @@ -0,0 +1,79 @@ +include(joinpath("kits", "julia", "simple", "lux", "annotate.jl")) +include(joinpath("kits", "julia", "simple", "lux", "constants.jl")) +include(joinpath("kits", "julia", "simple", "lux", "game_objects.jl")) +include(joinpath("kits", "julia", "simple", "lux", "game.jl")) + +""" + Agent(observation, configuration) :: Vector{String} +""" +struct Agent(observation, configuration) + ### Do not edit ### + if observation["step"] == 0 + game_state = Game(observation["updates"][1:2]) + _update!(game_state, messages["updates"][3:end]) + game_state.id = observation.player + else + _update!(game_state, observation["updates"]) + end + actions = String[] + + player = game_state.players[observation.player] + opponent = game_state.players[(observation.player + 1) % 2] + width, height = size(game_state.map) + + resource_tiles = Cell[] + for y in width + for x in height + cell = get_cell(game_state.map, Position(x, y)) + if has_resource(cell) + push!(resource_tiles, cell) + end + end + end + + # we iterate over all our units and do something with them + for unit in player.units + if unit.unit_type == worker && can_act(unit) + closest_dist = Inf + closest_resource_tile = nothing + if get_cargo_space_left(unit) > 0 + # if the unit is a worker and we have space in cargo, lets find the nearest resource tile and try to mine it + for resource_tile in resource_tiles + resource_tile.resource.r_type == coal && !researched_coal(player) && continue + resource_tile.resource.r_type == uranium && !researched_uranium(player) && continue + dist = distance_to(resource_tile.pos, unit.pos) + if dist < closest_dist + closest_dist = dist + closest_resource_tile = resource_tile + end + end + if !ismissing(closest_resource_tile) + append!(actions, move(unit, direction_to(unit.pos, closest_resource_tile.pos))) + end + else + # if unit is a worker and there is no cargo space left, and we have cities, lets return to them + if length(player.cities) > 0 + closest_dist = Inf + closest_city_tile = nothing + for city in values(player.cities) + for city_tile in city.citytiles + dist = distance_to(city_tile.pos, unit.pos) + if dist < closest_dist + closest_dist = dist + closest_city_tile = city_tile + end + end + end + if !isnothing(closest_city_tile) + move_dir = direction_to(unit.pos, closest_city_tile.pos) + append!(actions, move(unit, move_dir)) + end + end + end + end + end + + # you can add debug annotations using the functions in the annotate object + # append!(actions, circle(0, 0)) + actions +end diff --git a/kits/julia/simple/main.py b/kits/julia/simple/main.py new file mode 100644 index 00000000..31469856 --- /dev/null +++ b/kits/julia/simple/main.py @@ -0,0 +1,53 @@ +import sys +from agent import agent + +import julia +julia.install() + +from julia.api import Julia +jl = Julia(compiled_modules=False) + +from julia import Base +from julia import Main + +from julia import Pkg +Pkg.add(url = "./lux") # Repo with agent implementation + +Main.eval("using LuxAI") +from julia import LuxAI + +if __name__ == "__main__": + + def read_input(): + """ + Reads input from stdin + """ + try: + return input() + except EOFError as eof: + raise SystemExit(eof) + step = 0 + class Observation(Dict[str, any]): + def __init__(self, player=0) -> None: + self.player = player + # self.updates = [] + # self.step = 0 + observation = Observation() + observation["updates"] = [] + observation["step"] = 0 + player_id = 0 + while True: + inputs = read_input() + observation["updates"].append(inputs) + + if step == 0: + player_id = int(observation["updates"][0]) + observation.player = player_id + agent = LuxAI.agent(observation) + if inputs == "D_DONE": + actions = agent.update(observation) + observation["updates"] = [] + step += 1 + observation["step"] = step + print(",".join(actions)) + print("D_FINISH") diff --git a/package_all_kits.sh b/package_all_kits.sh index 6f63488f..e8693212 100644 --- a/package_all_kits.sh +++ b/package_all_kits.sh @@ -8,4 +8,5 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" (cd $SCRIPT_DIR/kits/python/simple && tar -czvf simple.tar.gz *) (cd $SCRIPT_DIR/kits/java/simple && tar -czvf simple.tar.gz *) (cd $SCRIPT_DIR/kits/js/simple && tar -czvf simple.tar.gz *) -(cd $SCRIPT_DIR/kits/ts/simple && tar -czvf simple.tar.gz *) \ No newline at end of file +(cd $SCRIPT_DIR/kits/julia/simple && tar -czvf simple.tar.gz *) +(cd $SCRIPT_DIR/kits/ts/simple && tar -czvf simple.tar.gz *) diff --git a/tests/kit.spec.ts b/tests/kit.spec.ts index 513794a6..e6e0bdda 100644 --- a/tests/kit.spec.ts +++ b/tests/kit.spec.ts @@ -32,6 +32,10 @@ describe('Test kits', () => { file: './kits/java/simple/Bot.java', name: 'java', }, + julia: { + file: './kits/julia/simple/main.jl', + name: 'julia', + }, py: { file: './kits/python/simple/main.py', name: 'py', @@ -124,6 +128,21 @@ describe('Test kits', () => { verifyCommands(cmds1, cmds2); }).timeout(10000); + it('should run julia', async () => { + let botList = [bots.js, bots.julia]; + const match = await luxdim.createMatch(botList, options); + const res = await match.run(); + + botList = [bots.julia, bots.julia]; + const match2 = await luxdim.createMatch(botList, options); + const res2 = await match.run(); + const state: LuxMatchState = match.state; + const state2: LuxMatchState = match.state; + const cmds1 = state.game.replay.data.allCommands; + const cmds2 = state2.game.replay.data.allCommands; + verifyCommands(cmds1, cmds2); + }).timeout(10000); + after(async () => { await luxdim.cleanup(); }); diff --git a/tests/match.ts b/tests/match.ts index 43f80dbd..97d10265 100644 --- a/tests/match.ts +++ b/tests/match.ts @@ -21,6 +21,7 @@ const pySimple = './kits/python/simple/main.py'; const cppSimple = './kits/cpp/simple/main.cpp'; const cppOrganic = './tests/bots/cpporganic/main.cpp'; const javaSimple = './kits/java/simple/Bot.java'; +const juliaSimple = './kits/julia/simple/main.jl'; const cppSimpleTranspiled = './kits/cpp/simple/main.js'; const cppOrganicTranspiled = './kits/cpp/organic/main.js'; const botList = [