4

私は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

4

4 に答える 4

7

Scalaのimho、命令ループは問題ありません。ループのように動作する再帰関数をいつでも作成できます。また、いくつかのパターンマッチングを投入しました。

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-loopswhileJust_から取得できます。

于 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バージョンの適切な実装は次のとおりです。

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それを使用しますが、最後のケースが省略されるため、機能しません。

ゲームの動きを捨てることができれば、その後dropWhileheadうまくいくでしょう。outputStateの代わりに副作用()があった場合Stream、そのルートに進むことはできますが、aの内部に副作用があると、ループStreamのあるaよりもはるかに悪くなります。varwhile

したがって、代わりに、span両方を使用しtakeWhileますが、中間結果を保存するように強制します。これは、メモリが懸念される場合は、ゲーム全体がメモリに保持されるため、の先頭を指すため、dropWhile非常に悪い場合があります。そのため、これらすべてを別のメソッド内にカプセル化する必要がありました。そうすれば、の結果を調べても、 'sを指すものは何もありません。movesStreamgameforeachgameStreamhead

別の選択肢は、あなたが持っている他の副作用を取り除くことです: getSelectionFromUser。を使用してそれを取り除くことIterateeができ、最後の動きを保存して再適用することができます。

または...自分でtakeToメソッドを作成して使用することもできます。

于 2011-11-19T21:13:01.070 に答える
1

次のようなものはどうですか?

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

ここprocessMoveで、はユーザーから選択を取得し、ボードを更新して新しい状態を返す関数です。

于 2011-11-19T16:41:53.657 に答える