haskellgloss

Reading user mouseclick position in haskells gloss


Edit: So i followed your pointers and got to this point: (following the rules of play)

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 = []

And it correctly opens the window and draws a black line accoarding to my testBoard function.

What im not sure about now is how to pass a new board when I click a button. Do I create another function to draw the new board or how would I go on about drawing a new board when clicking?


Solution

  • You want to use play instead of display.

    It explicitly takes an argument of type Event -> world -> world (world would be Board in your case) describing how to react to Events.

    Edit: Ok, I have implemented a toy example to show you how I'd structure my code. You can find it in a self-contained gist with the appropriate imports.

    What is the state of the system? How does it evolve?

    The first thing you need to figure out is how to represent the state of the board. In a game of Tic-tac-toe, you have a bunch of noughts and a bunch of crosses at given coordinates (a pair of Ints equal to either -1, 0 or 1) on the board. You also have a current player (which will change after the next move). So let's start with that:

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

    You can then describe various simple things. What should the empty board look like when you're beginning a match? What does a player's move (putting a new token on the board) do to the state of the system (it inserts the token in the list of the current player and then changes the current player to the adversary):

    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 }
    

    How do we display the state to the user?

    Next comes the problem of drawing a Picture corresponding to the current state. Here, I assume that the picture will be centered in (0, 0) which means that changing its size can be done by simply multiplying all the coordinates by a given constant. I will parameterise all my functions with a Size argument allowing me to tweak the size of the displayed board without any hassle.

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

    Noughts are easy to display: we can simply use a thickCircle of the right size and then translate them to their coordinate! Crosses are a bit harder to deal with because you have to combine 2 rectangles to draw them.

    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) ]
         ]
    

    In order to draw the board, we draw a black grid and then populate it with the noughts and crosses:

    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)]
           ]
    

    How do we react to inputs?

    Now that we have a board and that we can display it, we just have to be able to grab user inputs and respond to them so that we have a working game.

    Mouse clicks are received as a pair of floats corresponding to the position of the mouse in the drawing. We need to translate this position into appropriate coordinates. This is what checkCoordinate does: it divides a Float by the size we have picked for the drawing and checks which subdivision of the board that position corresponds to.

    Here I use guard, (<$) and (<|>) to have a declarative presentation of the various cases but you could use if ... then ... else ... if you wanted to.

    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)
    

    Finally, handleKeys can detect mouse clicks, check that they correspond to a position in the board and react appropriately by calling 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
    

    Putting it all together

    We can then declare a main function creating the window, starting the game with an emptyBoard, displaying it using drawBoard and handling user inputs with 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)
    

    What is there left to do?

    I haven't enforced any of the game logic: