нам не удастся, мы напомним пользователю как с нами общаться (remindMoves) и попросим сходить вновь:
askForMove :: IO Query
askForMove =
showAsk >>getLine >>=
maybe askAgain return . parseQuerywhere
askAgain = wrongMove >> askForMoveparseQuery :: String -> Maybe Query
parseQuery =
unwrongMove :: IO
()wrongMove =
putStrLn ”Не могу распознать ход.” >> remindMovesshowAsk :: IO
()showAsk =
unremindMoves :: IO
()remindMoves =
unМеханизм распознавания похож на случай с распознаванием числа. Значение завёрнуто в тип Maybe
. И всамом деле функция определена лишь частично, ведь не все строки кодируют то, что нам нужно.
Функции parseQuery и remindMoves тесно связаны. В первой мы распознаём ввод пользователя, а во вто-
рой напоминаем пользователю как мы закодировали его запросы. Тут стоит остановиться и серьёзно поду-
мать. Как закодировать значения типа Query
, чтобы пользователю было удобно набирать их? Но давайтеотвлечёмся от этой задачи, она слишком серьёзная. Оставим её на потом, а пока проверим не ушли ли мы
слишком далеко, возможно наша программа потеряла смысл. Проверим типы!
*Loop> :
r[1 of
2] Compiling Game( Game.
hs, interpreted )[2 of
2] Compiling Loop( Loop.
hs, interpreted )Ok
, modules loaded: Game, Loop.Пятнашки | 207
Приведём код в порядок
Нам осталось дописать функции распознавания запросов и несколько маленьких функций с фразами и
модуль Loop
будет готов. Но перед тем как сделать это давайте упорядочим функции. Видно, что у нас выде-лилось несколько задач по типу общения с пользователем. У нас есть задачи, в которых мы что-то показываем
пользователю, меняем состояние экрана и есть задачи, в которых мы просим от пользователя какие-то дан-
ные, ожидаем запросы функцией getLine. Также в самом верху выражения программы у нас расположены
функции, которые координируют действия остальных, это третья группа. Сгруппируем функции по этому
принципу.
Основные функции
play :: IO
()play =
greetings >> setup >>= gameLoopgameLoop :: Game -> IO
()gameLoop game
|
isGameOver game=
showResults game >> setup >>= gameLoop|
otherwise=
showGame game >> askForMove >>= reactOnMove gamesetup :: IO Game
setup =
putStrLn ”Начнём новую игру?” >>putStrLn ”Укажите сложность (положительное целое число): ” >>
getLine >>=
maybe setup shuffle . readIntЗапросы от пользователя (getLine)
reactOnMove :: Game -> Query -> IO
()reactOnMove game query = case
query ofQuit
->
quitNewGame
n->
gameLoop =<< shuffle nPlay
m
->
gameLoop $ move m gameaskForMove :: IO Query
askForMove =
showAsk >>getLine >>=
maybe askAgain return . parseQuerywhere
askAgain = wrongMove >> askForMoveparseQuery :: String -> Maybe Query
parseQuery =
unreadInt :: String -> Maybe Int
readInt =
unОтветы пользователю (putStrLn)
greetings :: IO
()greetings =
unshowResults :: Game -> IO
()showResults g =
showGame g >> putStrLn ”Игра окончена.”showGame :: Game -> IO
()showGame =
putStrLn . showshowAsk :: IO
()showAsk =
unquit :: IO
()quit =
putStrLn ”До встречи.” >> return ()По этим функциям видно, что нам немного осталось. Теперь вернёмся к запросам пользователя.
Формат запросов
Можно вывести с помощью deriving
экземпляр класса Read для типа Query и читать их функцией read.Но это плохая идея, потому что пользователь нашей программы может и не знать Haskell. Лучше введём
сокращённые имена для всех значений. Например такие:
208 | Глава 13: Поиграем
left
-- Play Left
right
-- Play Rigth
up
-- Play Up
down
-- Play Down
quit
-- Quit
new n
-- NewGame n
Можно обратить внимание на то, что все команды начинаются с разных букв. Воспользуемся этим и дадим
пользователю возможность набирать команды одной буквой. Это приводит на с к таким определениям для
функций разбора значения и напоминания ходов:
parseQuery :: String -> Maybe Query
parseQuery x = case
x of”up”
-> Just $ Play Up
”u”
-> Just $ Play Up
”down”
-> Just $ Play Down
”d”
-> Just $ Play Down
”left”
-> Just $ Play Left
”l”
-> Just $ Play Left
”right” -> Just $ Play Right
”r”
-> Just $ Play Right
”quit”
-> Just $ Quit
”q”
-> Just $ Quit
’n’:
’e’:’w’:’ ’:n-> Just . NewGame =<<
readInt n’n’:
’ ’:n-> Just . NewGame =<<
readInt n_
-> Nothing
remindMoves :: IO
()remindMoves =
mapM_ putStrLn talkwhere
talk = [”Возможные ходы пустой клетки:”,
”
left
или l
-- налево”,
”
right
или r
-- направо”,
”
up
или u
-- вверх”,
”
down
или d
-- вниз”,
”Другие действия:”,
”
new int
или n int -- начать новую игру, int - целое число,”,
”указывающее на сложность”,
”
quit
или q
-- выход из игры”]
Проверим работоспособность: