vertex2f :: G.GLfloat -> G.GLfloat -> IO()
vertex2f a b = G.vertex ( G.Vertex3a b 0)
vec2gl :: H.Vector ->( G.GLfloat, G.GLfloat)
vec2gl ( H.Vectorx y) =(d2gl x, d2gl y)
d2gl :: Double -> G.GLfloat
d2gl =realToFrac
d2gli :: Double -> G.GLsizei
d2gli =toEnum .fromEnum .d2gl
...
Функции не претерпевшие особых изменений пропущены. Теперь наше глобальное состояние ( State)
содержит тело шара (оно пригодится нам для вычисления его положения) и пространство, в котором живёт
наша модель. Стоит отметить функцию simulate. В ней происходит обновление состояния модели. При
этом мы возвращаем время, которое ушло на вычисление этой функции. Оно нужно нам для того, чтобы
показывать новые кадры равномерно. Мы вычтем время симуляции из общего времени, которое мы можем
потратить на один кадр (frameTime).
20.2 Боремся с IO
Кажется, что мы попали в какой-то другой язык. Это совсем не тот элегантный Haskell, знакомый нам по
предыдущим главам. Столько doи IOразбросано по всему коду. И такой примитивный результат в итоге.
Если так будет продолжаться и дальше, то мы можем не вытерпеть и бросить и нашу задачу и Haskell…
Не отчаивайтесь!
Давайте лучше подумаем как свести этот псевдо-Haskell к минимуму. Подумаем какие источники IO
точно будут в нашей программе. Это инициализация GLFWи Hipmunk, клики мышью, обновление модели в
Боремся с IO | 299
Hipmunk, также для рисования нам придётся считывать положения шаров. Нам придётся удалять и создавать
новые шары, добавляя их к пространству модели. Также в IOпроисходит отрисовка игры. Hipmunkбудет кон-
тролировать столкновения шаров, и эти данные нам тоже надо будет считывать из глобальных переменных.
Сколько всего! Голова идёт кругом.
Но помимо всего этого у нас есть логика игры. Логика игры отвечает за реакцию игрового мира на раз-
личные события. Например столкновение с “плохим” шаром влечёт к уменьшению жизней, если игрок стал-
кивается с бонусным шаром, определённые шары необходимо удалить. Приходит момент и мы выпусткаем
новый шар из лузы новый шар. Давайте подумаем как сохранить логику игры в чистоте.
Тип IOобычно отвечает за связь с внешним миром, это глаза, уши, руки и ноги программы. Через IOмы
получаем информацию из внешнего мира и отправляем её обратно. Но в нашем случае он проник в сердце
программы. За обновление объектов отвечает насыщенная IOбиблиотека Hipmunk.
Мы постараемся побороться с IO-кодом так. Сначала мы выделем те параметры, которые могут быть
обновлены чистыми функциями. Это все те параметры, для которых не нужен Hipmunk. Этот шаг разбивает
наш мир на два лагеря: “чистый” и “грязный”:
data World = World
{ worldPure
:: Pure
, worldDirty
:: Dirty}
Чистые данные хотят как-то узнать о том, что происходит в грязных данных. Также чистые данные могут
рассказать грязным, как им нужно измениться. Это приводит нас к определению двух языков запросов, на
которых чистый и грязный мир общаются между собой:
data Query = Remove Ball | HeroVelocity H.Velocity | MakeBall Freq
data Event = Touch Ball | UserClick H.Position
data Sense = Sense
{ senseHero
:: HeroBall
, senseBalls
::[ Ball]
}
Через Queryчистые данные могут рассказать грязным о том, что необходимо удалить шар из игры, об-
новить скорость шара игрока или создать новый шар ( Freqотвечает за параметры создания шара). Грязные
данные могут рассказать чистым на языке Eventи Senseо том, что один из шаров коснулся до шара иг-
рока, или игрок кликнул мышкой в определённой точке. Также мы сообщаем все обновлённые положения
параметры шаров в типе Sense. Тип Eventотвечает за события, которые происходят иногда, а тип Senseза
те параметры, которые мы наблюдаем непрерывно (это типы глазарук), Query– это язык действий (это тип
руконог). Нам понадобится ещё один маленький язык, на котором мы будем объясняться с OpenGL.
data Picture = Prim Color Primitive
| Join Picture Picture
data Primitive = Line Point Point | Circle Point Radius
data Point
= Point Double Double
type Radius = Double
data Color = Color Double Double Double
Эти три языка станут барьером, которым мы ограничим влияние IO. У нас будут функции:
Читать дальше