Cấu trúc dữ liệu cho các trò chơi bảng hai chiều bằng các ngôn ngữ Chức năng


9

Tôi đang tạo một triển khai MiniMax đơn giản bằng ngôn ngữ lập trình chức năng Elixir. Bởi vì có nhiều trò chơi kiến ​​thức hoàn hảo (tic tac toe, kết nối bốn, cờ đam, cờ vua, v.v.), việc triển khai này có thể là một khuôn khổ để tạo AI trò chơi cho bất kỳ trò chơi nào trong số này.

Tuy nhiên, một vấn đề tôi gặp phải là làm thế nào để lưu trữ đúng trạng thái trò chơi bằng ngôn ngữ chức năng. Các trò chơi này chủ yếu xử lý các bảng trò chơi hai chiều, trong đó các hoạt động sau thường xuyên xảy ra:

  • Đọc nội dung của một vị trí bảng cụ thể
  • Cập nhật nội dung của một vị trí bảng cụ thể (khi trả lại khả năng di chuyển mới)
  • Xem xét nội dung của một hoặc nhiều vị trí được kết nối với vị trí hiện tại (nghĩa là các vị trí ngang, dọc hoặc chéo tiếp theo hoặc trước đó)
  • Xem xét nội dung của nhiều địa điểm được kết nối theo bất kỳ hướng nào.
  • Xem xét nội dung của toàn bộ tập tin, cấp bậc và đường chéo.
  • Xoay hoặc phản chiếu bảng (để kiểm tra các đối xứng cung cấp kết quả giống như một cái gì đó đã được tính toán).

Hầu hết các ngôn ngữ chức năng sử dụng Danh sách liên kết và Tuples làm các khối xây dựng cơ bản của cấu trúc dữ liệu đa yếu tố. Tuy nhiên, những thứ này có vẻ rất tệ được thực hiện cho công việc:

  • Danh sách liên kết có thời gian tra cứu O (n) (tuyến tính). Ngoài ra, vì chúng tôi không thể 'quét và cập nhật bảng' trong một lần quét trên bảng, sử dụng danh sách có vẻ rất không thực tế.
  • Tuples có thời gian tra cứu O (1) (không đổi). Tuy nhiên, việc biểu diễn bảng dưới dạng một bộ kích thước cố định làm cho việc lặp lại các cấp bậc, tệp, đường chéo hoặc các loại hình vuông liên tiếp khác rất khó khăn. Ngoài ra, cả Elixir và Haskell (là hai ngôn ngữ chức năng mà tôi biết) đều thiếu cú ​​pháp để đọc phần tử thứ n của một tuple. Điều này sẽ làm cho không thể viết một giải pháp động sẽ hoạt động cho các bảng có kích thước tùy ý.

Elixir có cấu trúc dữ liệu Bản đồ tích hợp (Và Haskell có Data.Map) cho phép O (log n) (logarit) truy cập vào các phần tử. Ngay bây giờ tôi sử dụng bản đồ, với các x, ybộ dữ liệu đại diện cho vị trí là các phím.

Điều này 'hoạt động' nhưng cảm thấy sai khi lạm dụng bản đồ theo cách này, mặc dù tôi không biết chính xác tại sao. Tôi đang tìm kiếm một cách tốt hơn để lưu trữ một bảng trò chơi hai chiều bằng ngôn ngữ lập trình chức năng.


1
Tôi không thể nói về Praxis, nhưng có hai điều tôi nghĩ đến từ Haskell: khóa kéo , cho phép "các bước" liên tục trên các cấu trúc dữ liệu và comonad, liên quan đến dây kéo bởi một số lý thuyết mà tôi không nhớ cũng không hiểu đúng; )
phipsgabler

Bàn chơi này lớn cỡ nào? Big O đặc trưng cho cách một thuật toán chia tỷ lệ, chứ không phải nó nhanh như thế nào. Trên một bảng nhỏ (giả sử, ít hơn 100 ở mỗi hướng), O (1) so với O (n) dường như không quan trọng lắm, nếu bạn chỉ chạm vào mỗi ô vuông một lần.
Robert Harvey

@RobertHarvey Nó sẽ thay đổi. Nhưng để đưa ra một ví dụ: Trong Cờ vua, chúng tôi có một bảng 64x64, nhưng tất cả các tính toán để kiểm tra xem có thể di chuyển được không và để xác định giá trị heuristic của vị trí hiện tại (khác biệt về vật liệu, vua có kiểm tra hay không, thông qua cầm đồ, v.v.) tất cả cần truy cập vào các ô vuông của bảng.
Qqwy

1
Bạn có một bảng 8 x 8 trong cờ vua. Trong ngôn ngữ được ánh xạ bộ nhớ như C, bạn có thể thực hiện phép tính toán học để lấy địa chỉ chính xác của một ô, nhưng điều đó không đúng trong các ngôn ngữ được quản lý bộ nhớ (trong đó địa chỉ thứ tự là một chi tiết triển khai). Tôi sẽ không ngạc nhiên nếu nhảy qua (tối đa) 14 nút mất khoảng thời gian tương đương với việc giải quyết một phần tử mảng trong ngôn ngữ được quản lý bộ nhớ.
Robert Harvey

