Skip to content

Instantly share code, notes, and snippets.

@sebnozzi
Last active August 29, 2015 14:15
Show Gist options
  • Save sebnozzi/29bb5d82fbc33adcb1cd to your computer and use it in GitHub Desktop.
Save sebnozzi/29bb5d82fbc33adcb1cd to your computer and use it in GitHub Desktop.
package org.scalavienna.refactoringdojo.gameoflife
case class Pos(col: Int, row: Int) {
def +(other: Pos): Pos = Pos(col + other.col, row + other.row)
}
case class Board(liveCells: Seq[Pos], size: Int) {
def isLiveCell(p: Pos) = liveCells.contains(p)
def nextIteration = BoardCalculator.nextIteration(this)
override def toString = BoardFormatter.format(this)
}
object BoardCalculator {
def nextIteration(b: Board): Board =
Board(nextAliveCells(b), b.size)
private def nextAliveCells(b: Board) =
for {
row <- 1 to b.size
col <- 1 to b.size
p = Pos(col, row)
if (willBeAlive(p, b))
} yield p
private def willBeAlive(p: Pos, b: Board) = {
val liveAroundCell = countLiveAround(p, b)
val alive = b.isLiveCell(p)
liveAroundCell == 3 || (alive && liveAroundCell == 2)
}
private def countLiveAround(p: Pos, b: Board) =
neighbourDeltas
.map(deltaPos => p + deltaPos)
.count(b.isLiveCell)
private val neighbourDeltas =
for {
row <- -1 to 1
col <- -1 to 1
if !(row == 0 && col == 0)
} yield Pos(row, col)
}
object BoardParser {
def fromString(in: String): Board = {
val lines = in.split("\n").map(_.trim)
val liveCells: Seq[Pos] =
for {
(line, y) <- lines.zipWithIndex
(c, x) <- line.zipWithIndex
if (c == 'O')
} yield Pos(x + 1, y + 1)
Board(liveCells, size = lines.length)
}
}
object BoardFormatter {
def format(b: Board) =
((1 to b.size).map { row =>
(1 to b.size).map { col =>
cellChar(Pos(col, row), b)
}.mkString
}) mkString "\n"
private def cellChar(p: Pos, b: Board) = if (b.isLiveCell(p)) 'O' else '.'
}
package org.scalavienna.refactoringdojo.gameoflife
object Row {
def apply(value: Int) = new Row(value)
def range(r: Range) = r.map(v => Row(v))
val zero = new Row(0)
}
object Col {
def apply(value: Int) = new Col(value)
def range(r: Range) = r.map(v => Col(v))
val zero = new Col(0)
}
class Row(val value: Int) extends AnyVal {
def +(other: Row): Row = Row(this.value + other.value)
}
class Col(val value: Int) extends AnyVal {
def +(other: Col): Col = Col(this.value + other.value)
}
case class Pos(col: Col, row: Row) {
def +(other: Pos): Pos = Pos(col + other.col, row + other.row)
}
case class Board(liveCells: Seq[Pos], size: Int) {
def isLiveCell(p: Pos) = liveCells.contains(p)
def nextIteration = BoardCalculator.nextIteration(this)
override def toString = BoardFormatter.format(this)
}
object BoardCalculator {
def nextIteration(b: Board): Board =
Board(nextAliveCells(b), b.size)
private def nextAliveCells(b: Board) =
for {
row <- Row.range(1 to b.size)
col <- Col.range(1 to b.size)
p = Pos(col, row)
if (willBeAlive(p, b))
} yield p
private def willBeAlive(p: Pos, b: Board) = {
val liveAroundCell = countLiveAround(p, b)
val alive = b.isLiveCell(p)
liveAroundCell == 3 || (alive && liveAroundCell == 2)
}
private def countLiveAround(p: Pos, b: Board) =
neighbourDeltas
.map(deltaPos => p + deltaPos)
.count(b.isLiveCell)
private val neighbourDeltas =
for {
row <- Row.range(-1 to 1)
col <- Col.range(-1 to 1)
if !(row == Row.zero && col == Col.zero)
} yield Pos(col, row)
}
object BoardParser {
def fromString(in: String): Board = {
val lines = in.split("\n").map(_.trim)
val liveCells: Seq[Pos] =
for {
(line, y) <- lines.zipWithIndex
(c, x) <- line.zipWithIndex
if (c == 'O')
col = Col(x + 1)
row = Row(y + 1)
} yield Pos(col, row)
Board(liveCells, size = lines.length)
}
}
object BoardFormatter {
def format(b: Board) =
(Row.range(1 to b.size).map { row =>
Col.range(1 to b.size).map { col =>
cellChar(Pos(col, row), b)
}.mkString
}) mkString "\n"
private def cellChar(p: Pos, b: Board) = if (b.isLiveCell(p)) 'O' else '.'
}
@sebnozzi
Copy link
Author

The second variant is even more type-safe, introducing types for "rows" and "columns".

Might seem like an overkill, but actually it caught a (by chance harmless) bug in the first implementation: I had confused rows and columns in neighbourDeltas!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment