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]Мы будем кодировать фишки цифрами от нуля до 14, а пустая клетка будет равна 15. Это просто согла-
шения о внутреннем представлении фишек, показывать мы их будем совсем по-другому.
С этим значением мы можем легко определить функцию определения конца игры. Нам нужно только
добавить deriving
(Eq) к типу Game. Тогда функция isGameOver примет вид:isGameOver :: Game -> Bool
isGameOver =
( == initGame)Делаем ход
Напишем функцию:
move :: Move -> Game -> Game
Она обновляет позицию после хода. В пятнашках не во всех позициях доступны все ходы. Если пустышка
находится на краю, мы не можем вывести её за пределы доски. Это необходимо как-то учесть. Каждый ход
задаёт направление обмена фишками. Если у нас есть текущее положение пустышки и ход, то по ходу мы
можем узнать направление, а по направлению ту фишку, которая займёт место пустышки после хода. При
этом нам необходимо проверять находится ли та фишка, которую мы хотим поместить на пустое место в пре-
делах доски. Например если пустышка расположена в самом верху и мы хотим сделать ход Up
(передвинутьеё ещё выше), то положение игры не должно измениться.
import Prelude hiding
(Either(.. ))newtype Vec = Vec
(Int, Int)move :: Move -> Game -> Game
move m (Game
id board)|
within id’ = Game id’ $ board // updates|
otherwise= Game
id boardwhere
id’ = shift (orient m) idupdates =
[(id, board ! id’), (id’, emptyLabel)]-- определение того, что индексы внутри доски
within :: Pos -> Bool
within (a, b) =
p a && p bwhere
p x = x >= 0 && x <= 3