From 355ac8ec8e3a356c59289e335a08d6fadd5a7c2c Mon Sep 17 00:00:00 2001 From: John Long Date: Sat, 1 Jan 2022 20:45:20 -0800 Subject: [PATCH] Add Kotlin for Tic-Tac-Toe --- 89_Tic-Tac-Toe/kotlin/Board.kt | 80 ++++++++++++++++++++++++ 89_Tic-Tac-Toe/kotlin/README.md | 3 + 89_Tic-Tac-Toe/kotlin/TicTacToe2.kt | 96 +++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 89_Tic-Tac-Toe/kotlin/Board.kt create mode 100644 89_Tic-Tac-Toe/kotlin/README.md create mode 100644 89_Tic-Tac-Toe/kotlin/TicTacToe2.kt diff --git a/89_Tic-Tac-Toe/kotlin/Board.kt b/89_Tic-Tac-Toe/kotlin/Board.kt new file mode 100644 index 00000000..287a8d37 --- /dev/null +++ b/89_Tic-Tac-Toe/kotlin/Board.kt @@ -0,0 +1,80 @@ +/** + * @author John Long based on Java by Ollie Hensman-Crook + */ + +enum class Player(val char: Char) { + X('X'), + O('O') +} + +class Board { + // Initialize an array of size nine with all values set to null + private var boxes: Array = arrayOfNulls(9) + + /** + * Place 'X' or 'O' on the board position passed + * @param position + * @param player + */ + fun setArr(position: Int, player: Player) { + boxes[position - 1] = player + } + + fun printBoard() { + System.out.format( + """ + %c ! %c ! %c +----+----+---- + %c ! %c ! %c +----+----+---- + %c ! %c ! %c +""", + // converts each box to a char and then passes them in order to format + // If the person is unassigned, use a space ' ' + *(boxes.map{it?.char ?: ' '}.toTypedArray())) + } + + /** + * @param x + * @return the value of the char at a given position + */ + fun getBoardValue(x: Int): Player? { + return boxes[x - 1] + } + + private val winningCombos = listOf( + // horizontal + listOf(0,1,2), + listOf(3,4,5), + listOf(6,7,8), + // diagonal + listOf(0,4,8), + listOf(2,4,6), + // vertical + listOf(0,3,6), + listOf(1,4,7), + listOf(2,5,8) + ) + /** + * Go through the board and check for win + * @param player + * @return whether a win has occurred + */ + fun isWinFor(player: Player): Boolean { + // Check if any winningCombos have all their boxes set to player + return winningCombos.any{ combo -> + combo.all { boxes[it] == player } + } + } + + fun isDraw(): Boolean { + return !isWinFor(Player.X) && !isWinFor(Player.O) && boxes.all { it != null } + } + + /** + * Reset the board + */ + fun clear() { + boxes = arrayOfNulls(9) + } +} \ No newline at end of file diff --git a/89_Tic-Tac-Toe/kotlin/README.md b/89_Tic-Tac-Toe/kotlin/README.md new file mode 100644 index 00000000..f43a5b70 --- /dev/null +++ b/89_Tic-Tac-Toe/kotlin/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [Kotlin](https://kotlinlang.org/) diff --git a/89_Tic-Tac-Toe/kotlin/TicTacToe2.kt b/89_Tic-Tac-Toe/kotlin/TicTacToe2.kt new file mode 100644 index 00000000..389174f0 --- /dev/null +++ b/89_Tic-Tac-Toe/kotlin/TicTacToe2.kt @@ -0,0 +1,96 @@ +import java.util.Random +import kotlin.system.exitProcess + +/** + * @author John Long based on Java from Ollie Hensman-Crook + */ +private val compChoice = Random() +private val gameBoard = Board() + +fun main() { + println(" TIC-TAC-TOE") + println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + println("\nTHE BOARD IS NUMBERED: ") + println(" 1 2 3\n 4 5 6\n 7 8 9\n") + while (true) { + // Let the player choose whether to be X or O (Player.X or Player.O) + val (human, computer) = readXOrO() + while (true) { + // Get a valid move from the user and then move there + val validMoveIndex = readValidMove() + gameBoard.setArr(validMoveIndex, human) + gameBoard.printBoard() + + // Computer randomly fills a square (if the game isn't already over) + // This uses Kotlin's null handling and will only set the board + // if validRandomMove returned a non-null value + validRandomMove()?.let { + gameBoard.setArr(it, computer) + gameBoard.printBoard() + } + + // if there is a win print if player won or the computer won and ask if they + // want to play again + when { + gameBoard.isWinFor(human) -> { + checkPlayAgain("YOU WIN") + break + } + gameBoard.isWinFor(computer) -> { + checkPlayAgain("YOU LOSE") + break + } + gameBoard.isDraw() -> { + checkPlayAgain("DRAW") + break + } + } + } + } +} + +private fun checkPlayAgain(result: String) { + println("$result, PLAY AGAIN? (Y/N)") + gameBoard.clear() + if (!readYesOrNo()) exitProcess(0) +} + +private fun readYesOrNo(): Boolean { + while (true) { + when (readLine()?.get(0)?.uppercaseChar()) { + 'Y' -> return true + 'N' -> return false + else -> println("THAT'S NOT 'Y' OR 'N', TRY AGAIN") + } + } +} + +private fun validRandomMove(): Int? { + if (gameBoard.isDraw() || gameBoard.isWinFor(Player.O) || gameBoard.isWinFor(Player.X)) return null + println("THE COMPUTER MOVES TO") + // keep generating a random value until we find one that is null (unset) + return generateSequence { 1 + compChoice.nextInt(9) }.first { gameBoard.getBoardValue(it) == null } +} + +private fun readValidMove(): Int { + println("WHERE DO YOU MOVE") + while (true) { + val input = readln().toIntOrNull() + if (input != null && gameBoard.getBoardValue(input) == null) { + return input + } else { + println("INVALID INPUT, TRY AGAIN") + } + } +} + +private fun readXOrO(): Pair { + println("DO YOU WANT 'X' OR 'O'") + while (true) { + when (readln()[0].uppercaseChar()) { + 'X' -> return Player.X to Player.O + 'O' -> return Player.O to Player.X + else -> println("THAT'S NOT 'X' OR 'O', TRY AGAIN") + } + } +}