-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathChessBoard.hs
192 lines (173 loc) · 6.31 KB
/
ChessBoard.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
{-# LANGUAGE BangPatterns #-}
module ChessBoard (
PieceType (..)
, Piece (Piece)
, ChessBoard
, nextMove
, switch
, emptyBoard
, initialPosition
, at
, update
, remove
, toList
, save
, restore
, chessMap
) where
import qualified Data.Char as C
import qualified Data.Vector as V
import Data.List (intersperse, intercalate)
import Color
import Position
import Control.Monad (liftM)
data PieceType =
Pawn
| Knight
| Bishop
| Rook
| Queen
| King
deriving (Eq)
instance Show PieceType where
show Pawn = "p"
show Knight = "n"
show Bishop = "b"
show Rook = "r"
show Queen = "q"
show King = "k"
data Piece = Piece !Color !PieceType
deriving Eq
instance Show Piece where
show (Piece White t) = map C.toUpper $ show t
show (Piece Black t) = show t
instance Read Piece where
readsPrec _ [c] = if lc `elem` "pnbrqk"
then let color = if C.isUpper c then White else Black
t = case lc of
'p' -> Pawn
'n' -> Knight
'b' -> Bishop
'r' -> Rook
'q' -> Queen
'k' -> King
in [(Piece color t, "")]
else []
where lc = C.toLower c
-- | Representation of a chess position.
data ChessBoard = ChessBoard
{ -- | Transforms a ChessBoard into a 'Vector' of square (either populated
-- or empty), index 0 being square a1 and index 63 being square h8.
toVector :: !(V.Vector (Maybe Piece))
-- | Player making the next move.
, nextMove :: !Color
}
-- | Change player making the next move.
switch :: ChessBoard -> ChessBoard
switch !cb = ChessBoard { toVector = toVector cb,
nextMove = other $ nextMove cb }
instance Show ChessBoard where
-- Slice the ChessBoard into a list of rows, reverse the rows (because by
-- convention, chess boards are shown with the white camp downside)
-- and turns each row to a String before merging these Strings. The
-- removeLast is here only to get rid of the extra newline added by
-- unlines.
show cb = (unlines . V.toList . V.reverse . V.imap showLine . slice8 . toVector) cb
++ " " ++ concat (replicate 8 "+---") ++ "+\n "
++ intercalate " " (map ((:[]) . showFile) [0..7]) ++ " " ++
show (nextMove cb)
where
showLine :: Int -> V.Vector (Maybe Piece) -> String
showLine rank v =
" " ++ concat (replicate 8 "+---") ++ "+\n" ++ (
showRank rank : " | " ++
(intercalate " | " . map (:[])) (V.toList (V.imap (showSquare rank) v))
++ " |")
showSquare :: Int -> Int -> Maybe Piece -> Char
showSquare rank file Nothing = if rank `mod` 2 == file `mod` 2
then '-'
else ' '
showSquare _ _ (Just p) = head $ show p
showRank :: Int -> Char
showRank r = C.chr $ C.ord '1' + r
showFile f = C.chr $ C.ord 'a' + f
slice8 :: V.Vector a -> V.Vector (V.Vector a)
slice8 v
| V.null v = V.empty
| V.length v < 8 = V.singleton v
| otherwise = h `V.cons` slice8 t
where (h, t) = V.splitAt 8 v
-- | An empty chess board.
emptyBoard :: Color -> ChessBoard
emptyBoard !firstPlayer = ChessBoard {
toVector = V.replicate 64 Nothing
, nextMove = firstPlayer
}
-- | Classical initial position of a chess game.
initialPosition :: ChessBoard
initialPosition = ChessBoard {
toVector = V.fromList $ concat $
[whiteRearRow, whiteFrontRow]
++
replicate 4 emptyRow
++
[blackFrontRow, blackRearRow]
, nextMove = White
}
where
whiteRearRow = map (Just . Piece White) rearRow
whiteFrontRow = replicate 8 $ Just $ Piece White Pawn
emptyRow = replicate 8 Nothing
blackFrontRow = replicate 8 $ Just $ Piece Black Pawn
blackRearRow = map (Just . Piece Black) rearRow
rearRow = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
-- | Access to a position in O(1).
at :: ChessBoard -> Position -> Maybe Piece
at !cb !p
| i < 0 || i > 63 = Nothing
| otherwise = toVector cb V.! i
where i = toIndex p
-- | Put a piece on the chess board. If the position is not valid, this
-- function does nothing.
update :: Position -> Piece -> ChessBoard -> ChessBoard
update !pos !piece !cb =
cb { toVector = toVector cb V.// [(i, Just piece)] }
where
i = toIndex pos
-- | Returns a ChessBoard with an empty square at the given position.
remove :: Position -> ChessBoard -> ChessBoard
remove !pos !cb
| i < 0 || i > 63 = cb
| otherwise = ChessBoard (toVector cb V.// [(i, Nothing)]) $
nextMove cb
where
i = toIndex pos
toList :: ChessBoard -> [Maybe Piece]
toList !cb = V.toList $ toVector cb
-- | Produce a compact string describing the chess board, e.g. for storing to a
-- file.
save :: ChessBoard -> String
save cb = map (maybe '-' (head . show)) (toList cb) ++ case nextMove cb of
White -> "W"
Black -> "B"
-- | Construct a chess board from a string produced by 'save'.
restore :: Monad m => String -> m ChessBoard
restore s
| length s /= 65 = fail "restore: no parse (string is not 64-char long)"
| otherwise = ChessBoard <$>
(V.fromList <$> mapM (readMaybePiece . (:[])) (take 64 s)) <*>
(let c = last s in case c of
'W' -> return White
'B' -> return Black
_ -> fail $ "restore: no parse for last char " ++ [c]
)
where readMaybePiece :: Monad m => String -> m (Maybe Piece)
readMaybePiece [c] = case reads [c] of
[(p, "")] -> return $ Just p
_ -> if c == '-'
then return Nothing
else fail $ "restore: no parse on char " ++ [c]
readMaybePiece otherStr = fail $ "restore: no parse for " ++ otherStr
-- | 'chessMap f cb' maps 'f' to every position of the ChessBoard.
chessMap :: (Position -> a) -> ChessBoard -> [a]
chessMap f cb = map (f . fromIndex) [0..63]