Skip to content

Commit ce30352

Browse files
committed
Merge branch 'staging' into owr4-avenue-based
2 parents 9f61d69 + e418b57 commit ce30352

4 files changed

Lines changed: 65 additions & 36 deletions

File tree

src/main/scala/module/Main.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ abstract class AbstractMain {
3030
}
3131
}
3232

33+
private lazy val shouldIgnoreMirroredOrientations: Set[Int] = MirrorVariants.ignoreMirroredOrientations(resolve)
34+
3335
def main(args: Array[String]): Unit = start()
3436

3537
/** Creates a generator with a new context, runs its start method and outputs the resulting RUL2 code to file. */
@@ -54,6 +56,14 @@ abstract class AbstractMain {
5456
printer.println(rule.toRul2String)
5557
}
5658
}
59+
// finally remove accumulated orientations that we want to ignore (like accidentally mirrored TLAs)
60+
tileOrientationCache.accum.mapValuesInPlace { (id, repr) =>
61+
if (shouldIgnoreMirroredOrientations(id)) repr.filterNot(_.flipped)
62+
else repr
63+
}
64+
tileOrientationCache.accum.filterInPlace { (id, repr) => // remove from accum if equal to cache (so that regenerateTileOrientationCache stabilizes eventually)
65+
!tileOrientationCache.cache.get(id).contains(repr)
66+
}
5767
}
5868
}
5969
}

src/main/scala/module/MirrorVariants.scala

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,43 @@ object MirrorVariants {
5454
case _ => tile
5555
}
5656

57-
private val tlaPreprocessor: Rule[SymTile] => Iterator[Rule[SymTile]] = rule => {
58-
if (!rule.exists(containsTlaFlags)) {
59-
Iterator(rule)
60-
} else if (rule.forall(shouldProjectTlaLeftOnly)) {
61-
Iterator(rule.map(projectTlaLeft))
62-
} else {
63-
Iterator(rule.map(projectTlaLeft), rule.map(projectTlaRight))
64-
}
57+
val preprocessor: Rule[SymTile] => Iterator[Rule[SymTile]] = rule => {
58+
val hasMirrorVariant = rule.exists(tile => mirrorVariants.contains(tile))
59+
for {
60+
rule <- if (hasMirrorVariant) Iterator( // yield the two projected rules
61+
rule.map(tile => mirrorVariants.get(tile).map(_._1).getOrElse(tile)),
62+
rule.map(tile => mirrorVariants.get(tile).map(_._2).getOrElse(tile)),
63+
)
64+
else Iterator(rule)
65+
hasTlaFlags = rule.exists(containsTlaFlags)
66+
rule <- if (!hasTlaFlags) Iterator(rule)
67+
else if (rule.forall(shouldProjectTlaLeftOnly)) Iterator(rule.map(projectTlaLeft))
68+
else Iterator(rule.map(projectTlaLeft), rule.map(projectTlaRight))
69+
// We duplicate the rule by its R2F1 variant if there are TLAs or mirror
70+
// variants involved, as the left/right distinction can make them resolve
71+
// to different IDs (and the generators do not necessarily account for
72+
// this). For example, this is needed for Tla5/Avenue D×D.
73+
rule <- if (hasMirrorVariant || hasTlaFlags) Iterator(rule, rule.map(_ * R2F1)).distinct
74+
else Iterator(rule)
75+
} yield rule
6576
}
6677

67-
val preprocessor: Rule[SymTile] => Iterator[Rule[SymTile]] = rule => {
68-
if (!rule.exists(tile => mirrorVariants.contains(tile))) {
69-
Iterator(rule)
70-
} else {
71-
Iterator( // yield the two projected rules
72-
rule.map(tile => mirrorVariants.get(tile).map(_._1).getOrElse(tile)),
73-
rule.map(tile => mirrorVariants.get(tile).map(_._2).getOrElse(tile)))
78+
/* For plain orthogonal and diagonal tiles of TLA networks, we ignore any
79+
* remapping defined in `tileOrientationCache` that leads to mirroring, as
80+
* that would amplify the mirroring problems due to the presence of turning
81+
* lanes. Not a perfect solution, as occasional mirroring problems will remain.
82+
*/
83+
def ignoreMirroredOrientations(resolve: IdResolver): Set[Int] = {
84+
val ids = Set.newBuilder[Int]
85+
for {
86+
n <- Seq(Tla3, Tla5, Tla7m, Road, Onewayroad) // TODO consider adding all the networks
87+
dir <- Seq(NS, ES, SE) // these are arbitrary orth/diag directions to allow us find the IDs
88+
tile <- Seq(NetworkProperties.projectTlaLeft(n~dir), NetworkProperties.projectTlaRight(n~dir))
89+
idTile <- resolve.lift(tile)
90+
} {
91+
ids += idTile.id
7492
}
75-
}.flatMap(tlaPreprocessor)
93+
ids.result()
94+
}
95+
7696
}

src/test/scala/meta/RuleTransducerSpec.scala

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import scala.collection.immutable.StringOps
44
import org.scalatest.wordspec.AnyWordSpec
55
import org.scalatest.matchers.should.Matchers
66
import com.sc4nam.module, module.syntax._, Implicits._
7+
import com.sc4nam.module.{NetworkProperties => NP}
78
import group._, RotFlip._, SymGroup._, Network._, Flags._
89
import RuleTransducer._
910

@@ -21,46 +22,44 @@ class RuleTransducerSpec extends AnyWordSpec with Matchers {
2122

2223
context.preprocess( Tla3~WE & Road~NS | (Road ~> Tla3)~WE ).toSeq should have size (1)
2324
val diag = context.preprocess( Tla3~WE & Road~WS | (Road ~> Tla3)~WE ).toSeq
24-
diag should have size (2)
25+
diag should have size (4)
2526
for (r <- diag) {
2627
createRules(r.map(_.toIdSymTile(resolver)), context.tileOrientationCache.cache, context.tileOrientationCache.accum).toSeq should have size (2)
2728
}
2829
}
2930
}
3031

