Добавлен , опубликован
Возможно вам хотелось сделать окружающее пространство более живым и органичным? Данная наработка предоставляет возможность поэтапной смены тайлов, демонстрирующей "зарастание" покрова. Для примера будут использованы стандартные тайлы, хотя вы всегда можете подставить свои.
Быстрый старт
code
-- синхронизируем lua рандом
-- только для 1.26
math.randomseed(GetRandomInt(0, 2147483648-1))

local world_rect = GetWorldBounds()
local minX, maxX, minY, maxY = GetRectMinX(world_rect), GetRectMaxX(world_rect), GetRectMinY(world_rect), GetRectMaxY(world_rect)
local step = 128 -- шаг сетки

local Timer = TimerSystem()
local ts_array = SquareArray(minX, maxX, minY, maxY, step) -- квадратный массив для создания TerrainSystem
local ts_set = function (x,y,typ) SetTerrainType(x,y,typ,math.random(4)-1,1,0) end
local ts_get = GetTerrainType
ter_sys = TerrainSystem{
	Timer = Timer,
	array = ts_array,
	get_type = ts_get,
	set_type = ts_set,
}

-- функция конвертации строки-типа в число, для того чтобы показать пример
local function FourCC(str)
	local n, len = 0, #str
	for i = len, 1, -1 do n = n + (str:byte(i,i) << 8*(len-i)) end -- shift by 0,8,16,24
	return n
end

-- создаем 3 участка
SetTerrainType(-200,200, FourCC('Ldrg'), 1, 3, 0)

SetTerrainType(200,-200, FourCC('Lrok'), 1, 3, 0)

SetTerrainType(-200,-200, FourCC('Ybtl'), 1, 3, 0)

-- запуск по всей карте
-- ter_sys:init() 


-- запуск в квадратном участке
-- for x = -384, 384, 128 do
-- 	for y = -384, 384, 128 do
-- 		ter_sys:start_tox(x, y)
-- 	end
-- end

