Thuật toán để tối ưu hóa một trò chơi phù hợp với hàng đợi đã biết


10

Tôi đang cố gắng viết một bộ giải trong C # .NET cho một trò chơi có tên là Flowerz. Để bạn tham khảo, bạn có thể chơi nó trên MSN, tại đây: http://zone.msn.com/gameplayer/gameplayer.aspx?game=flowerz . Tôi đang viết nó cho vui, không phải cho bất kỳ loại bài tập hay bất kỳ công việc nào liên quan. Bởi vì điều này, giới hạn duy nhất là máy tính của tôi (lõi intel i7, với 8GB RAM). Nó không cần phải chạy ở bất cứ nơi nào khác, theo như tôi quan tâm.

Nói tóm lại, quy tắc của nó là như thế này:

  • Có một hàng đầy hoa màu. Chiều dài của nó là tùy ý
    • Hàng đợi không thể bị ảnh hưởng
    • Hàng đợi được tạo khi bắt đầu cấp
  • Hoa có một hoặc hai màu.
    • Nếu có hai màu, thì có màu bên ngoài và màu bên trong. Trong trường hợp có hai màu, màu bên ngoài được sử dụng để kết hợp.
    • Nếu có sự trùng khớp thì màu bên ngoài sẽ biến mất và bông hoa bây giờ là một bông hoa màu duy nhất có cùng màu với hoa bên trong
  • Mục tiêu của trò chơi là tạo ra các trận đấu gồm ba (hoặc nhiều hơn) cùng màu
    • Khi một bông hoa có một màu duy nhất là một phần của trận đấu, nó sẽ bị xóa khỏi sân chơi, tạo ra một khoảng trống
    • Bạn có thể kết hợp một bông hoa màu duy nhất với màu bên ngoài của một bông hoa hai màu. Trong trường hợp này, hoa màu đơn biến mất, màu bên ngoài của hoa hai màu biến mất và màu bên trong vẫn còn
  • Bạn giành chiến thắng trong vòng khi hàng đợi trống có ít nhất một khoảng trống còn lại
  • Trận đấu xếp tầng là có thể. Một thác là khi ba (hoặc nhiều) hoa bên ngoài biến mất, và khi màu sắc bên trong của chúng tạo thành một chuỗi 3 (hoặc nhiều hoa hơn).
  • Sân chơi luôn là 7x7
  • Một số không gian trên cánh đồng được bao phủ bởi những tảng đá
    • Bạn không thể đặt hoa trên đá
  • Hàng đợi cũng có thể chứa một thuổng mà bạn có thể sử dụng để di chuyển bất kỳ bông hoa nào được đặt vào một không gian trống
    • Bạn phải sử dụng thuổng, nhưng bạn thực sự không phải di chuyển bông hoa: việc đặt nó trở lại ngay từ nơi nó đến là hoàn toàn hợp pháp
  • Hàng đợi cũng có thể chứa một con bướm màu. Khi bạn sử dụng con bướm này trên một bông hoa, thì bông hoa sẽ có màu của con bướm
    • Áp dụng một con bướm cho một bông hoa có hai màu, kết quả là hoa chỉ nhận được một màu duy nhất, đó là màu của con bướm
    • Bạn có thể lãng phí con bướm trên một không gian trống hoặc một bông hoa đã có màu này
  • Xóa lĩnh vực không thắng trò chơi

Mục tiêu của người giải rất đơn giản: tìm cách làm trống hàng đợi, với càng nhiều khoảng trống còn lại trên sân chơi càng tốt. Về cơ bản, AI chơi trò chơi cho tôi. Đầu ra của bộ giải là một danh sách với các di chuyển mà nó tìm thấy. Tôi không quan tâm đến điểm số, nhưng sống sót càng lâu càng tốt, do đó tôi quan tâm đến những động tác để lại càng nhiều khoảng trống càng tốt.

