Learning Scala
I'm trying to learn Scala and I'm interested in criticism about how to make my code more idiomatic. I've programmed a simple Connect-four game. Can I get some feedback?
https://github.com/jamiely/connect-four-scala
The two most important classes are Board.scala and Game.scala. They are shown below for your convenience as well.
Board.scala
package connect_four
import connect_four._
class Index(val row: Int, val col: Int) {
def tuple(): (Int, Int) = (row, col)
}
class Size(val width: Int = 7, val height: Int = 6)
class Board(val size: Size = new Size()) {
val length = size.width * size.height
val board = (for (_ <- 0 to length-1) yield Markers.Empty).toArray
// Return a list of index objects
def getPositionIndices() = {
for {
row <- 0 to size.height-1
col <- 0 to size.width-1
}
yield new Index(row, col)
}
def isEmpty = board.forall(x => x == Markers.Empty)
def isInBounds(index: Index) = {
val row = index.row
val col = index.col
row >= 0 && row < size.height && col >= 0 && col < size.width
}
def fromIndex(index: Index): Option[Int] = {
if(isInBounds(index)) {
Some(index.row * size.width + index.col)
}
else None
}
def move(marker: Markers.Marker, index: Index): Boolean = {
val pos: Option[Int] = for {
pos <- fromIndex(index)
_ <- Some(updatePosition(marker, pos))
} yield pos
!pos.isEmpty
}
// Updates the given position without performing a check.
// @returns Returns a pair of the marker that was put at the position and the position.
def updatePosition(marker: Markers.Marker, position: Int) = {
board(position) = marker
(marker, position)
}
def markerAt(index: Index): Option[Markers.Marker] = {
for {
pos <- fromIndex(index)
m <- Some(board(pos))
} yield m
}
def posIs(marker: Markers.Marker, index: Index) = {
val result: Option[Boolean] = for {
m <- markerAt(index)
// return the result of a check against the marker
result <- Some(m == marker)
} yield result
result.getOrElse(false)
}
def hasMovesLeft = board.exists(x => x == Markers.Empty)
}
Game.scala
package connect_four
import connect_four._
class Game {
val board = new Board
var currentMarker = Markers.A
// directions are deltas used to check board locations in the cardinal directions
val directions = (for {
i <- -1 to 1
j <- -1 to 1
if !(i==0 && j==0)
} yield (i, j)).toList
def checkPosition(index:Index, marker:Markers.Marker, delta: (Int, Int), steps: Int): Boolean = {
if(steps == 0) {
true
}
else if (board.isInBounds(index) && board.posIs(marker, index)) {
val newIndex = new Index(index.row + delta._1, index.col + delta._2)
checkPosition(newIndex, marker, delta, steps-1)
}
else {
false
}
}
def isWin:Boolean =
board.getPositionIndices().exists(testWinAtIndex)
def testWinAtIndex(index:Index): Boolean = {
board.markerAt(index) match {
case None => false
case Some(Markers.Empty) => false
case Some(m) => directions.exists(delta => checkPosition(index, m, delta, 4))
}
}
def getFirstEmptyRowInColumn(c: Int): Option[Int] = {
def helper(row: Int): Int = {
if (row < 0) {
row
} else {
val marker = markerAt(new Index(row, c))
if (marker == Some(Markers.Empty)) row else helper(row - 1)
}
}
if(!board.isInBounds(new Index(0, c))) {
None
}
else {
val r = helper(board.size.height-1)
if(r < 0) None else Some(r)
}
}
def toggleMarker(): Markers.Marker = {
currentMarker = if(currentMarker == Markers.A) Markers.B else Markers.A
currentMarker
}
def updateBoard(index: Index): Markers.Marker = {
board.move(currentMarker, index)
toggleMarker()
}
def move(mv: Move): Option[Markers.Marker] = {
if(isWin) {
None
}
else {
for {
row <- getFirstEmptyRowInColumn(mv.col)
result <- if(row >= 0) Some(updateBoard(new Index(row, mv.col))) else None
} yield result
}
}
def markerAt(index:Index): Option[Markers.Marker] = board.markerAt(index)
def getCurrentMarker: Markers.Marker = currentMarker
}