I wrote a terminal-based TicTacToe game in Kotlin to practice both Kotlin and OOP. Any feedback on structure and code would be appreciated.
Board.kt
package main.com.github.me.tictactoe
class Board {
val originalBoardConfig = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9")
)
val board = originalBoardConfig.map { it.toMutableList() }.toMutableList()
var boardMap = hashMapOf<String, String>()
// initialises the boardMap
init {
resetBoardMap()
}
fun resetBoardMap() {
boardMap = hashMapOf(
"1" to board[0][0],
"2" to board[0][1],
"3" to board[0][2],
"4" to board[1][0],
"5" to board[1][1],
"6" to board[1][2],
"7" to board[2][0],
"8" to board[2][1],
"9" to board[2][2]
)
}
fun getBoardMap(): MutableMap<String, String> {
return boardMap
}
fun drawBoard() {
println("Current board:\n")
for (rowIdx in 0 until 3) {
println(" ${boardMap["${rowIdx * 3 + 1}"]} | ${boardMap["${rowIdx * 3 + 2}"]} | ${boardMap["${rowIdx * 3 + 3}"]}")
if (rowIdx < 2) {
println("-----------")
}
}
println()
}
}
Turns.kt
package main.com.github.me.tictactoe
import java.util.*
class Turns(val board: Board) {
val validPositions = listOf("1", "2", "3", "4", "5", "6", "7", "8", "9")
private fun validateAndPlaceSymbol(symbol: String, position: String): Boolean {
val boardMap = board.getBoardMap()
if (boardMap[position] in validPositions) {
boardMap[position] = symbol
return true
}
return false
}
fun makeAPlay(player: Int, symbol: String): String {
val scanner = Scanner(System.`in`)
while (true) {
board.drawBoard()
println("Player $player, place '$symbol' by selecting a number between 1 and 9 (or enter 'q' to quit): ")
val input = scanner.nextLine()
if (input == "q") {
println("Quitting the game.")
System.exit(0)
}
if (input.length != 1 || !input[0].isDigit()) {
println()
println("Invalid input. Please enter a single digit between 1 and 9.")
println()
continue
}
if (input in validPositions) {
if (validateAndPlaceSymbol(symbol, input)) {
return input
} else {
println()
println("Invalid position. The position has already been taken.")
println()
}
} else {
println("Invalid input. Please enter a digit between 1 and 9.")
}
}
}
fun switchPlayer(player: Int, symbol: String): Pair<Int, String> {
return when {
player == 1 && symbol == "X" -> {
Pair(2, "O")
}
else -> {
Pair(1, "X")
}
}
}
fun playAgain() {
println("Play again? (y/n)")
val scanner = Scanner(System.`in`)
while (true) {
val input = scanner.nextLine()
if (input == "n") {
println("Quitting the game.")
System.exit(0)
} else if (input == "y") {
board.resetBoardMap()
return
}
else {
println("Invalid input. Play again? (y/n)")
}
}
}
}
WinningConditions.kt
package main.com.github.me.tictactoe
class WinningConditions(val board: Board) {
private val winningCombinations = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7", "8", "9"),
listOf("1", "4", "7"),
listOf("2", "5", "8"),
listOf("3", "6", "9"),
listOf("1", "5", "9"),
listOf("3", "5", "7")
)
fun win(player: Int, symbol: String): Boolean {
val boardMap = board.getBoardMap()
for (combination in winningCombinations) {
if (combination.all { position -> boardMap[position] == symbol }) {
board.drawBoard()
println("Player $player WINS!")
return true
}
}
return false
}
}
main.kt
import main.com.github.me.tictactoe.Board
import main.com.github.me.tictactoe.Turns
import main.com.github.me.tictactoe.WinningConditions
fun main() {
println("Welcome to TicTacToe!")
val ticTacToeBoard = Board()
val turns = Turns(ticTacToeBoard)
val win = WinningConditions(ticTacToeBoard)
while (true) {
var player = 1
var symbol = "X"
var round = 0
while (round < 9) {
turns.makeAPlay(player, symbol)
if (win.win(player, symbol)) {
turns.playAgain()
break // Exit the current game loop if there's a winner
}
val playerSwitch = turns.switchPlayer(player, symbol)
player = playerSwitch.first
symbol = playerSwitch.second
round += 1
}
if (round == 9) {
ticTacToeBoard.drawBoard()
println("It's a draw.")
turns.playAgain()
}
}
}