packages feed

hcheckers-0.1.0.2: src/Formats/Compact.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RecordWildCards #-}

module Formats.Compact where

import Control.Monad
import Control.Monad.State
import Data.Char
import Data.Maybe
import qualified Data.Text as T
import Text.Megaparsec hiding (Label, State)
import Text.Megaparsec.Char
import qualified Data.Text.IO as TIO
import Text.Printf

import Core.Types
import Core.Board
import Formats.Types
import Formats.Fen (boardToFen, showFen)
import Formats.Pdn (showPdn, movesToInstructions)
import Rules.Russian

data SemiMove =
    Skip
  | Short Line Line Line
  | Full Label Label
  deriving (Eq, Show)

pSemiMove :: Parser SemiMove
pSemiMove = try pSkip <|> try pShort <|> pFull

pSkip :: Parser SemiMove
pSkip = do
  string "---"
  return Skip

pLetter :: Parser Line
pLetter = do
  let letters = ['a' .. 'l']
  letter <- oneOf letters
  return $ fromIntegral $ ord letter - ord 'a'

pDigit :: Parser Line
pDigit = do
  ch <- digitChar
  return $ fromIntegral $ ord ch - ord '1'

pShort :: Parser SemiMove
pShort = do
  letter1 <- pLetter
  letter2 <- pLetter
  digit <- pDigit
  return $ Short letter1 letter2 digit

pFull :: Parser SemiMove
pFull = do
  letter1 <- pLetter
  digit1 <- pDigit
  char '-'
  letter2 <- pLetter
  digit2 <- pDigit
  return $ Full (Label letter1 digit1) (Label letter2 digit2)

pGame :: Parser [SemiMove]
pGame = try pSemiMove `sepBy` space1

pGames :: Parser [[SemiMove]]
pGames = try pGame `sepBy` char ';'

parseCompactFile :: FilePath -> IO [[SemiMove]]
parseCompactFile path = do
  text <- TIO.readFile path
  forM (T.lines text) $ \line -> do
    case evalState (runParserT pGame path line) Nothing of
      Left err -> fail $ errorBundlePretty err
      Right game -> return game

findMove :: Side -> SemiMove -> Board -> Either String PossibleMove
findMove side (Short colFrom colTo rowTo) board = 
  let suits pm = aLabel (pmEnd pm) == Label colTo rowTo &&
                 labelColumn (aLabel (pmBegin pm)) == colFrom
  in case filter suits (possibleMoves russian side board) of
        [] -> Left $ printf "findMove: no pieces of %s at column %d" (show side) colFrom
        [pm] -> Right pm
        xs -> Left $ printf "findMove: ambigous move: %s" (show xs)

applySemiMove :: Side -> SemiMove -> Board -> Board
applySemiMove _ Skip b = b
applySemiMove _ (Full from to) board =
  let piece = fromJust $ getPiece' from board
      fromA = resolve from board
      toA   = resolve to board
      actions = [Take fromA, Put toA piece]
  in  applyMoveActions actions board
applySemiMove side sm@(Short colFrom colTo rowTo) board =
  case findMove side sm board of
    Left err -> error $ printf "applySemiMove: %s: %s" (show sm) err
    Right pm ->
      applyMoveActions (pmResult pm) board

convertSemiMove :: Side -> Board -> SemiMove -> Maybe SemiMoveRec
convertSemiMove _ _ Skip = Nothing
convertSemiMove _ _ (Full from to) = Just $ ShortSemiMoveRec from to False
convertSemiMove side board sm =
  case findMove side sm board of
    Left err -> error $ printf "convertSemiMove: %s: %s" (show sm) err
    Right pm -> Just $ ShortSemiMoveRec {
                         smrFrom = aLabel (pmBegin pm),
                         smrTo   = aLabel (pmEnd pm),
                         smrCapture = False
                       }

convertMoves :: SupervisorState -> [SemiMove] -> [MoveRec]
convertMoves rnd game = go (initBoard rnd russian) game
  where
    go _ [] = []
    go board0 [sm] =
      let smr = convertSemiMove First board0 sm
          board1 = applySemiMove First sm board0
          move = MoveRec smr Nothing
      in  [move]
    go board0 (sm1 : sm2 : rest) =
      let smr1 = convertSemiMove First board0 sm1
          board1 = applySemiMove First sm1 board0
          smr2 = convertSemiMove Second board1 sm2
          board2 = applySemiMove Second sm2 board1
          move = MoveRec smr1 smr2
      in  move : go board2 rest

gameToBoard :: SupervisorState -> [SemiMove] -> (Side, Board)
gameToBoard rnd game = go First (initBoard rnd russian) game
  where
    go side board [] = (side, board)
    go side board (sm : rest) =
      let board' = applySemiMove side sm board
      in  go (opposite side) board' rest

compactFileToFen :: SupervisorState -> FilePath -> IO ()
compactFileToFen rnd path = do
  games <- parseCompactFile path
  forM_ (zip [1.. ] games) $ \(i, game) -> do
    if null game
      then printf "empty game: %d" i
      else do
        let (side, board) = gameToBoard rnd game
            fen = boardToFen side board
            fenText = showFen (boardSize russian) fen
            targetPath = printf "draw%d.fen" (i :: Int)
        TIO.writeFile targetPath fenText

compactFileToPdn :: SupervisorState -> FilePath -> IO ()
compactFileToPdn rnd path = do
  games <- parseCompactFile path
  forM_ (zip [1.. ] $ filter (not . null) games) $ \(i, game) -> do
    let targetPath = printf "draw%d.pdn" (i :: Int)
        pdn = GameRecord tags (movesToInstructions moves) Nothing
        moves = convertMoves rnd game
        rules = SomeRules russian
        tags = [Event "Game Opening", GameType rules]
        pdnText = showPdn rules pdn
    TIO.writeFile targetPath pdnText