Created
December 15, 2013 13:13
-
-
Save akimboyko/7972910 to your computer and use it in GitHub Desktop.
Game of Life implemented on Scala during Global Day of Coderetreat 2013 at Kiev
This file contains 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 main.scala.com.coderetreat | |
object GameOfLife { | |
// data structure | |
class Cell(x: Int, y: Int) { | |
val posX = x | |
val posY = y | |
override def toString = "Cell: " + x + "," + y | |
override def equals(that: Any) = { | |
that match { | |
case t: Cell => t.posX == x && t.posY == y | |
case _ => false | |
} | |
} | |
override def hashCode = x.hashCode() + y.hashCode() | |
def isNeighbour(cell: Cell): Boolean = | |
!equals(cell) && Math.abs(posX - cell.posX) <= 1 && Math.abs(posY - cell.posY) <= 1 | |
} | |
// functions | |
def generate(cells: Set[Cell]): Set[Cell] = { | |
nextGenerationNeighbour(cells).filter(cell => { | |
val probability = neighboursCount(cell, cells) | |
probability == 3 || (probability == 2 && cells.contains(cell)) | |
}) | |
} | |
def neighboursCount(targetCell: Cell, cells: Set[Cell]): Int = { | |
cells.foldLeft(0)((neighbours, cell) => { | |
if(targetCell.isNeighbour(cell)) { | |
neighbours + 1 | |
} else { | |
neighbours | |
} | |
}) | |
} | |
def nextGenerationNeighbour(generation: Set[Cell]): Set[Cell] = { | |
val range = Set(-1, 0, +1) | |
val all = range.flatMap(x => range.map(y => (x, y))) | |
generation.flatMap(cell => all.map{ case (x, y) => new Cell(cell.posX + x, cell.posY + y) }) | |
} | |
def toPrintableForm(generation: Set[Cell]): Array[String] = { | |
def min(n1: Int, n2: Int) = Math.min(n1, n2) | |
def max(n1: Int, n2: Int) = Math.max(n1, n2) | |
def abs(n1: Int) = Math.abs(n1) | |
val dimensions = | |
generation.foldLeft(None: Option[(Int, Int, Int, Int)])((dimensions, cell) => { | |
dimensions match { | |
case Some((minX, minY, maxX, maxY)) => | |
Some( | |
min(cell.posX, minX), min(cell.posY, minY), | |
max(cell.posX, maxX), max(cell.posY, maxY)) | |
case None => | |
Some(cell.posX, cell.posY, cell.posX, cell.posY) | |
} | |
}) | |
val (mX, mY, width, height) = | |
dimensions match { | |
case Some((minX, minY, maxX, maxY)) => | |
(minX, minY, abs(maxX - minX) + 1, abs(maxY - minY) + 1) | |
case None => (0, 0, 1, 1) | |
} | |
val printable = | |
(0 until height).map(_ => (0 until width).map(_ => ' ').toArray).toArray | |
generation.foreach(cell => | |
printable(cell.posY - mY)(cell.posX - mX) = '#' | |
) | |
printable.map(chars => new String(chars)) | |
} | |
def main(args: Array[String]) { | |
val r = new scala.util.Random | |
var generation = (0 until 100).map(_ => new Cell(r.nextInt(20), r.nextInt(20))).toSet | |
while(!generation.isEmpty) { | |
toPrintableForm(generation).foreach(System.out.println) | |
System.out.println(new String((0 until 20).map(_ => '-').toArray)) | |
generation = generate(generation) | |
Thread.sleep(1000) | |
} | |
} | |
} |
This file contains 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 com.coderetreat | |
import org.junit.runner.RunWith | |
import org.scalatest.junit.JUnitRunner | |
import org.scalatest.matchers.ShouldMatchers | |
import org.scalatest.FunSuite | |
import main.scala.com.coderetreat.GameOfLife._ | |
@RunWith(classOf[JUnitRunner]) | |
class GameOfLifeSpec extends FunSuite with ShouldMatchers { | |
//test | |
test("Single cell is going to die on next generation") { | |
val cells = Set(new Cell(0, 0)) | |
val expected = Set[Cell]() | |
val actual = generate(cells) | |
assert(actual === expected) | |
} | |
test("Still life is not changing") { | |
val block = Set(new Cell(1, 1), new Cell(1, 2), new Cell(2, 1), new Cell(2, 2)) | |
val expected = Set(new Cell(1, 1), new Cell(1, 2), new Cell(2, 1), new Cell(2, 2)) | |
val actual = generate(block) | |
assert(actual === expected) | |
} | |
test("Oscillator is changing") { | |
val block = Set(new Cell(1, 2), new Cell(2, 2), new Cell(3, 2)) | |
val expected = Set(new Cell(2, 1), new Cell(2, 2), new Cell(2, 3)) | |
val actual = generate(block) | |
assert(actual === expected) | |
} | |
test("Single cell has no neighbours") { | |
val cell = new Cell(0, 0) | |
val cells = Set(cell) | |
val expected = 0 | |
val actual: Int = neighboursCount(cell, cells) | |
assert(actual === expected) | |
} | |
test("Corner of block has 3 neighbours") { | |
val cell = new Cell(1, 1) | |
val block = Set(cell, new Cell(1, 2), new Cell(2, 1), new Cell(2, 2)) | |
val expected = 3 | |
val actual: Int = neighboursCount(cell, block) | |
assert(actual === expected) | |
} | |
test("Distance block has no neighbours") { | |
val cell = new Cell(-1, -1) | |
val block = Set(new Cell(1, 1), new Cell(1, 2), new Cell(2, 1), new Cell(2, 2)) | |
val expected = 0 | |
val actual: Int = neighboursCount(cell, block) | |
assert(actual === expected) | |
} | |
test("Are neighbours neighbours") { | |
val cellA = new Cell(0, 0) | |
val cellB = new Cell(0, 1) | |
assert(cellA.isNeighbour(cellB) === true) | |
} | |
test("Is cell neighbour to itself") { | |
val cell = new Cell(0, 0) | |
assert(cell.isNeighbour(cell) === false) | |
} | |
test("Are distance cells not neighbour by X") { | |
val cellA = new Cell(0, 0) | |
val cellB = new Cell(2, 0) | |
assert(cellA.isNeighbour(cellB) === false) | |
} | |
test("Are distance cells not neighbour by Y") { | |
val cellA = new Cell(0, 0) | |
val cellB = new Cell(0, 2) | |
assert(cellA.isNeighbour(cellB) === false) | |
} | |
test("No next generation out of empty one") { | |
val prevGeneration = Set[Cell]() | |
val nextGeneration = Set[Cell]() | |
val actual: Set[Cell] = nextGenerationNeighbour(prevGeneration) | |
assert(actual === nextGeneration) | |
} | |
test("Get possible places for live") { | |
val prevGeneration = Set(new Cell(1, 1)) | |
val nextGeneration = Set( | |
new Cell(0, 0), new Cell(0, 1), new Cell(0, 2), | |
new Cell(1, 0), new Cell(1, 1), new Cell(1, 2), | |
new Cell(2, 0), new Cell(2, 1), new Cell(2, 2) | |
) | |
val actual: Set[Cell] = nextGenerationNeighbour(prevGeneration) | |
assert(actual === nextGeneration) | |
} | |
test("Get possible places for live out of block") { | |
val prevGeneration = Set(new Cell(1, 1), new Cell(1, 2), new Cell(2, 1), new Cell(2, 2)) | |
val nextGeneration = Set( | |
new Cell(0, 0), new Cell(1, 0), new Cell(2, 0), new Cell(3, 0), | |
new Cell(0, 1), new Cell(1, 1), new Cell(2, 1), new Cell(3, 1), | |
new Cell(0, 2), new Cell(1, 2), new Cell(2, 2), new Cell(3, 2), | |
new Cell(0, 3), new Cell(1, 3), new Cell(2, 3), new Cell(3, 3) | |
) | |
val actual: Set[Cell] = nextGenerationNeighbour(prevGeneration) | |
assert(actual === nextGeneration) | |
} | |
test("Convert empty generation to printable form") { | |
val generation = Set[Cell]() | |
val expectedForm = Array(" ") | |
val actual: Array[String] = toPrintableForm(generation) | |
assert(actual === expectedForm) | |
} | |
test("Convert single cell to printable form") { | |
val generation = Set(new Cell(0, 0)) | |
val expectedForm = Array("#") | |
val actual: Array[String] = toPrintableForm(generation) | |
assert(actual === expectedForm) | |
} | |
test("Convert vertical line to printable form") { | |
val generation = Set(new Cell(1, 1), new Cell(1, 2), new Cell(1, 3)) | |
val expectedForm = Array("#", "#", "#") | |
val actual: Array[String] = toPrintableForm(generation) | |
assert(actual === expectedForm) | |
} | |
test("Convert horizontal line to printable form") { | |
val generation = Set(new Cell(1, 1), new Cell(2, 1), new Cell(3, 1)) | |
val expectedForm = Array("###") | |
val actual: Array[String] = toPrintableForm(generation) | |
assert(actual === expectedForm) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment