我在Haskell的地图上移动时遇到问题。如果玩家的输入是plt.show()
,我需要将当前位置更改为新位置,但是我还需要检查是否有位置。
我不确定如何精确地编写BackOff
函数。我还想向玩家展示他们可以从当前位置移动到的位置(一次只能移动一个空间。)
我的第一个想法是检查例如输入是否为move
,并减少TurnLeft
使其变为Direction
(我假设我编程错误)并增加使其变为到West
等。
如何检查玩家想要旅行的位置是否可用以及如何更改位置?
East
答案 0 :(得分:4)
我在设计中看到的第一件事是您拥有全局常量location
和currentDirection
。这与Haskell的工作方式不兼容:由于Haskell纯粹是功能性的,因此您将永远无法更改location
或currentDirection
。相反,您将需要
data Player = Player { location :: Location
, direction :: Direction }
deriving (Eq, Ord, Show, Read)
然后您将希望move
具有类型
move :: Player -> Movement -> Player
第二点要注意的是,您为move
指定了类型Movement -> Direction -> Location
,但实际上并没有给它指定Movement
和Direction
的参数–您只能给它一个参数Movement
。相反,我们想要类似的东西
move :: Player -> Movement -> Player
move p m = case m of {- ... -}
请注意您的代码中没有的其他p
参数。
现在,我喜欢使用pred
和succ
进行车削的概念!不幸的是,问题在于pred
和succ
如果溢出,则会发生错误并爆炸,而不是四处缠绕,因此我们需要编写自己的消息。
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
切换回“表达风格”。再次,同样的事情!)
现在,要前进和后退,我假设您使用的类型为wonderland
和GameMap
的值filterAccess
。 GameMap
有点可疑:如果您希望保持原样,最好使用普通三元组(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
中的高阶函数之一。 (哪个?)