현실적인지도 생성


25

나는 MS Paint에서 몇 분 안에 상상의 단어 영역 의이 맵을 그렸습니다.

내지도

프로그래밍 방식으로 이와 같은 맵을 생성 할 수 있으면 정말 멋질 것 같습니다.

도전

양의 정수와을 포함 W하고 H비어 있지 않은 양의 정수 세트 를 취하는 프로그램을 작성하십시오 S.

너비 x 높이 픽셀 인 표준 트루 컬러 이미지를 생성합니다 .WH

의 각 정수 i에 대해 주변 영역과 다른 색상을 사용하여 S픽셀 단위의 면적이에 비례하는 이미지의 평면 영역을 그립니다 i. 특히 이미지의 모든 픽셀 이 영역에 속하도록 영역의 픽셀 수 W * H * i / sum(S)를 반올림 또는 내림 해야합니다 .

평면 영역은 영역 내에 머물면서 직교 (대각선이 아닌) 만 움직여서 영역의 모든 픽셀에 도달 할 수있는 속성을 가진 픽셀 집합입니다 . 위의지도에는 10 개의 평면 영역이 있습니다.

평면 영역의 모든 픽셀은 동일한 색상이어야하며 인접한 영역의 색상과 달라야합니다. 지역이 이웃이 아닌 경우 동일한 색상이 될 수 있습니다.

그렇지 않으면 영역의 모양, 위치 또는 색상을 지정하는 방법에 제한이 없습니다. 이것은 인기 콘테스트입니다. 목표는 모든 규모의 지형과 물리적 또는 정치적으로 가상 세계를 사실적으로 만드는 프로그램을 만드는 것입니다.

당연히 코드뿐만 아니라 최상의 출력 이미지를 과시하십시오.

세부

  • 파일, 명령 행, stdin 또는 유사한 항목에서 입력을 가져옵니다. 이미지를 표준 형식으로 저장하거나 화면에 표시하십시오.
  • 동일한 입력에 대해 프로그램이 결정적이어야합니다. 즉, 출력 영상은 항상 어떤 특정한 동일해야한다 H, W그리고 S. ( 목록이 아닌 S집합 이므로 순서는 중요하지 않습니다.) 그렇지 않으면 원하는대로 무작위를 사용할 수 있습니다 (그러나 강력하게 제안합니다).
  • 출력 이미지 지리학은 W또는 다른 값으로 "확장"할 필요가 없습니다 H. 완전히 다를 수 있습니다.
  • 최소 32 개의 임의의 색상 가능성이있는 한 두 색상의 이웃이 같은 색상을 갖지 않을 수 있으므로 이웃 색상 규칙을 무시하고 색상을 임의로 할당 할 수 있습니다.
  • 영역이 이미지 경계에서 멈 춥니 다. 랩핑 이 없습니다 .
  • 영역은 픽셀보다 많은 영역이있는 경우와 같이 0 개의 픽셀을 포함 할 수 있으므로 존재하지 않을 수 있습니다.

입력 예

유효한 제출물이 매개 변수를 사용하여 위의지도를 생성했을 수 있습니다.

W = 380
H = 260
S = {233, 420, 1300, 3511, 4772, 5089, 9507, 22107, 25117, 26744}

S값은 각 지역의 픽셀 수와 정확히 동일하지만 반드시 그럴 필요는 없습니다. 그 기억 S이 반드시 항상 분류되지 않도록, 집합입니다.

답변:


15

나는 다른 사람들에게 동의합니다. 이것은 놀랍게도 어려운 도전이었습니다. 부분적으로 동일한 지역 유형의 인접 픽셀을 연결해야한다는 요구 사항으로 인해 해당 지역을 국가의지도처럼 보이게하는 미학적 문제도 있습니다.

여기 내 시도가 있습니다 ... 그것은 끔찍하게 비효율적이지만 합리적인 결과를 산출하는 것 같습니다. 비교 목적으로 공통 입력을 사용하는 추세를 계속하십시오.

매개 변수 : 380260233420 1300 3511 4772 5089 9507 22107 25117 26744

여기에 이미지 설명을 입력하십시오

매개 변수 : 380260 5678 9 5678 9 5678 9 5

여기에 이미지 설명을 입력하십시오

카멜롯의 암흑기 213307111 1

여기에 이미지 설명을 입력하십시오

내 더 큰 예 : (640 480 6 1 7 2 9 3 5 5 1 9 8 7 44 3 9 9 5 5 7 7 3 3 3 9 3 5 5 8 8 5 5 1 1 1 1 1 1 2 6 7 8 9 63 3)

여기에 이미지 설명을 입력하십시오

더 많은 국가의 예 : 640480 6 1 7 2 9 3 4 5 6 1 9 8 7 44 3 1 9 4 5 6 7 2 3 4 9 3 4 5 9 8 7 5 6 1 2 1 2 6 7 8 9 63 5 33 11 8827 9 5 6 2 5 7

package GenerateRealisticMaps;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.imageio.ImageIO;

public class GenerateRealisticMaps
{
    private static final Random rand = new Random(3);
    private static final Color[] paletteizedColours = new Color[100];

    // create colour palette
    static
    {
        paletteizedColours[0] = new Color(0xFF000000);
        for (int i = 1; i < paletteizedColours.length; i++)
        {
            paletteizedColours[i] = Color.getHSBColor(rand.nextFloat(), rand.nextFloat(), 0.5f + rand.nextFloat() * 0.4f);
        }
    }

    /**
     * Represents a pixel that is the boundary of a region
     * @author default
     *
     */
    public static class BoundaryPixel
    {
        public BoundaryPixel(int x, int y, int otherRegionId)
        {
            super();
            this.x = x;
            this.y = y;
            this.otherRegionId = otherRegionId;
        }

        int x;
        int y;
        int otherRegionId;
    }

    /**
     * Group of adjacent pixels that represent a region (i.e. a country in the map)
     * @author default
     *
     */
    public static class Region
    {
        static private int masterId = 0;

