codinggame/FallChallenge2022.cs

180 lines
9.2 KiB
C#

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<Command> 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<object> Args { get; } = Enumerable.Empty<object>();
public virtual string Name => GetType().Name.ToUpper();
public static List<Command> 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<object> 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<object> 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<object> 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<Coordinate2D, int> PossibleRecyclerSpots(IReadOnlyCollection<Coordinate2D> recyclers) {
Dictionary<Coordinate2D, int> 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<Coordinate2D> recyclers, Coordinate2D coord)
=> recyclers.Any(i => (i - coord).ManhattanDistance <= 1);
public Dictionary<Coordinate2D, int> FindWhere(Func<FieldState, int> find) {
Dictionary<Coordinate2D, int> 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<Coordinate2D, FieldState> FindFields(Func<FieldState, bool> prediate) {
Dictionary<Coordinate2D, FieldState> 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<Coordinate2D, FieldState> FindFields(Owner owner)
=> FindFields(i => i.Owner == owner);
public Dictionary<Coordinate2D, int> 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<Coordinate2D> 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);
}