|
| 1 | +package com.sc4nam.module |
| 2 | + |
| 3 | +import io.github.memo33.metarules.meta._, syntax._, Network._, RotFlip._, Flags._ |
| 4 | +import Implicits.segmentToTile |
| 5 | +import NetworkProperties._ |
| 6 | + |
| 7 | +/** Resolves from IDs to meta tiles (on a best effort basis). |
| 8 | + * This is only intended for diagnosing problems with existing rules, not for |
| 9 | + * generating new metarule code. |
| 10 | + */ |
| 11 | +class ReverseResolver private[module] (val reverseTileMap: collection.Map[Int, ::[Tile]]) extends PartialFunction[IdTile | Int, ::[Tile]] { |
| 12 | + private def toId(idTile: IdTile | Int): Int = idTile match { |
| 13 | + case x: IdTile => x.id |
| 14 | + case id: Int => id |
| 15 | + } |
| 16 | + def isDefinedAt(idTile: IdTile | Int): Boolean = reverseTileMap.contains(toId(idTile)) |
| 17 | + def apply(idTile: IdTile | Int): ::[Tile] = { |
| 18 | + val fiber = reverseTileMap.apply(toId(idTile)) |
| 19 | + idTile match { |
| 20 | + case x: IdTile => fiber.map(_ * x.rf).asInstanceOf[::[Tile]] |
| 21 | + case id: Int => fiber |
| 22 | + } |
| 23 | + } |
| 24 | +} |
| 25 | + |
| 26 | +object ReverseResolver { |
| 27 | + |
| 28 | + /** Checks if tiles define the same tile if segments are merged. |
| 29 | + * e.g. (2,0,2,0) & (0,2,0,2) vs (2,2,2,2) |
| 30 | + * or (2,0,2,0) & (0,3,0,0) vs (2,13,2,0) |
| 31 | + */ |
| 32 | + def isSegmentContraction(tile1: Tile, tile2: Tile): Boolean = { |
| 33 | + val List(t1, t2) = List(tile1, tile2).sortBy(_.segs.size) |
| 34 | + if (t1.segs.size == 1 && t2.segs.size == 2 && t2.segs.forall(_.network == t1.segs.head.network)) { |
| 35 | + val s1 = t1.segs.head |
| 36 | + val Seq(s2, s3) = t2.segs.toSeq |
| 37 | + if ((0 to 3).forall(i => (s2.flags(i) == 0 || s3.flags(i) == 0) && ( |
| 38 | + s1.flags(i) == s2.flags(i) + s3.flags(i) |
| 39 | + || Seq(s1.flags(i), s2.flags(i) + s3.flags(i)).map(_.abs).forall(f => f == 1 || f == 3 || f == 11 || f == 13) |
| 40 | + ))) { |
| 41 | + true |
| 42 | + } else false |
| 43 | + } else false |
| 44 | + } |
| 45 | + |
| 46 | + /** Checks if tiles are DxD T or + intersections (which might use the same ID) |
| 47 | + * e.g. (3,0,0,1) & (0,3,0,0) vs (3,0,0,1) & (1,3,0,0) |
| 48 | + */ |
| 49 | + def isDiagonalTAmbuigity(tile1: Tile, tile2: Tile): Boolean = { |
| 50 | + val diff1 = tile1.segs &~ tile2.segs |
| 51 | + val diff2 = tile2.segs &~ tile1.segs |
| 52 | + (diff1.toSeq, diff2.toSeq) match { |
| 53 | + case (Seq(s1), Seq(s2)) if s1.network == s2.network => |
| 54 | + (0 to 3).filter(i => s1.flags(i) != s2.flags(i)) match { |
| 55 | + case Seq(j) => |
| 56 | + (s1.flags(j) == 0 || s2.flags(j) == 0) && { |
| 57 | + val f = (s1.flags(j) + s2.flags(j)).abs |
| 58 | + (f == 1 || f == 3) && (tile1.segs & tile2.segs).exists(s => s.flags(j).abs + f == 4) // i.e. diagonal and opposing diagonal |
| 59 | + } |
| 60 | + case _ => false |
| 61 | + } |
| 62 | + case _ => false |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + /** Expand this as necessary to allow multiple metarule definitions to resolve |
| 67 | + * to the same ID. |
| 68 | + */ |
| 69 | + def allowedConflict(idTile: IdTile, tile1: Tile, tile2: Tile): Boolean = { |
| 70 | + if (tile1.segs.exists(_.network.isTla) || tile2.segs.exists(_.network.isTla)) { |
| 71 | + true // TODO refine |
| 72 | + } else if (isSegmentContraction(tile1, tile2)) { |
| 73 | + true |
| 74 | + } else if (isDiagonalTAmbuigity(tile1, tile2)) { |
| 75 | + true |
| 76 | + } else if (idTile.id == 0x5D7E0800) { // L1Dtr->L2Dtr upper HT uses same ID as L0Dtr->L2Dtr |
| 77 | + true |
| 78 | + } else { |
| 79 | + false |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + def create(): ReverseResolver = { |
| 84 | + val maps = Seq( |
| 85 | + (new MiscResolver).tileMap, |
| 86 | + (new SamResolver).tileMap, |
| 87 | + (new RealRailwayResolver).tileMap, |
| 88 | + (new RhwResolver).tileMap, |
| 89 | + (new flexfly.FlexFlyResolver).tileMap, |
| 90 | + (new NwmResolver).tileMap, |
| 91 | + ) |
| 92 | + |
| 93 | + val reverseTileMap = collection.mutable.Map.empty[Int, ::[Tile]] |
| 94 | + val conflicts = collection.mutable.Buffer.empty[(IdTile, Tile, Tile)] |
| 95 | + maps.foreach(m => m.iterator.foreach { case (tile, idTile) => |
| 96 | + if (idTile.rf == R0F0) { // we only take and store R0F0 orientations, as tileMaps might not contain all of the other orientations |
| 97 | + val tiles = reverseTileMap.get(idTile.id).getOrElse(Nil) |
| 98 | + if (!tiles.contains(tile)) { |
| 99 | + tiles match { |
| 100 | + case (t :: _) if !allowedConflict(idTile, tile, t) => |
| 101 | + conflicts += ((idTile, tile, t)) |
| 102 | + case _ => |
| 103 | + reverseTileMap(idTile.id) = ::(tile, tiles) |
| 104 | + } |
| 105 | + } // else nothing to do |
| 106 | + } |
| 107 | + }) |
| 108 | + |
| 109 | + if (conflicts.nonEmpty) { |
| 110 | + for ((idTile, tile1, tile2) <- conflicts) { |
| 111 | + if (!allowedConflict(idTile, tile1, tile2)) { |
| 112 | + println(s"Conflicting metarule-resolution for ID $idTile: $tile1 vs $tile2") |
| 113 | + } |
| 114 | + } |
| 115 | + throw new AssertionError(s"There were ${conflicts.length} conflicting metarule-resolutions for IDs.") |
| 116 | + } |
| 117 | + new ReverseResolver(reverseTileMap) |
| 118 | + } |
| 119 | +} |
0 commit comments