Tôi đã giúp chuyển một trong những game này sang một nền tảng cầm tay. Nhìn lại mã AI của họ để tìm ra tiềm năng: yipe, nó phức tạp, tàn bạo (vòng lặp được lồng bốn lần, thỉnh thoảng tự gọi đệ quy, v.v.), và nó không xuất hiện ở tất cả thân thiện với bộ đệm ngay từ cái nhìn đầu tiên.
(Một số sự phức tạp đó đến từ việc cố gắng đánh giá sức mạnh của sự di chuyển trong bối cảnh: định giá chuỗi dài hơn cao hơn, tìm kiếm các combo, v.v ...)
Nhưng nó không thực sự cần phải "tối ưu"; chúng tôi thậm chí không chạm vào mã khi chúng tôi chuyển nó. Nó không bao giờ xuất hiện trên hồ sơ.
Nhìn vào nó bây giờ, ngay cả ở một từ 32 bit trên mỗi ô (và tôi nghĩ rằng họ thực sự đã sử dụng một byte cho mỗi ô), toàn bộ bảng sẽ phù hợp với bộ đệm L1 rất nhỏ và bạn có thể đọc nhiều thứ trên bộ nhớ cache mà không ảnh hưởng tốc độ khung hình quá nhiều. Đặc biệt là vì bạn chỉ cần thực hiện toàn bộ quá trình này một lần mỗi khi cấu hình bảng thay đổi. (Một con theta lớn bay lượn xung quanh n^2
không tệ đến mức khủng khiếp với mức rất thấp n
, chưa kể đến hệ số nhân nhỏ được cung cấp cho bộ nhớ đệm.)
Điều đó đã được nói: để giải trí, chúng ta hãy cố gắng song song hóa vấn đề. Bắt đầu với các hoạt động bitwise.
Giả sử bạn có một bitmask đại diện cho tất cả các mảnh (chúng ta sẽ gọi chúng là đá) trong một hàng thuộc một loại cụ thể (chúng ta sẽ sử dụng màu sắc để phân biệt các loại). Chúng ta sẽ chỉ bắt đầu nhìn vào những viên đá đỏ và lo lắng về chi phí tính toán bitmask sau này.
// Let's assume top right indexing.
// (The assumption is not necessary, --
// it just makes the left-shift and right-shift operators
// look like they're pointing in the correct direction.)
// this is for row 2
col index 76543210
color BRRGYRBR // blue, red, red, green, yellow, ...
"red" bits 01100101
Chúng tôi đang tìm kiếm các loạt mà cần chỉ là một trao đổi để trở thành một loạt các 3. Courtesy Kaj, đây là một trong ba kết hợp, về cơ bản: XoX
, oXX
, hoặc XXo
nơi X
là một hòn đá phù hợp và o
là cái gì khác.
(Ý tưởng này được mượn từ cuốn sách Tuyệt vời của Hacker tuyệt vời ; xem thêm fxtbook nếu những điều đó làm bạn hài lòng.)
// using c-style bitwise operators:
// & is "and"
// ^ is "xor"
// | is "or"
// << and >> are arithmetic (non-sign-extending) shifts
redBitsThisRow = redBitsRows[2]
// find the start of an XoX sequence
startOfXoXSequence = redBitsThisRow & (redBitsThisRow << 2);
// for our example, this will be 00000100
// find any two stones together in a row
startOfXXSequence = redBitsThisRow & (redBitsThisRow << 1);
// for our example, this will be 01000000
Sẽ hữu ích hơn khi biết vị trí của những viên đá bị thiếu, không phải là sự khởi đầu của chuỗi XX hoặc XoX:
// give us any sequences that might want a stone from the left
missingLeftStone = startOfXXSequence << 1;
// for our example, this will be 10000000
// give us any sequences that might want a stone from the right
missingRightStone = startOfXXSequence >> 2;
// for our example, this will be 00010000
// give us any sequences that might want a stone from the top or bottom
missingTopOrBottomStone = missingRightStone | missingLeftStone | (startOfXoXSequence >> 1)
// for our example, this will be 10010010
(Khoảng 1 tải và 9 hướng dẫn ALU - 5 ca, 2 ors, 2 ands - với CPU khủng khiếp không bao gồm bộ chuyển đổi nội tuyến. Trên nhiều kiến trúc, các ca này có thể miễn phí.)
Chúng ta có thể lấp đầy những chỗ còn thiếu không?
// look to the left, current row
leftMatches = redBitsThisRow & (missingLeftStone << 1)
// look to the right, current row
rightMatches = redBitsThisRow & (missingRightStone >> 1)
// look on the row above
topMatches = redBitsRow[1] & missingTopOrBottomStone
// look on the row below
bottomMatches = redBitsRow[3] & missingTopOrBottomStone
(2 lần tải khác và 6 hướng dẫn ALU - 4 và 2, 2 ca - với CPU kém. Lưu ý rằng hàng 0 và hàng 7 có thể gây rắc rối cho bạn - bạn có thể chọn phân nhánh xung quanh các tính toán này hoặc tránh phân nhánh bằng cách phân bổ không gian cho hai hàng thừa, một trước 0 và một sau 7 và bỏ trống chúng.)
Bây giờ chúng ta có một số lọ "khớp" cho biết vị trí của một hòn đá có thể hoán đổi để tạo ra một trận đấu.
Điều này giả định một phương pháp nội tuyến "đếm số không" hoặc rất rẻ:
swapType = RIGHT_TO_LEFT;
matches = leftMatches;
while ( (colIdx = ctz(matches)) < WORD_BITS ) {
// rowIdx is 2 in our examples above
workingSwaps.insert( SwapInfo(rowIdx, colIdx, swapType) );
// note that this SwapInfo construction could do some more advanced logic:
// run the swap on a temporary board and see how much score it accumulates
// assign some sort of value based on preferring one type of match to another, etc
matches = matches ^ (1<<colIdx); // clear the match, so we can loop to the next
}
// repeat for LEFT_TO_RIGHT with rightMatches
// repeat for TOP_TO_BOTTOM with topMatches
// repeat for BOTTOM_TO_TOP with bottomMatches
Lưu ý rằng không có logic bit nào bị phá vỡ trong môi trường endian nhỏ và endian lớn. Nó trở nên khó khăn hơn nhiều đối với các bảng rộng hơn kích thước từ máy của bạn. (Bạn có thể thử nghiệm một cái gì đó như thế std::bitset
này.)
Còn cột thì sao? Có thể dễ nhất chỉ cần có hai bản sao của bảng, một bản được lưu theo thứ tự hàng và một bản được lưu theo thứ tự cột. Nếu bạn có quyền truy cập bảng getters và setters, điều này sẽ là tầm thường. Tôi không ngại giữ hai mảng cập nhật, sau khi tất cả một tập hợp trở nên rowArray[y][x] = newType; colArray[x][y] = newType;
đơn giản.
... nhưng quản lý rowBits[color][row]
và colBits[color][col]
trở nên đáng ghét.
Tuy nhiên, như một cách nhanh chóng, nếu bạn có rowBits
và colBits
, bạn có thể chạy cùng một mã với rowBits trỏ vào colBits thay thế. Mã giả, giả sử chiều rộng bảng = chiều cao bảng = 8 trong trường hợp này ...
foreach color in colors {
foreach bits in rowBits, colBits {
foreach row in 0..7 { // row is actually col the second time through
// find starts, as above but in bits[row]
// find missings, as above
// generate matches, as above but in bits[row-1], bits[row], and bits[row+1]
// loop across bits in each matches var,
// evaluate and/or collect them, again as above
}
}
}
Điều gì xảy ra nếu chúng ta không muốn làm phiền với việc chuyển đổi một mảng 2D đẹp thành bit? Với bảng 8 x 8, 8 bit cho mỗi ô và bộ xử lý có khả năng 64 bit, chúng tôi có thể thoát khỏi nó: 8 ô = 8 byte = 64 bit. Bây giờ chúng tôi đang bị khóa với chiều rộng bảng của chúng tôi, nhưng điều này có vẻ đầy hứa hẹn.
Giả sử "0" được bảo lưu, các viên đá bắt đầu từ 1 và đi tới NUM_STONE_TYPES.
startOfXXSequence = rowBytes ^ (rowBytes << (8*1))
// now all bytes that are 0x00 are the start of a XX sequence
startOfXoXSequence = rowBytes ^ (rowBytes << (8*2))
// all bytes that are 0x00 are the start of a XoX sequence
Lưu ý điều này không cần một lượt cho mỗi màu. Trong BRBRBRGY
chúng ta sẽ nhận được một startOfXoXSequence
thứ có thể giống như 0x00 00 00 00 aa bb cc dd
- bốn byte trên cùng sẽ bằng 0, cho thấy một chuỗi có thể bắt đầu từ đó.
Sắp muộn rồi, vì vậy tôi sẽ rời khỏi đây và có thể quay lại sau - bạn có thể tiếp tục đi theo hướng này với các xors và thủ thuật "phát hiện byte 0 đầu tiên" hoặc bạn có thể xem xét một số tiện ích mở rộng SIMD nguyên .