        Region(int desiredSize)
        {
            this.desiredSize = desiredSize;
            id = ++masterId;
        }

        int desiredSize;
        int size = 0;
        int id;
        List<BoundaryPixel> boundary = new ArrayList<GenerateRealisticMaps.BoundaryPixel>();

    }

    /**
     * Container of regions
     * @author default
     *
     */
    public static class Regions
    {
        List<Region> regionList = new ArrayList<GenerateRealisticMaps.Region>();
        Map<Integer, Region> regionMap = new HashMap<Integer, GenerateRealisticMaps.Region>();
    }

    public static void main(String[] args) throws IOException
    {
        int width = Integer.parseInt(args[0]);
        int height = Integer.parseInt(args[1]);
        int[] s = new int[args.length - 2];

        // read in the region weights
        int sum = 0;
        for (int i = 0; i < args.length - 2; i++)
        {
            sum += s[i] = Integer.parseInt(args[i + 2]);
        }

        int totalPixels = width * height;

        double multiplier = ((double) totalPixels) / sum;

        // convert region weights to pixel counts
        int runningCount = 0;
        for (int i = 0; i < s.length - 1; i++)
        {
            runningCount += s[i] = (int) (multiplier * s[i]);
        }
        s[s.length - 1] = totalPixels - runningCount;

        Regions regions = new Regions();
        int[][] map = new int[width][height];

        // initialise region starting pixels
        for (int v : s)
        {
            Region region = new Region(v);
            regions.regionList.add(region);
            regions.regionMap.put(region.id, region);

            int x;
            int y;
            do
            {
                x = rand.nextInt(width);
                y = rand.nextInt(height);
            } while (map[x][y] != 0);

            map[x][y] = region.id;
            region.size++;

        }

        // initialise a "height" map that provides cost to claim a unclaimed region. This allows for more natural shaped countries
        int[][] heightMap = new int[width][height];
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                heightMap[i][j] = rand.nextInt(50);
            }
        }

        boolean equal = false;

        // main loop
        do
        {
            growRegions(map, heightMap, width, height, regions);

            // determine whether regions have reached their desired size
            equal = true;
            for (Region region : regions.regionList)
            {
                equal = equal && region.size == region.desiredSize;
            }

            if (equal)
            {
                HashMap<Integer, Set<Integer>> commonIsolatedRegions = new HashMap<Integer, Set<Integer>>();
                int isolatedRegionId = 0;
                int[][] isolatedRegions = new int[width][height];
                List<Integer> isolatedRegionSize = new ArrayList<Integer>();
                isolatedRegionSize.add(-1); // add dummy entry at index 0 since region ids start at 1

                // go though each pixel and attempt to identify an isolated region from that point if it as not
                // yet been identified... i.e. an enclosed area.
                for (int i = 0; i < width; i++)
                {
                    for (int j = 0; j < height; j++)
                    {
                        if (isolatedRegions[i][j] == 0)
                        {
                            isolatedRegionId++;

                            Point point = new Point(i, j);
                            int size = identifyEnclosedArea(map, isolatedRegions, width, height, point, isolatedRegionId);

                            // add this isolated region id to the group of isolated regions associated with the region at this pixel
                            Set<Integer> isolatedRegionSet = commonIsolatedRegions.get(map[i][j]);
                            if (isolatedRegionSet == null)
                            {
                                isolatedRegionSet = new HashSet<Integer>();
                                commonIsolatedRegions.put(map[i][j], isolatedRegionSet);
                            }
                            isolatedRegionSet.add(isolatedRegionId);
                            isolatedRegionSize.add(size);
                        }
                    }
                }

                // only keep the largest isolated region in each group. Mark the other members in the group areas as unclaimed.
                for (Region region : regions.regionList)
                {
                    Set<Integer> isolatedRegionSet = commonIsolatedRegions.get(region.id);

                    // find the largest isolatedRegion mapped to this region
                    int largestIsolatedRegionId = -1;
                    int largestIsolatedRegionSize = -1;
                    for (Integer isolatedRegionIdentifier : isolatedRegionSet)
                    {
                        if (isolatedRegionSize.get(isolatedRegionIdentifier) > largestIsolatedRegionSize)
                        {
                            largestIsolatedRegionSize = isolatedRegionSize.get(isolatedRegionIdentifier);
                            largestIsolatedRegionId = isolatedRegionIdentifier;
                        }
                    }
                    // remove the largest isolated region (i.e. retain those pixels)
                    isolatedRegionSet.remove(largestIsolatedRegionId);

                    if (isolatedRegionSet.size() > 0)
                    {
                        equal = false;

                        // for all remaining isolated regions mapped to this region, convert to unclaimed areas.
                        for (Integer isolatedRegionIdentifier : isolatedRegionSet)
                        {
                            for (int i = 0; i < width; i++)
                            {
                                for (int j = 0; j < height; j++)
                                {
                                    if (isolatedRegions[i][j] == isolatedRegionIdentifier)
                                        map[i][j] = 0;
                                }
                            }
                        }
                    }
                }
            }

        } while (!equal);

        saveOutputImage("out.final.png", map);
    }

    /**
     * Renders and saves the output image
     * 
     * @param filename
     * @param map
     * @throws IOException
     */
    public static void saveOutputImage(String filename, int[][] map) throws IOException
    {

        final int scale = 1;
        final int width = map.length;
        final int height = map[0].length;
        BufferedImage image = new BufferedImage(width * scale, height * scale, BufferedImage.TYPE_INT_RGB);

        Graphics2D g = (Graphics2D) image.getGraphics();

        for (int j = 0; j < height; j++)
        {
            for (int i = 0; i < width; i++)
            {
                g.setColor(paletteizedColours[map[i][j]]);
                g.fillRect(i * scale, j * scale, scale, scale);
            }
        }

        ImageIO.write(image, "png", new File(filename));
    }

    /**
     * Grows the regions of the world. Firstly by unclaimed cells and then by distributing cells amongst the regions.
     * 
     * @param map
     *            cell to region map
     * @param heightMap
     *            the "height" cost of unclaimed cells. Used to give more natural shapes.
     * @param width
     * @param height
     * @param regions
     */
    public static void growRegions(int[][] map, int[][] heightMap, int width, int height, Regions regions)
    {
        // reset region sizes
        for (Region region : regions.regionList)
        {
            region.size = 0;
            region.boundary.clear();
        }

        // populate corners with adjacent pixel region id... these pixels cannot ever be "grown" into.
        map[0][0] = map[1][0];
        map[width - 1][0] = map[width - 1][5];
        map[width - 1][height - 1] = map[width - 2][height - 1];
        map[0][height - 1] = map[1][height - 1];

        int i, x, y, dx = 0, dy = 0, currHeight, currentId = -1, pixelRegionId;
        Region currRegion = null;
        ;

        // calculate initial region sizes
        for (y = 0; y < height; y++)
        {
            for (x = 0; x < width; x++)
            {
                if (map[x][y] > 0)
                    regions.regionMap.get(map[x][y]).size++;
            }
        }

        // expand regions into surrounding unclaimed pixels.
        // construct a list of region boundary pixels in the process.
        for (y = 1; y < height - 1; y++)
        {
            for (x = 1; x < width - 1; x++)
            {
                int cellId = map[x][y];
                if (cellId > 0)
                {
                    if (cellId != currentId)
                    {

                        currRegion = regions.regionMap.get(map[x][y]);
                        currentId = currRegion.id;
                    }

                    currHeight = heightMap[x][y]++;

                    for (i = 0; i < 4; i++)
                    {
                        switch (i)
                        {
                        case 0:
                            dx = x - 1;
                            dy = y;
                            break;
                        case 1:
                            dx = x + 1;
                            dy = y;
                            break;
                        case 2:
                            dx = x;
                            dy = y - 1;
                            break;
                        case 3:
                            dx = x;
                            dy = y + 1;
                            break;
                        }
                        pixelRegionId = map[dx][dy];
                        switch (pixelRegionId)
                        {
                        // unclaimed cell...
                        case 0:
                            if (heightMap[dx][dy] < currHeight)
                            {
                                map[dx][dy] = currRegion.id;
                                currRegion.size++;
                            }
                            break;
                        // claimed cell...
                        default:
                            if (pixelRegionId != currRegion.id)
                            {
                                currRegion.boundary.add(new BoundaryPixel(dx, dy, pixelRegionId));
                            }
                            break;
                        }
                    }
                }
            }
        }

        HashMap<Integer, List<BoundaryPixel>> neighbourBorders = new HashMap<Integer, List<BoundaryPixel>>();

        // for all regions...
        for (Region region : regions.regionList)
        {
            // that are less than the desired size...
            if (region.size < region.desiredSize)
            {
                neighbourBorders.clear();

                // identify the boundary segment per neighbour of the region
                for (BoundaryPixel boundaryPixel : region.boundary)
                {
                    List<BoundaryPixel> neighbourBorderSegment = neighbourBorders.get(boundaryPixel.otherRegionId);
                    if (neighbourBorderSegment == null)
                    {
                        neighbourBorderSegment = new ArrayList<GenerateRealisticMaps.BoundaryPixel>();
                        neighbourBorders.put(boundaryPixel.otherRegionId, neighbourBorderSegment);
                    }
                    neighbourBorderSegment.add(boundaryPixel);
                }

                out:
                // for each neighbour...
                for (int id : neighbourBorders.keySet())
                {
                    Region neighbourRegion = regions.regionMap.get(id);
                    int surplusPixelCount = neighbourRegion.size - neighbourRegion.desiredSize;
                    // that has surplus pixels...
                    if (surplusPixelCount > 0)
                    {
                        // and convert the border segment pixels to the current region...
                        List<BoundaryPixel> neighbourBorderSegment = neighbourBorders.get(id);
                        int index = 0;
                        while (surplusPixelCount-- > 0 && index < neighbourBorderSegment.size())
                        {
                            BoundaryPixel boundaryPixel = neighbourBorderSegment.get(index++);
                            map[boundaryPixel.x][boundaryPixel.y] = region.id;
                            region.size++;
                            regions.regionMap.get(boundaryPixel.otherRegionId).size--;
                            // until we reach the desired size...
                            if (region.size == region.desiredSize)
                                break out;
                        }
                    }
                }
            }

            // if region contains more pixels than desired...
            else if (region.size > region.desiredSize)
            {
                // and the region has neighbours
                if (region.boundary.size() > 0)
                {
                    // choose a neighbour to off load extra pixels to
                    Region neighbour = regions.regionMap.get(region.boundary.remove(rand.nextInt(region.boundary.size())).otherRegionId);

                    ArrayList<BoundaryPixel> adjustedBoundary = new ArrayList<>();
                    // iterate over the boundary neighbour's boundary pixels...
                    for (BoundaryPixel boundaryPixel : neighbour.boundary)
                    {
                        // and then for those pixels which are of the current region, convert to the neighbour region
                        if (boundaryPixel.otherRegionId == region.id)
                        {
                            map[boundaryPixel.x][boundaryPixel.y] = neighbour.id;
                            neighbour.size++;
                            region.size--;
                            // stop when we reach the region's desired size.
                            if (region.size == region.desiredSize)
                                break;
                        }
                        else
                        {
                            adjustedBoundary.add(boundaryPixel);
                        }
                    }
                    neighbour.boundary = adjustedBoundary;
                }
            }
        }

    }

    /**
     * identifies the area, starting at the given point, in which adjacent pixels are of the same region id.
     * 
     * @param map
     * @param isolatedRegionMap
     *            cells identifying which area that the corresponding map cell belongs
     * @param width
     * @param height
     * @param point
     *            the starting point of the area to be identified
     * @param isolatedRegionId
     *            the id of the region to assign cells with
     * @return the size of the identified area
     */
    private static int identifyEnclosedArea(int[][] map, int[][] isolatedRegionMap, int width, int height, Point point, final int isolatedRegionId)
    {
        ArrayList<Point> stack = new ArrayList<Point>();
        final int EXPECTED_REGION_ID = map[point.x][point.y];
        stack.add(point);
        int size = 0;

        while (stack.size() > 0)
        {
            Point p = stack.remove(stack.size() - 1);
            int x = p.x;
            int y = p.y;
            if (y < 0 || y > height - 1 || x < 0 || x > width - 1 || isolatedRegionMap[x][y] > 0)
                continue;
            int val = map[x][y];
            if (val == EXPECTED_REGION_ID)
            {
                isolatedRegionMap[x][y] = isolatedRegionId;
                size++;
                stack.add(new Point(x + 1, y));
                stack.add(new Point(x - 1, y));
                stack.add(new Point(x, y + 1));
                stack.add(new Point(x, y - 1));
            }
        }

        return size;
    }

}

