当前位置:  开发笔记 > 编程语言 > 正文

将x项等距放置在n×m包裹网格上的算法

如何解决《将x项等距放置在n×m包裹网格上的算法》经验,为你挑选了3个好方法。

我正在创建一个简单的基于网格的浏览器游戏,我希望等距离放置玩家和目标单元格(想想山丘之王).理想情况下,这将以每个玩家也与最近的目标小区等距离的方式完成.

以下是要求:

游戏需要支持2到20名玩家.

n通过m网格可以任意大小,但更多的"广场样"就更好了.('square-like'背后的原理是减少穿越网格所需的最大距离 - 让事情更容易接近)

目标细胞的数量是灵活的.

每个玩家应该有相同数量的目标.

任何玩家或目标与任何其他玩家或目标之间的最小距离为4.

请注意,每个小区具有8个直接邻居(是对角线算作的距离1),和边缘缠绕.这意味着底部的那些在逻辑上与顶部的那些相邻,并且左/右相同.

我一直试图想出一个好的算法,将玩家和目标放在不同的分布中,而不必为每个玩家数量创建一个特定的预定网格.我发现了k-means聚类和劳埃德算法,但是我对它们并不是很熟悉,并且我不知道如何将它们应用于这个特定的情况,特别是因为目标细胞的数量是灵活的,我认为应该简化解决方案.

这里有一段非常简化的代码,创建了一个预先确定的6个玩家网格,只是为了展示我的目标:

var cellSize = 20;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

function Cell(x, y) {
  this.x = x * cellSize + cellSize / 2;
  this.y = y * cellSize + cellSize / 2;
  this.id = x + '-' + y;
  this.neighbors = [];
  this.type = null;
}

Cell.prototype.draw = function() {
  var color = '#ffffff';
  if (this.type === 'base') {
    color = '#0000ff';
  } else if (this.type === 'target') {
    color = '#ff0000';
  }
  var d = cellSize / 2;
  ctx.fillStyle = color;
  ctx.fillRect(this.x - d, this.y - d, this.x + d, this.y + d);
  ctx.rect(this.x - d, this.y - d, this.x + d, this.y + d);
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 3;
  ctx.stroke();
};

// Pre-set player and target cells for 6 players as an example
var playerCells = ['0-0', '8-0', '16-0', '0-8', '8-8', '16-8'];
var targetCells = ['4-4', '12-4', '20-4', '4-12', '12-12', '20-12'];
var n = 24;
var m = 16;
canvas.width = n * cellSize + 6;
canvas.height = m * cellSize + 6;

var cellList = [];

for (var i = 0; i < n; i++) {
  for (var j = 0; j < m; j++) {
    var cell = new Cell(i, j);
    if (playerCells.indexOf(cell.id) > -1) {
      cell.type = 'base';
    } else if (targetCells.indexOf(cell.id) > -1) {
      cell.type = 'target';
    }
    cellList.push(cell);
  }
}

// Give each cell a list of it's neighbors so we know where things can move
for (var i = 0; i < cellList.length; i++) {
  var cell = cellList[i];
  var neighbors = [];

  // Get the cell indices around the current cell
  var cx = [cell.x - 1, cell.x, cell.x + 1];
  var cy = [cell.y - 1, cell.y, cell.y + 1];
  var ci, cj;

  for (ci = 0; ci < 3; ci++) {
    if (cx[ci] < 0) {
      cx[ci] = n - 1;
    }
    if (cx[ci] >= n) {
      cx[ci] = 0;
    }
    if (cy[ci] < 0) {
      cy[ci] = m - 1;
    }
    if (cy[ci] >= m) {
      cy[ci] = 0;
    }
  }
  for (ci = 0; ci < 3; ci++) {
    for (cj = 0; cj < 3; cj++) {
      // Skip the current node since we don't need to link it to itself
      if (cellList[n * ci + cj] === cell) {
        continue;
      }
      neighbors.push(cellList[n * ci + cj]);
    }
  }
}

drawGrid();

function drawGrid() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (var i = 0; i < cellList.length; i++) {
    cellList[i].draw();
  }
}

它创建一个如下所示的网格: 网格示例

蓝色细胞是球员,红色细胞是目标.

有没有人对如何解决这个问题有任何建议?

非常感谢有用材料的链接.

是否有任何专家能够制作出满足上述所有条件的精彩放置算法?

如果解决方案还允许目标小区的数量和/或最小距离可以为任何数量的玩家配置并且仍然满足所有条件,那将是惊人的,尽管这不是绝对必要的.

编辑

经过其他一些游戏设计考虑后,我将玩家和目标之间的最小距离更改为4而不是2.上面的文本,代码和图像已相应更改.在编辑时,没有解决方案受到该要求的约束,因此它不应该影响任何事情.

编辑2

如果您提出解决方案,请提供JavaScript代码(或至少伪代码),概述解决方案的详细步骤.另请说明解决方案如何满足要求.谢谢!



1> adilapapaya..:

你被限制在一架平面上吗?如果可以移动到3D,则可以使用Fibonacci Spiral在球体上生成任意数量的等距点.在http://www.openprocessing.org/sketch/41142上有一个非常好的处理草图(带有代码).下图显示了它的外观.一个好处是你自动获得"包装".

斐波那契球体

如果你必须坚持使用2D,那么你可以尝试上面的方法,接着是一个保持映射的球面到平面的投影.这可能比你想要的要复杂一点......


球体没有正确的拓扑结构,他很清楚它与圆环相当.

2> גלעד ברקן..:

一个直观的解决方案是根据玩家的数量对称地划分平面,随机放置一个玩家及其目标,然后在其他部分中对称地反映位置.理论上将网格绑定在一个圆圈中(反之亦然),然后划分和反射.

在(理论)无限分辨率网格中,以中心为极坐标系的中心,我们可以先放置一个玩家和它的目标(顺便说一下,这些可以放在网格上的任何位置,对称性仍然可以保持),然后放置其他n - 1玩家和目标/ s,360° / n每次增加初始程度,保持相同的半径.但是,由于您的网格将具有实际大小限制,因此您需要以某种方式保证反射单元格存在于网格上,可能是通过限制初始生成和/或修改网格大小/奇偶校验的组合.

有点像:

var numPlayers = 6;
var ts = 2;
var r = 8

function convertFromPolar(cs) {
  return [Math.round(cs[0] * Math.cos(cs[1] * Math.PI / 180)) + r
         ,Math.round(cs[0] * Math.sin(cs[1] * Math.PI / 180)) + r];
}

var first = [r,0];

var targets = [];
for (var i = 0; i < ts; i++) {
  var _first = first.slice();
  _first[0] = _first[0] - 4 - Math.round(Math.random() * 3);
  _first[1] = _first[1] + Math.round(Math.random() * 8);
  targets.push(_first);
}

var playerCells = [];
var targetCells = [];

for (var i = 0; i < numPlayers; i++) {
  playerCells.push(convertFromPolar(first).join('-'));
  first[1] = (first[1] + 360 / numPlayers) % 360;
  for (var j = 0; j < ts; j++) {
    targetCells.push(convertFromPolar(targets[j]).join('-'));
    targets[j][1] = (targets[j][1] + 360 / numPlayers) % 360;
  }
}

var cellSize = 20;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

function Cell(x, y) {
  this.x = x * cellSize + cellSize / 2;
  this.y = y * cellSize + cellSize / 2;
  this.id = x + '-' + y;
  this.neighbors = [];
  this.type = null;
}

Cell.prototype.draw = function() {
  var color = '#ffffff';
  if (this.type === 'base') {
    color = '#0000ff';
  } else if (this.type === 'target') {
    color = '#ff0000';
  } else if (this.type === 'outOfBounds') {
    color = '#000000';
  }
  var d = cellSize / 2;
  ctx.fillStyle = color;
  ctx.fillRect(this.x - d, this.y - d, this.x + d, this.y + d);
  ctx.rect(this.x - d, this.y - d, this.x + d, this.y + d);
  ctx.strokeStyle = '#000';
  ctx.lineWidth = 3;
  ctx.stroke();
};

var n = 24;
var m = 16;
canvas.width = n * cellSize + 6;
canvas.height = m * cellSize + 6;

var cellList = [];

for (var i = 0; i < n; i++) {
  for (var j = 0; j < m; j++) {
    var cell = new Cell(i, j);
    if (playerCells.indexOf(cell.id) > -1) {
      cell.type = 'base';
    } else if (targetCells.indexOf(cell.id) > -1) {
      cell.type = 'target';
    } else if (Math.pow(i - r,2) + Math.pow(j - r,2) > (r + 2)*(r + 2) ) {
      cell.type = 'outOfBounds';
    }
    cellList.push(cell);
  }
}

// Give each cell a list of it's neighbors so we know where things can move
for (var i = 0; i < cellList.length; i++) {
  var cell = cellList[i];
  var neighbors = [];

  // Get the cell indices around the current cell
  var cx = [cell.x - 1, cell.x, cell.x + 1];
  var cy = [cell.y - 1, cell.y, cell.y + 1];
  var ci, cj;

  for (ci = 0; ci < 3; ci++) {
    if (cx[ci] < 0) {
      cx[ci] = n - 1;
    }
    if (cx[ci] >= n) {
      cx[ci] = 0;
    }
    if (cy[ci] < 0) {
      cy[ci] = m - 1;
    }
    if (cy[ci] >= m) {
      cy[ci] = 0;
    }
  }
  for (ci = 0; ci < 3; ci++) {
    for (cj = 0; cj < 3; cj++) {
      // Skip the current node since we don't need to link it to itself
      if (cellList[n * ci + cj] === cell) {
        continue;
      }
      neighbors.push(cellList[n * ci + cj]);
    }
  }
}

