Lập trình chức năng và phiêu lưu văn bản


14

Đây chủ yếu là một câu hỏi lý thuyết về FP, nhưng tôi sẽ thực hiện các cuộc phiêu lưu văn bản (như Zork trường học cũ) để minh họa quan điểm của tôi. Tôi muốn biết ý kiến ​​của bạn về cách bạn mô hình hóa một mô phỏng trạng thái với FP.

Cuộc phiêu lưu văn bản dường như thực sự kêu gọi OOP. Ví dụ: tất cả các "phòng" là các thể hiện của một Roomlớp, bạn có thể có một Itemlớp cơ bản và các giao diện như Item<Pickable>đối với những thứ bạn có thể mang theo, v.v.

Mô hình thế giới trong FP hoạt động khác nhau, đặc biệt nếu bạn muốn thực thi tính bất biến trong một thế giới phải biến đổi khi trò chơi tiến triển (đối tượng bị di chuyển, kẻ thù bị đánh bại, điểm số tăng lên, người chơi thay đổi vị trí của nó). Tôi tưởng tượng một vật thể lớn duy nhất Worldcó tất cả: các phòng bạn có thể khám phá là gì, chúng được liên kết như thế nào, người chơi đang mang gì, đòn bẩy nào đã được kích hoạt.

Tôi nghĩ rằng một cách tiếp cận thuần túy sẽ là về cơ bản chuyển đối tượng lớn này đến bất kỳ chức năng nào và nó đã được họ trả lại (có thể đã được sửa đổi). Ví dụ, tôi có một moveToRoomchức năng nhận Worldvà trả lại nó với sự World.player.locationthay đổi sang phòng mới, World.rooms[new_room].visited = Truev.v.

Ngay cả khi đây là cách "chính xác" hơn, nó dường như đang thực thi sự thuần khiết vì lợi ích của nó. Tùy thuộc vào ngôn ngữ lập trình, việc truyền Worldđối tượng có khả năng rất lớn này qua lại có thể tốn kém. Ngoài ra, mọi chức năng có thể cần phải có quyền truy cập vào bất kỳ Worldđối tượng. Ví dụ, một căn phòng có thể có thể truy cập được hoặc không phụ thuộc vào đòn bẩy được kích hoạt ở phòng khác vì nó có thể bị ngập nước, nhưng nếu người chơi mang áo phao, dù sao thì nó cũng có thể vào được. Một con quái vật có thể hung dữ hay không tùy thuộc vào việc người chơi đã giết anh em họ của mình trong một căn phòng khác. Điều này có nghĩa là roomCanBeEnteredhàm cần truy cập World.player.invetoryWorld.rooms, describeMonstercần truy cập World.monsters, v.v. (về cơ bản, bạn phảivượt qua toàn bộ tải xung quanh). Điều này thực sự đối với tôi để gọi một biến toàn cục, ngay cả khi đây là tất cả nhưng phong cách lập trình tốt, đặc biệt là trong FP.

Làm thế nào bạn sẽ giải quyết vấn đề này?


4
"Tùy thuộc vào ngôn ngữ lập trình, việc truyền đối tượng Thế giới rất lớn này có thể rất tốn kém." Nó có thể sẽ được thông qua tham khảo. "Ngoài ra, mọi chức năng có thể cần có quyền truy cập vào bất kỳ đối tượng Thế giới nào." Tôi cảm thấy khó tin khi mọi chức năng đều cần truy cập vào toàn bộ trạng thái của trò chơi.
Doval

2
Tôi nghĩ nghiên cứu của Chris Marten sẽ rất thú vị, nó nhằm mục đích chỉ ra cách làm cho tiểu thuyết tương tác bằng ngôn ngữ khai báo trở nên tốt đẹp. github.com/chrisamaphone/interactive-lp
Daniel Gratzer

2
Bạn có thể muốn xem blog này mô tả cách tiếp cận của tác giả để lập trình một trò chơi như vậy một cách có chức năng. Bài này nói riêng là khá về điểm.
gallais

3
Tôi phải tự hỏi liệu câu hỏi này có ảnh hưởng đến quyết định sau này của @ EricLippert khi viết một loạt bài viết về việc triển khai (các bộ phận) của máy Z trong Ocaml không ...?
Jules

1
@Jules Một liên kết đến phần đầu của loạt bài đó, dành cho những người quan tâm: ericlippert.com/2016/02/01/west-of-house
KChaloux

Câu trả lời:


4

Lưu ý rằng các ngôn ngữ chức năng sử dụng cấu trúc dữ liệu và các chức năng riêng biệt thay vì các đối tượng. Ví dụ, bạn sẽ có một bộ phòng và một danh sách các mặt hàng tồn kho như một thế giới thay thế.

