public IEnumerable <RoverAction> Simulate(IRoverStatusAccessor rover) { foreach (var action in GatherPower(rover)) { yield return(action); } while (true) { var adjacent = rover.Adjacent; TerrainType occupied = adjacent[Direction.None]; if (rover.MovesLeft <= 5) { foreach (var action in DoLowMoves(rover, adjacent)) { yield return(action); } yield break; } if (occupied.IsSampleable()) { yield return(RoverAction.CollectSample); UpdateMap(rover); if (rover.SamplesCollected >= Parameters.SamplesPerProcess && rover.Power > Parameters.ProcessCost + Parameters.MoveSmoothCost) { yield return(RoverAction.ProcessSamples); } } Direction nextMove = Direction.None; if (_path != null && _path.Count > 1) { nextMove = _path.Pop(); } else if (FindAdjacentSampleable(adjacent).HasValue) { nextMove = FindAdjacentSampleable(adjacent).Value; } if (nextMove == Direction.None || adjacent[nextMove] == TerrainType.Impassable) { Direction?reset = ResetDestination(rover.Position); if (reset == null) { yield break; // We're stuck. } nextMove = reset.Value; } yield return(new RoverAction(nextMove)); UpdateMap(rover); } }
/// <summary> /// Searches for at least two contiguous smooth tiles to later gather power from. /// </summary> private IEnumerable <RoverAction> Explore(IRoverStatusAccessor rover) { Position center = Parameters.BottomRight / 2; while (true) { Position position = rover.Position; // First we check to see if we've found a position that we can gather power from. for (Int32 i = 0; i < Direction.DirectionCount + 1; i++) { Direction direction = Direction.FromInt32(i); CoordinatePair neighbor = position + direction; if (_map[neighbor] == TerrainType.Smooth && _map.CountNeighborsOfType(neighbor, TerrainType.Smooth) > 0) { // If we have, break out of the exploration phase. Int32 requiredPower = Parameters.GetMovementPowerCost(rover.Adjacent[direction]) + 1; foreach (var action in EnsureSufficientPower(rover, requiredPower)) { yield return(action); } yield return(new RoverAction(direction)); UpdateMap(rover); yield break; } } Direction exploreDirection = FindNextExploreDirection(center, position); if (exploreDirection != Direction.None) { Int32 requiredPower = Parameters.GetMovementPowerCost(rover.Adjacent[exploreDirection]) + 1; foreach (var action in EnsureSufficientPower(rover, requiredPower)) { yield return(action); } yield return(new RoverAction(exploreDirection)); UpdateMap(rover); continue; } // None of our immediate neighbors have any unknown neighbors, so just find a path towards the nearest unknown tile. var path = _pathfinder.FindNearestUnknown(position); while (path.Count > 1) // Don't move on to the unknown tile, just next to it. { Int32 requiredPower = Parameters.GetMovementPowerCost(rover.Adjacent[path.Peek()]) + 1; foreach (var action in EnsureSufficientPower(rover, requiredPower)) { yield return(action); } yield return(new RoverAction(path.Pop())); UpdateMap(rover); } } }
private IEnumerable <RoverAction> EnsureSufficientPower(IRoverStatusAccessor rover, Int32 target) { if (rover.Power >= target) { return(System.Linq.Enumerable.Empty <RoverAction>()); } Int32 powerDeficit = Math.Max(target - rover.Power - rover.CollectablePower, 0); return(System.Linq.Enumerable.Repeat(RoverAction.CollectPower, powerDeficit + 1)); }
private IEnumerable <RoverAction> GatherPower(IRoverStatusAccessor rover) { UpdateMap(rover); foreach (var action in Explore(rover)) { yield return(action); } foreach (var action in Exploit(rover)) { yield return(action); } }
private Int32 CalculateExcessPowerThershold(IRoverStatusAccessor rover) { const Double smoothRoughRatio = 1; Double meanUnsampledMoveCost = (smoothRoughRatio * Parameters.MoveSmoothCost + Parameters.MoveRoughCost) / (smoothRoughRatio + 1.0); Double processCostPerSample = (Double)Parameters.ProcessCost / Parameters.SamplesPerProcess; Double meanSampleSequenceCost = meanUnsampledMoveCost + Parameters.SampleCost + processCostPerSample; Int32 maxSampleSequenceCount = (rover.MovesLeft - 1) / 3; Int32 targetPower = (Int32)(maxSampleSequenceCount * meanSampleSequenceCost) + Parameters.TransmitCost; return(targetPower); }
private AdjacentTerrain GetAdjacent(IRoverStatusAccessor rover) { var adjacent = rover.Adjacent; var position = rover.Position; return(new AdjacentTerrain ( IsDeadEnd(position, Direction.Up) ? TerrainType.Impassable : adjacent.Up, IsDeadEnd(position, Direction.Right) ? TerrainType.Impassable : adjacent.Right, IsDeadEnd(position, Direction.Down) ? TerrainType.Impassable : adjacent.Down, IsDeadEnd(position, Direction.Left) ? TerrainType.Impassable : adjacent.Left, IsDeadEnd(position, Direction.None) ? TerrainType.Impassable : adjacent.Occupied )); }
private Int32 CalculateExcessPowerThershold(IRoverStatusAccessor rover) { const Int32 smoothRoughRatio = 10; // For the default generator, the ratio is 2:1 Double meanUnsampledMoveCost = (smoothRoughRatio * Parameters.MoveSmoothCost + Parameters.MoveRoughCost) / (smoothRoughRatio + 1.0); Double processCostPerSample = (Double)Parameters.ProcessCost / Parameters.SamplesPerProcess; Double meanSampleCost = meanUnsampledMoveCost + Parameters.SampleCost + processCostPerSample; Double sampleToLogisticMoveRatio = 3.9; // For 2 every move/sample/process sequences, we estimate 1 moves will be purely logistics (move, power). Double meanCost = (meanSampleCost * sampleToLogisticMoveRatio + Parameters.MoveSmoothCost) / (sampleToLogisticMoveRatio + 1); Int32 targetPower = (Int32)((rover.MovesLeft - 1) * meanCost) + Parameters.TransmitCost; return(targetPower); }
public void Simulate(IRoverStatusAccessor rover) { var scratchRover = new ScratchRover(rover); Ai.Simulate(scratchRover); }
public IEnumerable <RoverAction> Simulate(IRoverStatusAccessor rover) { while (true) { var adjacent = GetAdjacent(rover); TerrainType occupied = adjacent[Direction.None]; (Direction? adjacentSmoothDir, Direction? adjacentRoughDir, Int32 smoothCount) = FindAdjacentUnsampled(adjacent); if (rover.MovesLeft <= 5) { foreach (var action in DoLowMoves(rover, adjacent)) { yield return(action); } yield break; } if (rover.Power < LowPowerThreshold || (!adjacentSmoothDir.HasValue && occupied == TerrainType.Smooth)) { if (rover.Power < CalculateExcessPowerThershold(rover) / 2 && rover.NoBacktrack > 3) { yield return(RoverAction.CollectPower); } if (rover.Power < LowPowerThreshold) { yield return(RoverAction.Transmit); } } else if (_gatheringPower && HasExcessPower(rover, rover.CollectablePower)) { yield return(RoverAction.CollectPower); _gatheringPower = false; } // While we're gather power, we don't collect samples and instead abuse the Backtracking mechanic gather a large amount of power. if (occupied.IsSampleable() && (!_gatheringPower || (adjacentSmoothDir == null && occupied == TerrainType.Smooth) || _gatherPowerDir != Direction.None)) { yield return(RoverAction.CollectSample); if (rover.SamplesCollected >= Parameters.SamplesPerProcess && rover.Power > Parameters.ProcessCost + Parameters.MoveSmoothCost) { yield return(RoverAction.ProcessSamples); } } Boolean hasExcessPower = HasExcessPower(rover); if (hasExcessPower) { _gatheringPower = false; } (Boolean isDeadEnd, Direction deadEndEscape) = CheckDeadEnd(adjacent); if (isDeadEnd && rover.Adjacent.Occupied != TerrainType.Smooth) { AddDeadEnd(rover.Position); } if (_gatherPowerDir != Direction.None) { _destination = _gatherPowerDir; } else if (adjacentSmoothDir.HasValue) { _destination = adjacentSmoothDir.Value; // Prioritize smooth squares } else if (hasExcessPower && !_gatheringPower && adjacentRoughDir.HasValue) { _destination = adjacentRoughDir.Value; // Visit rough squares if the rover has enough power } else if (isDeadEnd) { _destination = deadEndEscape; } Direction nextMove = _destination; if (nextMove == Direction.None || adjacent[nextMove] == TerrainType.Impassable) { if (_avoidanceDestination != Direction.None) { if (adjacent[_avoidanceDestination] == TerrainType.Impassable) { _destination = ResetDestination(adjacent); nextMove = _destination; _avoidanceDestination = Direction.None; } else { nextMove = _avoidanceDestination; } } else { nextMove = AvoidObstacle(adjacent); // Obstacle avoidance _avoidanceDestination = nextMove; } } else { _avoidanceDestination = Direction.None; } if (_gatheringPower && rover.Adjacent.Occupied == TerrainType.Smooth && smoothCount > 1) { _gatherPowerDir = nextMove.Opposite(); } else { _gatherPowerDir = Direction.None; } yield return(new RoverAction(nextMove)); _roundRobin++; } }
private IEnumerable <RoverAction> DoLowMoves(IRoverStatusAccessor rover, AdjacentTerrain adjacent) { if (rover.MovesLeft > 5) { yield break; } Boolean smoothOccupied = adjacent[Direction.None] == TerrainType.Smooth; Boolean roughOccupied = adjacent[Direction.None] == TerrainType.Rough; (Direction? smoothDir, Direction? roughDir, _) = FindAdjacentUnsampled(adjacent); if (rover.MovesLeft == 5 && rover.Power > Parameters.SampleCost + Parameters.MoveRoughCost + Parameters.SampleCost + Parameters.ProcessCost) { if (smoothOccupied || roughOccupied) { yield return(RoverAction.CollectSample); } Direction?moveDir = smoothDir ?? roughDir; if (moveDir.HasValue) { yield return(new RoverAction(moveDir.Value)); yield return(RoverAction.CollectSample); } yield return(RoverAction.ProcessSamples); yield return(RoverAction.Transmit); yield break; } if (rover.MovesLeft >= 4) { if (rover.Power < Parameters.ProcessCost + Parameters.SampleCost + Parameters.ProcessCost) { yield return(RoverAction.CollectPower); } yield return(RoverAction.CollectPower); if (rover.Power > Parameters.ProcessCost) { if (rover.Power > Parameters.ProcessCost + Parameters.SampleCost && smoothOccupied) { yield return(RoverAction.CollectSample); } yield return(RoverAction.ProcessSamples); } } else if (rover.MovesLeft == 3) { if (rover.Power > Parameters.ProcessCost + Parameters.SampleCost && smoothOccupied) { yield return(RoverAction.CollectSample); } else { yield return(RoverAction.CollectPower); } if (rover.Power > Parameters.ProcessCost) { yield return(RoverAction.ProcessSamples); } } if (rover.MovesLeft == 2) { if (rover.Power == 0) { yield return(RoverAction.CollectPower); } else if (rover.Power > Parameters.ProcessCost && rover.SamplesCollected > 0) { yield return(RoverAction.ProcessSamples); } } if (rover.SamplesProcessed > 0) { yield return(RoverAction.Transmit); } yield break; }
private Boolean HasExcessPower(IRoverStatusAccessor rover, Int32 potentialPower) { Int32 targetPower = CalculateExcessPowerThershold(rover); return(rover.Power + potentialPower >= targetPower); }
private void UpdateMap(IRoverStatusAccessor rover) => _map.UpdateTerrain(rover.Position, rover.Adjacent);
private Int32 CalculateNoBacktrackTarget(IRoverStatusAccessor rover) { Int32 power = CalculateExcessPowerThershold(rover); return((Int32)Math.Ceiling(Math.Pow(power, 1.0 / 3.0))); }
/// <summary> /// Gathers power by staying on smooth squares and revealing as much as possible until the threshold is reached. /// </summary> private IEnumerable <RoverAction> Exploit(IRoverStatusAccessor rover) { Stack <Direction> pathToUnknown = _pathfinder.FindNearestUnknownThroughSmooth(rover.Position); Boolean isPathDirty = false; while (!HasExcessPower(rover, rover.CollectablePower)) { Int32 noBacktrackTarget = CalculateNoBacktrackTarget(rover); Int32 movesToTarget = noBacktrackTarget - rover.NoBacktrack; Int32 powerNeeded = movesToTarget * Parameters.MoveSmoothCost + 1; Int32 excessPower = rover.Power - powerNeeded; Direction?preferredDir = _map.FindNeighborWithMultipleSmoothNeighbors(rover.Position); if (rover.Adjacent.Occupied == TerrainType.Smooth && preferredDir.HasValue && excessPower > 0 && !rover.IsHopperFull) { yield return(RoverAction.CollectSample); yield return(new RoverAction(preferredDir.Value)); UpdateMap(rover); isPathDirty = true; continue; } if (rover.Power < Parameters.MoveSmoothCost + 1) { Int32 cost = Parameters.GetMovementPowerCost(TerrainType.Smooth); Int32 requiredPower; if (cost <= 3) { requiredPower = cost + 1; } else { Int32 movesNeeded = (Int32)(Math.Ceiling(Math.Sqrt(4 * cost - 3) - 3) / 2); requiredPower = movesNeeded * cost + 1; } foreach (var action in EnsureSufficientPower(rover, requiredPower)) { yield return(action); } continue; } if (pathToUnknown != null) { // We reveal unknown tiles as we go, but without leaving the smooth area. if (pathToUnknown.Count <= 1 || isPathDirty) { pathToUnknown = _pathfinder.FindNearestUnknownThroughSmooth(rover.Position); isPathDirty = false; continue; } yield return(new RoverAction(pathToUnknown.Pop())); } else { Direction?smoothDir = FindAdjacentSmooth(rover.Adjacent); yield return(new RoverAction(smoothDir.Value)); } UpdateMap(rover); } yield return(RoverAction.CollectPower); }
IEnumerable <RoverAction> IAi.Simulate(IRoverStatusAccessor rover) { var scratchRover = new ScratchRover(rover); return(Ai.Simulate(scratchRover)); }
public ScratchRover(IRoverStatusAccessor rover) { Rover = rover ?? throw new ArgumentNullException(nameof(rover)); }
private Boolean HasExcessPower(IRoverStatusAccessor rover) => HasExcessPower(rover, 0);