Có thuật toán nào để phát hiện ra đại lục đất liền trên bản đồ 2D không?


28

Trên bản đồ này, "đại lục" là tất cả các vùng đất có thể được kết nối với trung tâm của bản đồ theo bốn hướng chính (bắc, nam, đông, tây - không theo đường chéo). nhập mô tả hình ảnh ở đây

Tôi muốn phát hiện đất liền và lấp đầy các lỗ hổng trong đó. Tôi nghĩ về ba điều:

  1. Tìm kiếm trong mọi ô không phải là nước (ô tối) nếu có thể được kết nối với trung tâm bản đồ bằng thuật toán tìm đường. Quá đắt! Nhưng điều này có thể làm việc cho các đảo.

  2. Đại lục chứa đầy một thùng sơn màu xanh lá cây. Mỗi lỗ được bao quanh bởi sơn ... bây giờ thì sao? Nếu tôi kiểm tra mọi điểm nước bên trong đất liền để tìm sự phụ thuộc, tôi sẽ xóa một số bán đảo và các đặc điểm địa lý khác được hiển thị trên bờ.

  3. Một số loại phát hiện cạnh để tìm ra đất liền. Giữ bất cứ thứ gì bên trong, đổ đầy nếu đó là nước, loại bỏ những gì bên ngoài. Phức tạp?

Có lẽ một số nhà phát triển có kinh nghiệm trò chơi có thể giúp tôi với điều này, có thể cho tôi tên của một số thuật toán hoặc kỹ thuật đã biết?


4
Tôi chỉ tự hỏi nếu bạn sử dụng một số loại thuật toán để tạo bản đồ này. Và nếu vậy, bạn đã sử dụng những gì?
jgallant

Đáng xem xét khi làm việc trong môi trường tilebided là Thuật toán Marching Squares . Với nó, bạn có thể phát hiện và giữ các đảo nhỏ hơn, sau đó sắp xếp theo kích thước loại bỏ các đảo đơn hoặc bất kỳ tiêu chí nào bạn có thể có.
William Mariager

@Jon vâng! thuật toán hình vuông kim cương để tạo ra một bản đồ chiều cao, sau đó tất cả dưới một giá trị là nước, phần còn lại, đất. Tôi dự định sử dụng nó như hình dạng lục địa, sau đó vượt qua để thêm chi tiết địa hình.
Gabriel A. Zorrilla

Thuật toán @Mind Marching Squares sẽ có ích để vẽ đường viền của lục địa. Cảm ơn đề nghị tốt đẹp!
Gabriel A. Zorrilla

Câu trả lời:


32

Đảo bỏ

Tôi đã từng làm điều này trước đây trong một trong những trò chơi của mình. Để thoát khỏi các hòn đảo bên ngoài, quá trình cơ bản là:

  1. Đầu tiên phải có một sự đảm bảo rằng trung tâm của bản đồ sẽ luôn thuộc về vùng đất chính và mỗi pixel bắt đầu là "Vùng đất" hoặc "Nước" (tức là các màu khác nhau).
  2. Sau đó, thực hiện lấp lũ bốn hướng bắt đầu từ trung tâm bản đồ và trải khắp mọi ô "Đất". Đánh dấu mỗi pixel được truy cập bởi vùng lũ này là một loại khác nhau, chẳng hạn như "MainLand".
  3. Cuối cùng đi qua toàn bộ bản đồ và chuyển đổi bất kỳ pixel "Đất" nào còn lại thành "Nước để thoát khỏi các đảo khác.

Loại bỏ hồ

Đối với việc loại bỏ các lỗ (hoặc hồ) bên trong hòn đảo, bạn thực hiện một quy trình tương tự nhưng bắt đầu từ các góc của bản đồ và lan truyền qua các ô "Nước". Điều này sẽ cho phép bạn phân biệt "Biển" với các khối nước khác, và sau đó bạn có thể thoát khỏi chúng giống như bạn đã thoát khỏi các đảo trước đây.

Thí dụ

Hãy để tôi khai thác việc thực hiện lấp lũ mà tôi có ở đây ở đâu đó (từ chối trách nhiệm, tôi không quan tâm đến hiệu quả, vì vậy tôi chắc chắn có nhiều cách hiệu quả hơn để thực hiện nó):

private void GenerateSea()
{
    // Initialize visited tiles list
    visited.Clear();

    // Start generating sea from the four corners
    GenerateSeaRecursive(new Point(0, 0));
    GenerateSeaRecursive(new Point(size.Width - 1, 0));
    GenerateSeaRecursive(new Point(0, size.Height - 1));
    GenerateSeaRecursive(new Point(size.Width - 1, size.Height - 1));
}

private void GenerateSeaRecursive(Point point)
{
    // End recursion if point is outside bounds
    if (!WithinBounds(point)) return;

    // End recursion if the current spot is a land
    if (tiles[point.X, point.Y].Land) return;

    // End recursion if this spot has already been visited
    if (visited.Contains(point)) return;

    // Add point to visited points list
    visited.Add(point);

    // Calculate neighboring tiles coordinates
    Point right = new Point(point.X + 1, point.Y);
    Point left = new Point(point.X - 1, point.Y);
    Point up = new Point(point.X, point.Y - 1);
    Point down = new Point(point.X, point.Y + 1);

    // Mark neighbouring tiles as Sea if they're not Land
    if (WithinBounds(right) && tiles[right.X, right.Y].Empty)
        tiles[right.X, right.Y].Sea = true;
    if (WithinBounds(left) && tiles[left.X, left.Y].Empty)
        tiles[left.X, left.Y].Sea = true;
    if (WithinBounds(up) && tiles[up.X, up.Y].Empty)
        tiles[up.X, up.Y].Sea = true;
    if (WithinBounds(down) && tiles[down.X, down.Y].Empty)
        tiles[down.X, down.Y].Sea = true;

    // Call the function recursively for the neighboring tiles
    GenerateSeaRecursive(right);
    GenerateSeaRecursive(left);
    GenerateSeaRecursive(up);
    GenerateSeaRecursive(down);
}

Tôi đã sử dụng điều này như một bước đầu tiên để thoát khỏi hồ trong trò chơi của tôi. Sau khi gọi điều đó, tất cả những gì tôi phải làm là một cái gì đó như:

private void RemoveLakes()
{
    // Now that sea is generated, any empty tile should be removed
    for (int j = 0; j != size.Height; j++)
        for (int i = 0; i != size.Width; i++)
            if (tiles[i, j].Empty) tiles[i, j].Land = true;
}

Chỉnh sửa

Thêm một số thông tin bổ sung dựa trên các ý kiến. Trong trường hợp không gian tìm kiếm của bạn quá lớn, bạn có thể gặp phải tình trạng tràn ngăn xếp khi sử dụng phiên bản đệ quy của thuật toán. Đây là một liên kết trên stackoverflow (ý định chơi chữ :-)) với phiên bản thuật toán không đệ quy, sử dụng Stack<T>thay thế (cũng trong C # để khớp với câu trả lời của tôi, nhưng nên dễ dàng thích nghi với các ngôn ngữ khác và có các triển khai khác trên đó liên kết quá).


11
Nếu bạn không thể đảm bảo rằng ô trung tâm sẽ luôn là đất và một phần của đất liền, hãy sử dụng công cụ lấp lũ để đánh dấu từng ô đất là thuộc về một hòn đảo cụ thể (lấp đầy từ mỗi ô không có id blob trên bản đồ, đánh dấu các ô được lấp đầy bằng cùng một blobid nếu nó chưa có). Sau đó loại bỏ tất cả trừ các blob với nhiều gạch nhất.
George Duckett

Phù hợp với những gì GeorgeDuckett đã nói - bạn có thể thử các thuật toán phát hiện blob của Google: đó là một vấn đề phổ biến trong đa chạm khi sử dụng FTIR. Tôi nhớ rằng tôi đã đưa ra một thuật toán thông minh hơn, nhưng tôi không thể nhớ được cuộc sống của tôi như thế nào.
Jonathan Dickinson

Khi tôi gặp vấn đề về stack trong PHP, tôi đã triển khai chương trình lấp đầy LIFO, đã hoạt động rất tuyệt vời.
Gabriel A. Zorrilla

Không phải nó chỉ là vùng lấp đầy khi thuật toán DFS được gọi từ thuật toán BFS sao? Hãy giải thích cho ai đó.
jcora

@Bane Ý bạn là gì? Sự khác biệt giữa DFS hoặc BFS chỉ là thứ tự các nút được truy cập. Tôi không nghĩ rằng thuật toán lấp đầy chỉ định thứ tự truyền tải - nó không quan tâm miễn là nó lấp đầy toàn bộ khu vực mà không cần xem lại các nút. Thứ tự phụ thuộc vào việc thực hiện. Xem dưới cùng của mục wikipedia để xem hình ảnh so sánh thứ tự truyền tải khi sử dụng hàng đợi so với sử dụng ngăn xếp. Việc thực hiện đệ quy cũng có thể được coi là một ngăn xếp (vì nó sử dụng ngăn xếp cuộc gọi).
David Gouveia


5

Đây là một hoạt động tiêu chuẩn trong xử lý hình ảnh. Bạn sử dụng một hoạt động hai pha.

Bắt đầu bằng cách tạo một bản sao của bản đồ. Từ bản đồ này, biến thành các pixel biển tất cả các pixel đất liền giáp biển. Nếu bạn làm điều này một lần, nó sẽ loại bỏ các đảo 2x2 và thu nhỏ các đảo lớn hơn. Nếu bạn làm điều đó hai lần, nó sẽ loại bỏ các đảo 4x4, vân vân.

Trong giai đoạn hai, bạn thực hiện gần như ngược lại: biến thành các pixel đất liền tất cả các pixel biển giáp đất liền, nhưng chỉ khi chúng là các pixel đất liền trong bản đồ gốc (Đó là lý do tại sao bạn tạo một bản sao trong giai đoạn 1). Điều này khiến đảo trở lại dạng ban đầu, trừ khi chúng bị loại bỏ hoàn toàn trong giai đoạn 1.


1

Tôi đã có một vấn đề tương tự nhưng không phát triển trò chơi. Tôi đã phải tìm các pixel trong một hình ảnh liền kề nhau và có cùng giá trị (các vùng được kết nối). Tôi đã thử sử dụng một vùng lũ đệ quy nhưng vẫn gây ra tình trạng tràn ngăn xếp (Tôi là một lập trình viên mới làm quen: P). Sau đó, tôi đã thử phương pháp này http://en.wikipedia.org/wiki/Connected-component_labeling nó thực sự hiệu quả hơn nhiều, cho vấn đề của tôi dù sao đi nữa.


Bạn nên thử phiên bản stack của algo lũ và lưu các vấn đề stack. Kết quả là đơn giản hơn nhiều so với CCL (mà tôi đã làm việc trong 2 tuần qua mà không gặp may mắn.)
Gabriel A. Zorrilla

Vâng tôi đã thử cả hai, nhưng tôi đã làm cho CCL hoạt động trước: P và nghĩ rằng đó là một ý tưởng gọn gàng. Vui mừng bạn đã giải quyết vấn đề của mình :)
Elegant_Cow
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.