Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala 游戏循环的功能替代方案_Scala_Functional Programming_Game Loop - Fatal编程技术网

Scala 游戏循环的功能替代方案

Scala 游戏循环的功能替代方案,scala,functional-programming,game-loop,Scala,Functional Programming,Game Loop,我刚开始使用Scala,正在尝试一个小玩具程序——在本例中是一个基于文本的TictaToe。我根据我对scala的了解编写了一个工作版本,但注意到它基本上是必需的,并且我的类是可变的 我正在研究并尝试实现一些函数式习惯用法,并且已经设法至少使代表游戏状态的类不可变。然而,我留下了一个类,负责根据可变状态和命令循环执行游戏循环,如下所示: var board: TicTacToeBoard = new TicTacToeBoard def start() { var gameSt

我刚开始使用Scala,正在尝试一个小玩具程序——在本例中是一个基于文本的TictaToe。我根据我对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)      
    }
  }
在这个循环中,有什么更惯用的方法来编程我必须要做的事情


完整的源代码在这里

类似于:

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

其中,
processMove
是一个从用户处获取选择、更新电路板并返回新状态的函数。

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

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

对于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 libs中,但在Haskell中,您可以从中获取
,而只需

我会选择递归版本,但下面是
版本的正确实现:

var板:TictoeBoard=新TictoeBoard

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
)而不是
流,我可以走这条路,但是
中的副作用要比
while
循环的
var
糟糕得多

因此,相反,我使用
span
,它既给我
takeWhile
又给我
dropWhile
,但强迫我保存中间结果——如果内存是一个问题,这可能是非常糟糕的,因为整个游戏都会被保存在内存中,因为
移动
指向
流的头部。所以我不得不将所有这些封装在另一个方法中,
game
。这样,当我通过
game
的结果
foreach
时,就不会有任何东西指向
流的
头部

另一种选择是消除您的另一个副作用:
getSelectionFromUser
。你可以通过一个
迭代对象来摆脱它,然后你可以保存最后一步并重新应用它


或者。。。你可以自己写一个方法并使用它。

你可以从瓦西里·雷米纽克的文章中获得一些灵感。@4e6+1'ed,我稍后将保留该链接。在那篇文章中,我现在讨论了scala的一些特性(特性,以及scala如何处理类型参数)。我将从零开始,也许在准备好的时候用这篇文章作为跳板。谢谢堆栈溢出的一个问题是您不能接受多个答案。谢谢大家的帮助!虽然你很关心诸如可变状态之类的副作用,但值得记住的是,除了将电能转化为热能之外,每一个有用的程序都必须改变“世界状态”。因此,具有移动输出、移动输入和板状态是可以的。请确保其他一切都不会干扰。也许我遗漏了Stream.continuoused和Stream.takeWhile的工作原理。使用此技术不允许我在isGameFinished为false时重复processMove。事实上,无论传入takeWhile的谓词是否返回true或false,它只执行一次processMove。github上的代码已更新以反映尝试。takeWhile是否会建立一个非常大的列表?还是Scala编译器发现流元素没有使用?@whaley我看到你在不断地创建新的板!也许您应该声明一个
val-board=new-TicTacToeBoard
并将其传递给
processMove
(或者更好地,将
processMove
作为
TicTacToeBoard
的一种方法)。考虑到
持续
延迟参数求值(),因为它的参数类型是
=>A
@DanBurton
是一个惰性列表,所以它可以是无限的。我认为,要使这个解决方案正常工作,您需要对流进行递归定义,以便前一步的状态是下一步的输入。如果您连续使用
,则必须依赖外部
var
s.
Stream.iterate
将更充分地表达问题。我将其重新实现为递归函数,效果很好。我想我应该相信scalac会检测到它并进行TCO。@whaley,信任,但是;)@whaley我不明白为什么Tic-Tac-Toe的实现需要尾部调用optimized@LuigiPlinge根本不会,但如果这是一个无限期运行“游戏循环”的游戏,那么它会