-- Timer.after(1, function()
-- 	ter_sys:stop_tox(0,128)
-- 	ter_sys:modification(128,0, 5)
-- end)
API
local ter_sys = TerrainSystem(...) -- см быстрый старт
ter_sys:start_tox(real x, real y) --> true or false | запустить зарастание
ter_sys:stop_tox(real x, real y) --> остановить зарастание
ter_sys:modification(rela x, real y, real ratio) --> true or false | ускорить или замедлить
-- изначально скорость 1, передача -0.5 ускорит на 50%, передача 3. замедлит на 300%
ter_sys:init() -- запускает start_tox() по всей карте
ter_sys:IsEarth(real x, real y) -- true, если это один из тайлов земли
ter_sys:IsFootballGrass(real x, real y) -- true, если это футбольная трава
ter_sys:IsGreenGrass(real x, real y) -- true, если это зеленая трава
ter_sys:IsYellowGrass(real x, real y)  -- true, если это желтая трава
БД
-- types - цепочки превращений земли до FOOTBALL_GRASS 
-- type - тип почвы
-- time - время перехода до следующей стадии
local EARTH = { 
	{types = {'Ldrt', 'Ldrg', 'Fdrg'}, time = 3},
	{types = {'Lrok', 'Frok'}, time = 4},
}
local FOOTBALL_GRASS = {type = 'Lgrs', time = 5}
local GREEN_GRASS = {type = 'Lgrd', time = 6}
local YELLOW_GRASS = {type = 'Fgrd'}
По кнопке скачать находится архив с исходниками и их адаптированными вариантами для 1.26 и reforget.
Отдельно спасибо МрачныйВорон за спонсирование данной наработки
`
ОЖИДАНИЕ РЕКЛАМЫ...
26
потому что создаешь объекты вне мейн потока
38
Круто, но какое-то рандомное оно, вот бы в разные стороны разрасталось островками такими и каждый новый уровень от центра островка этого
27
Хотел поделиться своим ГУИ. Ни в коем случае не подумайте, что я хочу обесценить кого-нить или еще что-нибудь в таком духе. Просто мне стало интересно сделать такое на гуи, так сказать, доказать для себя, что такое возможно. Зарастание проходит в заданной области и с конкретной указанной точки. Утечки не убирал, потому что просто хотел получить демонстрацию.
П.С. а как вставить видео на сайт без ютуба?
Загруженные файлы
27
вот для рефорджа карта и скрипт пример заростания почвы
скрипт
do
    local InitGlobalsOrigin = InitGlobals
    function InitGlobals()
        InitGlobalsOrigin()



----------------------------------------------- SquareArray
if not SquareArray then
local floor = math.floor
local ceil = math.ceil


local function round(x) return x > 0 and floor(x+0.5) or ceil(x-0.5) end

local function norm_x(self, x)
	if x < self.minX or x > self.maxX then return nil
	else return x end
end

local function norm_y(self, y)
	if y < self.minY or y > self.maxY then return nil
	else return y end
end

local function norm_ix(self, ix)
	if ix < self.minIX or ix > self.maxIX then return nil
	else return ix end
end

local function norm_iy(self, iy)
	if iy < self.minIY or iy > self.maxIY then return nil
	else return iy end
end

local index = {}

function index:norm_xy(x,y) return norm_x(self, x), norm_y(self, y) end

function index:is_xy(x, y) return norm_x(self, x) and norm_y(self, y) end

function index:real_to_index(x, y)
	x, y = self:norm_xy(x, y)
	if not (x and y) then return nil end
	local offsetX, offsetY, step = self.offsetX, self.offsetY, self.step
	local ix = round((x+offsetX)/step)
	local iy = round((y+offsetY)/step)
	return ix, iy
end

function index:get_xy(x, y)
	local ix, iy = self:real_to_index(x, y)
	return ix and self.grid[iy][ix] or nil
end

function index:set_xy(x, y, val)
	local ix, iy = self:real_to_index(x, y)
	if ix then self.grid[iy][ix] = val end
end

function index:get_ixiy(ix, iy)
	return self.grid[iy][ix]
end

function index:set_ixiy(ix, iy, val)
	self.grid[iy][ix] = val
end

function index:id_xy(x, y)
	local ix, iy = self:real_to_index(x, y)
	if ix then
		return iy*self.maxIX + ix
	else return nil end
end

function index:by_all_square()
	local offsetX, offsetY, step = self.offsetX, self.offsetY, self.step
	local minIX, minIY, maxIX, maxIY = self.minIX, self.minIY, self.maxIX, self.maxIY
	-- print(offsetX, offsetY, step)
	-- print(minIX, minIY, maxIX, maxIY)
	local t_minIX = minIX - 1
	return function()
		t_minIX = t_minIX + 1
		if t_minIX > maxIX then
			t_minIX = minIX
			minIY = minIY + 1
		end
		if minIY <= maxIY then
			return t_minIX*step-offsetX, minIY*step-offsetY, t_minIX, minIY
		end
	end
end

local mt = {__index = index}

function SquareArray(minX, maxX, minY, maxY, step)
	local grid = {}
	local object = { minX=minX, maxX=maxX, minY=minY, maxY=maxY, step=step,
		offsetY = -minY, offsetX = -minX, players = {},
		minIX = 0, minIY = 0, maxIX = round((maxX-minX)/step), maxIY = round((maxY-minY)/step),
		grid = grid,
	}
	for i=0, object.maxIY do grid[i] = {} end
	return setmetatable(object, mt)
end
end
----------------------------------------------- Time
if not TimeSystem then
function TimeSystem(ptime)
	local list = {}
	local ht = {}
	local tick = 0
	local ptime = ptime or 0.03125
	local Time = {}

	-- @param func function(time) [[your code]] end
	local function subscribe(func)
		table.insert(list, func)
		ht[func] = true
	end

	local function unsubscribe(func)
		if ht[func] then
			for i=1, #list do
				if list[i] == func then table.remove(list, i); ht[func] = nil end
			end
		end
	end

	local function update()
		tick = tick + 1
		local time = tick*ptime
		Time.time = time
		for i=1, #list do
			list[i](time)
		end
	end
	stop = true
	local function run()
		while stop do
			update()
		end
	end

	Time.ptime = ptime
	Time.time = 0
	Time.subscribe = subscribe
	Time.unsubscribe = unsubscribe
	Time.run = run

	-- connect API game
	TimerStart(CreateTimer(), ptime, true, update)
	return Time
end
end
----------------------------------------------- TimerHeap
if not TimerHeap then
local assert = assert
local floor = math.floor
--================================================================
-- https://github.com/Tieske/binaryheap.lua/blob/master/src/binaryheap.lua
-- basic heap sorting algorithm
--================================================================

--- Basic heap.
-- This is the base implementation of the heap. Under regular circumstances
-- this should not be used, instead use a _Plain heap_ or _Unique heap_.
-- @section baseheap

--- Creates a new binary heap.
-- This is the core of all heaps, the others
-- are built upon these sorting functions.
-- @param swap (function) `swap(heap, idx1, idx2)` swaps values at
-- `idx1` and `idx2` in the heaps `heap.values` and `heap.payloads` lists (see
-- return value below).
-- @param erase (function) `swap(heap, position)` raw removal
-- @param lt (function) in `lt(a, b)` returns `true` when `a < b` (for a min-heap)
-- @return table with two methods; `heap:bubbleUp(pos)` and `heap:sinkDown(pos)`
-- that implement the sorting algorithm and two fields; `heap.values` and
-- `heap.payloads` being lists, holding the values and payloads respectively.
local binaryHeap = function(swap, erase, lt)

	local heap = {
			values = {},  -- list containing values
			erase = erase,
			swap = swap,
			lt = lt,
		}

	function heap:bubbleUp(pos)
		local values = self.values
		while pos>1 do
			local parent = floor(pos/2)
			if not lt(values[pos], values[parent]) then
					break
			end
			swap(self, parent, pos)
			pos = parent
		end
	end

	function heap:sinkDown(pos)
		local values = self.values
		local last = #values
		while true do
			local min = pos
			local child = 2 * pos

			for c = child, child + 1 do
				if c <= last and lt(values[c], values[min]) then min = c end
			end

			if min == pos then break end

			swap(self, pos, min)
			pos = min
		end
	end

	return heap
end

--================================================================
-- plain heap management functions
--================================================================

--- Plain heap.
-- A plain heap carries a single piece of information per entry. This can be
-- any type (except `nil`), as long as the comparison function used to create
-- the heap can handle it.
-- @section plainheap
do end -- luacheck: ignore
-- the above is to trick ldoc (otherwise `update` below disappears)

local update_old
--- Updates the value of an element in the heap.
-- @function heap:update
-- @param pos the position which value to update
-- @param newValue the new value to use for this payload
update_old = function(self, pos, newValue)
	assert(newValue ~= nil, "cannot add 'nil' as value")
	assert(pos >= 1 and pos <= #self.values, "illegal position")
	self.values[pos] = newValue
	if pos > 1 then self:bubbleUp(pos) end
	if pos < #self.values then self:sinkDown(pos) end
end

local remove
--- Removes an element from the heap.
-- @function heap:remove
-- @param pos the position to remove
-- @return value, or nil if a bad `pos` value was provided
remove = function(self, pos)
	local last = #self.values
	if pos < 1 then
		return  -- bad pos

	elseif pos < last then
		local v = self.values[pos]
		self:swap(pos, last)
		self:erase(last)
		self:bubbleUp(pos)
		self:sinkDown(pos)
		return v

	elseif pos == last then
		local v = self.values[pos]
		self:erase(last)
		return v

	else
		return  -- bad pos: pos > last
	end
end

local insert
--- Inserts an element in the heap.
-- @function heap:insert
-- @param value the value used for sorting this element
-- @return nothing, or throws an error on bad input
insert = function(self, value) -- changed
	assert(value ~= nil, "cannot add 'nil' as value")
	local pos = #self.values + 1
	self.values[pos] = value
	self.position[value] = pos
	self:bubbleUp(pos)
end

local pop
--- Removes the top of the heap and returns it.
-- @function heap:pop
-- @return value at the top, or `nil` if there is none
pop = function(self)
	if self.values[1] ~= nil then
		return remove(self, 1)
	end
end

local peek
--- Returns the element at the top of the heap, without removing it.
-- @function heap:peek
-- @return value at the top, or `nil` if there is none
peek = function(self)
	return self.values[1]
end

local size
--- Returns the number of elements in the heap.
-- @function heap:size
-- @return number of elements
size = function(self)
	return #self.values
end

local function swap(heap, a, b) -- (pos_a and pos_b) -- changed
	heap.position[heap.values[a]], heap.position[heap.values[b]] = b, a
	heap.values[a], heap.values[b] = heap.values[b], heap.values[a]
end

local function erase(heap, pos) -- changed
	heap.position[heap.values[pos]] = nil
	heap.values[pos] = nil
end

--================================================================
-- new functions
-- changed: erase, swap, insert
local function append(self, obj)
	local pos = self.position[obj]
	if pos then
		self:update_old(pos, obj)
	else
		self:insert(obj)
	end
end

local function drop(self, obj)
	local pos = self.position[obj]
	if pos and self.values[pos] ~= nil then
		return remove(self, pos)
	end
end

local function update(self, obj)
	local pos = self.position[obj]
	if pos then
		self:update_old(pos, obj)
	else return nil end
end

local function first(self)
	return self.values[1]
end

--================================================================
--================================================================
-- TimerHeap heap creation
--================================================================

function TimerHeap(lt)
	-- if not lt then lt = function(a,b) return a[1] < b[1] end end
	local h = binaryHeap(swap, erase, lt)
	h.peek = peek
	h.pop = pop
	h.size = size
	h.remove = remove
	h.insert = insert -- changed
	h.update_old = update_old -- renamed

	h.position = {}
	h.append = append
	h.drop = drop
	h.update = update
	h.first = first
	return h
end
end
----------------------------------------------- Timer
if not TimerSystem then
local TimerHeap = TimerHeap
local TimeSystem = TimeSystem
assert(TimerHeap, TimeSystem)


----- class Timer
local index = {}

function index:every(delay, count, func, arg)
	if type(func) ~= 'function' then print('error') return end
	local Time = self.Time
	if delay <= 0 then delay = Time.ptime end
	self.delay = delay
	self.count = count
	self.func = func
	self.arg = arg
	self.start_time = Time.time
	self.end_time = Time.time + delay
	self.Heap:append(self)
	return self
end

function index:after(delay, func, arg)
	return self:every(delay , 1, func, arg)
end

function index:pause()
	return self.Heap:drop(self)
end

function index:remained()
	local time = self.end_time - self.Time.time
	return time > 0 and time or 0
end

local mt = {__index = index}
-----

local function lt(a,b) return a.end_time < b.end_time end

function TimerSystem(Time)
	Time = Time or TimeSystem()
	assert(Time)
	local Heap = TimerHeap(lt)

	local Timer = {}
	function Timer.new()
		local timer = {Time = Time, Heap = Heap}
		return setmetatable(timer, mt)
	end

	function Timer.after(...)
		return Timer.new():after(...)
	end

	function Timer.every(...)
		return Timer.new():every(...)
	end

	local function update_timer(self) -- timer
		self.count = self.count - 1
		if self.count > 0 then
			self.count = self.count - 1
			self.start_time = self.end_time
			self.end_time = self.end_time + self.delay
			Heap:append(self)
		end
		self.func(self, self.arg)
		if self.count <= 0 then Heap:pop() end
	end

	Time.subscribe(function(time)
		local timer = Heap:first()
		while timer and timer.end_time and timer.end_time <= time do
			update_timer(timer)
			timer = Heap:first()
		end
	end)
	return Timer
end
end
----------------------------------------------- TerrainSystem
if not TerrainSystem then
-- types - цепочки превращений земли до FOOTBALL_GRASS 
-- type - тип почвы
-- time - время перехода до следующей стадии
local EARTH = {
	{types = {'Ldrt', 'Ldrg', 'Fdrg'}, time = 3},
	{types = {'Lrok', 'Frok'}, time = 4},
}
local FOOTBALL_GRASS = {type = 'Lgrs', time = 5}
local GREEN_GRASS = {type = 'Lgrd', time = 6}
local YELLOW_GRASS = {type = 'Fgrd'}

local function FourCC(str)
	local n, len = 0, #str
	for i = len, 1, -1 do n = n + (str:byte(i,i) << 8*(len-i)) end -- shift by 0,8,16,24
	return n
end
local ALL_TYPES = {}
local function add_all_types(tbl) -- преобразовавыем строки в числа в бд
	if tbl.type then
		local str = tbl.type
		tbl.type = FourCC(tbl.type)
		ALL_TYPES[tbl.type] = str
	elseif tbl.types then
		for i=1, #tbl.types do
			local str = tbl.types[i]
			tbl.types[i] = FourCC(tbl.types[i])
			ALL_TYPES[tbl.types[i]] = str
		end
	else
		for _, dict in ipairs(tbl) do add_all_types(dict) end
	end
end
add_all_types{EARTH, FOOTBALL_GRASS, GREEN_GRASS, YELLOW_GRASS}

local function IsEarth(self,x,y)
	local typ = self.get_type(x,y)
	for _, chain in ipairs(EARTH) do
		for _, next_typ in ipairs(chain.types) do
			if next_typ == typ then return true end
		end
	end
end

local function IsYellowGrass(self,x,y)
	return self.get_type(x,y) == YELLOW_GRASS.type
end

local function IsGreenGrass(self,x,y)
	return self.get_type(x,y) == GREEN_GRASS.type
end

local function IsFootballGrass(self,x,y)
	return self.get_type(x,y) == FOOTBALL_GRASS.type
end

-- @param typ - optional - needed type
-- @return (next_type, time) or nil
local function next(self, x, y, typ)
	local typ = typ or self.get_type(x,y)
	local time, next_typ

	if not ALL_TYPES[typ] then return nil end
	if GREEN_GRASS.type == typ then
		time = GREEN_GRASS.time
		next_typ = YELLOW_GRASS.type
		return next_typ, time
	elseif FOOTBALL_GRASS.type == typ then
		time = FOOTBALL_GRASS.time
		next_typ = GREEN_GRASS.type
		return next_typ, time
	else
		for _, chain in ipairs(EARTH) do -- пробегаем по EARTH
			for i, next_typ in ipairs(chain.types) do
				if typ == next_typ then -- если совпал тип
					if i >= #chain.types then -- если последняя стадия
						next_typ = FOOTBALL_GRASS.type
					else
						next_typ = chain.types[i+1]
					end
					time = chain.time
					return next_typ, time
				end
			end
		end
	end
end

local update
local function save_point(self, x, y, time)
	local timer = self.array:get_xy(x,y)
	if not timer then
		timer = self.Timer.after(time, update, {self,x,y,1}) -- 1-speed
		-- timer = Timer.start(time, false, update, {self,x,y,1})
		self.array:set_xy(x,y, timer)
	else
		timer:after(time, update, {self,x,y,1})
	end
end

update = function(timer, arg)
	local self, x, y = arg[1], arg[2], arg[3]
	local typ, dtime = next(self, x, y)
	assert(typ)
	self.set_type(x,y, typ)
	typ, dtime = next(self, x, y, typ)
	-- print('next typ', ALL_TYPES[typ])
	if typ then
		timer:after(dtime, update, arg)
	else
		self.array:set_xy(x,y,nil)
	end
end

local index = {}
function index:start_tox(x,y) --> true or false
	if self.array:is_xy(x,y) then
		local typ, dtime = next(self, x, y)
		if not dtime then return false end
		save_point(self, x, y, dtime)
	else return false end
end

function index:stop_tox(x,y)
	local timer = self.array:get_xy(x,y)
	if timer then
		timer:pause()
		self.array:set_xy(x,y,nil)
	end
end

function index:modification(x,y, ratio) --> true or false
	local timer = self.array:get_xy(x,y)
	-- print('timer', timer, x, y)
	if timer then
		local speed = timer.arg[4] + ratio
		timer.arg[4] = speed
		local dtime = timer:remained()*(speed >= 0 and speed or 0)
		-- print('dtime', dtime)
		timer:after(dtime, timer.func, timer.arg)
		return true
	else return false end
end

function index:init()
	local rand, array = math.random, self.array
	local up_time = (array.maxIX*array.maxIY)/100
	for x, y, ix, iy in array:by_all_square() do
		local typ, dtime = next(self, x, y)
		if dtime then
			dtime = dtime+rand()*up_time
			save_point(self, x, y, dtime)
		end
	end
end

index.IsEarth = IsEarth
index.IsGreenGrass = IsGreenGrass
index.IsYellowGrass = IsYellowGrass
index.IsFootballGrass = IsFootballGrass

local mt = {__index = index}

-- @param Timer = require "Timer"
-- @param array = require "SquareArray"
-- @param get_type = function(real x, real y) --> terrain_type
-- @param set_type = function(real x, real y, terrain_type type)
function TerrainSystem(tbl)
	assert(tbl.Timer and tbl.array and tbl.get_type and tbl.set_type)
	local object = {
		Timer = tbl.Timer,
		get_type = tbl.get_type,
		set_type = tbl.set_type,
		array = tbl.array,
	}
	return setmetatable(object, mt)
end

end

----------------------------------------------- Пример использования TerrainSystem

print(1)
-- math.randomseed(GetRandomInt(0, 2147483648-1)) -- синхронизируем lua рандом
print(2)
local world_rect = GetWorldBounds()
local minX, maxX, minY, maxY = GetRectMinX(world_rect), GetRectMaxX(world_rect), GetRectMinY(world_rect), GetRectMaxY(world_rect)
local step = 128 -- шаг сетки
print(3)
local Timer = TimerSystem()
print(4)
local ts_array = SquareArray(minX, maxX, minY, maxY, step) -- квадратный массив для создания TerrainSystem
print(5)
local ts_set = function (x,y,typ) SetTerrainType(x,y,typ,math.random(4)-1,1,0) end
print(6)
local ts_get = GetTerrainType
print(7)
ter_sys = TerrainSystem{
	Timer = Timer,
	array = ts_array,
	get_type = ts_get,
	set_type = ts_set,
}
print(8)
-- функция конвертации строки-типа в число, для того чтобы показать пример
local function FourCC(str)
	local n, len = 0, #str
	for i = len, 1, -1 do n = n + (str:byte(i,i) << 8*(len-i)) end -- shift by 0,8,16,24
	return n
end
print(9)
-- создаем 3 участка
SetTerrainType(-200,200, FourCC('Ldrg'), 1, 3, 0)

SetTerrainType(200,-200, FourCC('Lrok'), 1, 3, 0)

SetTerrainType(-200,-200, FourCC('Ybtl'), 1, 3, 0)


-- print('ter_sys:IsEarth(-200, 200)', ter_sys:IsEarth(-200, 200))
-- print('ter_sys:IsYellowGrass(-200, 200)', ter_sys:IsYellowGrass(-200, 200))

-- запуск по всей карте
ter_sys:init() 


-- запуск в квадратном участке
-- for x = -384, 384, 128 do
-- 	for y = -384, 384, 128 do
-- 		ter_sys:start_tox(x, y)
-- 	end
-- end

-- Timer.after(1, function()
-- 	ter_sys:stop_tox(0,128)
-- 	ter_sys:modification(128,0, 5)
-- end)


    end
end
Загруженные файлы
18
Круто, но какое-то рандомное оно, вот бы в разные стороны разрасталось островками такими и каждый новый уровень от центра островка этого
По ТЗ там одновременно должно вырасти, рандома я добавил чтобы игра не провисла)

Обновление
  • Зафиксирован вылет на reforget из-за math.randomseed()
  • Для reforget теперь отдельный код, добавлена карта от МрачныйВорон
  • Для 1.26 файлы Scripts\blizzard.lua и Scripts\common.lua включены в карту сразу

Lord_Teo, класс. Для видео используй формат .mp4. Для того чтобы убедиться, что видео поддерживается сайтом, есть кнопка Предпросмотр в ресурсах и комментариях
27
Lord_Teo, класс. Для видео используй формат .mp4. Для того чтобы убедиться, что видео поддерживается сайтом, есть кнопка Предпросмотр в ресурсах и комментариях
Спасибо. Я в курсе про предосмотр, но я не знал как сделать, чтобы видео было видно😄
Загруженные файлы
Этот комментарий удален
18
  • Обновил библиотеки внутри работы
  • Поправил подсветку синтаксиса в шапке
27
Снова просматривал эту наработку от скуки. Я вот думаю, что почва так не растет. Она оч быстро растет у вас, по видео, но в качестве ознакомительного проекта сойдет.
Вот пример в реале, я на даче заметил, что там где песок, травы не видно. Но стоит туда чернозем завести, вода сделает свое дело, и со временем там зарастает. Стоит туда куслк навоза кинуть, и там уже маленький островок из травы. Если косишь траву, то вся эта скошенная трава становится новым удобрением для будущей травы, так и расширчется по тихоньку. И так далее.
18
Она оч быстро растет
Это ускоренный вариант для видео
18
Залипалово, думал мб подобное сделать, но через клеточные автоматы.
пока только на питоне набросал
код
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as anim
from matplotlib.colors import ListedColormap

np.random.seed(0)

# Цвета состояний
colors = [
    (139/255, 69/255, 19/255),    # 0 - Земля (коричневый)
    (144/255, 238/255, 144/255),  # 1 - Редкая трава (светло-зелёный)
    (34/255, 139/255, 34/255),    # 2 - Обычная трава (зелёный)
    (0/255, 100/255, 0/255),      # 3 - Густая трава (тёмно-зелёный)
    (0/255, 0/255, 255/255),      # 4 - Вода (синий)
    (169/255, 169/255, 169/255),  # 5 - Камень (серый)
]
cmap = ListedColormap(colors)

# Веса состояний
state_weights = {
    0: {  # Земля
        0:  0.001,  # Земля ~ Земля
        1:  0.3,    # Земля ~ Редкая трава
        2:  0.6,    # Земля ~ Обычная трава
        3:  0.9,    # Земля ~ Густая трава
        4:  1.5,    # Земля ~ Вода
        5: -0.5,    # Земля ~ Камень
    },
    1: {  # Редкая трава
        0:  0.001,  # Редкая трава ~ Земля
        1:  0.15,   # Редкая трава ~ Редкая трава
        2:  0.3,    # Редкая трава ~ Обычная трава
        3:  0.45,   # Редкая трава ~ Густая трава
        4:  0.75,   # Редкая трава ~ Вода
        5: -1.0,    # Редкая трава ~ Камень
    },
    2: {  # Обычная трава
        0:  0.001,  # Обычная трава ~ Земля
        1:  0.1,    # Обычная трава ~ Редкая трава
        2:  0.2,    # Обычная трава ~ Обычная трава
        3:  0.3,    # Обычная трава ~ Густая трава
        4:  0.5,    # Обычная трава ~ Вода
        5: -1.5,    # Обычная трава ~ Камень
    },
}

# Размер сетки
grid_size = 100

# Начальная сетка: вся земля
grid = np.zeros((grid_size, grid_size), dtype=int)

# Генерация воды и камней на сетке
for _ in range(np.random.randint(2, 6)):  # Случайные области воды
    x0, y0 = np.random.randint(0, grid_size, size=2)
    w, h = np.random.randint(2, 21, size=2)
    for i in range(w):
        for j in range(h):
            x, y = (x0 + i) % grid_size, (y0 + j) % grid_size
            grid[x, y] = 4  # Вода
for _ in range(np.random.randint(5, 21)):  # Случайные области камней
    x0, y0 = np.random.randint(0, grid_size, size=2)
    w, h = np.random.randint(2, 5, size=2)
    for i in range(w):
        for j in range(h):
            x, y = (x0 + i) % grid_size, (y0 + j) % grid_size
            grid[x, y] = 5  # Камень

# Генерация матрицы весов
def generate_weight_matrix(radius):
    size = 2 * radius + 1
    weight_matrix = np.zeros((size, size), dtype=float)
    for x in range(-radius, radius + 1):
        for y in range(-radius, radius + 1):
            weight_matrix[x + radius, y + radius] = (radius + 1 - abs(x)) * (radius + 1 - abs(y))
    return weight_matrix

# Нормализация матрицы весов
def normalize_matrix(matrix):
    return matrix / matrix.sum()

# Вычисление влияния для ячейки (x, y) на основе её соседей
def calculate_influence_for_cell(grid, weight_matrix, x, y):
    influence = 0
    height, width = grid.shape
    
    weight_height, weight_width = weight_matrix.shape
    
    half_weight_height = weight_height // 2
    half_weight_width = weight_width // 2

    xy_state = grid[x, y]
    
    for i in range(weight_height):
        for j in range(weight_width):
            dx = i - half_weight_height
            dy = j - half_weight_width
            
            nx = (x + dx) % height
            ny = (y + dy) % width
            
            nxny_state = grid[nx, ny]
            
            influence += state_weights[xy_state][nxny_state] * weight_matrix[i, j]
    
    return influence

# Обработка подгруппы ячеек
def process_cells(cells, grid, new_grid):
    for x, y in cells:
        if grid[x, y] >= 0 and grid[x, y] < 3:
            growth_probability = calculate_influence_for_cell(grid, weight_matrix, x, y)
            if np.random.rand() < growth_probability:
                new_grid[x, y] += 1

# Генерация матрицы весов
radius = 2
weight_matrix = normalize_matrix(generate_weight_matrix(radius))

# Подготовка ячеек для обработки
all_cells = [(x, y) for x in range(grid_size) for y in range(grid_size)]

# Количество подгрупп
num_chunks = 20

# Перемешивание и разбиение на подгруппы
def shuffle_and_split_cells(all_cells, num_chunks):
    np.random.shuffle(all_cells)
    return np.array_split(all_cells, num_chunks)

# Изначальное разбиение
chunks = shuffle_and_split_cells(all_cells, num_chunks)
chunk_index = 0
new_grid = grid.copy()

# Анимация
fig, ax = plt.subplots(figsize=(6, 6))
im = ax.imshow(grid, cmap=cmap, vmin=0, vmax=5)
ax.axis("off")

def update(frame):
    global grid, chunk_index, new_grid, chunks

    process_cells(chunks[chunk_index], grid, new_grid)
    chunk_index += 1
    
    if chunk_index >= num_chunks:
        grid[:] = new_grid
        chunks = shuffle_and_split_cells(all_cells, num_chunks)
        chunk_index = 0

    im.set_array(new_grid)
    return [im]

ani = anim.FuncAnimation(fig, update, frames=100, interval=10, blit=True)

plt.show()
Загруженные файлы
Чтобы оставить комментарий, пожалуйста, войдите на сайт.