文字冒险游戏问题与方向移动

时间:2018-12-22 23:45:20

标签: haskell

我在Haskell的地图上移动时遇到问题。如果玩家的输入是plt.show() ,我需要将当前位置更改为新位置,但是我还需要检查是否有位置。

我不确定如何精确地编写BackOff函数。我还想向玩家展示他们可以从当前位置移动到的位置(一次只能移动一个空间。)

我的第一个想法是检查例如输入是否为move,并减少TurnLeft使其变为Direction(我假设我编程错误)并增加使其变为到West等。

如何检查玩家想要旅行的位置是否可用以及如何更改位置?

East

2 个答案:

答案 0 :(得分:4)

我在设计中看到的第一件事是您拥有全局常量locationcurrentDirection。这与Haskell的工作方式不兼容:由于Haskell纯粹是功能性的,因此您将永远无法更改locationcurrentDirection。相反,您将需要

data Player = Player { location :: Location
                     , direction :: Direction }
            deriving (Eq, Ord, Show, Read)

然后您将希望move具有类型

move :: Player -> Movement -> Player

第二点要注意的是,您为move指定了类型Movement -> Direction -> Location,但实际上并没有给它指定MovementDirection的参数–您只能给它一个参数Movement。相反,我们想要类似的东西

move :: Player -> Movement -> Player
move p m = case m of {- ... -}

请注意您的代码中没有的其他p参数。

现在,我喜欢使用predsucc进行车削的概念!不幸的是,问题在于predsucc如果溢出,则会发生错误并爆炸,而不是四处缠绕,因此我们需要编写自己的消息。

turnRight :: Direction -> Direction
turnRight North = East
turnRight East  = South
turnRight South = West
turnRight West  = North

turnLeft :: Direction -> Direction
turnLeft North = West
turnLeft East  = North
turnLeft South = East
turnLeft West  = South

在这里,我使用的是“等式”,而不是turnLeft d = case d of ...。这很常见,但是两者是等效的。

因此,在move中,我们有

move :: Player -> Movement -> Player
move p m = case m of
  TurnLeft  -> p{direction = turnLeft  $ direction p}
  TurnRight -> p{direction = turnRight $ direction p}
  Advance   -> undefined
  BackOff   -> undefined

(在这里,我用case切换回“表达风格”。再次,同样的事情!)

现在,要前进和后退,我假设您使用的类型为wonderlandGameMap的值filterAccessGameMap有点可疑:如果您希望保持原样,最好使用普通三元组(Location, Direction, Location)而不是嵌套对。现在,filterAccess有点儿晦涩,因为它只检查起始位置而忽略方向。但是更好的设计可能是使用真实的Map,它可以让您直接通过键进行查找。确保将Ord添加到Direction的派生类中,然后您可以拥有

import Data.Map (Map)
import qualified Data.Map as M

type GameMap = Map Location (Map Direction Location)
  -- Or `Map (Location, Direction) Location`

nextLocation :: Player -> GameMap -> Maybe Direction
nextLocation Player{location = loc, direction = dir} gmap =
  case M.lookup loc gmap of
    Nothing -> Nothing
    Just m' -> M.lookup dir m'
-- Or:
--     nextLocation Player{location = loc, direction = dir} gmap =
--       M.lookup dir =<< M.lookup loc gmap

这应该可以解决这个问题。后退情况将类似,您只需要填写“后退方向”逻辑即可:-)

答案 1 :(得分:2)

给我的印象是您想自己解决这个问题,但这是一个简化的玩具程序,可能会给您一些想法:

import Data.Maybe (fromJust, isNothing)

data Direction = North | South | East | West
  deriving Show

data WorldState = WorldState { longitude :: Integer,
                               latitude :: Integer,
                               facing :: Direction
                             }
  deriving Show

initialState :: WorldState
initialState = WorldState 0 0 West

processCommands :: WorldState ->
                   (WorldState -> String -> Maybe WorldState) ->
                   [String] ->
                   [WorldState]
{- Given an interpreter that executes commands one at a time, and a list of commands
 - to execute, return a list of the WorldState after the execution of each command.
 -}
processCommands _ _ [] = []
processCommands initial interpreter (x:xs)
  | isNothing outcome' = []
  | otherwise          = outcome:(processCommands outcome interpreter xs)
    where outcome' = interpreter initial x
          outcome = fromJust outcome'

runCommand :: WorldState -> String -> Maybe WorldState
{- Given a WorldState and a command, returns the updated WorldState, or
 - Nothing if the program is to quit.
 -}
runCommand _ "quit" = Nothing
runCommand (WorldState a b North) "left" = Just (WorldState a b West)
runCommand (WorldState a b West)  "left" = Just (WorldState a b South)
runCommand (WorldState a b South) "left" = Just (WorldState a b East)
runCommand (WorldState a b East)  "left" = Just (WorldState a b North)
runCommand (WorldState a b North) "right" = Just (WorldState a b East)
runCommand (WorldState a b East)  "right" = Just (WorldState a b South)
runCommand (WorldState a b South) "right" = Just (WorldState a b West)
runCommand (WorldState a b West)  "right" = Just (WorldState a b North)
runCommand (WorldState a b North) "forward" = Just (WorldState a (b+1) North)
runCommand (WorldState a b West)  "forward" = Just (WorldState (a-1) b West)
runCommand (WorldState a b South) "forward" = Just (WorldState a (b-1) South)
runCommand (WorldState a b East)  "forward" = Just (WorldState (a+1) b East)
runCommand (WorldState a b North) "back" = Just (WorldState a (b-1) North)
runCommand (WorldState a b West)  "back" = Just (WorldState (a+1) b West)
runCommand (WorldState a b South) "back" = Just (WorldState a (b+1) South)
runCommand (WorldState a b East)  "back" = Just (WorldState (a-1) b East)
runCommand s _ = Just s

main :: IO ()
main = interact
     ( unlines
     . map show
     . processCommands initialState runCommand
     . lines )

main例程调用interact,该函数将输入映射到输出。由于输入和输出都是延迟读取的,因此输入的每一行都将在输入后立即进行评估。

该程序唯一不起作用的部分是对interact的调用,尤其是,您无需直接使用monad。 (尽管它们没什么好怕的。)只需编写将输入字符串转换为输出字符串的纯函数即可。

所以,我们采取了毫无意义的方式。它将输入分成几行,并在命令列表中调用processCommands,每行一个。

然后,processCommands函数在每个命令上重复调用runCommand,将当前状态传递给它,直到它说不继续或没有更多命令为止。它建立每个连续游戏状态的列表。它是尾递归模态,列表项在创建后就从左到右被消耗,因此除了其参数外,不需要保持任何状态。 (也不是每次调用runCommand都需要。)

此游戏状态列表被传递回main,后者将它们用show转换为可打印的字符串,然后将其格式化为单个输出字符串,并用换行符分隔各个项目。此输出字符串是延迟计算的,因此程序将在已知状态后立即打印每个状态。该程序因此以交互方式运行。

肯定有很多方法可以改善此程序!我敢肯定,您可以考虑一下并尝试一下。

例如,要检查某个方向是否可用,您需要查找代理商试图到达的某种世界地图上的位置,该世界地图可能是静态的,或者作为世界状态的一部分传递并更新。 processCommands实际上只是Data.List中的高阶函数之一。 (哪个?)