Игра перемешивается согласно правилам, поэтому функцию shuffle мы поселим в модуле Game
. А функ-ция readInt это скорее элемент взаимодействия с пользователем, ведь в ней мы распознаём число в строчном
ответе, она останется в модуле Loop
.Проверим работает ли наша программа:
*Loop> :
r[1 of
2] Compiling Game( Game.
hs, interpreted )[2 of
2] 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 gameshowResults :: Game -> IO
()showResults =
unshowGame :: Game -> IO
()showGame =
unПятнашки | 205
askForMove :: IO Query
askForMove =
unreactOnMove :: 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, то определениеокажется совсем простым:
-- в модуль Loop
showGame :: Game -> IO
()showGame =
putStrLn . show-- в модуль Game
instance Show Game where
show =
unРеакция на реплики пользователя
Теперь нужно определить функции askForMove и reactOnMove. Первая функция требует установить про-
токол реплик пользователя, в каком виде он будет набирать значения типа Query
. Нам пока лень об этомдумать и мы перейдём к функции reactOnMove. Вспомним её тип:
reactOnMove :: Game -> Query -> IO
()Функция принимает текущее положение и запрос пользователя. И ничего не возвращает, она продолжает
игру. В любом случае в этой функции будет сопоставление с образцом по запросам пользователя так что
можно написать:
reactOnMove :: Game -> Query -> IO
()reactOnMove game query = case
query ofQuit
->
NewGame
n->
Play
m
->
Рассмотрим каждый из случаев. В первом случае пользователь говорит, что ему надоело и он уже наиг-
рался. Чтож попрощаемся и вернём значение единичного типа.
206 | Глава 13: Поиграем
...
Quit
->
quit...
quit :: IO
()quit =
putStrLn ”До встречи.” >> return ()В следующем варианте пользователь хочет начать всё заново. Так начнём!
NewGame
n->
gameLoop =<< shuffle nМы вызвали функцию перемешивания shuffle с заданным уровнем сложности. И рекурсивно вызвали
цикл игры с новой позицией. Всё началось по новой. В третьей альтернативе пользователь делает ход, на это
мы должны обновить позицию запустить цикл игры с новым значением:
-- в модуль Loop
Play
m
->
gameLoop $ move m game-- в модуль Game
move :: Move -> Game -> Game
move =
unФункция move обновляет согласно правилам текущую позицию. Соберём все определения вместе:
reactOnMove :: Game -> Query -> IO
()reactOnMove game query = case
query ofQuit
->
quitNewGame
n->
gameLoop =<< shuffle nPlay
m
->
gameLoop $ move m gameСлушаем игрока
Теперь всё же вернёмся к функции askForMove, научимся слушать пользователя. Сначала мы скажем
какую-нибудь вводную фразу, предложение ходить (showAsk) затем запросим строку стандартной функцией
getLine, потом нам нужно будет распознать (parseQuery) в строке значение типа Query
. Если распознать его