mardi 16 mars 2021

Improvement of Mancala Game

https://www.mastersofgames.com/rules/mancala-rules.htm I am trying to create a working Mancala Game in scala, Currently, I have 3 Classes, Board.scala, Mancala.scala and Player.scala

Here is a copy of my Board Class


/**
 * The board of an ongoing game of mancala
 * @param player1: The number of seeds in each of lower player's pods (left to right)
 * @param bank1: The number of seeds in the lower player's bank
 * @param player2: The number of seeds in each of upper player's pods (left to right, from that player's perspective)
 * @param bank2: The number of seeds in the upper player's bank
 * @return a new board after the move takes place
 */
class Board(val player1: Seq[Int], val bank1: Int, val player2: Seq[Int], val bank2: Int) {

  /** Create a new board from a player taking a turn */
  def move(player: Int, position: Int): Board = {
    if (player != 1 && player != 2) throw new IllegalArgumentException
    if (position < 0 || position > 5) throw new IllegalArgumentException

    // Inner function for depositing seeds along a side
    def progress(seeds: Int, position: Int, side: Seq[Int]) = {
      val dist = math.min(seeds, 6 - position)
      val newSide = side.zipWithIndex.map {
        case (a, b) => if (b >= position && b < position + dist) a + 1 else a
      }
      (seeds - dist, newSide)
    }
    // Inner function for depositing a seed in the bank
    def depositInBank(seeds: Int, bank: Int): (Int, Int) = {
      if (seeds > 0) (seeds - 1, bank + 1) else (seeds, bank)
    }

    // Redistribute the pieces. Board movement positions are always counted
    // from the left
    val (startSide, oppSide) =
    if (player == 1) (player1.toArray, player2.reverse.toArray)
    else (player2.reverse.toArray, player1.toArray)
    val start = if (player == 1) position else 5 - position
    val playerBank = if (player == 1) bank1 else bank2
    if (startSide(start) == 0) throw new IllegalStateException
    val seeds = startSide(start)
    startSide(start) = 0
    val pos = start + 1

    // First side pass
    val (seedsFirstPass, playerPodsFirstPass) = progress(seeds, pos, startSide)
    val (seedsFirstPassAndBank, playerBankFirstPass) = depositInBank(seedsFirstPass, playerBank)

    // Move around the other side of the board
    val (seedsSecondPass, oppSideSecondPass) = progress(seedsFirstPassAndBank, 0, oppSide)

    // Skip the opponent's bank, but keep going if we can
    val (seedsThirdPass, playerPodsThirdPass) = progress(seedsSecondPass, 0, playerPodsFirstPass)

    // Stop by the bank
    val (seedsThirdsPassAndBank, playerBankSecondPass) = depositInBank(seedsThirdPass, playerBankFirstPass)

    // Just to be safe
    val (seedsFourthPass, oppSideFourthPass) = progress(seedsThirdsPassAndBank, 0, oppSideSecondPass)

    // Make our assumption explicit
    assert(seedsFourthPass == 0, "Still seeds after fourth pass, did not account for that")

    // Construct the new board
    if (player == 1)
      new Board(playerPodsThirdPass.toVector, playerBankSecondPass, oppSideFourthPass.reverse.toVector, bank2)
    else
      new Board(oppSideFourthPass.toVector, bank1, playerPodsThirdPass.reverse.toVector, playerBankSecondPass)
  }

  /** Capture pieces from a mancala game
   *
   * Pieces from the capturing pod and the opposite pod (same position, other player) are all emptied and moved
   * to the capturing player's bank.
   *
   * @param player the side (1 or 2) doing the capturing
   * @param pos the position doing the capturing
   * @return a new mancala board after the capture
   */
  def capture(player: Int, pos: Int): Board = {
    assert(player1(pos) != 0 && player2(pos) != 0)
    val (newBank1, newBank2) =
      if (player == 1)
        (bank1 + player1(pos) + player2(pos), bank2)
      else
        (bank1, bank2 + player1(pos) + player2(pos))
    new Board(player1.updated(pos, 0), newBank1, player2.updated(pos, 0), newBank2)
  }

  /** Calculate the last pod that will be visited by a move
   *
   * @param player the number (1, 2) of the player making the move
   * @param pos the index of the move about to be made (always counting from the left)
   * @return a tuple (player, pos) that represents the players side and position the last seed will be deposited in.
   *         A position of 6 represents a player's bank.
   */
  def lastPosAfterMove(player: Int, pos: Int): (Int, Int) = {
    val seeds = (if (player == 1) player1 else player2)(pos)
    val distToBank = if (player == 1) 6 - pos else pos + 1

    // Could use a better approach
    if (player == 1)
      if (seeds < distToBank) (1, pos + seeds)
      else if (seeds == distToBank) (1, 6)
      else if (seeds <= distToBank + 6) (2, 6 - (seeds - distToBank))
      else if (seeds <= distToBank + 12) (1, seeds - distToBank - 7)
      else if (seeds == distToBank + 13) (1, 6)
      else if (seeds <= distToBank + 19) (2, 19 - (seeds - distToBank))
      else { assert(false); (0, 0) }
    else
      if (seeds < distToBank) (2, pos - seeds)
      else if (seeds == distToBank) (2, 6)
      else if (seeds <= distToBank + 6) (1, seeds - distToBank - 1)
      else if (seeds <= distToBank + 12) (2, 12 - (seeds - distToBank))
      else if (seeds == distToBank + 13) (2, 6)
      else if (seeds <= distToBank + 19) (1, seeds - distToBank - 14)
      else { assert(false); (0, 0) }
  }

  override def toString = {
    val p2 = player2.foldLeft("")((a, b) => a + StringContext("  ", "  ").f(b)).trim
    val p1 = player1.foldLeft("")((a, b) => a + StringContext("  ", "  ").f(b)).trim
    val layer = "-" * 32
    val space = " " * 28
    f"""${layer + " "}\n   $p2 \n$bank2 ${space} $bank1\n   $p1\n${layer}"""
  }
}

/**
 * Board companion object. Used as a factory to create a starting board easily
 */
object Board {
  /** Create an initial board */
  def apply() = {
    val boardSide = Seq(4,4,4,4,4,4)
    new Board(boardSide, 0, boardSide, 0)
  }
}

I am trying to improve the lastPosAfterMove() and the move() functions, to make them more compact and understandable for others and use a better approach

Aucun commentaire:

Enregistrer un commentaire