【问题标题】:Reading user mouseclick position in haskells gloss在haskell的光泽中读取用户鼠标点击位置
【发布时间】:2016-02-19 14:46:09
【问题描述】:

编辑: 所以我按照你的指示,走到了这一点:(遵循游戏规则)

https://hackage.haskell.org/package/gloss-1.9.4.1/docs/Graphics-Gloss-Interface-Pure-Game.html

drawBoard :: IO ()
drawBoard = play (InWindow "Tic Tac Toe" (300,300)(10,10)) yellow 10 board (testBoard) (handleKeys) (iteration)

testBoard :: Board -> Picture
testBoard board = grid
    where
        grid =
            color black (line [ (-100, -300), (-100,  300) ])

iteration :: Float -> Board -> Board
iteration _ board = board 

handleKeys :: Event -> Board -> Board
handleKeys (EventKey (MouseButton LeftButton) Up _ (x, y)) board = board
handleKeys _ _ = board

board = []

它会正确打开窗口并根据我的testBoard 函数绘制一条黑线。

我现在不确定的是当我点击一个按钮时如何通过一个新的板子。我是否要创建另一个函数来绘制新板,或者单击时如何继续绘制新板?

【问题讨论】:

    标签: haskell gloss


    【解决方案1】:

    您想使用play 而不是display

    它明确地接受Event -> world -> world 类型的参数(在您的情况下world 将是Board)描述如何对Events 做出反应。

    编辑:好的,我已经实现了一个玩具示例来向您展示我将如何构建我的代码。您可以在 self-contained gist 中找到它并使用适当的导入。

    系统的状态是什么?它是如何演变的?

    首先需要弄清楚的是如何表示棋盘的状态。在井字游戏中,在给定的坐标(一对@ 987654332@s 等于板上的 -1、0 或 1)。你还有一个当前的玩家(在下一步行动后会改变)。所以让我们开始吧:

    type Coordinates = (Int, Int)
    data Player = Nought | Cross
    
    data Board = Board
      { noughts :: [Coordinates]
      , crosses :: [Coordinates]
      , player  :: Player
      }
    

    然后你可以描述各种简单的事情。当您开始比赛时,empty 板应该是什么样子的?玩家的动作(在棋盘上放置一个新的token)对系统的状态有什么影响(它将令牌插入当前玩家的列表中,然后将当前玩家更改为对手) :

    emptyBoard :: Board
    emptyBoard = Board [] [] Nought
    
    pushToken:: Coordinates -> Board -> Board
    pushToken c b = case player b of
      Nought -> b { noughts = c : noughts b, player = Cross  }
      Cross  -> b { crosses = c : crosses b, player = Nought }
    

    我们如何向用户显示状态?

    接下来是绘制与当前状态对应的Picture的问题。在这里,我假设图片将以(0, 0) 为中心,这意味着可以通过简单地将所有坐标乘以给定常数来更改其大小。我将使用Size 参数对我的所有函数进行参数化,这样我就可以毫不费力地调整显示板的大小。

    type Size = Float
    
    resize :: Size -> Path -> Path
    resize k = fmap (\ (x, y) -> (x * k, y * k))
    

    Noughts 很容易显示:我们可以简单地使用大小合适的thickCircle,然后将它们转换为它们的坐标!十字形比较难处理,因为你必须结合 2 个矩形来绘制它们。

    drawNought :: Size -> Coordinates -> Picture
    drawNought k (x, y) =
      let x' = k * fromIntegral x
          y' = k * fromIntegral y
      in color green $ translate x' y' $ thickCircle (0.1 * k) (0.3 * k)
    
    drawCross :: Size -> Coordinates -> Picture
    drawCross k (x, y) =
      let x' = k * fromIntegral x
          y' = k * fromIntegral y
      in color red $ translate x' y' $ Pictures
         $ fmap (polygon . resize k)
         [ [ (-0.35, -0.25), (-0.25, -0.35), (0.35,0.25), (0.25, 0.35) ]
         , [ (0.35, -0.25), (0.25, -0.35), (-0.35,0.25), (-0.25, 0.35) ]
         ]
    

    为了画棋盘,我们画了一个黑色的网格,然后用五角和十字填充它:

    drawBoard :: Size -> Board -> Picture
    drawBoard k b = Pictures $ grid : ns ++ cs where
    
      ns = fmap (drawNought k) $ noughts b
      cs = fmap (drawCross k)  $ crosses b
    
      grid :: Picture
      grid = color black $ Pictures $ fmap (line . resize k)
           [ [(-1.5, -0.5), (1.5 , -0.5)]
           , [(-1.5, 0.5) , (1.5 , 0.5)]
           , [(-0.5, -1.5), (-0.5, 1.5)]
           , [(0.5 , -1.5), (0.5 , 1.5)]
           ]
    

    我们如何对输入做出反应?

    现在我们有了一个看板并且可以显示它,我们只需要能够获取用户输入并对其做出响应,这样我们就可以制作一个可以运行的游戏了。

    鼠标点击被接收为一对与鼠标在绘图中的位置相对应的浮点数。我们需要将此位置转换为适当的坐标。这就是checkCoordinate 所做的事情:它将Float 除以我们为绘图选择的大小,并检查该位置对应于棋盘的哪个细分。

    在这里,我使用guard(&lt;$)(<|>) 对各种案例进行声明式演示,但如果您愿意,可以使用if ... then ... else ...

    checkCoordinate :: Size -> Float -> Maybe Int
    checkCoordinate k f' =
      let f = f' / k
      in  (-1) <$ guard (-1.5 < f && f < -0.5)
      <|> 0    <$ guard (-0.5 < f && f < 0.5)
      <|> 1    <$ guard (0.5  < f && f < 1.5)
    

    最后,handleKeys 可以检测鼠标点击,检查它们是否对应于棋盘中的位置,并通过调用 pushToken 做出适当的反应:

    handleKeys :: Size -> Event -> Board -> Board
    handleKeys k (EventKey (MouseButton LeftButton) Down _ (x', y')) b =
      fromMaybe b $ do
        x <- checkCoordinate k x'
        y <- checkCoordinate k y'
        return $ pushToken (x, y) b
    handleKeys k _ b = b
    

    把它们放在一起

    然后我们可以声明一个main 函数来创建窗口,使用emptyBoard 启动游戏,使用drawBoard 显示它并使用handleKeys 处理用户输入。

    main :: IO ()
    main =
      let window = InWindow "Tic Tac Toe" (300, 300) (10, 10)
          size   = 100.0
      in play window yellow 1 emptyBoard (drawBoard size) (handleKeys size) (flip const)
    

    还有什么事情要做?

    我没有强制执行任何游戏逻辑:

    • 玩家可以在已经被占用的网格的细分中放置一个标记,

    • 游戏不检测何时有赢家

    • 没有办法连续玩多个游戏

    【讨论】:

    • 在按照您指向我的方向编写代码后对我的帖子进行了编辑,现在我只是不确定单击后如何更新板:/
    • @darrrrUC 自己用大量代码进行了编辑。希望对您有所帮助。 ;)
    • 哇,会仔细研究一下,谢谢十亿!
    猜你喜欢
    • 2013-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    • 2015-02-18
    相关资源
    最近更新 更多