4

我刚开始使用 Scala 并正在尝试一个小玩具程序 - 在本例中是基于文本的井字游戏。我根据我对 scala 的了解编写了一个工作版本,但注意到它主要是命令式的,而且我的类是可变的。

我正在经历并尝试实现一些功能性习语,并设法至少使代表游戏状态的类不可变。但是,我剩下一个类负责执行依赖于可变状态和命令式循环的游戏循环,如下所示:

  var board: TicTacToeBoard = new TicTacToeBoard

  def start() {
    var gameState: GameState = new XMovesNext
    outputState(gameState)
    while (!gameState.isGameFinished) {
      val position: Int = getSelectionFromUser
      board = board.updated(position, gameState.nextTurn)
      gameState = getGameState(board)
      outputState(gameState)      
    }
  }

在这个循环中对我正在做的事情进行编程的更惯用的方法是什么?

完整的源代码在这里https://github.com/whaley/TicTacToe-in-Scala/tree/master/src/main/scala/com/jasonwhaley/tictactoe

4

4 回答 4

7

恕我直言,对于 Scala,命令式循环很好。你总是可以编写一个递归函数来表现得像一个循环。我还加入了一些模式匹配。

def start() {
    def loop(board: TicTacToeBoard) = board.state match {
        case Finished => Unit
        case Unfinished(gameState) => {
             gameState.output()
             val position: Int = getSelectionFromUser()
             loop(board.updated(position))
        }
    }

    loop(new TicTacToeBoard)
}

假设我们有一个函数whileSome : (a -> Option[a]) a -> (),它运行输入函数直到其结果为无。那会去掉一些样板。

def start() {
    def step(board: TicTacToeBoard) = {
        board.gameState.output()
        val position: Int = getSelectionFromUser()
        board.updated(position) // returns either Some(nextBoard) or None
    }

    whileSome(step, new TicTacToeBoard)
}

whileSome写起来应该很简单;它只是前一种模式的抽象。我不确定它是否在任何常见的 Scala 库中,但在 Haskell 中你可以whileJust_monad-loops中获取。

于 2011-11-19T18:41:47.797 回答
5

您可以将其实现为递归方法。这是一个不相关的例子:

object Guesser extends App {
  val MIN = 1
  val MAX = 100

  readLine("Think of a number between 1 and 100. Press enter when ready")

  def guess(max: Int, min: Int) {
    val cur = (max + min) / 2
    readLine("Is the number "+cur+"? (y/n) ") match {
      case "y" => println("I thought so")
      case "n" => {
        def smallerGreater() { 
          readLine("Is it smaller or greater? (s/g) ") match {
            case "s" => guess(cur - 1, min)
            case "g" => guess(max, cur + 1)
            case _   => smallerGreater()
          }
        }
        smallerGreater()
      }
      case _   => {
        println("Huh?")
        guess(max, min)
      } 
    }
  }

  guess(MAX, MIN)
}
于 2011-11-19T17:57:55.357 回答
1

怎么样:

Stream.continually(processMove).takeWhile(!_.isGameFinished)

whereprocessMove是一个从用户那里获取选择、更新板并返回新状态的函数。

于 2011-11-19T16:41:53.657 回答
1

我会使用递归版本,但这是该Stream版本的正确实现:

var board: TicTacToeBoard = new TicTacToeBoard

def start() {
  def initialBoard: TicTacToeBoard = new TicTacToeBoard
  def initialGameState: GameState = new XMovesNext
  def gameIterator = Stream.iterate(initialBoard -> initialGameState) _
  def game: Stream[GameState] = {
    val (moves, end) = gameIterator {
      case (board, gameState) =>
        val position: Int = getSelectionFromUser
        val updatedBoard = board.updated(position, gameState.nextTurn)
        (updatedBoard, getGameState(board))
    }.span { case (_, gameState) => !gameState.isGameFinished }
    (moves ::: end.take(1)) map { case (_, gameState) => gameState }
  }
  game foreach outputState
}

这看起来比它应该的更奇怪。理想情况下,我会使用takeWhile, 然后再使用map它,但它不会起作用,因为最后一种情况会被忽略!

如果游戏的动作可以被丢弃,那么dropWhile接下来head就可以了。如果我有副作用 ( outputState) 而不是Stream,我可以走那条路,但是在 a 中产生副作用Streamvar带有while循环的 a 更糟糕。

所以,相反,我使用spanwhich 给了我两者takeWhiledropWhile但强迫我保存中间结果——如果内存是一个问题,这可能会很糟糕,因为整个游戏将保存在内存中,因为moves指向Stream. 所以我不得不将所有这些封装在另一个方法中,game. 这样,当我foreach通过 的结果时game,就不会有任何指向Stream's 的东西了head

另一种选择是摆脱您拥有的其他副作用:getSelectionFromUser. 您可以使用 摆脱它Iteratee,然后您可以保存最后一步并重新应用它。

或者...您可以为自己编写一个takeTo方法并使用它。

于 2011-11-19T21:13:01.070 回答