Created
November 14, 2013 00:48
-
-
Save zlqhem/7459312 to your computer and use it in GitHub Desktop.
7주차 숙제 @ Functional Programming Principles in Scala
https://class.coursera.org/progfun-003/assignment/view?assignment_id=19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package streams | |
import common._ | |
/** | |
* This trait represents the layout and building blocks of the game | |
* | |
* @TODO: SHOULD RENAME `x` and `y` in class Pos to `row` and `col`. It's | |
* confusing to have `x` being the vertical axis. | |
*/ | |
trait GameDef { | |
/** | |
* The case class `Pos` encodes positions in the terrain. | |
* | |
* IMPORTANT NOTE | |
* - The `x` coordinate denotes the position on the vertical axis | |
* - The `y` coordinate is used for the horizontal axis | |
* - The coordinates increase when moving down and right | |
* | |
* Illustration: | |
* | |
* 0 1 2 3 <- y 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 | |
* | |
* ^ | |
* | | |
* | |
* x axis | |
*/ | |
case class Pos(x: Int, y: Int) { | |
/** The position obtained by changing the `x` coordinate by `d` */ | |
def dx(d: Int) = copy(x = x + d) | |
/** The position obtained by changing the `y` coordinate by `d` */ | |
def dy(d: Int) = copy(y = y + d) | |
} | |
/** | |
* The position where the block is located initially. | |
* | |
* This value is left abstract, it will be defined in concrete | |
* instances of the game. | |
*/ | |
val startPos: Pos | |
/** | |
* The target position where the block has to go. | |
* This value is left abstract. | |
*/ | |
val 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 `x` 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. | |
*/ | |
val 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 = Block(startPos, startPos) | |
/** | |
* 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.x <= b2.x && b1.y <= b2.y, "Invalid block position: b1=" + b1 + ", b2=" + b2) | |
/** | |
* Returns a block where the `x` coordinates of `b1` and `b2` are | |
* changed by `d1` and `d2`, respectively. | |
*/ | |
def dx(d1: Int, d2: Int) = Block(b1.dx(d1), b2.dx(d2)) | |
/** | |
* Returns a block where the `y` coordinates of `b1` and `b2` are | |
* changed by `d1` and `d2`, respectively. | |
*/ | |
def dy(d1: Int, d2: Int) = Block(b1.dy(d1), b2.dy(d2)) | |
/** The block obtained by moving left */ | |
def left = if (isStanding) dy(-2, -1) | |
else if (b1.x == b2.x) dy(-1, -2) | |
else dy(-1, -1) | |
/** The block obtained by moving right */ | |
def right = if (isStanding) dy(1, 2) | |
else if (b1.x == b2.x) dy(2, 1) | |
else dy(1, 1) | |
/** The block obtained by moving up */ | |
def up = if (isStanding) dx(-2, -1) | |
else if (b1.x == b2.x) dx(-1, -1) | |
else dx(-1, -2) | |
/** The block obtained by moving down */ | |
def down = if (isStanding) dx(1, 2) | |
else if (b1.x == b2.x) dx(1, 1) | |
else dx(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)] = | |
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)] = | |
neighbors.filter({ case (block, _) => block.isLegal }) | |
/** | |
* Returns `true` if the block is standing. | |
*/ | |
def isStanding: Boolean = b1.x == b2.x && b1.y == b2.y | |
/** | |
* Returns `true` if the block is entirely inside the terrain. | |
*/ | |
def isLegal: Boolean = terrain(b1) && terrain(b2) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package streams | |
import common._ | |
/** | |
* 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 = b.b1 == goal && b.b2 == goal | |
/** | |
* 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 stream 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]): Stream[(Block, List[Move])] = | |
{ | |
b.legalNeighbors.map((x:(Block, Move)) => (x._1, x._2::history)) | |
}.toStream | |
/** | |
* 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: Stream[(Block, List[Move])], | |
explored: Set[Block]): Stream[(Block, List[Move])] = | |
neighbors.filter{ case (block, _) => !explored.contains(block) } | |
/** | |
* The function `from` returns the stream of all possible paths | |
* that can be followed, starting at the `head` of the `initial` | |
* stream. | |
* | |
* The blocks in the stream `initial` are sorted by ascending path | |
* length: the block positions with the shortest paths (length of | |
* move list) are at the head of the stream. | |
* | |
* The parameter `explored` is a set of block positions that have | |
* been visited before, on the path to any of the blocks in the | |
* stream `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 stream 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 stream. | |
* | |
* Note: the solution should not look at or compare the lengths | |
* of different paths - the implementation should naturally | |
* construct the correctly sorted stream. | |
*/ | |
def from(initial: Stream[(Block, List[Move])], | |
explored: Set[Block]): Stream[(Block, List[Move])] = { | |
if (initial == Stream.empty) { | |
Stream.empty | |
} | |
else { | |
val nextBlocks = initial.flatMap { | |
case (block, history) => | |
newNeighborsOnly(neighborsWithHistory(block, history), explored) | |
} | |
initial ++ from(nextBlocks, explored.union(nextBlocks.map(_._1).toSet)) | |
} | |
} | |
/** | |
* The stream of all paths that begin at the starting block. | |
*/ | |
lazy val pathsFromStart: Stream[(Block, List[Move])] = | |
from((startBlock, Nil) #:: Stream.empty, Set()) | |
/** | |
* 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])] = | |
pathsFromStart.filter { case (Block(b1, b2),_) => b1 == goal && b2 == goal } | |
/** | |
* 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] = if (pathsToGoal == Stream.empty) Nil | |
else pathsToGoal.head._2 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package streams | |
import common._ | |
/** | |
* 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 = { | |
def valid(x:Int, y:Int) = x >= 0 && y >= 0 && | |
x < levelVector.length && | |
y < levelVector(x).length | |
{ | |
case Pos(x,y) if valid(x,y) => levelVector(x)(y) != '-' | |
case _ => false | |
} | |
} | |
/** | |
* 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 = { | |
val indexVec = levelVector.map(row => row.indexOf(c)) | |
val x = indexVec.indexWhere(ch => ch != -1) | |
val y = indexVec(x) | |
Pos(x, y) | |
} | |
private lazy val vector: Vector[Vector[Char]] = | |
Vector(level.split("\n").map(str => Vector(str: _*)): _*) | |
lazy val terrain: Terrain = terrainFunction(vector) | |
lazy val startPos: Pos = findChar('S', vector) | |
lazy val goal: Pos = findChar('T', vector) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment