Reading user mouseclick position in haskells gloss

自闭症网瘾萝莉.ら 提交于 2020-01-06 02:01:36

问题


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?


回答1:


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:

  • player can put a token in a subdivision of the grid that's already occupied,

  • the game does not detect when there is a winner

  • there is no way to play multiple games in a row



来源:https://stackoverflow.com/questions/35507997/reading-user-mouseclick-position-in-haskells-gloss

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!