1 > module DataTypes where
10 %------------------------------------------------------------------------------
12 The `presentable' class, my own co-class for Text
14 > class Presentable a where
15 > userFormat :: a -> String -- in prefered display format
17 Defaults, in terms of each other
19 And the default for lists.
21 > instance (Presentable a) => Presentable [a] where
22 > userFormat xs = unlines (map userFormat xs)
24 %------------------------------------------------------------------------------
25 Here are all the pieces allowed in chess.
35 > instance Presentable Piece where
36 > userFormat King = "K"
37 > userFormat Queen = "Q"
38 > userFormat Rook = "R"
39 > userFormat Knight = "N"
40 > userFormat Bishop = "B"
41 > userFormat Pawn = "P"
44 %------------------------------------------------------------------------------
49 %------------------------------------------------------------------------------
51 Here are the two sides.
53 > data Colour = Black | White deriving (Eq)
55 > instance Presentable Colour where
56 > userFormat White = "White"
57 > userFormat Black = "Black"
59 > changeColour :: Colour -> Colour
60 > changeColour White = Black
61 > changeColour Black = White
63 %------------------------------------------------------------------------------
65 Now the ranks and files.
67 > type ChessRank = Int -- 1-8
68 > type ChessFile = Int -- 1-8
70 > type BoardPos = (ChessFile,ChessRank) -- ChessFile (0-7) and ChessRank (0-7)
71 > type ExBoardPos = (Maybe ChessFile,Maybe ChessRank)
73 > extendBP :: BoardPos -> ExBoardPos
74 > extendBP (a,b) = (Just a,Just b)
76 > compExBPandBP :: ExBoardPos -> BoardPos -> Bool
77 > compExBPandBP (a,b) (c,d) = a `cmp` c && b `cmp` d
79 > cmp Nothing _ = True
80 > cmp (Just x) y = x == y
82 > userFormatBoardPos :: BoardPos -> String
83 > userFormatBoardPos (f,r) = userFormatFile f ++ userFormatRank r
84 > userFormatExBoardPos :: ExBoardPos -> String
85 > userFormatExBoardPos (Just f,Just r) = userFormatFile f ++ userFormatRank r
86 > userFormatExBoardPos (Just f,Nothing) = userFormatFile f
87 > userFormatExBoardPos (Nothing,Just r) = userFormatRank r
88 > userFormatExBoardPos _ = ""
89 > userFormatRank r = [toEnum (r + 48)]
90 > userFormatFile f = [toEnum (f + 96)]
92 %------------------------------------------------------------------------------
94 These are the components of a move.
97 > = PieceTok Piece -- Q,K,R,B,N
98 > | RankTok ChessRank -- 1 .. 8
99 > | FileTok ChessFile -- a .. h
100 > | PartCastleTok -- 0 | O | o
107 > charToMoveTok 'Q' = Just (PieceTok Queen)
108 > charToMoveTok 'K' = Just (PieceTok King)
109 > charToMoveTok 'R' = Just (PieceTok Rook)
110 > charToMoveTok 'B' = Just (PieceTok Bishop)
111 > charToMoveTok 'N' = Just (PieceTok Knight)
112 > charToMoveTok '1' = Just (RankTok 1)
113 > charToMoveTok '2' = Just (RankTok 2)
114 > charToMoveTok '3' = Just (RankTok 3)
115 > charToMoveTok '4' = Just (RankTok 4)
116 > charToMoveTok '5' = Just (RankTok 5)
117 > charToMoveTok '6' = Just (RankTok 6)
118 > charToMoveTok '7' = Just (RankTok 7)
119 > charToMoveTok '8' = Just (RankTok 8)
120 > charToMoveTok 'a' = Just (FileTok 1)
121 > charToMoveTok 'b' = Just (FileTok 2)
122 > charToMoveTok 'c' = Just (FileTok 3)
123 > charToMoveTok 'd' = Just (FileTok 4)
124 > charToMoveTok 'e' = Just (FileTok 5)
125 > charToMoveTok 'f' = Just (FileTok 6)
126 > charToMoveTok 'g' = Just (FileTok 7)
127 > charToMoveTok 'h' = Just (FileTok 8)
128 > charToMoveTok '0' = Just (PartCastleTok)
129 > charToMoveTok 'O' = Just (PartCastleTok)
130 > charToMoveTok 'o' = Just (PartCastleTok)
131 > charToMoveTok 'x' = Just (CaptureTok)
132 > charToMoveTok '-' = Just (MoveToTok)
133 > charToMoveTok '=' = Just (QueensWith)
134 > charToMoveTok '+' = Just (CheckTok)
135 > charToMoveTok '#' = Just (MateTok)
136 > charToMoveTok _ = Nothing
138 %------------------------------------------------------------------------------
141 > = QuantumMove String -- Short Description of move
142 > String -- Check or Mate (+ or #)
143 > String -- !,??,?!, etc
144 > Board -- Snap Shot of Board
145 > | QuantumNAG Int -- !,??,?! stuff
146 > | QuantumComment [String] -- { comment }
147 > | QuantumResult String -- 1-0, etc (marks end of game)
148 > | QuantumAnalysis [Quantum] -- ( analysis )
149 > | QuantumPrintBoard -- {^D}
151 > instance Presentable Quantum where
152 > userFormat (QuantumMove mv ch ann _)
154 > userFormat (QuantumNAG nag) = "$" ++ show nag
155 > userFormat (QuantumComment comment)
156 > = "[" ++ unwords comment ++ "]"
157 > --userFormat (QuantumNumber num) = userFormat num
158 > userFormat (QuantumResult str) = str
159 > userFormat (QuantumAnalysis anal) =
160 > "( " ++ unwords (map userFormat anal) ++ " )"
162 %------------------------------------------------------------------------------
164 > data Result = Win | Draw | Loss | Unknown
166 > instance Presentable Result where
167 > userFormat Win = "1-0"
168 > userFormat Draw = "1/2-1/2"
169 > userFormat Loss = "0-1"
170 > userFormat Unknown = "*"
172 > mkResult :: String -> Result
173 > mkResult "1-0" = Win
174 > mkResult "1/2-1/2" = Draw
175 > mkResult "0-1" = Loss
176 > mkResult _ = Unknown
178 %------------------------------------------------------------------------------
180 > data TagStr = TagStr String String
182 > instance Presentable TagStr where
183 > userFormat (TagStr tag str) = "[" ++ tag ++ " \"" ++ str ++ "\"]"
185 > getTagStr :: String -> String -> [TagStr] -> String
186 > getTagStr str def [] = def
187 > getTagStr str def (TagStr st ans:rest)
189 > | otherwise = getTagStr str def rest
196 > Maybe Int, -- Game Number
202 > getHeaderInfo tags = (
207 > white `par` whiteElo,
208 > black `par` blackElo,
211 > date = case getTagStr "Date" "?" tags of
212 > [a,b,c,d,'.','?','?','.','?','?'] -> [a,b,c,d]
213 > [a,b,c,d,'.',x,y,'.','?','?'] -> getMonth [x,y] ++ " " ++ [a,b,c,d]
215 > site = getTagStr "Site" "?" tags
216 > gameno = case getTagStr "GameNumber" "" tags of
217 > xs | all isDigit xs && not (null xs) -> Just (read xs)
219 > result = mkResult (getTagStr "Result" "*" tags)
220 > white = cannon (getTagStr "White" "?" tags)
221 > whiteElo = getTagStr "WhiteElo" "" tags
222 > black = cannon (getTagStr "Black" "?" tags)
223 > blackElo = getTagStr "BlackElo" "" tags
224 > opening = getOpening (getTagStr "ECO" "" tags)
227 > par xs ys = xs ++ " (" ++ ys ++ ")"
229 > getMonth "01" = "Jan"
230 > getMonth "02" = "Feb"
231 > getMonth "03" = "Mar"
232 > getMonth "04" = "Apr"
233 > getMonth "05" = "May"
234 > getMonth "06" = "Jun"
235 > getMonth "07" = "Jul"
236 > getMonth "08" = "Aug"
237 > getMonth "09" = "Sep"
238 > getMonth "10" = "Oct"
239 > getMonth "11" = "Nov"
240 > getMonth "12" = "Dec"
242 > cannon name = case span (/= ',') name of
243 > (a,[',',' ',b]) -> b : ". " ++ a
244 > (a,[',',b]) -> b : ". " ++ a
245 > (a,',':' ':b) -> b ++ " " ++ a
246 > (a,',':b) -> b ++ " " ++ a
250 > getOpening eco@[a,b,c] | a >= 'A' && a <= 'E' && isDigit b && isDigit c
251 > = getOpenName ((fromEnum a - fromEnum 'A') * 100
252 > + (fromEnum b - fromEnum '0') * 10
253 > + (fromEnum c - fromEnum '0')) ++ " " ++ eco
254 > getOpening other = other
256 > getOpenName :: Int -> String
258 > | otherwise = "Foo"
260 > | eco == 000 = "Irregular Openings"
261 > | eco == 001 = "Larsen Opening"
262 > | eco == 002 = "From's Gambit and Bird's Open"
263 > | eco == 003 = "Bird's Opening"
264 > | eco == 004 = "Dutch System"
265 > | eco == 005 = "Transposition to various Open"
266 > | eco == 006 = "Zukertort Opening"
267 > | eco >= 007 && eco <= 008
269 > | eco == 009 = "Reti Opening"
270 > | eco == 010 = "Variations of Dutch, QI, KI"
271 > | eco >= 011 && eco <= 014
273 > | eco == 015 = "English counter King's Fianch"
274 > | eco >= 016 && eco <= 039
275 > = "English Opening"
276 > | eco == 040 = "Unusual replies to 1.d4"
277 > | eco == 041 = "Modern Defence counter 1.d4"
278 > | eco == 042 = "Modern Defence with c2-c4"
279 > | eco >= 043 && eco <= 044
281 > | eco == 045 = "Queen's Pawn-Trompowski Var"
282 > | eco == 046 = "Queen's Pawn Opening"
283 > | eco == 047 = "Queen's Indian"
284 > | eco >= 048 && eco <= 049
286 > | eco == 050 = "Queen's Indian"
287 > | eco >= 051 && eco <= 052
288 > = "Budapest Defence"
289 > | eco >= 053 && eco <= 056
290 > = "Old Indian Defence"
291 > | eco >= 057 && eco <= 059
292 > = "Volga-Benko Gambit"
293 > | eco >= 060 && eco <= 079
295 > | eco >= 080 && eco <= 099
297 > | eco == 100 = "Owen Def, Nimzowitsch Def"
298 > | eco == 101 = "Center Counter"
299 > | eco >= 102 && eco <= 105
300 > = "Alekhine's Defence"
301 > | eco == 106 = "Modern Defence"
302 > | eco >= 107 && eco <= 109
304 > | eco >= 110 && eco <= 119
305 > = "Caro-Kann Defence"
306 > | eco >= 120 && eco <= 199
307 > = "Sicilian Defence"
308 > | eco >= 200 && eco <= 219
310 > | eco == 220 = "Rare moves"
311 > | eco == 221 = "Nordic Gambit"
312 > | eco == 222 = "Central Gambit"
313 > | eco >= 223 && eco <= 224
314 > = "Bishop's Opening"
315 > | eco >= 225 && eco <= 229
317 > | eco == 230 = "King's Gambit Declined"
318 > | eco >= 231 && eco <= 232
319 > = "Falkbeer Counter Gambit"
320 > | eco >= 233 && eco <= 239
322 > | eco == 240 = "Latvian Gambit"
323 > | eco == 241 = "Philidor Defence"
324 > | eco >= 242 && eco <= 243
325 > = "Russian Defence-Petrov"
326 > | eco >= 244 && eco <= 245
328 > | eco >= 246 && eco <= 249
330 > | eco == 250 = "Italian Opening"
331 > | eco >= 251 && eco <= 252
333 > | eco >= 253 && eco <= 254
334 > = "Italian Opening"
335 > | eco >= 255 && eco <= 259
336 > = "Two Knight's Play"
337 > | eco >= 260 && eco <= 299
339 > | eco >= 300 && eco <= 305
340 > = "Queen Pawn's Opening"
341 > | eco >= 306 && eco <= 307
343 > | eco >= 308 && eco <= 309
344 > = "Albins Counter Gambit"
345 > | eco >= 310 && eco <= 319
347 > | eco >= 320 && eco <= 329
348 > = "Queen's Gambit Accepted"
349 > | eco >= 330 && eco <= 369
351 > | eco >= 370 && eco <= 399
352 > = "Gruenfeld Defence"
353 > | eco >= 400 && eco <= 409
355 > | eco == 410 = "Blumenfeld Gambit"
356 > | eco >= 411 && eco <= 419
358 > | eco >= 420 && eco <= 459
360 > | eco >= 460 && eco <= 499
364 %------------------------------------------------------------------------------
366 > data MoveNumber = MoveNumber Int Colour
367 > instance Presentable MoveNumber where
368 > userFormat (MoveNumber n White) = show n ++ "."
369 > userFormat (MoveNumber n Black) = show n ++ "..."
371 > initMoveNumber = MoveNumber 1 White
372 > incMove (MoveNumber i White) = MoveNumber i Black
373 > incMove (MoveNumber i Black) = MoveNumber (i+1) White
374 > decMove (MoveNumber i White) = MoveNumber (i-1) Black
375 > decMove (MoveNumber i Black) = MoveNumber i White
376 > getMoveColour :: MoveNumber -> Colour
377 > getMoveColour (MoveNumber _ c) = c
379 %------------------------------------------------------------------------------
383 Both first and second level.
385 > = StringToken String
387 > | LeftABToken -- ??
388 > | RightABToken -- ??
389 > | NAGToken Int -- `normal' NAGS
390 > | NAGAnnToken Int String
391 > -- `special' move annotating NAGS (1-6)
392 > | SymbolToken String
393 > | CommentToken [String] -- list of words
401 Second level Token, as produced by the parser.
403 > | AnalToken [Token]
405 > instance Presentable Token where
406 > userFormat (StringToken str) = show str
407 > userFormat (IntToken n) = show n
408 > userFormat (PeriodToken) = "."
409 > userFormat (AsterixToken) = "*"
410 > userFormat (LeftSBToken) = "["
411 > userFormat (RightSBToken) = "]"
412 > userFormat (LeftRBToken) = "("
413 > userFormat (RightRBToken) = ")"
414 > userFormat (LeftABToken) = "<"
415 > userFormat (RightABToken) = ">"
416 > userFormat (NAGToken i) = "$" ++ show i
417 > userFormat (NAGAnnToken i s) = "$" ++ show i
418 > userFormat (SymbolToken str) = str
419 > userFormat (CommentToken str) = "{" ++ unwords str ++ "}"
420 > userFormat (AnalToken toks) = "( " ++ unwords (map userFormat toks)
423 %------------------------------------------------------------------------------
425 The Parser Emits a list of these.
427 > data Game a = Game [TagStr] [a]
429 > type AbsGame = Game Token
430 > type RealGame = Game Quantum
432 > instance (Presentable a) => Presentable (Game a) where
433 > userFormat (Game tags toks) =
434 > unlines (map userFormat tags
435 > ++ formatText 78 (map userFormat toks))
437 %------------------------------------------------------------------------------
439 Here are the moves that actually can be played,
440 all in the context of a particular board.
445 > BoardPos -- from here
446 > BoardPos -- to here (possibly capturing)
449 > mkPlayMove p f t = PlayMove p f t NothingSpecial
453 > | BigPawnMove -- allows e.p. next move
454 > | Queening Piece -- queen with this
455 > | EnPassant -- capture e.p.
458 > instance Presentable PlayMove where
459 > userFormat (PlayMove piece pos pos' sp) =
460 > userFormat piece ++
461 > userFormatBoardPos pos ++ "-" ++
462 > userFormatBoardPos pos' ++
465 > instance Presentable SpecialMove where
466 > userFormat (NothingSpecial) = ""
467 > userFormat (BigPawnMove) = "{b.p.m.}"
468 > userFormat (Queening p) = "=" ++ userFormat p
469 > userFormat (EnPassant) = "e.p."
471 > extractSrcFromPlayMove :: PlayMove -> BoardPos
472 > extractSrcFromPlayMove (PlayMove _ src _ _) = src
474 > extractDestFromPlayMove :: PlayMove -> BoardPos
475 > extractDestFromPlayMove (PlayMove _ _ dest _) = dest
477 > extractSpecialFromPlayMove :: PlayMove -> SpecialMove
478 > extractSpecialFromPlayMove (PlayMove _ _ _ sp) = sp
481 %------------------------------------------------------------------------------
483 Now the representation of the board itself.
494 > | OffBoard deriving (Eq)
496 > instance Presentable SquareContent where
497 > userFormat Vacant = "."
498 > userFormat Friendly = "*"
499 > userFormat Baddy = "#"
500 > userFormat OffBoard = "?"
503 %------------------------------------------------------------------------------
505 A Static representation of what the current placement of pieces is.
508 > = Board (Array BoardPos BoardSquare)
509 > MoveNumber -- current player & and move
510 > (Maybe ChessFile) -- e.p. possibilties.
512 This is Christmas for foldr/build !
514 > displayBoard :: Colour -> Board -> [String]
515 > displayBoard col (Board arr _ ep) =
516 > ([cjustify 33 (userFormat (changeColour col)),""] ++
518 > concat [ (case (even x,even y) of
519 > (True,True) -> showSq (x `div` 2) (y `div` 2)
520 > (False,False) -> "+"
521 > (True,False) -> "---"
522 > (False,True) -> (if x == 17 then "| " ++ show (y `div` 2) else "|"))
523 > | x <- [1..17::Int]]
524 > | y <- reverse [1..17::Int]] ++
525 > [concat [ " " ++ [x] ++ " " | x <- "abcdefgh" ]] ++
526 > ["",cjustify 33 (userFormat col),"",
529 > Just p -> "EnPassant:" ++ userFormatFile p ])
531 > make n str = take n (str ++ repeat ' ')
532 > lookupPlace :: Int -> Int -> BoardSquare
533 > lookupPlace x' y' = arr ! (x',y')
535 > bold :: String -> String
536 > bold str = map toLower str
538 > showSq x y = case lookupPlace x y of
539 > VacantSq -> [if_dot,if_dot,if_dot]
540 > (WhitesSq p) -> (if_dot : userFormat p) ++ [if_dot]
541 > (BlacksSq p) -> (if_dot : bold (userFormat p)) ++ [if_dot]
543 > if_dot = if (x - y) `rem` 2 == 0 then '.' else ' '
545 > instance Presentable Board where
546 > userFormat = unlines . displayBoard White
548 > boardSize :: (BoardPos,BoardPos)
549 > boardSize = ((1,1),(8,8))
552 This uses forsyth notation.
554 > buildBoard :: String -> Board
555 > buildBoard str = Board brd initMoveNumber Nothing
557 > brd = array boardSize (zipWith (=:) allSq (mkPieces str))
558 > allSq = [ (x,y) | y <- reverse [1..8::Int],x <- [1..8::Int]]
559 > mkPieces :: String -> [BoardSquare]
560 > mkPieces (hd:rest) | hd `elem` "KQRNBPkqrnbp" = pc : mkPieces rest
563 > 'K' -> WhitesSq King
564 > 'Q' -> WhitesSq Queen
565 > 'R' -> WhitesSq Rook
566 > 'N' -> WhitesSq Knight
567 > 'B' -> WhitesSq Bishop
568 > 'P' -> WhitesSq Pawn
569 > 'k' -> BlacksSq King
570 > 'q' -> BlacksSq Queen
571 > 'r' -> BlacksSq Rook
572 > 'n' -> BlacksSq Knight
573 > 'b' -> BlacksSq Bishop
574 > 'p' -> BlacksSq Pawn
575 > mkPieces ('/':rest) = mkPieces rest
576 > mkPieces (c:rest) | isDigit c =
577 > case span isDigit rest of
578 > (cs,rest') -> take (read (c:cs)) (repeat VacantSq)
582 > startBoard :: Board -- the uni before the big bang.
583 > startBoard = buildBoard "rnbqkbnr/pppppppp/32/PPPPPPPP/RNBQKBNR"
585 > lookupSquare :: Colour -> BoardSquare -> SquareContent
586 > lookupSquare _ VacantSq = Vacant
587 > lookupSquare White (WhitesSq p) = Friendly
588 > lookupSquare Black (WhitesSq p) = Baddy
589 > lookupSquare White (BlacksSq p) = Baddy
590 > lookupSquare Black (BlacksSq p) = Friendly
592 > lookupBoard :: Board -> BoardPos -> SquareContent
593 > lookupBoard (Board arr col _) pos =
594 > if inRange boardSize pos
595 > then lookupSquare (getMoveColour col) (arr ! pos)
598 > lookupBoardSquare :: Board -> BoardPos -> BoardSquare
599 > lookupBoardSquare (Board arr _ _) pos = arr ! pos
601 > getSquarePiece :: BoardSquare -> Maybe Piece
602 > getSquarePiece VacantSq = Nothing
603 > getSquarePiece (WhitesSq p) = Just p
604 > getSquarePiece (BlacksSq p) = Just p
606 > lookupBoardPiece :: Board -> BoardPos -> Maybe Piece
607 > lookupBoardPiece (Board arr _ _) pos =
609 > VacantSq -> Nothing
610 > WhitesSq piece -> Just piece
611 > BlacksSq piece -> Just piece
613 This will improve sharing, by
615 > {-# INLINE mkColBoardSq #-}
616 > mkColBoardSq :: Colour -> Piece -> BoardSquare
617 > mkColBoardSq White p = WhitesSq p
618 > mkColBoardSq Black p = BlacksSq p
620 > getBoardColour (Board _ mv _) = getMoveColour mv