*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 ( Gameid board)
|within id’ = Gameid’ $board //updates
|otherwise
= Gameid board
whereid’ =shift (orient m) id
updates =[(id, board !id’), (id’, emptyLabel)]
-- определение того, что индексы внутри доски
within :: Pos -> Bool
within (a, b) =p a &&p b
wherep x =x >=0 &&x <=3
-- смещение положение по направдению
shift :: Vec -> Pos -> Pos
shift ( Vec(va, vb)) (pa, pb) =(va +pa, vb +pb)
-- направление хода
orient :: Move -> Vec
orient m = Vec $ casem of
Up
->( -1, 0)
Down
->(1 , 0)
Left
Читать дальше