346 lines
12 KiB
Scala
346 lines
12 KiB
Scala
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 =
|
|
???
|