我刚刚开始使用Scala,我正在尝试一个小玩具程序 - 在这种情况下是一个基于文本的TicTacToe。我根据我对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
答案 0 :(得分:7)
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 -> ()
,它运行输入函数,直到结果为None。那会剥掉一些样板。
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中,您可以从monad-loops获取whileJust_
。
答案 1 :(得分: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)
}
答案 2 :(得分:1)
如下:
Stream.continually(processMove).takeWhile(!_.isGameFinished)
其中processMove
是一个从用户那里获取选择的函数,更新电路板并返回新状态。
答案 3 :(得分: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
,但它不起作用,因为 last 的情况会被遗漏!
如果可以放弃游戏的移动,那么dropWhile
后跟head
就可以了。如果我有副作用(outputState
)而不是Stream
,我可以走这条路线,但在Stream
内部产生副作用的方式比var
更差一个while
循环。
所以,相反,我使用span
给了我takeWhile
和dropWhile
,但强制我保存中间结果 - 如果内存是一个问题,这可能是真的很糟糕,因为moves
指向Stream
的头部,整个游戏将被保存在记忆中。所以我不得不将所有内容封装在另一个方法game
中。这样,当我foreach
通过game
的结果时,就没有任何内容指向Stream
的{{1}}。
另一种选择是摆脱你拥有的其他副作用:head
。您可以使用getSelectionFromUser
删除它,然后您可以保存最后一步并重新应用它。
或者......你可以自己写一个takeTo
方法并使用它。