|
| 1 | +package net.zomis.games |
| 2 | + |
| 3 | +import net.zomis.Best |
| 4 | + |
| 5 | +private fun flipX(start: Position): Position { |
| 6 | + return Position(start.sizeX - 1 - start.x, start.y, start.sizeX, start.sizeY) |
| 7 | +} |
| 8 | +private fun flipY(start: Position): Position { |
| 9 | + return Position(start.x, start.sizeY - 1 - start.y, start.sizeX, start.sizeY) |
| 10 | +} |
| 11 | +private fun rotate(start: Position): Position { |
| 12 | + return Position(start.sizeY - 1 - start.y, start.x, start.sizeY, start.sizeX) |
| 13 | +} |
| 14 | + |
| 15 | +data class Position(val x: Int, val y: Int, val sizeX: Int, val sizeY: Int) { |
| 16 | + fun next(): Position? { |
| 17 | + if (x == sizeX - 1) { |
| 18 | + return if (y == sizeY - 1) null else Position(0, y + 1, sizeX, sizeY) |
| 19 | + } |
| 20 | + return Position(this.x + 1, this.y, this.sizeX, this.sizeY) |
| 21 | + } |
| 22 | +} |
| 23 | + |
| 24 | +enum class TransformationType(val transforming: (Position) -> Position, val reverse: (Position) -> Position) { |
| 25 | + FLIP_X(::flipX, ::flipX), |
| 26 | + FLIP_Y(::flipY, ::flipY), |
| 27 | + ROTATE(::rotate, { start -> rotate(rotate(rotate(start))) }), |
| 28 | + ; |
| 29 | +} |
| 30 | + |
| 31 | +enum class Transformation(private val transformations: List<TransformationType>) { |
| 32 | + NO_CHANGE(listOf()), |
| 33 | + FLIP_X(listOf(TransformationType.FLIP_X)), |
| 34 | + FLIP_Y(listOf(TransformationType.FLIP_Y)), |
| 35 | + ROTATE_90(listOf(TransformationType.ROTATE)), |
| 36 | + ROTATE_180(listOf(TransformationType.ROTATE, TransformationType.ROTATE)), |
| 37 | + ROTATE_270(listOf(TransformationType.ROTATE, TransformationType.ROTATE, TransformationType.ROTATE)), |
| 38 | + ROTATE_90_FLIP_X(listOf(TransformationType.ROTATE, TransformationType.FLIP_X)), |
| 39 | + ROTATE_90_FLIP_Y(listOf(TransformationType.ROTATE, TransformationType.FLIP_Y)), |
| 40 | + ; |
| 41 | + |
| 42 | + fun transform(position: Position): Position { |
| 43 | + return transformations.fold(position) { pos, trans -> trans.transforming(pos) } |
| 44 | + } |
| 45 | + |
| 46 | + fun reverseTransform(position: Position): Position { |
| 47 | + return transformations.reversed().fold(position) { pos, trans -> trans.reverse(pos) } |
| 48 | + } |
| 49 | + |
| 50 | + private val referencePoints = arrayOf( |
| 51 | + Position(3, 2, 5, 5), |
| 52 | + Position(4, 0, 5, 5) |
| 53 | + ) |
| 54 | + fun apply(transformation: Transformation): Transformation { |
| 55 | + val simplestTransformation = Transformation.values().filter {result -> |
| 56 | + referencePoints.all {p -> transformation.transform(this.transform(p)) == result.transform(p) } |
| 57 | + } |
| 58 | + return simplestTransformation.single() |
| 59 | + } |
| 60 | + |
| 61 | +} |
| 62 | + |
| 63 | +class Map2D<T>(val sizeX: Int, val sizeY: Int, val getter: (x: Int, y: Int) -> T, val setter: (x: Int, y: Int, value: T) -> Unit) { |
| 64 | + |
| 65 | + fun standardizedTransformation(valueFunction: (T) -> Int): Transformation { |
| 66 | + // keep a Set<Transformation>, start with all of them |
| 67 | + // loop through Transformations and find ones with the extremes |
| 68 | + // the goal is that of all the possible transformations, the result should be the one with the lowest/highest value |
| 69 | + |
| 70 | + // start in the possible fields for the target map upper-left corner |
| 71 | + // then continue, line by line, beginning with increasing X and then increase Y |
| 72 | + val possibleTransformations = Transformation.values().toMutableSet() |
| 73 | + if (sizeX != sizeY) { |
| 74 | + // Rotating 90 or 270 degrees only works if both width or height is the same |
| 75 | + possibleTransformations.remove(Transformation.ROTATE_90) |
| 76 | + possibleTransformations.remove(Transformation.ROTATE_270) |
| 77 | + } |
| 78 | + |
| 79 | + var position: Position? = Position(0, 0, sizeX, sizeY) |
| 80 | + while (possibleTransformations.size > 1 && position != null) { |
| 81 | + val best = Best<Transformation> { transformation -> |
| 82 | + val originalPos = transformation.reverseTransform(position!!) |
| 83 | + val originalT = getter(originalPos.x, originalPos.y) |
| 84 | + valueFunction(originalT).toDouble() |
| 85 | + } |
| 86 | + possibleTransformations.forEach { |
| 87 | + best.next(it) |
| 88 | + } |
| 89 | + possibleTransformations.retainAll(best.getBest()) |
| 90 | + |
| 91 | + position = position.next() |
| 92 | + } |
| 93 | + |
| 94 | + val transformation = possibleTransformations.first() // map can be symmetric so don't use .single |
| 95 | + return transformation |
| 96 | + } |
| 97 | + |
| 98 | + fun transform(transformation: Transformation) { |
| 99 | + val rotated = Map2DX(sizeX, sizeY) { x, y -> |
| 100 | + val pos = Position(x, y, sizeX, sizeY) |
| 101 | + val oldPos = transformation.reverseTransform(pos) |
| 102 | + getter(oldPos.x, oldPos.y) |
| 103 | + } |
| 104 | + |
| 105 | + (0 until sizeY).forEach {y -> |
| 106 | + (0 until sizeX).forEach {x -> |
| 107 | + setter(x, y, rotated.grid[y][x]) |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + fun standardize(valueFunction: (T) -> Int) { |
| 113 | + this.transform(this.standardizedTransformation(valueFunction)) |
| 114 | + } |
| 115 | + |
| 116 | +} |
| 117 | + |
| 118 | +class Map2DX<T>(val sizeX: Int, val sizeY: Int, val factory: (x: Int, y: Int) -> T) { |
| 119 | + |
| 120 | + val grid: MutableList<MutableList<T>> = (0 until sizeY).map { y -> |
| 121 | + (0 until sizeX).map { x -> |
| 122 | + factory(x, y) |
| 123 | + }.toMutableList() |
| 124 | + }.toMutableList() |
| 125 | + |
| 126 | + fun set(x: Int, y: Int, value: T) { |
| 127 | + grid[y][x] = value |
| 128 | + } |
| 129 | + |
| 130 | + fun asMap2D(): Map2D<T> { |
| 131 | + return Map2D(sizeX, sizeY, { x, y -> grid[y][x] }) {x, y, v -> |
| 132 | + grid[y][x] = v |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + fun standardize(value: (T) -> Int): Map2DX<T> { |
| 137 | + asMap2D().standardize(value) |
| 138 | + return this |
| 139 | + } |
| 140 | + |
| 141 | + override fun toString(): String { |
| 142 | + return "Map2D(grid=$grid)" |
| 143 | + } |
| 144 | + |
| 145 | +} |
0 commit comments