This is an old revision of the document!


Lab 6. Scala on steroids

5-Tic-Tac-Toe

Tic Tac Toe is usually played on a 3×3 board, marking positions by each player in rounds. Our game is slightly different:

  • it can be played on a square board of any size larger or equal to 5.
  • A player wins if it has marked a line, column or diagonal of 5 consecutive positions in a row.

Example of a winning position for X on a 5×5 board:

X...0
0X.0.
..X0.
...X.
.0..X

Example of a winning position for 0 on a 7×7 board:

.X...X.
...0...
...0...
.X.0..X
0..0..0
...0...
...X...

Encodings

  • In your project template, X is encoded as the first player (One), and 0, as Two.
trait Player {}
trait Player {}
case object One extends Player {
  override def toString: String = "X"
}
case object Two extends Player {
  override def toString: String = "0"
}
case object Empty extends Player {
  override def toString: String = "."
}
  • A Board is encoded as a List of Lists of positions (i.e. a matrix), where a position can be One, Two or Empty. We make no distinction in the code between a position and a player, although Empty cannot be seen as a valid player. This makes the code slightly easier to write.
class Board(b: List[List[Player]]) {
  override def toString: String = b.map(line => line.map(p => p.toString()).mkString).mkString("\n")
}

Tasks

6.1.1. Write a function which converts a string into a Board. As a helper, you can use _.split( c ) where c is a separator string, and _.toList. The best solution is to use a combination of map calls with the above mentioned functions. A string is encoded exactly as in the examples shown above:

  • there are no whitespaces - empty positions are marked by the character '.'
  • lines are delimited by '\n' (the last line does not have a trailing '\n').
def makeBoard(s: String): Board = {
    def toPos(c: Char): Player =
      c match {
        case 'X' => One
        case '0' => Two
        case _ => Empty
      }
    ???
}

6.1.2. Write a function which checks if a position on the board is free. Recall that list indexing can be done using l(_). Positions are numbered from 0.

def isFree(x:Int, y:Int, b:Board):Boolean = ???

6.1.3. Write a function which returns the opponent of a player:

def complement(p: Player): Player =

6.1.4. Write a function which converts a board to a string, following the same strategy. Important: this function will be used throughout the tests. Make sure string doesn't end with '\n'. Hint: instead of foldRight, you can use reduce which works quite similarly, but without requiring an accumulator.

def show(b: Board): String = ???

6.1.5. Write a function which returns the columns of a board:

def getColumns(b:Board): Board = ???

6.1.6. Implement the following two functions for extracting the first and second diagonal, as lines, from a board. Hint: use for comprehensions.

def getFstDiag(b:Board): Line = ???
def getSndDiag(b:Board): Line = ???

6.1.7. Implement the following functions for extracting diagonals above/below the first/second diagonal, as lines. It's not really necessary to make sure that at least 5 positions are available, for now. Hint: if one function must be implemented with element-by-element iteration, the three other can be implemented using each-other, as single-line calls.

def getAboveFstDiag(b: Board): List[Line] = ???
def getBelowFstDiag(b: Board): List[Line] = ???
def getAboveSndDiag(b: Board): List[Line] = ???
def getBelowSndDiag(b: Board): List[Line] = ???

6.1.8. Write a function which checks if a player is the winner. Hint: functions l.forall(_) and l.exists(_) may be very helpful, together with patterns.

def winner(p: Player)(b: Board): Boolean = 

6.1.9. Write a function which updates a position from the board, with a given player. The position need not be empty and you are not required to check this. Hint: re-use an inner aux-function together with take and drop.

def update(p: Player)(ln: Int, col: Int, b: Board) : Board = ??? 

6.1.10. Write a function which generates all possible next-moves for any of the two players. A next-move consists in a new board, where the player-at-hand played his move. The order in which you generate next-moves is not important.

def next(p: Player)(b: Board): List[Board] = ???

Testing

Use the following board configurations to test your solutions:

    todo