окажется совсем простым:
-- в модуль 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 = casequery of
Quit
->
NewGamen
->
Play
m
->
Рассмотрим каждый из случаев. В первом случае пользователь говорит, что ему надоело и он уже наиг-
рался. Чтож попрощаемся и вернём значение единичного типа.
206 | Глава 13: Поиграем
...
Quit
->quit
...
quit :: IO()
quit =putStrLn ”До встречи.” >>return ()
В следующем варианте пользователь хочет начать всё заново. Так начнём!
NewGamen
->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 = casequery of
Quit
->quit
NewGamen
->gameLoop =<<shuffle n
Play
m
->gameLoop $move m game
Слушаем игрока
Теперь всё же вернёмся к функции askForMove, научимся слушать пользователя. Сначала мы скажем
какую-нибудь вводную фразу, предложение ходить (showAsk) затем запросим строку стандартной функцией
getLine, потом нам нужно будет распознать (parseQuery) в строке значение типа Query. Если распознать его
нам не удастся, мы напомним пользователю как с нами общаться (remindMoves) и попросим сходить вновь:
askForMove :: IO Query
askForMove =showAsk >>
getLine >>=maybe askAgain return .parseQuery
whereaskAgain =wrongMove >>askForMove
parseQuery :: String -> Maybe Query
parseQuery =un
wrongMove :: IO()
wrongMove =putStrLn ”Не могу распознать ход.” >>remindMoves
showAsk :: IO()
showAsk =un
remindMoves :: IO()
remindMoves =un
Механизм распознавания похож на случай с распознаванием числа. Значение завёрнуто в тип Maybe. И в
самом деле функция определена лишь частично, ведь не все строки кодируют то, что нам нужно.
Функции parseQuery и remindMoves тесно связаны. В первой мы распознаём ввод пользователя, а во вто-
рой напоминаем пользователю как мы закодировали его запросы. Тут стоит остановиться и серьёзно поду-
мать. Как закодировать значения типа Query, чтобы пользователю было удобно набирать их? Но давайте
отвлечёмся от этой задачи, она слишком серьёзная. Оставим её на потом, а пока проверим не ушли ли мы
слишком далеко, возможно наша программа потеряла смысл. Проверим типы!
*Loop> :r
[1 of2] Compiling Game
( Game.hs, interpreted )
[2 of2] Compiling Loop
( Loop.hs, interpreted )
Ok, modules loaded : Game, Loop.
Пятнашки | 207
Приведём код в порядок
Нам осталось дописать функции распознавания запросов и несколько маленьких функций с фразами и
модуль Loopбудет готов. Но перед тем как сделать это давайте упорядочим функции. Видно, что у нас выде-
лилось несколько задач по типу общения с пользователем. У нас есть задачи, в которых мы что-то показываем
пользователю, меняем состояние экрана и есть задачи, в которых мы просим от пользователя какие-то дан-
ные, ожидаем запросы функцией getLine. Также в самом верху выражения программы у нас расположены
функции, которые координируют действия остальных, это третья группа. Сгруппируем функции по этому
Читать дальше