Difference between revisions of "Camera View, Rotate, Trim and Pad a Map in Dart"

From RogueBasin
Jump to navigation Jump to search
(added generic types)
(added camera view)
Line 6: Line 6:


== Usage ==
== Usage ==
=== Camera View ===
<syntaxhighlight lang="dart" line>
List<List<String>> map = [ ... ];
int x = player.x;
int y = player.y;
int cameraWidth = 40;
int cameraheight = 20;
List<List<String>> camera =
    camera<String>(map, x, y, cameraWidth, cameraHeight);
</syntaxhighlight>


=== Rotate Right ===
=== Rotate Right ===
Line 154: Line 169:


/// Allows for rotating, padding and trimming a list-of-lists.
/// Allows for rotating, padding and trimming a list-of-lists.
import 'dart:math';
/// Allows for rotating, padding, trimming (and more) a list-of-lists (or map).
class Transform {
class Transform {
   // -----------------------------------------------------------------------
   // -----------------------------------------------------------------------
Line 182: Line 200:
   // -----------------------------------------------------------------------
   // -----------------------------------------------------------------------


   /// Pads with WALL tiles returned by [getWall].
   /// Pads with WALL tiles all around.
  ///
  /// The wall tiles are returned by [getWall].
   static List<List<T>> pad<T>(
   static List<List<T>> pad<T>(
       List<List<T>> mapShape, int padding, T Function() getWall) {
       List<List<T>> mapShape, int padding, T Function() getWall) {
Line 259: Line 279:
   /// Copies the given coordinates.
   /// Copies the given coordinates.
   ///
   ///
   /// `x1` and `y1` are inclusive.
   /// [x1] and [y1] are inclusive.
   static List<List<T>> copy<T>(
   static List<List<T>> copy<T>(
       List<List<T>> target, int x0, int y0, int x1, int y1) {
       List<List<T>> target, int x0, int y0, int x1, int y1) {
Line 276: Line 296:


     return copy;
     return copy;
  }
  /// The method returns a camera-section of a map [target].
  ///
  /// The returned area contains [worldX]/[worldY] (for example the player).
  ///
  /// [cameraWidth] and [cameraHeight] are defining the size of the returned area.
  /// In case the map [target] is smaller than the camera, [cameraWidth] or [cameraHeight]
  /// are set to the respective dimensions of [target] internally.
  ///
  /// ### Usage
  ///
  /// ```
  /// List<List<String>> map = [ ... ];
  ///
  /// int x = player.x;
  /// int y = player.y;
  ///
  /// int cameraWidth = 40;
  /// int cameraheight = 20;
  ///
  /// List<List<T>> camera = camera(map, x, y, cameraWidth, cameraHeight);
  /// ```
  static List<List<T>> camera<T>(List<List<T>> target, int worldX, int worldY,
      int cameraWidth, int cameraHeight) {
    final targetWidth = target.first.length;
    final targetHeight = target.length;
    final camTopBorder = 0;
    final camLeftBorder = 0;
    final camRightBorder = targetWidth - 1;
    final camBottomBorder = targetHeight - 1;
    // ---------------------------------------------------------------------
    // Ensure that camera is smaller than or equal to map size
    // ---------------------------------------------------------------------
    cameraWidth = min(cameraWidth, targetWidth);
    cameraHeight = min(cameraHeight, targetHeight);
    // ---------------------------------------------------------------------
    // Camera coordinates
    // ---------------------------------------------------------------------
    int cameraX0 = worldX - cameraWidth ~/ 2;
    int cameraY0 = worldY - cameraHeight ~/ 2;
    // ---------------------------------------------------------------------
    // Camera coordinates are forbidden to exceed map extends
    // ---------------------------------------------------------------------
    cameraX0 = max(cameraX0, camLeftBorder);
    cameraY0 = max(cameraY0, camTopBorder);
    // Same code as below but do dense and harder to understand.
    // cameraX0 = min(cameraX0, camRightBorder - (cameraWidth - 1));
    // cameraY0 = min(cameraY0, camBottomBorder - (cameraHeight - 1));
    if (cameraX0 > camRightBorder - cameraWidth) {
      cameraX0 = camRightBorder - (cameraWidth - 1);
    }
    int cameraX1 = cameraX0 + (cameraWidth - 1);
    if (cameraY0 > camBottomBorder - cameraHeight) {
      cameraY0 = camBottomBorder - (cameraHeight - 1);
    }
    int cameraY1 = cameraY0 + (cameraHeight - 1);
    return copy<T>(target, cameraX0, cameraY0, cameraX1, cameraY1);
   }
   }


Line 305: Line 394:
   // FILTER
   // FILTER
   // -----------------------------------------------------------------------
   // -----------------------------------------------------------------------
  /// Filters [target] by applying [doFilter].
  ///
  /// Returns a list of objects. Each object holding (a) a Point coordinate and
  /// (b) the filtered element at this coordinate.
  ///
  /// ```
  /// bool myFilter(M tile) => ... true or false ...;
  ///
  /// List<List<T>> target = [ ... ];
  ///
  /// final List<(Point<int>, M)> filtered =
  ///  Transform.filter(target, myFilter);
  /// ```


   static List<(Point<int> position, T tile)> filter<T>(
   static List<(Point<int> position, T tile)> filter<T>(

Revision as of 14:14, 29 October 2023

Dart Implementation of Map Transformations

The code below is versatile in that it is map engine agnostic.

It will trim, pad, copy and rotate about any list-of-lists.

Usage

Camera View

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

int x = player.x;
int y = player.y;

int cameraWidth = 40;
int cameraheight = 20;

List<List<String>> camera = 
    camera<String>(map, x, y, cameraWidth, cameraHeight);

Rotate Right

final map = '''
##########
###....###
###....###
###....###
###....###
###....###
######.###
######▒▒▒#''';

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

List<List<String>> result = Transform.rotateRight<String>(mapList);

String mapString = result.map((e) => e.join('')).join('\n');

print(mapString);

// ########
// ########
// ########
// ##.....#
// ##.....#
// ##.....#
// ▒......#
// ▒#######
// ▒#######
// ########

Pad

final map = '''
##########
###....###
###....###
###....###
###....###
###....###
######.###
######▒▒▒#''';

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

String getWall() => '#';

List<List<String>> result = Transform.pad<String>(mapList, 2, getWall);

String mapString = result.map((e) => e.join('')).join('\n');

print(mapString);

// ##############
// ##############
// ##############
// #####....#####
// #####....#####
// #####....#####
// #####....#####
// #####....#####
// ########.#####
// ########▒▒▒###
// ##############
// ##############

Trim

final map = '''
##############
##############
##############
#####....#####
#####....#####
#####....#####
#####....#####
#####....#####
########.#####
########▒▒▒###
##############
##############''';

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

bool isOuterWall(dynamic str) => str == '#';

List<List<String>> result = Transform.trim<String>(mapListPadded, isOuterWall);

String mapString = result.map((e) => e.join('')).join('\n');

print(mapString);

// ########
// #....###
// #....###
// #....###
// #....###
// #....###
// ####.###
// ####▒▒▒#
// ########

Copy

final map = '''
##########
###....###
###....###
###....###
###....###
###....###
######.###
######▒▒▒#''';

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

List<List<String>> result = Transform.copy<String>(mapList, 0, 0, 5, 5);

String mapString = result.map((e) => e.join('')).join('\n');

print(mapString);

// ######
// ###...
// ###...
// ###...
// ###...
// ###...

Dart Implementation

import 'dart:math';

/// Allows for rotating, padding and trimming a list-of-lists.
import 'dart:math';

/// Allows for rotating, padding, trimming (and more) a list-of-lists (or map).
class Transform {
  // -----------------------------------------------------------------------
  // ROTATE
  // -----------------------------------------------------------------------

  /// Rotates to the right.
  static List<List<T>> rotateRight<T>(List<List<T>> target) {
    List<List<T>> rotatedList = [];
    final width = target.first.length;
    final height = target.length - 1;

    for (int column = 0; column < width; column++) {
      final List<T> lineNew = [];

      for (int row = height; row >= 0; row--) {
        lineNew.add(target[row][column]);
      }

      rotatedList.add(lineNew);
    }

    return rotatedList;
  }

  // -----------------------------------------------------------------------
  // PAD
  // -----------------------------------------------------------------------

  /// Pads with WALL tiles all around.
  ///
  /// The wall tiles are returned by [getWall].
  static List<List<T>> pad<T>(
      List<List<T>> mapShape, int padding, T Function() getWall) {
    List<List<T>> padded = _padTop(mapShape, padding, getWall);

    padded = rotateRight(padded);
    padded = _padTop(padded, padding, getWall);
    padded = rotateRight(padded);
    padded = _padTop(padded, padding, getWall);
    padded = rotateRight(padded);
    padded = _padTop(padded, padding, getWall);
    padded = rotateRight(padded);

    return padded;
  }

  /// Pads a WALL at the top
  static List<List<T>> _padTop<T>(
      List<List<T>> other, int padding, T Function() getWall) {
    final width = other.first.length;

    final List<List<T>> mapNew = [];
    mapNew.addAll(List<List<T>>.generate(padding, (index) {
      return List<T>.generate(width, (index) => getWall.call());
    }));
    mapNew.addAll(other);

    return mapNew;
  }

  // -----------------------------------------------------------------------
  // TRIM
  // -----------------------------------------------------------------------

  /// Trims all outer WALLS down to a single outer wall.
  static List<List<T>> trim<T>(
      List<List<T>> mapShape, bool Function(T e) isOuterWall) {
    List<List<T>> trimmed = _trimTopInternal(mapShape, isOuterWall);
    trimmed = rotateRight(trimmed);
    trimmed = _trimTopInternal(trimmed, isOuterWall);
    trimmed = rotateRight(trimmed);
    trimmed = _trimTopInternal(trimmed, isOuterWall);
    trimmed = rotateRight(trimmed);
    trimmed = _trimTopInternal(trimmed, isOuterWall);
    trimmed = rotateRight(trimmed);

    return trimmed;
  }

  /// Trims WALL tiles from the top of the other elements, leaves ony one WALL.
  static List<List<T>> _trimTopInternal<T>(
      List<List<T>> other, bool Function(T e) isOuterWall) {
    final List<List<T>> rows = [];

    int trimTop = 0;
    for (int row = 0; row < other.length; row++) {
      final List<T> line = other.elementAt(row);

      final isOnlyWallsInternal =
          line.where((T e) => isOuterWall.call(e)).length == line.length;

      if (isOnlyWallsInternal == false) {
        break;
      }
      trimTop = row;
    }

    rows.addAll(other.getRange(trimTop, other.length));
    return rows;
  }

// -----------------------------------------------------------------------
// COPY
// -----------------------------------------------------------------------

  /// Copies the given coordinates.
  ///
  /// [x1] and [y1] are inclusive.
  static List<List<T>> copy<T>(
      List<List<T>> target, int x0, int y0, int x1, int y1) {
    assert(x0 >= 0 && x1 < target.first.length);
    assert(y0 >= 0 && y1 < target.length);

    final List<List<T>> copy = [];

    for (int y = y0; y <= y1; y++) {
      final List<T> copyRow = [];
      for (int x = x0; x <= x1; x++) {
        copyRow.add(target[y][x]);
      }
      copy.add(copyRow);
    }

    return copy;
  }

  /// The method returns a camera-section of a map [target].
  ///
  /// The returned area contains [worldX]/[worldY] (for example the player).
  ///
  /// [cameraWidth] and [cameraHeight] are defining the size of the returned area.
  /// In case the map [target] is smaller than the camera, [cameraWidth] or [cameraHeight]
  /// are set to the respective dimensions of [target] internally.
  ///
  /// ### Usage
  ///
  /// ```
  /// List<List<String>> map = [ ... ];
  ///
  /// int x = player.x;
  /// int y = player.y;
  ///
  /// int cameraWidth = 40;
  /// int cameraheight = 20;
  ///
  /// List<List<T>> camera = camera(map, x, y, cameraWidth, cameraHeight);
  /// ```
  static List<List<T>> camera<T>(List<List<T>> target, int worldX, int worldY,
      int cameraWidth, int cameraHeight) {
    final targetWidth = target.first.length;
    final targetHeight = target.length;

    final camTopBorder = 0;
    final camLeftBorder = 0;
    final camRightBorder = targetWidth - 1;
    final camBottomBorder = targetHeight - 1;

    // ---------------------------------------------------------------------
    // Ensure that camera is smaller than or equal to map size
    // ---------------------------------------------------------------------

    cameraWidth = min(cameraWidth, targetWidth);
    cameraHeight = min(cameraHeight, targetHeight);

    // ---------------------------------------------------------------------
    // Camera coordinates
    // ---------------------------------------------------------------------

    int cameraX0 = worldX - cameraWidth ~/ 2;
    int cameraY0 = worldY - cameraHeight ~/ 2;

    // ---------------------------------------------------------------------
    // Camera coordinates are forbidden to exceed map extends
    // ---------------------------------------------------------------------

    cameraX0 = max(cameraX0, camLeftBorder);
    cameraY0 = max(cameraY0, camTopBorder);

    // Same code as below but do dense and harder to understand.
    // cameraX0 = min(cameraX0, camRightBorder - (cameraWidth - 1));
    // cameraY0 = min(cameraY0, camBottomBorder - (cameraHeight - 1));

    if (cameraX0 > camRightBorder - cameraWidth) {
      cameraX0 = camRightBorder - (cameraWidth - 1);
    }
    int cameraX1 = cameraX0 + (cameraWidth - 1);

    if (cameraY0 > camBottomBorder - cameraHeight) {
      cameraY0 = camBottomBorder - (cameraHeight - 1);
    }
    int cameraY1 = cameraY0 + (cameraHeight - 1);

    return copy<T>(target, cameraX0, cameraY0, cameraX1, cameraY1);
  }

  // -----------------------------------------------------------------------
  // CENTER TILE
  // -----------------------------------------------------------------------

  /// Returns the center tile of shapes with uneven `width` and `height`.
  ///
  /// Undefined for other shapes.
  ///
  /// Will throw if width or height are 1.
  static (Point<int> position, T tile) getCenterTile<T>(
      int x, int y, List<List<T>> target) {
    final int targetHeight = target.length;
    final int targetWidth = target.first.length;

    if (targetHeight == 1 || targetWidth == 1) {
      throw Exception('Not implemented');
    }

    final y0 = (targetHeight / 2).ceil() - 1;
    final x0 = (targetWidth / 2).ceil() - 1;
    final tile = target[y0][x0];
    return (Point<int>(x + x0, y + y0), tile);
  }

  // -----------------------------------------------------------------------
  // FILTER
  // -----------------------------------------------------------------------

  /// Filters [target] by applying [doFilter].
  ///
  /// Returns a list of objects. Each object holding (a) a Point coordinate and
  /// (b) the filtered element at this coordinate.
  ///
  /// ```
  /// bool myFilter(M tile) => ... true or false ...;
  ///
  /// List<List<T>> target = [ ... ];
  ///
  /// final List<(Point<int>, M)> filtered =
  ///   Transform.filter(target, myFilter);
  /// ```

  static List<(Point<int> position, T tile)> filter<T>(
      List<List<T>> target, bool Function(T tile) doFilter) {
    final List<(Point<int> position, T tile)> ret = [];

    final int height = target.length;
    final int width = target.first.length;

    for (int y = 0; y < height; y++) {
      for (int x = 0; x < width; x++) {
        if (doFilter.call(target[y][x])) {
          ret.add((Point<int>(x, y), target[y][x]));
        }
      }
    }
    return ret;
  }
}