Game of Life with Scala and Functional Programming
You're doing a lot of indexing, which is efficient in an Array, but indicates that you're thinking in small steps. As you get to know the Scala Standard Library you start thinking in larger chunks because it offers many ways to process data collections all at once.

I'll start at the bottom and work my way up.

*prettyPrint() - Testing for value 0 means that the prettyPrint() method knows about the underlying representation. If you test for CELL_DEAD and/or CELL_ALIVE then prettyPrint() should still work even if the grid implementation changes.

Here I chose to turn each row into a String and then println() it.

def prettyPrint(board: Board): Unit =
  board.grid
       .map(_.map(c =&gt; if (c == CELL_DEAD) " - " else " * ").mkString)
       .foreach(println)


*random() - Most Scala collections offer many different "builder" methods on the companion object. Here I use fill() to populate a 2-dimensional Array.

def random(rows:Int = DEFAULT_ROWS, columns:Int = DEFAULT_COLUMNS): Board =
  Board(Array.fill(rows,columns)(Random.nextInt(2)))


*isValid() - The Standard Library doesn't offer many collection methods with early termination, but forall() is one of them. It stops after the first false encountered.

Here I use -2 as a bit-mask to test the value of all bits except for the lowest.

private def isValid(grid: Array[Array[Int]]): Boolean =
  grid.forall(_.forall(n =&gt; (n &amp; -2)==0))


*isJagged() - The exists() method is the compliment of forall(). It stops after the first true encountered.

private def isJagged(grid: Array[Array[Int]]): Boolean =
  grid.exists(_.length != grid.head.length)


*liveCount - Idiomatic Scala avoids mutable variables. In order to calculate the value of liveCount once, without any post-evaluation adjustments, we'll want a way to get all valid neighbor-cell indexes, also without any post-evaluation adjustments.

val liveCount = (for {
  x &lt;- (0 max i-1) to (i+1 min rows-1)
  y &lt;- (0 max j-1) to (j+1 min columns-1)
} yield grid(x)(y)).sum - cellValue


*evolve() - tabulate() is another one of those "builder" methods that appears to do everything you need, in this situation, all at once. In this case, because we're building a 2-D Array, tabulate() passes two arguments, the row index and the column index, to its lambda argument. And because the getNextCellState() method takes those same two arguments in the same order, they don't need to be explicitly specified.

def evolve(): Board = Board(Array.tabulate(rows,columns)(getNextCellState))


It's worth noting that you test if grid is null but you don't test to see if it's empty. Board.random(0,0) will throw a run-time error.