Không cần phải nói, không gian tìm kiếm phát triển nhanh chóng khi hàng đợi càng lớn, do đó, một lực lượng vũ phu là không thể. Hàng đợi bắt đầu từ 15 và tăng lên với mỗi 5 hoặc ba cấp độ, nếu tôi nhớ đúng. Và, tất nhiên, đặt bông hoa thứ nhất trên (0,0) và bông thứ hai trên (0,1) khác với đặt bông hoa thứ nhất trên (1,0) và bông hoa thứ hai trên (0,0), đặc biệt là khi cánh đồng đã được trồng đầy hoa từ một vòng trước đó. Một quyết định đơn giản như vậy có thể tạo ra sự khác biệt trong việc đưa ra hay không.

Các câu hỏi tôi có như sau:

  • Đây là loại vấn đề gì? (nghĩ rằng nhân viên bán hàng du lịch, ba lô hoặc một số vấn đề kết hợp khác). Biết điều này có thể làm cho Google-fu của tôi trở nên tốt hơn.
  • Loại thuật toán nào có thể cho tôi kết quả tốt, nhanh?

Về vấn đề thứ hai: Lúc đầu, tôi đã cố gắng viết thuật toán heuristic của riêng mình (về cơ bản: tôi sẽ giải quyết nó như thế nào, nếu tôi biết xếp hàng?), Nhưng điều đó dẫn đến rất nhiều trường hợp cạnh và ghi điểm phù hợp mà tôi có thể bỏ lỡ.

Tôi đã nghĩ đến việc sử dụng một thuật toán di truyền (vì ít nhất tôi biết cách sử dụng nó ...), nhưng tôi gặp một số vấn đề khi quyết định đại diện nhị phân của bảng. Sau đó, có vấn đề chéo, nhưng điều đó có thể được giải quyết với một toán tử chéo được đặt hàng hoặc một loại hoạt động tương tự.

Tôi đoán là người giải phải luôn biết cấu hình bảng và hàng đợi nó đang cố để trống.

Tôi biết một vài thuật toán heuristic khác như mạng thần kinh và hệ thống logic mờ, nhưng tôi thiếu kinh nghiệm để biết cái nào phù hợp nhất, hoặc nếu có những thuật toán khác phù hợp hơn cho nhiệm vụ trong tay.


Tôi đã từng làm việc rằng không gian tìm kiếm của một số trò chơi phức tạp mà tôi đang làm việc sẽ là 32Gb. Vào thời điểm đó (tôi có ổ đĩa 20Mb) sẽ không khả thi, nhưng ngày nay, nó chỉ có thể thực hiện được trong RAM đối với một số máy tính.
Jonathan

Những bông hoa chỉ có một màu biến mất hoàn toàn khi kết hợp? Và những bông hoa có hai màu có thể khớp với lớp ngoài của chúng so với màu đơn của hoa một màu không? Tôi đoán như vậy trên cả hai tổng số, nhưng những điều này không bao giờ được quy định rõ ràng trong mô tả vấn đề ...
Steven Stadnicki

@StevenStadnicki Cảm ơn! Tôi đã thêm thông tin đó vào câu hỏi ban đầu.
user849924

1
Một lưu ý nhỏ, tình cờ, rất có khả năng phiên bản 'boolean' của vấn đề này (có cách nào để đặt những bông hoa trong hàng đợi để cuối bảng hoàn toàn trống không?) Đã hoàn thành NP; nó có những điểm tương đồng rõ ràng với vấn đề Clickomania ( erikdemaine.org/clickomania ) là NP hoàn chỉnh, và vấn đề không khó hơn NP vì đưa ra một giải pháp có mục đích (có độ dài đa thức) rất dễ xác minh bằng cách chỉ chạy mô phỏng. Điều này có nghĩa là vấn đề tối ưu hóa có lẽ nằm ở FP ^ NP.
Steven Stadnicki

Câu trả lời:


9

