【问题标题】:Haskell Variable UpdationHaskell 变量更新
【发布时间】:2015-10-05 17:37:48
【问题描述】:
  • 我正在使用 Haskell 构建游戏(类似于扑克),但我是 Haskell 新手,在尝试思考如何用纯函数式语言实现事物时通常会卡住。*

游戏有 3 名玩家(只是为了便于解释)。两名玩家将从桌上的牌中玩,而一名玩家将从堆叠中玩。两种玩家都会拿一张牌并在有用时使用它(替换您手中的一张牌)或直接丢弃它(如果没有)。 胜利只是满堂彩或顺子

他们两个有一些understanding,他们之前同意互相帮助。就像我从牌桌上拿了 K(比如说),而下一手牌我从牌桌上拿了 4,这意味着我要满屋子了,因此如果可以的话,你应该提供帮助。这些玩家中的每一个都应该互相帮助,这样他们中的一个就应该在玩家玩筹码之前获胜,而不是以牺牲自己为代价。

桌子共有 5 张牌,每个玩家也是如此

实现两个玩家之间的理解:

我不知道如何跟踪其他玩家从牌桌上拿走了什么。我试图通过将卡片添加到我的手上来做到这一点(实际上记住了这件事)并且当我的手(真实)的东计算时我将只取前五张卡片并且在计算帮助他时我'要丢五张牌,然后用它来计算。 有没有更好的策略来做同样的事情?这可能是微不足道的,但我是haskell世界的业余爱好者。如果你想要代码,我可以把它放上来。

我的游戏的基本设置:

data Card = Card {
    c :: (Int, Char) 
   } deriving (Eq, Ord, Show)

data Deck = Deck {
   d :: [Card]
} deriving (Show, Eq, Ord)  

data Game = Game {
  bact1Hand :: [Card],
  bact2Hand :: [Card],
  orgHand  :: [Card],
  tableCards :: [Card],
  deck :: Deck
} deriving (Show, Eq, Ord)  

type GameState a = State Game a`

【问题讨论】:

  • 我们在 Haskell 中没有变量!使用recursion
  • 至少我知道。但是你要怎么记住?关于最后一手牌或其他玩家从桌上拿走的牌?谢谢
  • 函数的附加参数。
  • 从游戏位置的角度思考。一个位置完全决定了游戏的状态。您传递代表位置的数据。移动将一个位置转换为另一个位置。从定义能够表示位置的数据类型开始。
  • 是的,提交你的代码。

标签: haskell poker


【解决方案1】:

如果您想保持简单并且还没有为高级 Haskell 事物做好准备,您可以使用递归转换,正如 cmets 中向您提出的问题所建议的那样。

主要思想是创建一个完整描述游戏状态的数据类型data Game;然后你编写一个函数“玩家 A 做出动作 1”,改变游戏(例如act:: Player -> Action -> Game -> Game)。 这样的函数转换了完整的游戏状态: 它保留了从输入到输出的状态,但改变了它的某些部分,与玩家和动作相关(例如,将卡片添加到手牌)。 最后一步是将函数绑定到IO 操作:在最简单的设置中,您创建一个主循环,其中Game 更改,等待每次迭代的IO 输入(例如,就像OpenGL 主循环一样)。或者您可以使用Control.Concurrent.MVar 异步传递IO 操作。

所以,简而言之:

-- | State of the AI
data Mind = Mind
    { overallLogic :: ???
    , perPlayerLogic :: [(PlayerID, Understanding)]
    }

-- | Undersanding of one player by another player
data Understanding = ???

newtype PlayerID = PlayerID Int

-- | Full state of the player
data Player = Player
    { id :: PlayerID
    , hand :: [Card]
    , mind :: Mind
    , ...
    }

-- | indication of the game state
data Status = PlayersTurn PlayerID | PlayerWon PlayerID

-- | Full state of the game
data Game = Game
    { status :: Status
    , players :: [Player] -- or consider (Int)Map of player-id
    , ...
    }

-- | Events in game are passed from IO: they indicate players' actions
data Event = PlayersTurn PlayerID Action ...
           | WhateverElseEvent

-- | the function to transform Game's state
react :: Event -> Game -> Game
react ev game = game'
    where game' = ... -- here is game logic 

你把主循环放在一个单独的线程中:

mainLoop :: MVar Event -> Game -> IO ()
mainLoop mevent game = do
    event <- takeMVar mevent
    let game' = react event game
    if checkIfFinished game'
        then return ()
        else mainLoop mevent game'

注意尾递归调用 - 循环一直持续到 checkIfFinished 返回 TruecheckIfFinished 是您根据Game 状态决定是否结束游戏的逻辑。 takeMVar 等待另一个线程放置一些东西。 在其他线程中,您添加获取用户输入的 IO 操作,从中提取 Event 数据,然后使用 putMVar 放置它。

更新:感谢 cmets,我将答案的一些离题部分放在底部作为“进一步阅读”。

如果您想全局记住状态,GHC 为您提供了两种类型: Data.STRefControl.Concurrent.MVar。 但是,这不是“纯粹的功能”方法。 您可以在http://learnyouahaskell.com/ 阅读关于全状态计算和阅读器 monad 的信息。评论员认为 state monad 在当前目的上比 reader monad 更好 - 谢谢,我承认这一点。不管怎样,learnyouahaskell 是这类信息的一个很好的来源:)。

在应用程序中最纯粹的功能和有用的方法是使用Functional Reactive Programming。 有一个行为的概念来表示一个随时间变化的值。 有些人会说 FRP 在这里有点过头了,而且他们可能是对的。 但是当你决定添加一个 GUI 和其他 IO 时,使用例如提供的漂亮接口。 reactive-banana 将大大简化您的生活。

【讨论】:

  • Reader 和它有什么关系?
  • 我认为 FRP 对于扑克游戏来说太过分了。
  • 不,它更难,在这种情况下它没有用,因为你希望 state 保持不变(不仅在本地修改) - 你描述的内容对解释器或应用程序配置很有用- 但是 state-monad 被命名是有原因的
  • 这里的 FRP 将是交互的实现细节 - 问题更基本:它是如何在纯设置中处理状态
  • 嗯,这个问题有点含糊,所以你真的不能责怪很难制定一个强有力的答案。但我认为你也有点偏离了 IO 的答案(问题似乎是关于游戏内部的逻辑,而不是关于用户交互)。由于主要问题似乎是如何更新 Haskell 中的变量,我认为提问者将从如何使用 State monad 的解释中受益最大(因为这是此类问题的首选解决方案)。您显然在答案中付出了相当多的努力,但更切中要害会受益。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-02
  • 1970-01-01
  • 1970-01-01
  • 2019-05-02
  • 1970-01-01
相关资源
最近更新 更多