2016-05-20 24 views
5

I'v có một bảng (mảng 2d), c x r. Cần tạo ra một mẫu ngẫu nhiên của các ô được kết nối bên trong nó. Không có giao lộ tự và không có đường chéo. Xem hình ảnh liên quan chẳng hạn. ex. 1 с = 6, r = 7, mẫu được hiển thị bằng số.cách tốt nhất để tạo mẫu ngẫu nhiên bên trong một bảng

Tôi đã viết một chức năng cho điều này và nó hoạt động tốt, nhưng tôi đang tìm kiếm tối ưu hóa cứng. Trong đoạn code dưới đây bạn có thể thấy rằng nếu mẫu được đưa vào một ngõ cụt, nó chỉ tự xây dựng lại từ đầu. Điều đó rất không hiệu quả nếu chiều dài mẫu gần hoặc bằng với số lượng ô, c * r (42 trong ví dụ). Vì vậy, một số giải pháp thông minh là cần thiết cho việc này, như di chuyển toàn bộ mô hình đối xứng khi nó hết các động thái có thể hoặc để thêm một số phân tích vào hàm để nó không bao giờ tuôn ra ở đầu chết. Một lần nữa, đối với các giá trị thấp của c, r và patternLength ví dụ của tôi hoạt động tốt, nhưng tôi đang tìm kiếm sự hoàn hảo về thuật toán và hiệu suất cao ngay cả trên các con số khá cao.

