我有一个可变大小的矩形网格,但平均值为500x500,其中包含少量的x,y点(小于5)。我需要找到一个返回x,y对的算法,该算法距离任何其他点都是最远的。
上下文:应用程序的屏幕(网格)和一组x,y点(敌人)。玩家死了,我需要一种能够在远离敌人的情况下重新生成它们的算法,这样他们就不会在重生后立即死亡。
到目前为止我所拥有的内容:我编写的算法有效,但在较慢的手机中无法表现出色。我基本上将网格划分为正方形(很像tic tac toe),并为每个正方形分配一个数字。然后我检查每个方格对抗所有敌人并存储每个方格最近的敌人。数量最多的广场是距离最近的敌人最近的广场。我也尝试对现有点进行平均并做类似的事情,虽然性能可以接受,但方法的可靠性却没有。
答案 0 :(得分:1)
这是我能想到的最简单的算法,它仍能提供良好的结果。它只检查9个可能的位置:角落,两侧中间和中心点。大多数时候玩家最终都在角落里,但你显然需要比敌人更多的位置。
该算法在我的i5桌面上运行0.013毫秒。如果用Math.abs()替换Math.pow(),则会降低到0.0088ms,但结果可靠性较差。 (奇怪的是,这比我使用三角函数的其他答案要慢。)
运行代码片段(重复)将在canvas元素中随机定位敌人显示结果。
function furthestFrom(enemy) {
var point = [{x:0,y:0},{x:250,y:0},{x:500,y:0},{x:0,y:250},{x:250,y:250},{x:500,y:250},{x:0,y:500},{x:250,y:500},{x:500,y:500}];
var dist2 = [500000,500000,500000,500000,500000,500000,500000,500000,500000];
var max = 0, furthest;
for (var i in point) {
for (var j in enemy) {
var d = Math.pow(point[i].x - enemy[j].x, 2) + Math.pow(point[i].y - enemy[j].y, 2);
if (d < dist2[i]) dist2[i] = d;
}
if (dist2[i] > max) {
max = dist2[i];
furthest = i;
}
}
return(point[furthest]);
}
// CREATE TEST DATA
var enemy = [];
for (var i = 0; i < 5; i++) enemy[i] = {x: Math.round(Math.random() * 500), y: Math.round(Math.random() * 500)};
// RUN FUNCTION
var result = furthestFrom(enemy);
// SHOW RESULT ON CANVAS
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
canvas = canvas.getContext("2d");
for (var i = 0; i < 5; i++) {
paintDot(canvas, enemy[i].x, enemy[i].y, 10, "red");
}
paintDot(canvas, result.x, result.y, 20, "blue");
function paintDot(canvas, x, y, size, color) {
canvas.beginPath();
canvas.arc(x, y, size, 0, 6.2831853);
canvas.closePath();
canvas.fillStyle = color;
canvas.fill();
}
<BODY STYLE="margin: 0; border: 0; padding: 0;">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background-color: #EEE;"></CANVAS>
</BODY>
答案 1 :(得分:0)
这与你已经在做的类似,但有两次传球,第一次传球可能相当粗糙。首先降低分辨率。将500x500网格划分为10x10网格,每个网格为50x50。对于每个产生的100个子网格 - 确定哪个至少有一个敌人并找到距离包含敌人的子网格最远的子网格。在这个阶段,只有100个子网格需要担心。一旦找到离敌人最远的子网格 - 增加分辨率。该子网格有50x50 = 2500个正方形。用这些方块做你原来的方法。结果是50x50 + 100 = 2600个方格来处理而不是500x500 = 250,000。 (根据没有500x500但具有相同基本策略的情况适当调整数字。)
这是一个Python3实现。它使用两个函数:
1)fullResSearch(a,b,n,enemies)
此函数采用一组敌人,一个角落位置(a,b)
和一个int n
,并在左上角的nxn平方位置找到该点手角是(a,b)并找到该方格中与敌人有最大最小距离的点。不假设敌人在这个nxn网格中(虽然他们当然可以)
2)findSafePoint(n, enemies, mesh = 20)
此函数接受一组假设位于(0,0)的nxn网格中的敌人。 mesh
确定子网格的大小,默认为20.整个网格分为mesh
x mesh
个子网格(如果mesh
没有,则沿边界略小一些划分n
)我认为是领土。如果领土内有敌人,我称领土为敌方领土。我创建了一组敌方区域,并将其传递给fullResSearch
,参数n
除以mesh
而不是n
。返回值为我提供了距离任何敌方领土最远的领土。这样的领土可以被认为是相当安全的。我将该区域反馈回fullResSearch
以找到该区域中最安全的点作为整体返回函数。得到的点是最佳的或接近最佳的并且非常快速地计算。这是代码(连同test
函数):
import random
def fullResSearch(a,b,n,enemies):
minDists = [[0]*n for i in range(n)]
for i in range(n):
for j in range(n):
minDists[i][j] = min((a+i - x)**2 + (b+j - y)**2 for (x,y) in enemies)
maximin = 0
for i in range(n):
for j in range(n):
if minDists[i][j] > maximin:
maximin = minDists[i][j]
farthest = (a+i,b+j)
return farthest
def findSafePoint(n, enemies, mesh = 20):
m = n // mesh
territories = set() #enemy territories
for (x,y) in enemies:
i = x//mesh
j = y//mesh
territories.add((i,j))
(i,j) = fullResSearch(0,0,m,territories)
a = i*mesh
b = j*mesh
k = min(mesh,n - a,n - b) #in case mesh doesn't divide n
return fullResSearch(a,b,k,enemies)
def test(n, numEnemies, mesh = 20):
enemies = set()
count = 0
while count < numEnemies:
i = random.randint(0,n-1)
j = random.randint(0,n-1)
if not (i,j) in enemies:
enemies.add ((i,j))
count += 1
for e in enemies: print("Enemy at", e)
print("Safe point at", findSafePoint(n,enemies, mesh))
典型的运行:
>>> test(500,5)
Enemy at (216, 67)
Enemy at (145, 251)
Enemy at (407, 256)
Enemy at (111, 258)
Enemy at (26, 298)
Safe point at (271, 499)
(我通过在整个网格上使用fullResSearch进行验证,(271,499)实际上是这些敌人的最佳选择)
答案 2 :(得分:0)
这是一个有趣的解决方案,但我无法测试它的效率。对于每个敌人,从每个数字中创建一行数字,从1开始,每增加一个距离增加1。四个初始线将来自四个边缘,每次进一步向外,您创建另一条以90度角出现的线,同时增加每个距离变化的数量。如果数字行遇到已经创建的小于它的数字,它将不会覆盖它并且将停止进一步延伸。从本质上讲,这使得如果线条找到比它小的数字,它就不会检查任何进一步的网格标记,从而无需检查所有敌人的整个网格。
<<<<<<^^^^^^^
<<<<<<^^^^^^^
<<<<<<X>>>>>>
vvvvvvv>>>>>>
vvvvvvv>>>>>>
public void map(int posX, int posY)
{
//left up right down
makeLine(posX, posY, -1, 0, 0, -1);
makeLine(posX, posY, 0, 1, -1, 0);
makeLine(posX, posY, 1, 0, 0, 1);
makeLine(posX, posY, 0, -1, 1, 0);
grid[posX][posY] = 1000;
}
public void makeLine(int posX, int posY, int dirX, int dirY, int dir2X, int dir2Y)
{
int currentVal = 1;
posX += dirX;
posY += dirY;
while (0 <= posX && posX < maxX && posY < maxY && posY >= 0 && currentVal < grid[posX][posY])
{
int secondaryPosX = posX + dir2X;
int secondaryPosY = posY + dir2Y;
int secondaryVal = currentVal + 1;
makeSecondaryLine( secondaryPosX, secondaryPosY, dir2X, dir2Y, secondaryVal);
makeSecondaryLine( secondaryPosX, secondaryPosY, -dir2X, -dir2Y, secondaryVal);
grid[posX][posY] = currentVal;
posX += dirX;
posY += dirY;
currentVal++;
}
}
public void makeSecondaryLine(int secondaryPosX, int secondaryPosY, int dir2X, int dir2Y, int secondaryVal)
{
while (0 <= secondaryPosX && secondaryPosX < maxX && secondaryPosY < maxY &&
secondaryPosY >= 0 && secondaryVal < grid[secondaryPosX][secondaryPosY])
{
grid[secondaryPosX][secondaryPosY] = secondaryVal;
secondaryPosX += dir2X;
secondaryPosY += dir2Y;
secondaryVal++;
}
}
}
这是我用来绘制整个网格的代码。关于这一点的好处是,检查/写入数字的次数并不是很大程度上取决于屏幕上的敌人数量。使用计数器和随机生成的敌人,我得到了这个:124个敌人和1528537个支票,68个敌人和1246769个支票,15个敌人和795695 500个敌人和1747452支票。与你之前的代码相比,这是一个巨大的差异,它可以做多个敌人*空格数。 对于124个敌人,你已经完成了31000000次检查,而这确实是1528537次,这少于正常检查次数的5%。
答案 3 :(得分:0)
此方法查看中心点的所有敌人,检查他们所处的方向,找到最空的扇区,然后返回通过该扇区中间的一条线,距离中心250 。
结果并不总是完美的,安全点永远不会位于中心(虽然可以添加),但也许它已经足够好了。
该算法在我的i5台式机上每秒运行超过一百万次,但手机可能不如三角测量那么好。该算法每个敌人使用3个三角函数:atan2(),cos()和sin()。这些可能会对执行速度产生最大影响。也许你可以用查找表替换cos()和sin()。
运行代码段以查看随机定位敌人的示例。
function furthestFrom(e) {
var dir = [], widest = 0, bisect;
for (var i = 0; i < 5; i++) {
dir[i] = Math.atan2(e[i].y - 250, e[i].x - 250);
}
dir.sort(function(a, b){return a - b});
dir.push(dir[0] + 6.2831853);
for (var i = 0; i < 5; i++) {
var angle = dir[i + 1] - dir[i];
if (angle > widest) {
widest = angle;
bisect = dir[i] + angle / 2;
}
}
return({x: 250 * (1 + Math.cos(bisect)), y: 250 * (1 + Math.sin(bisect))});
}
// CREATE TEST DATA
var enemy = [];
for (var i = 0; i < 5; i++) enemy[i] = {x: Math.round(Math.random() * 500), y: Math.round(Math.random() * 500)};
// RUN FUNCTION AND SHOW RESULT ON CANVAS
var result = furthestFrom(enemy);
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
canvas = canvas.getContext("2d");
for (var i = 0; i < 5; i++) {
paintDot(canvas, enemy[i].x, enemy[i].y, "red");
}
paintDot(canvas, result.x, result.y, "blue");
// PAINT DOT ON CANVAS
function paintDot(canvas, x, y, color) {
canvas.beginPath();
canvas.arc(x, y, 10, 0, 6.2831853);
canvas.closePath();
canvas.fillStyle = color;
canvas.fill();
}
&#13;
<BODY STYLE="margin: 0; border: 0; padding: 0">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background-color: #EEE;"CANVAS>
</BODY>
&#13;
答案 4 :(得分:0)
Triangulate敌人(那里小于5?);并且用最近的一对敌人对网格的每个角进行三角测量。其中一个三角形的外心应该是重新产生的好地方。
以下是JavaScript中的示例。我用m69的答案进行了演示。绿点是经过测试的候选人,以达到蓝色的建议。由于我们正在对角落进行三角测量,因此它们不是作为解决方案提供的(也许随机更接近的解决方案对于玩家来说是令人兴奋的?或者,也只是测试角落......)。
// http://stackoverflow.com/questions/4103405/what-is-the-algorithm-for-finding-the-center-of-a-circle-from-three-points
function circumcenter(x1,y1,x2,y2,x3,y3)
{
var offset = x2 * x2 + y2 * y2;
var bc = ( x1 * x1 + y1 * y1 - offset ) / 2;
var cd = (offset - x3 * x3 - y3 * y3) / 2;
var det = (x1 - x2) * (y2 - y3) - (x2 - x3)* (y1 - y2);
var idet = 1/det;
var centerx = (bc * (y2 - y3) - cd * (y1 - y2)) * idet;
var centery = (cd * (x1 - x2) - bc * (x2 - x3)) * idet;
return [centerx,centery];
}
var best = 0,
candidates = [];
function better(pt,pts){
var temp = Infinity;
for (var i=0; i<pts.length; i+=2){
var d = (pts[i] - pt[0])*(pts[i] - pt[0]) + (pts[i+1] - pt[1])*(pts[i+1] - pt[1]);
if (d <= best)
return false;
else if (d < temp)
temp = d;
}
best = temp;
return true;
}
function f(es){
if (es.length < 2)
return "farthest corner";
var corners = [0,0,500,0,500,500,0,500],
bestcandidate;
// test enemies only
if (es.length > 2){
for (var i=0; i<es.length-4; i+=2){
for (var j=i+2; j<es.length-2; j+=2){
for (var k=j+2; k<es.length; k+=2){
var candidate = circumcenter(es[i],es[i+1],es[j],es[j+1],es[k],es[k+1]);
if (candidate[0] < 0 || candidate[1] < 0 || candidate[0] > 500 || candidate[1] > 500)
continue;
candidates.push(candidate[0]);
candidates.push(candidate[1]);
if (better(candidate,es))
bestcandidate = candidate.slice();
}
}
}
}
//test corners
for (var i=0; i<8; i+=2){
for (var j=0; j<es.length-2; j+=2){
for (var k=j+2; k<es.length; k+=2){
var candidate = circumcenter(corners[i],corners[i+1],es[j],es[j+1],es[k],es[k+1]);
if (candidate[0] < 0 || candidate[1] < 0 || candidate[0] > 500 || candidate[1] > 500)
continue;
candidates.push(candidate[0]);
candidates.push(candidate[1]);
if (better(candidate,es))
bestcandidate = candidate.slice();
}
}
}
best = 0;
return bestcandidate;
}
// SHOW RESULT ON CANVAS
var canvas = document.getElementById("canvas");
canvas.width = 500; canvas.height = 500;
context = canvas.getContext("2d");
//setInterval(function() {
// CREATE TEST DATA
context.clearRect(0, 0, canvas.width, canvas.height);
candidates = [];
var enemy = [];
for (var i = 0; i < 8; i++) enemy.push(Math.round(Math.random() * 500));
// RUN FUNCTION
var result = f(enemy);
for (var i = 0; i < 8; i+=2) {
paintDot(context, enemy[i], enemy[i+1], 10, "red");
}
for (var i = 0; i < candidates.length; i+=2) {
paintDot(context, candidates[i], candidates[i+1], 7, "green");
}
paintDot(context, result[0], result[1], 18, "blue");
function paintDot(context, x, y, size, color) {
context.beginPath();
context.arc(x, y, size, 0, 6.2831853);
context.closePath();
context.fillStyle = color;
context.fill();
}
//},1500);
&#13;
<BODY STYLE="margin: 0; border: 0; padding: 0;">
<CANVAS ID="canvas" STYLE="width: 200px; height: 200px; background:
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.15) 30%, rgba(255,255,255,.3) 32%, rgba(255,255,255,0) 33%) 0 0,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.3) 13%, rgba(255,255,255,0) 14%) 0 0,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 17%, rgba(255,255,255,.43) 19%, rgba(255,255,255,0) 20%) 0 110px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) -130px -170px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) 130px 370px,
radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.2) 13%, rgba(255,255,255,0) 14%) 0 0,
linear-gradient(45deg, #343702 0%, #184500 20%, #187546 30%, #006782 40%, #0b1284 50%, #760ea1 60%, #83096e 70%, #840b2a 80%, #b13e12 90%, #e27412 100%);
background-size: 470px 470px, 970px 970px, 410px 410px, 610px 610px, 530px 530px, 730px 730px, 100% 100%;
background-color: #840b2a;"></CANVAS>
<!-- http://lea.verou.me/css3patterns/#rainbow-bokeh -->
</BODY>
&#13;
答案 5 :(得分:0)
你可以在网格上选择一些随机点然后从敌人中迭代移动它,这是我在python中的实现:
from numpy import array
from numpy.linalg import norm
from random import random as rnd
def get_pos(enem):
# chose random start position
pos = array([rnd() * 500., rnd() * 500.])
# make several steps from enemies
for i in xrange(25): # 25 steps
s = array([0., 0.]) # step direction
for e in enem:
vec = pos - array(e) # direction from enemy
dist = norm(vec) # distance from enemy
vec /= dist # normalize vector
# calculate size of step
step = (1000. / dist) ** 2
vec *= step
s += vec
# update position
pos += s
# ensure that pos is in bounds
pos[0] = min(max(0, pos[0]), 500.)
pos[1] = min(max(0, pos[1]), 500.)
return pos
def get_dist(enem, pos):
dists = [norm(pos - array(e)) for e in enem]
print 'Min dist: %f' % min(dists)
print 'Avg dist: %f' % (sum(dists) / len(dists))
enem = [(0., 0.), (250., 250.), (500., 0.), (0., 500.), (500., 500.)]
pos = get_pos(enem)
print 'Position: %s' % pos
get_dist(enem, pos)
输出:
Position: [ 0. 250.35338215]
Min dist: 249.646618
Avg dist: 373.606883