Created
May 2, 2023 22:43
-
-
Save sagoez/73a0e98647759d011b34da42375165ca to your computer and use it in GitHub Desktop.
Super convoluted NoughtsDeterminer in Scala, but cooler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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