Last active
August 29, 2015 14:15
-
-
Save sebnozzi/29bb5d82fbc33adcb1cd to your computer and use it in GitHub Desktop.
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 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 '.' | |
} |
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 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 '.' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
!