diff --git a/Codingame.csproj b/Codingame.csproj new file mode 100644 index 0000000..1d22a36 --- /dev/null +++ b/Codingame.csproj @@ -0,0 +1,9 @@ + + + + Exe + net6.0 + enable + + + diff --git a/Codingame.sln b/Codingame.sln new file mode 100644 index 0000000..c985af2 --- /dev/null +++ b/Codingame.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33122.133 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Codingame", "Codingame.csproj", "{4C43862B-37C5-4543-BD26-08875DC59CAC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4C43862B-37C5-4543-BD26-08875DC59CAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C43862B-37C5-4543-BD26-08875DC59CAC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C43862B-37C5-4543-BD26-08875DC59CAC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C43862B-37C5-4543-BD26-08875DC59CAC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4EE8EC05-1729-4E48-AC79-5847801FFCA1} + EndGlobalSection +EndGlobal diff --git a/FallChallenge2022.cs b/FallChallenge2022.cs new file mode 100644 index 0000000..fc28bc2 --- /dev/null +++ b/FallChallenge2022.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Codingame { + public class FallChallenge2022 { + public static string ReadInput() => Console.ReadLine() ?? throw new InvalidProgramException("No input available!"); + + public static void Main(string[] args) { + string[] inputs; + inputs = ReadInput().Split(' '); + var width = int.Parse(inputs[0]); + var height = int.Parse(inputs[1]); + var state = new GameState(width, height, new FieldState[width, height]); + + // game loop + while (true) { + inputs = ReadInput().Split(' '); + state.MyMatter = int.Parse(inputs[0]); + state.OppMatter = int.Parse(inputs[1]); + for (var i = 0; i < height; i++) for (var j = 0; j < width; j++) { + ref var field = ref state.Fields[j, i]; + inputs = ReadInput().Split(' '); + field.ScrapAmount = int.Parse(inputs[0]); + field.Owner = (Owner)int.Parse(inputs[1]); + if (!Enum.IsDefined(field.Owner)) throw new InvalidProgramException("Invalid owner!"); + field.Units = int.Parse(inputs[2]); + field.Recycler = int.Parse(inputs[3]) == 1; + field.CanBuild = int.Parse(inputs[4]) == 1; + field.CanSpawn = int.Parse(inputs[5]) == 1; + field.InRangeOfRecycler = int.Parse(inputs[6]) == 1; + } + + // Write an action using Console.WriteLine() + // To debug: Console.Error.WriteLine("Debug messages..."); + + var commands = GetCommands(state).ToList() is { Count: > 0 } cmds ? cmds : Command.Empty; + Console.WriteLine(string.Join(';', commands.Select(i => i.GetCommandString()))); + } + } + + public static IEnumerable GetCommands(GameState state) { + var units = state.FindUnits(); + var neutral = state.FindFields(Owner.Neutral); + var recyclers = state.FindFields(i => i.Recycler).Keys.ToList(); + var possibleBuilds = state.PossibleRecyclerSpots(recyclers).OrderByDescending(i => i.Value).Take(1).ToList(); + while (possibleBuilds.Count > 0) { + var possibleBuild = possibleBuilds.First(); + if (state.MyMatter < GameState.RecyclerCost) break; + state.MyMatter -= GameState.RecyclerCost; + Console.Error.WriteLine($"Building recycler at {possibleBuild.Key} for {possibleBuild.Value} amount"); + yield return new Build(possibleBuild.Key); + recyclers.Add(possibleBuild.Key); + possibleBuilds = state.PossibleRecyclerSpots(recyclers).OrderByDescending(i => i.Value).Take(1).ToList(); + } + foreach (var (position, amount) in units) { + for (int i = 0; i < amount; i++) { + if (neutral.Count == 0) yield break; + var emptySpot = neutral.Keys.First(); + if (!neutral.Remove(emptySpot, out var field)) throw new InvalidProgramException(); + yield return new Move(1, position, emptySpot); + } + } + } + } + + public abstract record class Command { + public virtual string GetCommandString() => $"{Name} {string.Join(' ', Args.Select(i => i.ToString()))}"; + protected virtual IEnumerable Args { get; } = Enumerable.Empty(); + public virtual string Name => GetType().Name.ToUpper(); + public static List Empty { get; } = new() { new Wait() }; + } + public abstract record class CoordinateCommand(int X, int Y) : Command { + public CoordinateCommand(Coordinate2D Coord) : this(Coord.X, Coord.Y) { } + protected override IEnumerable Args { + get { + yield return X; + yield return Y; + } + } + } + public record class Move(int Amount, int FromX, int FromY, int ToX, int ToY) : Command { + public Move(int Amount, Coordinate2D From, Coordinate2D To) : this(Amount, From.X, From.Y, To.X, To.Y) { } + protected override IEnumerable Args { + get { + yield return Amount; + yield return FromX; + yield return FromY; + yield return ToX; + yield return ToY; + } + } + } + public record class Build(int X, int Y) : CoordinateCommand(X, Y) { + public Build(Coordinate2D coord) : this(coord.X, coord.Y) { } + } + public record class Spawn(int Amount, int X, int Y) : CoordinateCommand(X, Y) { + public Spawn(int Amount, Coordinate2D Coord) : this(Amount, Coord.X, Coord.Y) { } + protected override IEnumerable Args => base.Args.Prepend(Amount); + } + public record class Wait : Command; + public record class Message : Command; + public enum Owner { + Player = 1, + Enemy = 0, + Neutral = -1 + } + public record struct GameState(int Width, int Height, FieldState[,] Fields, int MyMatter = 0, int OppMatter = 0) { + public const int RecyclerCost = 10; + public FieldState FieldAt(Coordinate2D coord) => this[coord]; + public ref FieldState this[Coordinate2D coord] => ref Fields[coord.X, coord.Y]; + public Dictionary PossibleRecyclerSpots(IReadOnlyCollection recyclers) { + Dictionary found = new(); + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) { + Coordinate2D coord = new(x, y); + var field = this[coord]; + if (!field.CanBuild) continue; + var potentialMaterial = coord.Neighbors().Append(coord) + .Where(WithinBounds) + .Where(i => !NearRecycler(recyclers, i)) + .Select(FieldAt) + .Sum(i => i.ScrapAmount); + if (potentialMaterial != 0) + found[coord] = potentialMaterial; + } + return found; + } + + public static bool NearRecycler(IReadOnlyCollection recyclers, Coordinate2D coord) + => recyclers.Any(i => (i - coord).ManhattanDistance < 2); + + public Dictionary FindWhere(Func find) { + Dictionary found = new(); + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) { + Coordinate2D coord = new(x, y); + var amount = find(this[coord]); + if (amount != 0) found[coord] = found.GetValueOrDefault(coord, 0) + amount; + } + return found; + } + public bool WithinBounds(Coordinate2D coord) => coord.X >= 0 && coord.X < Width && coord.Y >= 0 && coord.Y < Height; + public Dictionary FindFields(Func prediate) { + Dictionary found = new(); + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) { + Coordinate2D coord = new(x, y); + var field = this[coord]; + if (prediate(field)) found[coord] = field; + } + return found; + } + public Dictionary FindFields(Owner owner) + => FindFields(i => i.Owner == owner); + public Dictionary FindUnits(Owner owner = Owner.Player) + => FindWhere(i => i.Owner == owner && i is { Units: > 0 and var amount } ? amount : 0); + + } + public record struct Coordinate2D(int X, int Y) { + public override string ToString() => $"{X},{Y}"; + public IEnumerable Neighbors() { + yield return this with { X = X + 1 }; + yield return this with { X = X - 1 }; + yield return this with { Y = Y + 1 }; + yield return this with { Y = Y - 1 }; + } + public static Coordinate2D operator -(Coordinate2D left, Coordinate2D right) => new(left.X - right.X, left.Y - right.Y); + public static Coordinate2D operator +(Coordinate2D left, Coordinate2D right) => new(left.X + right.X, left.Y + right.Y); + public int ManhattanDistance => Math.Abs(X) + Math.Abs(Y); + } + public record struct FieldState(int ScrapAmount, Owner Owner, int Units, bool Recycler, bool CanBuild, bool CanSpawn, bool InRangeOfRecycler); +}