Подтвердить что ты не робот

Функциональный реактивный F # - Сохранение состояний в играх

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

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

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

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

4b9b3361

Ответ 1

Там более одного способа сделать 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)

Ответ 2

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

В то время как сами монады в основном используются в 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 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

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

Ответ 4

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

Ответ 5

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