1. import scala.collection.mutable.{Map => MMap}
  2. import scala.collection.mutable.{Set => MSet}
  3. object Wrapper {
  4. type Image = Array[Array[Char]]
  5. val lines = scala.io.Source.fromFile("input").getLines.toArray
  6. // Main source of data, the tile and it Flips.
  7. // Flips includes the rotation and what nots.
  8. val tileFlipsMaps = MMap[Int, Array[Image]]()
  9. // number of tiles 9 in small 144 in large
  10. val nT = lines.filter(_.contains("Tile")).size
  11. // size of board 3x3 small 12x12 big
  12. val nB = Math.sqrt(nT).toInt
  13. // Read image from start line
  14. def readImage(start: Int) = {
  15. val id = lines(start).replaceAll("\\D+","").toInt
  16. val cur: Image = Array.ofDim[Char](10,10)
  17. for(idx <- 0 until 10){
  18. cur(idx) = lines(start+1+idx).toCharArray
  19. }
  20. tileFlipsMaps(id) = expand(cur)
  21. }
  22. // Every image is 10 lines + Tile + blank
  23. (0 until nT).foreach(t => readImage(t*12))
  24. def debug(a: Image) = {println;a.map(_.mkString(" ")).foreach(println)}
  25. // Flips operations§
  26. def hFlip(m: Image) = {
  27. val res = m.map(_.clone)
  28. val M = m.length
  29. for(i<-0 until M){
  30. res(i)= m(M-i-1)
  31. }
  32. res
  33. }
  34. def vFlip(m: Image) = {
  35. val res = m.map(_.clone)
  36. val M = m.length
  37. for(i<-0 until M;j<-0 until M){
  38. res(i)(j)= m(i)(M-j-1)
  39. }
  40. res
  41. }
  42. def r1(m: Image) = {
  43. val res = m.map(_.clone)
  44. val M = m.length
  45. for(r <- 0 until M; c <- 0 until M){
  46. res(r)(c) = m(c)(M-r-1)
  47. }
  48. res
  49. }
  50. def r2(m:Image) = r1(r1(m))
  51. def r3(m:Image) = r1(r2(m))
  52. // Edges operations
  53. def top(m:Image) = m(0)
  54. def bot(m:Image) = m(9)
  55. def left(m:Image) = top(m.transpose)
  56. def right(m:Image) = bot(m.transpose)
  57. // Expand all possible flips and rotates of this image, we call it the Flips
  58. def expand(cur: Image) : Array[Image] = {
  59. Array(cur, r1(cur), r2(cur), r3(cur), vFlip(cur), hFlip(cur), cur.transpose )
  60. }
  61. // Which tile we pick for the board
  62. val tileBoard = Array.ofDim[Int](nB, nB)
  63. // Which incarnation
  64. val flipBoard = Array.ofDim[Int](nB, nB)
  65. var goodArrangement = false
  66. // Main result for part 1
  67. var cornersMultiplied = 0L
  68. // Tiles can't be reused
  69. val usedTiles = MSet[Int]()
  70. // Keep track proper arrangements for second part
  71. var foundTiles = Array.ofDim[Int](nB, nB)
  72. var foundFlips = Array.ofDim[Int](nB, nB)
  73. // Here is the search for the proper tile flips and arrangements.
  74. // Start from top left all the way to bottom right (linear)
  75. // Always look back to top and left, only proceed if match
  76. def rearrangeTiles(rc: Int): Unit = {
  77. if(!goodArrangement)
  78. if(rc == nT){
  79. cornersMultiplied = (tileBoard(0)(0).toLong * tileBoard(0)(nB-1).toLong * tileBoard(nB-1)(0).toLong * tileBoard(nB-1)(nB-1).toLong)
  80. println(s"Part 1: $cornersMultiplied")
  81. // Transpose^2 ==> deep clone, keep for part2
  82. foundTiles = tileBoard.transpose.transpose
  83. foundFlips = flipBoard.transpose.transpose
  84. goodArrangement = true
  85. } else {
  86. // Linear RC to rows and cols
  87. val r = rc / nB
  88. val c = rc % nB
  89. // Right most edge of the left or nothing
  90. val leftCheck = if(c > 0) right(tileFlipsMaps(tileBoard(r)(c-1))(flipBoard(r)(c-1))) else Array()
  91. // Bottom edge of the top or nothing
  92. val topCheck = if(r > 0) bot(tileFlipsMaps(tileBoard(r-1)(c))(flipBoard(r-1)(c))) else Array()
  93. // Make sure not to reuse tiles.
  94. val unusedTiles = tileFlipsMaps -- usedTiles
  95. unusedTiles.foreach { case (tile, flips) =>
  96. usedTiles += tile
  97. for(flip <- 0 until flips.size){
  98. val cur = tileFlipsMaps(tile)(flip)
  99. val curLeft = left(cur)
  100. val curTop = top(cur)
  101. if( (leftCheck.isEmpty || (leftCheck sameElements curLeft)) &&
  102. (topCheck.isEmpty || (topCheck sameElements curTop))) {
  103. tileBoard(r)(c) = tile
  104. flipBoard(r)(c) = flip
  105. rearrangeTiles(rc+1)
  106. }
  107. }
  108. usedTiles -= tile
  109. }
  110. }
  111. }
  112. val RC = nB*8
  113. val monster : Image = scala.io.Source.fromFile("monster").getLines.toArray.map(_.toCharArray)
  114. val mR = monster.size
  115. val mC = monster(0).size
  116. def countMonster(img : Image) = {
  117. var nMonster = 0
  118. for(r <- 0 to RC - mR; c <- 0 to RC-mC){
  119. var found = true
  120. for(rr <- 0 until mR;cc <- 0 until mC){
  121. if(monster(rr)(cc) == '#' && img(r+rr)(c+cc) != '#') found = false
  122. }
  123. if(found) {
  124. // Mark monster
  125. for(rr <- 0 until mR;cc <- 0 until mC){
  126. if(monster(rr)(cc) == '#') img(r+rr)(c+cc) = 'O'
  127. }
  128. nMonster += 1
  129. }
  130. }
  131. nMonster
  132. }
  133. def key(img: Image) = img.map(_.mkString(":")).mkString(".")
  134. val visited = MSet[String]()
  135. def searchMonster(img: Image): Unit = {
  136. val kImg = key(img)
  137. if(!visited.contains(kImg)){
  138. visited += kImg
  139. val nMonster = countMonster(img)
  140. if(nMonster > 0) {
  141. debug(img)
  142. println(s"$nMonster monsters found")
  143. // Monster are marked with O so we count the rest
  144. println(s"Part 2: ${img.flatten.filter(_ == '#').size}")
  145. }
  146. val next = expand(img).filter(i => !visited.contains(key(i)))
  147. next.foreach(searchMonster)
  148. }
  149. }
  150. def removeEdges(): Image = {
  151. val result = Array.ofDim[Char](nB*8, nB*8)
  152. for(t <- 0 until nB; u <- 0 until nB){
  153. val tile = tileFlipsMaps(foundTiles(t)(u))(foundFlips(t)(u))
  154. for(r <- 1 to 8; c<- 1 to 8)
  155. result(t*8 + r-1)(u*8 + c-1) = tile(r)(c)
  156. }
  157. result
  158. }
  159. def solve() = {
  160. rearrangeTiles(0)
  161. searchMonster(removeEdges())
  162. }
  163. }
  164. Wrapper.solve()