2011-11-19 47 views
4

我剛剛開始與斯卡拉,我正在嘗試一個小玩具程序 - 在這種情況下,基於文本的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

+4

您可以從Vasil Remeniuk的[帶虛幻類型的Tic-Tac-Toe API]獲取靈感(http://vasilrem.com/blog/software-development/tic-tac-toe-api-with-phantom-types /)文章。 – 4e6

+1

@ 4e6 + 1'ed,我將繼續堅持以後的鏈接。在那篇文章(traits,以及scala如何處理類型參數)中討論過的scala的功能現在我不太熟悉。準備好之後,我可以從頭開始使用這篇文章作爲跳板。謝謝! – whaley

+1

堆棧溢出的一個問題是你不能接受多個答案。感謝大家的幫助! – whaley

回答

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) 
} 
+0

我將它重新實現爲遞歸函數,並且工作正常。我想我應該相信,scalac會檢測到它並執行TCO。 – whaley

+3

@whaley,信任但[驗證](http://www.scala-lang.org/api/current/index.html#scala.annotation.tailrec);) – 4e6

+0

@whaley我不明白爲什麼執行Tic Tac Toe需要進行尾部呼叫優化 –

1

如何像:

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

其中processMove是來自用戶,更新板得到選擇並返回新狀態的功能。

+0

也許我錯過了Stream.continually和Stream.takeWhile是如何工作的。使用這種技術不允許我繼續重複processMove,而我的isGameFinished爲false。實際上,無論我的謂詞傳遞給takeWhile還是true或false,它都只執行一次processMove。代碼已在github上更新以反映嘗試。 – whaley

+0

'takeWhile'會建立一個非常大的列表嗎?或者Scala編譯器是否認識到流元素未被使用? –

+1

@whaley我看到你正在創建_continually_新主板!也許你應該聲明一個'val board = new TicTacToeBoard'並把它傳遞給'processMove'(或者甚至更好,使'processMove'成爲'TicTacToeBoard'的一個方法)。考慮到'持續'推遲參數評估([按名稱調用](http://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name)),因爲它的參數類型是'=> A'。 – jglatre

6

imho for 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_

1

我會用遞歸版本去,但這裏有一個正確實施的Stream版本:

VAR板:TicTacToeBoard =新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,我可以走這條路線,但在Stream內有副作用比循環的var差。

所以,相反,我用span這給了我兩個takeWhiledropWhile但迫使我保存中間結果 - 這可能是真的壞,如果記憶是一個問題,因爲整場比賽將被保存在內存中,因爲moves指向Stream的負責人。所以我必須將所有內容封裝在另一種方法中,即game。這樣,當我foreach通過game的結果,將不會有任何東西指向Streamhead

另一種選擇是擺脫其他副作用,你有:getSelectionFromUser。你可以用Iteratee擺脫那個,然後你可以保存最後一步並重新應用它。

或...你可以自己寫一個takeTo方法並使用它。