- import scala.collection.mutable.{Map => MMap}
- import scala.collection.mutable.{Set => MSet}
- object Wrapper {
- type Image = Array[Array[Char]]
- val lines = scala.io.Source.fromFile("input").getLines.toArray
- // Main source of data, the tile and it Flips.
- // Flips includes the rotation and what nots.
- val tileFlipsMaps = MMap[Int, Array[Image]]()
- // number of tiles 9 in small 144 in large
- val nT = lines.filter(_.contains("Tile")).size
- // size of board 3x3 small 12x12 big
- val nB = Math.sqrt(nT).toInt
- // Read image from start line
- def readImage(start: Int) = {
- val id = lines(start).replaceAll("\\D+","").toInt
- val cur: Image = Array.ofDim[Char](10,10)
- for(idx <- 0 until 10){
- cur(idx) = lines(start+1+idx).toCharArray
- }
- tileFlipsMaps(id) = expand(cur)
- }
- // Every image is 10 lines + Tile + blank
- (0 until nT).foreach(t => readImage(t*12))
- def debug(a: Image) = {println;a.map(_.mkString(" ")).foreach(println)}
- // Flips operations§
- def hFlip(m: Image) = {
- val res = m.map(_.clone)
- val M = m.length
- for(i<-0 until M){
- res(i)= m(M-i-1)
- }
- res
- }
- def vFlip(m: Image) = {
- val res = m.map(_.clone)
- val M = m.length
- for(i<-0 until M;j<-0 until M){
- res(i)(j)= m(i)(M-j-1)
- }
- res
- }
- def r1(m: Image) = {
- val res = m.map(_.clone)
- val M = m.length
- for(r <- 0 until M; c <- 0 until M){
- res(r)(c) = m(c)(M-r-1)
- }
- res
- }
- def r2(m:Image) = r1(r1(m))
- def r3(m:Image) = r1(r2(m))
- // Edges operations
- def top(m:Image) = m(0)
- def bot(m:Image) = m(9)
- def left(m:Image) = top(m.transpose)
- def right(m:Image) = bot(m.transpose)
- // Expand all possible flips and rotates of this image, we call it the Flips
- def expand(cur: Image) : Array[Image] = {
- Array(cur, r1(cur), r2(cur), r3(cur), vFlip(cur), hFlip(cur), cur.transpose )
- }
- // Which tile we pick for the board
- val tileBoard = Array.ofDim[Int](nB, nB)
- // Which incarnation
- val flipBoard = Array.ofDim[Int](nB, nB)
- var goodArrangement = false
- // Main result for part 1
- var cornersMultiplied = 0L
- // Tiles can't be reused
- val usedTiles = MSet[Int]()
- // Keep track proper arrangements for second part
- var foundTiles = Array.ofDim[Int](nB, nB)
- var foundFlips = Array.ofDim[Int](nB, nB)
- // Here is the search for the proper tile flips and arrangements.
- // Start from top left all the way to bottom right (linear)
- // Always look back to top and left, only proceed if match
- def rearrangeTiles(rc: Int): Unit = {
- if(!goodArrangement)
- if(rc == nT){
- cornersMultiplied = (tileBoard(0)(0).toLong * tileBoard(0)(nB-1).toLong * tileBoard(nB-1)(0).toLong * tileBoard(nB-1)(nB-1).toLong)
- println(s"Part 1: $cornersMultiplied")
- // Transpose^2 ==> deep clone, keep for part2
- foundTiles = tileBoard.transpose.transpose
- foundFlips = flipBoard.transpose.transpose
- goodArrangement = true
- } else {
- // Linear RC to rows and cols
- val r = rc / nB
- val c = rc % nB
- // Right most edge of the left or nothing
- val leftCheck = if(c > 0) right(tileFlipsMaps(tileBoard(r)(c-1))(flipBoard(r)(c-1))) else Array()
- // Bottom edge of the top or nothing
- val topCheck = if(r > 0) bot(tileFlipsMaps(tileBoard(r-1)(c))(flipBoard(r-1)(c))) else Array()
- // Make sure not to reuse tiles.
- val unusedTiles = tileFlipsMaps -- usedTiles
- unusedTiles.foreach { case (tile, flips) =>
- usedTiles += tile
- for(flip <- 0 until flips.size){
- val cur = tileFlipsMaps(tile)(flip)
- val curLeft = left(cur)
- val curTop = top(cur)
- if( (leftCheck.isEmpty || (leftCheck sameElements curLeft)) &&
- (topCheck.isEmpty || (topCheck sameElements curTop))) {
- tileBoard(r)(c) = tile
- flipBoard(r)(c) = flip
- rearrangeTiles(rc+1)
- }
- }
- usedTiles -= tile
- }
- }
- }
- val RC = nB*8
- val monster : Image = scala.io.Source.fromFile("monster").getLines.toArray.map(_.toCharArray)
- val mR = monster.size
- val mC = monster(0).size
- def countMonster(img : Image) = {
- var nMonster = 0
- for(r <- 0 to RC - mR; c <- 0 to RC-mC){
- var found = true
- for(rr <- 0 until mR;cc <- 0 until mC){
- if(monster(rr)(cc) == '#' && img(r+rr)(c+cc) != '#') found = false
- }
- if(found) {
- // Mark monster
- for(rr <- 0 until mR;cc <- 0 until mC){
- if(monster(rr)(cc) == '#') img(r+rr)(c+cc) = 'O'
- }
- nMonster += 1
- }
- }
- nMonster
- }
- def key(img: Image) = img.map(_.mkString(":")).mkString(".")
- val visited = MSet[String]()
- def searchMonster(img: Image): Unit = {
- val kImg = key(img)
- if(!visited.contains(kImg)){
- visited += kImg
- val nMonster = countMonster(img)
- if(nMonster > 0) {
- debug(img)
- println(s"$nMonster monsters found")
- // Monster are marked with O so we count the rest
- println(s"Part 2: ${img.flatten.filter(_ == '#').size}")
- }
- val next = expand(img).filter(i => !visited.contains(key(i)))
- next.foreach(searchMonster)
- }
- }
- def removeEdges(): Image = {
- val result = Array.ofDim[Char](nB*8, nB*8)
- for(t <- 0 until nB; u <- 0 until nB){
- val tile = tileFlipsMaps(foundTiles(t)(u))(foundFlips(t)(u))
- for(r <- 1 to 8; c<- 1 to 8)
- result(t*8 + r-1)(u*8 + c-1) = tile(r)(c)
- }
- result
- }
- def solve() = {
- rearrangeTiles(0)
- searchMonster(removeEdges())
- }
- }
- Wrapper.solve()