/* * Skytils - Hypixel Skyblock Quality of Life Mod * Copyright (C) 2021 Skytils * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package skytils.skytilsmod.utils.tictactoe import java.lang.StringBuilder import java.util.HashSet /** * Represents the Tic Tac Toe board. * Modified version of LazoCoder's Tic-Tac-Toe Java Implementation, GPLv3 License * @link https://github.com/LazoCoder/Tic-Tac-Toe */ class Board internal constructor() { enum class State { Blank, X, O } private val board: Array> = Array(BOARD_WIDTH) { arrayOfNulls(BOARD_WIDTH) } /** * Check to see who's turn it is. * @return the player who's turn it is */ var turn: State = State.X /** * Check to see who won. * @return the player who won (or Blank if the game is a draw) */ var winner: State? = null get() { //check(isGameOver) { "TicTacToe is not over yet." } return field } /** * Get the indexes of all the positions on the board that are empty. * @return the empty cells */ var availableMoves: HashSet private set private var moveCount = 0 /** * Check to see if the game is over (if there is a winner or a draw). * @return true if the game is over */ var isGameOver = false private set var algorithmBestMove = -1 /** * Set the cells to be blank and load the available moves (all the moves are * available at the start of the game). */ private fun initialize() { for (row in 0 until BOARD_WIDTH) { for (col in 0 until BOARD_WIDTH) { board[row][col] = State.Blank } } availableMoves.clear() for (i in 0 until BOARD_WIDTH * BOARD_WIDTH) { availableMoves.add(i) } } /** * Restart the game with a new blank board. */ fun reset() { moveCount = 0 isGameOver = false turn = State.X winner = State.Blank initialize() } /** * Places an X or an O on the specified index depending on whose turn it is. * @param index the position on the board (example: index 4 is location (0, 1)) * @return true if the move has not already been played */ fun move(index: Int): Boolean { return move(index % BOARD_WIDTH, index / BOARD_WIDTH) } /** * Places an X or an O on the specified location depending on who turn it is. * @param x the x coordinate of the location * @param y the y coordinate of the location * @return true if the move has not already been played */ fun move(x: Int, y: Int): Boolean { check(!isGameOver) { "TicTacToe is over. No moves can be played." } if (board[y][x] == State.Blank) { board[y][x] = turn } else { return false } moveCount++ availableMoves.remove(y * BOARD_WIDTH + x) // The game is a draw. if (moveCount == BOARD_WIDTH * BOARD_WIDTH) { winner = State.Blank isGameOver = true } // Check for a winner. checkRow(y) checkColumn(x) checkDiagonalFromTopLeft(x, y) checkDiagonalFromTopRight(x, y) turn = if (turn == State.X) State.O else State.X return true } /** * Places an X or an O on the specified location based on a parameter * @param x the x coordinate of the location * @param y the y coordinate of the location * @param player whether or not an X or an O should be played * @return true if the move has not already been played */ fun place(x: Int, y: Int, player: State): Boolean { check(!isGameOver) { "TicTacToe is over. No moves can be played." } if (board[y][x] == State.Blank) { board[y][x] = player } else { return false } moveCount++ availableMoves.remove(y * BOARD_WIDTH + x) // The game is a draw. if (moveCount == BOARD_WIDTH * BOARD_WIDTH) { winner = State.Blank isGameOver = true } // Check for a winner. checkRow(y) checkColumn(x) checkDiagonalFromTopLeft(x, y) checkDiagonalFromTopRight(x, y) turn = if (turn == State.X) State.O else State.X return true } /** * Get a copy of the array that represents the board. * @return the board array */ fun toArray(): Array> { return board.clone() } /** * Checks the specified row to see if there is a winner. * @param row the row to check */ private fun checkRow(row: Int) { for (i in 1 until BOARD_WIDTH) { if (board[row][i] != board[row][i - 1]) { break } if (i == BOARD_WIDTH - 1) { winner = turn isGameOver = true } } } /** * Checks the specified column to see if there is a winner. * @param column the column to check */ private fun checkColumn(column: Int) { for (i in 1 until BOARD_WIDTH) { if (board[i][column] != board[i - 1][column]) { break } if (i == BOARD_WIDTH - 1) { winner = turn isGameOver = true } } } /** * Check the left diagonal to see if there is a winner. * @param x the x coordinate of the most recently played move * @param y the y coordinate of the most recently played move */ private fun checkDiagonalFromTopLeft(x: Int, y: Int) { if (x == y) { for (i in 1 until BOARD_WIDTH) { if (board[i][i] != board[i - 1][i - 1]) { break } if (i == BOARD_WIDTH - 1) { winner = turn isGameOver = true } } } } /** * Check the right diagonal to see if there is a winner. * @param x the x coordinate of the most recently played move * @param y the y coordinate of the most recently played move */ private fun checkDiagonalFromTopRight(x: Int, y: Int) { if (BOARD_WIDTH - 1 - x == y) { for (i in 1 until BOARD_WIDTH) { if (board[BOARD_WIDTH - 1 - i][i] != board[BOARD_WIDTH - i][i - 1]) { break } if (i == BOARD_WIDTH - 1) { winner = turn isGameOver = true } } } } /** * Get a deep copy of the Tic Tac Toe board. * @return an identical copy of the board */ val deepCopy: Board get() { val board = Board() for (i in board.board.indices) { board.board[i] = this.board[i].clone() } board.turn = turn board.winner = winner board.availableMoves = HashSet() board.availableMoves.addAll(availableMoves) board.moveCount = moveCount board.isGameOver = isGameOver return board } override fun toString(): String { val sb = StringBuilder() for (y in 0 until BOARD_WIDTH) { for (x in 0 until BOARD_WIDTH) { if (board[y][x] == State.Blank) { sb.append("-") } else { sb.append(board[y][x]!!.name) } sb.append(" ") } if (y != BOARD_WIDTH - 1) { sb.append("\n") } } return String(sb) } companion object { const val BOARD_WIDTH = 3 } /** * Construct the Tic Tac Toe board. */ init { availableMoves = HashSet() reset() } }