adaptive-ai/training/trainer_kobold.lua

309 lines
9.6 KiB
Lua

dofile("./trainer.lua")
dofile("../kobold/kobold.lua")
local trainer = adaptive_ai.trainer
local helper = adaptive_ai.helper
local calc = adaptive_ai.calc
local kobolds = adaptive_ai.kobolds
local file_tag = kobolds.trained_tag
local random = math.random
--local mod = math.fmod or math.mod
local move_creature = trainer.move_creature
local truncate = calc.truncate
local distance = calc.dist_euclidean
local pop = helper.pop
local valid = trainer.valid
local adjacent = helper.adjacent
local turn_max = 300
local popul_size = 100
local function fill_cell_kobold(cell, y)
if random() <= 0.01 then
-- Putting impassable obstacle
cell[1] = nil
else
if random() <= 0.02 then
-- Planting mushroom
cell[2] = true
else
cell[2] = false
end
end
end
local function fill_kobold(kobold, mother, father)
kobold.fear_height = kobolds.stats.fear_height
kobold.pos = {x=random(1,40), z=random(1,40)}
kobolds.setup(kobold, mother, father)
kobold.fitness = 0
trainer.console_log("Created new kobold: "..kobold.name)
end
local function reset_kobold(kobold, map)
kobold.food = {}
kobold.fitness = 0
kobold.hp = kobolds.stats.hp_max
kobold.hunger = kobolds.stats.hunger_max
kobold.pos.y = map[kobold.pos.x][kobold.pos.z][1]
while not kobold.pos.y do
kobold.pos = {x=random(1,40), z=random(1,40)}
kobold.pos.y = map[kobold.pos.x][kobold.pos.z][1]
end
end
local kobold_decisions = {
function(self, dtime) -- idle
end,
function(self, dtime) -- wander
local n = random(1,#adjacent)
move_creature(self, n)
self.hunger = self.hunger - 1
end,
function(self, dtime) -- eat
if self.food and #self.food > 0 then
local satiate = pop(self.food)
self:eat(satiate)
end
end,
function(self, dtime) -- breed
self.fitness = self.fitness + 1
self.hunger = self.hunger - 10
end,
function(self, dtime) -- move North
move_creature(self, 5)
end,
function(self, dtime) -- move South
move_creature(self, 4)
end,
function(self, dtime) -- move East
move_creature(self, 7)
end,
function(self, dtime) -- move West
move_creature(self, 2)
end,
function(self, dtime) -- move NE
move_creature(self, 8)
end,
function(self, dtime) -- move SE
move_creature(self, 6)
end,
function(self, dtime) -- move NW
move_creature(self, 3)
end,
function(self, dtime) -- move SW
move_creature(self, 1)
end,
}
local function manage_kobold(player, map, dtime)
if player.hp > 0 then
local neighbors = {}
local p, pos = {}, player.pos
for i=1,#adjacent do
p.x = pos.x + adjacent[i].x
p.z = pos.z + adjacent[i].z
if valid(p) and map[p.x][p.z][1] then
p.y = map[p.x][p.z][1] - player.pos.y
else
p.y = pos.y + player.climb + 1
end
neighbors[i] = {x=p.x, y=p.y, z=p.z}
--print("Neighbor "..i.." = "..p.y)
end
player.hunger = player.hunger - dtime
local decision, gene = player.decide(player, neighbors)
if decision and decision ~= -1 then
player.action = decision
player.last_gene = gene
kobold_decisions[decision](player, dtime)
else
if not player.action then player.action = 1 end
end
pos = player.pos
if map[pos.x][pos.z][2] then
table.insert(player.food, random(2,5))
map[pos.x][pos.z][2] = false
if player.closest_food
and player.closest_food.x == pos.x
and player.closest_food.z == pos.z then
player.closest_food = nil
end
end
if player.hunger < 0 then
player.hunger = 0
player.hp = player.hp - 1
end
player.fitness = player.fitness + player:score()
return false
else
return true
end
end
local function print_terrain(map, player)
local s = string.rep("#",42).."\n"
local line, cell
local p = player.pos
for i=1,40 do
line = "#"
for j=1,40 do
if p.x == i and p.z == j then
cell = "i"
elseif map[i][j][1] == nil then
cell = "O"
elseif map[i][j][1] == p.y and map[i][j][2] == true then
cell = "T"
elseif map[i][j][1] == p.y then
cell = ""
elseif map[i][j][1] - p.y == -1 then
cell = ""
elseif map[i][j][1] - p.y == 1 then
cell = ""
elseif map[i][j][1] - p.y < -1 then
cell = ""
elseif map[i][j][1] - p.y > 1 then
cell = " "
else
cell = "?"
end
line = line..cell
end
s = s..line.."#\n"
end
s = s..string.rep("#",42)
return s
end
local function final_fitness(player, turn, max_turn)
local c1 = turn
local c2 = player.fitness
local m = 3 -- * max_turn
return (m*c1 + c2)/100
end
local function turn_log_kobold(player, map, gen_stats, turn)
local s = "\n\nBest creature: "..gen_stats.champ.."\tBest creature fitness: "..truncate(gen_stats.max_peak).."\n"
s = s.."Best creature lifespan: "..(gen_stats.lifespan or "None").."\tBest creature gen: "..(gen_stats.gen or "None").."\n"
s = s.."Last gen avg: "..truncate(gen_stats.avg).."\t\tLast gen peak: "..truncate(gen_stats.peak).."\n\n"
s = s.."Current gen: "..gen_stats.num.."\t\tCurrent creature: "..player.id.."\n"
s = s.."Turn: "..(turn < 10 and "0" or "")..turn.."\t\tCreature name: "..player.name.."\n"
s = s.."Action: "..kobolds.actions[player.action].."\t\tFocus: "..player.focus.."\n"
s = s.."Gene: "..(player.last_gene or "None").."\t\tFitness: "..truncate(final_fitness(player, turn, turn_max)).."\n\n"
s = s..print_terrain(map, player)
return s, 0.008
end
local function gen_log_kobold(num, gen_stats)
local s = "\n\nBest creature: "..gen_stats.champ.."\tBest creature fitness: "..truncate(gen_stats.max_peak).."\n"
s = s.."Best creature lifespan: "..(gen_stats.lifespan or "None").."\tBest creature gen: "..(gen_stats.gen or "None").."\n"
s = s.."Last gen average: "..truncate(gen_stats.avg).."\t\tLast gen peak: "..truncate(gen_stats.peak).."\n\n"
end
local function map_checks_kobold(player, map)
if random() <= 0.05 then
map[random(1,40)][random(1,40)][2] = true
end
for i=1,40 do
for j=1,40 do
if map[i][j][1] and map[i][j][2] == true then
local pos = {x=i, y=map[i][j][1], z=j}
if not pos.y then error("No pos.y in "..i..","..j) end
if not player.pos.y then error("No player.pos.y") end
if player.closest_food and not player.closest_food.y then error("No player.closest_food.y") end
if not player.closest_food
or distance(player.pos, pos) < distance(player.pos, player.closest_food) then
player.closest_food = pos--{x=pos.x, y=pos.y, z=pos.z}
end
end
end
end
end
local function gen_stats_update(champ, gen_stats)
gen_stats.champ = champ.name
gen_stats.peak = champ.fitness
gen_stats.lifespan = champ.lifespan
end
local function global_log(gen_stats)
local s = string.rep("-", 42).."\n"..string.rep("-", 42).."\n\n"
s = s.."Last champion: "..gen_stats.champ.."\n"
s = s.."From generation "..gen_stats.gen.."\tTotal generations: "..gen_stats.num.."\n"
s = s.."Fitnes score: "..gen_stats.max_peak.."\tLifespan: "..(gen_stats.lifespan or "None").."\n"
s = s.."\n"..string.rep("-", 42).."\n"..string.rep("-", 42).."\n"
return s
end
local function train()
local input = false
local option = input and 0 or 300
local offset = 0
local population = {}
repeat
local gens_option = tonumber(option)
if gens_option and gens_option > 0 then
local elapsed = os.clock()
local stats = {
champ = "None",
lifespan = 0,
gen = 0,
}
population, offset = trainer.train({
dims = {x=40, y=10, z=40},
output = input,
gens = gens_option,
fill_cell = fill_cell_kobold,
fill_creature = fill_kobold,
reset_creature = reset_kobold,
decision_managers = kobold_decisions,
manage_creature = manage_kobold,
clone = kobolds.clone,
map_checks = map_checks_kobold,
turn_log = turn_log_kobold,
gen_log = gen_log_kobold,
training_log = global_log,
fitness_threshold = 40,
fitness_func = final_fitness,
max_turn = turn_max,
popul = population,
custom_stats = gen_stats_update,
gen_stats = stats,
to_file = file_tag,
top_size = 4,
}, offset)
if not minetest then
elapsed = os.clock() - elapsed
print(global_log(stats))
print()
print("Elapsed time: ", elapsed)
end
end
if minetest or not input then
option = "q"
else
print("Please enter number of gens to simulate:")
option = io.read()
end
until option == "q" or option == "Q"
end
train()