drawGrid();

function drawGrid() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (var i = 0; i < cellList.length; i++) {
    cellList[i].draw();
  }
}


3> Cedric Reich..:

如前所述,在没有过高的计算费用的情况下,可能没有完美的解决方案可以满足您的所有要求.1

途径

我的方法是通过更灵活的条件替换所有目标要求的相同距离.

在下面的示例中,我为每个单元格引入了一个属性,它应该直观地表示目标的可用性/接近度.它的计算方法是将相对于地图上每个目标的热量相加.与目标相关的热量仅为1除以它们之间的距离(在我的例子中为曼哈顿).也许你会想要使用不同的函数heat和实现distance.

球员定位

对于分发玩家,我们执行以下操作:

    保留所有单元格的列表,按热量排序

    从某个单元格(当前是用户选择的)开始,在排序列表中找到它并使用其邻居(具有相似的热值)作为玩家位置

这确保了玩家单元的热值总是尽可能接近.更好的解决方案是在排序列表中搜索一系列尽可能相似的热值并使用它们.

示例代码

重新加载以具有不同的目标位置

var numPlayers = 4;
var numTargets = numPlayers;
var gridSize = numPlayers * 4;
var minDistance = 4;

var targetPositions = [];
for (var i = 0; i < numTargets; i++) {
	// TODO: Make sure targets don't get too close
  targetPositions[i] = randomPos();
}

var heatMap = [];
for (var i = 0; i < gridSize; i++) {
  heatMap[i] = [];
  for (var j = 0; j < gridSize; j++) {
    heatMap[i][j] = heat(i, j);
  }
}
printHeat();

function heat(x, y) {
  var result = 0;
  for (var i in targetPositions) {
    var pos = targetPositions[i];
    result += 1 / distance(x - pos.x, y - pos.y); // XXX: What about zero division?
  }
  return result;
}

function distance(l1, l2) {
  // manhattan distance
  return Math.abs(l1) + Math.abs(l2);
}

function randomPos() {
  return {
    x: random(gridSize),
    y: random(gridSize),
    toString: function() {
      return this.x + '/' + this.y
    }
  };

  function random(max) {
    return Math.floor(Math.random() * max);
  }
}

function printHeat() {
  for (var i = 0; i < gridSize; i++) {
    var tr = $('');
    $('#heat').append(tr);
    for (var j = 0; j < gridSize; j++) {
      var heatVal = heatMap[i][j];
      var td = $(' ' + heatVal + ' ');
      if (heatVal > numTargets) // hack
        td.addClass('target');
      td.attr('data-x', i).attr('data-y', j);
      td.css('background-color', 'rgb(' + Math.floor(heatVal * 255) + ',160,80)');
      tr.append(td);
    }
  }
}

var cellsSorted = $('td').sort(function(a, b) {
  return numOfCell(a) > numOfCell(b);
}).toArray();
$('td').click(function() {
  $('.player').removeClass('player');
  var index = cellsSorted.indexOf(this);
  // TODO: Don't just search downwards, but in both directions with lowest difference
  for (var k = 0; k < numPlayers; k++) {
    var newIndex = index - k; // XXX Check against outOfBounds
    var cell = cellsSorted[newIndex];
    if (!validPlayerCell(cell)) {
      // skip one
      k--;
      index--;
      continue;
    }
    $(cell).addClass('player');
  }
});

function validPlayerCell(cell) {
  var otherItems = $('.player, .target').toArray();
  for (var i in otherItems) {
    var item = otherItems[i];
    var xa = parseInt($(cell).attr('data-x'));
    var ya = parseInt($(cell).attr('data-y'));
    var xb = parseInt($(item).attr('data-x'));
    var yb = parseInt($(item).attr('data-y'));
    if (distance(xa - xb, ya - yb) < minDistance)
      return false;
  }
  return true;
}

function numOfCell(c) {
  return parseFloat($(c).text());
}
body {
  font-family: sans-serif;
}

h2 {
  margin: 1ex 0;
}

td {
  border: 1px solid #0af;
  padding: 0.5ex;
  font-family: monospace;
  font-size: 10px;
  max-width: 4em;
  height: 4em;
  overflow: hidden;
  text-overflow: ellipsis;
}

td.target {
  border-color: #f80;
}

td.player {
  border-color: black;
}

td.player::after {
  font-family: sans-serif;
  content: "player here";
  position: absolute;
  color: white;
  background-color: rgba(0, 0, 0, 0.5);
  font-weight: bold;
  padding: 2px;
}

Click a cell to distribute players

推荐阅读
地之南_816
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有