Chapter 91

マップ生成

miku
miku
2021.11.19に更新

この章では2つのマップ生成のアルゴリズムを紹介する。

マップ生成

二次元配列を用意して中にはマップデータとなる壁と床のどちらかの値が入る。最初はランダムな値を入れるのだがこれだとマップとして成り立たないので、周りに壁が多いなら床を壁に変えるなどの処理を入れてマップとして成り立つぐらいの形に持っていく。

マップ生成 その1

  1. 二次元配列を2つ用意する。(計算用、表示用)
  2. 計算用の中身を一定の確率で壁または通路の値になるように埋めていく。
  3. 表示用の中身をすべて壁の値で埋めておく。
  4. 計算用のセル一つ一つを走査する。(x=2 ~幅-2, y=2 ~高さ-2)
  5. 自身のセルの周り 3x3, 5x5 にある壁の数を数える。(自身のセルも含める)
  6. 3x3 の壁の数が5以上、もしくは5x5の壁の数が2以下なら表示用のセルを壁に、そうでなければ通路に変える。
  7. 表示用の中身をすべて計算用にコピーする。
  8. 上記 4~7 を複数回繰り返す。
  9. 表示用をもとに画面の描画を行う。
const CellType = {
  Wall: "wall",
  Floor: "floor",
};

const tileWidth = 128;
const tileHeight = 64;
const tileSize = 5;
const iterations = 5;
const fillProbability = 0.4;
let map;
let temp;

function setup() {
  createCanvas(windowWidth, windowHeight);

  initMap();
  for (let i = 0; i < iterations; i++) {
    generateMap();
  }

  clear();
  strokeWeight(1);
  fill("#7eb5cc");
  for (let y = 0; y < tileHeight; y++) {
    for (let x = 0; x < tileWidth; x++) {
      if (map[y][x] === CellType.Floor) {
        rect(x * tileSize, y * tileSize, tileSize, tileSize);
      }
    }
  }
}

function initMap() {
  temp = [];
  map = [];

  for (let y = 0; y < tileHeight; y++) {
    temp[y] = [];
    for (let x = 0; x < tileWidth; x++) {
      temp[y][x] = random() < fillProbability ? CellType.Wall : CellType.Floor;
    }
  }

  for (let y = 0; y < tileHeight; y++) {
    map[y] = [];
    for (let x = 0; x < tileWidth; x++) {
      map[y][x] = CellType.Wall;
    }
  }
}

function onBoard(x, y) {
  return 0 <= x && x < tileWidth && 0 <= y && y < tileHeight;
}

function generateMap() {
  for (let y = 2; y < tileHeight - 2; y++) {
    for (let x = 2; x < tileWidth - 2; x++) {
      let count33 = 0;
      let count55 = 0;

      for (let ty = y - 1; ty <= y + 1; ty++) {
        for (let tx = x - 1; tx <= x + 1; tx++) {
          if (onBoard(tx, ty) && temp[ty][tx] === CellType.Wall) {
            count33++;
          }
        }
      }

      for (let ty = y - 2; ty <= y + 2; ty++) {
        for (let tx = x - 2; tx <= x + 2; tx++) {
          if (onBoard(tx, ty) && temp[ty][tx] === CellType.Wall) {
            count55++;
          }
        }
      }

      if (5 <= count33 || count55 <= 2) {
        map[y][x] = CellType.Wall;
      } else {
        map[y][x] = CellType.Floor;
      }
    }
  }

  for (let y = 0; y < tileHeight; y++) {
    for (let x = 0; x < tileWidth; x++) {
      temp[y][x] = map[y][x];
    }
  }
}

マップ生成 その2

  1. 二次元配列を用意する。
  2. 配列の中身を一定の確率で島または海の値になるように埋めていく。
  3. ランダムな座標を取得する。
  4. 対象の座標にある、セルの周り 3x3 の海の数を数える。(自身のセルも含める)
  5. 3x3 の海の数が5以上なら、対象の座標のセルを海に、そうでなければ島に変える。
  6. 上記 3~5 を複数回繰り返す。
  7. 配列の値をもとに描画を行う。
const CellType = {
  Sea: "sea",
  Land: "land",
};

const tileWidth = 128;
const tileHeight = 64;
const iterations = 40000;
const tileSize = 5;
const fillProbability = 0.4;
let map;

function setup() {
  createCanvas(windowWidth, windowHeight);

  initMap();

  for (let i = 0; i < iterations; i++) {
    generateMap();
  }

  strokeWeight(1);
  for (let y = 0; y < tileHeight; y++) {
    for (let x = 0; x < tileWidth; x++) {
      if (map[y][x] === CellType.Sea) {
        fill("#4977bc");
        rect(x * tileSize, y * tileSize, tileSize, tileSize);
      }
    }
  }
}

function initMap() {
  map = [];

  for (let y = 0; y < tileHeight; y++) {
    map[y] = [];
    for (let x = 0; x < tileWidth; x++) {
      map[y][x] = random(1.0) < fillProbability ? CellType.Land : CellType.Sea;
    }
  }
}

function generateMap() {
  const tx = floor(random(tileWidth));
  const ty = floor(random(tileHeight));

  map[ty][tx] = 5 <= getNeighborSeaCount(tx, ty) ? CellType.Sea : CellType.Land;
}

function getNeighborSeaCount(x, y) {
  let count = 0;
  for (let ty = y - 1; ty <= y + 1; ty++) {
    for (let tx = x - 1; tx <= x + 1; tx++) {
      if (isSea(tx, ty)) {
        count++;
      }
    }
  }
  return count;
}

function onBoard(x, y) {
  return 0 <= x && x < tileWidth && 0 <= y && y < tileHeight;
}

function isSea(x, y) {
  return onBoard(x, y) && map[y][x] === CellType.Sea;
}