H. scale (H. normalize $ p1 - p0) (max minVel $ H. len v0)
mouse2canvas :: G.Size -> G.Position -> H.Vector
mouse2canvas (G.Size sx sy) (G.Position mx my) = H.Vector x y
where d a b
= fromIntegral a / fromIntegral b
x
= width * (d mx sx - 0.5)
y
= height * (negate $ d my sy - 0.5)
vertex2f :: G.GLfloat -> G.GLfloat -> IO ()
vertex2f a b = G. vertex (G.Vertex3 a b 0)
vec2gl :: H.Vector -> (G.GLfloat, G.GLfloat)
vec2gl (H.Vector x 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