Camera View, Rotate, Trim and Pad a Map in Dart

From RogueBasin
Revision as of 09:41, 19 October 2023 by Erik (talk | contribs) (added generic types)
Jump to navigation Jump to search

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

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.
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 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;
  }

  // -----------------------------------------------------------------------
  // 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
  // -----------------------------------------------------------------------

  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;
  }
}