function ClassLogic:generatePattern() 
    --[[ subfunctions ]] 
    --choosing next point for the pattern 
    local move = function(seq) 
    --getting the last sequence point 
    local last = seq[#seq] 

    -- checking the nearness of walls 
    local 
     wallLeft, 
     wallRight, 
     wallUp, 
     wallDown = 
     (last.c==1), 
     (last.c==config.tableSize.c), 
     (last.r==1), 
     (last.r==config.tableSize.r)  

    -- checking the nearness of already sequenced points 
    local 
     spLeft, 
     spRight, 
     spUp, 
     spDown = 
     (utilities.indexOfTable(seq, { c = last.c - 1, r = last.r })~=-1), 
     (utilities.indexOfTable(seq, { c = last.c + 1, r = last.r })~=-1), 
     (utilities.indexOfTable(seq, { c = last.c, r = last.r - 1 })~=-1), 
     (utilities.indexOfTable(seq, { c = last.c, r = last.r + 1 })~=-1) 

    local leftRestricted = (wallLeft or spLeft) 
    local rightRestricted = (wallRight or spRight) 
    local upRestricted = (wallUp or spUp) 
    local downRestricted = (wallDown or spDown) 

    if (leftRestricted and rightRestricted and upRestricted and downRestricted) then 
     -- dead end 
     print('d/e') 
     return nil 
    else  
     -- go somewhere possible 
     local possibleDirections = {} 
     if (not leftRestricted) then possibleDirections[#possibleDirections+1] = 1 end 
     if (not rightRestricted) then possibleDirections[#possibleDirections+1] = 2 end 
     if (not upRestricted) then possibleDirections[#possibleDirections+1] = 3 end 
     if (not downRestricted) then possibleDirections[#possibleDirections+1] = 4 end 

     local direction = possibleDirections[math.random(1, #possibleDirections)]  
     if (direction==1) then 
     --next point is left 
     return { c = last.c - 1, r = last.r } 
     elseif (direction==2) then 
     --next point is right 
     return { c = last.c + 1, r = last.r } 
     elseif (direction==3) then 
     --next point is up 
     return { c = last.c, r = last.r - 1 } 
     elseif (direction==4) then 
     --next point is down 
     return { c = last.c, r = last.r + 1 } 
     end 
    end 
    end 
    --[[ subfunctions end ]] 

    -- choose random entry point 
    local entry = { c = math.random(1, config.tableSize.c), 
        r = math.random(1, config.tableSize.r) } 

    -- start points sequence 
    local pointSequence = { [1] = entry } 

    -- building the pattern 
    local succeed = false 
    while (not succeed) do 
    for i = 2, self.patternLength do 
     local nextPoint = move(pointSequence) 
     if (nextPoint~=nil) then 
     pointSequence[i] = nextPoint 
     if (i==self.patternLength) then succeed = true end 
     else 
     pointSequence = { [1] = entry } 
     break 
     end  
    end 
    end 
    return pointSequence 
end 

Bất kỳ ý tưởng hay cách tiếp cận nào về việc này có thể được thực hiện sẽ được đánh giá cao. Có lẽ một số backtracker đệ quy hoặc một pathfinding hoặc một thuật toán đi bộ ngẫu nhiên?

+2

Nếu bạn đạt đến một ngõ cụt bạn có thể sử dụng https://en.wikipedia.org/wiki/Backtracking để hoàn tác các bước thay vì đầu từ đầu một lần nữa. – MrSmith42

+1

Có cần phải hoàn toàn ngẫu nhiên trong số tất cả các chuyến đi như vậy không? Hầu hết các phương pháp "vá nó lên đến một chút lớn hơn" sẽ không đảm bảo điều đó. – btilly

+0

@ MrSmith42 Vâng, tôi đã nghĩ về nó. Nhưng có vẻ như yêu cầu phân tích khá thông minh. Cần bao nhiêu lần hoàn tác? Do nó đảm bảo rằng mô hình sẽ đi lên tốt sau đó? Hướng nào nên được sử dụng thay thế (nên thuật toán nhớ đó là sự lựa chọn)? Vv Nó có thể ảnh hưởng nghiêm trọng đến hiệu suất. (c = 1000, r = 1000, patternLength = 1000000 và chúng tôi cần tính toán tất cả các giải pháp có thể). Trong thực tế, nếu patternLength == c * r hoặc rất gần với nó, tôi nghĩ rằng nó sẽ được veryfied và mô hình nên hành động với một số sửa chữa trên ngẫu nhiên để _avoid_ chết kết thúc thay vì sử dụng undos. – Aleksei

Trả lời

2

Việc phát triển kiểu rắn là không đủ cho hiệu suất tốt.
Ý tưởng chính là để sửa đổi một cách ngẫu nhiên con đường được tạo ra bằng cách thêm đường vòng nhỏ như sau:

- - 6 - -   - - 8 - - 
- - 5 - -   - 6 7 - - 
- - 4 1 - ===> - 5 4 1 - 
- - 3 2 -   - - 3 2 - 
- - - - -   - - - - - 

(lưu ý hai tế bào thêm thêm vào bên trái của 4-5 phân khúc)

thực hiện như vậy hoạt động rất nhanh cho khu vực điền < 95%

local function generate_path(W, H, L) 
    -- W = field width (number of columns) -- c = 1..W 
    -- H = field height (number of rows) -- r = 1..H 
    -- L = path length, must be within range 1..W*H 

    assert(L >= 1 and L <= W * H, "Path length is greater than field area") 

    local function get_idx(x, y) 
     return x >= 1 and x <= W and y >= 1 and y <= H and (y - 1) * W + x 
    end 

    local function get_x_y(idx) 
     local x = (idx - 1) % W + 1 
     local y = (idx - x)/W + 1 
     return x, y 
    end 

    local function random_sort(array) 
     for last = #array, 2, -1 do 
     local pos = math.random(last) 
     array[pos], array[last] = array[last], array[pos] 
     end 
    end 

    local path_sum_x = 0 
    local path_sum_y = 0 
    local path_ctr = 0 

    local is_unused = {} -- [idx] = true/nil (or idx recently swapped with) 

    local function mark_as_unused(idx, value) 
     local x, y = get_x_y(idx) 
     path_sum_x = path_sum_x - x 
     path_sum_y = path_sum_y - y 
     path_ctr = path_ctr - 1 
     is_unused[idx] = value or true 
    end 

    local function mark_as_path(idx) 
     local x, y = get_x_y(idx) 
     path_sum_x = path_sum_x + x 
     path_sum_y = path_sum_y + y 
     path_ctr = path_ctr + 1 
     is_unused[idx] = nil 
    end 

    for x = 1, W do 
     for y = 1, H do 
     is_unused[get_idx(x, y)] = true 
     end 
    end 

    -- create path of length 1 by selecting random cell 
    local idx = get_idx(math.random(W), math.random(H)) 
    mark_as_path(idx) 
    local path = {first = idx, last = idx, [idx] = {}} 
    -- path[idx] == {next=next_idx/nil, prev=prev_idx/nil} 

    local function grow() 
     local variants = { 
     {dx=-1, dy=0, origin="last"}, {dx=1, dy=0, origin="last"}, 
     {dx=0, dy=-1, origin="last"}, {dx=0, dy=1, origin="last"}, 
     {dx=-1, dy=0, origin="first"}, {dx=1, dy=0, origin="first"}, 
     {dx=0, dy=-1, origin="first"}, {dx=0, dy=1, origin="first"} 
     } 
     random_sort(variants) 
     for _, vector in ipairs(variants) do 
     local x, y = get_x_y(path[vector.origin]) 
     local idx = get_idx(vector.dx + x, vector.dy + y) 
     if is_unused[idx] then 
      if vector.origin == 'first' then 
       -- add new first cell of the path 
       local old_first = path.first 
       path[old_first].prev = idx 
       path[idx] = {next = old_first} 
       path.first = idx 
      else 
       -- add new last cell of the path 
       local old_last = path.last 
       path[old_last].next = idx 
       path[idx] = {prev = old_last} 
       path.last = idx 
      end 
      mark_as_path(idx) 
      return true 
     end 
     end 
    end 

    local function shrink() 
     if math.random(2) == 2 then 
     -- remove first cell of the path 
     local old_first = path.first 
     local new_first = assert(path[old_first].next) 
     path[old_first] = nil 
     path.first = new_first 
     path[new_first].prev = nil 
     mark_as_unused(old_first) 
     else 
     -- remove last cell of the path 
     local old_last = path.last 
     local new_last = assert(path[old_last].prev) 
     path[old_last] = nil 
     path.last = new_last 
     path[new_last].next = nil 
     mark_as_unused(old_last) 
     end 
    end 

    local function inflate() 
     local variants = {} 
     local idx1 = path.first 
     repeat 
     local idx4 = path[idx1].next 
     if idx4 then 
      local x1, y1 = get_x_y(idx1) 
      local x4, y4 = get_x_y(idx4) 
      local dx14, dy14 = x4 - x1, y4 - y1 
      local dx, dy = dy14, dx14 
      for side = 1, 2 do 
       dx, dy = -dx, -dy 
       local x2, y2 = x1 + dx, y1 + dy 
       local idx2 = get_idx(x2, y2) 
       local idx3 = get_idx(x2 + dx14, y2 + dy14) 
       if is_unused[idx2] and is_unused[idx3] then 
        table.insert(variants, {idx1, idx2, idx3, idx4}) 
       end 
      end 
     end 
     idx1 = idx4 
     until not idx4 
     if #variants > 0 then 
     local idx1, idx2, idx3, idx4 = 
      (table.unpack or unpack)(variants[math.random(#variants)]) 
     -- insert idx2 and idx3 between idx1 and idx4 
     path[idx1].next = idx2 
     path[idx2] = {prev = idx1, next = idx3} 
     path[idx3] = {prev = idx2, next = idx4} 
     path[idx4].prev = idx3 
     mark_as_path(idx2) 
     mark_as_path(idx3) 
     return true 
     end 
    end 

    local function euclid(dx, dy) 
     return dx*dx + dy*dy 
    end 

    local function swap() 
     local variants = {} 
     local path_center_x = path_sum_x/path_ctr 
     local path_center_y = path_sum_y/path_ctr 
     local idx1 = path.first 
     repeat 
     local idx2 = path[idx1].next 
     local idx3 = idx2 and path[idx2].next 
     if idx3 then 
      local x1, y1 = get_x_y(idx1) 
      local x2, y2 = get_x_y(idx2) 
      local x3, y3 = get_x_y(idx3) 
      local dx12, dy12 = x2 - x1, y2 - y1 
      local dx23, dy23 = x3 - x2, y3 - y2 
      if dx12 * dx23 + dy12 * dy23 == 0 then 
       local x, y = x1 + dx23, y1 + dy23 
       local idx = get_idx(x, y) 
       local dist2 = euclid(x2 - path_center_x, y2 - path_center_y) 
       local dist = euclid(x - path_center_x, y - path_center_y) 
       if is_unused[idx] and dist2<dist and is_unused[idx]~=idx2 then 
        table.insert(variants, {idx1, idx2, idx3, idx}) 
       end 
      end 
     end 
     idx1 = idx2 
     until not idx3 
     if #variants > 0 then 
     local idx1, idx2, idx3, idx = 
      (table.unpack or unpack)(variants[math.random(#variants)]) 
     -- swap idx2 and idx 
     path[idx1].next = idx 
     path[idx] = path[idx2] 
     path[idx3].prev = idx 
     path[idx2] = nil 
     mark_as_unused(idx2, idx) 
     mark_as_path(idx) 
     return true 
     end 
    end 

    local actions = {grow, inflate, swap} 

    repeat 
     random_sort(actions) 
     local success 
     for _, action in ipairs(actions) do 
     success = action() 
     if success then 
      break 
     end 
     end 
     if not success and path_ctr < L then 
     -- erase and rewind 
     while path_ctr > 1 do 
      shrink() 
     end 
     end 
    until path_ctr >= L 

    while path_ctr > L do 
     shrink() 
    end 

    local pointSequence = {} 
    local idx = path.first 
    local step = 0 
    repeat 
     step = step + 1 
     path[idx].step = step 
     local x, y = get_x_y(idx) 
     pointSequence[step] = {c = x, r = y} 
     idx = path[idx].next 
    until not idx 
    local field = 'W = '..W..', H = '..H..', L = '..L..'\n' 
    for y = 1, H do 
     for x = 1, W do 
     local c = path[get_idx(x, y)] 
     field = field..(' '..(c and c.step or '-')):sub(-4) 
     end 
     field = field..'\n' 
    end 
    print(field) 

    return pointSequence 

end 

Cách sử dụng Ví dụ:

math.randomseed(os.time()) 

local pointSequence = generate_path(6, 7, 10) 
-- pointSequence = {[1]={r=r1,c=c1}, [2]={r=r2,c=c2},...,[10]={r=r10,c=c10}} 

Kết quả ví dụ:

W = 5, H = 5, L = 10 

- - - 9 10 
- 6 7 8 - 
- 5 4 1 - 
- - 3 2 - 
- - - - - 


W = 5, H = 5, L = 19 

15 16 17 18 19 
14 1 2 3 4 
13 12 11 6 5 
- - 10 7 - 
- - 9 8 - 



W = 6, H = 7, L = 35 

- 35 34 25 24 23 
- - 33 26 21 22 
- 31 32 27 20 19 
- 30 29 28 - 18 
- 1 10 11 12 17 
3 2 9 8 13 16 
4 5 6 7 14 15 



W = 19, H = 21, L = 394 

77 78 79 84 85 118 119 120 121 122 123 124 125 126 127 128 129 254 255 
76 75 80 83 86 117 116 115 114 141 140 139 138 135 134 131 130 253 256 
73 74 81 82 87 88 89 112 113 142 145 146 137 136 133 132 - 252 257 
72 69 68 67 92 91 90 111 - 143 144 147 148 149 150 151 152 251 258 
71 70 65 66 93 108 109 110 163 162 161 160 159 158 157 156 153 250 259 
58 59 64 63 94 107 166 165 164 191 192 193 196 197 - 155 154 249 260 
57 60 61 62 95 106 167 168 189 190 - 194 195 198 241 242 243 248 261 
56 55 54 53 96 105 170 169 188 203 202 201 200 199 240 239 244 247 262 
47 48 51 52 97 104 171 172 187 204 205 206 231 232 237 238 245 246 263 
46 49 50 99 98 103 174 173 186 209 208 207 230 233 236 267 266 265 264 
45 42 41 100 101 102 175 184 185 210 211 228 229 234 235 268 269 270 271 
44 43 40 39 38 177 176 183 214 213 212 227 226 225 276 275 274 273 272 
33 34 35 36 37 178 179 182 215 216 217 218 223 224 277 278 279 280 281 
32 29 28 23 22 - 180 181 12 11 10 219 222 287 286 285 284 283 282 
31 30 27 24 21 18 17 14 13 8 9 220 221 288 289 290 291 292 293 
380 381 26 25 20 19 16 15 394 7 4 3 304 303 300 299 296 295 294 
379 382 383 384 387 388 391 392 393 6 5 2 305 302 301 298 297 312 313 
378 371 370 385 386 389 390 347 346 343 342 1 306 307 308 309 310 311 314 
377 372 369 364 363 350 349 348 345 344 341 340 333 332 319 318 317 316 315 
376 373 368 365 362 351 352 353 354 355 338 339 334 331 320 321 322 323 324 
375 374 367 366 361 360 359 358 357 356 337 336 335 330 329 328 327 326 325 
+0

Nó rất hữu ích, cảm ơn bạn. Tôi tự hỏi nếu nó có thể tìm thấy một giải pháp mà hoạt động nhanh trên bất kỳ tỷ lệ phần trăm của điền - thậm chí 95 đến 100, và không sử dụng tua lại. Giống như phân tích trên bay chiều dài của phần mô hình còn lại, so sánh nó với trường có sẵn và chọn ngẫu nhiên chỉ từ các động tác để lại 100% khả năng để tránh các kết thúc chết và hoàn thành nhiệm vụ. – Aleksei

Các vấn đề liên quan