2013-08-03 20 views
7

Tôi biết rất ít về lập trình chức năng ngoài ý tưởng về các hàm thuần túy. Trong năm 2013, Quakecon nói về một trong những câu hỏi thường được hỏi về lập trình chức năng liên quan đến trò chơi: làm thế nào để bắn súng và gây sát thương cho người chơi khác nếu bạn không có quyền truy cập vào nhà nước? (diễn giải) Trong đề cập đến một cái gì đó về một hệ thống sự kiện, mà tôi đã không hoàn toàn hiểu, vì có vẻ như với tôi một hệ thống sự kiện vẫn sẽ cần nhà nước?Trong trò chơi được xây dựng với lập trình hàm thuần túy, làm cách nào một người chơi có thể gây sát thương (trạng thái thay đổi) của người chơi khác?

Làm cách nào để thực hiện điều này bằng ngôn ngữ thuần túy?

+0

Trong Haskell, bạn có thể sử dụng đơn vị trạng thái. – Jocke

+3

Bạn đã thấy vô số câu hỏi khác về FP và tiểu bang chưa? Một số người trong số họ thậm chí còn được liệt kê bên phải trong "Liên quan". TL, DR - Có trạng thái ổn, không có trạng thái nào có thể thay đổi được. (Các đơn vị 'State' được đề cập ở trên không phải là ma thuật, nó chỉ là ba mươi dòng mã thư viện.) – delnan

+1

Sử dụng" State monad "(googlable), vâng. Về cơ bản nó chỉ là 'gõ State s a = s -> (a, s)' với 'Monad' & co thể hiện. Để có ví dụ "trò chơi" thuần khiết, hãy xem http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html. – JJJ

Trả lời

8

Để lặp lại một trong những dấu ngoặc kép yêu thích của tôi

... mất trong trạng thái của thế giới và trả về một thế giới mới, do đó còn tinh khiết.

Điều này đã nói về Clean, một người anh em họ của Haskell nhưng nó vẫn liên quan. Ý chính của nó là, bạn nói đúng, bạn cần một loại trạng thái nào đó, nhưng nó không phải là có thể thay đổi được. Hãy xem xét

myFun :: StateOfTheWorld -> a -> (StateOfTheWorld, b) 

vì vậy chúng tôi không sửa đổi trạng thái, chúng tôi chỉ tạo một hình mới. Điều này có tính minh bạch liên quan, vì được đưa ra cùng một trạng thái của thế giới và cùng một hành động, bạn sẽ nhận được cùng một điều trở lại.

Đối với bạn, bạn có thể có một cái gì đó giống như

killPlayer :: Game -> Event -> Game 
killPlayer g (Kill x) = g { isDead = x : isDead g } 

mà chỉ được sử dụng cập nhật chức năng cho hồ sơ. Đây là một chút klunky, vì vậy chúng tôi có thể làm điều gì đó như

killPlayer :: Game -> Event -> Action 
killPlayer (PlayerDamaged x amount) = if playerHealth g x <= amount 
             then KillPlayer x 
             else ReduceHealth x amount 

Vì vậy, chúng tôi chỉ trả lại sự khác biệt, không phải trạng thái trò chơi đầy đủ.

Công trình này, nhưng rất xấu. Vì vậy, chúng tôi prettify này với ký hiệu do và Control.Monad.State. Điều này nghe có vẻ đáng sợ nhưng đó là chính xác những gì chúng tôi đã làm ở trên, chỉ với một chút trừu tượng cú pháp. Trên thực tế, đây cũng là điều mà IO cũng có trên GHC. Tôi không biết nếu bạn đã học về Monads, nhưng các đơn vị nhà nước thường là ví dụ thúc đẩy.

Cuối cùng để quay lại trò chơi, nhiều trò chơi tôi đã thấy như thế này: đống những thứ lắng nghe sự kiện và sau đó gợi ý một số thay đổi nhỏ về trạng thái trò chơi và trả lại sự khác biệt các cuộc gọi OpenGL thích hợp hoặc bất kỳ điều gì để thực hiện những thay đổi đó.

1

Để hỗ trợ nhận xét của tôi, đây là một ví dụ chuyển thể từ http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html:

-- ----------------------------------------------------------------------------- 
-- * Our homegrown state monad (use @[email protected] from the MTL package in production). 

-- | @[email protected] is a function (lets call it "state-updater") which "updates" a 
-- state @[email protected] and returns some associated result @[email protected] 
newtype State s r = State { run :: s -> (r, s) } 

-- | This state-updater function is a monad. 
instance Monad (State s) where 

    -- | Build a state-updater which returns @[email protected] and don't change the state. 
    return x = State $ \st -> (x, st) 

    -- | From a state-updater @[email protected] and a function @[email protected] which returns a state-updater 
    -- we can build a new (lazy) state-updater by performing update actions of this two 
    -- state-updaters. 
    m >>= f = State $ \st -> let (x, st') = run m st in run (f x) st' 

-- | Simply swap the state. 
put :: s -> State s() 
put st = State $ const ((), st) 

-- | Get the current state as a result of this state-updater. 
get :: State r r 
get = State $ \st -> (st, st) 

-- ----------------------------------------------------------------------------- 
-- * An example. 

-- | Player with its health. 
newtype Player = Player { _health :: Int } deriving (Show) 

-- | Game of two players. 
data Game = Game { _player1 :: !Player, _player2 :: !Player } deriving (Show) 

-- | Starting from weak and strong players. 
initialState :: Game 
initialState = Game (Player 10) (Player 20) 

-- | First player hit second. 
hit12 :: State Game() 
hit12 = do 
    [email protected](Game _ [email protected](Player health)) <- get 
    put g { _player2 = p2 { _health = health - 1 } } 

-- | Second player hit first. 
hit21 :: State Game() 
hit21 = do 
    [email protected](Game [email protected](Player health) _) <- get 
    put g { _player1 = p1 { _health = health - 1 } } 

-- | Test it. 
test :: ((), Game) 
test = run (do { hit12; hit12; hit12; hit21 }) initialState 
-- 
-- initialState 
-- => 
-- Game {_player1 = Player {_health = 10}, _player2 = Player {_health = 20}} 
-- 
-- snd test 
-- => 
-- Game {_player1 = Player {_health = 9}, _player2 = Player {_health = 17}} 
-- 

Lenses cho phép để viết

hit12 = player2.health -= 1 

hit21 = player1.health -= 1 

State transformer (mà bạn nên sử dụng anyway) cho phép kết hợp đơn nguyên khác (như IO) đến State, nhưng về cơ bản tất cả đều thuần khiết và hoạt động như sau:

... đưa vào trạng thái của thế giới và trả về một thế giới mới, do đó còn lại thuần túy.

như một nhận xét khác được trích dẫn.

2

Bang chỉ là một tập hợp các giá trị trong môi trường. Haskell làm cho bạn xử lý môi trường của bạn một cách rõ ràng, vì vậy chúng tôi có thể gọi nó là Env. Chúng tôi tạo ra những cái mới

letThereBeLight :: Env 
letThereBeLight = Env { personHealth = 100 } 

Và thay đổi chúng

shootEmUp :: Env -> Env 
shootEmUp oldEnv = oldEnv { personHealth = personHealth oldEnv - 30 } 

loại như Env -> Env được gọi là Endo Env trong đó bạn áp dụng chúng end-to-end để thực hiện nhiều thay đổi đối với nhà nước.

assassinate = shootEmUp . shootEmUp . shootEmUp . shootEmUp 

Nếu bạn muốn làm nhiều hơn là chỉ sửa đổi một trạng thái bạn cần thiết lập trình tự các giá trị khác cùng với trạng thái của bạn Endo. Thay vì trông giống như Env -> Env, bạn bắt đầu thấy Env -> (Env, a) trong đó a mô hình luồng dữ liệu khác của bạn và tạo từ đó. Điều này được gọi là Monad nhà nước bởi vì có một số cách rất thông minh để làm cho thao tác hai luồng thông tin này lại với nhau khá dễ dàng.