Функциональная реактивная F # - сохранение состояний в играх - PullRequest
23 голосов
/ 28 июля 2010

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

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

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

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

Ответы [ 5 ]

13 голосов
/ 28 июля 2010

Существует более одного способа сделать 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 голосов
/ 28 июля 2010

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

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

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

Принимая (модифицированную) реализацию из этого источника , монада 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

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

3 голосов
/ 28 июля 2010

Томас дал хороший разговор о реактивном программировании на F #.Многие концепции должны применяться в вашем случае.

0 голосов
/ 09 января 2013

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

0 голосов
/ 10 мая 2011

Может быть, вы захотите взглянуть на FsReactive .

...