Difference between revisions of "Dijkstra Map of Generic Map in Dart"

From RogueBasin
Jump to navigation Jump to search
(initial)
 
(added level of abstraction)
 
(2 intermediate revisions by the same user not shown)
Line 9: Line 9:
* A map is represented by a List-of-lists.
* A map is represented by a List-of-lists.
* A start Point.
* A start Point.
* A set of Strings defining non-traversables.
* A function defining non-traversables.


The code will return a map (list-of-lists) with the distances as double values relative to the start Point.
The code will return a map (list-of-lists) with the distances as double values relative to the start Point.
Line 17: Line 17:
'''Basic'''
'''Basic'''


<syntaxhighlight lang="dart" line>
<syntaxhighlight lang="dart">
List<List<String>> mapList = [ ... ];
List<List<String>> mapList = [ ... ];


Point<int> start = Point(3, 1);
Point<int> start = Point(3, 1);


Set<String> walls = {'#'};
bool isNonTraverseable(dynamic str) => {'#'}.contains(str);


Dijkstra dijkstra = Dijkstra();
Dijkstra dijkstra = Dijkstra();


List<List<double?>> mapDistance = dijkstra.dijkstraMap(mapList, start, wallTypes);
List<List<double?>> mapDistance = dijkstra.dijkstraMap(mapList, start, isNonTraverseable);


List<List<double?>> mapDistanceInverted =  
List<List<double?>> mapDistanceInverted =  
     dijkstra.dijkstraMapInvert(mapList, start, wallTypes, factor: -1.2);
     dijkstra.dijkstraMapInvert(mapList, start, isNonTraverseable, factor: -1.2);
</syntaxhighlight>
</syntaxhighlight>


'''Details'''
'''Details'''


<syntaxhighlight lang="dart" line>
<syntaxhighlight lang="dart">
final mapString =
final mapString =
     '''
     '''
Line 59: Line 59:
List<List<String>> mapList =
List<List<String>> mapList =
     mapString.split('\n').map((row) => row.split('')).toList();
     mapString.split('\n').map((row) => row.split('')).toList();
bool isNonTraverseable(dynamic str) => {'#'}.contains(str);


Dijkstra dijkstra = Dijkstra();
Dijkstra dijkstra = Dijkstra();
List<List<double?>> mapDistance = dijkstra.dijkstraMap(mapList, Point(3, 1), {'#'});
 
List<List<double?>> mapDistance =  
    dijkstra.dijkstraMap(mapList, Point(3, 1), isNonTraverseable);


assert(expected == mapDistance);
assert(expected == mapDistance);
</syntaxhighlight>


String pretty = dijkstra.dijkstraMapPretty(result);
'''Debug'''
print(pretty); // call only for small maps
 
<syntaxhighlight lang="dart">
String pretty = dijkstra.dijkstraMapPretty(mapDistance);
 
// call only for small maps
 
print(pretty);
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight>
                0.0  1.0  2.0  3.0
                1.0  2.0  3.0  4.0
                2.0  3.0  4.0  5.0
                3.0  4.0  5.0  6.0
                4.0  5.0  6.0  7.0
                                8.0
                                9.0 10.0 11.0
</syntaxhighlight >


