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

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

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-цвета и параметр прозрачности. Затем мы говорим, что программе делать при закрытии окна.

Мы устанавливаем функцию обратного вызова (callback) windowCloseCallback. В самом конце мы входим в

цикл, который только и делает, что стирает окно цветом фона и делает рабочий буфер видимым. Что такое

буфер? Буфер – это место в котором мы рисуем. У нас есть два буфера. Один мы показываем пользователю,

а в другом в это в время рисуем, когда приходит время обновлять картинку мы просто меняем их местами

командой swapBuffers.

Посмотрим, что у нас получилось:

$ ghc --make HelloOpenGL.hs

$ ./HelloOpenGL

Нарисуем упрощённое начальное положение нашей игры: прямоугольную рамку и в ней – красный шар:

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

module Main where

import Graphics.UI.GLFW

import Graphics.Rendering.OpenGL

import System.Exit

title = ”Hello OpenGL”

width, height :: GLsizei

width

= 700

height

= 600

w2, h2 :: GLfloat

w2 = (fromIntegral $ width) / 2

h2 = (fromIntegral $ height)

/ 2

dw2, dh2 :: GLdouble

dw2 = fromRational $ toRational w2

dh2 = fromRational $ toRational h2

main = do

initialize

openWindow (Size width height) [] Window

windowTitle $= title

clearColor $= Color4 1 1 1 1

ortho (-dw2-50) (dw2+50) (-dh2-50) (dh2+50) (-1) 1

windowCloseCallback $= exitWith ExitSuccess

windowSizeCallback

$= (\size -> viewport $= (Position 0 0, size))

loop

loop = do

display

loop

display = do

clear [ColorBuffer]

color black

line (-w2) (-h2) (-w2) h2

line (-w2) h2

w2

h2

line w2

h2

w2

(-h2)

line w2

(-h2)

(-w2) (-h2)

color red

circle 0 0 10

swapBuffers

vertex2f :: GLfloat -> GLfloat -> IO ()

vertex2f a b = vertex (Vertex3 a b 0)

-- colors

white = Color4 (0::GLfloat)

black = Color4 (0::GLfloat) 0 0 1

red

= Color4 (1::GLfloat) 0 0 1

-- primitives

line :: GLfloat -> GLfloat -> GLfloat -> GLfloat -> IO ()

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

line ax ay bx by = renderPrimitive Lines $ do

vertex2f ax ay

vertex2f bx by

circle :: GLfloat -> GLfloat -> GLfloat -> IO ()

circle cx cy rad =

renderPrimitive Polygon $ mapM_ (uncurry vertex2f) points

where n = 50

points = zip xs ys

xs = fmap (\x -> cx + rad * sin (2*pi*x/n)) [0 .. n]

ys = fmap (\x -> cy + rad * cos (2*pi*x/n)) [0 .. n]

Рис. 20.1: Начальное положение