使用Scala在功能上生成游戏移动

时间:2011-03-29 14:45:25

标签: scala functional-programming

我正在努力理解使用Scala编写策略游戏的功能,但不幸的是我似乎陷入了非常基础的困境。 (这不是家庭作业,而是我尝试学习新东西,即“纯粹的”函数式编程。)

让我们采取以下简单的“游戏”:(唯一)玩家在无穷无尽的方块上有 x 相同的棋子。棋子从方块0开始,每转一圈,他可以向前移动一个方块。

作为数据结构,我将使用List[Int],每个项目是一件的位置(正方形)。

为了产生我想出的可能的动作:

def moves(start: List[Int]) = 
    (0 until start.length).map({i => start.updated(i, start(i) + 1)});

val m1 = moves(List(0,0,0))
// m1 then contains Vector(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))

val m2 = moves(List(1,2,3))
// m1 then contains Vector(List(2, 2, 3), List(1, 3, 3), List(1, 2, 4))

我不喜欢的是使用索引循环(0 until start.length)。它对我来说似乎不太“功能”。这是正确的方法吗?还是有更好的方法?


现在在我的游戏示例中,所有部分都是相同的,所以在m1的情况下,所有三个可能的移动也是相同的,可以/应该被压缩成一个移动。我修改了moves来对每个移动项目进行排序,这样我就可以获得不同项目的列表:

def moves(start: List[Int]) = 
    (0 until start.length).map({i => start.updated(i, start(i) + 1).sorted}).distinct;

val m1 = moves(List(0,0,0))
// m1 then contains Vector(List(0, 0, 1))

val m2 = moves(List(1,2,3))
// m1 then contains Vector(List(2, 2, 3), List(1, 3, 3), List(1, 2, 4))

然而,这要求数据结构是可排序的,在我的“真实”应用程序中,它很可能不是List[Int],而是元组或案例类。我想我需要的是一个distinct方法,它采用一个定义相等的函数。我该如何实现呢?

2 个答案:

答案 0 :(得分:4)

如果你的作品相同,我认为你的数据结构错误了。你想要一个Map [Int,Int],其中键告诉你你的方块的索引,并且该值告诉你有多少块(没有默认的计数集或者这将更容易)。然后

def moves(start: Map[Int,Int]) = start.keySet.map(k => {
  val n = start(k)
  val pickup = (if (n == 1) (start - k) else start + (k -> (n-1)))
  pickup + ((k+1) -> (start.getOrElse(k+1, 0) + 1))
})

这解决了玩具示例中的所有问题(但可能不是真正的问题)。它组成很好:

scala> val firstmoves = moves(Map(0->3))                          
firstmoves: scala.collection.Set[scala.collection.immutable.Map[Int,Int]] =
Set(Map((0,2), (1,1)))

scala> val secondmoves = firstmoves.flatMap(moves)                           
secondmoves: scala.collection.Set[scala.collection.immutable.Map[Int,Int]] =
Set(Map((0,1), (1,2)), Map((0,2), (2,1)))

scala> val thirdmoves = secondmoves.flatMap(moves)
thirdmoves: scala.collection.Set[scala.collection.immutable.Map[Int,Int]] =
Set(Map((1,3)), Map((0,1), (1,1), (2,1)), Map((0,2), (3,1)))

答案 1 :(得分:4)

作为辅助选择,您可以将(0 until start.length)替换为start.indices。递归解决方案完全避免使用索引:

def moves(start: List[Int]): List[List[Int]] =  start match {
  case Nil => Nil
  case head :: tail => (head + 1 :: tail) :: (moves(tail) map (head :: _))
}

这比使用索引访问具有更好的性能,并且它还具有比您的解决方案更好的内存占用,因为它具有非常高的列表组件重用率。它还使用了一种常见的功能技术,即将问题分为已知和递归步骤。

让我解释一下。对于任何非空列表,解决方案的一个元素将是一个列表,其中第一个元素增加1,所有其他元素相同。这是上面非空列表解决方案的第一部分:

head + 1 :: tail

现在,所有其他解决方案的共同点是第一个元素将是相同的。因此,假设solutions包含所有其他解决方案而不是第一个元素,那么以下内容将重新创建解决方案:

solutions map (solution => head :: solution)

或者,以压缩形式,

solutions map (head :: _)

现在我们只需要计算solutions。碰巧的是,我们已经有了一种计算方法:moves本身!我们只需要提供列表的tail

(moves(tail) map (head :: _))

因此,如果我们将这两者结合在一起,我们就会在上面的代码中显示解决方案。

说了这么多,我不确定列表是否也是这个问题的良好数据结构。

至于获取一个独特的解决方案列表,如果你创建一个类来存储移动,那么你可以使用equals方法忽略元素的排序,在这种情况下,方法如{{1}会工作得很好。

如果这不可行,你可以使用distinct的特性 - 他们使用隐式SortedSet来确定相等 - 来解决问题。例如:

Ordering