== Dart Implementation ==
== Dart Implementation ==
Line 82: Line 103:
   /// the distance to the [start] [Point], excluding [nonTraverse] tiles.
   /// the distance to the [start] [Point], excluding [nonTraverse] tiles.


   List<List<double?>> dijkstraMap(
   List<List<double?>> dijkstraMap(List<List<dynamic>> map, Point<int> start,
      List<List<String>> map, Point<int> start, Set<String> nonTraverse) {
      bool Function(dynamic obj) isNonTraverseable) {
     final List<List<double?>> dijkstraMap = List.generate(
     final List<List<double?>> dijkstraMap = List.generate(
         map.length,
         map.length,
Line 102: Line 123:
       final List<Point<int>?> neighbors = [left, top, right, bottom]
       final List<Point<int>?> neighbors = [left, top, right, bottom]
           .where((neighbor) =>
           .where((neighbor) =>
              neighbor != null &&
                  neighbor != null &&
               nonTraverse.contains(map[neighbor.y][neighbor.x]) == false)
                  isNonTraverseable.call(map[neighbor.y][neighbor.x]) == false
               // nonTraverse.contains(map[neighbor.y][neighbor.x]) == false
              )
           .toList();
           .toList();


Line 124: Line 147:


   /// Calculates an inverted Dijkstra map.
   /// Calculates an inverted Dijkstra map.
   List<List<double?>> dijkstraMapInvert(
   List<List<double?>> dijkstraMapInvert(List<List<dynamic>> map,
      List<List<String>> map, Point<int> start, Set<String> nonTraverse,
      Point<int> start, bool Function(dynamic obj) isNonTraverseable,
       {double factor = -1.2}) {
       {double factor = -1.2}) {
     List<List<double?>> ret = dijkstraMap(map, start, nonTraverse);
     List<List<double?>> ret = dijkstraMap(map, start, isNonTraverseable);
     for (int y = 0; y < ret.length; y++) {
     for (int y = 0; y < ret.length; y++) {
       for (int x = 0; x < ret[y].length; x++) {
       for (int x = 0; x < ret[y].length; x++) {
Line 177: Line 200:
     Point<int>?,
     Point<int>?,
     Point<int>?
     Point<int>?
   ) _getNeighborsLTRB(List<List<String>> map, Point<int> tile) {
   ) _getNeighborsLTRB(List<List<dynamic>> map, Point<int> tile) {
     return (
     return (
       _getNeighbor(map, tile.x, tile.y, _Direction.west),
       _getNeighbor(map, tile.x, tile.y, _Direction.west),
Line 191: Line 214:


   Point<int>? _getNeighbor(
   Point<int>? _getNeighbor(
       List<List<String>> map, int x, int y, _Direction direction) {
       List<List<dynamic>> map, int x, int y, _Direction direction) {
     Point<int>? ret;
     Point<int>? ret;



Latest revision as of 12:17, 16 October 2023

Dart Implementation of Dijkstra Maps

The Dart code below calculates a map of distances, based on an input map.

It is versatile in that it is agnostic of any map engine.

The input consists of:

  • A map is represented by a List-of-lists.
  • A start Point.
  • A function defining non-traversables.

The code will return a map (list-of-lists) with the distances as double values relative to the start Point.

Usage

Basic

List<List<String>> mapList = [ ... ];

Point<int> start = Point(3, 1);

bool isNonTraverseable(dynamic str) => {'#'}.contains(str);

Dijkstra dijkstra = Dijkstra();

List<List<double?>> mapDistance = dijkstra.dijkstraMap(mapList, start, isNonTraverseable);

List<List<double?>> mapDistanceInverted = 
    dijkstra.dijkstraMapInvert(mapList, start, isNonTraverseable, factor: -1.2);

Details

final mapString =
    '''
##########
###....###
###....###
###....###
###....###
###....###
######.###
######▒▒▒#''';

final expected = [
  [null, null, null, null, null, null, null, null, null, null],
  [null, null, null, 0.0, 1.0, 2.0, 3.0, null, null, null],
  [null, null, null, 1.0, 2.0, 3.0, 4.0, null, null, null],
  [null, null, null, 2.0, 3.0, 4.0, 5.0, null, null, null],
  [null, null, null, 3.0, 4.0, 5.0, 6.0, null, null, null],
  [null, null, null, 4.0, 5.0, 6.0, 7.0, null, null, null],
  [null, null, null, null, null, null, 8.0, null, null, null],
  [null, null, null, null, null, null, 9.0, 10.0, 11.0, null]
];

List<List<String>> mapList =
    mapString.split('\n').map((row) => row.split('')).toList();

bool isNonTraverseable(dynamic str) => {'#'}.contains(str);

Dijkstra dijkstra = Dijkstra();

List<List<double?>> mapDistance = 
    dijkstra.dijkstraMap(mapList, Point(3, 1), isNonTraverseable);

assert(expected == mapDistance);

Debug

String pretty = dijkstra.dijkstraMapPretty(mapDistance);

// call only for small maps

print(pretty);
                 0.0  1.0  2.0  3.0
                 1.0  2.0  3.0  4.0
                 2.0  3.0  4.0  5.0
                 3.0  4.0  5.0  6.0
                 4.0  5.0  6.0  7.0
                                8.0
                                9.0 10.0 11.0

Dart Implementation

import 'dart:math';

class Dijkstra {

  /// Based on the [start] [Point] and the [map] a Dijkstra Map
  /// will be calculated and returned.
  ///
  /// The numeric values of the cells of the Dijkstra Map indicate
  /// the distance to the [start] [Point], excluding [nonTraverse] tiles.

  List<List<double?>> dijkstraMap(List<List<dynamic>> map, Point<int> start,
      bool Function(dynamic obj) isNonTraverseable) {
    final List<List<double?>> dijkstraMap = List.generate(
        map.length,
        (index) =>
            List.generate(map.first.length, (index) => null, growable: false),
        growable: false);

    final List<Point<int>> frontier = [start];
    final Map<Point<int>, double> distance = {};
    distance[start] = 0;

    while (frontier.isNotEmpty) {
      final current = frontier.removeAt(0);

      final (left, _, top, _, right, _, bottom, _) =
          _getNeighborsLTRB(map, current);

      final List<Point<int>?> neighbors = [left, top, right, bottom]
          .where((neighbor) =>
                  neighbor != null &&
                  isNonTraverseable.call(map[neighbor.y][neighbor.x]) == false
              // nonTraverse.contains(map[neighbor.y][neighbor.x]) == false
              )
          .toList();

      for (final Point<int>? neighbour in neighbors) {
        if (distance.containsKey(neighbour!) == false) {
          frontier.add(neighbour);
          distance[neighbour] = 1 + distance[current]!;
        }
      }
    }

    for (final entry in distance.entries) {
      final tile = entry.key;
      final dist = entry.value;
      dijkstraMap[tile.y][tile.x] = dist;
    }

    return dijkstraMap;
  }

  /// Calculates an inverted Dijkstra map.
  List<List<double?>> dijkstraMapInvert(List<List<dynamic>> map,
      Point<int> start, bool Function(dynamic obj) isNonTraverseable,
      {double factor = -1.2}) {
    List<List<double?>> ret = dijkstraMap(map, start, isNonTraverseable);
    for (int y = 0; y < ret.length; y++) {
      for (int x = 0; x < ret[y].length; x++) {
        if (ret[y][x] != null) {
          ret[y][x] = ret[y][x]! * factor;
        }
      }
    }
    return ret;
  }

  /// The parameter [map] is the result of [dijkstraMap] or [dijkstraMapInvert].
  ///
  /// The result is a formated [String] with distance values.
  String dijkstraMapPretty(List<List<double?>> map) {
    final maxLength = map
        .expand((e) => e)
        .toList()
        .map((e) => e == null ? '' : e.toStringAsFixed(1))
        .fold(0, (prev, curr) => prev > curr.length ? prev : curr.length);
    final padding = maxLength + 1;

    final List<List<String>> ret = [];
    for (final row in map) {
      final List<String> rowNew = [];
      for (final double? entry in row) {
        if (entry == null) {
          rowNew.add(" ".padLeft(padding));
        } else {
          rowNew.add(entry.toStringAsFixed(1).padLeft(padding));
        }
      }
      ret.add(rowNew);
    }
    return ret.map((e) => e.join('')).join('\n');
  }

  // -----------------------------------------------------------------------
  // NEIGHBORS
  // -----------------------------------------------------------------------

  (
    Point<int>?,
    Point<int>?,
    Point<int>?,
    Point<int>?,
    Point<int>?,
    Point<int>?,
    Point<int>?,
    Point<int>?
  ) _getNeighborsLTRB(List<List<dynamic>> map, Point<int> tile) {
    return (
      _getNeighbor(map, tile.x, tile.y, _Direction.west),
      _getNeighbor(map, tile.x, tile.y, _Direction.northwest),
      _getNeighbor(map, tile.x, tile.y, _Direction.north),
      _getNeighbor(map, tile.x, tile.y, _Direction.northeast),
      _getNeighbor(map, tile.x, tile.y, _Direction.east),
      _getNeighbor(map, tile.x, tile.y, _Direction.southeast),
      _getNeighbor(map, tile.x, tile.y, _Direction.south),
      _getNeighbor(map, tile.x, tile.y, _Direction.southwest),
    );
  }

  Point<int>? _getNeighbor(
      List<List<dynamic>> map, int x, int y, _Direction direction) {
    Point<int>? ret;

    if (x < 0 || y < 0 || x > map.first.length - 1 || y > map.length - 1) {
      ret = null;
    } else {
      switch (direction) {
        case _Direction.north:
          y--;
          break;
        case _Direction.south:
          y++;
          break;
        case _Direction.east:
          x++;
          break;
        case _Direction.west:
          x--;
          break;
        case _Direction.northeast:
          y--;
          x++;
          break;
        case _Direction.northwest:
          y--;
          x--;
          break;
        case _Direction.southeast:
          y++;
          x++;
          break;
        case _Direction.southwest:
          y++;
          x--;
          break;
      }
      if (x >= 0 && x < map.first.length && y >= 0 && y < map.length) {
        ret = Point(x, y);
      }
    }
    return ret;
  }
}

enum _Direction {
  west,
  northwest,
  north,
  northeast,
  east,
  southeast,
  south,
  southwest
}