Thoạt nhìn , đây dường như là một vấn đề tìm kiếm tác nhân duy nhất . Đó là: bạn có một tác nhân ("người chơi" AI). Có một trạng thái trò chơi đại diện cho trạng thái của bảng trò chơi và hàng đợi và bạn có chức năng kế tiếp có thể tạo các trạng thái mới từ một trạng thái nhất định.

Ngoài ra còn có một tiêu chí mục tiêu cho bạn biết khi nào trạng thái là trạng thái "được giải quyết". Và chi phí đường dẫn - chi phí chuyển sang trạng thái nhất định (luôn luôn là "1 di chuyển" trong trường hợp này).

Một câu đố nguyên mẫu của loại này là 15 Câu đố . Và cách điển hình để giải quyết nó là với một tìm kiếm có thông tin - ví dụ: tìm kiếm heuristic cổ điển A * và các biến thể của nó.


Tuy nhiên, có một vấn đề với cách tiếp cận này thoạt nhìn. Các thuật toán như A * được thiết kế để cung cấp cho bạn đường dẫn ngắn nhất tới mục tiêu (ví dụ: số lần di chuyển nhỏ nhất). Trong trường hợp của bạn, số lần di chuyển luôn luôn cố định - không có con đường ngắn nhất - do đó, một tìm kiếm heuristic sẽ chỉ cung cấp cho bạn một đường dẫn đến một trò chơi kết thúc.

Những gì bạn muốn là một chuỗi các động tác cung cấp cho bạn trạng thái trò chơi hoàn thành tốt nhất .

Vì vậy, những gì bạn phải làm là xoay chuyển vấn đề một chút. Thay vì bảng trò chơi là "trạng thái", chuỗi di chuyển trở thành "trạng thái". (Tức là: Đặt các mục trong hàng đợi tại các vị trí "D2, A5, C7, B3, A3, ...")

Điều này có nghĩa là chúng tôi không thực sự quan tâm làm thế nào những trạng thái đó được tạo ra. Hội đồng quản trị là ngẫu nhiên, chỉ cần để đánh giá chất lượng của một nhà nước nhất định.

Điều này biến vấn đề thành vấn đề tối ưu hóa , có thể được giải quyết bằng thuật toán tìm kiếm cục bộ (về cơ bản có nghĩa là tạo các trạng thái xung quanh một trạng thái nhất định và chọn trạng thái tốt nhất mà không cần quan tâm đến đường dẫn giữa các trạng thái.)

Các câu đố nguyên mẫu của này loại là Tám Queens Puzzle .

Trong lớp vấn đề này, bạn đang tìm kiếm không gian trạng thái để tìm giải pháp tốt, trong đó "tốt" được đánh giá bằng hàm mục tiêu (còn gọi là hàm đánh giá hoặc, đối với thuật toán di truyền, hàm thể lực ).

Đối với vấn đề của bạn, hàm mục tiêu có thể trả về giá trị trong khoảng từ 0 đến N, cho số lượng mục trong hàng đợi đã được sử dụng trước khi đạt đến trạng thái lỗi (trong đó N là độ dài của hàng đợi). Và, nếu không, giá trị của N + M, trong đó M là số khoảng trống còn lại trên bảng sau khi hàng đợi trống. Như vậy - giá trị càng cao, giải pháp "khách quan càng tốt".

(Điều đáng chú ý, tại thời điểm này, bạn nên tối ưu hóa crap ra khỏi mã chạy trò chơi - biến trạng thái thành một bảng hoàn thành có thể được sử dụng cho chức năng mục tiêu.)


Đối với các ví dụ về thuật toán tìm kiếm cục bộ : Mẫu cơ bản là tìm kiếm leo đồi có trạng thái nhất định, biến đổi nó và chuyển sang trạng thái tiếp theo mang lại kết quả tốt hơn.

Rõ ràng điều này có thể bị mắc kẹt trong mức tối đa địa phương (và tương tự). Trong hình thức này, nó được gọi là một tìm kiếm địa phương tham lam . Có một loạt các biến thể để giải quyết vấn đề này và các vấn đề khác ( Wikipedia đã đề cập đến bạn ). Một số trong đó (ví dụ: tìm kiếm chùm tia cục bộ ) theo dõi nhiều trạng thái cùng một lúc.

