2010-07-28 8 views
22

Я учащийся, изучающий в настоящее время функциональную реактивную парадигму с использованием F #. Это радикально новая точка зрения для меня. Вчера я узнал о создании простой игры для пинг-понга, используя эту парадигму. Идея, которую я понимаю до сих пор, такова: мы считаем ценности как функции времени. В чистом виде он без гражданства. Однако мне нужно запомнить положение мяча (или состояния). Поэтому я всегда передаю текущее положение шара в качестве параметра глобальной функции.Функциональный реактивный F # - Сохранение состояний в играх

Если мы говорим о небольших более сложных играх, как Space Invaders, у нас есть много государств (позиция пришельцев пришельцев тока HP, количество оставшихся бомб и т.д.)

Есть элегантный/лучший способ решить эту проблему? Всегда ли мы сохраняем состояния на верхнем уровне? Должны ли все текущие состояния вводиться в качестве дополнительного входного аргумента глобальной функции?

Может ли кто-нибудь объяснить это, используя простой образец на F #? Большое спасибо.

ответ

13

Существует более чем один способ сделать FRP, и это активная область исследований. Что лучше всего может зависеть от того, как вещи взаимодействуют друг с другом, а новые и лучшие методы могут появиться в будущем.

В целом идея состоит в том, чтобы иметь поведение, которое является функцией времени вместо обычных значений (как вы сказали). Поведение может быть определено с точки зрения других видов поведения и может быть определено для обмена между другими поведениями, когда происходят определенные события.

В вашем примере вам вообще не нужно будет помнить положение мяча через аргументы (но для некоторых видов FRP, которые вы могли бы сделать).Вместо этого вы можете просто иметь поведение:
ballPos : time -> (float * float)
Это может иметь глобальный охват, или для более крупной программы может быть лучше иметь локальную область действия со всеми ее применениями в этой области.

По мере усложнения ситуации вы будете иметь поведение, определяемое более сложными способами, в зависимости от других видов поведения и событий, включая рекурсивные зависимости, которые по-разному обрабатываются в разных FRP-средах. В F # для рекурсивных зависимостей я бы ожидал, что вам понадобится let rec, включая все задействованные поведения. Они могут все еще быть организованы в структуры, хотя - на самом высоком уровне вы можете иметь:

type alienInfo = { pos : float*float; hp : float } 
type playerInfo = { pos : float*float; bombs : int } 
let rec aliens : time -> alienInfo array =    // You might want laziness here. 
    let behaviours = [| for n in 1..numAliens -> 
         (alienPos player n, alienHP player n) |] 
    fun t -> [| for (posBeh, hpBeh) in behaviours -> 
       {pos=posBeh t; hp=hpBeh t} |]   // You might want laziness here. 
and player : time -> playerInfo = fun t -> 
    { pos=playerPos aliens t; bombs=playerBombs aliens t} 

, а затем поведение для alienPos, alienHP можно определить, с зависимостями от игрока, и playerPos, playerBombs может быть определена с зависимостями от инопланетян.

В любом случае, если вы можете дать более подробную информацию о том, какую FRP вы используете, будет легче дать более конкретные советы. (И если вам нужен совет, какой вид - лично я бы рекомендовал прочитать: http://conal.net/papers/push-pull-frp/push-pull-frp.pdf)

6

У меня нет опыта с реактивным программированием под F #, но проблема глобального состояния в чисто функциональных системах довольно распространена и имеет довольно элегантное решение: Monads.

В то время как сами монады в основном используются в Haskell, базовая концепция превратила ее в F # как computation expressions.

Идея состоит в том, что вы фактически не изменяете состояния, а просто описываете переходы состояния, то есть как создавать новые состояния. Само государство может быть полностью скрыто в программе. Используя специальный монадический синтаксис, вы можете писать чистые, но сдержанные программы почти обязательно.

Принимая (модифицированную) реализацию из this source, то State монада может выглядеть следующим образом

let (>>=) x f = 
    (fun s0 -> 
     let a,s = x s0  
     f a s)  
let returnS a = (fun s -> a, s) 

type StateBuilder() = 
    member m.Delay(f) = f() 
    member m.Bind(x, f) = x >>= f 
    member m.Return a = returnS a 
    member m.ReturnFrom(f) = f 

let state = new StateBuilder()  

let getState = (fun s -> s, s) 
let setState s = (fun _ ->(),s) 

let runState m s = m s |> fst 

Так давайте пример: Мы хотим, чтобы написать функцию, которая может записывать значения в журнал (только список) при продолжении. Поэтому мы определяем

let writeLog x = state { 
    let! oldLog = getState // Notice the ! for monadic computations (i.e. where the state is involved) 
    do! setState (oldLog @ [x]) // Set new state 
    return() // Just return(), we only update the state 
} 

В state, теперь мы можем использовать это в императивном синтаксис без необходимости обращаться к списку журнала вручную.

let test = state { 
    let k = 42 
    do! writeLog k // It's just that - no log list we had to handle explicitly 
    let b = 2 * k 
    do! writeLog b 
    return "Blub" 
} 

let (result, finalState) = test [] // Run the stateful computation (starting with an empty list) 
printfn "Result: %A\nState: %A" result finalState 

Тем не менее, все чисто функциональный здесь;)

+0

Это в основном о государственной монаде. Функциональное реактивное программирование часто включает в себя монады, но обычно это простая форма государственной монады. – RD1

+0

Как я уже сказал, у меня нет опыта в FRP. Тем не менее государственная монада (или монады вообще), по-видимому, представляет собой концепцию, которая была запрошена - удобно хранить и изменять контекстные данные без потери ссылочной прозрачности. Если FTP уже использует монадическую инфраструктуру, тем лучше. Государственный трансформатор монады должен сделать это (вы имеете в виду это с * простым видом *?). Но, не объяснив основы, эта информация была бы бесполезной! – Dario

+0

Основная точка FRP позволяет определять поведение как непрерывные функции времени - например, вы можете определить z-положение шара под действием силы тяжести при z (t) = 9,8 * t * t. Монадическое состояние имеет значение только для состояния, которое производит дискретные изменения - дискретные изменения также допускаются в FRP, но они менее центральны и часто не соответствуют точной форме монады. – RD1

3

Томас дал nice talk о реактивном программирования в F #. В вашем случае должны применяться многие концепции.

+1

Функциональное реактивное программирование - это нечто большее, чем просто реактивное программирование на функциональном языке. Основной метод представляет поведение как функции времени. Такое поведение может зависеть друг от друга и от событий. Так что разговор не так прямо релевантен - он имеет некоторую актуальность, но только потому, что события являются частью FRP. (Я бы согласился, что это хороший разговор.) – RD1

0

Возможно, вам нужно взглянуть на FsReactive.

+6

Объяснение того, как FsReactive помогает ответить на вопрос, улучшит полезность вашего ответа. –

0

Elm - это современная реализация FRP. Для моделирования динамических коллекций, которые являются повсеместными в таких играх, как Space Invaders, он содержит Automaton library, основанный на концепциях стрелкового FRP. Вы обязательно должны это проверить.