1
\$\begingroup\$

This is a follow-up from my previous review on this site. To be clear, the code is stand-alone, and does not depend on the previous review.

As before my goal is to come up with a design for a chess engine in Kotlin that hides implementation details, and that ensures that the implementation cannot accidentally become spaghetti.

Example client code

package chessapp

import chessapp.api.internal.createBoard
import chessapp.api.internal.createSearchEngine

fun main() {
    val board = createBoard("1R/1p w")
    val searchEngine = createSearchEngine()
    val move = searchEngine.findBestMove(board)
    println(move)
}

Client facing public API

package chessapp.api

enum class PieceColor { BLACK, WHITE }
enum class PieceType { PAWN, ROOK}
data class ColoredPiece(val pieceColor: PieceColor, val pieceType: PieceType)
interface Board {
    val pieces: List<ColoredPiece>
}
interface SearchEngine {
    fun findBestMove(board: Board): String
}

Intended to be clean and easy to understand, but bad for performance.

Inward facing API

package chessapp.api.internal

import chessapp.api.Board

internal const val BLACK_PAWN: Byte = (0x00 + 0x01).toByte()
internal const val WHITE_ROOK: Byte = (0x08 + 0x05).toByte()
internal interface InternalBoard: Board {
    val bytePieces: ByteArray
    fun doInternalMove(from: Int, to: Int)
}

Intended to be optimized for performance and potentially hard to understand.
Additionally, the interface is likely volatile and can change quickly, which should not affect the public API.

Implementation that supports both public and internal API

package chessapp.api.internal

import chessapp.api.Board
import chessapp.api.ColoredPiece
import chessapp.api.PieceColor
import chessapp.api.PieceType

fun createBoard(fen: String): Board = InternalBoardImpl(fen)
internal fun createInternalBoard(board: Board): InternalBoard = InternalBoardImpl(board as InternalBoardImpl)

internal class InternalBoardImpl(override val bytePieces: ByteArray) : InternalBoard {
    constructor(fen: String) : this(byteArrayOf(BLACK_PAWN, WHITE_ROOK)) {}
    constructor(rhs: InternalBoardImpl) : this(rhs.bytePieces.copyOf()) {}

    override val pieces: List<ColoredPiece>
        get() = bytePieces.map { it.toColoredPiece() }

    override fun doInternalMove(from: Int, to: Int) {}
}

internal fun Byte.toColoredPiece() = when (this) {
    BLACK_PAWN -> ColoredPiece(PieceColor.BLACK, PieceType.PAWN)
    WHITE_ROOK -> ColoredPiece(PieceColor.WHITE, PieceType.ROOK)
    else -> null
}!!

As you may notice, there is a hidden assumption that a Board will always be an InternalBoard, and even more, implemented as an InternalBoardImpl. As yet I haven't figured out how to do that better without sacrificing the separation of the public and internal interface. So please feel free to comment.

Search engine implementation that uses only the internal interface

package chessapp.api.internal

import chessapp.api.Board
import chessapp.api.SearchEngine

fun createSearchEngine(): SearchEngine = OptimizedSearchEngine()

internal class OptimizedSearchEngine: SearchEngine {
    override fun findBestMove(board: Board): String {
        // Intended to be badly readable code just because it is supposed to be optimized for speed
        return listOf(Pair(7, 8), Pair(7, 15)).map {
            val newInternalBoard = createInternalBoard(board)
            newInternalBoard.doInternalMove(it.first, it.second)
            Pair(evaluate(newInternalBoard), it)
        }.maxBy { it.first }?.second.toString()
    }
    private fun evaluate(internalBoard: InternalBoard) = 42
}

Final words

I'd appreciate any thoughts you may have how this example design code can be improved.

To be honest, I'm not pleased with the hidden assumption that a Board has to be an InternalBoard, which in turn has to be an InternalBoardImpl. It goes against the OO concept that an interface should be able to have different implementations.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.