Bảng C99 - 3x3 trong 0,084s
Chỉnh sửa: Tôi đã cấu trúc lại mã của mình và phân tích sâu hơn về kết quả.
Chỉnh sửa thêm : Thêm cắt tỉa bằng đối xứng. Điều này tạo ra 4 cấu hình thuật toán: có hoặc không có đối xứng X có hoặc không có cắt tỉa alpha-beta
Chỉnh sửa xa nhất: Đã thêm ghi nhớ bằng cách sử dụng bảng băm, cuối cùng đạt được điều không thể: giải quyết bảng 3x3!
Các tính năng chính:
- thực hiện đơn giản minimax với cắt tỉa alpha-beta
- quản lý bộ nhớ rất ít (duy trì dll các di chuyển hợp lệ; O (1) cập nhật cho mỗi nhánh trong tìm kiếm cây)
- tập tin thứ hai với việc cắt tỉa bằng đối xứng. Vẫn đạt được cập nhật O (1) trên mỗi nhánh (về mặt kỹ thuật O (S) trong đó S là số lượng đối xứng. Đây là 7 cho bảng vuông và 3 cho bảng không vuông)
- tập tin thứ ba và thứ tư thêm ghi nhớ. Bạn có quyền kiểm soát kích thước của hashtable (
#define HASHTABLE_BITWIDTH
). Khi kích thước này lớn hơn hoặc bằng số lượng tường, nó đảm bảo không có va chạm và cập nhật O (1). Các hashtag nhỏ hơn sẽ có nhiều va chạm hơn và chậm hơn một chút.
- biên dịch với
-DDEBUG
bản in
Cải tiến tiềm năng:
sửa lỗi rò rỉ bộ nhớ nhỏ trong lần chỉnh sửa đầu tiên
cắt tỉa alpha / beta được thêm vào trong lần chỉnh sửa thứ 2
đối xứng prune được thêm vào trong lần chỉnh sửa thứ 3 (lưu ý rằng các đối xứng không được xử lý bằng cách ghi nhớ, do đó vẫn là một tối ưu hóa riêng biệt.)
ghi nhớ được thêm vào trong lần chỉnh sửa thứ 4
- hiện tại ghi nhớ sử dụng một bit chỉ thị cho mỗi bức tường. Một bảng 3x4 có 31 bức tường, vì vậy phương pháp này không thể xử lý các bảng 4 x 4 bất kể hạn chế về thời gian. cải tiến sẽ là mô phỏng các số nguyên X-bit, trong đó X ít nhất bằng số lượng tường.
Mã
Do thiếu tổ chức, số lượng tệp đã vượt quá tầm tay. Tất cả mã đã được chuyển đến Kho lưu trữ Github này . Trong phần chỉnh sửa ghi nhớ, tôi đã thêm một tập lệnh makefile và thử nghiệm.
Các kết quả
Ghi chú về độ phức tạp
Brute-force tiếp cận với các chấm và hộp nổ tung rất phức tạp .
Hãy xem xét một bảng với R
các hàng và C
cột. Có R*C
hình vuông, R*(C+1)
tường dọc và C*(R+1)
tường ngang. Đó là tổng cộng W = 2*R*C + R + C
.
Bởi vì Lembik yêu cầu chúng tôi giải quyết trò chơi bằng minimax, chúng tôi cần đi qua những chiếc lá của cây trò chơi. Bây giờ chúng ta hãy bỏ qua việc cắt tỉa, bởi vì điều quan trọng là thứ tự cường độ.
Có những W
lựa chọn cho bước đầu tiên. Đối với mỗi người, người chơi tiếp theo có thể chơi bất kỳ W-1
bức tường nào còn lại, v.v. Điều đó cho chúng ta không gian tìm kiếm SS = W * (W-1) * (W-2) * ... * 1
, hoặc SS = W!
. Yếu tố rất lớn, nhưng đó mới chỉ là khởi đầu. SS
là số nút lá trong không gian tìm kiếm. Liên quan nhiều hơn đến phân tích của chúng tôi là tổng số quyết định phải đưa ra (tức là số nhánh B
trong cây). Lớp đầu tiên của các nhánh có W
các tùy chọn. Đối với mỗi người, cấp độ tiếp theo có W-1
, v.v.
B = W + W*(W-1) + W*(W-1)*(W-2) + ... + W!
B = SUM W!/(W-k)!
k=0..W-1
Hãy xem xét một số kích thước bảng nhỏ:
Board Size Walls Leaves (SS) Branches (B)
---------------------------------------------------
1x1 04 24 64
1x2 07 5040 13699
2x2 12 479001600 1302061344
2x3 17 355687428096000 966858672404689
Những con số này đang trở nên vô lý. Ít nhất họ giải thích lý do tại sao mã lực lượng vũ phu dường như bị treo mãi mãi trên bảng 2x3. Không gian tìm kiếm của bảng 2x3 lớn hơn 742560 lần so với 2x2 . Nếu 2x2 mất 20 giây để hoàn thành, phép ngoại suy bảo thủ dự đoán hơn 100 ngày thời gian thực hiện cho 2x3. Rõ ràng chúng ta cần cắt tỉa.
Phân tích cắt tỉa
Tôi bắt đầu bằng cách thêm việc cắt tỉa rất đơn giản bằng thuật toán alpha-beta. Về cơ bản, nó ngừng tìm kiếm nếu một đối thủ lý tưởng sẽ không bao giờ cho nó cơ hội hiện tại. "Này này - tôi sẽ thắng rất nhiều nếu đối thủ của tôi cho phép tôi có được mọi ô vuông!", Không bao giờ nghĩ đến AI.
chỉnh sửa Tôi cũng đã thêm cắt tỉa dựa trên bảng đối xứng. Tôi không sử dụng phương pháp ghi nhớ, chỉ trong trường hợp một ngày nào đó tôi thêm ghi nhớ và muốn tách riêng phân tích đó. Thay vào đó, nó hoạt động như thế này: hầu hết các dòng có "cặp đối xứng" ở một nơi khác trên lưới. Có tới 7 đối xứng (ngang, dọc, xoay 180, xoay 90, xoay 270, chéo và các đường chéo khác). Tất cả 7 áp dụng cho bảng vuông, nhưng 4 cuối cùng không áp dụng cho bảng không vuông. Mỗi bức tường có một con trỏ là "cặp" cho mỗi đối xứng này. Nếu, đi vào một ngã rẽ, bảng là đối xứng theo chiều ngang, thì chỉ cần chơi một trong mỗi cặp ngang .
chỉnh sửa chỉnh sửa Ghi nhớ! Mỗi bức tường có một id duy nhất, mà tôi thuận tiện đặt là một bit chỉ báo; bức tường thứ n có id 1 << n
. Băm của một bảng, sau đó, chỉ là OR của tất cả các bức tường được chơi. Điều này được cập nhật tại mỗi chi nhánh trong thời gian O (1). Kích thước của hashtable được đặt trong a #define
. Tất cả các thử nghiệm đã được chạy với kích thước 2 ^ 12, vì tại sao không? Khi có nhiều bức tường hơn các bit lập chỉ mục hashtable (12 bit trong trường hợp này), 12 ít quan trọng nhất được che dấu và được sử dụng làm chỉ mục. Va chạm được xử lý với một danh sách được liên kết tại mỗi chỉ số hashtable. Biểu đồ sau đây là phân tích nhanh và bẩn của tôi về cách kích thước có thể băm ảnh hưởng đến hiệu suất. Trên máy tính có RAM vô hạn, chúng tôi sẽ luôn đặt kích thước của bảng thành số lượng tường. Một bảng 3x4 sẽ có hashtable dài 2 ^ 31. Than ôi chúng ta không có sự xa xỉ đó.
Ok, quay lại cắt tỉa .. Bằng cách dừng tìm kiếm ở trên cây, chúng ta có thể tiết kiệm rất nhiều thời gian bằng cách không đi xuống lá. "Yếu tố cắt tỉa" là một phần của tất cả các nhánh có thể mà chúng tôi phải ghé thăm. Brute-force có hệ số cắt tỉa là 1. Càng nhỏ thì càng tốt.