<commit_before>{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}

module Bot.NetHack.InferWorldState
  ( inferWorldState
  , inferSquare
  , inferSearch
  , inferPeaceful
  , inferItemPileImage
  , dirtyInventory
  , setLastSearchedPosition
  , nameToItem
  , levelSquares )
  where

import Bot.NetHack.InferWorldState.PeacefulCheck
import Bot.NetHack.Logs
import Bot.NetHack.MonadAI
import Bot.NetHack.ScreenPattern
import Bot.NetHack.WorldState
import Control.Lens hiding ( Level, levels )
import Control.Monad.State.Strict
import qualified Data.Array.MArray as A
import qualified Data.Array.ST as A
import Data.Foldable
import qualified Data.IntMap.Strict as IM
import Data.List
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid
import qualified Data.Set as S
import qualified Data.Text as T
import Text.Regex.TDFA hiding ( match )
import Data.Traversable ( for )
import Terminal.Screen

-- | Uses what is currently seen on the screen to infer and update bot's world
-- state (see `WorldState`).
inferWorldState :: MonadAI m => [T.Text] -> WorldState -> m WorldState
inferWorldState messages = execStateT $ do
  inferTurnCount
  inferStatuses
  inferCurrentLevel messages

inferTurnCount :: (MonadAI m, MonadState WorldState m) => m ()
inferTurnCount =
  matchf (regex " T:([0-9]+) ") >>= \case
    [_whole, read -> turncount] ->
      logTrace ("Turn is " <> show turncount) $ turn .= turncount
    x -> logTrace ("Couldn't read turn count " <> show x) $ return ()

inferLevelIndex :: MonadAI m => StateT WorldState m ()
inferLevelIndex = do
  cl <- use currentLevel
  expected_description <- use (levelMeta.at cl._Just.levelDescription)

  last_line <- getScreenLine 23

  let lvl_description = T.strip $ fst $ T.breakOn "$:" last_line
  if lvl_description == expected_description
    then return ()
    else do send "#overview\n"
            lookForYouAreHere lvl_description
 where
  lookForYouAreHere lvl_description = do
    -- Use the --More-- prompt to figure the column where branches are
    -- listed.
    Just dd <- matchf "--More--"

    let branch_column = if x dd == 1
                          then 0
                          else x dd

        branchFolder branchmap row = do line <- getScreenLine' row branch_column
                                        return $ if T.head line /= ' '
                                          then M.insert row (T.strip line) branchmap
                                          else branchmap

    -- We don't actually look at the level names at all. Just branch
    -- names and where "You are here" sign is.
    -- This builds a map of branch names on the screen, indexed by row
    branch_names <- foldlM branchFolder M.empty [0..y dd-1]

    -- Look for the "You are here" sign.
    matchf "<- You are here." >>= \case
      -- Maybe on next page?
      Nothing -> do send " "
                    lookForYouAreHere lvl_description
      Just you_are_here_row -> do
        let Just (_, branchname) = M.lookupLE (y you_are_here_row) branch_names

        -- Do we know this level?
        levelmetas <- use levelMeta

        let looper [] = return False
            looper ((lvlindex, meta):rest) = if meta^.levelDescription == lvl_description &&
                                                meta^.branchName == branchname
                                               then do currentLevel .= lvlindex
                                                       return True
                                               else looper rest

        found_level <- looper (IM.assocs levelmetas)
        unless found_level $ do
          let new_index = if IM.null levelmetas
                            then 1
                            else let (largest_key, _) = IM.findMax levelmetas
                                  in largest_key+1

          levelMeta.at new_index .= Just (LevelMeta { _levelDescription = lvl_description
                                                    , _branchName = branchname })
          levels.at new_index .= Just emptyLevel
          currentLevel .= new_index

        -- Dismiss --More--s
        dismissMores

  dismissMores = do
    b <- matchf "--More--"<commit_msg>Log about entering new levels.<commit_after>{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}

module Bot.NetHack.InferWorldState
  ( inferWorldState
  , inferSquare
  , inferSearch
  , inferPeaceful
  , inferItemPileImage
  , dirtyInventory
  , setLastSearchedPosition
  , nameToItem
  , levelSquares )
  where

