我正在创建一个简单的基于网格的浏览器游戏,我希望等距离放置玩家和目标单元格(想想山丘之王).理想情况下,这将以每个玩家也与最近的目标小区等距离的方式完成.
以下是要求:
游戏需要支持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代码(或至少伪代码),概述解决方案的详细步骤.另请说明解决方案如何满足要求.谢谢!
你被限制在一架平面上吗?如果可以移动到3D,则可以使用Fibonacci Spiral在球体上生成任意数量的等距点.在http://www.openprocessing.org/sketch/41142上有一个非常好的处理草图(带有代码).下图显示了它的外观.一个好处是你自动获得"包装".
如果你必须坚持使用2D,那么你可以尝试上面的方法,接着是一个保持映射的球面到平面的投影.这可能比你想要的要复杂一点......
一个直观的解决方案是根据玩家的数量对称地划分平面,随机放置一个玩家及其目标,然后在其他部分中对称地反映位置.理论上将网格绑定在一个圆圈中(反之亦然),然后划分和反射.
在(理论)无限分辨率网格中,以中心为极坐标系的中心,我们可以先放置一个玩家和它的目标(顺便说一下,这些可以放在网格上的任何位置,对称性仍然可以保持),然后放置其他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();
}
}
如前所述,在没有过高的计算费用的情况下,可能没有完美的解决方案可以满足您的所有要求.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
推荐阅读
-
如何解决《使用Dapper自动映射输出参数》经验,为你挑选了0个好方法。 ...
[详细]
-
如何解决《如何清除数组中的TextField?》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《将语言更改为BootstrapDatetimepicker》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《文件系统变为只读时的弹性搜索》经验,为你挑选了0个好方法。 ...
[详细]
-
如何解决《如何阅读appSettings键》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《如何从几个INSERT中插入id》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《从javascriptonclick返回值到变量或使用onclick更改全局变量》经验,为你挑选了0个好方法。 ...
[详细]
-
如何解决《按函数名称c++11参数化模板》经验,为你挑选了0个好方法。 ...
[详细]
-
如何解决《UWPApp中的计时器未链接到UI》经验,为你挑选了2个好方法。 ...
[详细]
-
如何解决《ViewPageraddOnPageChangeListener不在同一个tabClick上工作》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《django1.8不适用于CentOs6.5服务器》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《按某些重复索引值拆分列表》经验,为你挑选了0个好方法。 ...
[详细]
-
如何解决《BigSwift类导致Xcode中的自动完成速度慢-如何拆分成几个文件》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《方法是否重载多态在C#中的早期绑定?》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《TeamCity中的MSBuild步骤考虑app.config而不是ASP.NETWEBAPI项目的web.config,导致警告MSB3247》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《PhpRegex在字符串中的第一个全大写字母后插入字符》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《在bootstrap页面上居中表》经验,为你挑选了2个好方法。 ...
[详细]
-
如何解决《可以在go中覆盖自动格式化程序》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《Angular服务返回undefined》经验,为你挑选了1个好方法。 ...
[详细]
-
如何解决《使用别名表更新SQL仍会返回"表格不明确"错误》经验,为你挑选了1个好方法。 ...
[详细]
吐了个 "CAO" !
Tags | 热门标签
RankList | 热门文章
-
1mysqli_affected_rows总是返回0
-
2任务==懒惰?
-
3Django:使用OneToOneField的ModelForm
-
4为什么crontab在指定月份和星期几时使用OR?
-
5Viewpager不在协调器布局中滚动
-
6连接到sqlite文件时强制只读
-
7tasks参数包含null值
-
8BASH:Raplace文件名不在列表中的字符
-
9docker数据卷vs已挂载的主机目录
-
10可以在哪里更改Elastic Beanstalk环境的RDS_DB_NAME设置
-
11将ASP.NET 5项目发布到Docker时找不到名为"Custom"的AspnetPublishHandler
-
12覆盖yii2上模块的转换路径
-
13Laravel的Sublime文本自动完成功能
-
14为什么SPAN的高度超过其字体大小
-
15如何在javascript中获取服务器端cookie值?
-
16悬停时在链接前显示"▶"?
-
17C++调用默认构造函数而不是复制构造函数
-
18如何在Laravel测试中禁用选定的中间件
-
19包含NSTableView的深色充满活力的窗口 - 调整背景颜色
-
20C#WinForm多次单击事件处理程序以实现类似功能
DevBox开发工具箱 | 专业的在线开发工具网站 京公网安备 11010802040832号 | 京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有