Câu trả lời:


6

A Mapchính xác là cấu trúc dữ liệu cơ sở đúng ở đây. Tôi không chắc tại sao nó sẽ làm bạn khó chịu. Nó có thời gian tra cứu và cập nhật tốt, kích thước động và rất dễ dàng để tạo cấu trúc dữ liệu phái sinh từ đó. Ví dụ: (haskell):

filterWithKey (\k _ -> (snd k) == column) -- All pieces in given column
filterWithKey (\k _ -> (fst k) == row)    -- All pieces in given row
mapKeys (\(x, y) -> (-x, y))              -- Mirror

Một điều khác thường khiến các lập trình viên khó nắm bắt khi lần đầu tiên bắt đầu lập trình với tính bất biến hoàn toàn là bạn không cần phải chỉ bám vào một cấu trúc dữ liệu. Bạn thường chọn một cấu trúc dữ liệu làm "nguồn sự thật" của mình, nhưng bạn có thể tạo ra nhiều công cụ phái sinh như bạn muốn, thậm chí là công cụ phái sinh và bạn biết chúng sẽ được đồng bộ hóa miễn là bạn cần chúng.

Điều đó có nghĩa là bạn có thể sử dụng Mapmức cao nhất, sau đó chuyển sang Listshoặc Arraysđể phân tích hàng, sau đó Arrayslập chỉ mục theo cách khác để phân tích cột, sau đó bitmasks để phân tích mẫu, sau đó Stringsđể hiển thị. Các chương trình chức năng được thiết kế tốt không vượt qua một cấu trúc dữ liệu duy nhất xung quanh. Chúng là một chuỗi các bước đưa một cấu trúc dữ liệu vào và phát ra một cấu trúc mới phù hợp cho bước tiếp theo.

Miễn là bạn có thể đi ra phía bên kia với một động thái ở định dạng cấp cao nhất có thể hiểu, bạn không cần phải lo lắng về việc bạn tái cấu trúc dữ liệu ở giữa bao nhiêu. Điều đó là bất biến, vì vậy có thể theo dõi một con đường trở về nguồn sự thật ở cấp cao nhất.


4

Gần đây tôi đã thực hiện điều này trong F # và cuối cùng tôi đã sử dụng danh sách một chiều (trong F #, đó là danh sách liên kết đơn). Trong thực tế, tốc độ của bộ chỉ mục danh sách O (n) không phải là nút cổ chai đối với kích thước bảng có thể sử dụng được của con người. Tôi đã thử nghiệm với các loại khác như mảng 2d, nhưng cuối cùng, đó là sự đánh đổi bằng cách viết mã kiểm tra bình đẳng giá trị của riêng tôi hoặc bản dịch thứ hạng và tệp để lập chỉ mục và quay lại. Cái sau thì đơn giản hơn. Tôi muốn nói rằng nó hoạt động trước, và sau đó tối ưu hóa kiểu dữ liệu của bạn sau nếu cần. Nó không có khả năng tạo ra một sự khác biệt đủ lớn để quan trọng.

Cuối cùng, việc triển khai của bạn không quan trọng miễn là các hoạt động của hội đồng quản trị của bạn được đóng gói đúng theo loại và hoạt động của Hội đồng. Ví dụ, đây là cách một số thử nghiệm của tôi có thể tìm cách thiết lập một bảng:

let pos r f = {Rank = r; File = f} // immutable record type
// or
let pos r f = OnBoard (r, f) // algebraic type
...
let testBoard =
    Board.createEmpty ()
    |> Board.setPiece p (pos 1 2)
    |> ...

Đối với mã này (hoặc bất kỳ mã gọi nào), việc bảng được trình bày như thế nào không quan trọng. Bảng được đại diện bởi các hoạt động trên nó nhiều hơn cấu trúc cơ bản của nó.


Xin chào, bạn có mã được xuất bản ở đâu đó không? Tôi hiện đang làm việc trong một trò chơi giống như cờ vua trong F # (một dự án vui nhộn) và thậm chí tôi đang sử dụng Bản đồ <Square, Piece> để thể hiện bảng, tôi rất thích xem bạn gói gọn nó vào một Bảng như thế nào loại và mô-đun.
asibahi

Không, nó không được công bố ở bất cứ đâu.
Kasey speakman

Vì vậy, bạn có phiền khi xem qua triển khai hiện tại của tôi và tư vấn làm thế nào tôi có thể cải thiện nó?
asibahi

Liếc nhìn các loại, tôi quyết định thực hiện rất giống nhau cho đến khi tôi đến loại Vị trí. Tôi sẽ làm một phân tích kỹ lưỡng hơn về Code Review sau.
Kasey speakman
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.