설명 (의견에서)

알고리즘은 매우 간단합니다. 먼저 임의 가중치로 맵을 초기화하고 각 국가 지역에 대해 임의의 시드 픽셀을 선택하십시오. 둘째로, 청구되지 않은 인접 픽셀을 요구함으로써 각 영역을 "성장"시킨다. 현재 픽셀의 무게가 청구되지 않은 무게를 초과 할 때 발생합니다.

영역의 각 픽셀은 성장주기마다 가중치를 증가시킵니다. 또한, 영역이 이웃을 갖는 경우 고려되는 현재 영역이 원하는 것보다 적은 픽셀을 갖는 경우, 이웃이 원하는 것보다 많은 픽셀을 갖는 경우 이웃으로부터 픽셀을 훔칠 것이다. 현재 지역이 이웃보다 더 많은 픽셀을 가지고 있다면, 이웃을 무작위로 선택한 다음 모든 잉여 픽셀을 해당 이웃에 제공합니다. 모든 영역의 크기가 올바른 경우 세 번째 단계가 수행되어 분할되어 더 이상 연속적이지 않은 영역을 식별하고 변환합니다.

영역의 가장 큰 분할 만 유지되고 다른 분할은 교정되지 않은 픽셀로 변환되고 두 번째 단계가 다시 시작됩니다. 이것은 영역의 모든 픽셀이 인접하고 모든 영역이 올바른 크기가 될 때까지 반복됩니다.


멋지다! 알고리즘이 어떻게 작동하는지 조금 설명해 주시겠습니까?
Arnaud

1
큰! 색상 만 더 "Earthy"가 될 수 있다고 생각합니다 (세 번째 는 Camelot 의 Dark Purple Age 와 비슷합니다 : P). 마지막 예제의 이미지를 잊어 버린 것 같습니다.
Calvin 's Hobbies

@ Calvin'sHobbies는 임의의 색상 선택을 좋아해야합니다 ... 컴퓨터에 자주색 : P가 있습니다. 죄송합니다. 세 번째 예를 잊어 버렸습니다 ... 생성하고 업데이트합니다.
Moogie

12

이 도전은 놀랍게도 어렵습니다. Pygame을 사용하여 Python으로 맵 생성기를 작성했습니다. 이 프로그램은 색 영역을 여유 공간으로 자르고 맵처럼 보이는 이미지를 만듭니다.

남은 영역에 충분한 공간이 없을 수 있기 때문에 알고리즘이 국가를 항상 완성하지는 못하지만 재미있는 효과가 있다고 생각하고 더 이상 시간을 소비하지 않을 것입니다. 남은 푸른 패치는 큰 호수로 간주 될 수 있으며 국가 간 얼룩덜룩 한 파란색 지형은 경계를 나타내는 강입니다 (버그가 아닌 지형지 물입니다!).

Super Chafouin과 비교하기 위해 매개 변수 예제를 사용했습니다.

매개 변수 : 380260233420 1300 3511 4772 5089 9507 22107 25117 26744

표준 테스트

매개 변수 : 380260 5678 9 5678 9 5678 9 5

실시 예 2

카멜롯의 암흑기 (213307 11 1 1)

실시 예 3

내 더 큰 예 : (640 480 6 1 7 2 9 3 5 5 1 9 8 7 44 3 9 9 5 5 7 7 3 3 3 9 3 5 5 8 8 5 5 1 1 1 1 1 1 2 6 7 8 9 63 3)

동유럽을 다루는 더 큰 예는?

이 예는 동유럽과 약간 비슷합니까?

더 많은 국가의 예 : 640480 6 1 7 2 9 3 4 5 6 1 9 8 7 44 3 1 9 4 5 6 7 2 3 4 9 3 4 5 9 8 7 5 6 1 2 1 2 6 7 8 9 63 5 33 11 8827 9 5 6 2 5 7

부드러운 색상으로 더 많은 국가

이 예제에서 색상 생성기를 colors = [(80+ri(100), 80+ri(100), 80+ri(100)) for c in counts] 보다 부드러운 (및 맵과 같은) 범위로 변경했습니다.

파이썬 코드 :

from pygame.locals import *
import pygame, sys, random

BACK = (0,0,200)
ORTH = [(-1,0), (1,0), (0,-1), (0,1)]
PI = 3.141592

random.seed(9999)
def ri(n):
    return int(random.random() * n)

args = [int(v) for v in sys.argv[1:]]
W, H = args[:2]
shares = sorted(args[2:])
ratio = float(W*H) / sum(shares)
counts = [int(s*ratio) for s in shares]
for i in range(W*H - sum(counts)):
    counts[i] += 1

colors = [(2+ri(250), 2+ri(250), 2+ri(250)) for c in counts]
countries = range(len(counts))
random.shuffle(countries)

border = ( set((x,y) for x in (0,W-1) for y in range(H)) |
            set((x,y) for x in range(W) for y in (0,H-1)) )

screen = pygame.display.set_mode((W,H))
screen.fill(BACK)
pix = screen.set_at
def look(p):
    if 0 <= p[0] < W and 0 <= p[1] < H:
        return screen.get_at(p)
    else:
        return None

clock = pygame.time.Clock()

while True:
    dt = clock.tick(300)
    pygame.display.flip()

    if countries:
        country = countries.pop()
        color = colors[country]
        if not countries:
            color = (20,20,200)  # last fill color to be water
        count = counts[country]
        frontier = set()
        plotted = 0
        loc = border.pop()
        while plotted < count:
            pix(loc, color)
            if plotted % 50 == 0:
                pygame.display.flip()
            plotted += 1
            direc = [(loc[0]+dx, loc[1]+dy) for dx,dy in ORTH]
            for dloc in direc:
                if look(dloc) == BACK:
                    frontier.add(dloc)
            border |= frontier
            if frontier:
                loc = frontier.pop()
                border.discard(loc)
            else:
                print 'Country %s cover %u of %u' % (
                    shares[country], plotted, count)
                break
        if not countries:
            fn = 'mapper%u.png' % ri(1000)
            pygame.image.save(screen, fn)

    for event in pygame.event.get():
        if event.type == QUIT: sys.exit(0)
        if not hasattr(event, 'key'): continue
        if event.key == K_ESCAPE: sys.exit(0)

규칙을 준수하는지 확실하지 않습니다 "any pixel in the region can be reached from any other by staying within the region and only moving orthogonally". 고립 된 픽셀이 보입니까?
Arnaud

이 알고리즘은 인용 한 연속성 규칙을 충족하지만 다른 영역에서는 실패합니다. 격리 된 픽셀은 배경이 보이는 국가에서 "구멍"입니다. 이 효과 및 기타로 인해 각 실행의 여러 국가에서 모든 픽셀이 생성되지는 않습니다. 일부 국가에서는 대부분 의 픽셀을 놓 칩니다. 지정된 모든 규칙을 준수하지는 않지만 흥미로운 결과라고 생각했습니다. 알고리즘은 완벽한 맵을 생성하기 위해 상당한 노력이 필요합니다.
논리 기사

기술적으로 규칙에 위배되지만 여전히 멋지다. 질문을 한 후에 이와 비슷한 것을 시도했으며 비슷한 문제가 있습니다. 생각보다 까다 롭습니다!
Calvin 's Hobbies

8

게으르고이 질문에 대한 나의 대답을 수정합시다 !

  1. 이 알고리즘은 전체 직사각형을 채우는 왼쪽 상단에서 시작하여 "스네이크 경로"를 계산합니다. 뱀은 위, 아래, 왼쪽, 오른쪽으로 만 갈 수 있습니다.

  2. 뱀의 경로가 뒤 따르고 색 비율을 고려하여 첫 번째 색, 두 번째 색 등으로 채워집니다.

  3. 이 알고리즘은 많은 직선을 생성합니다. 그것을 개선하기 위해, 그것들을 감지하고 같은 양의 픽셀을 유지하는 "파동"으로 대체합니다.

매개 변수 : 380260233420 1300 3511 4772 5089 9507 22107 25117 26744

여기에 이미지 설명을 입력하십시오

매개 변수 : 380260 5678 9 5678 9 5678 9 5

여기에 이미지 설명을 입력하십시오

카멜롯의 암흑기 (213307 11 1 1)

여기에 이미지 설명을 입력하십시오

코드:

package map;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;


public class GenMap2 {

    private enum State { NO, YES, SHIFT };
    public final static int TOP = 1, BOTTOM = 2, LEFT = 4, RIGHT = 8;
    enum Action { ADD_LINE_TOP, ADD_LINE_LEFT, DOUBLE_SIZE, CREATE};

