”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
where talk = [
”Возможные ходы пустой клетки:”,
”
left
или l
-- налево”,
”
right
или r
-- направо”,
”
up
или u
-- вверх”,
”
down
или d
-- вниз”,
”Другие действия:”,
”
new int
или n int -- начать новую игру, int - целое число,”,
”указывающее на сложность”,
”
quit
или q
-- выход из игры”]
Проверим работоспособность:
Prelude> :l Loop
[1 of 2] Compiling Game
( Game. hs, interpreted )
[2 of 2] Compiling Loop
( Loop. hs, interpreted )
Loop. hs:46:28:
Ambiguous occurrence ‘Left’
It could 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:
Ambiguous occurrence ‘Left’
...
...
Failed, modules loaded: Game.
*Game>
По ошибкам видно, что произошёл конфликт имён. Конструкторы Left и Right уже определены в Prelude.
Это конструкторы типа Either. Давайте скроем их, добавим в модуль такую строчку:
import Prelude hiding (Either(.. ))
Пятнашки | 209
Теперь проверим:
*Game> :r
[2 of 2] Compiling Loop
( Loop. hs, interpreted )
Ok, modules loaded: Game, Loop.
*Loop>
Всё работает, можно двигаться дальше.
Последние штрихи
В модуле Loop нам осталось определить несколько маленьких функций. Поиск по слову un говорит нам
о том, что осталось определить функции “
greetings
:: IO ()
readInt
:: String -> Maybe Int
showAsk
:: IO ()
Самая простая это функция showAsk, она приглашает игрока сделать ход:
showAsk :: IO ()
showAsk = putStrLn ”Ваш ход: ”
Теперь функция распознавания целого числа:
import Data.Char (isDigit)
...
readInt :: String -> Maybe Int
readInt n
| all isDigit n = Just $ read n
| otherwise
= Nothing
В первой альтернативе мы с помощью стандартной функции isDigit :: Char -> Bool проверяем, что
строка состоит из одних только чисел. Если все символы числа, то мы пользуемся функцией из модуля Read
и читаем целое число, иначе возвращаем Nothing.
Последняя функция, это функция приветствия. Когда игрок входит в игру он сталкивается с её результа-
тами. Определим её так:
-- в модуль Loop
greetings :: IO ()
greetings = putStrLn ”Привет! Это игра пятнашки” >>
showGame initGame >>
remindMoves
-- в модуль Game
initGame :: Game
initGame = un
Сначала мы приветствуем игрока, затем показываем состояние (initGame), к которому ему нужно стре-
миться, и напоминаем как делаются ходы. На этом определении мы раскрыли все выражения в модуле Loop,
нам остался лишь модуль Game.
Правила игры
Определим модуль Game, но мы будем определять его не с чистого листа. Те функции, которые нам нуж-
ны уже определились в ходе описания диалога с пользователем. Нам нужно уметь составлять начальное
состояние initGame, уметь составлять перемешанное состояние игры shuffle, нам нужно уметь реагиро-
вать на ходы move, определять какая позиция является выигрышной isGameOver и уметь показывать фишки
в красивом виде. Приступим!
initGame
:: Game
shuffle
:: Int -> IO Game
isGameOver
:: Game -> Bool
move
:: Move -> Game -> Game
instance Show Game where
show = un
Таков наш план.
210 | Глава 13: Поиграем
Начальное состояние
Начнём с самой простой функции, составим начальное состояние:
initGame :: Game
initGame = Game (3, 3) $ listArray ((0, 0), (3, 3)) $ [0 .. 15]