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 (usually called 5-in-a-row):
- 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
), and0
, asTwo
.
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 beOne
,Two
orEmpty
. We make no distinction in the code between a position and a player, althoughEmpty
cannot be seen as a valid player. This makes the code slightly easier to write.
type Line = List[Player] type BoardList = List[Line] case class Board(b: BoardList) { override def toString: String = ??? }
Tasks
The following functions have a given signature. However, it is up to the student to decide whether these will be methods of a class or just simple functions.
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):Boolean = ???
6.1.3. Write a function which returns the opponent of a player:
def complement(p: Player): Player = ???
6.1.4. We want to write a function which converts a board to a string, following the same strategy. Complete the toString
in the Board class. Hint: instead of foldRight
, you can use reduce
which works quite similarly, but without requiring an accumulator.
6.1.5. Write a function which returns the columns of a board:
def getColumns: 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(): Line = ??? def getSndDiag(): 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: List[Line] = ??? def getBelowFstDiag: List[Line] = ??? def getAboveSndDiag: List[Line] = ??? def getBelowSndDiag: 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): 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) : 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.
def next(p: Player): List[Board] = ???