Выбрать главу

”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]