How to print chess board with list of positions of chess pieces

后端 未结 1 1514

I\'m stuck at doing my homework. I have to write a function that has 2 [String]. List string contains 3 chars:

  • 1st is Chess piece (e.g. \'K\' - King, \'Q\' - Queen
相关标签:
1条回答
  • 2021-01-23 00:35

    Foreword.

    I will give you an overview, and allow myself to leave some details unpolished. Please adjust my advice to your liking.

    My answer will be structured like this:

    1. Explain the idea.
    2. Decide on the strategy.
    3. Draft the types.
    4. Fill in the definitions.

    In real life my process is "dialectic", and all these lines of thought grow simultaneously, by trial and error.

    Idea.

    I am thinking that, given two fields with some pieces each, I can always put these fields "on top of each other", so that every piece is found in the same place in the received field as it was in one of the given fields. (Unless there are two pieces on the same place, in which case the behaviour is undefined.) Once I can add two fields this way, I can add any number thereof. And it should not be too hard to produce a field with a single piece. This technique is called "folding a monoid" and you will see it used a lot in Haskell.

    Strategy.

    This is how I will solve this problem:

    • Define a getPiece function to read a piece.
    • Define a putPiece function to display a field with one piece.
    • Define an overlay function that overlays any two fields.
    • Fold over the list of pieces with this function.

    Types.

    type Piece = (Char, Int, Int)  -- Piece, row, column.
    
    type Field = [String]  -- Rows.
    
    getPiece :: String -> Piece
    
    putPiece :: Piece -> Field
    
    overlay :: Field -> Field -> Field
    
    chess :: [String] -> [String] -> Field
    

    It may be worth your time to take a piece of paper and draw some pictures of how these types and functions may possibly connect.

    Definitions.

    getPiece :: String -> Piece
    getPiece [v, x, y] = (piece, row, column)
      where
        piece  = v
        row    = (Char.ord y - 48)
        column = (Char.ord x - 96)
    
    putPiece :: Piece -> Field
    putPiece (v, x, y) = reverse
                       $ replaceAt (x - 1) (replaceAt (y - 1) v blank) (replicate 8 blank)
      where
        blank = replicate 8 ' '
    
        replaceAt :: Int -> a -> [a] -> [a]
        replaceAt i y xs =
          let (before, (_: after)) = List.splitAt i xs
          in  before ++ y: after
    
    overlay :: Field -> Field -> Field
    overlay u v = zipWith (zipWith overlayOne) u v
      where
        overlayOne ' ' y = y
        overlayOne  x  _ = x
    
    chess :: [String] -> [String] -> Field
    chess white black = List.foldl1' overlay . fmap putPiece $ pieces
      where
        pieces = fmap (makeWhite . getPiece) white ++ fmap getPiece black
    
        makeWhite :: Piece -> Piece
        makeWhite (c, x, y) = (Char.toLower c, x, y)
    

    A tricky part here is how two zipWith functions are combined to achieve a "zip" effect on a list of lists. Notice also that I am not hesitating to define a helper function replaceAt when I think it will make the main function really simple.

    Conclusion.

    I find it to be most comfortable to approach even a simple problem with the right toolbox of abstractions. In this case, we make use of a monoid (defined by overlay) and a recursion scheme (List.foldl' is an instance of "catamorphism"). I am sure you will meet many more cases in your programming practice where these ideas can be put to use.

    Leave me a comment if something is not approachable or poorly explained.

    Enjoy Haskell!

     

    P.S.   See also another, algorithmically faster approach to a very similar problem.

    0 讨论(0)
提交回复
热议问题