Lý tưởng nhất là bạn cũng sẽ giới hạn lượng dữ liệu bạn cung cấp cho các chức năng ở mức độ chúng thực sự yêu cầu nhiều nhất có thể thay vì đi qua toàn bộ thế giới (giả sử bạn trích xuất một phòng có liên quan từ thế giới của mình; tất nhiên thế giới hoàn toàn phụ thuộc lẫn nhau có thể khó tách rời). Kết quả sẽ được tái hợp nhất vào cấu trúc dữ liệu thế giới, tạo ra một trạng thái mới. Bạn không thể mô hình trạng thái mà không sử dụng trạng thái; như bạn nói một số điều vốn đã yêu cầu đột biến.

Hầu hết các ngôn ngữ chức năng thực tế cung cấp một cách để nhận ra đột biến trực tiếp (giả sử đơn vị ST trong Haskell hoặc tạm thời trong Clojure) hoặc mô phỏng nó một cách hiệu quả (thường bằng cách sử dụng lại các phần không thay đổi của cấu trúc dữ liệu (cấu trúc dữ liệu mặc định của Clojure là một ví dụ điển hình ở đây) ). Hoặc là độ tinh khiết được duy trì.

Vì số lượng trạng thái cần được thay đổi dường như bị hạn chế, tôi sẽ không lo lắng quá nhiều về các vấn đề hiệu suất và đi theo phương pháp chức năng ngây thơ (có thể đã được tối ưu hóa).

Một tùy chọn khác mà tôi đã thấy sẽ chỉ trả về các hướng dẫn để thay đổi một phần của thế giới khỏi các chức năng riêng lẻ của bạn và sau đó cập nhật thế giới của bạn theo những điều này. Một loạt các bài đăng blog mô tả điều này có sẵn tại http://prog21.dadgum.com/23.html .

Cả hai câu trả lời này liên quan nhiều đến cách tổ chức các thay đổi hơn là không chuyển toàn bộ thế giới của bạn sang các chức năng, bởi vì một người phụ thuộc hoàn toàn phụ thuộc nhau không thể được phân chia theo định nghĩa - nhưng hãy thử và làm tốt nhất có thể trong trường hợp của bạn, điều này không chỉ chức năng, nhưng cũng thực hành tốt.


0

Bản thân tôi, tôi chắc chắn sẽ xem xét khả năng của ngôn ngữ được đề cập để truy cập vào một số dạng cơ sở dữ liệu. Hầu hết các sự kiện xảy ra đồng thời thay đổi trạng thái của thế giới sẽ chỉ được ghi vào đĩa và sẽ không ảnh hưởng đến người chơi hiện tại trong phòng hiện tại (bên ngoài các trường hợp đặc biệt như vụ nổ hoặc trong MMO, các công tắc mở cửa từ xa, tiếng hét của người chơi khác, v.v.).

Như vậy, khách hàng hiện tại thực sự chỉ cần nhận thức được Roomđối tượng và những thứ ảnh hưởng trực tiếp đến nó. noticableEventsOutsideRoomsau đó có thể chỉ đơn giản là một lớp con Roombị ảnh hưởng bởi những thay đổi gần đây của cơ sở dữ liệu và đối tượng trò chơi của bạn đã trở nên nhỏ hơn nhiều.


Tôi hiểu cách tiếp cận này không ảnh hưởng nhiều đến việc tìm đường hoặc kích hoạt các sự kiện địa phương (chẳng hạn như agro trên mob gần đó), nhưng trước đây tôi đã biết lạm dụng cơ sở dữ liệu ... Có lẽ tôi chỉ cần gửi một cuộc gọi đến update mobs set agro=1 where distance<5và được thực hiện với nó Có lẽ đó không phải là cách thực hành tốt nhất, nhưng nó phù hợp với mục đích của tôi. Đối với tìm đường thông qua cơ sở dữ liệu, người ta luôn có thể sử dụng thuật toán đường dẫn ngắn nhất của Dijkstra ...
Ayelis

0

Giải pháp thực sự không phải là thu thập mọi thứ cho một vật thể Thế giới lớn, rồi đưa nó đi khắp nơi. Thay vào đó, bạn nên xác định chính xác loại chức năng bạn đang xử lý. Dưới đây là một số ví dụ:

BAD:
   f :: (World, Int) -> World

Good:
   f :: (Int,Int,Int,Int) -> World

Ví dụ xấu là cố gắng sửa đổi đối tượng hiện có, nhưng ví dụ tốt là cố gắng tạo một thế giới từ không gian trạng thái mà trò chơi của bạn có. Về cơ bản, để tạo ra một thế giới, bạn cần biết tất cả dữ liệu cần thiết để chọn đúng thế giới.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.