package ScalaGo import SimpleList.* object ScalaGo: /** * Type for specifying a stone's or field's position */ type Position = (Int, Int) /** * Color of a player or stone */ enum Color: case Black, White, Nobody import Color.* /** * FieldContent models the different fields the game board can have. */ enum FieldContent (color: Color): /** * A stone of a specific color. * * @param color color of the stone */ case Stone (color: Color) extends FieldContent (color: Color) /** * An empty field. * Has another color than Nobody if a stone was removed from this field in the last round; Referred to as RemovedStone in this case * * @param color color the stone belonged if a stone was removed from this field in the last round; Nobody if there was no stone */ case Empty (color: Color) extends FieldContent (color) import ScalaGo.FieldContent.* /** * The game state controls the progression of the game. * * The transitions array contains tuples of functions and target states. * If the function of a tuple evaluates to true the target state is activated. * * @param player who's turn it is * @param transitions array of a tuple of transition functions and next GameState * @param startAction function to execute when entering the state */ case class GameState(player: Color, transitions: Array[(String => Boolean, GameState)], startAction: () => Unit) /** * Chain of stones * * @param color color of the stones * @param position positions of the stones * @param liberties positions of the chain's liberties */ case class Chain(color: Color, position: SimpleList[Position], liberties: SimpleList[Position]) // Custom exceptions case class OutOfBoardException(msg: String) extends Exception case class InvalidBordsizeException(msg: String) extends Exception case class InvalidMoveException(msg: String = "This move is not possible!") extends Exception /** * Alias type for the game board */ type Goboard = Array[Array[FieldContent]] var board: Goboard = null // The current board var prevBoard: Goboard = null // The previous board (used for detecting repeating positions) var prevPrevBoard: Goboard = null // The board before the last board (used for detecting repeating positions) var gs: GameState = null // Starting GameState, this global variable is used throughout the implementation def main(args: Array[String]): Unit = val (start, end) = buildStartToEndStates() gs = start REPL.repl(end) /** * Generate all necessary states with transitions for the game * * @return a tuple of the start and the end states */ def buildStartToEndStates(): (GameState, GameState) = var startState = GameState(Nobody,new Array[(String => Boolean, GameState)](1), ()=> println("Start game!")) var blackPlayState = GameState(Black, new Array[(String => Boolean, GameState)](2), outputBoardAndPlayer()) var whitePlayState = GameState(White, new Array[(String => Boolean, GameState)](2), outputBoardAndPlayer()) var blackPlayWhitePassedState = GameState(Black, new Array[(String => Boolean, GameState)](2), outputBoardAndPlayer()) var whitePlayBlackPassedState = GameState(White, new Array[(String => Boolean, GameState)](2), outputBoardAndPlayer()) var calculateScoreState = GameState(Nobody, new Array[(String => Boolean, GameState)](1), calculateScore()) var endState = GameState(Nobody, Array.empty, {}) startState.transitions(0) = (buildBoardTransition(), blackPlayState) blackPlayState.transitions(0) = (placeStoneTransition(), whitePlayState) blackPlayState.transitions(1) = (passTransition(), whitePlayState) whitePlayState.transitions(0) = (placeStoneTransition(), blackPlayState) whitePlayState.transitions(1) = (passTransition(), blackPlayState) blackPlayWhitePassedState(0) = (placeStoneTransition(), whitePlayState) blackPlayWhitePassedState(1) = (passTransition(), calculateScoreState) whitePlayBlackPassedState(0) = (placeStoneTransition(), blackPlayState) whitePlayBlackPassedState(1) = (passTransition(), calculateScoreState) calculateScoreState.transitions(0) = ((_) => true, endState) return (startState, endState) /** * Output the current board an player and afterwards remove old RemovedStone markers */ def outputBoardAndPlayer(): Unit = REPL.printBoard(board, prevBoard) REPL.printCurrentPlayer() removeOldRemovedStones(board) /** * Remove all RemovedStone markers from the board * * @param b board to remove from */ def removeOldRemovedStones(b: Goboard): Unit = for i <- b.indices; j <- b.indices do { if b(i)(j) == Empty then Empty(Nobody) } /** * Remove stones of each chain's positions where the chain has no liberties * * @param chains list of chains * @param c only remove stones of this color * @param b board to remove the stones from */ def removeStonesOfZeroChains(chains: SimpleList[Chain], c: Color, b: Goboard): Unit = var chain = chains while chain != null do { if (chain.entry.isDefined && length(chain.entry.get.liberties) == 0 && chain.entry.get.color == c) { var positions = chain.entry.get.position while positions != null do { setPosition(b, positions.entry.get, Empty(c)) positions = positions.next } } chain = chain.next } /** * Remove all stones of chains with zero liberties starting with the opposing players color and then remove remaining * chains with zero liberties of the own color * * @param b board to remove the stones from */ def killChains(b: Goboard): Unit = var chain = findChains(b) updateLiberties(chain, b) removeStonesOfZeroChains(chain, if gs.player == Black then White else Black, b) updateLiberties(chain, b) removeStonesOfZeroChains(chain, gs.player, b) /** * Tests if two boards are identical with regard to only Empty and Stones. * * @param b1 board one * @param b2 board two * @return true if both boards represent equal positions, false otherwise */ def equalBoardPositions(b1: Goboard, b2: Goboard): Boolean = (b1(i)(j), b(i)(j)) match case (Stone(x), Stone(y)) if (x != y) => false case (Empty(_), Stone(_)) => false case (Stone(_), Empty(_)) => false case _ => /** * Create a real copy of the board * * @param b board to copy * @return an identical board at a different memory location */ def copyBoard(b: Goboard): Goboard = b.clone() /** * Set a position within a board. Thows an OutOfBoardException if the position is not within the board. * * @param b the board to place the field in * @param pos the position * @param fc the new content of the field at position */ def setPosition(b: Goboard, pos: Position, fc: FieldContent): Unit = if pos._1 < 0 || pos._1 >= b.length || pos._2 < 0 || pos._2 >= b.length then throw new OutOfBoardException("Nicht innerhalb des Spielfeldes") b(pos._1)(pos._2) = fc /** * Test if a field is only surrounded by stones of one color. This search may span multiple Empty fields. * * @param pos start position * @param c color to match * @param b board to search * @return true if the field is only surrounded by stones of the given color, false otherwise */ def isSurroundedByOnly(pos: Position, c: Color, b: Goboard): Boolean = ??? /** * Calculate the score according to the area covered by the players * */ def calculateScore(): Unit = removeOldRemovedStones(board) var stonesWhite = 0 var stonesBlack = 0 var fieldsWhite = 0 var fieldsBlack = 0 for (i <- board.indices; j <- board.indices) do { getFieldContent(i, j , board) match case Stone(White) => stonesWhite = stonesWhite + 1 case Stone(Black) => stonesBlack = stonesBlack + 1 case Empty(Nobody) => if (isSurroundedByOnly((i,j),Black,board)) then fieldsBlack = fieldsBlack + 1 else if (isSurroundedByOnly((i,j), White, board)) then fieldsWhite = fieldsWhite + 1 case _ => throw new Exception("Du hast verkackt beim programieren") } REPL.printFinalScore(stonesWhite, fieldsWhite, stonesBlack, fieldsBlack) /** * Generate a board of size*size dimensions. * * @param size the horizontal and vertical dimension of the board * @return true if a valid board could be printed, false otherwise */ def buildBoardTransition(size: String): Boolean = if () then true else false /** * Parse a player's input to detect a stone placement command and place a stone * * @param command input from the player * @return true if the command could be understood, false otherwise */ def placeStoneTransition(command: String): Boolean = ??? /** * Parse a player's input to detect the 'pass' command * * @param command input from the player * @return true if the command could be understood, false otherwise */ def passTransition(command: String): Boolean = if command.equals("pass") then return true else return false /** * Get the contents of a field by row,column indices. * * This is a convenience function that directly calls getFieldConten(p: Position, b: Goboard) * * @param row row index * @param column column index * @param b the board * @return the FieldContent which may be OutOfBounds */ def getFieldContent(row: Int, column: Int, b: Goboard): FieldContent = getFieldContent((row, column), b) /** * Get the contents of a field by Position. * * @param p the position * @param b the board * @return the FieldContent which may be OutOfBounds */ def getFieldContent(p: Position, b: Goboard): FieldContent = ??? /** * Used with the exploreChain function to add stones' position to the chain * * @param chain chain to extend * @param pos position to check * @param b board to use */ def addAdjacentStones(chain: Chain, pos: Position, b: Goboard): Unit = ??? /** * Explore all positions of a chain and apply passed function to all fields surrounding the chain's positions * * @param chain chain to explore * @param apply apply this function to all fields surrounding the chain's positions * @param b the board to use while exploring the chain */ def exploreChain(chain: Chain, apply: (Chain, Position, Goboard) => Unit, b: Goboard): Unit = ??? /** * Find all chains on the board * * @param b board to search for chains * @return list of chains */ def findChains(b: Goboard): SimpleList[Chain] = ??? /** * Used with the exploreChain function to add the positions of liberties to the chain * * @param chain chain to add the liberties to * @param pos position to search for a libertie * @param b board to use */ def addLiberties(chain: Chain, pos: Position, b: Goboard): Unit = ??? /** * Find all positions that are liberties along a chain * * @param chains chain to search for libertie positions * @param b board to use */ def updateLiberties(chains: SimpleList[Chain], b: Goboard): Unit = ??? /** * Check if the position is already a position of the chain * * @param pos position to check * @param chains chain to search * @return true if the position is already in the chain's position list */ def isPositionInChains(pos: Position, chains: SimpleList[Chain]): Boolean = ???