Difference between revisions of "Camera View, Rotate, Trim and Pad a Map in Dart"
Jump to navigation
Jump to search
(added camera view) |
m (Erik moved page Rotate, Trim and Pad a Map in Dart to Camera View, Rotate, Trim and Pad a Map in Dart: added functionality) |
(No difference)
|
Latest revision as of 14:15, 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;
}
}