import Bot.NetHack.InferWorldState.PeacefulCheck
import Bot.NetHack.Logs
import Bot.NetHack.MonadAI
import Bot.NetHack.ScreenPattern
import Bot.NetHack.WorldState
import Control.Lens hiding ( Level, levels )
import Control.Monad.State.Strict
import qualified Data.Array.MArray as A
import qualified Data.Array.ST as A
import Data.Foldable
import qualified Data.IntMap.Strict as IM
import Data.List
import qualified Data.Map.Strict as M
import Data.Maybe
import Data.Monoid
import qualified Data.Set as S
import qualified Data.Text as T
import Text.Regex.TDFA hiding ( match )
import Data.Traversable ( for )
import Terminal.Screen

-- | Uses what is currently seen on the screen to infer and update bot's world
-- state (see `WorldState`).
inferWorldState :: MonadAI m => [T.Text] -> WorldState -> m WorldState
inferWorldState messages = execStateT $ do
  inferTurnCount
  inferStatuses
  inferCurrentLevel messages

inferTurnCount :: (MonadAI m, MonadState WorldState m) => m ()
inferTurnCount =
  matchf (regex " T:([0-9]+) ") >>= \case
    [_whole, read -> turncount] ->
      logTrace ("Turn is " <> show turncount) $ turn .= turncount
    x -> logTrace ("Couldn't read turn count " <> show x) $ return ()

inferLevelIndex :: MonadAI m => StateT WorldState m ()
inferLevelIndex = do
  cl <- use currentLevel
  expected_description <- use (levelMeta.at cl._Just.levelDescription)

  last_line <- getScreenLine 23

  let lvl_description = T.strip $ fst $ T.breakOn "$:" last_line
  if lvl_description == expected_description
    then return ()
    else do send "#overview\n"
            lookForYouAreHere lvl_description
 where
  lookForYouAreHere lvl_description = do
    -- Use the --More-- prompt to figure the column where branches are
    -- listed.
    Just dd <- matchf "--More--"

    let branch_column = if x dd == 1
                          then 0
                          else x dd

        branchFolder branchmap row = do line <- getScreenLine' row branch_column
                                        return $ if T.head line /= ' '
                                          then M.insert row (T.strip line) branchmap
                                          else branchmap

    -- We don't actually look at the level names at all. Just branch
    -- names and where "You are here" sign is.
    -- This builds a map of branch names on the screen, indexed by row
    branch_names <- foldlM branchFolder M.empty [0..y dd-1]

    -- Look for the "You are here" sign.
    matchf "<- You are here." >>= \case
      -- Maybe on next page?
      Nothing -> do send " "
                    lookForYouAreHere lvl_description
      Just you_are_here_row -> do
        let Just (_, branchname) = M.lookupLE (y you_are_here_row) branch_names

        -- Do we know this level?
        levelmetas <- use levelMeta

        let looper [] = return False
            looper ((lvlindex, meta):rest) = if meta^.levelDescription == lvl_description &&
                                                meta^.branchName == branchname
                                               then do currentLevel .= lvlindex
                                                       return True
                                               else looper rest

        found_level <- looper (IM.assocs levelmetas)
        unless found_level $ logTrace ("Entered a new level " <> show (lvl_description, branchname)) $ do
          let new_index = if IM.null levelmetas
                            then 1
                            else let (largest_key, _) = IM.findMax levelmetas
                                  in largest_key+1

          levelMeta.at new_index .= Just (LevelMeta { _levelDescription = lvl_description
                                                    , _branchName = branchname })
          levels.at new_index .= Just emptyLevel
          currentLevel .= new_index

        -- Dismiss --More--s
        dismissMores

  dismissMores = do
    b <- matchf "--More--"
{'fraction_non_alphanumeric': 0.08334286040928318, 'fraction_numerical': 0.002286498227963873, 'mean_word_length': 1.9969167523124358, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2218098', 'n_tokens_mistral': 2471, 'n_tokens_neox': 2365, 'n_words': 771}