1

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, 'R' - Rook, 'B' - Bishops, 'N' - Knights, 'P' - Pawns)
  • 2nd specifies column ('a' - 'h')
  • 3rd is the row number ('1' - '8')

First list of string is for BLACK pieces, the second is for WHITE pieces. Free fields of the boards are represented by '.'. Black chess pieces will be upper cased, white will be lower cased.

Printing 1-8 and a-h on board isn't necessary.

This is required function type:

chess :: [String] -> [String] -> [String]

we have this function for print

pp :: Result -> IO ()
pp x = putStr (concat (map (++"\n") x))

This is IO example:

Prelude> pp( chess["Ke1","Ra1","Rh1","Pa2","Be5"] ["Ke8","Ra8","Rh8","Pa7","Qd8","Bc8","Nb8"])
8rnbqk..r
7p.......
6........
5....B...
4........
3........
2P.......
1R...K..R
 abcdefgh

What have I tried: eg.

chess :: [String] -> [String] -> [String]
chess _ [] = []
chess [] _ = []
chess ((x1:x2:x3:_):xs) ((y1:y2:y3:y3s):ys) 
    | 'a' == x2 && '1' == x3 = [x1] : chess xs ys
    | 'a' == y2 && '1' == y3 = [y1] : chess xs ys
    | 'b' == x2 && '1' == x3 = [x1] : chess xs ys
    | 'c' == x2 && '1' == x3 = [x1] : chess xs ys
    | 'd' == x2 && '1' == x3 = [x1] : chess xs ys
    | 'e' == x2 && '1' == x3 = [x1] : chess xs ys
    | 'e' == x2 && '1' == x3 = [x1] : chess xs ys
    | 'g' == x2 && '1' == x3 = [x1] : chess xs ys
    | otherwise = ['.'] : chess xs ys

Input was: chess["Ke1","Ra1","Rh1","Pa2","Be1"] ["Kb1","Ra8","Rh8","Pa7","Qd8","Bc8","Na1"] Output was: ["K","R",".",".","B"]

One more..

chess :: [String] -> [String] -> [String]
chess _ [] = []
chess [] _ = []
chess ((x1:x2:x3:_):xs) ((y1:y2:y3:y3s):ys) 
    | (x2 == 'a' && x3 == ['1']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['2']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['3']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['4']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['5']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['6']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['7']) = [x1] : chess (xs) (ys)
    | (x2 == 'a' && x3 == ['8']) = [x1] : chess (xs) (ys)
    | otherwise =  chess (xs) (ys)

Input: chess["Ke1","Ra1","Rh1","Pa2","Be1"] ["Ke8","Ra8","Rh8","Pa7","Qd8","Bc8","Nb8"] Output: K R R

None of them work as I wanted. I've tried checking one row first, then each column (a-h) - this is right I think, because I need to print something like ["K...Q...", "P......."] - each element is one row. What if I check eg. black, and if there's not black, there still can be white, so I need to check second string for white pieces before printing '.'. Please help, I'm confused. I've tried to code like 4 functions, but they took more than 4 hours. Thank you

Lishaak
  • 43
  • 1
  • 7
  • You have forgotten to explain the type called `Result`. Is it a type synonym for `[String]`? – Ignat Insarov Nov 03 '19 at 21:03
  • Also I think there is some confusion of white and black in your post. The example you provide displays the first list of chess pieces in capital letters, and your specification says that capital letters are for black pieces and the first list is that of the white. I think this is contradictory. – Ignat Insarov Nov 03 '19 at 21:44
  • Result was synonym for [String], edited. The white and black was reversed, so the first list is black and white is the second list. – Lishaak Nov 04 '19 at 11:52

2 Answers2

2

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.

Ignat Insarov
  • 4,660
  • 18
  • 37
  • I quite understand what you did, some of the functions are confusing, eg. function fold or overlay - never used that. Thank you. But I think your code is too intermediate. On our lessons we never did so many tricks. It was only some easy coded functions (eg. reverse Picture ([String]) - so function was ```revPic = reverse``` and so on. Or example to find all goldbach numbers.. Never something that complicated. I'll try to understand your code more and try to simplify it somehow - to be more understandable for beginners like me. – Lishaak Nov 04 '19 at 12:01
  • @Lishaak You will be amazed how _"intermediate"_ [a solution to a seemingly trivial problem](https://stackoverflow.com/a/57840965) may get if one makes an effort to ensure it is free of corner case errors. But behind these seemingly complicated constructions is dead simple thinking. It may be possible with ingenuity to construct a solution that explores some unique features of a problem to get a compact _"shortcut"_ solution, but then the code will be hard to grasp, narrow and fragile, while a program built out of simple and verified abstractions is straightforward, flexible and robust. – Ignat Insarov Nov 04 '19 at 13:17
  • Can you explain the replaceAt function? I never seen something like `replaceAt i y xs = let (before, (_: after)) = splitAt i xs in before ++ y: after` what's before and after? And how you can combine ++ and : in `before ++ y: after`? Thanks – Lishaak Nov 05 '19 at 16:17
  • @Lishaak `replaceAt` is prior art, see for example [here](https://stackoverflow.com/a/53989351) _(called `patch`)_ and [here](https://stackoverflow.com/a/23428227). A search even reveals a dedicated [question](https://stackoverflow.com/q/9220022). `before` and `after` are the parts of the list before and after the element to be replaced. `++` and `:` are `infixr` _(check with, for example, `:info (++)` in `ghci`)_, so they associate to the right: the reading is `before ++ (y: after)`. – Ignat Insarov Nov 05 '19 at 20:09
  • For what it's worth, I agree with Lishaak that this solution is over-engineered. I have posted an answer with an alternate implementation idea which I think meets the criteria of "simple and verified abstractions, straightforward, flexible, and robust" but which is much more beginner-friendly. In a way, the only difference is that I've inverted the iteration order. – Daniel Wagner Nov 01 '22 at 13:21
0

Consider structuring your code this way:

-- given the three characters you describe, figure out the position and
-- piece name (do whatever you want with nonsense)
--
-- e.g. decode "Ke8" would return ((5, 8), 'k')
--      decode "Kz92" and decode "f" can return anything convenient
decode :: String -> ((Int, Int), Char)
decode = undefined

-- given a key, find all the values in a list associated with that key
--
-- e.g. assoc (5, 8) [((3, 4), 'q'), ((5, 8), 'k'), ((1, 2), 'p')]
-- would return ['k']
--
-- in non-homeworks, don't use this; instead, use the pre-existing
-- Prelude.lookup or the containers' package's Data.Map
assoc :: Eq k => k -> [(k, v)] -> [v]
assoc = undefined

-- if there's a single good choice for character at a given board position,
-- return it; otherwise, use a . to mark an empty space
--
-- e.g. choose ['k']      would return 'k'
--      choose []         would return '.'
--      choose ['k', 'P'] can return anything convenient
choose :: String -> Char
choose = undefined

chess :: [String] -> [String] -> [String]
chess white_ black_ = [[undefined | col <- [1..8]] | row <- [8,7..1]] where
    white = [(pos, toUpper piece) | (pos, piece) <- decode white_]
    black = [(pos, toLower piece) | (pos, piece) <- decode black_]
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380