Một biến thể đặc biệt về điều này là thuật toán di truyền ( Wikipedia ). Các bước cơ bản cho một thuật toán di truyền là:

  1. Xác định một số cách để chuyển đổi một trạng thái thành một chuỗi của một số loại. Trong trường hợp của bạn, đây có thể là một chuỗi các chữ số có độ dài hàng đợi từ 1 đến 49 (đại diện cho tất cả các vị trí có thể có trên bảng 7x7, có thể được lưu trữ mỗi byte 1 byte). (Phần "thuổng" của bạn có thể được biểu thị bằng hai mục nhập hàng đợi tiếp theo, cho mỗi giai đoạn di chuyển.)
  2. Chọn ngẫu nhiên một quần thể sinh sản, đưa ra xác suất cao hơn cho các tiểu bang có thể lực tốt hơn . Quần thể sinh sản phải có cùng kích thước với quần thể gốc - bạn có thể chọn các trạng thái từ quần thể gốc nhiều lần.
  3. Ghép nối các trạng thái trong quần thể sinh sản (thứ nhất đi với thứ hai, thứ ba đi với thứ tư, v.v.)
  4. Chọn ngẫu nhiên các điểm giao nhau cho mỗi cặp (một vị trí trong chuỗi).
  5. Tạo hai con cho mỗi cặp bằng cách hoán đổi phần của chuỗi sau điểm giao nhau.
  6. Đột biến ngẫu nhiên mỗi trạng thái của con cái. Ví dụ: chọn ngẫu nhiên để thay đổi vị trí ngẫu nhiên trong chuỗi thành giá trị ngẫu nhiên.
  7. Lặp lại quy trình với dân số mới cho đến khi dân số hội tụ một hoặc nhiều giải pháp (hoặc sau một số thế hệ nhất định hoặc tìm thấy giải pháp đủ tốt).

Một giải pháp thuật toán di truyền cảm thấy như nó thể phù hợp với vấn đề của bạn - với một số điều chỉnh. Khó khăn lớn nhất mà tôi thấy là, với cách biểu diễn chuỗi ở trên, bạn sẽ thấy rằng việc chuyển đổi nửa đuôi của các trạng thái với các nửa phía trước rất khác nhau có thể dẫn đến trạng thái "chết" (do di chuyển xung đột giữa hai nửa, kết quả là trong một số điểm thể dục thấp).

Có lẽ có thể khắc phục vấn đề này. Một ý tưởng xuất hiện trong đầu là làm cho nhiều quốc gia có nửa mặt trước tương tự trở thành cặp sinh sản. Điều này có thể đơn giản như sắp xếp dân số chăn nuôi của các quốc gia, trước khi ghép chúng lại. Nó cũng có thể giúp dần dần di chuyển vị trí có khả năng của chéo, từ đầu đến cuối chuỗi, khi số thế hệ tăng lên.

Cũng có thể đưa ra một đại diện cho các bước di chuyển trong trạng thái có khả năng chống chịu cao hơn (thậm chí là hoàn toàn miễn dịch) khi gặp phải trạng thái thất bại "vuông là đầy đủ". Có lẽ đại diện cho di chuyển như tọa độ tương đối từ di chuyển trước. Hoặc có di chuyển chọn không gian trống gần nhất đến vị trí nhất định.

Như với tất cả các vấn đề AI không tầm thường như thế này, nó sẽ đòi hỏi một số vấn đề đáng kể.

Và, như tôi đã đề cập trước đây, thách thức lớn khác chỉ đơn giản là tối ưu hóa chức năng mục tiêu của bạn. Làm điều này nhanh hơn sẽ cho phép bạn tìm kiếm một lượng lớn không gian và tìm kiếm giải pháp cho các trò chơi có hàng đợi dài hơn.