31-
def makeTileLeft(t: Tile): Tile = Tile(t.segs map (s => if (!s.network.isTla) s else s.copy(flags = s.flags.spinLeft)))
32-
def makeTileRight(t: Tile): Tile = Tile(t.segs map (s => if (!s.network.isTla) s else s.copy(flags = s.flags.spinRight)))
3332
"Resolver" should {
3433
"resolve left/right-spinned TLAs correctly" in {
3534
val tiles = Seq[Tile]( Tla3~WE & Road~ES, Tla3~WE & Ard3~ES, Tla3~WE & Tla3~ES )
3635
val tile2 = Seq[Tile]( Tla3~WE, Tla3~WE & Road~NS, Tla3~WE & Ard3~NS, Tla3~WE & Tla3~NS )
3736
for (t <- tiles) {
38-
resolver(makeTileLeft(t)) should not be resolver(makeTileRight(t))
37+
resolver(NP.projectTlaLeft(t)) should not be resolver(NP.projectTlaRight(t))
3938
}
4039
for (t <- tile2) {
41-
resolver(makeTileLeft(t)) should be (resolver(makeTileRight(t)))
40+
resolver(NP.projectTlaLeft(t)) should be (resolver(NP.projectTlaRight(t)))
4241
}
4342
}
4443
"handle flipped left/right-spinned TLAs correctly" in {
4544
val (t1, t2) = ( Tla3~WE & Road~ES, Tla3~WE & Road~WS )
46-
resolver(makeTileLeft(t1)).id should not be (resolver(makeTileLeft(t2)).id)
45+
resolver(NP.projectTlaLeft(t1)).id should not be (resolver(NP.projectTlaLeft(t2)).id)
4746
for ((t, i) <- Seq(t1, t2).zipWithIndex) {
48-
makeTileLeft(t).toIdSymTile(resolver).repr.filter(_.flipped ^ (i!=0)) should be (Symbol("empty"))
49-
makeTileRight(t).toIdSymTile(resolver).repr.filter(!_.flipped ^ (i!=0)) should be (Symbol("empty"))
47+
NP.projectTlaLeft(t).toIdSymTile(resolver).repr.filter(_.flipped ^ (i!=0)) should be (Symbol("empty"))
48+
NP.projectTlaRight(t).toIdSymTile(resolver).repr.filter(!_.flipped ^ (i!=0)) should be (Symbol("empty"))
5049
}
5150
}
5251
"find RHS for TLA" in {
53-
val rule = (Tla3~WE | (Road ~> Tla3)~(2,0,11,0)) map makeTileLeft map (_.toIdSymTile(resolver))
52+
val rule = (Tla3~WE | (Road ~> Tla3)~(2,0,11,0)) map NP.projectTlaLeft map (_.toIdSymTile(resolver))
5453
possibleMapOrientation(Set(R0F0, R1F0), R3F0/R2F1, Quotient.Dih4, R1F1/R2F1) should not be (Symbol("empty"))
5554
createRules(rule, context.tileOrientationCache.cache, context.tileOrientationCache.accum)
5655
}
5756
"resolve diagonal TLA intersections" in {
58-
val t1 = makeTileLeft(Tla3~ES & Road~WS)
59-
val t2 = makeTileRight(Tla3~ES & Road~WS)
60-
val t3 = makeTileLeft(Tla3~WS & Road~ES)
57+
val t1 = NP.projectTlaLeft(Tla3~ES & Road~WS)
58+
val t2 = NP.projectTlaRight(Tla3~ES & Road~WS)
59+
val t3 = NP.projectTlaLeft(Tla3~WS & Road~ES)
6160
resolver(t3) * R0F1 should be (resolver(t2))
6261
resolver(t1).id should not be (resolver(t2).id)
63-
resolver(makeTileLeft(Tla3~ES & Tla3~WS)) should be (resolver(makeTileRight(Tla3~ES & Tla3~WS)))
62+
resolver(NP.projectTlaLeft(Tla3~ES & Tla3~WS)) should be (resolver(NP.projectTlaRight(Tla3~ES & Tla3~WS)))
6463
}
6564
}
6665
}

