Skip to content

Instantly share code, notes, and snippets.

@sagoez
Created May 2, 2023 22:43
Show Gist options
  • Select an option

  • Save sagoez/73a0e98647759d011b34da42375165ca to your computer and use it in GitHub Desktop.

Select an option

Save sagoez/73a0e98647759d011b34da42375165ca to your computer and use it in GitHub Desktop.

Revisions

  1. sagoez revised this gist May 2, 2023. No changes.
  2. sagoez created this gist May 2, 2023.
    237 changes: 237 additions & 0 deletions NoughtsDeterminer.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,237 @@
    object Main extends App {
    // It is assumed that the board is always square, and that
    // there's only one possible move to win the game.

    import Board._
    import syntax._

    type Matrix = Array[Array[Board]]
    val board2: Matrix = Array(
    Array(X, O, Empty),
    Array(Empty, O, Empty),
    Array(O, X, Empty)
    )

    val board1: Matrix = Array(
    Array(X, O, X),
    Array(O, X, O),
    Array(O, Empty, Empty)
    )

    val board: Matrix = Array(
    Array(O, X, X),
    Array(X, O, O),
    Array(O, X, Empty)
    )

    // 1. First approach. Readable but not efficient and the notion
    // of the index is lost.
    def row = helper(board)
    def column = helper(board.transpose)
    def diagonal = helper(Array(board.diagonal))
    def rDiagonal = helper(Array(board.rDiagonal))

    private def helper(board: Array[Array[Board]]) = {
    (for {
    row <- board.indices
    column <- board(row).indices
    } yield {
    if (board(row).isValidX || board(row).isValidO) {
    board(row).get
    } else None
    }).toOption
    }

    println(row.orElse(column).orElse(diagonal).orElse(rDiagonal))

    // 2. Second approach. Less readable but more efficient.
    // The notion of the index is preserved.
    def rowOpt: Option[(Board, Int)] = helperOpt(board)

    val BoardLength = board.length

    // Here we could take advantage of the fact that we know the
    // board size ahead of time. However, it would be less generic.
    // Hence, we use the transpose method.
    def columnOpt: Option[(Board, Int)] = {
    def calculateIndex(length: Int, column: Int, row: Int) = {
    // This is the calculation of the index of a matrix
    // given that it can be represented as a vector.
    column * length + row
    }

    for {
    row <- board.indices
    column <- board(row).indices
    } yield {
    val index = calculateIndex(BoardLength, column, row)

    if (board(row).isValidXOpt || board(row).isValidOOpt) {
    board(row).get.map((_, index))
    } else None
    }
    }.toOptionOpt

    def diagonalOpt: Option[(Board, Int)] = {
    val diagonal = (for {
    i <- board.indices
    j <- board(i).indices
    if i == j
    } yield (board(i)(j), i * BoardLength + j)).toArray

    val r = diagonal.map(_._1)

    if (r.isValidXOpt || r.isValidOOpt) {
    val index = diagonal.map(_._2).last
    r.get.map((_, index))
    } else None
    }
    def rDiagonalOpt: Option[(Board, Int)] = {
    val rDiagonal: Array[(Board, Int)] =
    (
    for {
    i <- board.indices
    j <- board(i).indices
    if i + j == BoardLength - 1
    } yield {
    (board(i)(j), i * BoardLength + j)
    }
    ).toArray

    val r: Array[Board] = rDiagonal.map(_._1)

    if (r.isValidXOpt || r.isValidOOpt) {
    // The expression to calculate the index i * length + j
    val index = rDiagonal.map(_._2).last
    r.get.map((_, index))
    } else None
    }

    private def helperOpt(board: Array[Array[Board]]) = {
    (for {
    row <- board.indices
    column <- board(row).indices
    } yield {
    if (board(row).isValidOOpt || board(row).isValidXOpt) {
    board(row).getWithIndex
    } else None
    }).toOptionOpt
    }

    println(
    rowOpt
    .orElse {
    println("Trying column")
    columnOpt
    }
    .orElse {
    println("Trying diagonal")
    diagonalOpt
    }
    .orElse {
    println("Trying rDiagonal")
    rDiagonalOpt
    }
    )
    }

    sealed trait Board

    object Board {
    case object X extends Board
    case object O extends Board
    case object Empty extends Board

    final implicit class BoardOps(private val boardArr: Array[Board]) {
    def isValidX: Boolean = {
    // Check if all elements are different than O
    boardArr.forall(_ != O) && boardArr.count(_ == X) == 2
    }

    def isValidXOpt: Boolean = {
    // Count and check in one iteration
    var count = 0
    var isValid = true
    for (elem <- boardArr) {
    if (elem == X) count += 1
    else if (elem == O) isValid = false
    }
    count == 2 && isValid
    }

    def isValidO: Boolean = {
    // Check if all elements are different than X
    boardArr.forall(_ != X) && boardArr.count(_ == O) == 2
    }

    def isValidOOpt: Boolean = {
    // Count and check in one iteration
    var count = 0
    var isValid = true
    for (elem <- boardArr) {
    if (elem == O) count += 1
    else if (elem == X) isValid = false
    }
    count == 2 && isValid
    }

    def get: Option[Board] = {
    if (isValidX) Some(X)
    else if (isValidO) Some(O)
    else None
    }

    def getWithIndex: Option[(Board, Int)] = {
    if (isValidX) Some((X, boardArr.indexOf(Empty)))
    else if (isValidO) Some((O, boardArr.indexOf(Empty)))
    else None
    }
    }
    }

    object syntax {

    import scala.reflect.ClassTag
    final implicit class ArrayOptionOps[A: ClassTag](
    private val seqOption: IndexedSeq[Option[A]]
    ) {
    def toOption: Option[A] = {
    seqOption.flatten.toArray match {
    case arr if arr.nonEmpty => arr.headOption
    case _ => None
    }
    }

    def toOptionOpt: Option[A] = {
    seqOption.flatMap(identity).find(_ => true)
    }
    }

    final implicit class MatrixOps[A: ClassTag](
    private val matrix: Array[Array[A]]
    ) {

    /** Returns the main diagonal of the matrix
    */
    def diagonal: Array[A] = {
    val diagonal = for {
    row <- matrix.indices
    column <- matrix(row).indices
    if row == column
    } yield matrix(row)(column)
    diagonal.toArray
    }

    /** Returns the secondary diagonal of the matrix
    */
    def rDiagonal: Array[A] = {
    val rDiagonal = for {
    row <- matrix.indices
    column <- matrix(row).indices
    if row + column == matrix.length - 1
    } yield matrix(row)(column)
    rDiagonal.toArray
    }

    }
    }