haskell-notes

ментов. В С пишут и чистые функции, такие функции переносятся в Haskell без потери чистоты, но это не

всегда возможно.

В этой главе мы напишем небольшую 2D-игру, подключив две FFI-библиотеки, это графическая библио-

тека OpenGL и физический движок Chipmunk.

Описание игры

Игра происходит на бильярдной доске. Игрок управляет красным шаром, кликнув в любую точку экрана,

он может изменить направление вектора скорости красного шара. Шар покатится туда, куда кликнул пользо-

ватель в последний раз. Из луз будут вылетать шары трёх типов: синие, зелёные и оранжевые. Столкновение

красного шара с синим означает минус одну жизнь, с зелёным – плюс одну жизнь, оранжевый шар означает

бонус. Если шар игрока сталкивается с оранжевым шаром все шары в определённом радиусе от места столк-

новения исчезают и записываются в бонусные очки, за каждый шар по одному очку, при этом шар с которым

произошло столкновение не считается. Все столкновения – абсолютно упругие, поэтому при столкновении

энергия сохраняется и шары никогда не остановятся. Если шар попадает в лузу, то он исчезает. Если в лузу

попал шар игрока – это означает, что игра окончена. Игрок стартует с несколькими жизнями, когда их чис-

ло подходит к нулю игра останавливается. После столкновения с зелёным шаром, шар пропадает, а после

столкновения с синим – нет. В итоге все против игрока, кроме зелёных и оранжевых шаров.

20.1 Основные библиотеки

Контролировать физику игрового мира будет библиотека Chipmunk, а библиотека OpenGL будет рисовать

(конечно если мы её этому научим). Пришло время с ними познакомится.

288 | Глава 20: Императивное программирование

Изменяемые значения

Перед тем как мы перейдём к библиотекам нам нужно узнать ещё кое-что. В Haskell мы не можем изменять

значения. Но в С это делается постоянно, а соответственно и в библиотеках написанных на С тоже. Для того

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

объявить изменяемое значение и обновлять его, но только в пределах типа IO.

IORef

Тип IORef из модуля Data.IORef описывает изменяемые значения:

newIORef :: a -> IO IORef

readIORef

:: IORef a -> IO a

writeIORef

:: IORef a -> a -> IO ()

modifyIORef :: IORef a -> (a -> a) -> IO ()

Функция newIORef создаёт изменяемое значение и инициализирует его некоторым значением, кото-

рые мы можем считать с помощью функции readIORef или обновить с помощью функций writeIORef или

modifyIORef. Посмотрим как это работает:

module Main where

import Data.IORef

main = var >>= (v ->

readIORef v >>= print

>> writeIORef v 4

>> readIORef v >>= print)

where var = newIORef 2

Теперь посмотрим на ответ ghci:

*Main> :l HelloIORef

[1 of 1] Compiling Main

( HelloIORef. hs, interpreted )

Ok, modules loaded: Main.

*Main> main

2

4

Самое время вернуться к главе 17 и вспомнить о do-нотации. Такой императивный код гораздо нагляднее

писать так:

main = do

var <- newIORef 2

x <- readIORef var

print x

writeIORef var 4

x <- readIORef var

print x

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

тивный язык. Такие переменные встречаются очень часто в библиотеках, заимствованных из~С.

StateVar

В модуле Data.StateVar определены типы, которые накладывают ограничение на права по чтению и

записи. Мы можем определять переменные доступные только для чтения (GettableStateVar a), только для

записи (SettableStateVar a) или обычные изменяемые переменные (SetVar a).

Операции чтения и записи описываются с помощью классов:

class HasGetter s where

get :: s a -> IO a

class HasSetter s where

($=) :: s a -> a -> IO ()

Основные библиотеки | 289

Тип IORef принадлежит и тому, и другому классу:

main = do

var <- newIORef 2

x

<- get var

print x

var $= 4

x

<- get var

print x

OpenGL

OpenGL является ярким примером библиотеки построенной на изменяемых переменных. OpenGL можно

представить как большой конечный автомат. Каждая строчка кода – это запрос на изменение состояния. При-

чём этот автомат является глобальной переменной. Его текущее состояние зависит от всей цепочки преды-

дущих команд. Параметры рисования задаются глобальными переменными (тип StateVar).

OpenGL не зависит от конкретной оконной системы, она отвечает лишь за рисование. Для того чтобы

создать окно и перехватывать в нём действия пользователя нам понадобится отдельная библиотека. Для

этого мы воспользуемся GLFW, это библиотека также пришла в Haskell из С. Интерфейсы GLFW и OpenGL очень

похожи. Мы будем обновлять различные параметры библиотеки с помощью типа StateVar. Давайте создадим

окно и закрасим фон белым цветом:

module Main where

import Graphics.UI.GLFW

import Graphics.Rendering.OpenGL

import System.Exit

title = ”Hello OpenGL”

width

= 700

height

= 600

main = do

initialize

openWindow (Size width height) [] Window

windowTitle $= title

clearColor $= Color4 1 1 1 1

windowCloseCallback $= exitWith ExitSuccess

loop

loop = do

display

loop

display = do

clear [ColorBuffer]

swapBuffers

Мы инициализируем GLFW, задаём параметры окна. Устанавливаем цвет фона. Цвет имеет четыре пара-

метра это RGB-цвета и параметр прозрачности. Затем мы говорим, что программе делать при закрытии окна.

Страницы: 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