Để có câu trả lời này, đặc biệt là để có được tất cả các thuật ngữ đúng, tôi đã phải tìm hiểu cuốn sách giáo khoa AI của trường đại học của tôi, "Trí tuệ nhân tạo: Cách tiếp cận hiện đại" của Russell và Norvig. Không chắc nó có "tốt" không (tôi không có bất kỳ văn bản AI nào khác để so sánh với nó), nhưng nó không tệ. Ít nhất là nó khá lớn;)


Tôi cũng xác định được vấn đề đó với sự giao nhau: rất có thể một đứa trẻ có nhiều vật phẩm được đặt hơn hàng có sẵn trong hàng đợi (loại thiếu GA cho TSP: nó có thể đến các thành phố hai lần trở lên (hoặc hoàn toàn không!) crossover. Có lẽ một giao thoa được đặt hàng ( permulationcity.co.uk/projects/mutants/tsp.html ) có thể hoạt động. Điều này đặc biệt áp dụng khi bạn thực hiện chuỗi di chuyển trạng thái.
user849924

Không chắc điều đó hoàn toàn đúng - trong suy nghĩ của tôi, trạng thái thất bại là một mảnh được đặt ở vị trí đã bị chiếm đóng (do đó kết thúc trò chơi đó sớm, dẫn đến điểm số thể lực thấp). Vì vậy, chiều dài hàng đợi khớp với chiều dài của chuỗi di truyền - nó không bao giờ là độ dài sai. Tuy nhiên - bạn có thể lên một cái gì đó với ý tưởng hoán đổi và đặt hàng. Nếu một thứ tự nhất định dẫn đến một trò chơi đã hoàn thành và bạn trao đổi hai lần di chuyển, tôi tưởng tượng rằng có nhiều khả năng trạng thái bị đột biến cũng là một trò chơi hoàn thành hơn là bạn chỉ cần đặt ngẫu nhiên một (hoặc hai?) .
Andrew Russell

Trạng thái thất bại là khi bạn không có thêm tùy chọn để đặt di chuyển, tức là khi bạn hết chỗ trống và không có kết quả khớp nào xảy ra với di chuyển đó. Tương tự như những gì bạn đang nói: bạn phải đặt nó vào một vị trí đã chiếm (nhưng điều đó chỉ đúng khi không còn nơi nào để bắt đầu). Sự giao nhau mà tôi đã đăng có thể thú vị. Nhiễm sắc thể A có các mục được đặt trên A1, B1, ..., G1, A2, B2 và C2 và nhiễm sắc thể B trên G7 ... A7, G6, F6 và E6. Chọn một vài randoms từ A và giữ chỉ mục của chúng. Chọn phần bù của A từ B và giữ chỉ mục của chúng và hợp nhất cho một đứa trẻ.
user849924

"Vấn đề" với sự giao nhau này là nhiều di chuyển trên cùng một vị trí được cho phép. Nhưng điều đó có thể dễ dàng giải quyết bằng một cái gì đó tương tự như SimulationAutomaticChanges từ giải pháp của Stefan K: áp dụng bộ di chuyển / trạng thái của đứa trẻ vào trạng thái cơ bản (chỉ cần áp dụng tất cả các di chuyển, từng cái một) của sân chơi và nếu trạng thái chấp nhận (hàng đợi trống ) không thể đạt được (vì bạn phải đặt một bông hoa trên một vị trí bị chiếm đóng), sau đó đứa trẻ không hợp lệ và chúng tôi sẽ cần phải sinh sản một lần nữa. Đây là nơi tình trạng thất bại của bạn bật lên. Tôi có được cái đó bây giờ, heh. : D
user849924

Tôi chấp nhận đây là câu trả lời, vì hai lý do. Đầu tiên: bạn đã cho tôi ý tưởng tôi cần để GA hoạt động cho vấn đề này. Thứ hai: bạn là người đầu tiên. ; p
user849924

2

Phân loại

Câu trả lời không dễ. Lý thuyết trò chơi có một số phân loại cho các trò chơi, nhưng dường như không có trận đấu 1: 1 rõ ràng nào cho trò chơi đó với một lý thuyết đặc biệt. Đó là một dạng đặc biệt của bài toán tổ hợp.

Đó không phải là nhân viên bán hàng du lịch, mà sẽ quyết định cho một đơn hàng mà bạn truy cập vào "nút" với một số chi phí để đến nút tiếp theo từ nút cuối cùng. Bạn không thể sắp xếp lại hàng đợi, bạn cũng không phải sử dụng tất cả các trường trên bản đồ.

Knapsack không khớp bởi vì một số trường trở nên trống rỗng trong khi đưa một số mặt hàng vào "ba lô". Vì vậy, nó có thể là một dạng mở rộng của điều đó, nhưng rất có thể các thuật toán sẽ không được áp dụng vì điều này.

Wikipedia đưa ra một số gợi ý về phân loại ở đây: http://en.wikipedia.org/wiki/Game_theory#Types_of_games

Tôi sẽ phân loại nó là "vấn đề kiểm soát tối ưu thời gian rời rạc" ( http://en.wikipedia.org/wiki/Optimal_control ), nhưng tôi không nghĩ rằng điều này sẽ giúp bạn.

Thuật toán

Trong trường hợp bạn thực sự biết hàng đợi đầy đủ, bạn có thể áp dụng thuật toán tìm kiếm cây. Như bạn đã nói, sự phức tạp của vấn đề tăng rất nhanh với chiều dài hàng đợi. Tôi đề nghị sử dụng một thuật toán như "Tìm kiếm theo chiều sâu (DFS)", không yêu cầu nhiều bộ nhớ. Vì điểm số không quan trọng với bạn, bạn chỉ có thể dừng lại sau khi đã tìm ra giải pháp đầu tiên. Để quyết định tìm kiếm nhánh con nào trước, bạn nên áp dụng phương pháp heuristic để đặt hàng. Điều đó có nghĩa là bạn nên viết một hàm đánh giá (ví dụ: số trường trống; cái này càng tinh vi thì càng tốt), sẽ cho điểm để so sánh bước tiếp theo nào hứa hẹn nhất.

Sau đó, bạn chỉ cần các phần sau:

  1. mô hình trạng thái trò chơi, lưu trữ tất cả thông tin của trò chơi (ví dụ: trạng thái bảng / bản đồ, hàng đợi, số di chuyển / vị trí trong hàng đợi)
  2. một trình tạo di chuyển, cung cấp cho bạn tất cả các di chuyển hợp lệ cho một trạng thái trò chơi nhất định
  3. chức năng "làm di chuyển" và chức năng "hoàn tác di chuyển"; áp dụng / hoàn tác một động thái (hợp lệ) nhất định sang trạng thái trò chơi. Trong khi đó chức năng "làm di chuyển" sẽ lưu trữ một số "thông tin hoàn tác" cho chức năng "hoàn tác". Sao chép trạng thái trò chơi và sửa đổi nó trong mỗi lần lặp sẽ làm chậm đáng kể việc tìm kiếm! Ít nhất hãy thử lưu trữ trạng thái trên ngăn xếp (= biến cục bộ, không phân bổ động bằng cách sử dụng "mới").
  4. một chức năng đánh giá, cho điểm tương đương với từng trạng thái trò chơi
  5. chức năng tìm kiếm

Đây là một triển khai tham chiếu không đầy đủ cho tìm kiếm theo chiều sâu:

public class Item
{
    // TODO... represents queue items (FLOWER, SHOVEL, BUTTERFLY)
}

public class Field
{
    // TODO... represents field on the board (EMPTY or FLOWER)
}

public class Modification {
    int x, y;
    Field originalValue, newValue;

    public Modification(int x, int y, Field originalValue, newValue) {
        this.x = x;
        this.y = y;
        this.originalValue = originalValue;
        this.newValue = newValue;
    }

    public void Do(GameState state) {
        state.board[x,y] = newValue;
    }

    public void Undo(GameState state) {
        state.board[x,y] = originalValue;
    }
}

class Move : ICompareable {

    // score; from evaluation function
    public int score; 

    // List of modifications to do/undo to execute the move or to undo it
    Modification[] modifications;

    // Information for later knowing, what "control" action has been chosen
    public int x, y;   // target field chosen
    public int x2, y2; // secondary target field chosen (e.g. if moving a field)


    public Move(GameState state, Modification[] modifications, int score, int x, int y, int x2 = -1, int y2 = -1) {
        this.modifications = modifications;
        this.score = score;
        this.x = x;
        this.y = y;
        this.x2 = x2;
        this.y2 = y2;
    }

    public int CompareTo(Move other)
    {
        return other.score - this.score; // less than 0, if "this" precededs "other"...
    }

    public virtual void Do(GameState state)
    {
        foreach(Modification m in modifications) m.Do(state);
        state.queueindex++;
    }

    public virtual void Undo(GameState state)
    {
        --state.queueindex;
        for (int i = m.length - 1; i >= 0; --i) m.Undo(state); // undo modification in reversed order
    }
}

class GameState {
    public Item[] queue;
    public Field[][] board;
    public int queueindex;

    public GameState(Field[][] board, Item[] queue) {
        this.board = board;
        this.queue = queue;
        this.queueindex = 0;
    }

    private int Evaluate()
    {
        int value = 0;
        // TODO: Calculate some reasonable value for the game state...

        return value;
    }

    private List<Modification> SimulateAutomaticChanges(ref int score) {
        List<Modification> modifications = new List<Modification>();
        // TODO: estimate all "remove" flowers or recoler them according to game rules 
        // and store all changes into modifications...
        if (modifications.Count() > 0) {
            foreach(Modification modification in modifications) modification.Do(this);

            // Recursively call this function, for cases of chain reactions...
            List<Modification> moreModifications = SimulateAutomaticChanges();

            foreach(Modification modification in modifications) modification.Undo(this);

            // Add recursively generated moves...
            modifications.AddRange(moreModifications);
        } else {
            score = Evaluate();
        }

        return modifications;
    }

    // Helper function for move generator...
    private void MoveListAdd(List<Move> movelist, List<Modifications> modifications, int x, int y, int x2 = -1, int y2 = -1) {
        foreach(Modification modification in modifications) modification.Do(this);

        int score;
        List<Modification> autoChanges = SimulateAutomaticChanges(score);

        foreach(Modification modification in modifications) modification.Undo(this);

        modifications.AddRange(autoChanges);

        movelist.Add(new Move(this, modifications, score, x, y, x2, y2));
    }


    private List<Move> getValidMoves() {
        List<Move> movelist = new List<Move>();
        Item nextItem = queue[queueindex];
        const int MAX = board.length * board[0].length + 2;

        if (nextItem.ItemType == Item.SHOVEL)
        {

            for (int x = 0; x < board.length; ++x)
            {
                for (int y = 0; y < board[x].length; ++y)
                {
                    // TODO: Check if valid, else "continue;"

                    for (int x2 = 0; x2 < board.length; ++x2)
                    {
                        for(int y2 = 0; y2 < board[x].length; ++y2) {
                            List<Modifications> modifications = new List<Modifications>();

                            Item fromItem = board[x][y];
                            Item toItem = board[x2][y2];
                            modifications.Add(new Modification(x, y, fromItem, Item.NONE));
                            modifications.Add(new Modification(x2, y2, toItem, fromItem));

                            MoveListAdd(movelist, modifications, x, y, x2, y2);
                        }
                    }
                }
            }

        } else {

            for (int x = 0; x < board.length; ++x)
            {
                for (int y = 0; y < board[x].length; ++y)
                {
                    // TODO: check if nextItem may be applied here... if not "continue;"

                    List<Modifications> modifications = new List<Modifications>();
                    if (nextItem.ItemType == Item.FLOWER) {
                        // TODO: generate modifications for putting flower at x,y
                    } else {
                        // TODO: generate modifications for putting butterfly "nextItem" at x,y
                    }

                    MoveListAdd(movelist, modifications, x, y);
                }
            }
        }

        // Sort movelist...
        movelist.Sort();

        return movelist;
    }


    public List<Move> Search()
    {
        List<Move> validmoves = getValidMoves();

        foreach(Move move in validmoves) {
            move.Do(this);
            List<Move> solution = Search();
            if (solution != null)
            {
                solution.Prepend(move);
                return solution;
            }
            move.Undo(this);
        }

        // return "null" as no solution was found in this branch...
        // this will also happen if validmoves == empty (e.g. lost game)
        return null;
    }
}

Mã này không được xác minh để hoạt động, cũng không thể biên dịch hoặc hoàn thành. Nhưng nó sẽ cho bạn một ý tưởng làm thế nào để làm điều đó. Công việc quan trọng nhất là chức năng đánh giá. Càng tinh vi, thuật toán "thử" sai sẽ thử (và phải hoàn tác) sau này. Điều này cực kỳ làm giảm sự phức tạp.

Nếu điều này quá chậm, bạn cũng có thể thử áp dụng một số phương pháp trò chơi hai người như HashTables. Vì vậy, bạn sẽ phải tính toán khóa băm (lặp) cho từng trạng thái trò chơi mà bạn đánh giá và đánh dấu các trạng thái không dẫn đến giải pháp. Ví dụ: mỗi lần trước khi phương thức Search () trả về "null", một mục nhập HashTable phải được tạo và khi vào Tìm kiếm () bạn sẽ kiểm tra xem trạng thái này đã đạt được chưa cho đến khi không có kết quả dương và nếu trả về "null" mà không có tiếp tục điều tra. Đối với điều này, bạn sẽ cần một bảng băm lớn và sẽ phải chấp nhận "va chạm băm" có thể khiến bạn có thể không tìm thấy giải pháp hiện có, nhưng điều này rất khó xảy ra, nếu các hàm băm của bạn đủ tốt và bảng của bạn là đủ lớn (rủi ro của rủi ro có thể tính toán).

Tôi nghĩ rằng không có thuật toán nào khác để giải quyết vấn đề này (như được mô tả bởi bạn) hiệu quả hơn, giả sử chức năng đánh giá của bạn là tối ưu ...


Vâng, tôi có thể biết hàng đợi đầy đủ. Việc triển khai chức năng đánh giá cũng sẽ xem xét một vị trí hợp lệ, nhưng có khả năng xấu? Có khả năng xấu là một động thái như đặt nó bên cạnh bông hoa có màu khác khi đã có một màu tương tự trên sân? Hoặc đặt một bông hoa ở đâu đó mà một khối hoàn toàn khác nhau vì thiếu không gian?
user849924

Câu trả lời này đã cho tôi ý tưởng cho mô hình và cách làm việc với các quy tắc trò chơi, vì vậy tôi sẽ nâng cao nó. Cảm ơn vì đầu vào của bạn!
user849924

@ user849924: Có, tất nhiên chức năng đánh giá phải tính toán "giá trị" đánh giá cho điều đó. Trạng thái trò chơi hiện tại càng trở nên tồi tệ (gần mất), giá trị đánh giá được trả về càng tệ. Đánh giá dễ nhất sẽ là trả về số lượng trường trống. Bạn có thể cải thiện điều này bằng cách thêm 0,1 cho mỗi bông hoa được đặt bên cạnh một bông hoa có màu tương tự. Để xác minh chức năng của bạn, chọn một số trạng thái trò chơi ngẫu nhiên, tính toán giá trị của chúng và so sánh chúng. Nếu bạn nghĩ trạng thái A tốt hơn trạng thái B, điểm số trước A sẽ tốt hơn trạng thái của B.
SDwarfs
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.