diff --git a/a6-streams/src/main/scala/streams/GameDef.scala b/a6-streams/src/main/scala/streams/GameDef.scala index 22a679e..781dc3f 100644 --- a/a6-streams/src/main/scala/streams/GameDef.scala +++ b/a6-streams/src/main/scala/streams/GameDef.scala @@ -84,7 +84,7 @@ trait GameDef { * This function returns the block at the start position of * the game. */ - def startBlock: Block = ??? + def startBlock: Block = Block(startPos, startPos) /** * A block is represented by the position of the two cubes that @@ -134,23 +134,25 @@ trait GameDef { * Returns the list of blocks that can be obtained by moving * the current block, together with the corresponding move. */ - def neighbors: List[(Block, Move)] = ??? + def neighbors: List[(Block, Move)] = + List((left, Left), (right, Right), (up, Up), (down, Down)) /** * Returns the list of positions reachable from the current block * which are inside the terrain. */ - def legalNeighbors: List[(Block, Move)] = ??? + def legalNeighbors: List[(Block, Move)] = + neighbors filter (_._1.isLegal) /** * Returns `true` if the block is standing. */ - def isStanding: Boolean = ??? + def isStanding: Boolean = b1 == b2 /** * Returns `true` if the block is entirely inside the terrain. */ - def isLegal: Boolean = ??? + def isLegal: Boolean = terrain(b1) && terrain(b2) } } diff --git a/a6-streams/src/main/scala/streams/Solver.scala b/a6-streams/src/main/scala/streams/Solver.scala index d6aa237..942e6e2 100644 --- a/a6-streams/src/main/scala/streams/Solver.scala +++ b/a6-streams/src/main/scala/streams/Solver.scala @@ -10,7 +10,8 @@ trait Solver extends GameDef { /** * Returns `true` if the block `b` is at the final position */ - def done(b: Block): Boolean = ??? + def done(b: Block): Boolean = + b.isStanding && b.b1 == goal /** * This function takes two arguments: the current block `b` and @@ -28,7 +29,8 @@ trait Solver extends GameDef { * It should only return valid neighbors, i.e. block positions * that are inside the terrain. */ - def neighborsWithHistory(b: Block, history: List[Move]): Stream[(Block, List[Move])] = ??? + def neighborsWithHistory(b: Block, history: List[Move]): Stream[(Block, List[Move])] = + (b.legalNeighbors map (x => (x._1, x._2 :: history))).toStream /** * This function returns the list of neighbors without the block @@ -36,7 +38,8 @@ trait Solver extends GameDef { * make sure that we don't explore circular paths. */ def newNeighborsOnly(neighbors: Stream[(Block, List[Move])], - explored: Set[Block]): Stream[(Block, List[Move])] = ??? + explored: Set[Block]): Stream[(Block, List[Move])] = + neighbors filter (b => !(explored contains b._1)) /** * The function `from` returns the stream of all possible paths @@ -62,18 +65,41 @@ trait Solver extends GameDef { * construct the correctly sorted stream. */ def from(initial: Stream[(Block, List[Move])], - explored: Set[Block]): Stream[(Block, List[Move])] = ??? + explored: Set[Block]): Stream[(Block, List[Move])] = { + if (initial.isEmpty) initial + else { + // Depth-first search +// val (currentBlock, history) = initial.head +// val newExplored = currentBlock.legalNeighbors.map(_._1) +// val newNeighbors = newNeighborsOnly(neighborsWithHistory(currentBlock, history), explored) +// initial #::: from(newNeighbors, explored ++ newExplored) + + // Breadth-first search + val more: Stream[(Block, List[Move])] = + for { + (block, path) <- initial + (newBlock, newPath) <- newNeighborsOnly(neighborsWithHistory(block, path), explored) + if !(explored contains newBlock) + } yield (newBlock, newPath) + initial #::: from(more, explored ++ more.unzip._1) + } + } /** * The stream of all paths that begin at the starting block. */ - lazy val pathsFromStart: Stream[(Block, List[Move])] = ??? + lazy val pathsFromStart: Stream[(Block, List[Move])] = { + val initialStream = Stream((startBlock, List())) + val initialExplored: Set[Solver.this.type#Block] = Set(startBlock) + from(initialStream, initialExplored) + } /** * Returns a stream of all possible pairs of the goal block along * with the history how it was reached. */ - lazy val pathsToGoal: Stream[(Block, List[Move])] = ??? + lazy val pathsToGoal: Stream[(Block, List[Move])] = + pathsFromStart filter (x => done(x._1)) /** * The (or one of the) shortest sequence(s) of moves to reach the @@ -83,5 +109,7 @@ trait Solver extends GameDef { * the first move that the player should perform from the starting * position. */ - lazy val solution: List[Move] = ??? + lazy val solution: List[Move] = + if (pathsToGoal.isEmpty) Nil + else pathsToGoal.head._2 } diff --git a/a6-streams/src/main/scala/streams/StringParserTerrain.scala b/a6-streams/src/main/scala/streams/StringParserTerrain.scala index 12e9a8b..1fed8ab 100644 --- a/a6-streams/src/main/scala/streams/StringParserTerrain.scala +++ b/a6-streams/src/main/scala/streams/StringParserTerrain.scala @@ -52,7 +52,11 @@ trait StringParserTerrain extends GameDef { * a valid position (not a '-' character) inside the terrain described * by `levelVector`. */ - def terrainFunction(levelVector: Vector[Vector[Char]]): Pos => Boolean = ??? + def terrainFunction(levelVector: Vector[Vector[Char]]): Pos => Boolean = { case pos => + pos.y >= 0 && pos.x >= 0 && + pos.x < levelVector.size && pos.y < levelVector(pos.x).size && + levelVector(pos.x)(pos.y) != '-' + } /** * This function should return the position of character `c` in the @@ -62,7 +66,11 @@ trait StringParserTerrain extends GameDef { * Hint: you can use the functions `indexWhere` and / or `indexOf` of the * `Vector` class */ - def findChar(c: Char, levelVector: Vector[Vector[Char]]): Pos = ??? + def findChar(c: Char, levelVector: Vector[Vector[Char]]): Pos = { + val x = levelVector indexWhere (_.contains(c)) + val y = levelVector(x).indexOf(c) + Pos(x, y) + } private lazy val vector: Vector[Vector[Char]] = Vector(level.split("\n").map(str => Vector(str: _*)): _*) diff --git a/a6-streams/src/test/scala/streams/BloxorzSuite.scala b/a6-streams/src/test/scala/streams/BloxorzSuite.scala index 3f9329b..1cd9561 100644 --- a/a6-streams/src/test/scala/streams/BloxorzSuite.scala +++ b/a6-streams/src/test/scala/streams/BloxorzSuite.scala @@ -17,13 +17,14 @@ class BloxorzSuite extends FunSuite { * is a valid solution, i.e. leads to the goal. */ def solve(ls: List[Move]): Block = - ls.foldLeft(startBlock) { case (block, move) => move match { - case Left => block.left - case Right => block.right - case Up => block.up - case Down => block.down + ls.foldLeft(startBlock) { + case (block, move) => move match { + case Left => block.left + case Right => block.right + case Up => block.up + case Down => block.down + } } - } } trait Level1 extends SolutionChecker { @@ -42,14 +43,53 @@ class BloxorzSuite extends FunSuite { test("terrain function level 1") { new Level1 { - assert(terrain(Pos(0,0)), "0,0") - assert(!terrain(Pos(4,11)), "4,11") + assert(terrain(Pos(0, 0)), "0,0") + assert(!terrain(Pos(4, 11)), "4,11") } } test("findChar level 1") { new Level1 { - assert(startPos == Pos(1,1)) + assert(startPos == Pos(1, 1)) + } + } + + test("neighborsWithHistory") { + new Level1 { + val actual = neighborsWithHistory(Block(Pos(1, 1), Pos(1, 1)), List(Left, Up)).toSet + val expected = Set( + (Block(Pos(1, 2), Pos(1, 3)), List(Right, Left, Up)), + (Block(Pos(2, 1), Pos(3, 1)), List(Down, Left, Up))) + assert(actual == expected) + } + } + + test("newNeighborsOnly") { + new Level1 { + val actual: Stream[(Block, List[Move])] = newNeighborsOnly( + Set( + (Block(Pos(1, 2), Pos(1, 3)), List(Right, Left, Up)), + (Block(Pos(2, 1), Pos(3, 1)), List(Down, Left, Up))).toStream, + Set(Block(Pos(1, 2), Pos(1, 3)), Block(Pos(1, 1), Pos(1, 1)))) + + val expected: Set[(Block, List[Move])] = Set( + (Block(Pos(2, 1), Pos(3, 1)), List(Down, Left, Up))) + + assert(actual.toSet == expected) + } + } + + test("`pathsFromStart` are legal") { + new Level1 { + assert(pathsFromStart.take(30).toList.forall(_._1.isLegal)) + } + } + + test("`pathsFromStart` path lengths are increasing") { + new Level1 { + val lengthStream: Stream[Int] = pathsFromStart map (x => x._2.length) + val ascendingPairs: Stream[(Int, Int)] = lengthStream zip lengthStream.drop(1) + assert(ascendingPairs.take(30).toList.forall(x => x._1 <= x._2)) } } @@ -64,4 +104,15 @@ class BloxorzSuite extends FunSuite { assert(solution.length == optsolution.length) } } + + test("no solution") { + new SolutionChecker { + val level = + """---------- + |oSoTooo--- + |----------""".stripMargin + + assert(solution.isEmpty) + } + } }