public int?Distance(Point p1) { int distance; if (_cache.TryGetValue(p1, out distance)) { // it is already within cache return(distance); } if (_map.IsDirectPath(_p0, p1)) { // air path distance = _p0.Distance(p1); Add(p1, distance); return(distance); } for (var curDistance = _lastCompletedLevel + 1; !_cache.ContainsKey(p1); curDistance++, _lastCompletedLevel++) { // it is completed var previousLevel = _cache.Where(p => p.Value == curDistance - 1).Select(p => p.Key).ToHashSet(); var nextLevel = previousLevel.SelectMany(p => _map.Directions(p).Select(d => p.Go(d))) .Distinct() .Where(p => !_cache.ContainsKey(p)) .ToHashSet(); if (!nextLevel.Any()) { // no way to go further return(null); } foreach (var p in nextLevel) { Add(p, curDistance); } } return(_cache[p1]); }
// destination with hint in which direction to go public Tuple <Point, Direction> GetDestination(Man man) { var enemies = MenOfRace(Enemy(man.Race)).ToArray(); { var enimiesDistance = enemies.ToDictionary(e => e, e => e.Position.Distance(man.Position)).ToArray(); var minDistanceAsIs = enimiesDistance.Min(p => p.Value); Assert.True(minDistanceAsIs > 0, "never enemies on the same position"); if (minDistanceAsIs == 1) { // we are in position to attack an enemy, no need to move return(null); } } // positions in range versus enemies var inRange = enemies .SelectMany(e => _map.Directions(e.Position).Select(e.Position.Go)) .ToHashSet() .ToDictionary(p => p, p => p.Distance(man.Position)) .ToArray() ; // different enemies can have same in range positions // it could happen that all enemies (ie the only one) are rounded from all sides // in this way no way to go if (!inRange.Any()) { return(null); } // simplest strategy: select path with minimal distance var minDistance = inRange.Min(p => p.Value); if (minDistance == 0) { return(null); // no way to go } // more difficult task var optimizer = new Optimizer(_map, man.Position); var partialResult = new Dictionary <Point, int>(); int?curMinimalDistance = null; foreach (var inRangePoint in inRange.OrderBy(p => p.Value)) { // we try from by air shortest distance if (curMinimalDistance.HasValue) { if (curMinimalDistance.Value < inRangePoint.Value) { // the distance already found is less then shortest candidate by air // we may terminate break; } } var distance = optimizer.Distance(inRangePoint.Key); if (distance.HasValue) { partialResult.Add(inRangePoint.Key, distance.Value); curMinimalDistance = curMinimalDistance.HasValue ? Math.Min(curMinimalDistance.Value, distance.Value) : distance.Value; } } if (!curMinimalDistance.HasValue) { // no way to connect two points return(null); } var destinationList = partialResult.Where(p => p.Value == curMinimalDistance.Value).Select(p => p.Key); var selectedDestination = destinationList.OrderBy(p => p.ReadingOrder).First(); var optimizer1 = new Optimizer(_map, selectedDestination); var options = _map.Directions(man.Position) .ToDictionary(d => d, d => man.Position.Go(d)) .OrderBy(p => p.Value.ReadingOrder) .ToArray(); foreach (var o in options) { var distance = optimizer1.Distance(o.Value); // decreased distance due to the step ahead to the target if (distance.HasValue) { if ((curMinimalDistance.Value - 1) == distance.Value) { // this is the correct direction and expected distance return(Tuple.Create(selectedDestination, o.Key)); } } } throw new Exception("unexpected processing of options"); }