using Bobo.System.Maze.Bot.Interface; using HightechICT.Amazeing.Client.Rest; using Microsoft.Extensions.Logging; namespace Bobo.System.Maze.Bot { public class SimpleBot : BaseBot { private Stack Directions { get; set; } = new Stack(); private List> ExitDirections { get; set; } = new List>(); private List> CollectionPointDirections { get; set; } = new List>(); public SimpleBot(AmazeingClient mazeClient, ILogger logger) : base(mazeClient, logger) { } public override async Task Run(MazeInfo maze) { if (MazeClient == null) throw new ArgumentException(nameof(MazeClient)); if (string.IsNullOrWhiteSpace(maze.Name)) throw new ArgumentException(nameof(maze.Name)); Random random = new Random(); Logger.Log(LogLevel.Information, "Entering maze '{MazeName}'", maze.Name); PossibleActionsAndCurrentScore result = await MazeClient.EnterMaze(maze.Name); int collected = 0; // --- not DRY but eww --- \\ // if this is an exit, copy the path so we know how to get there if (result.CanExitMazeHere) { Logger.Log(LogLevel.Information, "Found an exit!"); List directions = new List(Directions); directions.Reverse(); ExitDirections.Add(directions); } // if this is a collection point, copy the path so we know how to get there if (result.CanCollectScoreHere) { Logger.Log(LogLevel.Information, "Found a collection point!"); List directions = new List(Directions); directions.Reverse(); CollectionPointDirections.Add(directions); } do { if (result.PossibleMoveActions.All(m => m.HasBeenVisited)) { Logger.Log(LogLevel.Debug, "All adjacent tiles have been visited! Going back."); result = await MoveBackOne(); continue; } else { MoveAction[] notVisited = result.PossibleMoveActions.Where(m => !m.HasBeenVisited).ToArray(); Logger.Log(LogLevel.Debug, $"{string.Join(", ", notVisited.Select(m => m.Direction.ToString()))} have not been visited, selecting next move."); int selected = random.Next(notVisited.Length - 1); MoveAction moveAction = notVisited[selected]; Logger.Log(LogLevel.Debug, $"Moving {moveAction.Direction}, I have {(!moveAction.HasBeenVisited ? "not" : string.Empty)} been here."); result = await MazeClient.Move(moveAction.Direction); Directions.Push(moveAction.Direction); } // if this is an exit, copy the path so we know how to get there if (result.CanExitMazeHere) { Logger.Log(LogLevel.Information, "Found an exit!"); List directions = new List(Directions); directions.Reverse(); ExitDirections.Add(directions); } // if this is a collection point, copy the path so we know how to get there if (result.CanCollectScoreHere) { Logger.Log(LogLevel.Information, "Found a collection point!"); List directions = new List(Directions); directions.Reverse(); CollectionPointDirections.Add(directions); } Logger.Log(LogLevel.Debug, $"New tile has {(!result.CanExitMazeHere ? "no" : string.Empty)} exit."); } while (maze.PotentialReward != result.CurrentScoreInHand || !CollectionPointDirections.Any() || !ExitDirections.Any()); if (!result.CanCollectScoreHere) { result = await MoveUsingList(CollectionPointDirections); } // go to collection collected += result.CurrentScoreInHand; _ = await MazeClient.CollectScore(); result = await MoveUsingList(ExitDirections); await MazeClient.ExitMaze(); Logger.Log(LogLevel.Information, "Collected {Collected}!", collected); Logger.Log(LogLevel.Information, "Exited maze {MazeName}!", maze.Name); // purge the data Directions.Clear(); ExitDirections.Clear(); CollectionPointDirections.Clear(); return collected; } private async Task MoveUsingList(List> possibleDirectionsFromStart) { PossibleActionsAndCurrentScore returnResult = new(); // re-create a list of the directions to the current point List fromStart = new List(Directions); // reverse the list so that index 0 is the first move fromStart.Reverse(); // see what collection point is closest (if multiple) List finalDirections = possibleDirectionsFromStart.First(); int commonIndex = int.MinValue; foreach (List directionList in possibleDirectionsFromStart) { int lowest = fromStart.Count() < directionList.Count() ? fromStart.Count() : directionList.Count(); int lastCommonIndex = 0; // start from index 0 and work up to the lowest size for (lastCommonIndex = 0; lastCommonIndex < lowest; lastCommonIndex++) { if (fromStart[lastCommonIndex] != directionList[lastCommonIndex]) { // not common, break loop break; } } if (lastCommonIndex > commonIndex) { finalDirections = directionList; commonIndex = lastCommonIndex; } } // first go back to the common index for (int returnMoves = 0; returnMoves < fromStart.Count - commonIndex; returnMoves++) { returnResult = await MoveBackOne(); } // follow the directions after the common index for (int toExtiIndex = commonIndex; toExtiIndex < finalDirections.Count(); toExtiIndex++) { returnResult = await MazeClient.Move(finalDirections[toExtiIndex]); Directions.Push(finalDirections[toExtiIndex]); } return returnResult; } private Task MoveBackOne() { Direction direction = Directions.Pop(); switch (direction) { case Direction.Left: direction = Direction.Right; break; case Direction.Right: direction = Direction.Left; break; case Direction.Up: direction = Direction.Down; break; case Direction.Down: direction = Direction.Up; break; } Logger.Log(LogLevel.Debug, "Moving {Direction}, I have been here.", direction); return MazeClient.Move(direction); } } }