этого модуля. Программа прошла проверку типов, значит она осмысленна и мы можем двигаться дальше.
У нас три варианта дальнейшей детализации это функции greetings, setup и gameLoop. Мы пока пропу-
стим greetings там мы напишем какое-нибудь приветствие и сообщим игроку куда он попал и как ходить.
204 | Глава 13: Поиграем
В функции setup нам нужно начать первую игру. Для начала игры нам нужно узнать её сложность, на
сколько ходов перемешивать позицию. Это значит, что нам нужно спросить у игрока целое число. Мы спро-
сим число функцией getLine, а затем попробуем его распознать. Если пользователь ввёл не число, то мы
попросим его повторить ввод. Функция readInt :: String -> Maybe Intраспознаёт число. Она возвращает
целое число завёрнутое в Maybe, потому что строка может оказаться не числом. Затем это число мы исполь-
зуем в функции shuffle (перемешать), которая будет возвращать позицию, которая перемешана с заданной
глубиной.
-- в модуль Loop
setup :: IO Game
setup =putStrLn ”Начнём новую игру?” >>
putStrLn ”Укажите сложность (положительное целое число): ” >>
getLine >>=maybe setup shuffle .readInt
readInt :: String -> Maybe Int
readInt =un
-- в модуль Game:
shuffle :: Int -> IO Game
shuffle =un
Функция shuffle возвращает состояние игры Game, которое завёрнуто в IO. Оно завёрнуто в IO, потому
что перемешивать позицию мы будем случайным образом, это значит, что мы воспользуемся функциями из
модуля Random. Мы хотим чтобы каждая новая игра начиналась с новой позиции, поэтому скорее всего где-то
в недрах функции shuffle мы воспользуемся newStdGen, которая и потянет за собой тип IO.
Игра перемешивается согласно правилам, поэтому функцию shuffle мы поселим в модуле Game. А функ-
ция readInt это скорее элемент взаимодействия с пользователем, ведь в ней мы распознаём число в строчном
ответе, она останется в модуле Loop.
Проверим работает ли наша программа:
*Loop> :r
[1 of2] Compiling Game
( Game.hs, interpreted )
[2 of2] Compiling Loop
( Loop.hs, interpreted )
Ok, modules loaded : Game, Loop.
*Loop>
Работает! Можно спускаться по дереву выражения ниже. Сейчас нам предстоит написать одну из самых
сложных функций, это функция gameLoop.
13.2 Пятнашки
Цикл игры
Функция цикла игры принимает текущую позицию. При этом у нас два варианта. Возможно игра пришла
в конечное положение (isGameOver) и мы можем сообщить игроку о том, что он победил (showResults), если
это не так, то мы покажем текущее положение (showGame), спросим ход (askForMove) и среагируем на ход
(reactOnMove).
-- в модуль Loop
gameLoop :: Game -> IO()
gameLoop game
|isGameOver game
=showResults game >>setup >>=gameLoop
|otherwise
=showGame game >>askForMove >>=reactOnMove game
showResults :: Game -> IO()
showResults =un
showGame :: Game -> IO()
showGame =un
Пятнашки | 205
askForMove :: IO Query
askForMove =un
reactOnMove :: Game -> Query -> IO()
reactOnMove =un
-- в модуль Game
isGameOver :: Game -> Bool
isGameOver =un
Как определить закончилась игра или нет это скорее дело модуля Game. Все остальные функции принадле-
жат модулю Loop. Функция askForMove возвращает реплику пользователя и тут же направляет её в функцию
reactOnMove. Функции showGame и showResults ничего не возвращают, они только меняют состояния экрана.
После того как игра закончится мы предложим игроку начать новую.
Обратите внимание на то, как даже не дав определение функции, мы всё же очерчиваем её смысл в
объявлении типа. Так посмотрев на функцию askForMove и сопоставив тип с именем, мы можем понять, что
эта функция предназначена для запроса значения типа Query, для запроса реплики пользователя. А по типу
функции showGame мы можем понять, что она проводит какой-то побочный эффект, судя по имени что-то
показывает, из типа видно что показывает значение типа Gameили текущую позицию.
Отображение позиции
Определим функции отображения результата и позиции. Когда игра закончится мы покажем итоговое
положение и объявим результат.
showResults :: Game -> IO()
showResults g =showGame g >>putStrLn ”Игра окончена.”
Теперь определим функцию showGame. Если тип Gameявляется экземпляром класса Show, то определение
Читать дальше