принципу.
Основные функции
play :: IO()
play =greetings >>setup >>=gameLoop
gameLoop :: Game -> IO()
gameLoop game
|isGameOver game
=showResults game >>setup >>=gameLoop
|otherwise
=showGame game >>askForMove >>=reactOnMove game
setup :: IO Game
setup =putStrLn ”Начнём новую игру?” >>
putStrLn ”Укажите сложность (положительное целое число): ” >>
getLine >>=maybe setup shuffle .readInt
Запросы от пользователя (getLine)
reactOnMove :: Game -> Query -> IO()
reactOnMove game query = casequery of
Quit
->quit
NewGamen
->gameLoop =<<shuffle n
Play
m
->gameLoop $move m game
askForMove :: IO Query
askForMove =showAsk >>
getLine >>=maybe askAgain return .parseQuery
whereaskAgain =wrongMove >>askForMove
parseQuery :: String -> Maybe Query
parseQuery =un
readInt :: String -> Maybe Int
readInt =un
Ответы пользователю (putStrLn)
greetings :: IO()
greetings =un
showResults :: Game -> IO()
showResults g =showGame g >>putStrLn ”Игра окончена.”
showGame :: Game -> IO()
showGame =putStrLn .show
showAsk :: IO()
showAsk =un
quit :: 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 = casex 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 talk
wheretalk =[
”Возможные ходы пустой клетки:”,
”
left
или l
-- налево”,
”
right
или r
-- направо”,
”
up
или u
-- вверх”,
”
down
или d
-- вниз”,
”Другие действия:”,
”
new int
или n int -- начать новую игру, int - целое число,”,
”указывающее на сложность”,
”
quit
или q
-- выход из игры”]
Проверим работоспособность:
Prelude> :l Loop
[1 of2] Compiling Game
( Game.hs, interpreted )
[2 of2] Compiling Loop
( Loop.hs, interpreted )
Loop.hs :46 :28 :
Ambiguousoccurrence ‘ Left’
Itcould refer to either ‘ Prelude.Left’,
imported from ‘ Prelude’at Loop.hs :1 :8 -11
(and originally defined in‘ Data.Either’)
or ‘ Game.Left’,
imported from ‘ Game’at Loop.hs :5 :1 -11
(and originally defined at Game.hs :10 :25 -28)
Loop.hs :47 :28 :
Ambiguousoccurrence ‘ Left’
...
...
Failed, modules loaded : Game.
*Game>
По ошибкам видно, что произошёл конфликт имён. Конструкторы Leftи Rightуже определены в Prelude.
Это конструкторы типа Either. Давайте скроем их, добавим в модуль такую строчку:
import Prelude hiding( Either( ..))
Пятнашки | 209
Теперь проверим:
*Game> :r
[2 of2] Compiling Loop
( Loop.hs, interpreted )
Ok, modules loaded : Game, Loop.
Читать дальше