src/test/scala/module/MirrorVariantsSpec.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,29 +56,29 @@ class MirrorVariantsSpec extends AnyWordSpec with Matchers {
5656

5757
"produce expected results for TLA network crossings" in {
5858
implicit val context = RuleTransducer.Context(resolve, preprocess = MirrorVariants.preprocessor)
59-
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Rail~ES).toSeq shouldBe Seq(
59+
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Rail~ES).toSeq should contain theSameElementsAs Seq(
6060
Rule(0x51000000,1,0, 0x03010200,1,0, 0x51000000,1,0, 0x51005500,3,0),
6161
Rule(0x51000000,3,0, 0x03010200,1,0, 0x51000000,3,0, 0x51005500,3,0),
6262
Rule(0x51000000,3,0, 0x03020500,3,1, 0x51000000,3,0, 0x51005500,1,1),
6363
Rule(0x51000000,1,0, 0x03020500,3,1, 0x51000000,1,0, 0x51005500,1,1))
64-
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Rail~ES).toSeq shouldBe Seq(
64+
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Rail~ES).toSeq should contain theSameElementsAs Seq(
6565
Rule(0x51100000,3,0, 0x03010200,1,0, 0x51100000,3,0, 0x51105500,3,0),
6666
Rule(0x51100000,1,0, 0x03020500,3,1, 0x51100000,1,0, 0x51105500,1,1))
67-
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Road~ES).toSeq shouldBe Seq(
67+
RuleTransducer(Tla5~WE | (Road~>Tla5)~WE & Road~ES).toSeq should contain theSameElementsAs Seq(
6868
Rule(0x51100000,3,0, 0x00003900,1,0, 0x51100000,3,0, 0x51105100,3,0),
6969
Rule(0x51100000,1,0, 0x00003900,3,1, 0x51100000,1,0, 0x71105100,1,1)) // 0x71... variant
70-
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Lightrail~ES).toSeq shouldBe Seq(
70+
RuleTransducer(Tla3~WE | (Road~>Tla3)~WE & Lightrail~ES).toSeq should contain theSameElementsAs Seq(
7171
Rule(0x51000000,1,0, 0x08DD1600,1,1, 0x51000000,1,0, 0x51005600,3,0),
7272
Rule(0x51000000,3,0, 0x08DD1600,1,1, 0x51000000,3,0, 0x51005600,3,0),
7373
Rule(0x51000000,3,0, 0x08DD1600,3,0, 0x51000000,3,0, 0x51005600,1,1),
7474
Rule(0x51000000,1,0, 0x08DD1600,3,0, 0x51000000,1,0, 0x51005600,1,1))
75-
RuleTransducer(Tla5~EW | (Avenue~>Tla5)~EW & Avenue~NS).toSet shouldBe Set(
75+
RuleTransducer(Tla5~EW | (Avenue~>Tla5)~EW & Avenue~NS).toSeq should contain theSameElementsAs Seq(
7676
Rule(0x51100000,3,0, 0x04009000,0,0, 0x51100000,3,0, 0x71101300,2,1),
7777
Rule(0x51100000,1,0, 0x04009000,1,0, 0x51100000,1,0, 0x51101300,0,0))
78-
RuleTransducer(Tla5~EW & Avenue~NS | (Avenue~>Tla5)~EW & Avenue~SN).toSet shouldBe Set(
78+
RuleTransducer(Tla5~EW & Avenue~NS | (Avenue~>Tla5)~EW & Avenue~SN).toSeq should contain theSameElementsAs Seq(
7979
Rule(0x71101300,2,1, 0x04009000,3,0, 0x71101300,2,1, 0x51101300,2,0),
8080
Rule(0x51101300,0,0, 0x04009000,2,0, 0x51101300,0,0, 0x71101300,0,1))
81-
RuleTransducer(Tla5~EW & Avenue~SN | (Avenue~>Tla5)~EW).toSet shouldBe Set(
81+
RuleTransducer(Tla5~EW & Avenue~SN | (Avenue~>Tla5)~EW).toSeq should contain theSameElementsAs Seq(
8282
Rule(0x51101300,2,0, 0x04006100,3,0, 0x51101300,2,0, 0x51100000,3,0),
8383
Rule(0x71101300,0,1, 0x04006100,1,0, 0x71101300,0,1, 0x51100000,1,0))
8484
}

0 commit comments

Comments
 (0)