Добавлен Koladik,
опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
vJass
Тип:
Алгоритм
Версия Warcraft:
1.31+
Введение
Данный алгоритм позволяет создать квазислучайное распределение. То есть позволяет создать случайное распределение точек, минимальное расстояние между которыми фиксированно, но тем не менее само распределение точек сохраняет свойства случайного. Например это нужно, для того, что бы случайное появление баз в или героев имело минимальное расстояние между собой, чтобы всегда имелось расстояние между ними. Алгоритм быстрый, так как занимает время O(N), где N число точек распределения.
Используемые библиотеки
Данная реализация использует четыре дополнительные библиотеки. Две опубликованные.
И три мои неопубликованные.
Библиотека математики
mathlib
library MathLib initializer onInit
struct math
static method ln takes real x returns real
local real y
local real dy
local integer n = 1
set x = x - 1
set dy = x
set y = dy
loop
set n = n + 1
set dy = -1*dy*x/I2R(n*(n-1))
set y = y + dy
exitwhen (RAbsBJ(dy) < 0.0001)
endloop
return y
endmethod
static method floor takes real r returns integer
if( r >= 0) then
return R2I(r)
else
return R2I(r - 1)
endif
endmethod
static method rabs takes real a returns real
if (a >= 0) then
return a
else
return -a
endif
endmethod
static method exp takes real x returns real
local integer k = 1
local real Ox = 1
local real y = Ox
loop
set Ox = Ox*x/k
set y = y + Ox
set k = k + 1
exitwhen(math.rabs(Ox) < 0.001)
endloop
return y
endmethod
static method factorial takes integer k returns integer
local integer i = 0
local integer m = 1
loop
set i = i + 1
set m = m*i
exitwhen (i == k)
endloop
return m
endmethod
static method isgn takes integer x returns integer
if (x > 0) then
return 1
else
return -1
endif
endmethod
static method rsgn takes real x returns real
if (x > 0) then
return 1.
else
return -1.
endif
endmethod
static method rmax takes real x1, real x2 returns real
if(x1 > x2)then
return x1
else
return x2
endif
endmethod
static method rhoL2 takes real x1, real y1, real x2, real y2 returns real
local real x = x1 - x2
local real y = y1 - y2
return SquareRoot(x*x + y*y)
endmethod
static method rhoL1 takes real x1, real y1, real x2, real y2 returns real
return math.rabs(x1 - x2) + math.rabs(y1 - y2)
endmethod
static method rhoL0 takes real x1, real y1, real x2, real y2 returns real
return math.rmax(math.rabs(x1 - x2), math.rabs(y1 - y2))
endmethod
private static method onInit takes nothing returns nothing
//Заглушка
endmethod
endstruct
endlibrary
Библиотека массивов, расширяющая возможности Multidimensional Array v1.2b by AGD
numjusslib
library numjuss initializer onInit uses MultidimensionalArray
globals
//private hashtable hash
endglobals
struct nj
static method empty_I2D takes integer shape_x, integer shape_y returns nj_I2D
return nj_I2D.create(shape_x, shape_y)
endmethod
static method fill_I2D takes integer shape_x, integer shape_y, integer value returns nj_I2D
local nj_I2D this = nj_I2D.create(shape_x, shape_y)
call this.fill.execute(value)
return this
endmethod
static method zeros_I2D takes integer shape_x, integer shape_y returns nj_I2D
return nj.fill_I2D(shape_x, shape_y, 0)
endmethod
endstruct
struct nj_I1D
integer shape_x
private Integer1D ar
method operator []= takes integer idx, integer value returns nothing
set this.ar[idx] = value
endmethod
method operator [] takes integer idx returns integer
return this.ar[idx]
endmethod
method append takes integer value returns nothing
set this[shape_x] = value
set this.shape_x = this.shape_x + 1
endmethod
static method create takes integer shape_x returns nj_I1D
local nj_I1D this = thistype.allocate()
set this.ar = Integer1D.create()
set this.shape_x = shape_x
return this
endmethod
endstruct
struct nj_I2D extends Integer2D
integer shape_x
integer shape_y
method fill takes integer value returns nothing
local integer i
local integer j
set i = 0
loop
exitwhen(i == this.shape_x)
set j = 0
loop
exitwhen(j == this.shape_y)
set this[i][j] = value
set j = j + 1
endloop
set i = i + 1
endloop
endmethod
method print takes nothing returns nothing
local string s_col = ""
local integer i
local integer j
local texttag loc_tt = CreateTextTag()
set i = 0
loop
exitwhen(i == shape_x)
set j = 0
loop
exitwhen(j == shape_y)
set s_col = s_col + I2S(this[i][j]) + " "
set j = j + 1
endloop
call BJDebugMsg(s_col)
set s_col = ""
set i = i + 1
endloop
endmethod
static method create takes integer shape_x, integer shape_y returns nj_I2D
local nj_I2D this = Integer2D.create()
set this.shape_x = shape_x
set this.shape_y = shape_y
return this
endmethod
endstruct
private function onInit takes nothing returns nothing
//local nj_I2D A = nj.zeros_I2D(255,255)
//call BJDebugMsg("Все")
endfunction
endlibrary
Библиотека управления сеткой карты, продолжающая расширять возможности двумерных массивов, привязывающая ячейки сетки к карте и имеющая функция преобразования ячеек массива в координаты и обратно.
matmaplib
library MatMapLib initializer onInit uses numjuss, MathLib, MultidimensionalArray
struct MatMap
integer DX
integer DY
nj_I2D map
method operator [] takes integer i returns Integer1D
return this.map[i]
endmethod
// ==== Свойства ====
method operator shape_x takes nothing returns integer
return this.map.shape_x
endmethod
method operator shape_y takes nothing returns integer
return this.map.shape_y
endmethod
static method operator maxX takes nothing returns integer
return R2I(GetRectMaxX(GetWorldBounds()))
endmethod
static method operator minX takes nothing returns integer
return R2I(GetRectMinX(GetWorldBounds()))
endmethod
static method operator maxY takes nothing returns integer
return R2I(GetRectMaxY(GetWorldBounds()))
endmethod
static method operator minY takes nothing returns integer
return R2I(GetRectMinY(GetWorldBounds()))
endmethod
//
method mat_i2x takes integer i returns real
return I2R((2*i+1)*this.DX/2 + thistype.minX)
endmethod
method mat_j2y takes integer j returns real
return thistype.maxY - I2R(2*j+1)*this.DY/2
endmethod
method mat_x2i takes real x returns integer
return math.floor((x - thistype.minX)/this.DX)
endmethod
method mat_y2j takes real y returns integer
return math.floor((thistype.maxY - y)/this.DY)
endmethod
static method create takes integer shape_x, integer shape_y returns thistype
local thistype this = thistype.allocate()
set this.DX = (thistype.maxX - thistype.minX)/shape_x
set this.DY = (thistype.maxY - thistype.minY)/shape_y
set this.map = nj.zeros_I2D(shape_x, shape_y)
return this
endmethod
static method create_fill takes integer shape_x, integer shape_y, integer value returns thistype
local thistype this = thistype.allocate()
set this.DX = (thistype.maxX - thistype.minX)/shape_x
set this.DY = (thistype.maxY - thistype.minY)/shape_y
set this.map = nj.fill_I2D(shape_x, shape_y, value)
return this
endmethod
static method create_grid takes integer dx, integer dy returns thistype
local thistype this = thistype.allocate()
local integer shape_x = (thistype.maxX - thistype.minX)/dx
local integer shape_y = (thistype.maxY - thistype.minY)/dy
set this.DX = dx
set this.DY = dy
set this.map = nj.zeros_I2D(shape_x, shape_y)
return this
endmethod
static method create_small takes nothing returns thistype
return thistype.create_grid(128,128)
endmethod
static method create_medium takes nothing returns thistype
return thistype.create_grid(256,256)
endmethod
static method create_large takes nothing returns thistype
return thistype.create_grid(512,512)
endmethod
static method onInit takes nothing returns nothing
//Заглушка
endmethod
endstruct
endlibrary
Описание алгоритма
Данный алгоритм представляет собой реализацию в vJass алгоритма Бридсона для двумерного пространства.
Начальные параметры. Минимальное расстояние между точками real r. Число integer K попыток генерации точек вокруг каждой точки распределения. Область которой будет генерироваться алгоритм rect rec.
- Шаг инициализации Инициализируем два массива для сохранения координат точек распределения по x и по y. Инициализируем целочисленную матрицу, соответствующую ячейкам сетки для сохранения номера массива и ускорения пространственного поиска. Выбираем размер ячейки сетки равный r/sqrt(n), так, что каждая ячейка содержит не более одной точки распределения, так как диагональ квадрата сетки равна r . -1 указывает на то, что ячейка не содержит точек распределения, а неотрицательное значение указывает на индекс экземпляра, расположенного в ячейке.
- Шаг первый Генерируем начальную точку, случайно, в области генерации rec. И вносим ее в сетку и в активный лист.
- Шаг второй Выберем индекс из массива точек распределения из активного листа, скажем i-й. сгенерируем k точек случайно выбранных равномерно в кольце между радиусом r и 2r вокруг x_i точки, проверяем находится ли она в пределах расстояния r существующих точек распределения, используя сетку, что бы проверять только ближайшие точки, а не все. Если точка достаточно далеко от существующих экземпляров внедрять ее в массив точек распределения и добавляем ее в активный лист. После генерации k точек, удаляем x_i из активного листа.
- Шаг третий повторяем второй шаг пока не закончится активный лист.
Результат
Код алгоритма
QuasiGaussLib
library QuasiRandom uses MatMapLib, MathLib
private struct RB
MatMap map
nj_I1D xar
nj_I1D yar
rect rec
integer K
real R
method operator minX takes nothing returns integer
return R2I(GetRectMinX(this.rec))
endmethod
method operator minY takes nothing returns integer
return R2I(GetRectMinY(this.rec))
endmethod
method operator maxX takes nothing returns integer
return R2I(GetRectMaxX(this.rec))
endmethod
method operator maxY takes nothing returns integer
return R2I(GetRectMaxY(this.rec))
endmethod
static method create takes real R, integer K, rect rec returns thistype
local thistype this = thistype.allocate()
local location loc
local real x1
local real y1
//
set this.K = K
set this.R = R
set this.xar = nj_I1D.create(0)
set this.yar = nj_I1D.create(0)
set this.map = GridCreate(R)
set this.rec = rec
return this
endmethod
method GenerateLocation takes real x0, real y0, real r returns location
local real xi_r = r*SquareRoot(GetRandomReal(0, 1)) + r
local real xi_phi = GetRandomReal(0, 2*bj_PI)
local real x = xi_r*Cos(xi_phi) + x0
local real y = xi_r*Sin(xi_phi) + y0
if(this.minX < x and x < this.maxX and /*
*/ this.minY < y and y < this.maxY)then
return Location(x, y)
else
return null
endif
endmethod
method GetFirst takes nothing returns location
local integer x = GetRandomInt(this.minX, this.maxX)
local integer y = GetRandomInt(this.minY, this.maxY)
return Location(I2R(x), I2R(y))
endmethod
static method GridCreate takes real r returns MatMap
local real DX = r/SquareRoot(2)
local real DY = r/SquareRoot(2)
local integer nx = math.floor(I2R(MatMap.maxX - MatMap.minX)/DX)+1
local integer ny = math.floor(I2R(MatMap.maxY - MatMap.minY)/DY)+1
return MatMap.create_fill(nx, ny, -1)
endmethod
method AddPointAnyWay takes real x, real y returns nothing
local integer i = this.map.mat_x2i(x)
local integer j = this.map.mat_y2j(y)
set this.map[i][j] = this.xar.shape_x
call this.xar.append(R2I(x))
call this.yar.append(R2I(y))
endmethod
method CheckCellFromPoint takes real x, real y, integer i, integer j returns boolean
local real rho
local integer k2
if(not(/*
*/ 0 <= i and i < this.map.shape_x and /*
*/ 0 <= j and j < this.map.shape_y))then
return true
endif
set k2 = this.map[i][j]
if (k2 != -1) then
set rho = math.rhoL2(x, y, I2R(this.xar[k2]),I2R(this.yar[k2]))
if (rho < this.R) then
return false
else
return true
endif
else
return true
endif
endmethod
method firststep takes nothing returns nothing
local location loc = this.GetFirst()
local real x1 = GetLocationX(loc)
local real y1 = GetLocationY(loc)
call RemoveLocation(loc)
call this.AddPointAnyWay(x1, y1)
endmethod
method generatefor takes integer k1 returns nothing
local integer k2
local location loc
local real x1 = this.xar[k1]
local real y1 = this.yar[k1]
local real x2
local real y2
local integer i
local integer j
local boolean bool
local real rho
set k2 = 0
loop
exitwhen(k2 == this.K)
set loc = GenerateLocation(x1, y1, this.R)
if(loc != null)then
set x2 = GetLocationX(loc)
set y2 = GetLocationY(loc)
set i = map.mat_x2i(x2)
set j = map.mat_y2j(y2)
if (map[i][j] == -1) then
set bool = true
if(/*
*/ not(this.CheckCellFromPoint(x2, y2, i-1, j )) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i-1, j+1)) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i, j+1)) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i+1, j+1)) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i+1, j )) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i+1, j-1)) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i, j-1)) or /*
*/ not(this.CheckCellFromPoint(x2, y2, i-1, j-1))) then
set bool = false
endif
if(bool)then
call this.AddPointAnyWay(x2, y2)
endif
endif
call RemoveLocation(loc)
endif
set k2 = k2 + 1
endloop
endmethod
method step takes integer k1 returns integer
local integer i = this.xar.shape_x
call generatefor.execute(k1)
return this.xar.shape_x - i
endmethod
static method Gen takes real R, integer K, rect rec returns thistype
local thistype this = thistype.create(R, K, rec)
local integer j = 0
call this.firststep()
loop
exitwhen(j == this.xar.shape_x)
call this.step(j)
set j = j + 1
endloop
return this
endmethod
static method onInit takes nothing returns nothing
endmethod
endstruct
struct QR
nj_I1D xar
nj_I1D yar
static method create takes real R, integer K, rect rec returns thistype
local thistype this = thistype.allocate()
local RB rb = RB.Gen(R, K, rec)
set this.xar = rb.xar
set this.yar = rb.yar
return this
endmethod
endstruct
endlibrary
Использование API
library TestQR initializer onInit
private function Test takes QR qr returns nothing
local integer i = 0
call BJDebugMsg("Найдено точек " + I2S(qr.xar.shape_x))
loop
exitwhen(i == qr.xar.shape_x)
call CreateUnit(Player(0),'hfoo', qr.xar[i], qr.yar[i], 0)
set i = i + 1
endloop
endfunction
private function onInit takes nothing returns nothing
local QR qr = QR.create(1024.0, 10, GetPlayableMapRect())
call Test(qr)
endfunction
endlibrary
Функция local QR qr = QR.create(1024.0, 10, GetPlayableMapRect()) заполняет область GetPlayableMapRect() точками, доступ к котрым осуществляется через qr.xar[i] и qr.yar[i] для координаты x и y соответственно. Размер массива можно узнать по команде qr.xar.shape_x
в результате получается следующее распределение точек
в результате получается следующее распределение точек
Общее число точек может отличатся от генерации к генерации. Карту пример можно скачать по ссылке
Тот же алгоритм но на питоне, писал, что бы разораться и легче перенести в war3. Может кому-то пригодится.
Python
import numpy as np
import math
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.ticker import FixedLocator
class QuasiRand2d:
def getcirk(self,i,j):
array = [(i - 1, j),
(i - 1, j + 1),
(i, j + 1),
(i + 1, j + 1),
(i + 1, j),
(i + 1, j - 1),
(i, j - 1),
(i - 2, j),
(i, j + 2),
(i + 2, j),
(i, j - 2)]
return [self.grid[array[i]] for i in range(0,len(array)) if
0 <= array[i][0] < self.nx and
0 <= array[i][1] < self.ny]
def x2i(self, x):
return np.int32(np.floor((x - self.minX) / self.dx))
def y2j(self, y):
return np.int32(np.floor((y - self.minY) / self.dy))
def getsubarray(self, x0, y0, k=20):
r = self.r
grid = self.grid
R = r * np.sqrt(self.rng.random(size=k)) + r
phi = self.rng.random(size=k) * 2 * np.pi
X = []
Y = []
for i in range(0, k):
x = R[i] * np.cos(phi[i]) + x0
y = R[i] * np.sin(phi[i]) + y0
if(
self.minX <= x <= self.maxX and
self.minY <= y <= self.maxY):
X.append(x)
Y.append(y)
X = np.int32(np.array(X))
Y = np.int32(np.array(Y))
return X, Y
def expandarray(self, x0, y0, k=20):
X, Y = self.getsubarray(x0, y0, k)
I = self.x2i(X)
J = self.y2j(Y)
for i in range(0, len(I)):
if self.grid[I[i], J[i]] == -1:
b = True
for i2 in self.getcirk(I[i], J[i]):
if i2 != -1:
l = math.sqrt((self.X[i2] - X[i]) ** 2 + (self.Y[i2] - Y[i]) ** 2)
if l < self.r:
b = False
break
if b:
print(len(self.X))
self.grid[I[i], J[i]] = len(self.X)
self.X.append(X[i])
self.Y.append(Y[i])
def step(self):
imax = len(self.X)
for i in range(self.i, imax):
self.expandarray(self.X[i], self.Y[i], k=20)
self.i = imax
def __init__(self, minX, maxX, minY, maxY, r):
self.dx = r/math.sqrt(2)
self.dy = self.dx
self.r = r
self.minX = minX
self.minY = minY
self.maxX = maxX
self.maxY = maxY
self.rng = np.random.default_rng()
self.nx = int((maxX - minX)/self.dx)+1
self.ny = int((maxY - minY)/self.dy)+1
self.grid = np.full((self.nx, self.ny), -1, np.int32)
self.X = [np.int32(0)]#[self.rng.integers(int(maxX - minX), dtype=np.int32) + minX]
self.Y = [np.int32(0)]#[self.rng.integers(int(maxY - minY), dtype=np.int32) + minY]
self.grid[self.x2i(self.X[0]), self.y2j(self.Y[0])] = 0
self.i = 0
maxX = 512*34
maxY = 512*34
minX = -maxX
minY = -maxY
QR = QuasiRand2d(minX, maxX, minY, maxY, 512)
for i in range(0,40):
QR.step()
X = QR.X
Y = QR.Y
minorsX = np.arange(QR.minX, QR.maxX+1, QR.dx)
minorsY = np.arange(QR.minY, QR.maxY+1, QR.dy)
fig = plt.figure()
fig.set_size_inches(10, 6)
ax = fig.add_subplot(1, 1, 1)
ax.xaxis.set_minor_locator(FixedLocator(minorsX))
ax.yaxis.set_minor_locator(FixedLocator(minorsY))
ax.grid(which='minor', color='black', linestyle='-', linewidth = "0.5")
# im1 = ax.imshow(np.transpose(B))
# im1.set_cmap(cmap)
# im1.set_clim(0, cmap.N)
ax.plot(X,Y,'*')
ax.set_xlim((minX, maxX))
ax.set_ylim((minY, maxY))
plt.show()
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
BrEd Pitt
1 месяц назад
0
За питоновый алгоритм отдельное спасибо! Даже не знал про такой способ генерации
Чтобы оставить комментарий, пожалуйста, войдите на сайт.