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

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

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

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

Кто-либо может объяснить эту простую выборку использования на F#?Большое спасибо.

23
задан user248836 28 July 2010 в 07:12
поделиться

3 ответа

Существует несколько способов выполнения 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 )

13
ответ дан 29 November 2019 в 02:54
поделиться

У меня нет опыта реактивного программирования на 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
}

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

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

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

6
ответ дан 29 November 2019 в 02:54
поделиться

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

3
ответ дан 29 November 2019 в 02:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: