Skip to content

Instantly share code, notes, and snippets.

@samgj18
Created May 2, 2023 22:43
Show Gist options
  • Save samgj18/73a0e98647759d011b34da42375165ca to your computer and use it in GitHub Desktop.
Save samgj18/73a0e98647759d011b34da42375165ca to your computer and use it in GitHub Desktop.
Super convoluted NoughtsDeterminer in Scala, but cooler
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
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment