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
. У нас будут функции:percept
:: Dirty -> IO
(Sense, [Event])updatePure
:: Sense ->
[Event] -> Pure -> (Pure, [Query])react
::
[Query] -> Dirty -> IO DirtyupdateDirty :: Dirty -> IO Dirty
picture
:: Pure -> Picture
draw
:: Picture -> IO
()Вся логика игры будет происходить в чистой функции updatePure, обновлять модель мира мы будем в
updateDirty. Давайте опять начнём проектироваание сверху-вниз. С этими функциями мы уже можем напи-
сать основную функцию цикла игры:
loop :: IORef World -> IO
()loop worldRef = do
world <-
get worldRef300 | Глава 20: Императивное программирование
drawWorld world
(world, dt) <-
updateWorld worldworldRef $=
worldG.
addTimerCallback (max 0 $ frameTime - dt) $ loop worldRefupdateWorld :: World -> IO
(World, Time)updateWorld world = do
t0 <-
get G. elapsedTime(sense, events) <-
percept dirtylet
(pure’, queries) = updatePure sense events puredirty’ <-
updateDirty =<< react queries dirtyt1 <-
get G. elapsedTimereturn (World
pure’ dirty’, t1 - t0)where
dirty = worldDirty worldpure
=
worldPureworld
drawWorld :: World -> IO
()drawWorld =
draw . picture . worldPure20.3 Определяемся с типами
Давайте подумаем, из чего состоят типы Dirty
и Pure. Начнём с Pure. Там точно будет вся информациянеобходимая нам для рисования картинки (ведь функция picture определена на Pure
). Для рисования намнеобходимо знать положения всех шаров и их типы (они определяют цвет). На картинке мы будем показывать
разную статистику (данные о жизнях, бонусные очки). Также из типа Pure
мы будем управлять созданиемшаров. Так мы приходим к типу:
data Pure = Pure
{ pureScores
:: Scores
, pureHero
:: HeroBall
, pureBalls
::
[Ball], pureStat
:: Stat
, pureCreation
:: Creation
}
Что нам нужно знать о шаре героя? Нам нужно его положение для отрисовки и модуль вектора скорости
(он понадобится нам при обновлении вектора скорости шара игрока):
data HeroBall = HeroBall
{ heroPos
:: H.Position
, heroVel
:: H.CpFloat
}
Для остальных шаров нам нужно знать только тип шара, его положение и идентификатор шара. По иден-
тификатору потом мы сможем понять какой шар удалить из грязных данных:
data Ball = Ball
{ ballType
:: BallType
, ballPos
:: H.Position
, ballId
:: Id
}
data BallType = Hero | Good | Bad | Bonus
deriving
(Show, Eq, Enum)type Id = Int
Статистика игры состоит из числа жизней и бонусных очков:
data Scores = Scores
{ scoresLives :: Int
, scoresBonus :: Int
}
Определяемся с типами | 301
Как будет происходить создание новых шаров? Если плохих шаров будет слишком много, то играть будет
не интересно, игрок слишком быстро проиграет. Если хороших шаров будет слишком много, то игроку также
быстро надоест. Будет очень легко. Нам необходимо поддерживать определённый баланс шаров. Создание
шаров будет происходить случайным образом через равные промежутки времени, но создание нового шара
будет зависеть от пропорции шаров на доске в данный момент. Если у нас слишком много плохих шаров,
то скорее всего мы создадим хороший шар и наоборот. Если общее число шаров велико, то мы не будем