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.
11 kB · 375 lines
Lua
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
-- Example of Creature with Genetic Algorithm
-- Structure:-- n possible actions-- Single vector 1 value for each combination of inputs-- Each value in vector is 1..n to decide an action
local goblins = {}
local distance = adaptive_ai.calc.dist_euclideanlocal chat_log = adaptive_ai.chat_loglocal random = math.randomlocal abs = math.abslocal floor = math.floorlocal min = math.minlocal max = math.maxlocal round = adaptive_ai.calc.roundlocal chat_log = adaptive_ai.chat_log or function(s) end--print(s) end
local file_tag = {"GOBLIN", "END_GOBLIN"}local trained_tag = "trained_goblins"
local actions = { "idle", "wander", "eat", "breed", --"get_food", "move_N", "move_S", "move_E", "move_W", "move_NE", "move_SE", "move_NW", "move_SW"}
-- Considerations:-- Hunger/life = Good, bad (2 values)-- Direction of food = N, S, E, W, NE, NW, SE, SW (8 values)-- Has pocketed food = Yes, No (2 values)-- Walkable neighbors = walkable, non-walkable (2^4 values)-- Possible actions (12 values)-- Total values: matrix of (4*8*2*2^4) x 12 = 3072 possible values-- For each action:-- idle, wander, breed --> small reward at high satiation, punishment at low satiation-- eat --> reward in increase of hunger-- move --> punishment if can't move, reward if closer to food, punishment if drop too high-- x*12 = 1944
-- Global statslocal stats = { total_states = 64, gamma = 0.6, view_range = 20, jump = 5, hunger_max = 100, hp_max = 10, fear_height = 2}
local function get_genes(goblin) genes = {} for i = 1, stats.total_genes do genes[i] = random(1, #actions) end goblin.genes = genesend
local score = function(self) local total = 0 total = total + (stats.hunger_max - self.hunger)/stats.hunger_max total = total + 2 * (stats.hp_max - self.hp)/stats.hp_max return floor(total + 0.5)end
local function decide(self, neighbor_list) if random() < self.focus then return nil end local pos = self.pos
local hunger_level -- Hunger level if self.hunger > stats.hunger_max * 0.6 then hunger_level = 0 -- Good --chat_log("Hunger: good") elseif self.hunger > stats.hunger_max * 0.1 then hunger_level = 1 -- Bad --chat_log("Hunger: bad") else hunger_level = 2 -- Critical --chat_log("Hunger: critical") end --chat_log("Hunger "..hunger_level) hunger_level = hunger_level * 648 -- stats.total_genes/4
local dir_level local closest_food = self.closest_food if closest_food then if pos.z < closest_food.z then -- North if pos.x < closest_food.x then dir_level = 0 -- North-East elseif pos.x > closest_food.x then dir_level = 1 -- North-West else dir_level = 4 -- North end elseif pos.z < closest_food.z then if pos.x < closest_food.x then dir_level = 2 -- South-East elseif pos.x > closest_food.x then dir_level = 3 -- South-West else dir_level = 5 -- South end elseif pos.x < closest_food.x then dir_level = 6 -- East elseif pos.x > closest_food.x then dir_level = 7 -- West else dir_level = 4 end else dir_level = 5 --chat_log("No close food") end --chat_log("Dir "..dir_level) dir_level = dir_level * 81
local current_neighbor, node_height, neighbors_total = 0, 0, 0 --chat_log("Current: "..current_neighbor.."\tNeighbors "..neighbors_total) for i=1,3 do -- North node_height = (neighbor_list[i].y) + node_height end node_height = round(node_height/3) if node_height > self.climb then current_neighbor = 2 -- Climb elseif node_height < self.fear_height then current_neighbor = 1 -- Drop else current_neighbor = 0 end neighbors_total = current_neighbor --chat_log("Height: "..node_height.."\tCurrent: "..current_neighbor.."\tNeighbors "..neighbors_total)
node_height = 0 for _,i in ipairs({1,4,6}) do -- West node_height = (neighbor_list[i].y) + node_height end node_height = round(node_height/3) if node_height > self.climb then current_neighbor = 2 -- Climb elseif node_height < self.fear_height then current_neighbor = 1 -- Drop else current_neighbor = 0 end neighbors_total = neighbors_total * 3 + current_neighbor --chat_log("Height: "..node_height.."\tCurrent: "..current_neighbor.."\tNeighbors "..neighbors_total)
node_height = 0 for _,i in ipairs({3,5,8}) do -- East node_height = (neighbor_list[i].y) + node_height end node_height = round(node_height/3) if node_height > self.climb then current_neighbor = 2 -- Climb elseif node_height < self.fear_height then current_neighbor = 1 -- Drop else current_neighbor = 0 end neighbors_total = neighbors_total * 3 + current_neighbor --chat_log("Height: "..node_height.."\tCurrent: "..current_neighbor.."\tNeighbors "..neighbors_total)
node_height = 0 for i=6,8 do -- South node_height = (neighbor_list[i].y) + node_height end node_height = round(node_height/3) if node_height > self.climb then current_neighbor = 2 -- Climb elseif node_height < self.fear_height then current_neighbor = 1 -- Drop else current_neighbor = 0 end neighbors_total = neighbors_total * 3 + current_neighbor --chat_log("Height: "..node_height.."\tCurrent: "..current_neighbor.."\tNeighbors "..neighbors_total)
local gene = hunger_level + dir_level + neighbors_total + 1 chat_log("Gene: "..gene.."\tHunger id: "..hunger_level.."\tDirection id: "..dir_level.."\tNeighbors id: "..neighbors_total)
return self.genes[gene], geneend
local function breed(mother, father) local genes = {} for i = 1, stats.total_genes do if random() <= stats.mutation_chance then genes[i] = random(1, #actions) else genes[i] = random() <= 0.5 and mother.genes[i] or father.genes[i] end end
local focus if random() <= stats.mutation_chance then focus = random() else local fmax = max(mother.focus, father.focus) local fmin = min(mother.focus, father.focus) focus = fmin + random() * (fmax - fmin) end
return genes, focusend
-- Sugarlocal name_glyphs = { "a", "ka", "ta", "pa", "na", "sa", "i", "ki", "ch", "p", "ny", "si", "y", "k", "z", "f", "n", "s", "u", "ku", "tu", "pu", "nu", "su"}
local function new_name(count) local s = name_glyphs[math.random(1,#name_glyphs)] if math.random() < 0.2 and (count > 2) then return s end local n = count + 1 if n > 3 then return s end return s..new_name(n)end
local function uppercase(s) return s:sub(1,1):upper()..s:sub(2)end
local function correct_name(name) name = name:gsub("zs", "ts") name = name:gsub("pn", "m") name = name:gsub("kn", "ng") name = name:gsub("yy", "yi") name = name:gsub("ks", "kh") name = name:gsub("sch", "sh")
return nameend
local function get_name(goblin, mother) if goblin.firstname and goblin.midname and goblin.family and not goblin.name then goblin.name = goblin.firstname.." "..goblin.midname.." "..goblin.family return end if mother then if mother.family == "Sisanzik" then goblin.family = correct_name(mother.firstname.."nzik") else goblin.family = mother.family end goblin.midname = mother.firstname.."anuy" else goblin.midname = "" goblin.family = "Sisanzik" end
local name = correct_name(new_name(0)) goblin.firstname = uppercase(name)
goblin.name = goblin.firstname.." "..goblin.midname.." "..goblin.familyend
local function create(self, mother, father) if not self.name then get_name(self, mother) end if not self.genes then if mother and father then local genes, focus = breed(mother, father) self.genes = genes self.focus = focus else get_genes(self) self.focus = random() end end
self.food = {} self.climb = 2
self.hunger = self.hunger or stats.hunger_max self.hp = self.hp or stats.hp_max self.fear_height = self.fear_height or stats.fear_height self.fitness = self.fitness or 0
return selfend
local function clone(goblin, cloned) cloned = cloned or {}
for k, v in pairs(goblin) do if k ~= "genes" then cloned[k] = v end end cloned.genes = {} for i, v in ipairs(goblin.genes) do cloned.genes[i] = v end
return clonedend
local function to_lines(self) local s = {} s[1] = file_tag[1] s[2] = "genes" s[3] = "{" for _,gene in ipairs(self.genes) do s[3] = s[3]..gene.."," end s[3] = s[3].."}" s[4] = "focus" s[5] = tostring(self.focus) s[6] = "firstname" s[7] = "\""..self.firstname.."\"" s[8] = "midname" s[9] = "\""..self.midname.."\"" s[10] = "family" s[11] = "\""..self.family.."\"" if self.tribe_id then table.insert(s, "tribe_id") table.insert(s, self.tribe_id) end if self.food then table.insert(s, "food") local s_food = "{" for _,f in ipairs(self.food) do s_food = s_food..f.."," end table.insert(s, s_food.."}") end table.insert(s, file_tag[2])
return send
local function setup(self, mother, father) create(self, mother, father) self.decide = decide self.score = score self.to_lines = to_linesend
local function from_lines(s) goblin = {} if s[1] ~= file_tag[1] or s[#s] ~= file_tag[2] then error("Kobold expected. Not a goblin") end for i=2,#s - 1,2 do loadstring("goblin[\""..s[i].."\"] = "..s[i+1])() end if type(goblin.genes) ~= "table" then error("Kobold genes are not a table, but a "..type(goblin.genes)) end
create(goblin)
return goblinend
goblins.stats = statsgoblins.actions = actionsgoblins.create = creategoblins.setup = setupgoblins.clone = clonegoblins.file_tag = file_taggoblins.trained_tag = trained_taggoblins.from_lines = from_linesgoblins.decide = decidegoblins.score = scoregoblins.to_lines = goblins.to_lines
adaptive_ai.goblins = goblins