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++) { Coordinate2D coord = new(j, i); ref var field = ref state[coord]; field.Coordinates = coord; 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 enemies = state.FindUnits(Owner.Enemy); 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) { Console.Error.WriteLine(string.Join("\n", state.PossibleRecyclerSpots(recyclers).OrderByDescending(i => i.Value).Select(i => $"Recycler at {i.Key} yields {i.Value} resources!"))); 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(); } while (state.MyMatter >= GameState.RobotCost) { var maxRobots = state.MyMatter / GameState.RobotCost; var cost = maxRobots * GameState.RobotCost; yield return new Spawn(maxRobots, units.First().Key); state.MyMatter -= cost; } foreach (var (position, amount) in units) { var (enemyPosition, enemyAmount) = enemies.MinBy(i => (i.Key - position).ManhattanDistance); yield return new Move(amount, position, enemyPosition); } } } 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 const int RobotCost = 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 || field.InRangeOfRecycler) continue; var potentialMaterial = coord.Neighbors().Append(coord) .Where(WithinBounds) .Select(FieldAt) .Where(i => !i.InRangeOfRecycler && !NearRecycler(recyclers, i.Coordinates)) .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 <= 1); 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, Coordinate2D Coordinates); }