adaptive-ai/training/trainer_goblin.lua
2018-06-26 19:20:49 +02:00

305 lines
9.5 KiB
Lua

dofile("./trainer.lua")
dofile("../goblin/goblin.lua")
local trainer = adaptive_ai.trainer
local helper = adaptive_ai.helper
local calc = adaptive_ai.calc
local goblins = adaptive_ai.goblins
local file_tag = goblins.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_goblin(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_goblin(goblin, mother, father)
goblin.fear_height = goblins.stats.fear_height
goblin.pos = {x=random(1,40), z=random(1,40)}
goblins.setup(goblin, mother, father)
goblin.fitness = 0
trainer.console_log("Created new goblin: "..goblin.name)
end
local function reset_goblin(goblin, map)
goblin.food = {}
goblin.fitness = 0
goblin.hp = goblins.stats.hp_max
goblin.hunger = goblins.stats.hunger_max
goblin.pos.y = map[goblin.pos.x][goblin.pos.z][1]
while not goblin.pos.y do
goblin.pos = {x=random(1,40), z=random(1,40)}
goblin.pos.y = map[goblin.pos.x][goblin.pos.z][1]
end
end
local goblin_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.hunger = self.hunger + satiate
self.hp = self.hp + 1
end
end,
function(self, dtime) -- breed
self.fitness = self.fitness + 2
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_goblin(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]
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 then
player.action = decision
player.last_gene = gene
goblin_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 modifier = function(turn, max_turn) return (1 + turn/max_turn) end
local function turn_log_goblin(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 average: "..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: "..goblins.actions[player.action].."\t\tFocus: "..player.focus.."\n"
s = s.."Gene: "..(player.last_gene or "None").."\t\tFitness: "..truncate(player.fitness * modifier(turn, turn_max)).."\n\n"
s = s..print_terrain(map, player)
return s, 0.008
end
local function gen_log_goblin(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_goblin(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 10000
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_goblin,
fill_creature = fill_goblin,
reset_creature = reset_goblin,
decision_managers = goblin_decisions,
manage_creature = manage_goblin,
clone = goblins.clone,
map_checks = map_checks_goblin,
turn_log = turn_log_goblin,
gen_log = gen_log_goblin,
training_log = global_log,
fitness_threshold = 40,
fitness_modifier = modifier,
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()