haskell-notes

несколько ходов произвольным образом. Количество ходов определяет сложность игры:

shuffle :: Int -> IO Game

shuffle n = (iterate (shuffle1 =<< ) $ pure initGame) !! n

shuffle1 :: Game -> IO Game

shuffle1 = un

Функция shuffle1 перемешивает фишки один раз. С помощью функции iterate мы строим список рас-

становок, которые мы получаем на каждом шаге перемешивания. В самом конце мы выбираем из списка

n-тую позицию. Обратите внимание на то, что мы не можем просто написать:

iterate shuffle1 initGame

Так у нас не совпадут типы. Для функции iterate нужно чтобы вход и выход функции имели одинаковые

типы. Поэтому мы пользуемся в функции iterate методами классов Monad и Applicative (глава 6).

Теперь определим функцию shuffle1. Мы делаем ход в текущей позиции, который мы выбрали случай-

ным образом из списка доступных ходов. Выбором случайного элемента из списка, будет заниматься функция

randomElem, а функция nextMoves будет возвращать список доступных ходов для данного положения:

shuffle1 :: Game -> IO Game

shuffle1 g = flip move g (randomElem $ nextMoves g)

randomElem :: [a] -> IO a

randomElem = un

nextMoves :: Game -> [Move]

nextMoves = un

Нам осталось определить всего две функции, и всё готово для игры. Определим выбор случайного эле-

мента из списка:

import System.Random

randomElem :: [a] -> IO a

randomElem xs = (xs !! ) randomRIO (0, length xs 1)

Мы генерируем случайное число в диапазоне индексов списка и затем извлекаем элемент. Теперь функ-

ция определения ходов в текущем положении:

nextMoves g = filter (within . moveEmptyTo . orient) allMoves

where moveEmptyTo v = shift v (emtyField g)

allMoves = [Up, Down, Left, Right]

Мы выполняем схожие операции с теми, что были в функции move. Мы фильтруем из списка всех ходов

те, что выводят пустую фишку за пределы доски.

212 | Глава 13: Поиграем

Отображение положения

Я немного поторопился, нам осталась ещё одна функция. Это отображение позиции. Я не буду подробно

останавливаться на теле функции, скажу лишь то, что она составляет строку так как это показано в коммен-

тарии к функции.

+—-+—-+—-+—-+

|

1 |

2 |

3 |

4 |

+—-+—-+—-+—-+

|

5 |

6 |

7 |

8 |

+—-+—-+—-+—-+

|

9 | 10 | 11 | 12 |

+—-+—-+—-+—-+

| 13 | 14 | 15 |

|

+—-+—-+—-+—-+

instance Show Game where

show (Game _ board) = ”n” ++ space ++ line ++

(foldr (a b -> a ++ space ++ line ++ b) ”n” $ map column [0 .. 3])

where post id = showLabel $ board ! id

showLabel n

= cell $ show $ case n of

15 -> 0

n

-> n+1

cell ”0”

=

cell [x]

= ’ ’:’ ’: x :’ ’:[]

cell [a,b] = ’ ’: a : b :’ ’:[]

line = ”+—-+—-+—-+—-+n”

nums = ((space ++ ”|”) ++ ) . foldr (a b -> a ++ ”|” ++ b) ”n” .

map post

column i = nums $ map (x -> (i, x)) [0 .. 3]

space = ”t”

Теперь мы можем загрузить модуль Loop в интерпретатор и набрать play. Немного отвлечёмся и поигра-

ем.

Prelude> :l Loop

[1 of 2] Compiling Game

( Game. hs, interpreted )

[2 of 2] Compiling Loop

( Loop. hs, interpreted )

Ok, modules loaded: Loop, Game.

*Loop> play

Привет! Это игра пятнашки

+—-+—-+—-+—-+

|

1 |

2 |

3 |

4 |

+—-+—-+—-+—-+

|

5 |

6 |

7 |

8 |

+—-+—-+—-+—-+

|

9 | 10 | 11 | 12 |

+—-+—-+—-+—-+

| 13 | 14 | 15 |

|

+—-+—-+—-+—-+

Возможные ходы пустой клетки:

left

или l

— налево

right

или r

— направо

up

или u

— вверх

down

или d

— вниз

Другие действия:

new int

или n int — начать новую игру, int — целое число,

указывающее на сложность

quit

или q

— выход из игры

Начнём новую игру?

Укажите сложность (положительное целое число):

5

+—-+—-+—-+—-+

|

1 |

2 |

3 |

4 |

+—-+—-+—-+—-+

|

5 |

6 |

7 |

8 |

+—-+—-+—-+—-+

Пятнашки | 213

|

9 |

| 10 | 11 |

+—-+—-+—-+—-+

| 13 | 14 | 15 | 12 |

+—-+—-+—-+—-+

Ваш ход:

r

+—-+—-+—-+—-+

|

1 |

2 |

3 |

4 |

+—-+—-+—-+—-+

|

5 |

6 |

7 |

8 |

+—-+—-+—-+—-+

|

9 | 10 |

| 11 |

+—-+—-+—-+—-+

| 13 | 14 | 15 | 12 |

+—-+—-+—-+—-+

Ваш ход:

r

+—-+—-+—-+—-+

|

1 |

2 |

3 |

4 |

+—-+—-+—-+—-+

|

5 |

6 |

7 |

8 |

+—-+—-+—-+—-+

|

9 | 10 | 11 |

|

+—-+—-+—-+—-+

| 13 | 14 | 15 | 12 |

+—-+—-+—-+—-+

Ваш ход:

d

+—-+—-+—-+—-+

|

1 |

2 |

3 |

4 |

+—-+—-+—-+—-+

|

5 |

6 |

7 |

8 |

+—-+—-+—-+—-+

|

9 | 10 | 11 | 12 |

+—-+—-+—-+—-+

| 13 | 14 | 15 |

|

+—-+—-+—-+—-+

Игра окончена.

Ураа, получилось. Мы так долго писали программу, проверяя лишь типы, и в самом конце, когда мы

закончили определение, всё работает. Конечно не всё работает так гладко, я уже написал эту программу и

объясняю готовое решение, но когда общая схема программы утряслась, возможные ошибки определяются

на раз. Мы могли вызвать отображение позиции не в том порядке или забыть проверку конца игры, всё это

несколько строчек изменений.

Самые неприятные ошибки происходят, когда в середине выясняется, что мы ошиблись с типами. Типы,

которые мы выбрали не могут описать явление, возможно мы не можем делать какие-то операции, которые

нам, как неожиданно выяснилось, очень нужны. Это значит, что нужно менять каркас. Менять каркас, это

значит сносить весь дом и строить новый. Возможно разрушения окажутся локальными, мы строим не дом,

а город. И сносить придётся не всё, а несколько кварталов. Но это тоже большие перемены. Поэтому шаг

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162