Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simplifying some Haskell code

Tags:

haskell

monads

So I'm working on a minimax implementation for a checkers-like game to help myself learn Haskell better. The function I'm having trouble with takes a list for game states, and generates the list of immediate successor game states. Like checkers, if a jump is available, the player must take it. If there's more than one, the player can choose.

For the most part, this works nicely with the list monad: loop over all the input game states, loop over all marbles that could be jumped, loop over all jumps of that marble. This list monad nicely flattens all the lists out into a simple list of states at the end.

The trick is that, if no jumps are found for a given game state, I need to return the current game state, rather than the empty list. The code below is the best way I've come up with of doing that, but it seems really ugly to me. Any suggestions on how to clean it up?

eHex :: Coord -> Coord -- Returns the coordinates immediately to the east on the board
nwHex :: Coord -> Coord -- Returns the coordinates immediately to the northwest on the board

generateJumpsIter :: [ZertzState] -> [ZertzState]
generateJumpsIter states = do
    ws <- states
    case children ws of
      [] -> return ws
      n@_ -> n
  where 
    children ws@(ZertzState s1 s2 b p) = do
      (c, color)  <- occupiedCoords ws
      (start, end) <- [(eHex, wHex), (wHex, eHex), (swHex, neHex),
                       (neHex, swHex), (nwHex, seHex), (seHex, nwHex)]
      if (hexOccupied b $ start c) && (hexOpen b $ end c)
        then case p of
          1 -> return $ ZertzState (scoreMarble s1 color) s2
                                   (jumpMarble (start c) c (end c) b) p
          (-1) -> return $ ZertzState s1 (scoreMarble s2 color)
                                      (jumpMarble (start c) c (end c) b) p
        else []

EDIT: Provide example type signatures for the *Hex functions.

like image 403
Resistor Avatar asked Mar 20 '26 02:03

Resistor


1 Answers

The trick is that, if no jumps are found for a given game state, I need to return the current game state, rather than the empty list.

Why? I've written minimax several times, and I can't imagine a use for such a function. Wouldn't you be better off with a function of type

nextStates :: [ZertzState] -> [Maybe [ZertzState]]

or

nextStates :: [ZertzState] -> [[ZertzState]]

However if you really want to return "either the list of next states, or if that list is empty, the original state", then the type you want is

nextStates :: [ZertzState] -> [Either ZertzState [ZertzState]]

which you can then flatten easily enough.

As to how to implement, I recommend defining a helper function of type

[ZertzState] -> [(ZertzState, [ZertzState])]

and than you can map

(\(start, succs) -> if null succs then Left start else Right succs)

over the result, plus various other things.

As Fred Brooks said (paraphrasing), once you get the types right, the code practically writes itself.

like image 200
Norman Ramsey Avatar answered Mar 21 '26 14:03

Norman Ramsey



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!