Skip to content

Commit

Permalink
Implemented XY-tracker without route finding
Browse files Browse the repository at this point in the history
  • Loading branch information
boekabart committed Feb 25, 2020
1 parent 8d3aa83 commit d7b4a0f
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 23 deletions.
20 changes: 20 additions & 0 deletions Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;

namespace Maze
{
internal static class Extensions
{
public static Direction Reversed( this Direction bestDir)
{
switch (bestDir)
{
case Direction.Down: return Direction.Up;
case Direction.Up: return Direction.Down;
case Direction.Left: return Direction.Right;
case Direction.Right: return Direction.Left;
default:
throw new ArgumentException("Bad dir");
}
}
}
}
45 changes: 22 additions & 23 deletions MazeSolver.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Maze
{
internal class MazeSolver
{
private readonly IAmazeingClient _client;
private readonly XyGrid _xyGrid = new XyGrid();

public MazeSolver( IAmazeingClient client,MazeInfo maze )
{
_client = client;
this._maze = maze;
_maze = maze;
}
public async Task Solve()
{
var options = await _client.EnterMaze(_maze.Name);
_xyGrid.Register(options);
TrackExits(options, _exitCrumbs);
TrackCollectionPoints(options, _collectCrumbs);

Expand All @@ -43,7 +46,7 @@ private async Task GoToExit(PossibleActionsAndCurrentScore options)
if (stack != null)
{
var dir = stack.Peek();
var step = ReverseDir(dir);
var step = dir.Reversed();
options = await MakeMove(step, _exitCrumbs, _collectCrumbs, _crawlCrumbs);
continue;
}
Expand All @@ -59,7 +62,7 @@ private async Task GoToExit(PossibleActionsAndCurrentScore options)
// Back-track if no new directions were found
if (_crawlCrumbs.Any())
{
var dir = ReverseDir(_crawlCrumbs.Peek());
var dir = _crawlCrumbs.Peek().Reversed();
options = await MakeMove(dir, _exitCrumbs, _collectCrumbs, _crawlCrumbs);
continue;
}
Expand All @@ -83,7 +86,7 @@ private async Task<PossibleActionsAndCurrentScore> CollectScoreInHand(PossibleAc
if (stack != null)
{
var dir = stack.Peek();
var step = ReverseDir(dir);
var step = dir.Reversed();
options = await MakeMove(step, _exitCrumbs, _collectCrumbs, _crawlCrumbs);
continue;
}
Expand All @@ -99,7 +102,7 @@ private async Task<PossibleActionsAndCurrentScore> CollectScoreInHand(PossibleAc
// Back-track if no new directions were found
if (_crawlCrumbs.Any())
{
var dir = ReverseDir(_crawlCrumbs.Peek());
var dir = _crawlCrumbs.Peek().Reversed();
options = await MakeMove(dir, _exitCrumbs, _collectCrumbs, _crawlCrumbs);
continue;
}
Expand All @@ -123,14 +126,20 @@ private async Task<PossibleActionsAndCurrentScore> CollectAllPoints(PossibleActi

if (_crawlCrumbs.Any())
{
var dir = ReverseDir(_crawlCrumbs.Peek());
var dir = _crawlCrumbs.Peek().Reversed();
options = await MakeMove(dir, _exitCrumbs, _collectCrumbs, _crawlCrumbs);
continue;
}

Console.Error.WriteLine("Stuck while collecting!");
}

if (false && !_xyGrid.InvalidState)
{
_xyGrid.Draw();
Console.ReadKey();
}

return options;
}

Expand All @@ -147,7 +156,7 @@ private static void TrackExits(PossibleActionsAndCurrentScore options,
foreach (var dir in options.PossibleMoveActions.Where(ma => ma.AllowsExit))
{
var stack = new Stack<Direction>();
stack.Push(ReverseDir(dir.Direction));
stack.Push(dir.Direction.Reversed());
exitCrumbs.Add(stack);
}
}
Expand All @@ -158,7 +167,7 @@ private static void TrackCollectionPoints(PossibleActionsAndCurrentScore options
foreach (var dir in options.PossibleMoveActions.Where(ma => ma.AllowsScoreCollection))
{
var stack = new Stack<Direction>();
stack.Push(ReverseDir(dir.Direction));
stack.Push(dir.Direction.Reversed());
collectCrumbs.Add(stack);
}
}
Expand All @@ -172,6 +181,8 @@ private async Task<PossibleActionsAndCurrentScore> MakeMove(Direction direction,
var newOptions = await _client.Move(direction);
if (newOptions == null)
throw new ArgumentException();
_xyGrid.RegisterMove(direction);
_xyGrid.Register(newOptions);

// Record the move in all crumbs
foreach (var st in exitCrumbs)
Expand Down Expand Up @@ -201,7 +212,7 @@ private async Task<PossibleActionsAndCurrentScore> MakeMove(Direction direction,
private readonly List<Stack<Direction>> _collectCrumbs = new List<Stack<Direction>>();
private readonly List<Stack<Direction>> _exitCrumbs = new List<Stack<Direction>>();

private static MoveAction MostUsefulDirForCollecting(PossibleActionsAndCurrentScore options,
private MoveAction MostUsefulDirForCollecting(PossibleActionsAndCurrentScore options,
Stack<Direction> crawlCrumbs)
{
var mostUsefulDir = options
Expand All @@ -211,6 +222,7 @@ private static MoveAction MostUsefulDirForCollecting(PossibleActionsAndCurrentSc
//.ThenBy(ma => ma.AllowsScoreCollection) // Un-prefer ScoreCollection Points, we'll get there later
//.ThenBy(ma => ma.AllowsExit) // Un-prefer exits, we'll get there later
.ThenBy(ma => ma.RewardOnDestination == 0) // Prefer reward directions over non-reward. It might be the last straw!
.ThenBy(ma => _xyGrid.SeenTile(ma.Direction)) // Un-prefer tiles I've seen and apparently didn't visit.. for a reason?
.ThenBy(ma => HugTheLeftWall(ma.Direction, crawlCrumbs))
//.ThenBy(ma => RandomGenerator.Next())
.ThenByDescending(ma => ma.Direction) // Prefer Starting Left over Down over Right over Up... no real reason, just for predictability
Expand Down Expand Up @@ -262,23 +274,10 @@ static void Push(Stack<Direction> stack, Direction dir)
return;
}

if (stack.Count != 0 && stack.Peek() == ReverseDir(dir))
if (stack.Count != 0 && stack.Peek() == dir.Reversed())
stack.Pop();
else
stack.Push(dir);
}

private static Direction ReverseDir(Direction bestDir)
{
switch (bestDir)
{
case Direction.Down: return Direction.Up;
case Direction.Up: return Direction.Down;
case Direction.Left: return Direction.Right;
case Direction.Right: return Direction.Left;
default:
throw new ArgumentException("Bad dir");
}
}
}
}
171 changes: 171 additions & 0 deletions XyGrid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Drawing;
using System.Linq;

namespace Maze
{
internal class XyGrid
{
internal class Tile
{
public bool IsExit { get; }
public bool IsCollectionPoint { get; }
public bool IsVisited { get; }
public ImmutableHashSet<Direction> PossibleDirections { get; }

public Tile(PossibleActionsAndCurrentScore currentLocation)
{
IsExit = currentLocation.CanExitMazeHere;
IsCollectionPoint = currentLocation.CanCollectScoreHere;
IsVisited = true;
PossibleDirections =
currentLocation.PossibleMoveActions.Select(ma => ma.Direction).ToImmutableHashSet();
}

public Tile(MoveAction moveAction, ImmutableHashSet<Direction> alreadyKnownDirections = null)
{
alreadyKnownDirections = alreadyKnownDirections ?? ImmutableHashSet<Direction>.Empty;
IsExit = moveAction.AllowsExit;
IsCollectionPoint = moveAction.AllowsScoreCollection;
IsVisited = moveAction.HasBeenVisited;
PossibleDirections = alreadyKnownDirections.Add(moveAction.Direction.Reversed());
}

public static Tile TryMerge(MoveAction moveAction, Tile previousKnownState)
{
if (moveAction.AllowsExit != previousKnownState.IsExit
|| moveAction.AllowsScoreCollection != previousKnownState.IsCollectionPoint
|| moveAction.HasBeenVisited != previousKnownState.IsVisited)
return null;
return new Tile(moveAction, previousKnownState.PossibleDirections);
}

public static Tile TryMerge(PossibleActionsAndCurrentScore truth, Tile previousKnownState)
{
var newTile = new Tile(truth);
if (previousKnownState.IsExit != newTile.IsExit
|| previousKnownState.IsCollectionPoint != newTile.IsCollectionPoint)
return null;

if (previousKnownState.IsVisited)
{
return Match(previousKnownState.PossibleDirections, newTile.PossibleDirections)
? previousKnownState
: null;
}

// From NOT visited to visited
return previousKnownState.PossibleDirections.All(newTile.PossibleDirections.Contains) ? newTile : null;
}

public static bool Match(ISet<Direction> lhs, ISet<Direction> rhs)
{
return lhs.Count == rhs.Count && lhs.All(rhs.Contains);
}
}

public void Draw()
{
var offsetX = _dick.Keys.Min(xy => xy.X);
var offsetY = _dick.Keys.Max(xy => xy.Y);
Console.Clear();
foreach (var kvp in _dick)
{
var x = 1 + kvp.Key.X - offsetX;
var y = 1 + offsetY - kvp.Key.Y;

Console.SetCursorPosition(x, y);
var tile = kvp.Value;
Console.ForegroundColor = tile.IsVisited ? ConsoleColor.White : ConsoleColor.DarkGray;
Console.Write( "X");
}
Console.SetCursorPosition(1 + X - offsetX, 1 + offsetY - Y);
}

public (int X, int Y) CurrentLocation { get; private set; }
public int X => CurrentLocation.X;
public int Y => CurrentLocation.Y;
public bool InvalidState { get; private set; }
public void Register(PossibleActionsAndCurrentScore options)
{
if (InvalidState) return;
if (!UpdateCurrent(options))
return;

foreach (var pma in options.PossibleMoveActions)
{
if (!UpdateNext(pma))
return;
}
}

public int SeenTile(Direction dir) => InvalidState ? 0 :
_dick.TryGetValue(Moved(dir), out var tile) ? tile.PossibleDirections.Count : 0;

private readonly Dictionary<(int X, int Y), Tile> _dick = new Dictionary<(int X, int Y), Tile>();

private bool UpdateCurrent(PossibleActionsAndCurrentScore options)
{
var newTile = _dick.TryGetValue(CurrentLocation, out Tile previousState)
? Tile.TryMerge(options, previousState)
: new Tile(options);

if (newTile == null)
{
// Draw();
// Console.ReadKey();

Console.Error.WriteLine($"XY-incompatible maze detected. Conflict with known state at {CurrentLocation.X},{CurrentLocation.Y}.");
InvalidState = true;
return false;
}

_dick[CurrentLocation] = newTile;
return true;
}

private bool UpdateNext(MoveAction moveAction)
{
var location = Moved(moveAction.Direction);
var newTile = _dick.TryGetValue(location, out Tile previousState)
? Tile.TryMerge(moveAction, previousState)
: moveAction.HasBeenVisited
? null
: new Tile(moveAction);

if (newTile == null)
{
// Draw();
// Console.ReadKey();

Console.Error.WriteLine($"XY-incompatible maze detected. Conflict with known state at {location.X},{location.Y} {moveAction.Direction}.");
InvalidState = true;
return false;
}

_dick[location] = newTile;
return true;
}

private (int X, int Y) Moved(Direction dir)
{
switch(dir)
{
case Direction.Up:
return (X, Y + 1);
case Direction.Right:
return (X + 1, Y);
case Direction.Down:
return (X, Y - 1);
case Direction.Left:
return (X - 1, Y);
default:
throw new ArgumentOutOfRangeException(nameof(dir), dir, null);
}
}

public void RegisterMove(Direction dir) => CurrentLocation = Moved(dir);
}
}

0 comments on commit d7b4a0f

Please sign in to comment.