From 1c0ddbb5b5d95c7546182f5799681a6923424f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Wed, 30 Oct 2019 14:37:57 +0100 Subject: [PATCH] Import streams handout --- .vscode/settings.json | 8 + build.sbt | 12 + grading-tests.jar | Bin 0 -> 78542 bytes project/MOOCSettings.scala | 25 ++ project/StudentTasks.scala | 323 ++++++++++++++++++ project/build.properties | 1 + project/buildSettings.sbt | 7 + project/plugins.sbt | 2 + src/main/scala/streams/Bloxorz.scala | 49 +++ src/main/scala/streams/GameDef.scala | 167 +++++++++ src/main/scala/streams/InfiniteTerrain.scala | 15 + src/main/scala/streams/Solver.scala | 85 +++++ .../scala/streams/StringParserTerrain.scala | 72 ++++ src/test/scala/streams/BloxorzSuite.scala | 74 ++++ student.sbt | 9 + 15 files changed, 849 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 build.sbt create mode 100644 grading-tests.jar create mode 100644 project/MOOCSettings.scala create mode 100644 project/StudentTasks.scala create mode 100644 project/build.properties create mode 100644 project/buildSettings.sbt create mode 100644 project/plugins.sbt create mode 100644 src/main/scala/streams/Bloxorz.scala create mode 100644 src/main/scala/streams/GameDef.scala create mode 100644 src/main/scala/streams/InfiniteTerrain.scala create mode 100644 src/main/scala/streams/Solver.scala create mode 100644 src/main/scala/streams/StringParserTerrain.scala create mode 100644 src/test/scala/streams/BloxorzSuite.scala create mode 100644 student.sbt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a35362b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "dotty": { + "trace": { + "remoteTracingUrl": "wss://lamppc36.epfl.ch/dotty-remote-tracer/upload/lsp.log", + "server": { "format": "JSON", "verbosity": "verbose" } + } + } +} diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..4b4906c --- /dev/null +++ b/build.sbt @@ -0,0 +1,12 @@ +course := "progfun2" +assignment := "streams" +name := course.value + "-" + assignment.value +testSuite := "streams.BloxorzSuite" + +scalaVersion := "0.19.0-RC1" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test +libraryDependencies += ("org.scalacheck" %% "scalacheck" % "1.14.2").withDottyCompat(scalaVersion.value) + +testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v", "-s") diff --git a/grading-tests.jar b/grading-tests.jar new file mode 100644 index 0000000000000000000000000000000000000000..d7da321249b9fe0ddf52ce67b2776148b1b193f6 GIT binary patch literal 78542 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 ID8Ntu0~2OWQ2+n{ literal 0 HcmV?d00001 diff --git a/project/MOOCSettings.scala b/project/MOOCSettings.scala new file mode 100644 index 0000000..ea30b36 --- /dev/null +++ b/project/MOOCSettings.scala @@ -0,0 +1,25 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val testSuite = SettingKey[String]("testSuite") + val options = SettingKey[Map[String, Map[String, String]]]("options") + } + + override def trigger = allRequirements + + override val projectSettings: Seq[Def.Setting[_]] = Seq( + parallelExecution in Test := false, + // Report test result after each test instead of waiting for every test to finish + logBuffered in Test := false + ) +} diff --git a/project/StudentTasks.scala b/project/StudentTasks.scala new file mode 100644 index 0000000..587ba85 --- /dev/null +++ b/project/StudentTasks.scala @@ -0,0 +1,323 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ + +// import scalaj.http._ +import java.io.{File, FileInputStream, IOException} +import java.nio.file.FileSystems +import org.apache.commons.codec.binary.Base64 +// import play.api.libs.json.{Json, JsObject, JsPath} +import scala.util.{Failure, Success, Try} + +import MOOCSettings.autoImport._ + +case class AssignmentInfo( + key: String, + itemId: String, + premiumItemId: Option[String], + partId: String +) + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + object autoImport { + val assignmentInfo = SettingKey[AssignmentInfo]("assignmentInfo") + + val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project") + val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources") + val packageSubmissionZip = TaskKey[File]("packageSubmissionZip") + val packageSubmission = inputKey[Unit]("package solution as an archive file") + val runGradingTests = taskKey[Unit]("run black-box tests used for final grading") + } + + + import autoImport._ + + override lazy val projectSettings = Seq( + packageSubmissionSetting, + // submitSetting, // FIXME: restore assignmentInfo setting on assignments + runGradingTestsSettings, + + fork := true, + connectInput in run := true, + outputStrategy := Some(StdoutOutput) + ) ++ packageSubmissionZipSettings + + lazy val runGradingTestsSettings = runGradingTests := { + val testSuiteJar = "grading-tests.jar" + if (!new File(testSuiteJar).exists) { + throw new MessageOnlyException(s"Could not find tests JarFile: $testSuiteJar") + } + + val classPath = s"${(Test / dependencyClasspath).value.map(_.data).mkString(File.pathSeparator)}${File.pathSeparator}$testSuiteJar" + val junitProcess = + Fork.java.fork( + ForkOptions(), + "-cp" :: classPath :: + "org.junit.runner.JUnitCore" :: + (Test / testSuite).value :: + Nil + ) + + // Wait for tests to complete. + junitProcess.exitValue() + } + + + /** ********************************************************** + * SUBMITTING A SOLUTION TO COURSERA + */ + + val packageSubmissionZipSettings = Seq( + packageSubmissionZip := { + val submission = crossTarget.value / "submission.zip" + val sources = (packageSourcesOnly in Compile).value + val binaries = (packageBinWithoutResources in Compile).value + IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission) + submission + }, + artifactClassifier in packageSourcesOnly := Some("sources"), + artifact in (Compile, packageBinWithoutResources) ~= (art => art.withName(art.name + "-without-resources")) + ) ++ + inConfig(Compile)( + Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++ + Defaults.packageTaskSettings(packageBinWithoutResources, Def.task { + val relativePaths = + (unmanagedResources in Compile).value.flatMap(Path.relativeTo((unmanagedResourceDirectories in Compile).value)(_)) + (mappings in (Compile, packageBin)).value.filterNot { case (_, path) => relativePaths.contains(path) } + }) + ) + + val maxSubmitFileSize = { + val mb = 1024 * 1024 + 10 * mb + } + + /** Check that the jar exists, isn't empty, isn't crazy big, and can be read + * If so, encode jar as base64 so we can send it to Coursera + */ + def prepareJar(jar: File, s: TaskStreams): String = { + val errPrefix = "Error submitting assignment jar: " + val fileLength = jar.length() + if (!jar.exists()) { + s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength == 0L) { + s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath) + failSubmit() + } else if (fileLength > maxSubmitFileSize) { + s.log.error(errPrefix + "jar archive is too big. Allowed size: " + + maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" + + jar.getAbsolutePath) + failSubmit() + } else { + val bytes = new Array[Byte](fileLength.toInt) + val sizeRead = try { + val is = new FileInputStream(jar) + val read = is.read(bytes) + is.close() + read + } catch { + case ex: IOException => + s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString) + failSubmit() + } + if (sizeRead != bytes.length) { + s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead) + failSubmit() + } else encodeBase64(bytes) + } + } + + /** Task to package solution to a given file path */ + lazy val packageSubmissionSetting = packageSubmission := { + val args: Seq[String] = Def.spaceDelimited("[path]").parsed + val s: TaskStreams = streams.value // for logging + val jar = (packageSubmissionZip in Compile).value + + val base64Jar = prepareJar(jar, s) + + val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath) + scala.tools.nsc.io.File(path).writeAll(base64Jar) + } + +/* + /** Task to submit a solution to coursera */ + val submit = inputKey[Unit]("submit solution to Coursera") + lazy val submitSetting = submit := { + val args: Seq[String] = Def.spaceDelimited("").parsed + val s: TaskStreams = streams.value // for logging + val jar = (packageSubmissionZip in Compile).value + + val assignmentDetails = assignmentInfo.value + val assignmentKey = assignmentDetails.key + val courseName = + course.value match { + case "capstone" => "scala-capstone" + case "bigdata" => "scala-spark-big-data" + case other => other + } + + val partId = assignmentDetails.partId + val itemId = assignmentDetails.itemId + val premiumItemId = assignmentDetails.premiumItemId + + val (email, secret) = args match { + case email :: secret :: Nil => + (email, secret) + case _ => + val inputErr = + s"""|Invalid input to `submit`. The required syntax for `submit` is: + |submit + | + |The submit token is NOT YOUR LOGIN PASSWORD. + |It can be obtained from the assignment page: + |https://www.coursera.org/learn/$courseName/programming/$itemId + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + """.stripMargin + s.log.error(inputErr) + failSubmit() + } + + val base64Jar = prepareJar(jar, s) + val json = + s"""|{ + | "assignmentKey":"$assignmentKey", + | "submitterEmail":"$email", + | "secret":"$secret", + | "parts":{ + | "$partId":{ + | "output":"$base64Jar" + | } + | } + |}""".stripMargin + + def postSubmission[T](data: String): Try[HttpResponse[String]] = { + val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1") + val hs = List( + ("Cache-Control", "no-cache"), + ("Content-Type", "application/json") + ) + s.log.info("Connecting to Coursera...") + val response = Try(http.postData(data) + .headers(hs) + .option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s + .asString) // kick off HTTP POST + response + } + + val connectMsg = + s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course + |Using: + |- email: $email + |- submit token: $secret""".stripMargin + s.log.info(connectMsg) + + def reportCourseraResponse(response: HttpResponse[String]): Unit = { + val code = response.code + val respBody = response.body + + /* Sample JSON response from Coursera + { + "message": "Invalid email or token.", + "details": { + "learnerMessage": "Invalid email or token." + } + } + */ + + // Success, Coursera responds with 2xx HTTP status code + if (response.is2xx) { + val successfulSubmitMsg = + s"""|Successfully connected to Coursera. (Status $code) + | + |Assignment submitted successfully! + | + |You can see how you scored by going to: + |https://www.coursera.org/learn/$courseName/programming/$itemId/ + |${ + premiumItemId.fold("") { id => + s"""or (for premium learners): + |https://www.coursera.org/learn/$courseName/programming/$id + """.stripMargin + } + } + |and clicking on "My Submission".""".stripMargin + s.log.info(successfulSubmitMsg) + } + + // Failure, Coursera responds with 4xx HTTP status code (client-side failure) + else if (response.is4xx) { + val result = Try(Json.parse(respBody)).toOption + val learnerMsg = result match { + case Some(resp: JsObject) => + (JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get + case Some(x) => // shouldn't happen + "Could not parse Coursera's response:\n" + x + case None => + "Could not parse Coursera's response:\n" + respBody + } + val failedSubmitMsg = + s"""|Submission failed. + |There was something wrong while attempting to submit. + |Coursera says: + |$learnerMsg (Status $code)""".stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera responds with 5xx HTTP status code (server-side failure) + else if (response.is5xx) { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera seems to be unavailable at the moment (Status $code) + |Check https://status.coursera.org/ and try again in a few minutes. + """.stripMargin + s.log.error(failedSubmitMsg) + } + + // Failure, Coursera repsonds with an unexpected status code + else { + val failedSubmitMsg = + s"""|Submission failed. + |Coursera replied with an unexpected code (Status $code) + """.stripMargin + s.log.error(failedSubmitMsg) + } + } + + // kick it all off, actually make request + postSubmission(json) match { + case Success(resp) => reportCourseraResponse(resp) + case Failure(e) => + val failedConnectMsg = + s"""|Connection to Coursera failed. + |There was something wrong while attempting to connect to Coursera. + |Check your internet connection. + |${e.toString}""".stripMargin + s.log.error(failedConnectMsg) + } + + } +*/ + + def failSubmit(): Nothing = { + sys.error("Submission failed") + } + + /** + * ***************** + * DEALING WITH JARS + */ + def encodeBase64(bytes: Array[Byte]): String = + new String(Base64.encodeBase64(bytes)) +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..c0bab04 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/project/buildSettings.sbt b/project/buildSettings.sbt new file mode 100644 index 0000000..f7847f8 --- /dev/null +++ b/project/buildSettings.sbt @@ -0,0 +1,7 @@ +// Used for base64 encoding +libraryDependencies += "commons-codec" % "commons-codec" % "1.10" + +// Used for Coursera submussion +// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.3.0" +// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.9" + diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..64a2492 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("io.get-coursier" % "sbt-coursier" % "2.0.0-RC3-5") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.3.4") diff --git a/src/main/scala/streams/Bloxorz.scala b/src/main/scala/streams/Bloxorz.scala new file mode 100644 index 0000000..86e3967 --- /dev/null +++ b/src/main/scala/streams/Bloxorz.scala @@ -0,0 +1,49 @@ +package streams + +/** + * A main object that can be used to execute the Bloxorz solver + */ +object Bloxorz extends App { + + /** + * A level constructed using the `InfiniteTerrain` trait which defines + * the terrain to be valid at every position. + */ + object InfiniteLevel extends Solver with InfiniteTerrain { + val startPos = Pos(1,3) + val goal = Pos(5,8) + } + + println(InfiniteLevel.solution) + + /** + * A simple level constructed using the StringParserTerrain + */ + abstract class Level extends Solver with StringParserTerrain + + object Level0 extends Level { + val level = + """------ + |--ST-- + |--oo-- + |--oo-- + |------""".stripMargin + } + + println(Level0.solution) + + /** + * Level 1 of the official Bloxorz game + */ + object Level1 extends Level { + val level = + """ooo------- + |oSoooo---- + |ooooooooo- + |-ooooooooo + |-----ooToo + |------ooo-""".stripMargin + } + + println(Level1.solution) +} diff --git a/src/main/scala/streams/GameDef.scala b/src/main/scala/streams/GameDef.scala new file mode 100644 index 0000000..4ae4eeb --- /dev/null +++ b/src/main/scala/streams/GameDef.scala @@ -0,0 +1,167 @@ +package streams + +/** + * This trait represents the layout and building blocks of the game + */ +trait GameDef { + + /** + * The case class `Pos` encodes positions in the terrain. + * + * IMPORTANT NOTE + * - The `row` coordinate denotes the position on the vertical axis + * - The `col` coordinate is used for the horizontal axis + * - The coordinates increase when moving down and right + * + * Illustration: + * + * 0 1 2 3 <- col axis + * 0 o o o o + * 1 o o o o + * 2 o # o o # is at position Pos(2, 1) + * 3 o o o o + * + * ^ + * | + * + * row axis + */ + case class Pos(row: Int, col: Int) { + /** The position obtained by changing the `row` coordinate by `d` */ + def deltaRow(d: Int): Pos = copy(row = row + d) + + /** The position obtained by changing the `col` coordinate by `d` */ + def deltaCol(d: Int): Pos = copy(col = col + d) + } + + /** + * The position where the block is located initially. + * + * This value is left abstract, it will be defined in concrete + * instances of the game. + */ + def startPos: Pos + + /** + * The target position where the block has to go. + * This value is left abstract. + */ + def goal: Pos + + /** + * The terrain is represented as a function from positions to + * booleans. The function returns `true` for every position that + * is inside the terrain. + * + * As explained in the documentation of class `Pos`, the `row` axis + * is the vertical one and increases from top to bottom. + */ + type Terrain = Pos => Boolean + + + /** + * The terrain of this game. This value is left abstract. + */ + def terrain: Terrain + + + /** + * In Bloxorz, we can move left, right, Up or down. + * These moves are encoded as case objects. + */ + sealed abstract class Move + case object Left extends Move + case object Right extends Move + case object Up extends Move + case object Down extends Move + + /** + * This function returns the block at the start position of + * the game. + */ + def startBlock: Block = ??? + + + /** + * A block is represented by the position of the two cubes that + * it consists of. We make sure that `b1` is lexicographically + * smaller than `b2`. + */ + case class Block(b1: Pos, b2: Pos) { + + // checks the requirement mentioned above + require(b1.row <= b2.row && b1.col <= b2.col, s"Invalid block position: b1=$b1, b2=$b2") + + /** + * Returns a block where the `row` coordinates of `b1` and `b2` are + * changed by `d1` and `d2`, respectively. + */ + def deltaRow(d1: Int, d2: Int) = Block(b1.deltaRow(d1), b2.deltaRow(d2)) + + /** + * Returns a block where the `col` coordinates of `b1` and `b2` are + * changed by `d1` and `d2`, respectively. + */ + def deltaCol(d1: Int, d2: Int) = Block(b1.deltaCol(d1), b2.deltaCol(d2)) + + + /** The block obtained by moving left */ + def left = + if isStanding then + deltaCol(-2, -1) + else if b1.row == b2.row then + deltaCol(-1, -2) + else + deltaCol(-1, -1) + + /** The block obtained by moving right */ + def right = + if isStanding then + deltaCol(1, 2) + else if b1.row == b2.row then + deltaCol(2, 1) + else + deltaCol(1, 1) + + /** The block obtained by moving up */ + def up = + if isStanding then + deltaRow(-2, -1) + else if b1.row == b2.row then + deltaRow(-1, -1) + else + deltaRow(-1, -2) + + /** The block obtained by moving down */ + def down = + if isStanding then + deltaRow(1, 2) + else if b1.row == b2.row then + deltaRow(1, 1) + else + deltaRow(2, 1) + + + /** + * Returns the list of blocks that can be obtained by moving + * the current block, together with the corresponding move. + */ + def neighbors: List[(Block, Move)] = ??? + + /** + * Returns the list of positions reachable from the current block + * which are inside the terrain. + */ + def legalNeighbors: List[(Block, Move)] = ??? + + /** + * Returns `true` if the block is standing. + */ + def isStanding: Boolean = ??? + + /** + * Returns `true` if the block is entirely inside the terrain. + */ + def isLegal: Boolean = ??? + } +} diff --git a/src/main/scala/streams/InfiniteTerrain.scala b/src/main/scala/streams/InfiniteTerrain.scala new file mode 100644 index 0000000..e43cc16 --- /dev/null +++ b/src/main/scala/streams/InfiniteTerrain.scala @@ -0,0 +1,15 @@ +package streams + +/** + * This trait defines an infinite terrain, where the block can + * go on any position. + * + * It keeps the `startPos` and the `goal` positions abstract. + * + * Using this trait is useful for testing. It can be used to find + * the shortest path between two positions without terrain + * restrictions. + */ +trait InfiniteTerrain extends GameDef { + val terrain: Terrain = (pos: Pos) => true +} diff --git a/src/main/scala/streams/Solver.scala b/src/main/scala/streams/Solver.scala new file mode 100644 index 0000000..0b65d86 --- /dev/null +++ b/src/main/scala/streams/Solver.scala @@ -0,0 +1,85 @@ +package streams + +/** + * This component implements the solver for the Bloxorz game + */ +trait Solver extends GameDef { + + /** + * Returns `true` if the block `b` is at the final position + */ + def done(b: Block): Boolean = ??? + + /** + * This function takes two arguments: the current block `b` and + * a list of moves `history` that was required to reach the + * position of `b`. + * + * The `head` element of the `history` list is the latest move + * that was executed, i.e. the last move that was performed for + * the block to end up at position `b`. + * + * The function returns a lazy list of pairs: the first element of + * the each pair is a neighboring block, and the second element + * is the augmented history of moves required to reach this block. + * + * It should only return valid neighbors, i.e. block positions + * that are inside the terrain. + */ + def neighborsWithHistory(b: Block, history: List[Move]): LazyList[(Block, List[Move])] = ??? + + /** + * This function returns the list of neighbors without the block + * positions that have already been explored. We will use it to + * make sure that we don't explore circular paths. + */ + def newNeighborsOnly(neighbors: LazyList[(Block, List[Move])], + explored: Set[Block]): LazyList[(Block, List[Move])] = ??? + + /** + * The function `from` returns the lazy list of all possible paths + * that can be followed, starting at the `head` of the `initial` + * lazy list. + * + * The blocks in the lazy list `initial` are sorted by ascending path + * length: the block positions with the shortest paths (length of + * move list) are at the head of the lazy list. + * + * The parameter `explored` is a set of block positions that have + * been visited before, on the path to any of the blocks in the + * lazy list `initial`. When search reaches a block that has already + * been explored before, that position should not be included a + * second time to avoid cycles. + * + * The resulting lazy list should be sorted by ascending path length, + * i.e. the block positions that can be reached with the fewest + * amount of moves should appear first in the lazy list. + * + * Note: the solution should not look at or compare the lengths + * of different paths - the implementation should naturally + * construct the correctly sorted lazy list. + */ + def from(initial: LazyList[(Block, List[Move])], + explored: Set[Block]): LazyList[(Block, List[Move])] = ??? + + /** + * The lazy list of all paths that begin at the starting block. + */ + lazy val pathsFromStart: LazyList[(Block, List[Move])] = ??? + + /** + * Returns a lazy list of all possible pairs of the goal block along + * with the history how it was reached. + */ + lazy val pathsToGoal: LazyList[(Block, List[Move])] = ??? + + /** + * The (or one of the) shortest sequence(s) of moves to reach the + * goal. If the goal cannot be reached, the empty list is returned. + * + * Note: the `head` element of the returned list should represent + * the first move that the player should perform from the starting + * position. + */ + lazy val solution: List[Move] = ??? +} diff --git a/src/main/scala/streams/StringParserTerrain.scala b/src/main/scala/streams/StringParserTerrain.scala new file mode 100644 index 0000000..12960c6 --- /dev/null +++ b/src/main/scala/streams/StringParserTerrain.scala @@ -0,0 +1,72 @@ +package streams + +/** + * This component implements a parser to define terrains from a + * graphical ASCII representation. + * + * When mixing in that component, a level can be defined by + * defining the field `level` in the following form: + * + * val level = + * """------ + * |--ST-- + * |--oo-- + * |--oo-- + * |------""".stripMargin + * + * - The `-` character denotes parts which are outside the terrain + * - `o` denotes fields which are part of the terrain + * - `S` denotes the start position of the block (which is also considered + inside the terrain) + * - `T` denotes the final position of the block (which is also considered + inside the terrain) + * + * In this example, the first and last lines could be omitted, and + * also the columns that consist of `-` characters only. + */ +trait StringParserTerrain extends GameDef { + + /** + * A ASCII representation of the terrain. This field should remain + * abstract here. + */ + val level: String + + /** + * This method returns terrain function that represents the terrain + * in `levelVector`. The vector contains parsed version of the `level` + * string. For example, the following level + * + * val level = + * """ST + * |oo + * |oo""".stripMargin + * + * is represented as + * + * Vector(Vector('S', 'T'), Vector('o', 'o'), Vector('o', 'o')) + * + * The resulting function should return `true` if the position `pos` is + * a valid position (not a '-' character) inside the terrain described + * by `levelVector`. + */ + def terrainFunction(levelVector: Vector[Vector[Char]]): Pos => Boolean = ??? + + /** + * This function should return the position of character `c` in the + * terrain described by `levelVector`. You can assume that the `c` + * appears exactly once in the terrain. + * + * Hint: you can use the functions `indexWhere` and / or `indexOf` of the + * `Vector` class + */ + def findChar(c: Char, levelVector: Vector[Vector[Char]]): Pos = ??? + + private lazy val vector: Vector[Vector[Char]] = + Vector(level.split("\r?\n").map(str => Vector(str: _*)).toIndexedSeq: _*) + + lazy val terrain: Terrain = terrainFunction(vector) + lazy val startPos: Pos = findChar('S', vector) + lazy val goal: Pos = findChar('T', vector) + +} diff --git a/src/test/scala/streams/BloxorzSuite.scala b/src/test/scala/streams/BloxorzSuite.scala new file mode 100644 index 0000000..bebf970 --- /dev/null +++ b/src/test/scala/streams/BloxorzSuite.scala @@ -0,0 +1,74 @@ +package streams + +import org.junit._ +import org.junit.Assert.assertEquals + +import Bloxorz._ + +class BloxorzSuite { + trait SolutionChecker extends GameDef with Solver with StringParserTerrain { + /** + * This method applies a list of moves `ls` to the block at position + * `startPos`. This can be used to verify if a certain list of moves + * is a valid solution, i.e. leads to the goal. + */ + def solve(ls: List[Move]): Block = + ls.foldLeft(startBlock) { case (block, move) => + require(block.isLegal) // The solution must always lead to legal blocks + move match + case Left => block.left + case Right => block.right + case Up => block.up + case Down => block.down + } + } + + trait Level1 extends SolutionChecker { + /* terrain for level 1*/ + + val level = + """ooo------- + |oSoooo---- + |ooooooooo- + |-ooooooooo + |-----ooToo + |------ooo-""".stripMargin + + val optsolution = List(Right, Right, Down, Right, Right, Right, Down) + } + + + @Test def `terrain function level 1 (10pts)`: Unit = + new Level1 { + assert(terrain(Pos(0,0)), "0,0") + assert(terrain(Pos(1,1)), "1,1") // start + assert(terrain(Pos(4,7)), "4,7") // goal + assert(terrain(Pos(5,8)), "5,8") + assert(!terrain(Pos(5,9)), "5,9") + assert(terrain(Pos(4,9)), "4,9") + assert(!terrain(Pos(6,8)), "6,8") + assert(!terrain(Pos(4,11)), "4,11") + assert(!terrain(Pos(-1,0)), "-1,0") + assert(!terrain(Pos(0,-1)), "0,-1") + } + + @Test def `find char level 1 (10pts)`: Unit = + new Level1 { + assertEquals(Pos(1, 1), startPos) + } + + + @Test def `optimal solution for level 1 (5pts)`: Unit = + new Level1 { + assertEquals(Block(goal, goal), solve(solution)) + } + + + @Test def `optimal solution length for level 1 (5pts)`: Unit = + new Level1 { + assertEquals(optsolution.length, solution.length) + } + + + @Rule def individualTestTimeout = new org.junit.rules.Timeout(10 * 1000) +} diff --git a/student.sbt b/student.sbt new file mode 100644 index 0000000..855fa0c --- /dev/null +++ b/student.sbt @@ -0,0 +1,9 @@ +// Used for base64 encoding +libraryDependencies += "commons-codec" % "commons-codec" % "1.10" + +// Used for Coursera submussion +// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" + +// Student tasks (i.e. packageSubmission) +enablePlugins(StudentTasks)