Something went wrong. Try again.
[Archived] evolutionary genetic algorithm project and mob plugin for Minetest as part of a bachelor degree final thesis
Something went wrong. Try again.
8.2 kB · 306 lines
Lua
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
adaptive_ai = not adaptive_ai and {} or adaptive_ai
dofile("../helper.lua")dofile("./noise.lua")
trainer = {}
math.randomseed(os.time())
local random = math.randomlocal helper = adaptive_ai.helperlocal calc = adaptive_ai.calc
local ceil = math.ceillocal avg = calc.averagelocal round = calc.round
local adjacent = helper.adjacentlocal conditions = {}local gen_stats, popul, top_size -- condition valueslocal fill_creature, reset_creature, manage_creature, fill_cell, final_fitness -- condition functions
require "socket"
local function sleep(sec) socket.select(nil, nil, sec)end
local function console_log(s, delay) if conditions.output and s then print(s) if delay then sleep(delay) end endend
-- TERRAIN CONTROLlocal function valid(pos) if pos.x and pos.x < 1 or pos.x > conditions.dims.x then return false elseif pos.z and pos.z < 1 or pos.z > conditions.dims.z then return false else return true endend
-- Fill the training area-- Cell[1] stores the height. Cell[2] is the space to store custom metadatalocal function fill_terrain(map) console_log("Filling terrain...")
local p local max_x, max_y, max_z = conditions.dims.x, conditions.dims.y, conditions.dims.z local x_dimming, z_dimming = max_x/4, max_z/4
for i=1,max_x do for j=1,max_z do if map[i][j][1] == -1 then p = {x=i, z=j} p.y = round((SimplexNoise.Noise2D(p.x/x_dimming, p.z/z_dimming) + 1)*5) p.y = p.y + round(SimplexNoise.Noise2D(p.x/2, p.z/2)) p.y = p.y + round(SimplexNoise.Noise2D(p.x, p.z)/1.5) if valid(p) then if p.y > max_y then p.y = max_y elseif p.y < 1 then p.y = 1 end end map[i][j][1] = p.y
fill_cell(map[i][j]) end end end
console_log("Success filling terrain.\n")end
local function gen_terrain() console_log("Prepare new terrain...") local map = {} for i=1,40 do map[i] = {} for j=1,40 do map[i][j] = {-1} end end
SimplexNoise.seedP(os.time()) fill_terrain(map) return mapend
-- POPULATION CONTROLlocal function fill_popul(popul, cull) console_log("Populating...")
math.randomseed(os.time()) local creature local child_popul = {} if cull then creature = {} local j = random(1,cull) fill_creature(creature, popul[1], popul[j]) table.insert(child_popul, creature) end
local popul_size = conditions.popul_max - #popul while #child_popul <= popul_size do creature = {} if conditions.champ_breed and (not cull or random() < 0.05) then fill_creature(creature) else local i = random(1,cull) local j repeat j = random(1,cull) until i ~= j fill_creature(creature, popul[i], popul[j]) end table.insert(child_popul, creature) end
for i,child in ipairs(child_popul) do table.insert(popul, child) child_popul[i] = nil end
console_log("Success populating.\n")end
local function fitness_sort(a, b) return a.fitness > b.fitnessend
-- TURN MANAGEMENTlocal function move_creature(creature, n) local p = {} p.x = creature.pos.x + adjacent[n].x p.z = creature.pos.z + adjacent[n].z if valid(p) then p.y = terrain[p.x][p.z][1] end if p.y and p.y - creature.pos.y < creature.climb then creature.pos = p if creature.pos.y - p.y > creature.fear_height then creature.hp = creature.hp - ceil(creature.pos.y - p.y) end endend
local function manage_turn(creature, map, turn) creature.lifespan = turn local last = manage_creature(creature, map, conditions.dtime)
console_log(conditions.turn_log(creature, map, gen_stats, turn)) conditions.map_checks(creature, map, turn)
return lastend
local function manage_lifetime(creature, map) local last, turn, max_turn = false, 0, conditions.max_turn
while turn <= max_turn and not last do turn = turn + 1 last = manage_turn(creature, map, turn) end
if conditions.fitness_func then creature.fitness = conditions.fitness_func(creature, turn, max_turn) endend
local function manage_gen(num) gen_stats.num = num
if not output and not minetest then print("-- Start of generation "..num.." --") end
local s = string.rep("-", 42).."\n"..string.rep("-", 42).."\n\n" s = s.."*** START OF GEN "..num.." ***".."\n" console_log(s)
local map = gen_terrain() terrain = map
local creature for i=1,#popul do creature = popul[i] creature.id = i
reset_creature(creature, map) manage_lifetime(creature, map) end
table.sort(popul, fitness_sort)
local champ = popul[1]
gen_stats.avg = avg(popul, function(creature) return creature.fitness end) gen_stats.peak = champ.fitness
local i, elite = 1, gen_stats.top[1] while elite and i <= top_size do if champ.fitness > elite.fitness then gen_stats.top[i] = clone_creature(champ) elite = nil else i = i + 1 end end if not gen_stats.top[i] then gen_stats.top[i] = clone_creature(champ) end
if champ.fitness < conditions.fitness_threshold then popul = {} fill_popul(popul) else if champ.fitness > gen_stats.max_peak then gen_stats.gen = num gen_stats.max_peak = champ.fitness conditions.custom_stats(champ, gen_stats) end
local cull = ceil(#popul*conditions.cull_threshold) for i=cull,#popul do popul[i] = nil end
fill_popul(popul, cull) end
console_log(conditions.gen_log(num, gen_stats))end
-- TRAINING PROCESSlocal function train(options, offset) conditions = { dims = options.dims or {x=40, y=40, z=40}, output = options.output or false, gens = options.gens or 100, dtime = options.dtime or 1, turn_log = options.turn_log or function() end, gen_log = options.gen_log or function() end, training_log = options.training_log or function() end, map_checks = options.map_checks or function() end, fitness_threshold = options.fitness_threshold or 0, cull_threshold = options.cull_threshold or 3/5, champ_breed = options.champ_breed or true, max_turn = options.max_turn or 200, fitness_func = options.fitness_func, custom_stats = options.custom_stats, popul_max = options.popul_max or 150, }
gen_stats = options.gen_stats or {} gen_stats.num = 0 gen_stats.avg = 0 gen_stats.peak = 0 gen_stats.max_peak = 0 gen_stats.top = {}
popul = options.popul or {} fill_cell = options.fill_cell fill_creature = options.fill_creature reset_creature = options.reset_creature manage_creature = options.manage_creature clone_creature = options.clone or function(creature) return creature end top_size = options.top_size > 0 and options.top_size or 1
fill_popul(popul)
local last_gen = ofsset or 0
for i=1,conditions.gens do manage_gen(i + last_gen) end last_gen = gen_stats.num console_log(conditions.training_log(gen_stats))
if options.to_file then for i=1,top_size do s = popul[i]:to_lines() file = io.open(options.to_file..".txt", "a") for _,line in ipairs(s) do file:write(line, "\n") end file:close() end end
return popul, last_genend
trainer.train = traintrainer.valid = validtrainer.console_log = console_logtrainer.fitness_sort = fitness_sorttrainer.move_creature = move_creature
adaptive_ai.trainer = trainer