List 如何在Haskell中制作ticktack游戏的模式?

List 如何在Haskell中制作ticktack游戏的模式?,list,haskell,tuples,list-comprehension,List,Haskell,Tuples,List Comprehension,实现具有2个参数的函数ticktack。第一个参数是自然数的元组,定义了一个游戏场的行数和列数。第二个列表包含一个滴答声游戏的比赛记录,由玩家“x”和玩家“o”轮流玩的坐标给出。打印游戏的实际状态,游戏场将以字符“-”和“|”为边界,空白方块“和字符“x”和“o”将在玩家玩过的方块上 ticktack::(Int,Int) -> [(Int,Int)] -> Result 我已经试过这样的方法: type Result = [String] pp :: Result ->

实现具有2个参数的函数ticktack。第一个参数是自然数的元组,定义了一个游戏场的行数和列数。第二个列表包含一个滴答声游戏的比赛记录,由玩家“x”和玩家“o”轮流玩的坐标给出。打印游戏的实际状态,游戏场将以字符“-”和“|”为边界,空白方块“和字符“x”和“o”将在玩家玩过的方块上

ticktack::(Int,Int) -> [(Int,Int)] -> Result

我已经试过这样的方法:

type Result = [String]
pp :: Result -> IO ()
pp x = putStr (concat (map (++"\n") x))

ticktack::(Int,Int) -> [(Int,Int)] -> Result
ticktack (0,0) (x:xs) = []
ticktack (a,b) [] = []
ticktack (a,b) (x:xs) =[ [if a == fst x && b == snd x then 'x' else ' ' | b <- [1..b]]| a <- [1..a]] ++ ticktack (a,b) xs 
类型结果=[String]
pp::结果->IO()
pp x=putStr(concat(map(+“\n”)x))
ticktack::(Int,Int)->[(Int,Int)]->结果
滴答声(0,0)(x:xs)=[]
滴答声(a,b)[]=[]

ticktack(a,b)(x:xs)=[[如果a==fst x&&b==snd x,那么“x”else'”| b正如@luqui在对问题的评论中所说:


您可以合并输出…也可以为中的每个空间搜索一次历史记录 董事会

前一种解决方案是。国际象棋问题已经解决了 这里解决的问题与你的“零和十字架”问题只有表面上的区别,所以应该是这样的 不太难适应解决方案。但是:

  • 在这种情况下,电路板大小是固定的,而且很小,因此我们不担心效率低下 两两合并电路板

  • 在这种情况下,电路板的大小是可变的,因此采用后一种方法的解决方案可能值得一试

使算法更加高效,而不是在黑板上滚动和搜索 在每个单元格匹配移动,我们将滚动移动并将值分配给一块板 表示为可变数组。可变数组在 函数式编程,所以对于中级Haskeller来说也是一个很好的练习 以前用过一两次,让我们看看我能不能弄明白

这将如何运作? 程序的核心是一个矩形字节数组。数组有两种风格: 可变和“冻结”。虽然冻结的数组不能更改,但可变数组是一条规则 可能只存在于一元上下文中,因此我们只能在数组冻结时自由传递数组。 如果这似乎过于复杂,我只能要求读者相信 安全保证值得这么复杂

无论如何,以下是类型:

type Position = (Int, Int)

type Field s = STUArray s Position Char

type FrozenField = UArray Position Char
我们将创建一个函数,将移动列表“应用”到数组中,并将其解冻和冻结为 需要

(移动
的想法是,在电路板上做个标记就足够了,而不需要知道 轮到谁了。)

应用于适当大小的空字段,此函数将解决我们的问题-我们将 只需要调整输入和输出的格式

empty :: Position -> FrozenField

positionsToMoves :: [Position] -> [Move]

arrayToLists :: FrozenField -> [[Char]]
我们的最终计划将如下所示:

tictac :: Position -> [Position] -> IO ()
tictac corner = pp . arrayToLists . applyMoves (empty corner) . positionsToMoves
我希望它看起来是合理的,尽管我们还没有编写任何具体的代码

我们能写代码吗? 对

首先,我们需要一些进口商品。没有人喜欢进口商品,但由于某些原因,现在还不是 自动化。那么,这里:

import Data.Foldable (traverse_)
import Data.Array.Unboxed
import Data.Array.ST
import GHC.ST (ST)
使用数组可以做的最简单的事情是创建一个空数组。让我们试试:

empty :: Position -> FrozenField
empty corner = runSTUArray (newArray ((1, 1), corner) ' ')
其思想是
newArray
在内存中声明一个区域并用空格填充,以及
runSTUArray
将其冻结以便可以安全地传输到程序的另一部分。我们可以改为 “内联”创建数组并赢得一定的速度,但我们只需要做一次,而我 想要保持它的可组合性-我认为这样程序会更简单

另一个简单的方法是编写调整输入和输出格式的“粘合”代码:

positionsToMoves :: [Position] -> [Move]
positionsToMoves = zip (cycle ['x', 'o'])

arrayToLists :: FrozenField -> [[Char]]
arrayToLists u =
  let ((minHeight, minWidth), (maxHeight, maxWidth)) = bounds u
  in [ [ u ! (row, column) | column <- [minWidth.. maxWidth] ] | row <- [minHeight.. maxHeight] ]
-根据单子定律,这相当于
u
。诀窍在于两者之间发生的事情:

traverse_ (f u) moves
让我们检查一下类型

λ :type traverse_
traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
因此,调用了一些函数,参数为
u
,但结果是无用的
()
类型。 在函数设置中,这一行没有任何意义。但在monad中,可能会出现绑定值 更改。
f
是一个可以更改单子状态的函数,因此可以更改单子的值 返回绑定名称时的绑定名称。C中的类似代码如下所示:

tictac :: Position -> [Position] -> IO ()
tictac corner = pp . arrayToLists . applyMoves (empty corner) . positionsToMoves
char*foldST(void f(char*,Move),int n_start,char start[],int n_moves,Move moves[])
{

//u您可以使用带有
(相当混乱,因为您将比较输出字符)的双
Zipw合并输出,或者您可以为电路板中的每个空格搜索一次历史记录。也就是说,使用一些辅助函数
getLocation::[(Int,Int)]->(Int,Int)->Char
(其中第二个
(Int,Int)
是一个职位)。@luqui谢谢你的回答。但是你能告诉我这个用双zipWith进行的修改会是什么样子吗?我还是haskell的新手,所以任何代码的例子都能帮到我。最后一行:
zipWith(zipWith mergeChar)[[if a==fst x…](ticktack(a,b)xs)
。也就是说,我们“压缩合并”,而不是将剩余移动生成的板连接起来(
++
)它们。这里的
mergeChar::Char->Char->Char
是您编写的一个函数,例如,它将取
'
'x'
并给出
'x'
如何组合每个位置。非常感谢。您的解决方案确实帮助我理解Haskell中更复杂的函数和内容。我还在学习Haskell嗯,所以我对STUArray和UArray的事情有点困惑,所以你能给我解释一下这个数据类型和它们是如何工作的吗?当我试图理解
applyMoves
函数时,我也感到困惑。但我真的很感谢你的帮助。@Weliras你看了我链接的另一个问题了吗?可能更多可访问。我还添加了
STUArray
fol的解释
traverse_ (f u) moves
λ :type traverse_
traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()