使用 Scala 生成游戏动作
我试图了解如何使用 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直到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))
但是,这需要数据结构可排序,并且在我的“真实”应用程序中,它很可能不是 < code>List[Int],而是一个 Tuple 或 case 类。我想我需要的是一个 distinct
方法,它采用一个定义相等的函数。我将如何实施?
I'm trying to understand writing strategy games using Scala functionally, but unfortunately I seem to be stuck at the very basics. (This is not home work, but my attempts to learn something new, namely "pure" functional programming.)
Let's take following simple "game": the (sole) player has x identical pieces on a endless row of squares. The pieces start on square 0 and each turn he can move one piece forward one square.
As the data structure I will use a List[Int]
were each item is the position (square) of one piece.
To generate the possible moves I came up with:
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))
What I don't like is the use of the index loop (0 until start.length)
. It doesn't seem very "functional" to me. Is this the right way to do this or is there a better way?
Now in my game example all pieces are identical, so in case m1
all three possible moves are also identical and could/should be condensed into one move. I modified moves
to sort each move item, so that I could get a list of distinct items:
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))
However this requires to data structure to be sortable and in my "real" application, it's most likely not a List[Int]
, but a Tuple or a case class. What I guess I'd need is a distinct
method, that takes an function that defines equality. How would I implement that?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果你的部分是相同的,我认为你的数据结构是错误的。您需要一个 Map[Int,Int],其中键告诉您方块的索引,值告诉您有多少块(没有默认的计数集,否则这会更容易)。然后
这解决了您的玩具示例中的所有问题(但可能不是您真正的问题)。它组成得很好:
If your pieces are identical, I think you've got the wrong data structure. You want a Map[Int,Int] where the key tells you the index of your square, and the value tells you how many pieces are there (there is no default counted set or this would be even easier). Then
This solves all the problems in your toy example (but perhaps not your real one). And it composes nicely:
作为次要选择,您可以将
(0 直到 start.length)
替换为start.indices
。递归解决方案完全避免使用索引:这比使用索引访问具有更好的性能,并且比您的解决方案具有更好的内存占用,因为它对列表组件的重用率非常高。它还使用一种常见的函数技术,将问题分为已知步骤和递归步骤。
让我解释一下。对于任何非空列表,解决方案的一个元素将是第一个元素加一的列表,所有其他元素相同。这是上面非空列表的解决方案的第一部分:
现在,所有其他解决方案的共同点是第一个元素是相同的。因此,想象
solutions
具有减去第一个元素的所有其他解决方案,然后以下内容将重新创建该解决方案:或者,以压缩形式,
现在我们只需要计算
solutions
。碰巧的是,我们已经有了一个计算方法:移动
本身!我们只需要将列表的tail
提供给它:因此,如果我们将这两者结合在一起,我们就会得到上面代码中显示的解决方案。
话虽如此,我也不确定列表是否是解决这个问题的良好数据结构。
至于获取不同的解决方案列表,如果您创建一个类来存储移动,那么您可以有一个忽略元素顺序的 equals 方法,在这种情况下,诸如
distinct 之类的方法
会工作得很好。如果这不可行,您可以使用
SortedSet
的特性(它们使用隐式Ordering
来确定相等性)来解决问题。例如:As a minor pick, you can replace
(0 until start.length)
withstart.indices
. A recursive solution avoids the use of indices altogether:This has much better performance than using indexed access, and it also has better memory footprint than your solution, as it has a very high re-use of list components. It also uses one common functional technique, which is divide the problem into a known and a recursive step.
Let me explain that a bit. For any non-empty list, one of the elements of the solution will be the a list with the first element increased by one, and all other elements the same. This is the first part of the solution for the non-empty list above:
Now, all other solutions have in common that the first element will be the same. So, imagine that
solutions
has all other solutions minus the first element, then the following will recreate the solution:Or, in a compressed form,
Now we only need to compute
solutions
. As it happens, we already have a method to compute that:moves
itself! We only have to feed it thetail
of the list:So, if we combine these two together, we get the solution displayed in the code above.
Having said all that, I'm not sure if a list is a good data structure for the this problem either.
As for getting a distinct list of solutions, if you create a class to store the moves, then you could have an
equals
method that ignored ordering of the elements, in which case methods such asdistinct
would work fine.If that isn't viable, you could use a peculiarity of
SortedSet
-- that they use the implicitOrdering
to determine equality -- to solve the problem. For example: