diff --git a/.gitignore b/.gitignore index 59e4d4c4..26bbea5a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ kits/cpp/**/*/main.js kits/cpp/**/*/main.wasm kits/cpp/**/*.out kits/java/**/*.class +kits/csharp/**/obj/* +kits/csharp/**/bin/* +kits/csharp/**/.vs/* __pycache__ kits/ts/dist diff --git a/kits/csharp/README.md b/kits/csharp/README.md new file mode 100644 index 00000000..3f492c1c --- /dev/null +++ b/kits/csharp/README.md @@ -0,0 +1,53 @@ +# C# Kit + +This is the folder for the C# 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/Bot.cs` and you can use create other files to help you as well. You should leave `main.py` and the `Lux` package alone in `Bot/`. Read the `Bot.Kt` file to get an idea of how a bot is programmed and a feel for the C# API. + +Make sure you have dotnet core 3.1 (higher versions are probably fine too). + +To confirm your setup, in your bot folder run + +``` +compile.bat +``` + +and this should produce a `Bot.dll` file in `Bot/bin/Release/netcoreapp3.1` which comprise your compiled bot. To then test run your bot, run + +``` +lux-ai-2021 run.bat run.bat --out=replay.json +``` + +which should produce no errors. + +To debug your bot locally you can use any remote dotnet debugger (embedded in your preferred IDE). +First, start a game with `debug.bat` script (also increase the bot timeout): + +```lux-ai-2021 run.sh debug.sh --maxtime=9999999``` + +Then, connect the debugger to the running process. 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. + +Note that your submitted bot must include the compiled `.dll` file with it or else it will not work on the competition servers. + +## 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 + +Not ready yet. + diff --git a/kits/csharp/simple/Bot.sln b/kits/csharp/simple/Bot.sln new file mode 100644 index 00000000..a7eb25e8 --- /dev/null +++ b/kits/csharp/simple/Bot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31727.386 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot", "Bot/Bot.csproj", "{4F2D90F4-3C60-45FF-82B0-A15E7CE6882D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4F2D90F4-3C60-45FF-82B0-A15E7CE6882D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F2D90F4-3C60-45FF-82B0-A15E7CE6882D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F2D90F4-3C60-45FF-82B0-A15E7CE6882D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F2D90F4-3C60-45FF-82B0-A15E7CE6882D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE1F8531-39A1-4C36-8C68-A4E7E2D86C1B} + EndGlobalSection +EndGlobal diff --git a/kits/csharp/simple/Bot/Bot.cs b/kits/csharp/simple/Bot/Bot.cs new file mode 100644 index 00000000..7774a3dc --- /dev/null +++ b/kits/csharp/simple/Bot/Bot.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Bot.Lux; + +namespace Bot +{ + public class Bot + { + static void Main(string[] args) + { + var agent = new Agent(); + // initialize + agent.Initialize(); + while (true) + { + // Do not edit! + // wait for updates + agent.Update(); + + var actions = new List(); + var gameState = agent.GameState; + /** AI Code Goes Below! **/ + + var player = gameState.Players[gameState.Id]; + var opponent = gameState.Players[(gameState.Id + 1) % 2]; + var gameMap = gameState.Map; + + var resourceTiles = new List(); + for (var y = 0; y < gameMap.Height; y++) + { + for (var x = 0; x < gameMap.Width; x++) + { + var cell = gameMap.GetCell(x, y); + if (cell.HasResource()) + { + resourceTiles.Add(cell); + } + } + } + + // we iterate over all our units and do something with them + foreach (var unit in player.Units) + { + if (unit.IsWorker() && unit.CanAct()) + { + if (unit.GetCargoSpaceLeft() > 0) + { + // if the unit is a worker and we have space in cargo, lets find the nearest resource tile and try to mine it + Cell closestResourceTile = null; + double closestDist = 9999999; + foreach (Cell cell in resourceTiles) + { + if (cell.Resource.Type == GameConstants.RESOURCE_TYPES.COAL && !player.ResearchedCoal()) continue; + if (cell.Resource.Type == GameConstants.RESOURCE_TYPES.URANIUM && !player.ResearchedUranium()) continue; + double dist = cell.Pos.DistanceTo(unit.Pos); + if (dist < closestDist) + { + closestDist = dist; + closestResourceTile = cell; + } + } + + if (closestResourceTile != null) + { + var dir = unit.Pos.DirectionTo(closestResourceTile.Pos); + // move the unit in the direction towards the closest resource tile's position. + actions.Add(unit.Move(dir)); + } + } + else + { + // if unit is a worker and there is no cargo space left, and we have cities, lets return to them + if (player.Cities.Count > 0) + { + double closestDist = 999999; + CityTile closestCityTile = null; + foreach (var city in player.Cities.Values) + { + foreach (CityTile cityTile in city.CityTiles) + { + var dist = cityTile.Pos.DistanceTo(unit.Pos); + if (dist < closestDist) + { + closestCityTile = cityTile; + closestDist = dist; + } + } + } + + if (closestCityTile != null) + { + var dir = unit.Pos.DirectionTo(closestCityTile.Pos); + actions.Add(unit.Move(dir)); + } + } + } + } + } + + // you can add debug annotations using the static methods of the Annotate class. + // actions.Add(Annotate.circle(0, 0)); + + /** AI Code Goes Above! **/ + + /** Do not edit! **/ + StringBuilder commandBuilder = new StringBuilder(""); + for (int i = 0; i < actions.Count; i++) + { + if (i != 0) + { + commandBuilder.Append(","); + } + commandBuilder.Append(actions[i]); + } + Console.Out.WriteLine(commandBuilder.ToString()); + // end turn + agent.EndTurn(); + + } + } + } +} diff --git a/kits/csharp/simple/Bot/Bot.csproj b/kits/csharp/simple/Bot/Bot.csproj new file mode 100644 index 00000000..c73e0d16 --- /dev/null +++ b/kits/csharp/simple/Bot/Bot.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp3.1 + + + diff --git a/kits/csharp/simple/Bot/Lux/Agent.cs b/kits/csharp/simple/Bot/Lux/Agent.cs new file mode 100644 index 00000000..d72f336c --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Agent.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; + +namespace Bot.Lux +{ + public class Agent + { + public GameState GameState = new GameState(); + public TextReader Reader; + + /** + * Constructor for a new agent User should edit this according to their `Design` + */ + public Agent() + { + Reader = Console.In; + } + + /** + * Initialize Agent for the `Match` User should edit this according to their + * `Design` + */ + public void Initialize() + { + // get agent ID + GameState.Id = int.Parse(Reader.ReadLine() ?? throw new InvalidOperationException()); + var mapInfo = Console.ReadLine(); + if (mapInfo == null) return; + var mapInfoSplit = mapInfo.Split(" "); + var mapWidth = int.Parse(mapInfoSplit[0]); + var mapHeight = int.Parse(mapInfoSplit[1]); + GameState.Map = new GameMap(mapWidth, mapHeight); + } + + /** + * Updates agent's own known state of `Match` User should edit this according to + * their `Design`. + */ + public void Update() + { + // wait for the engine to send any updates + GameState.Map = new GameMap(GameState.Map.Width, GameState.Map.Height); + GameState.Turn += 1; + GameState.Players[0].Cities.Clear(); + GameState.Players[0].Units.Clear(); + GameState.Players[1].Cities.Clear(); + GameState.Players[1].Units.Clear(); + while (true) + { + var updateInfo = Reader.ReadLine(); + if (updateInfo == IOConstants.DONE) + { + break; + } + var updates = updateInfo.Split(" "); + var inputIdentifier = updates[0]; + if (inputIdentifier == IOConstants.RESEARCH_POINTS) + { + var team = int.Parse(updates[1]); + GameState.Players[team].ResearchPoints = int.Parse(updates[2]); + } + else if (inputIdentifier == IOConstants.RESOURCES) + { + var type = updates[1]; + var x = int.Parse(updates[2]); + var y = int.Parse(updates[3]); + var amt = (int)double.Parse(updates[4]); + GameState.Map.SetResource(type, x, y, amt); + } + else if (inputIdentifier == IOConstants.UNITS) + { + var i = 1; + var unitType = int.Parse(updates[i++]); + var team = int.Parse(updates[i++]); + var unitId = updates[i++]; + var x = int.Parse(updates[i++]); + var y = int.Parse(updates[i++]); + var cooldown = double.Parse(updates[i++]); + var wood = int.Parse(updates[i++]); + var coal = int.Parse(updates[i++]); + var uranium = int.Parse(updates[i++]); + var unit = new Unit(team, unitType, unitId, x, y, cooldown, wood, coal, uranium); + GameState.Players[team].Units.Add(unit); + } + else if (inputIdentifier == IOConstants.CITY) + { + var i = 1; + var team = int.Parse(updates[i++]); + var cityId = updates[i++]; + var fuel = double.Parse(updates[i++]); + var lightUpkeep = double.Parse(updates[i++]); + GameState.Players[team].Cities.Add(cityId, new City(team, cityId, fuel, lightUpkeep)); + } + else if (inputIdentifier == IOConstants.CITY_TILES) + { + var i = 1; + var team = int.Parse(updates[i++]); + var cityId = updates[i++]; + var x = int.Parse(updates[i++]); + var y = int.Parse(updates[i++]); + var cooldown = double.Parse(updates[i++]); + var city = GameState.Players[team].Cities[cityId]; + var cityTile = city.AddCityTile(x, y, cooldown); + GameState.Map.GetCell(x, y).CityTile = cityTile; + GameState.Players[team].CityTileCount += 1; + } + else if (inputIdentifier == IOConstants.ROADS) + { + var i = 1; + var x = int.Parse(updates[i++]); + var y = int.Parse(updates[i++]); + var road = double.Parse(updates[i++]); + var cell = GameState.Map.GetCell(x, y); + cell.Road = road; + } + } + } + + /** + * End a turn + */ + public void EndTurn() + { + Console.Out.Write("D_FINISH"); + Console.Out.Flush(); + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Annotate.cs b/kits/csharp/simple/Bot/Lux/Annotate.cs new file mode 100644 index 00000000..88fa26d3 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Annotate.cs @@ -0,0 +1,30 @@ +namespace Bot.Lux +{ + public class Annotate + { + public static string Circle(int x, int y) + { + return $"dc {x} {y}"; + } + public static string X(int x, int y) + { + return $"dx {x} {y}"; + } + public static string Line(int x1, int y1, int x2, int y2) + { + return $"dl {x1} {y1} {x2} {y2}"; + } + public static string Text(int x1, int y1, string message) + { + return $"dt {x1} {y1} '{message}' 16 "; + } + public static string Text(int x1, int y1, string message, int fontSize) + { + return $"dt {x1} {y1} '{message}' {fontSize}"; + } + public static string SideText(string message) + { + return $"dst '{message}'"; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Cargo.cs b/kits/csharp/simple/Bot/Lux/Cargo.cs new file mode 100644 index 00000000..d4bff3e8 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Cargo.cs @@ -0,0 +1,16 @@ +namespace Bot.Lux +{ + public class Cargo + { + public int Wood; + public int Coal; + public int Uranium; + + public Cargo(int wood, int coal, int uranium) + { + Wood = wood; + Coal = coal; + Uranium = uranium; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Cell.cs b/kits/csharp/simple/Bot/Lux/Cell.cs new file mode 100644 index 00000000..7e703a90 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Cell.cs @@ -0,0 +1,19 @@ +namespace Bot.Lux +{ + public class Cell + { + public Position Pos; + public Resource Resource = null; + public double Road = 0; + public CityTile CityTile = null; + + public Cell(int x, int y) + { + Pos = new Position(x, y); + } + public bool HasResource() + { + return Resource != null && Resource.Amount > 0; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/City.cs b/kits/csharp/simple/Bot/Lux/City.cs new file mode 100644 index 00000000..27e69b82 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/City.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace Bot.Lux +{ + public class City + { + public string CityId; + public int Team; + public double Fuel; + public List CityTiles = new List(); + private readonly double _lightUpKeep; + + public City(int teamId, string cityId, double fuel, double lightUpKeep) + { + CityId = cityId; + Team = teamId; + Fuel = fuel; + _lightUpKeep = lightUpKeep; + } + + public CityTile AddCityTile(int x, int y, double cooldown) + { + var ct = new CityTile(Team, CityId, x, y, cooldown); + CityTiles.Add(ct); + return ct; + } + + public double GetLightUpkeep() + { + return _lightUpKeep; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/CityTile.cs b/kits/csharp/simple/Bot/Lux/CityTile.cs new file mode 100644 index 00000000..ac5f92b2 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/CityTile.cs @@ -0,0 +1,37 @@ +namespace Bot.Lux +{ + public class CityTile + { + public string CityId; + public int Team; + public Position Pos; + public double Cooldown; + public CityTile(int teamId, string cityId, int x, int y, double cooldown) + { + CityId = cityId; + Team = teamId; + Pos = new Position(x, y); + Cooldown = cooldown; + } + + public bool CanAct() + { + return Cooldown < 1; + } + + public string Research() + { + return $"r {Pos.X:d} {Pos.Y:d}"; + } + + public string BuildWorker() + { + return $"bw {Pos.X:d} {Pos.Y:d}"; + } + + public string BuildCart() + { + return $"bc {Pos.X:d} {Pos.Y:d}"; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Direction.cs b/kits/csharp/simple/Bot/Lux/Direction.cs new file mode 100644 index 00000000..c2934fee --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Direction.cs @@ -0,0 +1,11 @@ +namespace Bot.Lux +{ + public static class Direction + { + public const string NORTH = "n"; + public const string EAST = "e"; + public const string SOUTH = "s"; + public const string WEST = "w"; + public const string CENTER = "c"; + } +} diff --git a/kits/csharp/simple/Bot/Lux/GameConstants.cs b/kits/csharp/simple/Bot/Lux/GameConstants.cs new file mode 100644 index 00000000..3da94e8c --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/GameConstants.cs @@ -0,0 +1,67 @@ +using System; + +namespace Bot.Lux +{ + public class GameConstants + { + public static class UNIT_TYPES + { + public static int WORKER = 0; + public static int CART = 1; + } + public static class RESOURCE_TYPES + { + public static String WOOD = "wood"; + public static String COAL = "coal"; + public static String URANIUM = "uranium"; + } + public static class PARAMETERS + { + public static int DAY_LENGTH = 30; + public static int NIGHT_LENGTH = 10; + public static int MAX_DAYS = 360; + public static class LIGHT_UPKEEP + { + public static int CITY = 23; + public static int WORKER = 4; + public static int CART = 10; + } + public static double WOOD_GROWTH_RATE = 1.025; + public static int MAX_WOOD_AMOUNT = 500; + public static int CITY_ADJACENCY_BONUS = 5; + public static int CITY_BUILD_COST = 100; + public static class RESOURCE_CAPACITY + { + public static int WORKER = 100; + public static int CART = 2000; + } + public static class WORKER_COLLECTION_RATE + { + public static int WOOD = 20; + public static int COAL = 5; + public static int URANIUM = 2; + } + public static class RESOURCE_TO_FUEL_RATE + { + public static int WOOD = 1; + public static int COAL = 10; + public static int URANIUM = 40; + } + public static class RESEARCH_REQUIREMENTS + { + public static int COAL = 50; + public static int URANIUM = 200; + } + public static int CITY_ACTION_COOLDOWN = 10; + public static class UNIT_ACTION_COOLDOWN + { + public static int CART = 3; + public static int WORKER = 2; + } + public static double MAX_ROAD = 6; + public static double MIN_ROAD = 0; + public static double CART_ROAD_DEVELOPMENT_RATE = 0.75; + public static double PILLAGE_RATE = 0.5; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/GameMap.cs b/kits/csharp/simple/Bot/Lux/GameMap.cs new file mode 100644 index 00000000..30eaf08b --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/GameMap.cs @@ -0,0 +1,44 @@ +using System; + +namespace Bot.Lux +{ + public class GameMap + { + public int Width; + public int Height; + public Cell[][] Map; + + public GameMap(int width, int height) + { + Width = width; + Height = height; + Map = new Cell[height][]; + + for (var y = 0; y < Height; y++) + { + Map[y] ??= new Cell[width]; + for (var x = 0; x < Width; x++) + { + Map[y][x] = new Cell(x, y); + } + } + } + + public Cell GetCellByPos(Position pos) + { + return Map[pos.Y][pos.X]; + } + + public Cell GetCell(int x, int y) + { + return Map[y][x]; + } + + /** Internal use only */ + public void SetResource(String rType, int x, int y, int amount) + { + var cell = GetCell(x, y); + cell.Resource = new Resource(rType, amount); + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/GameState.cs b/kits/csharp/simple/Bot/Lux/GameState.cs new file mode 100644 index 00000000..a68964df --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/GameState.cs @@ -0,0 +1,15 @@ +namespace Bot.Lux +{ + public class GameState + { + public GameMap Map; + public int Turn = 0; + public int Id = 0; + public Player[] Players = { new Player(0), new Player(1) }; + + public GameState() + { + + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/IOConstants.cs b/kits/csharp/simple/Bot/Lux/IOConstants.cs new file mode 100644 index 00000000..5d4f0700 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/IOConstants.cs @@ -0,0 +1,13 @@ +namespace Bot.Lux +{ + public static class IOConstants + { + public const string DONE = "D_DONE"; + public const string RESEARCH_POINTS = "rp"; + public const string RESOURCES = "r"; + public const string UNITS = "u"; + public const string CITY = "c"; + public const string CITY_TILES = "ct"; + public const string ROADS = "ccd"; + } +} diff --git a/kits/csharp/simple/Bot/Lux/Player.cs b/kits/csharp/simple/Bot/Lux/Player.cs new file mode 100644 index 00000000..4c6231fb --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Player.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Bot.Lux +{ + public class Player + { + public int Team; + public int ResearchPoints; + public List Units = new List(); + public Dictionary Cities = new Dictionary(); + public int CityTileCount; + + public Player(int team) + { + Team = team; + ResearchPoints = 0; + CityTileCount = 0; + } + + public bool ResearchedCoal() + { + return ResearchPoints >= GameConstants.PARAMETERS.RESEARCH_REQUIREMENTS.COAL; + } + + public bool ResearchedUranium() + { + return ResearchPoints >= GameConstants.PARAMETERS.RESEARCH_REQUIREMENTS.URANIUM; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Position.cs b/kits/csharp/simple/Bot/Lux/Position.cs new file mode 100644 index 00000000..ef07138f --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Position.cs @@ -0,0 +1,70 @@ +using System; + +namespace Bot.Lux +{ + public class Position + { + public int X; + public int Y; + + public Position(int x, int y) + { + X = x; + Y = y; + } + + public bool IsAdjacent(Position pos) + { + var dx = X - pos.X; + var dy = Y - pos.Y; + + return Math.Abs(dx) + Math.Abs(dy) <= 1; + } + + public bool Equals(Position pos) + { + return X == pos.X && Y == pos.Y; + } + + public Position Translate(string direction, int units) + { + return direction switch + { + Direction.NORTH => new Position(X, Y - units), + Direction.EAST => new Position(X + units, Y), + Direction.SOUTH => new Position(X, Y + units), + Direction.WEST => new Position(X - units, Y), + Direction.CENTER => new Position(X, Y), + _ => throw new Exception("Did not supply valid direction") + }; + } + + public double DistanceTo(Position pos) + { + return Math.Abs(pos.X - X) + Math.Abs(pos.Y - Y); + } + + public string DirectionTo(Position targetPos) + { + var checkDirections = new []{ Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST, }; + var closestDirection = Direction.CENTER; + var closestDist = DistanceTo(targetPos); + + foreach(var dir in checkDirections) + { + var newPos = Translate(dir, 1); + var dist = targetPos.DistanceTo(newPos); + if (!(dist < closestDist)) continue; + closestDist = dist; + closestDirection = dir; + } + + return closestDirection; + } + + public override string ToString() + { + return "(" + X + ", " + Y + ")"; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Resource.cs b/kits/csharp/simple/Bot/Lux/Resource.cs new file mode 100644 index 00000000..a3d6e012 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Resource.cs @@ -0,0 +1,14 @@ +namespace Bot.Lux +{ + public class Resource + { + public string Type; + public int Amount; + + public Resource(string type, int amt) + { + Type = type; + Amount = amt; + } + } +} diff --git a/kits/csharp/simple/Bot/Lux/Unit.cs b/kits/csharp/simple/Bot/Lux/Unit.cs new file mode 100644 index 00000000..cfee0a82 --- /dev/null +++ b/kits/csharp/simple/Bot/Lux/Unit.cs @@ -0,0 +1,75 @@ +namespace Bot.Lux +{ + public class Unit + { + public Position Pos; + public int Team; + public string Id; + public int Type; + public double Cooldown; + public Cargo Cargo; + + public Unit(int teamId, int type, string unitId, int x, int y, double cooldown, int wood, int coal, int uranium) + { + Pos = new Position(x, y); + Team = teamId; + Id = unitId; + Type = type; + Cooldown = cooldown; + Cargo = new Cargo(wood, coal, uranium); + } + + public bool IsWorker() + { + return Type == 0; + } + + public bool IsCart() + { + return Type == 1; + } + + public int GetCargoSpaceLeft() + { + var spaceUsed = Cargo.Wood + Cargo.Coal + Cargo.Uranium; + if (Type == GameConstants.UNIT_TYPES.WORKER) + { + return GameConstants.PARAMETERS.RESOURCE_CAPACITY.WORKER - spaceUsed; + } + + return GameConstants.PARAMETERS.RESOURCE_CAPACITY.CART - spaceUsed; + } + + public bool CanBuild(GameMap gameMap) + { + var cell = gameMap.GetCellByPos(Pos); + return !cell.HasResource() && CanAct() && + Cargo.Wood + Cargo.Coal + Cargo.Uranium >= GameConstants.PARAMETERS.CITY_BUILD_COST; + } + + public bool CanAct() + { + return Cooldown < 1; + } + + public string Move(string dir) + { + return $"m {Id} {dir}"; + } + + public string Transfer(string destId, string resourceType, int amount) + { + return $"t {Id} {destId} {resourceType} {amount}"; + } + + public string BuildCity() + { + return $"bcity {Id}"; + } + + public string Pillage() + { + return $"p {Id}"; + } + } +} diff --git a/kits/csharp/simple/compile.bat b/kits/csharp/simple/compile.bat new file mode 100644 index 00000000..7a14e389 --- /dev/null +++ b/kits/csharp/simple/compile.bat @@ -0,0 +1 @@ +dotnet build ./Bot/ --configuration Release \ No newline at end of file diff --git a/kits/csharp/simple/debug.bat b/kits/csharp/simple/debug.bat new file mode 100644 index 00000000..516349e1 --- /dev/null +++ b/kits/csharp/simple/debug.bat @@ -0,0 +1 @@ +dotnet run --project=./Bot/Bot.csproj \ No newline at end of file diff --git a/kits/csharp/simple/main.py b/kits/csharp/simple/main.py new file mode 100644 index 00000000..89f588d5 --- /dev/null +++ b/kits/csharp/simple/main.py @@ -0,0 +1,68 @@ +from subprocess import Popen, PIPE +from threading import Thread +from queue import Queue, Empty + +import atexit +import os +import sys +agent_processes = [None, None] +t = None +q = None +def cleanup_process(): + global agent_processes + for proc in agent_processes: + if proc is not None: + proc.kill() +def enqueue_output(out, queue): + for line in iter(out.readline, b''): + queue.put(line) + out.close() +def cpp_agent(observation, configuration): + """ + a wrapper around a java agent + """ + global agent_processes, t, q + + agent_process = agent_processes[observation.player] + ### Do not edit ### + if agent_process is None: + if "__raw_path__" in configuration: + cwd = os.path.dirname(configuration["__raw_path__"]) + else: + cwd = os.path.dirname(__file__) + agent_process = Popen(["dotnet", "./bin/Release/*/Bot.dll"], stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=cwd) + agent_processes[observation.player] = agent_process + atexit.register(cleanup_process) + + # following 4 lines from https://stackoverflow.com/questions/375427/a-non-blocking-read-on-a-subprocess-pipe-in-python + q = Queue() + t = Thread(target=enqueue_output, args=(agent_process.stderr, q)) + t.daemon = True # thread dies with the program + t.start() + if observation.step == 0: + # fixes bug where updates array is shared, but the first update is agent dependent actually + observation["updates"][0] = f"{observation.player}" + + # print observations to agent + agent_process.stdin.write(("\n".join(observation["updates"]) + "\n").encode()) + agent_process.stdin.flush() + + # wait for data written to stdout + agent1res = (agent_process.stdout.readline()).decode() + _end_res = (agent_process.stdout.readline()).decode() + + while True: + try: line = q.get_nowait() + except Empty: + # no standard error received, break + break + else: + # standard error output received, print it out + print(line.decode(), file=sys.stderr, end='') + + outputs = agent1res.split("\n")[0].split(",") + actions = [] + for cmd in outputs: + if cmd != "": + actions.append(cmd) + return actions diff --git a/kits/csharp/simple/run.bat b/kits/csharp/simple/run.bat new file mode 100644 index 00000000..09ec80e5 --- /dev/null +++ b/kits/csharp/simple/run.bat @@ -0,0 +1 @@ +dotnet ./bin/Release/*/Bot.dll