    public static void main(String[] args) throws IOException {

        int w = Integer.parseInt(args[0]), h = Integer.parseInt(args[1]);
        List<Integer> areas = new ArrayList<Integer>();
        int total = 0;
        for (int i = 2; i < args.length; i++) {
            int area = Integer.parseInt(args[i]);
            areas.add(area);
            total += area;
        }
        Collections.sort(areas);
        Collections.reverse(areas);
        int [][] tab = build(w, h);

        BufferedImage dest = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        int [] black = {0, 0, 0};
        for (int j = 0; j < dest.getHeight(); j++) {
            for (int i = 0; i < dest.getWidth(); i++) {
                dest.getRaster().setPixel(i, j, black);
            }
        }

        int x = 0, y = -1;
        int go = BOTTOM, previous = BOTTOM;

        List<Color> colors = new ArrayList<Color>();
        Random rand = new Random(0); // prog must be deterministic
        while (colors.size() < areas.size()) {
            Color c = new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256));
            boolean ok = true;
            for (Color existing : colors) {
                if (existing.equals(c)) {
                    ok = false;
                    break;
                }
            }
            if (ok) {
                colors.add(c);
            }
        }

        int [][] map = new int[w][h];
        int cpt = 0;
        while (true) {
            if (go == BOTTOM) y++;
            if (go == TOP) y--;
            if (go == LEFT) x--;
            if (go == RIGHT) x++;

            int tmp = (int)(((long)cpt) * total / (w * h));
            int i = 0;
            for (i = 0; i < areas.size(); i++) {
                int area = areas.get(i);
                if (tmp < area) {
                    break;
                }
                tmp -= area;
            }

            map[x][y] = i;

            previous = go;

            go = -1;
            if ((tab[x][y] & TOP) != 0 && previous != BOTTOM) go = TOP;
            if ((tab[x][y] & BOTTOM) != 0 && previous != TOP) go = BOTTOM;
            if ((tab[x][y] & LEFT) != 0 && previous != RIGHT) go = LEFT;
            if ((tab[x][y] & RIGHT) != 0 && previous != LEFT) go = RIGHT;
            if (go == -1) break;
            cpt++;
        }

        String [] src0 = srcPattern(16);
        String [] repl0 = destPattern(16);
        while (findPattern(map, src0, Arrays.asList(repl0, flip(repl0)))){}
        while (findPattern(map, rotate(src0), Arrays.asList(rotate(repl0), rotate(flip(repl0))))){}
        String [] src1 = srcPattern(8);
        String [] repl1 = destPattern(8);
        while (findPattern(map, src1, Arrays.asList(repl1, flip(repl1)))){}
        while (findPattern(map, rotate(src1), Arrays.asList(rotate(repl1), rotate(flip(repl1))))){}
        String [] src2 = srcPattern(4);
        String [] repl2 = destPattern(4);
        while (findPattern(map, src2, Arrays.asList(repl2, flip(repl2)))){}
        while (findPattern(map, rotate(src2), Arrays.asList(rotate(repl2), rotate(flip(repl2))))){}


        for (y = 0; y < h; y++) {
            for (x = 0; x < w; x++) {
                Color c = colors.get(map[x][y]);
                dest.getRaster().setPixel(x, y, new int[] {c.getRed(), c.getGreen(), c.getBlue()});
            }
        }

        ImageIO.write(dest, "png", new FileOutputStream("map.png"));
    }

    private static Random randPat = new Random(0);


    private static String [] srcPattern(int size) {
        String [] ret = new String[size*2];
        for (int i = 0; i < size*2; i++) {
            ret[i] = "";
            for (int j = 0; j < size*4; j++) {
                ret[i] += i < size ? "1" : "2";
            }
        }
        return ret;
    }

    private static String [] destPattern(int size) {
        String [] ret = new String[size*2];
        for (int i = 0; i < size*2; i++) {
            ret[i] = "";
            for (int j = 0; j < size*2; j++) {
                //int target = (int)((1 + Math.sin(j * Math.PI * .5/ size) * .4) * size);
                int target = (int)((1 + (Math.cos(j * Math.PI/ size) - 1) * .2) * size);
                ret[i] += (i < target)  ? '1' : '2';
            }
        }

        for (int i = 0; i < size*2; i++) {
            for (int j = 0; j < size*2; j++) {
                ret[i] += ret[size*2 - 1 - i].charAt(size*2 - 1 - j) == '1' ? '2' : '1';
            }
        }
        return ret;
    }
    private static String [] flip(String [] pat) {
        String [] ret = new String[pat.length];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = new StringBuilder(pat[i]).reverse().toString();

        }
        return ret;
    }
    private static String [] rotate(String [] pat) {
        String [] ret = new String[pat[0].length()];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = "";
            for (int j = 0; j < pat.length; j++) {
                ret[i] += pat[j].charAt(i);
            }
        }
        return ret;
    }

    private static boolean findPattern(int [][] map, String [] src, List<String []> dest) {
        for (int y = 0; y < map[0].length - src.length; y++) {
            for (int x = 0; x < map.length - src[0].length(); x++) {
                int c1 = -1, c2 = -1;
                boolean wrong = false;
                for (int y1 = 0; y1 < src.length; y1++) {
                    for (int x1 = 0; x1 < src[0].length(); x1++) {
                        if (src[y1].charAt(x1) == '1') {
                            if (c1 == -1) {
                                c1 = map[x+x1][y+y1];
                            } else {
                                if (c1 != map[x+x1][y+y1]) {
                                    wrong = true;
                                }
                            }
                        }
                        if (src[y1].charAt(x1) == '2') {
                            if (c2 == -1) {
                                c2 = map[x+x1][y+y1];
                            } else {
                                if (c2 != map[x+x1][y+y1]) {
                                    wrong = true;
                                }
                            }
                        }
                        if (c1 != -1 && c1 == c2) wrong = true;
                        if (wrong) break;
                    }
                    if (wrong) break;
                }
                if (!wrong) {
                    System.out.println("Found match at " + x + " " + y);
                    String [] repl = dest.get(randPat.nextInt(dest.size()));
                    for (int y1 = 0; y1 < src.length; y1++) {
                        for (int x1 = 0; x1 < src[0].length(); x1++) {
                            map[x+x1][y+y1] = repl[y1].charAt(x1) == '1' ? c1 : c2;

                        }
                    }
                    return true;
                }
            }
        }           
        return false;
    }

    public static int [][] build(int width, int height) {
        List<Action> actions = new ArrayList<Action>();
        while (height>1 && width>1) {
            if (height % 2 == 1) {
                height--;
                actions.add(Action.ADD_LINE_TOP);
            }
            if (width % 2 == 1) {
                width--;                
                actions.add(Action.ADD_LINE_LEFT);
            }
            if (height%2 == 0 && width%2 == 0) {
                actions.add(Action.DOUBLE_SIZE);
                height /= 2;
                width /= 2;
            }
        }
        actions.add(Action.CREATE);
        Collections.reverse(actions);
        int [][] tab = null;
        for (Action action : actions) {
            if (action == Action.CREATE) {
                tab = new int[width][height];
                if (height >= width) {
                    for (int i = 0; i < height-1; i++) {
                        tab[0][i] = TOP|BOTTOM;
                    }
                    tab[0][height-1] = TOP;
                } else {
                    tab[0][0] = TOP|RIGHT;
                    for (int i = 1; i < width-1; i++) {
                        tab[i][0] = RIGHT|LEFT;
                    }
                    tab[width-1][0] = LEFT;

                }
            }
            if (action == Action.DOUBLE_SIZE) {
                tab = doubleTab(tab);
            }
            if (action == Action.ADD_LINE_TOP) {
                int [][] tab2 = new int[tab.length][tab[0].length+1];
                for (int i = 0; i < tab.length; i++) {
                    for (int j = 0; j < tab[0].length; j++) {
                        tab2[i][j+1] = tab[i][j];
                    }
                }
                tab2[0][0] = BOTTOM|RIGHT;
                for (int i = 1; i < tab.length-1; i++) {
                    tab2[i][0] = RIGHT|LEFT;
                }
                tab2[tab.length-1][0] = TOP|LEFT;
                mirror(tab2);
                tab = tab2;
            }
            if (action == Action.ADD_LINE_LEFT) {
                int [][] tab2 = new int[tab.length+1][tab[0].length];
                for (int i = 0; i < tab.length; i++) {
                    for (int j = 0; j < tab[0].length; j++) {
                        tab2[i+1][j] = tab[i][j];
                    }
                }
                tab2[0][0] = BOTTOM|RIGHT;
                tab2[1][0] |= LEFT;
                tab2[1][0] -= TOP;
                for (int i = 1; i < tab[0].length-1; i++) {
                    tab2[0][i] = TOP|BOTTOM;
                }
                tab2[0][tab[0].length-1] = TOP|BOTTOM;
                flip(tab2);
                tab = tab2;
            }

        }

        return tab;
    }

    private static void mirror(int [][] tab) {
        for (int i = 0; i < tab.length/2; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                int tmp = tab[tab.length - 1 - i][j];
                tab[tab.length - 1 - i][j] = tab[i][j];
                tab[i][j] = tmp;
            }
        }
        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                if ((tab[i][j] & LEFT)!=0 && (tab[i][j] & RIGHT)==0) {
                    tab[i][j] -= LEFT; tab[i][j] |= RIGHT;
                } else if ((tab[i][j] & RIGHT)!=0 && (tab[i][j] & LEFT)==0) {
                    tab[i][j] -= RIGHT; tab[i][j] |= LEFT;
                }
            }
        }
    }

    private static void flip(int [][] tab) {
        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length/2; j++) {
                int tmp = tab[i][tab[0].length - 1 - j];
                tab[i][tab[0].length - 1 - j] = tab[i][j];
                tab[i][j] = tmp;
            }
        }
        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                if ((tab[i][j] & TOP)!=0 && (tab[i][j] & BOTTOM)==0) {
                    tab[i][j] -= TOP; tab[i][j] |= BOTTOM;
                } else if ((tab[i][j] & BOTTOM)!=0 && (tab[i][j] & TOP)==0) {
                    tab[i][j] -= BOTTOM; tab[i][j] |= TOP;
                }
            }
        }
    }


    public static int [][] doubleTab(int [][] tab) {
        boolean [][] shiftTop = new boolean[tab.length][], 
                shiftLeft = new boolean[tab.length][],
                shiftBottom = new boolean[tab.length][],
                shiftRight = new boolean[tab.length][];
        for (int i = 0; i < tab.length; i++) {
            shiftTop[i] = new boolean[tab[i].length];
            shiftLeft[i] = new boolean[tab[i].length];
            shiftBottom[i] = new boolean[tab[i].length];
            shiftRight[i] = new boolean[tab[i].length];
        }

        int x = 0, y = -1;
        for (int i = 0; i < tab.length; i++) {
            if ((tab[i][0] & TOP) != 0) {
                x = i;
            }
        }
        int go = BOTTOM, previous = BOTTOM;
        boolean init = false;
        while (true) {
            if (go == BOTTOM) y++;
            if (go == TOP) y--;
            if (go == LEFT) x--;
            if (go == RIGHT) x++;

            previous = go;

            go = -1;
            if ((tab[x][y] & TOP) != 0 && previous != BOTTOM) go = TOP;
            if ((tab[x][y] & BOTTOM) != 0 && previous != TOP) go = BOTTOM;
            if ((tab[x][y] & LEFT) != 0 && previous != RIGHT) go = LEFT;
            if ((tab[x][y] & RIGHT) != 0 && previous != LEFT) go = RIGHT;
            if (previous == BOTTOM) {
                shiftTop[x][y] = y==0 ? init : shiftBottom[x][y-1];
            }
            if (previous == TOP) {
                shiftBottom[x][y] = shiftTop[x][y+1];
            }
            if (previous == RIGHT) {
                shiftLeft[x][y] = shiftRight[x-1][y];
            }
            if (previous == LEFT) {
                shiftRight[x][y] = shiftLeft[x+1][y];       
            }
            if (go == -1) break;

            if (previous == BOTTOM && go == LEFT) {
                shiftLeft[x][y] = !shiftTop[x][y];
            }
            if (previous == BOTTOM && go == RIGHT) {
                shiftRight[x][y] = shiftTop[x][y];
            }
            if (previous == BOTTOM && go == BOTTOM) {
                shiftBottom[x][y] = shiftTop[x][y];
            }


            if (previous == TOP && go == LEFT) {
                shiftLeft[x][y] = shiftBottom[x][y];
            }
            if (previous == TOP && go == RIGHT) {
                shiftRight[x][y] = !shiftBottom[x][y];
            }
            if (previous == TOP && go == TOP) {
                shiftTop[x][y] = shiftBottom[x][y];
            }

            if (previous == RIGHT && go == TOP) {
                shiftTop[x][y] = !shiftLeft[x][y];
            }
            if (previous == RIGHT && go == BOTTOM) {
                shiftBottom[x][y] = shiftLeft[x][y];
            }
            if (previous == RIGHT && go == RIGHT) {
                shiftRight[x][y] = shiftLeft[x][y];
            }

            if (previous == LEFT && go == TOP) {
                shiftTop[x][y] = shiftRight[x][y];
            }
            if (previous == LEFT && go == BOTTOM) {
                shiftBottom[x][y] = !shiftRight[x][y];
            }
            if (previous == LEFT && go == LEFT) {
                shiftLeft[x][y] = shiftRight[x][y];
            }
        }
        int [][] tab2 = new int[tab.length * 2][];
        for (int i = 0; i < tab2.length; i++) {
            tab2[i] = new int[tab[0].length * 2];
        }

        for (int i = 0; i < tab.length; i++) {
            for (int j = 0; j < tab[0].length; j++) {
                State left = State.NO, right = State.NO, top = State.NO, bottom = State.NO; 
                if ((tab[i][j] & LEFT) != 0) {
                    left = shiftLeft[i][j] ? State.SHIFT : State.YES;
                }
                if ((tab[i][j] & TOP) != 0) {
                    top = shiftTop[i][j] ? State.SHIFT : State.YES;
                }
                if ((tab[i][j] & RIGHT) != 0) {
                    right = shiftRight[i][j] ? State.SHIFT : State.YES;
                }
                if ((tab[i][j] & BOTTOM) != 0) {
                    bottom = shiftBottom[i][j] ? State.SHIFT : State.YES;
                }

                int [] comp = compute(left, top, right, bottom);
                tab2[i*2][j*2] = comp[0];
                tab2[i*2+1][j*2] = comp[1];
                tab2[i*2][j*2+1] = comp[2];
                tab2[i*2+1][j*2+1] = comp[3];
            }
        }
        return tab2;
    }

    private static int [] compute(State left, State top, State right, State bottom) {
        //   |
        // --+
        //
        if (left == State.YES && top == State.SHIFT) {
            return new int[] {LEFT|BOTTOM, TOP|BOTTOM, TOP|RIGHT, TOP|LEFT};// "v^>^";
        }
        if (left == State.SHIFT && top == State.YES) {
            return new int[] {TOP|RIGHT, LEFT|BOTTOM, LEFT|RIGHT, LEFT|TOP}; //"^<>^";
        }
        //   
        // --+
        //   |
        if (left == State.YES && bottom == State.YES) {
            return new int[] {LEFT|RIGHT, LEFT|BOTTOM, RIGHT|BOTTOM, LEFT|TOP}; //">vv<";
        }
        if (left == State.SHIFT && bottom == State.SHIFT) {
            return new int[] {RIGHT|BOTTOM, LEFT|BOTTOM, LEFT|TOP, TOP|BOTTOM}; //">v^v";
        }
        //   |
        //   +--
        //
        if (right == State.SHIFT && top == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM,LEFT|TOP,TOP|RIGHT, LEFT|RIGHT}; //" v<>>";
        }
        if (right == State.YES && top == State.YES) {
            return new int [] {TOP|BOTTOM,RIGHT|BOTTOM,TOP|RIGHT,TOP|LEFT}; //"v>>^";
        }
        //   
        //   +--
        //   |
        if (right == State.YES && bottom == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM, LEFT|RIGHT, TOP|RIGHT, LEFT|BOTTOM}; //"v<>v";
        }
        if (right == State.SHIFT && bottom == State.YES) {
            return new int [] {RIGHT|BOTTOM, LEFT|BOTTOM, TOP|BOTTOM, RIGHT|TOP}; //"v<v^";
        }
        //   
        // --+--
        //   
        if (right == State.YES && left == State.YES) {
            return new int [] {LEFT|BOTTOM, RIGHT|BOTTOM, TOP|RIGHT, LEFT|TOP}; 
        }
        if (right == State.SHIFT && left == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM, LEFT|BOTTOM, LEFT|TOP, RIGHT|TOP}; 
        }
        //   |
        //   +
        //   |
        if (top == State.YES && bottom == State.YES) {
            return new int [] {TOP|RIGHT, LEFT|BOTTOM, BOTTOM|RIGHT, LEFT|TOP}; 
        }
        if (top == State.SHIFT && bottom == State.SHIFT) {
            return new int [] {RIGHT|BOTTOM, LEFT|TOP, RIGHT|TOP, LEFT|BOTTOM}; 
        }
        //
        //   +--
        //
        if (right == State.YES && bottom == State.NO && left == State.NO && top == State.NO) {
            return new int [] {BOTTOM, RIGHT|BOTTOM, TOP|RIGHT, LEFT|TOP}; 
        }
        if (right == State.SHIFT && bottom == State.NO && left == State.NO && top == State.NO) {
            return new int [] {RIGHT|BOTTOM, LEFT|BOTTOM, TOP, RIGHT|TOP}; 
        }

        //   |
        //   +
        //
        if (top == State.YES && bottom == State.NO && left == State.NO && right == State.NO) {
            return new int [] {TOP|RIGHT, LEFT|BOTTOM, RIGHT, LEFT|TOP}; 
        }
        if (top == State.SHIFT && bottom == State.NO && left == State.NO && right == State.NO) {
            return new int [] {BOTTOM|RIGHT, LEFT|TOP, TOP|RIGHT, LEFT}; 
        }
        //   
        //   +
        //   |
        if (bottom == State.YES && top == State.NO && left == State.NO && right == State.NO) {
            return new int [] {RIGHT, LEFT|BOTTOM, BOTTOM|RIGHT, LEFT|TOP}; 
        }
        if (bottom == State.SHIFT && top == State.NO && left == State.NO && right == State.NO) {
            return new int [] {BOTTOM|RIGHT, LEFT, TOP|RIGHT, LEFT|BOTTOM}; 
        }
        //
        // --+
        //
        if (left == State.YES && bottom == State.NO && right == State.NO && top == State.NO) {
            return new int [] {LEFT|BOTTOM, BOTTOM, TOP|RIGHT, LEFT|TOP}; 
        }
        if (left == State.SHIFT && bottom == State.NO && right == State.NO && top == State.NO) {
            return new int [] {BOTTOM|RIGHT, LEFT|BOTTOM, LEFT|TOP, TOP}; 
        }
        return null;
    }
}

1
내 눈에, 이것들은 매우 현실적으로 보이지 않습니다. 대부분의 직선 때문에 ...
Beta Decay

2
@BetaDecay OP는 "어떤 규모로든"지정하므로이를 국가 또는 국가의 하위 지역으로 가정하십시오. 그런 다음 네브라스카의 군도 와 매우 유사하지만 현실적 일 수 있습니다 .
Geobits

1
@ 모두 직선을 ​​수정하기 위해 "파도"를 추가했습니다.
Arnaud

@ βετѧΛєҫαγ 그것은 현실적으로 보이지는 않지만 미국과 캐나다 사이의 경계를 보았습니다. 대부분의 직선에서 이루어지며 일부 아프리카 국가의 경계와 동일합니다.
user902383
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.