Читаем Учебник по Haskell полностью

Игра перемешивается согласно правилам, поэтому функцию 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 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, то определение

окажется совсем простым:

-- в модуль 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 of

Quit

->

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 of

Quit

-> quit

NewGame n

-> gameLoop =<< shuffle n

Play

m

-> gameLoop $ move m game

Слушаем игрока

Теперь всё же вернёмся к функции askForMove, научимся слушать пользователя. Сначала мы скажем

какую-нибудь вводную фразу, предложение ходить (showAsk) затем запросим строку стандартной функцией

getLine, потом нам нужно будет распознать (parseQuery) в строке значение типа Query. Если распознать его

Перейти на страницу:

Похожие книги

C++: базовый курс
C++: базовый курс

В этой книге описаны все основные средства языка С++ - от элементарных понятий до супервозможностей. После рассмотрения основ программирования на C++ (переменных, операторов, инструкций управления, функций, классов и объектов) читатель освоит такие более сложные средства языка, как механизм обработки исключительных ситуаций (исключений), шаблоны, пространства имен, динамическая идентификация типов, стандартная библиотека шаблонов (STL), а также познакомится с расширенным набором ключевых слов, используемым в .NET-программировании. Автор справочника - общепризнанный авторитет в области программирования на языках C и C++, Java и C# - включил в текст своей книги и советы программистам, которые позволят повысить эффективность их работы. Книга рассчитана на широкий круг читателей, желающих изучить язык программирования С++.

Герберт Шилдт

Программирование, программы, базы данных