Initial commit

This commit is contained in:
Vylion 2018-06-26 19:20:49 +02:00
commit 64a609ebe8
25 changed files with 5664 additions and 0 deletions

76
api.lua Normal file
View file

@ -0,0 +1,76 @@
---------------------
-- ADAPTIVE AI API --
---------------------
local random = math.random
local ai_pathfinding = adaptive_ai.ai_pathfinding
local ai_manager = adaptive_ai.ai_manager
local ai_on_step = adaptive_ai.ai_on_step
adaptive_ai.breeding = {}
function adaptive_ai:register_ai(name, def)
adaptive_ai.breeding[name] = {
asexual = def.asexual or false,
breed = def.breed
}
if not def.do_custom then
if not def.ai_manager then
def.ai_manager = adaptive_ai.ai_manager
end
def.do_custom = function(self, dtime)
--chat_log("do custom "..())
if def.action_managers then
-- there's a manager function per action
if self.action then
--chat_log("action managers in action")
def.action_managers[self.action](self, dtime)
end
end
local skip = def.ai_manager(self, dtime)
if skip == false then return false end
ai_on_step(self, dtime)
return false
end
end
mobs:register_mob(name, def)
end
--[[
def (adaptive_ai.api related):
asexual :: bool
breed :: function(parent) if asexual
breed :: function(mother, father) if not asexual
actions :: list of strings
action_managers :: list of functions (index corresponds with action index)
camp :: if there's a node the creature is bound to (like a spawner)
Notes:
do_custom API functionality from mobs_redo is respected
States from mobs_redo are overwritten with internal adaptive_ai state management
-- stand (stationary idle)
-- wander (equivalent to mob_redo's idle + walk_chance)
-- move (towards or away from a target - with or without pathfinding)
Actions are custom mob states with a different name to avoid conflict with previous states
--]]
function adaptive_ai:place_ai(def)
local name = def.name
--local setup = def.setup
local pos = def.pos
--local height = minetest.registered_entities[name].jump_height
local obj = minetest.add_entity(pos, name)
local ent = obj and obj:get_luaentity() or nil
if def.setup and ent then
def.setup(ent)
end
return ent or nil
end

595
api.md Normal file
View file

@ -0,0 +1,595 @@
Adaptive AI API
===============
---
This is a simple platform for development and experimentation of simple adaptive
AI, in the form of a Minetest mod. This mod allows for the creation of mobs that
have some learning algorithm (like a genetic algorithm), as well as bring a
couple examples of learning mobs.
I decided to build this on top of some of the Mob Redo API. I wanted to have a
custom on_step function and skip some parts of the original API, yet keeping a
few auxiliar functions like do_jump, but since most of those auxiliar functions
are defined as local, many were copied here. These are accesible in
`adaptive_ai.helper.mobs`.
Registering Adaptive Mobs
-------------------------
---
To register an adaptive mob there's the following function:
adaptive_ai:register_ai(name, def)
This function calls the `mobs:register_mob(name, def)` function from Mobs Redo,
so all the def parameters from Mobs Redo API work here. In addition, there are
the following parameters:
- **asexual:** type bool. If true, the creature breeds asexually with only one
parent. If false, it breeds with 2 parents.
- **breed:** type `function(parent)` if asexual, or `function(mother, father)`
if not.
- **actions:** list of strings with the name of the possible mob actions.
- **action_managers:** list of `function(self, dtime)` that define the behavior
for each action of the corresponding index. *(More on this later)*
- **ai_manager (Optional):** type `function(self, dtime)` that is in charge of
checking the environment and deciding on the next `self.action` of the creature.
If it returns `false`, it skips the rest of the `on_step` functions (but not the
`action_managers`).
- **Camp (Optional):** if the mob is bound to a node (like a spawner, or a
"town square") this will be the default node if it is not provided in the func-
tion that spawns a new creature of this type.
Adaptive Mob Behavior
---------------------
---
There is no default behavior. Anyone using this API is expected to implement
their own mob behavior, using at least `action` and `action_managers`. However,
there is a default behavior *manager*, and works this way:
- At each game step, the manager will look at `self.action`, and then call a
specific function from the `action_managers` using the `self.action` value as an
index. This `self.action` of the creature should be set at the end of an
`action_managers` function if one wishes to change the next action taken. These
actions are the mob's "external" states.
- Then it will manage the movement behavior. For that, it uses a set of "inter-
nal" states. The current one is stored in `self.state` as one of `"stand"`,
`"wander"`, `"move"`; they replace Mobs Redo states. They do the following:
- - **Stand:** Equivalent to Mobs Redo `idle` state. The creature stands in
place, doing nothing.
- - **Wander:** Equivalent to Mobs Redo `walk` state. The creature walks aim-
lessly.
- - **Move:** The creature moves respectively to a target. This is used for
pathfinding, and can be used for either following or attacking. If
`self.target.dir` is `"towards"`, the creature will move towards the target. If
it's `"away"`, the creature will try to get away (equivalent to the running away
behavior of Mobs Redo).
The value stored in `self.action` can be either an integer or a string, but it
must be the index used to access the corresponding `action_managers` function.
If the pathfinding target is an object (a player or another entity), then it
must be stored in `self.target.obj`; else, the target position must be stored in
`self.target.pos`. And don't forget to store either `"towards"` or `"away"` in
`self.target.dir` whenever you do so.
Custom Mob Behavior
-------------------
---
If you wish to make your own behavior, you should implement a function with pa-
rameters `(self, dtime)` and save it in a value called `ai_manager` when regis-
tering the AI. This function should manage the different values of `self.state`,
as well as moving the environment checks to decide the next action from the
`action_managers` to this function. If using pathfinding and following a target
that is an object, it should also update `self.target.pos` with the position of
`self.target.obj` at the very beginning.
The default `ai_manager` is available in `adaptive_ai.ai_manager` to be called
from your own `ai_manager` function in case you want the default behavior as
part of yours without needing to retype it all. Auxiliar functions like
`do_jump` (checks if it is necessary to jump) in `adaptive_ai.helper.mobs`; and
`ai_pathfinding` (copied and edited from `smart_mobs` in Mobs Redo, does path-
finding from creature to `self.target.pos` if `self.target.dir` is `"towards"`)
are available too.
Like in Mobs Redo, returning false in your custom `ai_manager` will skip the
rest of the `on_step` auxiliar functions. By default, both `action_managers` and
`ai_manager` are used; if you wish not to do so, make a function called
`do_custom` instead, which will outright skip this API and be passed straight to
the Mobs Redo API.
To Do
-----
---
This API is incomplete; I have yet to port all of the features related to com-
bat, mob ownership, etc. In case you wish to use non-implemented Mobs Redo pa-
rameters, like `runaway_from`, you can use your own `ai_manager` that uses them
(together with the default `adaptive_ai.ai_manager`, if you want).
I also want to make the adaptive AI examples to be optional in the future, maybe
through `minetest.conf` or making a separate mod that uses this API (like Mobs
Redo does with its animals and monsters).
---
Bellow is a copy of the Mobs Redo API (omitting all the features that need to be
but have not yet been reimplemented, and editing the ones modified).
---
---
Mobs Redo API
=============
---
Welcome to the world of mobs in minetest and hopefully an easy guide to defining
your own mobs and having them appear in your worlds.
Registering Mobs
----------------
---
To register a mob and have it ready for use requires the following function:
mobs:register_mob(name, definition)
The 'name' of a mob usually starts with the mod name it's running from followed
by it's own name e.g.
"mobs_monster:sand_monster" or "mymod:totally_awesome_beast"
... and the 'definition' is a table which holds all of the settings and
functions needed for the mob to work properly which contains the following:
'nametag' contains the name which is shown above mob.
'hp_min' has the minimum health value the mob can spawn with.
'hp_max' has the maximum health value the mob can spawn with.
'armor' holds strength of mob, 100 is normal, lower is more powerful
and needs more hits and better weapons to kill.
'walk_velocity' is the speed that your mob can walk around.
'run_velocity' is the speed your mob can run with, usually when attacking.
'walk_chance' has a 0-100 chance value your mob will walk from standing,
set to 0 for jumping mobs only.
'jump' when true allows your mob to jump updwards.
'jump_height' holds the height your mob can jump, 0 to disable jumping.
'stepheight' height of a block that your mob can easily walk up onto,
defaults to 1.1.
'fly' when true allows your mob to fly around instead of walking.
'fly_in' holds the node name that the mob flies (or swims) around
in e.g. "air" or "default:water_source".
'view_range' how many nodes in distance the mob can see.
'damage' how many health points the mob does to a player or another
mob when melee attacking.
'knock_back' when true has mobs falling backwards when hit, the greater
the damage the more they move back.
'fear_height' is how high a cliff or edge has to be before the mob stops
walking, 0 to turn off height fear.
'fall_speed' has the maximum speed the mob can fall at, default is -10.
'fall_damage' when true causes falling to inflict damage.
'water_damage' holds the damage per second infliced to mobs when standing in
water.
'lava_damage' holds the damage per second inflicted to mobs when standing
in lava or fire.
'light_damage' holds the damage per second inflicted to mobs when it's too
bright (above 13 light).
'suffocation' when true causes mobs to suffocate inside solid blocks.
'floats' when set to 1 mob will float in water, 0 has them sink.
'follow' mobs follow player when holding any of the items which appear
on this table, the same items can be fed to a mob to tame or
breed e.g. {"farming:wheat", "default:apple"}
'reach' is how far the mob will stop from target in pathfinding.
'blood_amount' contains the number of blood droplets to appear when
mob is hit.
'blood_texture' has the texture name to use for droplets e.g.
"mobs_blood.png", or table {"blood1.png", "blood2.png"}
'pathfinding' set to 1 for mobs to use pathfinder feature, set to 2
so they can also build/break also (only works when
'mobs_griefing' in minetest.conf is not false).
'immune_to' is a table that holds specific damage when being hit by
certain items e.g.
{"default:sword_wood", 0} -- causes no damage.
{"default:gold_lump", -10} -- heals by 10 health points.
{"default:coal_block", 20} -- 20 damage when hit on head with coal blocks.
'makes_footstep_sound' when true you can hear mobs walking.
'sounds' this is a table with sounds of the mob
'distance' maximum distance sounds can be heard, default is 10.
'random' random sound that plays during gameplay.
'war_cry' special mob shout.
'damage' sound heard when mob is hurt.
'death' played when mob is killed.
'jump' played when mob jumps.
'fuse' sound played when mob explode timer starts.
'explode' sound played when mob explodes.
'drops' table of items that are dropped when mob is killed, fields are:
'name' name of item to drop.
'chance' chance of drop, 1 for always, 2 for 1-in-2 chance etc.
'min' minimum number of items dropped.
'max' maximum number of items dropped.
'visual' holds the look of the mob you wish to create:
'cube' looks like a normal node
'sprite' sprite which looks same from all angles.
'upright_sprite' flat model standing upright.
'wielditem' how it looks when player holds it in hand.
'mesh' uses separate object file to define mob.
'visual_size' has the size of the mob, defaults to {x = 1, y = 1}
'collisionbox' has the box in which mob can be interacted with the
world e.g. {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
'selectionbox' has the box in which player can interact with mob
'textures' holds a table list of textures to be used for mob, or you
could use multiple lists inside another table for random
selection e.g. { {"texture1.png"}, {"texture2.png"} }
'child_texture' holds the texture table for when baby mobs are used.
'gotten_texture' holds the texture table for when self.gotten value is
true, used for milking cows or shearing sheep.
'mesh' holds the name of the external object used for mob model
e.g. "mobs_cow.b3d"
'gotten_mesh" holds the name of the external object used for when
self.gotten is true for mobs.
'rotate' custom model rotation, 0 = front, 90 = side, 180 = back,
270 = other side.
'double_melee_attack' when true has the api choose between 'punch' and
'punch2' animations.
'animation' holds a table containing animation names and settings for use with mesh models:
'stand_start' start frame for when mob stands still.
'stand_end' end frame of stand animation.
'stand_speed' speed of animation in frames per second.
'walk_start' when mob is walking around.
'walk_end'
'walk_speed'
'run_start' when a mob runs.
'run_end'
'run_speed'
'fly_start' when a mob is flying.
'fly_end'
'fly_speed'
'die_start' death animation
'die_end'
'die_speed'
'die_loop' when set to false stops the animation looping.
Using '_loop = false' setting will stop any of the above animations from
looping.
'speed_normal' is used for animation speed for compatibility with some
older mobs.
Node Replacement
----------------
---
Mobs can look around for specific nodes as they walk and replace them to mimic
eating.
'replace_what' group of items to replace e.g.
{"farming:wheat_8", "farming:carrot_8"}
or you can use the specific options of what, with and
y offset by using this instead:
{
{"group:grass", "air", 0},
{"default:dirt_with_grass", "default:dirt", -1}
}
'replace_with' replace with what e.g. "air" or in chickens case "mobs:egg"
'replace_rate' how random should the replace rate be (typically 10)
'replace_offset' +/- value to check specific node to replace
'on_replace(self, pos, oldnode, newnode)' is called when mob is about to
replace a node.
'self' ObjectRef of mob
'pos' Position of node to replace
'oldnode' Current node
'newnode' What the node will become after replacing
If false is returned, the mob will not replace the node.
By default, replacing sets self.gotten to true and resets the object
properties.
Custom Definition Functions
---------------------------
---
Along with the above mob registry settings we can also use custom functions to
enhance mob functionality and have them do many interesting things:
'on_die' a function that is called when the mob is killed the
parameters are (self, pos)
'on_rightclick' its same as in minetest.register_entity()
'on_blast' is called when an explosion happens near mob when using TNT
functions, parameters are (object, damage) and returns
(do_damage, do_knockback, drops)
'on_spawn' is a custom function that runs on mob spawn with 'self' as
variable, return true at end of function to run only once.
'after_activate' is a custom function that runs once mob has been activated
with these paramaters (self, staticdata, def, dtime)
'on_breed' called when two similar mobs breed, paramaters are
(parent1, parent2) objects, return false to stop child from
being resized and owner/tamed flags and child textures being
applied. Function itself must spawn new child mob.
'on_grown' is called when a child mob has grown up, only paramater is
(self).
'do_punch' called when mob is punched with paramaters (self, hitter,
time_from_last_punch, tool_capabilities, direction), return
false to stop punch damage and knockback from taking place.
'custom_attack' when set this function is called instead of the normal mob
melee attack, parameters are (self, to_attack).
'on_die' a function that is called when mob is killed (self, pos)
'do_custom' a custom function that is called every tick while mob is
active and which has access to all of the self.* variables
e.g. (self.health for health or self.standing_in for node
status), return with 'false' to skip remainder of mob API.
Internal Variables
------------------
---
The mob api also has some preset variables and functions that it will remember
for each mob.
'self.health' contains current health of mob (cannot exceed
self.hp_max)
'self.texture_list' contains list of all mob textures
'self.child_texture' contains mob child texture when growing up
'self.base_texture' contains current skin texture which was randomly
selected from textures list
'self.gotten' this is used for obtaining milk from cow and wool from
sheep
'self.horny' when animal fed enough it is set to true and animal can
breed with same animal
'self.hornytimer' background timer that controls breeding functions and
mob childhood timings
'self.child' used for when breeding animals have child, will use
child_texture and be half size
'self.nametag' contains the name of the mob which it can show above
Spawning Mobs in World
----------------------
---
mobs:register_spawn(name, nodes, max_light, min_light, chance,
active_object_count, max_height, day_toggle)
mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
These functions register a spawn algorithm for the mob. Without this function
the call the mobs won't spawn.
'name' is the name of the animal/monster
'nodes' is a list of nodenames on that the animal/monster can
spawn on top of
'neighbors' is a list of nodenames on that the animal/monster will
spawn beside (default is {"air"} for
mobs:register_spawn)
'max_light' is the maximum of light
'min_light' is the minimum of light
'interval' is same as in register_abm() (default is 30 for
mobs:register_spawn)
'chance' is same as in register_abm()
'active_object_count' number of this type of mob to spawn at one time inside
map area
'min_height' is the minimum height the mob can spawn
'max_height' is the maximum height the mob can spawn
'day_toggle' true for day spawning, false for night or nil for
anytime
'on_spawn' is a custom function which runs after mob has spawned
and gives self and pos values.
A simpler way to handle mob spawns has been added with the mobs:spawn(def)
command which uses above names to make settings clearer:
mobs:spawn({name = "mobs_monster:tree_monster",
nodes = {"group:leaves"},
max_light = 7,
})
For each mob that spawns with this function is a field in `mobs.spawning_mobs`.
It tells if the mob should spawn or not. Default is true. So other mods can
only use the API of this mod by disabling the spawning of the default mobs in
this mod.
mobs:spawn_abm_check(pos, node, name)
This global function can be changed to contain additional checks for mobs to
spawn e.g. mobs that spawn only in specific areas and the like. By returning
true the mob will not spawn.
'pos' holds the position of the spawning mob
'node' contains the node the mob is spawning on top of
'name' is the name of the animal/monster
Spawn Eggs
----------
---
mobs:register_egg(name, description, background, addegg, no_creative)
This function registers a spawn egg which can be used by admin to properly spawn in a mob.
'name' this is the name of your new mob to spawn e.g. "mob:sheep"
'description' the name of the new egg you are creating e.g. "Spawn Sheep"
'background' the texture displayed for the egg in inventory
'addegg' would you like an egg image in front of your texture (1 = yes,
0 = no)
'no_creative' when set to true this stops spawn egg appearing in creative
mode for destructive mobs like Dungeon Masters.
Explosion Function
------------------
---
mobs:explosion(pos, radius) -- DEPRECATED!!! use mobs:boom() instead
---
mobs:boom(self, pos, radius)
'self' mob entity
'pos' centre position of explosion
'radius' radius of explosion (typically set to 3)
This function generates an explosion which removes nodes in a specific radius
and damages any entity caught inside the blast radius. Protection will limit
node destruction but not entity damage.
Capturing Mobs
--------------
---
mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
force_take, replacewith)
This function is generally called inside the `on_rightclick` section of the mob
api code, it provides a chance of capturing the mob by hand, using the net or
lasso items, and can also have the player take the mob by force if tamed and
replace with another item entirely.
'self' mob information
'clicker' player information
'chance_hand' chance of capturing mob by hand (1 to 100) 0 to disable
'chance_net' chance of capturing mob using net (1 to 100) 0 to disable
'chance_lasso' chance of capturing mob using magic lasso (1 to 100) 0 to
disable
'force_take' take mob by force, even if tamed (true or false)
'replacewith' once captured replace mob with this item instead (overrides
new mob eggs with saved information)
Feeding and Taming/Breeding
---------------------------
---
mobs:feed_tame(self, clicker, feed_count, breed, tame)
This function allows the mob to be fed the item inside `self.follow` be it apple,
wheat or whatever a set number of times and be tamed or bred as a result.
Will return true when mob is fed with item it likes.
'self' mob information
'clicker' player information
'feed_count' number of times mob must be fed to tame or breed
'breed' true or false stating if mob can be bred and a child created
afterwards
'tame' true or false stating if mob can be tamed so player can pick
them up
Protecting Mobs
---------------
---
mobs:protect(self, clicker)
This function can be used to right-click any tamed mob with `mobs:protector` item,
this will protect the mob from harm inside of a protected area from other
players. Will return true when mob right-clicked with `mobs:protector` item.
'self' mob information
'clicker' player information
Riding Mobs
-----------
---
Mobs can be ridden by players and the following shows its functions and
usage:
mobs:attach(self, player)
This function attaches a player to the mob so it can be ridden.
'self' mob information
'player' player information
---
mobs:detach(player, offset)
This function will detach the player currently riding a mob to an offset
position.
'player' player information
'offset' position table containing offset values
---
mobs:drive(self, move_animation, stand_animation, can_fly, dtime)
This function allows an attached player to move the mob around and animate it at
same time.
'self' mob information
'move_animation' string containing movement animation e.g. "walk"
'stand_animation' string containing standing animation e.g. "stand"
'can_fly' if true then jump and sneak controls will allow mob to fly
up and down
'dtime' tick time used inside drive function
---
mobs:fly(self, dtime, speed, can_shoot, arrow_entity, move_animation, stand_animation)
This function allows an attached player to fly the mob around using directional
controls.
'self' mob information
'dtime' tick time used inside fly function
'speed' speed of flight
'can_shoot' true if mob can fire arrow (sneak and left mouse button
fires)
'arrow_entity' name of arrow entity used for firing
'move_animation' string containing name of pre-defined animation e.g. "walk"
or "fly" etc.
'stand_animation' string containing name of pre-defined animation e.g.
"stand" or "blink" etc.
Note: animation names above are from the pre-defined animation lists inside mob
registry without extensions.
---
mobs:set_animation(self, name)
This function sets the current animation for mob, defaulting to "stand" if not
found.
'self' mob information
'name' name of animation
Certain variables need to be set before using the above functions:
'self.v2' toggle switch used to define below values for the
first time
'self.max_speed_forward' max speed mob can move forward
'self.max_speed_reverse' max speed mob can move backwards
'self.accel' acceleration speed
'self.terrain_type' integer containing terrain mob can walk on
(1 = water, 2 or 3 = land)
'self.driver_attach_at' position offset for attaching player to mob
'self.driver_eye_offset' position offset for attached player view
'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1}
External Settings for "minetest.conf"
------------------------------------
---
'enable_damage' if true monsters will attack players (default is true)
'only_peaceful_mobs' if true only animals will spawn in game (default is
false)
'mobs_disable_blood' if false blood effects appear when mob is hit (default
is false)
'mobs_spawn_protected' if set to false then mobs will not spawn in protected
areas (default is true)
'remove_far_mobs' if true then untamed mobs that are outside players
visual range will be removed (default is true)
'mobname' can change specific mob chance rate (0 to disable) and
spawn number e.g. mobs_animal:cow = 1000,5
'mob_difficulty' sets difficulty level (health and hit damage
multiplied by this number), defaults to 1.0.
'mob_show_health' if false then punching mob will not show health status
(true by default)
'mob_chance_multiplier' multiplies chance of all mobs spawning and can be set
to 0.5 to have mobs spawn more or 2.0 to spawn less.
e.g. 1 in 7000 * 0.5 = 1 in 3500 so better odds of
spawning.
'mobs_spawn' if false then mobs no longer spawn without spawner or
spawn egg.
'mobs_drop_items' when false mobs no longer drop items when they die.
'mobs_griefing' when false mobs cannot break blocks when using either
pathfinding level 2, replace functions or mobs:boom
function.
Players can override the spawn chance for each mob registered by adding a line
to their minetest.conf file with a new value, the lower the value the more each
mob will spawn e.g.
mobs_animal:sheep_chance 11000
mobs_monster:sand_monster_chance 100

476
api_mobs.lua Normal file
View file

@ -0,0 +1,476 @@
-- Imported and modified API functions from Mobs_Redo
-- WIP; pathfinding is not functional
-- Localize math functions
local pi = math.pi
local square = math.sqrt
local sin = math.sin
local cos = math.cos
local abs = math.abs
local min = math.min
local max = math.max
local atann = math.atan
local random = math.random
local floor = math.floor
local atan = function(x)
if not x or x ~= x then
--error("atan bassed NaN")
return 0
else
return atann(x)
end
end
local helper = adaptive_ai.helper
local set_yaw = helper.mobs.set_yaw
local do_jump = helper.mobs.do_jump
local set_velocity = helper.mobs.set_velocity
local is_at_cliff = helper.mobs.is_at_cliff
local flight_check = helper.mobs.flight_check
local get_distance = helper.mobs.get_distance
local mob_sound = helper.mobs.mob_sound
local do_env_damage = helper.mobs.do_env_damage
local replace = helper.mobs.replace
local flop = helper.mobs.flop
local runaway_from = helper.mobs.runaway_from
local set_animation = helper.mobs.set_animation
local mobs_griefing = helper.mobs.mobs_griefing
local los_switcher = false
local height_switcher = false
local chat_log = adaptive_ai.chat_log
-- Edited from mob_redo's smart_mobs pathfinding function
-- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
local ai_pathfinding = function(self, s, p, dist, dtime)
chat_log("pathfinding tete")
local s1 = self.path.lastpos
local target_pos = self.target.pos
-- is it becoming stuck?
if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
self.path.stuck_timer = self.path.stuck_timer + dtime
else
self.path.stuck_timer = 0
end
self.path.lastpos = {x = s.x, y = s.y, z = s.z}
local use_pathfind = false
local has_lineofsight = minetest.line_of_sight(
{x = s.x, y = (s.y) + .5, z = s.z},
{x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z},
.2)
-- im stuck, search for path
if not has_lineofsight then
if los_switcher == true then
use_pathfind = true
los_switcher = false
end -- cannot see target!
else
if los_switcher == false then
los_switcher = true
use_pathfind = false
minetest.after(1, function(self)
if has_lineofsight then self.path.following = false end
end, self)
end -- can see target!
end
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if has_lineofsight then self.path.following = false end
end, self)
end
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
use_pathfind = true
self.path.stuck_timer = 0
minetest.after(1, function(self)
if has_lineofsight then self.path.following = false end
end, self)
end
if abs(vector.subtract(s,target_pos).y) > self.stepheight then
if height_switcher then
use_pathfind = true
height_switcher = false
end
else
if not height_switcher then
use_pathfind = false
height_switcher = true
end
end
if use_pathfind then
-- lets try find a path, first take care of positions
-- since pathfinder is very sensitive
local sheight = self.collisionbox[5] - self.collisionbox[2]
-- round position to center of node to avoid stuck in walls
-- also adjust height for player models!
s.x = floor(s.x + 0.5)
-- s.y = floor(s.y + 0.5) - sheight
s.z = floor(s.z + 0.5)
local ssight, sground = minetest.line_of_sight(s, {x = s.x, y = s.y - 4, z = s.z}, 1)
-- determine node above ground
if not ssight then
s.y = sground.y + 1
end
local p1 = self.target.pos
p1.x, p1.y, p1.z = floor(p1.x + 0.5), floor(p1.y + 0.5), floor(p1.z + 0.5)
local dropheight = 6
if self.fear_height ~= 0 then dropheight = self.fear_height end
self.path.way = minetest.find_path(s, p1, self.view_range, self.stepheight, dropheight, "A*")
self.state = "move"
self.target.dir = "towards"
-- no path found, try something else
if not self.path.way then
self.path.following = false
-- lets make way by digging/building if not accessible
if self.pathfinding == 2 and mobs_griefing then
-- is player higher than mob?
if s.y < p1.y then
-- build upwards
if not minetest.is_protected(s, "") then
local ndef1 = minetest.registered_nodes[self.standing_in]
if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
minetest.set_node(s, {name = mobs.fallback_node})
end
end
local sheight = math.ceil(self.collisionbox[5]) + 1
-- assume mob is 2 blocks high so it digs above its head
s.y = s.y + sheight
-- remove one block above to make room to jump
if not minetest.is_protected(s, "") then
local node1 = node_ok(s, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.set_node(s, {name = "air"})
minetest.add_item(s, ItemStack(node1))
end
end
s.y = s.y - sheight
self.object:setpos({x = s.x, y = s.y + 2, z = s.z})
else -- dig 2 blocks to make door toward player direction
local yaw1 = self.object:get_yaw() + pi / 2
local p1 = {x = s.x + cos(yaw1), y = s.y, z = s.z + sin(yaw1)}
if not minetest.is_protected(p1, "") then
local node1 = node_ok(p1, "air").name
local ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.add_item(p1, ItemStack(node1))
minetest.set_node(p1, {name = "air"})
end
p1.y = p1.y + 1
node1 = node_ok(p1, "air").name
ndef1 = minetest.registered_nodes[node1]
if node1 ~= "air"
and node1 ~= "ignore"
and ndef1
and not ndef1.groups.level
and not ndef1.groups.unbreakable
and not ndef1.groups.liquid then
minetest.add_item(p1, ItemStack(node1))
minetest.set_node(p1, {name = "air"})
end
end
end
end
-- will try again in 2 second
self.path.stuck_timer = stuck_timeout - 2
-- frustration! cant find the damn path :(
mob_sound(self, self.sounds.random)
else
-- yay I found path
mob_sound(self, self.sounds.war_cry)
set_velocity(self, self.walk_velocity)
-- follow path now that I have it
self.path.following = true
end
end
end
-- Pieced together from mob_redo's state management functions
-- self.target.pos
-- self.target.dir = "towards" or "away"
local ai_manager = function(self, dtime)
if self.action_managers then
-- there's a manager function per action
if self.action then
--chat_log("action managers in action")
self.action_managers[self.action](self, dtime)
end
end
--chat_log(self.state == "manager on")
if self.target and self.target.obj then
self.target.pos = self.target.obj:get_pos()
end
local yaw = self.object:get_yaw() or 0
local s = self.object:get_pos()
local lp = nil
if self.state == "stand" then
--chat_log("I am idle standing")
if random(1, 4) == 1 then
local objs = minetest.get_objects_inside_radius(s, 3)
for n = 1, #objs do
if objs[n]:is_player() then
lp = objs[n]:get_pos()
break
end
end
-- look at any players nearby, otherwise turn randomly
if lp then
local vec = {x = lp.x - s.x, z = lp.z - s.z}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
else
yaw = yaw + random(-0.5, 0.5)
end
yaw = set_yaw(self, yaw, 8)
end
if self.walk_chance ~= 0
and self.facing_fence ~= true
and random(1, 100) <= self.walk_chance
and is_at_cliff(self) == false then
set_velocity(self, self.walk_velocity)
self.state = "wander"
set_animation(self, "walk")
else
set_velocity(self, 0)
set_animation(self, "stand")
end
elseif self.state == "wander" then
--chat_log("I am idle wandering")
-- is there something I need to avoid?
local avoid = {}
if self.water_damage > 0 then table.insert(avoid, "group:water") end
if self.lava_damage > 0 then table.insert(avoid, "group:lava") end
if #avoid > 0 then lp = minetest.find_node_near(s, 1, avoid) end
if lp then
-- if mob in water or lava then look for land
if (self.lava_damage and minetest.registered_nodes[self.standing_in].groups.lava)
or (self.water_damage and minetest.registered_nodes[self.standing_in].groups.water) then
lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone", "group:sand", node_ice, node_snowblock})
-- did we find land?
if lp then
local vec = {x = lp.x - s.x, z = lp.z - s.z}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
-- look towards land and jump/move in that direction
yaw = set_yaw(self, yaw, 6)
do_jump(self)
set_velocity(self, self.walk_velocity)
else
yaw = yaw + random(-0.5, 0.5)
end
else
local vec = {x = lp.x - s.x, z = lp.z - s.z}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
end
yaw = set_yaw(self, yaw, 8)
-- otherwise randomly turn
elseif random(1, 100) <= 30 then
yaw = yaw + random(-0.5, 0.5)
yaw = set_yaw(self, yaw, 8)
end
-- stand for great fall in front
local temp_is_cliff = is_at_cliff(self)
if self.facing_fence == true or temp_is_cliff or random(1, 100) <= 30 then
set_velocity(self, 0)
self.state = "stand"
set_animation(self, "stand")
else
set_velocity(self, self.walk_velocity)
if flight_check(self)
and self.animation
and self.animation.fly_start and self.animation.fly_end then
set_animation(self, "fly")
else
set_animation(self, "walk")
end
end
elseif self.state == "runaway" then
--chat_log("I am moving away")
self.runaway_timer = self.runaway_timer + 1
-- stop after 5 seconds or when at cliff
if self.runaway_timer > 5
or is_at_cliff(self) then
self.runaway_timer = 0
set_velocity(self, 0)
self.state = "stand"
set_animation(self, "stand")
self.runaway_timer = 0
else
set_velocity(self, self.run_velocity)
set_animation(self, "walk")
end
elseif self.state == "move" then
-- is there something I need to avoid?
if self.water_damage > 0
and self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
elseif self.water_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water"})
elseif self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:lava"})
end
if lp then
-- if mob in water or lava then look for land
if (self.lava_damage
and minetest.registered_nodes[self.standing_in].groups.lava)
or (self.water_damage
and minetest.registered_nodes[self.standing_in].groups.water) then
lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
"group:sand", node_ice, node_snowblock})
-- did we find land?
if lp then
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
-- look towards land and jump/move in that direction
yaw = set_yaw(self, yaw, 6)
local jumped = do_jump(self)
if jumped then chat_log("Jumping while moving") end
set_velocity(self, self.walk_velocity)
else
yaw = yaw + random(-0.5, 0.5)
end
else
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
end
yaw = set_yaw(self, yaw, 8)
end
-- stand for great fall in front
local temp_is_cliff = is_at_cliff(self)
if self.facing_fence == true
or temp_is_cliff then
set_velocity(self, 0)
self.state = "stand"
set_animation(self, "stand")
else
set_velocity(self, self.walk_velocity)
if flight_check(self)
and self.animation
and self.animation.fly_start
and self.animation.fly_end then
set_animation(self, "fly")
else
set_animation(self, "walk")
end
end
end
-- end ai_manager
end
local ai_on_step = function(self, dtime)
-- To do: add rest of mob_redo functionality, like:
-- Attacking
-- Follow
-- Etc
-- mob plays random sound at times
if random(1, 100) == 1 then
mob_sound(self, self.sounds.random)
end
-- environmental damage timer (every 1 second)
self.env_damage_timer = self.env_damage_timer + dtime
if (self.env_damage_timer > 1) then
self.env_damage_timer = 0
-- check for environmental damage (water, fire, lava etc.)
do_env_damage(self)
-- node replace check (cow eats grass etc.)
replace(self, pos)
end
--flop(self)
do_jump(self)
runaway_from(self)
end
adaptive_ai.ai_pathfinding = ai_pathfinding
adaptive_ai.ai_manager = ai_manager
adaptive_ai.ai_on_step = ai_on_step

2
depends.txt Normal file
View file

@ -0,0 +1,2 @@
default
mobs

375
goblin/goblin.lua Normal file
View file

@ -0,0 +1,375 @@
-- 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_euclidean
local chat_log = adaptive_ai.chat_log
local random = math.random
local abs = math.abs
local floor = math.floor
local min = math.min
local max = math.max
local round = adaptive_ai.calc.round
local 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 stats
local 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 = genes
end
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], gene
end
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, focus
end
-- Sugar
local 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 name
end
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.family
end
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 self
end
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 cloned
end
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 s
end
local function setup(self, mother, father)
create(self, mother, father)
self.decide = decide
self.score = score
self.to_lines = to_lines
end
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 goblin
end
goblins.stats = stats
goblins.actions = actions
goblins.create = create
goblins.setup = setup
goblins.clone = clone
goblins.file_tag = file_tag
goblins.trained_tag = trained_tag
goblins.from_lines = from_lines
goblins.decide = decide
goblins.score = score
goblins.to_lines = goblins.to_lines
adaptive_ai.goblins = goblins

View file

@ -0,0 +1,514 @@
dofile(minetest.get_modpath("adaptive_ai").."/kobold/kobold.lua")
local kobolds = adaptive_ai.kobolds
local stats = kobolds.stats
local helper = adaptive_ai.helper
local calc = adaptive_ai.calc
local population = adaptive_ai.population
local random = math.random
local floor = math.floor
local chat_log = adaptive_ai.chat_log
local ai_on_step = adaptive_ai.ai_on_step
local sort_nils_out = helper.sort_nils_out
local get_surface = helper.search_surface
local pop = helper.pop
local shuffle = helper.shuffle
local distance = calc.dist_euclidean
local kobold_from_lines = kobolds.from_lines
local kobold_score = kobolds.score
local kobold_decide = kobolds.decide
local kobold_create = kobolds.create
local get_from_census = population.get_from_census
local create_tribe = population.create_tribe
local add_to_tribe = population.add_to_tribe
local empty_population = population.empty
local do_jump = helper.mobs.do_jump
local set_velocity = helper.mobs.set_velocity
local is_at_cliff = helper.mobs.is_at_cliff
local set_yaw = helper.mobs.set_yaw
local flight_check = helper.mobs.flight_check
local set_animation = helper.mobs.set_animation
local adjacent = helper.adjacent
local file_tag = kobolds.file_tag
local popul_path = population.path
local train_path = adaptive_ai.modpath.."/training/"
local pi = math.pi
local actions = kobolds.actions
local yaws = {
0, pi, 3*pi/2, pi/2, -- North, South, East, West
7*pi/4, 5*pi/4, pi/4, 3*pi/4 -- NE, SE, NW, SW
}
local food_nodes = {"adaptive_ai:moondrum_mushroom"}
local founders = nil
local min_founders = 4
local timer = 0
-- Out of Mobs_Redo
-- Equal to do_states(self, dtime) for self.state == "walk"
-- except without turning to random directions when safe
local function state_move(self, dtime)
local yaw = self.object:get_yaw() or 0
local s = self.object:get_pos()
local lp = nil
-- is there something I need to avoid?
if self.water_damage > 0
and self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
elseif self.water_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water"})
elseif self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:lava"})
end
if lp then
-- if mob in water or lava then look for land
if (self.lava_damage
and minetest.registered_nodes[self.standing_in].groups.lava)
or (self.water_damage
and minetest.registered_nodes[self.standing_in].groups.water) then
lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
"group:sand", node_ice, node_snowblock})
-- did we find land?
if lp then
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
-- look towards land and jump/move in that direction
yaw = set_yaw(self, yaw, 6)
local jumped = do_jump(self)
if jumped then chat_log("Jumping while moving") end
set_velocity(self, self.walk_velocity)
else
yaw = yaw + random(-0.5, 0.5)
end
else
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
end
yaw = set_yaw(self, yaw, 8)
end
-- stand for great fall in front
local temp_is_cliff = is_at_cliff(self)
if self.facing_fence == true
or temp_is_cliff then
set_velocity(self, 0)
self.state = "stand"
set_animation(self, "stand")
else
set_velocity(self, self.walk_velocity)
if flight_check(self)
and self.animation
and self.animation.fly_start
and self.animation.fly_end then
set_animation(self, "fly")
else
set_animation(self, "walk")
end
end
end
local function move_creature(self, yaw_id)
if self.state == "stand" or self.state == "walk" or self.state == "move" then
self.object:set_yaw(yaws[yaw_id])
set_velocity(self, self.walk_velocity)
self.state = "move"
end
return false
end
local function load_founders()
local file = io.open(train_path..kobolds.trained_tag..".txt", "r")
local counter = 0
if file then
founders = {}
local reading, kobold = false, {}
for line in file:lines() do
counter = counter + 1
if not reading then
reading = line == file_tag[1]
if reading then
kobold = {}
table.insert(kobold,line)
end
else
reading = line ~= file_tag[2]
table.insert(kobold,line)
if not reading then
kobold = kobold_from_lines(kobold)
kobold = kobold_create(kobold)
table.insert(founders, kobold)
chat_log("Founder loaded: "..kobold.name)
end
end
end
file:close()
end
if #founders == 0 then
--error("Founders not found")
end
end
load_founders()
local function get_founders(n)
chat_log(#founders.." founders")
founders = founders or {}
local f
while #founders < min_founders do
f = {}
kobold_create(f)
table.insert(founders, f)
end
if #founders > min_founders then
n = n or min_founders
local tribe_founders = {}
shuffle(founders)
for i=1,n do
table.insert(tribe_founders, founders[i])
end
return tribe_founders
else
return founders
end
end
local function load_kobold(self)
chat_log("Loading from census "..(self.nametag and "true" or "false"))
local old_self = get_from_census(self)
if old_self then
self.kobold = old_self
if not old_self.genes then error("No genes") end
chat_log(old_self.name.." loaded "..#old_self.genes.." genes")
self.timer = self.walk_velocity
return true
else
--self.kobold = nil
--chat_log("Nil kobold")
return false
end
end
local function set_kobold(self, kobold)
self.kobold = kobold
self.nametag = kobold.name
end
local function setup_kobold(self, mother, father)
if load_kobold(self) then
return
else
local kobold = {}
kobold_create(kobold, mother, father)
set_kobold(self, kobold)
end
return false
end
local function breed_kobold(self)
local mother = self.kobold
if not mother.tribe_id then
--error("Breeding with no tribe.")
return
end
local tribe_id = mother.tribe_id
local tribe = population.tribes[tribe_id]
if tribe then
local popul_size = tribe:popul_size()
if popul_size < tribe.size * 2 then
local p = mother.pos
p = {x=(p.x), y=(p.y), z=(p.z)}
p.x = p.x + (random(0,1) - 0.5)*2
p.z = p.z + (random(0,1) - 0.5)*2
local father = get_couple(tribe_id, self.kobold)
local child = place_kobold(p)
setup_kobold(child, mother, father)
child = child.kobold
child.tribe_id = tribe_id
population.add_to_tribe(tribe_id, child)
return true
end
end
return false
end
kobolds.breed_kobold = breed_kobold
local kobold_decisions = {
function(self, dtime) -- idle
set_velocity(self, 0)
end,
function(self, dtime) -- wander
local n = random(1,#yaws)
local move = move_creature(self, n)
local kobold = self.kobold
if move then
kobold.hunger = kobold.hunger - 1
self.state = "walk"
end
end,
function(self, dtime) -- eat
local kobold = self.kobold
if kobold.food and #kobold.food > 0 then
local satiate = pop(kobold.food)
kobold.hunger = kobold.hunger + satiate
kobold.hp = self.object:get_hp() + 1
self.object:set_hp(kobold.hp)
end
end,
function(self, dtime) -- breed
local kobold = self.kobold
kobold.fitness = kobold.fitness + 2
kobold.hunger = kobold.hunger - 10
kobolds.breed_kobold(self)
end,
function(self, dtime) -- move North
local move = move_creature(self, 1)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move South
local move = move_creature(self, 2)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move East
local move = move_creature(self, 3)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move West
local move = move_creature(self, 4)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move NE
local move = move_creature(self, 5)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move SE
local move = move_creature(self, 6)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move NW
local move = move_creature(self, 7)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move SW
local move = move_creature(self, 8)
self.state = move and "move" or self.state
end,
}
local manage_kobold = function(self, dtime)
local kobold = self.kobold
if kobold then
if kobold.delete or kobold.hp <= 0 then
self.object:remove()
return
end
kobold.hp = self.object:get_hp()
self.timer = self.timer + dtime
if kobold.hp > 0 then
local p = self.object:get_pos()
p = {x=floor(p.x+0.5), y=floor(p.y+0.5), z=floor(p.z+0.5)}
kobold.pos = p
local food_pos = minetest.find_node_near(self.object:get_pos(), self.reach, food_nodes)
if food_pos then
--chat_log("Pocketing food from "..food_pos.x..","..food_pos.z)
if kobold.closest_food
and kobold.closest_food.x == food_pos.x
and kobold.closest_food.z == food_pos.z then
kobold.closest_food = nil
end
table.insert(kobold.food, random(2,5))
minetest.set_node(food_pos, {name = "air"})
end
food_pos = minetest.find_node_near(self.object:get_pos(), self.view_range, food_nodes)
if food_pos then
--chat_log("Smelling good food")
food_pos = {x=floor(food_pos.x+0.5),y=floor(food_pos.y+0.5),z=floor(food_pos.z+0.5)}
if food_pos and (not kobold.closest_food
or distance(p, food_pos) < distance(p, kobold.closest_food)) then
kobold.closest_food = food_pos
end
end
if not kobold.action or self.timer >= self.walk_velocity then
self.timer = 0
--chat_log("Time to decide for "..kobold.name)
kobold.hunger = kobold.hunger - dtime
local neighbors = {}
local pos, pos2 = {}
for i=1,#adjacent do
pos.x = p.x + adjacent[i].x
pos.y = p.y
pos.z = p.z + adjacent[i].z
pos2 = get_surface(pos)
neighbors[i] = pos2 and {x=pos2.x,y=pos2.y,z=pos2.z} or {x=pos.x, y=self.kobold.climb + 1,z=pos.z}
end
chat_log("Closest food "..(kobold.closest_food and kobold.closest_food.x-p.x..","..kobold.closest_food.z-p.z or "none"))
local decision, d_id = kobold_decide(kobold, neighbors)
chat_log(kobold.name.." decides "..(decision and actions[decision] or "nothing").." -- "..(d_id or "error"))
kobold.action = decision
if decision then
kobold_decisions[decision](self, dtime)
else
if not kobold.action then
kobold.action = 1
kobold_decisions[1](self, dtime)
end
end
kobold.fitness = kobold.fitness + kobold_score(kobold)
end
if kobold.hunger < 0 then
kobold.hunger = 0
kobold.hp = self.object:get_hp() - 1
self.object:set_hp(kobold.hp)
end
end
else
chat_log("No kobold")
end
end
local function custom_on_step(self, dtime)
manage_kobold(self, dtime)
if self.state == "move" then
state_move(self, dtime)
end
do_jump(self, dtime)
--ai_on_step(self, dtime)
return false
end
local function place_kobold(p)
local ent = adaptive_ai:place_ai({
name = "adaptive_ai:kobold",
pos = p,
})
return ent
end
local function place_tribe(p)
local roots = {x=p.x, y=p.y-2, z=p.z}
local ents = {}
local popul = create_tribe(p, "adaptive_ai:kobold", get_founders(), kobolds.stats.view_range, kobolds.clone, 5)
local ent, pos, c
c = 0
for i,k in ipairs(popul) do
pos = get_surface(k.pos)
ent = place_kobold(pos)
set_kobold(ent, k)
--chat_log("Placing "..ent.nametag.."!")
ent.kobold.pos = pos
end
chat_log("Placed tribe!")
minetest.set_node(roots, {name = "adaptive_ai:moondrum_roots"})
end
adaptive_ai:register_ai("adaptive_ai:kobold", {
stepheight = 0.8,
hp_min = kobolds.stats.hp_max,
hp_max = kobolds.stats.hp_max,
armor = 100,
walk_velocity = 1.5,
run_velocity = 2,
jump = true,
jump_height = kobolds.stats.jump,
view_range = kobolds.stats.view_range,
damage = 2,
knock_back = true,
fear_height = kobolds.stats.fear_height,
floats = 1,
reach = 1,
attack_type = "dogfight",
makes_footstep_sound = true,
visual = "cube",
visual_size = {x=0.8, y=1.6},
textures = {
"aa_imp_top.png",
"aa_imp_top.png",
"aa_imp_side.png",
"aa_imp_side.png",
"aa_imp_face.png",
"aa_imp_side.png",
},
collisionbox = {-0.5,-0.8,-0.5, 0.5,0.8,0.5},
walk_chance = 1,
actions = kobolds.actions,
action_managers = kobold_decisions,
on_spawn = load_kobold,
do_custom = custom_on_step,
asexual = false,
breed = kobold_create,
})
minetest.register_chatcommand("kobold", {
func = function(name, param)
local player = minetest.get_player_by_name(name)
local pos = player and player:getpos() or nil
if pos then
place_tribe(pos)
end
end
})
minetest.register_chatcommand("empty_camps", {
func = function(name, param)
empty_population()
end
})
minetest.register_chatcommand("founders", {
func = function(name, param)
for i,f in ipairs(founders) do
chat_log("Founder "..f.name)
end
end
})

135
helper.lua Normal file
View file

@ -0,0 +1,135 @@
--adaptive_ai.chat_log = function() end
-- Localize math functions
local floor = math.floor
local ceil = math.ceil
local sqrt = math.sqrt
local random = math.random
-- Helper calculation functions
local calc = {}
calc.sum = function(vector)
local total = 0
for i = 1, #vector do
total = total + vector[i]
end
return total
end
calc.round = function(x)
if x>=0 then
if (x - floor(x)) >= 0.5 then
return ceil(x)
else
return floor(x)
end
else
if (ceil(x) - x) >= 0.5 then
return floor(x)
else
return ceil(x)
end
end
end
calc.normalize = function(vector)
local total = calc.sum(vector)
for i = 1, #vector do
vector[i] = vector[i]/total
end
end
calc.dist_manhattan = function(pos1, pos2)
local dist = abs(pos1.x - pos2.x) + abs(pos1.y - pos2.y) + abs(pos1.z - pos2.z)
return dist
end
calc.dist_euclidean = function(a, b)
local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
return sqrt(x * x + y * y + z * z)
end
calc.average = function(t, to_num)
local avg, cnt = 0, 0
for _,v in ipairs(t) do
if to_num then
avg = avg + to_num(v)
else
avg = avg + v
end
cnt = cnt + 1
end
avg = avg/cnt
return avg
end
calc.truncate = function(n)
return floor(n*100 + 0.5)/100
end
adaptive_ai.calc = calc
-- Helper generic functions
local helper = {}
helper.adjacent = {
{x=-1, z=-1},
{x=-1, z= 0},
{x=-1, z= 1},
{x= 0, z=-1},
--{x= 0, z= 0}, -- Center
{x= 0, z= 1},
{x= 1, z=-1},
{x= 1, z= 0},
{x= 1, z= 1},
}
helper.sort_nils_out = function(a, b)
if not a then return false end
return true
end
helper.compare_pos = function(p1, p2)
return p1.x == p2.x and p1.y == p2.y and p1.z == p2.z
end
helper.pop = function(t, i)
if #t <= 0 then return nil end
local i = i or 1
local res = t[i]
for i=i,#t do
t[i] = t[i+1]
end
return res
end
helper.shuffle = function(t, n)
n = n or #t
for i = 1, n+1 do
local j = random(n)
local k = random(n)
t[j], t[k] = t[k], t[j]
end
end
helper.load_file = function(filename, decompressing)
local file = io.open(filename, "r")
if file then
local t = file:read("*all")
file:close()
if decompressing then
t = minetest.decompress(t)
end
t = minetest.deserialize(t)
return t
elseif not filename:find("tribe") then
error(filename)
end
end
--helper.dist_euclidean = get_distance
adaptive_ai.helper = helper

954
helper_mobs.lua Normal file
View file

@ -0,0 +1,954 @@
local helper = adaptive_ai.helper
local chat_log = function(s)
minetest.chat_send_player("singleplayer", s)
end
adaptive_ai.chat_log = chat_log
local function get_node_group(name, group)
if type(group) == "table" then
local check = false
for _,g in ipairs(group) do
check = check or get_node_group(name, g)
end
return check
else
if not minetest.registered_nodes[name] or not minetest.registered_nodes[name].groups[group] then
return nil
end
return minetest.registered_nodes[name].groups[group]
end
end
local function is_airlike(name, airlike_groups)
airlike_groups = airlike_groups or {}
if name == "air" then return true end
return get_node_group(name, airlike_groups)
end
local function search_surface(pos, airlike_groups)
local top = minetest.get_node_or_nil(pos)
if not top then return nil end
local top_pos = {x=pos.x, y=pos.y, z=pos.z}
top = minetest.get_node_or_nil(top_pos)
--chat_log("Found "..top.name.."...")
if not is_airlike(top.name) then
repeat
top_pos.y = top_pos.y + 1
top = minetest.get_node_or_nil(top_pos)
--chat_log("Going up - found "..top.name.."...")
until not top or is_airlike(top.name)
top_pos = not top and nil or top_pos
else
repeat
top_pos.y = top_pos.y - 1
top = minetest.get_node(top_pos)
--chat_log("Going down - found "..top.name.."...")
until not top or not is_airlike(top.name)
top_pos = not top and nil or top_pos
top_pos.y = top_pos.y + 1
end
--chat_log("Found!")
return top_pos
end
helper.get_node_group = get_node_group
helper.search_surface = search_surface
----------------------------------------------
-- Auxiliar functions copied from mobs_redo --
----------------------------------------------
-- Localize math functions
local pi = math.pi
local square = math.sqrt
local sin = math.sin
local cos = math.cos
local abs = math.abs
local min = math.min
local max = math.max
local atann = math.atan
local floor = math.floor
local random = math.random
local atan = function(x)
if not x or x ~= x then
--error("atan bassed NaN")
return 0
else
return atann(x)
end
end
-- config values
local show_health = minetest.settings:get_bool("mob_show_health") ~= false
local use_cmi = minetest.global_exists("cmi")
local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
-- default nodes
local node_fire = "fire:basic_flame"
local node_permanent_flame = "fire:permanent_flame"
local node_ice = "default:ice"
local node_snowblock = "default:snowblock"
local node_snow = "default:snow"
helper.mobs = {}
local get_distance = function(a, b)
local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
return square(x * x + y * y + z * z)
end
local set_animation = function(ent, anim)
mobs:set_animation(ent,anim)
end
local mob_sound = function(ent, sound)
if sound then
minetest.sound_play(sound, {
object = ent.object,
gain = 1.0,
max_hear_distance = ent.sounds.distance
})
end
end
local set_velocity = function(ent, v)
-- do not move if mob has stopped
if ent.state == "stand" then
ent.object:setvelocity({x = 0, y = 0, z = 0})
return
end
local yaw = (ent.object:get_yaw() or 0) + ent.rotate
ent.object:setvelocity({
x = sin(yaw) * -v,
y = ent.object:getvelocity().y,
z = cos(yaw) * v
})
end
local get_velocity = function(ent)
local v = ent.object:getvelocity()
return (v.x * v.x + v.z * v.z) ^ 0.5
end
local node_ok = function(pos, fallback)
fallback = fallback or mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
local set_yaw = function(ent, yaw, delay)
mobs:yaw(ent, yaw, delay)
return ent.target_yaw
end
local do_jump = function(self)
if not self.jump
or self.jump_height == 0
or self.fly
or self.child
or self.order == "stand" then
return false
end
self.facing_fence = false
-- something stopping us while moving?
if self.state ~= "stand"
and get_velocity(self) > 0.5
and self.object:getvelocity().y ~= 0 then
return false
end
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
-- what is mob standing on?
pos.y = pos.y + self.collisionbox[2] - 0.2
local nod = node_ok(pos)
if minetest.registered_nodes[nod.name].walkable == false then
return false
end
-- where is front
local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
-- what is in front of mob?
local nod = node_ok({
x = pos.x + dir_x,
y = pos.y + 0.5,
z = pos.z + dir_z
})
-- thin blocks that do not need to be jumped
if nod.name == node_snow then
return false
end
if minetest.registered_items[nod.name].walkable then
if not nod.name:find("fence")
and not nod.name:find("gate") then
local v = self.object:getvelocity()
v.y = self.jump_height
set_animation(self, "jump") -- only when defined
self.object:setvelocity(v)
-- when in air move forward
minetest.after(0.3, function(self, v)
if self.object:get_luaentity() then
self.object:set_acceleration({
x = v.x * 2,--1.5,
y = 0,
z = v.z * 2,--1.5
})
end
end, self, v)
if get_velocity(self) > 0 then
mob_sound(self, self.sounds.jump)
end
else
self.facing_fence = true
end
return true
end
return false
end
local is_at_cliff = function(ent)
if ent.fear_height == 0 then -- 0 for no falling protection!
return false
end
local yaw = ent.object:get_yaw()
local dir_x = -sin(yaw) * (ent.collisionbox[4] + 0.5)
local dir_z = cos(yaw) * (ent.collisionbox[4] + 0.5)
local pos = ent.object:get_pos()
local ypos = pos.y + ent.collisionbox[2] -- just above floor
if minetest.line_of_sight(
{x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
{x = pos.x + dir_x, y = ypos - ent.fear_height, z = pos.z + dir_z}
, 1) then
return true
end
return false
end
local within_limits = function(pos, radius)
if (pos.x - radius) > -30913
and (pos.x + radius) < 30928
and (pos.y - radius) > -30913
and (pos.y + radius) < 30928
and (pos.z - radius) > -30913
and (pos.z + radius) < 30928 then
return true -- within limits
end
return false -- beyond limits
end
-- update nametag colour
local update_tag = function(ent)
local col = "#00FF00"
local qua = ent.hp_max / 4
if ent.health <= floor(qua) then
col = "#FF0000"
elseif ent.health <= floor(qua * 2) then
col = "#FF6600"
elseif ent.health <= floor(qua * 3) then
col = "#FFFF00"
end
ent.object:set_properties({nametag = ent.nametag, nametag_color = col})
end
-- check line of sight (BrunoMine)
local line_of_sight = function(ent, pos1, pos2, stepsize)
stepsize = stepsize or 1
local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
-- normal walking and flying mobs can see you through air
if s == true then
return true
end
-- New pos1 to be analyzed
local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
-- Checks the return
if r == true then return true end
-- Nodename found
local nn = minetest.get_node(pos).name
-- Target Distance (td) to travel
local td = get_distance(pos1, pos2)
-- Actual Distance (ad) traveled
local ad = 0
-- It continues to advance in the line of sight in search of a real
-- obstruction which counts as 'normal' nodebox.
while minetest.registered_nodes[nn]
and (minetest.registered_nodes[nn].walkable == false
or minetest.registered_nodes[nn].drawtype == "nodebox") do
-- Check if you can still move forward
if td < ad + stepsize then
return true -- Reached the target
end
-- Moves the analyzed pos
local d = get_distance(pos1, pos2)
npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
-- NaN checks
if d == 0
or npos1.x ~= npos1.x
or npos1.y ~= npos1.y
or npos1.z ~= npos1.z then
return false
end
ad = ad + stepsize
-- scan again
r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
if r == true then return true end
-- New Nodename found
nn = minetest.get_node(pos).name
end
return false
end
-- are we flying in what we are suppose to? (taikedz)
local flight_check = function(ent, pos_w)
local nod = ent.standing_in
local def = minetest.registered_nodes[nod]
if not def then return false end -- nil check
if type(ent.fly_in) == "string"
and nod == ent.fly_in then
return true
elseif type(ent.fly_in) == "table" then
for _,fly_in in pairs(ent.fly_in) do
if nod == fly_in then
return true
end
end
end
-- stops mobs getting stuck inside stairs and plantlike nodes
if def.drawtype ~= "airlike"
and def.drawtype ~= "liquid"
and def.drawtype ~= "flowingliquid" then
return true
end
return false
end
local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
radius = radius or 2
min_size = min_size or 0.5
max_size = max_size or 1
gravity = gravity or -10
glow = glow or 0
minetest.add_particlespawner({
amount = amount,
time = 0.25,
minpos = pos,
maxpos = pos,
minvel = {x = -radius, y = -radius, z = -radius},
maxvel = {x = radius, y = radius, z = radius},
minacc = {x = 0, y = gravity, z = 0},
maxacc = {x = 0, y = gravity, z = 0},
minexptime = 0.1,
maxexptime = 1,
minsize = min_size,
maxsize = max_size,
texture = texture,
glow = glow,
})
end
-- drop items
local item_drop = function(ent, cooked)
-- no drops if disabled by setting
if not mobs_drop_items then return end
-- no drops for child mobs
if ent.child then return end
local obj, item, num
local pos = ent.object:get_pos()
ent.drops = ent.drops or {} -- nil check
for n = 1, #ent.drops do
if random(1, ent.drops[n].chance) == 1 then
num = random(ent.drops[n].min or 1, ent.drops[n].max or 1)
item = ent.drops[n].name
-- cook items when true
if cooked then
local output = minetest.get_craft_result({
method = "cooking", width = 1, items = {item}})
if output and output.item and not output.item:is_empty() then
item = output.item:get_name()
end
end
-- add item if it exists
obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
if obj and obj:get_luaentity() then
obj:setvelocity({
x = random(-10, 10) / 9,
y = 6,
z = random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
end
end
end
ent.drops = {}
end
-- check if mob is dead or only hurt
local check_for_death = function(ent, cause, cmi_cause)
-- has health actually changed?
if ent.health == ent.old_health and ent.health > 0 then
return
end
ent.old_health = ent.health
-- still got some health? play hurt sound
if ent.health > 0 then
mob_sound(ent, ent.sounds.damage)
-- make sure health isn't higher than max
if ent.health > ent.hp_max then
ent.health = ent.hp_max
end
-- backup nametag so we can show health stats
if not ent.nametag2 then
ent.nametag2 = ent.nametag or ""
end
if show_health and (cmi_cause and cmi_cause.type == "punch") then
ent.htimer = 2
ent.nametag = "" .. ent.health .. " / " .. ent.hp_max
update_tag(ent)
end
return false
end
-- dropped cooked item if mob died in lava
if cause == "lava" then
item_drop(ent, true)
else
item_drop(ent, nil)
end
mob_sound(ent, ent.sounds.death)
local pos = ent.object:get_pos()
-- execute custom death function
if ent.on_die then
ent.on_die(ent, pos)
if use_cmi then
cmi.notify_die(ent.object, cmi_cause)
end
ent.object:remove()
return true
end
-- default death function and die animation (if defined)
if ent.animation and ent.animation.die_start and ent.animation.die_end then
local frames = ent.animation.die_end - ent.animation.die_start
local speed = ent.animation.die_speed or 15
local length = max(frames / speed, 0)
ent.attack = nil
ent.v_start = false
ent.timer = 0
ent.blinktimer = 0
ent.passive = true
ent.state = "die"
set_velocity(ent, 0)
set_animation(ent, "die")
minetest.after(length, function(ent)
if use_cmi then
cmi.notify_die(ent.object, cmi_cause)
end
ent.object:remove()
end, ent)
else
if use_cmi then
cmi.notify_die(ent.object, cmi_cause)
end
ent.object:remove()
end
effect(pos, 20, "tnt_smoke.png")
return true
end
local do_env_damage = function(ent)
-- feed/tame text timer (so mob 'full' messages dont spam chat)
if ent.htimer > 0 then
ent.htimer = ent.htimer - 1
end
-- reset nametag after showing health stats
if ent.htimer < 1 and ent.nametag2 then
ent.nametag = ent.nametag2
ent.nametag2 = nil
update_tag(ent)
end
local pos = ent.object:get_pos()
ent.time_of_day = minetest.get_timeofday()
-- remove mob if beyond map limits
if not within_limits(pos, 0) then
ent.object:remove()
return
end
-- bright light harms mob
if ent.light_damage ~= 0
-- and pos.y > 0
-- and ent.time_of_day > 0.2
-- and ent.time_of_day < 0.8
and (minetest.get_node_light(pos) or 0) > 12 then
ent.health = ent.health - ent.light_damage
effect(pos, 5, "tnt_smoke.png")
if check_for_death(ent, "light", {type = "light"}) then return end
end
local y_level = ent.collisionbox[2]
if ent.child then
y_level = ent.collisionbox[2] * 0.5
end
-- what is mob standing in?
pos.y = pos.y + y_level + 0.25 -- foot level
ent.standing_in = node_ok(pos, "air").name
-- print ("standing in " .. ent.standing_in)
-- don't fall when on ignore, just stand still
if ent.standing_in == "ignore" then
ent.object:setvelocity({x = 0, y = 0, z = 0})
end
local nodef = minetest.registered_nodes[ent.standing_in]
pos.y = pos.y + 1 -- for particle effect position
-- water
if ent.water_damage and nodef.groups.water then
if ent.water_damage ~= 0 then
ent.health = ent.health - ent.water_damage
effect(pos, 5, "bubble.png", nil, nil, 1, nil)
if check_for_death(ent, "water", {type = "environment",
pos = pos, node = ent.standing_in}) then return end
end
-- lava or fire
elseif ent.lava_damage
and (nodef.groups.lava
or ent.standing_in == node_fire
or ent.standing_in == node_permanent_flame) then
if ent.lava_damage ~= 0 then
ent.health = ent.health - ent.lava_damage
effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
if check_for_death(ent, "lava", {type = "environment",
pos = pos, node = ent.standing_in}) then return end
end
-- damage_per_second node check
elseif nodef.damage_per_second ~= 0 then
ent.health = ent.health - nodef.damage_per_second
effect(pos, 5, "tnt_smoke.png")
if check_for_death(ent, "dps", {type = "environment",
pos = pos, node = ent.standing_in}) then return end
end
check_for_death(ent, "", {type = "unknown"})
end
-- find and replace what mob is looking for (grass, wheat etc.)
local replace = function(ent, pos)
if not mobs_griefing
or not ent.replace_rate
or not ent.replace_what
or ent.child == true
or ent.object:getvelocity().y ~= 0
or random(1, ent.replace_rate) > 1 then
return
end
local what, with, y_offset
if type(ent.replace_what[1]) == "table" then
local num = random(#ent.replace_what)
what = ent.replace_what[num][1] or ""
with = ent.replace_what[num][2] or ""
y_offset = ent.replace_what[num][3] or 0
else
what = ent.replace_what
with = ent.replace_with or ""
y_offset = ent.replace_offset or 0
end
pos.y = pos.y + y_offset
if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
local oldnode = {name = what}
local newnode = {name = with}
local on_replace_return
if ent.on_replace then
on_replace_return = ent.on_replace(ent, pos, oldnode, newnode)
end
if on_replace_return ~= false then
minetest.set_node(pos, {name = with})
-- when cow/sheep eats grass, replace wool and milk
if ent.gotten == true then
ent.gotten = false
ent.object:set_properties(ent)
end
end
end
end
-- specific runaway
local specific_runaway = function(list, what)
-- no list so do not run
if list == nil then
return false
end
-- found entity on list to attack?
for no = 1, #list do
if list[no] == what then
return true
end
end
return false
end
-- find someone to runaway from
local runaway_from = function(ent)
if not ent.runaway_from then
return
end
local s = ent.object:get_pos()
local p, sp, dist
local player, obj, min_player
local type, name = "", ""
local min_dist = ent.view_range + 1
local objs = minetest.get_objects_inside_radius(s, ent.view_range)
for n = 1, #objs do
if objs[n]:is_player() then
if mobs.invis[ objs[n]:get_player_name() ]
or ent.owner == objs[n]:get_player_name() then
type = ""
else
player = objs[n]
type = "player"
name = "player"
end
else
obj = objs[n]:get_luaentity()
if obj then
player = obj.object
type = obj.type
name = obj.name or ""
end
end
-- find specific mob to runaway from
if name ~= "" and name ~= ent.name
and specific_runaway(ent.runaway_from, name) then
p = player:get_pos()
sp = s
-- aim higher to make looking up hills more realistic
p.y = p.y + 1
sp.y = sp.y + 1
dist = get_distance(p, s)
-- choose closest player/mpb to runaway from
if dist < min_dist
and line_of_sight(ent, sp, p, 2) == true then
min_dist = dist
min_player = player
end
end
end
if min_player then
local lp = player:get_pos()
local vec = {
x = lp.x - s.x,
y = lp.y - s.y,
z = lp.z - s.z
}
local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - ent.rotate
if lp.x > s.x then
yaw = yaw + pi
end
yaw = set_yaw(ent, yaw, 4)
ent.state = "runaway"
ent.runaway_timer = 3
end
end
local flop = function(ent)
-- swimmers flop when out of their element, and swim again when back in
if ent.fly then
local s = ent.object:get_pos()
if not flight_check(ent, s) then
ent.state = "flop"
ent.object:setvelocity({x = 0, y = -5, z = 0})
set_animation(ent, "stand")
return
elseif ent.state == "flop" then
ent.state = "stand"
end
end
end
local do_stand = function(ent, dtime)
local yaw = self.object:get_yaw() or 0
if random(1, 4) == 1 then
local lp = nil
local s = ent.object:get_pos()
local objs = minetest.get_objects_inside_radius(s, 3)
for n = 1, #objs do
if objs[n]:is_player() then
lp = objs[n]:get_pos()
break
end
end
-- look at any players nearby, otherwise turn randomly
if lp then
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - ent.rotate
if lp.x > s.x then yaw = yaw + pi end
else
yaw = yaw + random(-0.5, 0.5)
end
yaw = set_yaw(ent, yaw, 8)
end
set_velocity(ent, 0)
set_animation(ent, "stand")
-- npc's ordered to stand stay standing
if ent.type ~= "npc"
or ent.order ~= "stand" then
if ent.walk_chance ~= 0
and ent.facing_fence ~= true
and random(1, 100) <= ent.walk_chance
and is_at_cliff(ent) == false then
set_velocity(ent, ent.walk_velocity)
ent.state = "walk"
set_animation(ent, "walk")
--[[ fly up/down randomly for flying mobs
if ent.fly and random(1, 100) <= ent.walk_chance then
local v = ent.object:getvelocity()
local ud = random(-1, 2) / 9
ent.object:setvelocity({x = v.x, y = ud, z = v.z})
end--]]
end
end
end
local do_walk = function(ent, dtime)
local yaw = self.object:get_yaw() or 0
local s = self.object:get_pos()
local lp = nil
-- is there something I need to avoid?
if self.water_damage > 0
and self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
elseif self.water_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water"})
elseif self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:lava"})
end
if lp then
-- if mob in water or lava then look for land
if (self.lava_damage
and minetest.registered_nodes[self.standing_in].groups.lava)
or (self.water_damage
and minetest.registered_nodes[self.standing_in].groups.water) then
lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
"group:sand", node_ice, node_snowblock})
-- did we find land?
if lp then
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
-- look towards land and jump/move in that direction
yaw = set_yaw(self, yaw, 6)
do_jump(self)
set_velocity(self, self.walk_velocity)
else
yaw = yaw + random(-0.5, 0.5)
end
else
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
end
yaw = set_yaw(self, yaw, 8)
-- otherwise randomly turn
elseif random(1, 100) <= 30 then
yaw = yaw + random(-0.5, 0.5)
yaw = set_yaw(self, yaw, 8)
end
-- stand for great fall in front
local temp_is_cliff = is_at_cliff(self)
if self.facing_fence == true
or temp_is_cliff
or random(1, 100) <= 30 then
set_velocity(self, 0)
self.state = "stand"
set_animation(self, "stand")
else
set_velocity(self, self.walk_velocity)
if flight_check(self)
and self.animation
and self.animation.fly_start
and self.animation.fly_end then
set_animation(self, "fly")
else
set_animation(self, "walk")
end
end
end
-- Config values
helper.mobs.show_health = show_health
helper.mobs.use_cmi = use_cmi
helper.mobs.mobs_griefing = mobs_griefing
-- Auxiliar functions
helper.mobs.get_distance = get_distance
helper.mobs.set_animation = set_animation
helper.mobs.mob_sound = mob_sound
helper.mobs.node_ok = node_ok
helper.mobs.set_velocity = set_velocity
helper.mobs.get_velocity = get_velocity
helper.mobs.is_at_cliff = is_at_cliff
helper.mobs.within_limits = within_limits
helper.mobs.update_tag = update_tag
helper.mobs.flight_check = flight_check
helper.mobs.effect = effect
helper.mobs.item_drop = item_drop
helper.mobs.check_for_death = check_for_death
helper.mobs.do_env_damage = do_env_damage
helper.mobs.replace = replace
helper.mobs.flop = flop
helper.mobs.runaway_from = runaway_from
helper.mobs.do_jump = do_jump
helper.mobs.set_yaw = set_yaw
helper.mobs.do_state_stand = do_state_stand
adaptive_ai.helper = helper

22
init.lua Normal file
View file

@ -0,0 +1,22 @@
print("This file will be run at load time!")
adaptive_ai= {}
adaptive_ai.path = minetest.get_worldpath().."\\adaptive_ai_"
local modpath = minetest.get_modpath("adaptive_ai")
adaptive_ai.modpath = modpath
dofile(modpath.."/helper.lua")
dofile(modpath.."/helper_mobs.lua")
dofile(modpath.."/api_mobs.lua")
dofile(modpath.."/api.lua")
dofile(modpath.."/population.lua")
adaptive_ai.population.load_tribes()
dofile(modpath.."/moondrum.lua")
-- dofile(modpath.."/imps/imp_integration.lua")
dofile(modpath.."/kobold/kobold_integration.lua")

369
kobold/kobold.lua Normal file
View file

@ -0,0 +1,369 @@
-- 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 kobolds = {}
local distance = adaptive_ai.calc.dist_euclidean
local chat_log = adaptive_ai.chat_log
local random = math.random
local abs = math.abs
local floor = math.floor
local min = math.min
local max = math.max
local round = adaptive_ai.calc.round
local chat_log = adaptive_ai.chat_log or function(s) end--print(s) end
local file_tag = {"KOBOLD", "END_KOBOLD"}
local trained_tag = "trained_kobolds"
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, critical (3 values)
-- Direction of food = N, S, E, W, NE, NW, SE, SW (8 values)
-- Height of 4 directions = leveled, drop, climb (3^4 values = 81)
-- Total values = 3*8*81 = 1944 possible values
-- Global stats
local stats = {
total_genes = 1944,
mutation_chance = 0.2,
view_range = 20,
jump = 5,
climb = 1,
hunger_max = 100,
hp_max = 10,
fear_height = 2
}
local function get_genes(kobold)
genes = {}
for i = 1, stats.total_genes do
genes[i] = random(1, #actions)
end
kobold.genes = genes
end
local score = function(self)
local total = 0
total = total + (stats.hunger_max - self.hunger)/stats.hunger_max
total = total + (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], gene
end
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, focus
end
-- Sugar
local 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 name
end
local function get_name(kobold, mother)
if kobold.firstname and kobold.midname and kobold.family and not kobold.name then
kobold.name = kobold.firstname.." "..kobold.midname.." "..kobold.family
return
end
if mother then
if mother.family == "Sisanzik" then
kobold.family = correct_name(mother.firstname.."nzik")
else
kobold.family = mother.family
end
kobold.midname = mother.firstname.."anuy"
else
kobold.midname = ""
kobold.family = "Sisanzik"
end
local name = correct_name(new_name(0))
kobold.firstname = uppercase(name)
kobold.name = kobold.firstname.." "..kobold.midname.." "..kobold.family
end
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 = stats.climb
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 self
end
local function clone(kobold, cloned)
cloned = cloned or {}
for k, v in pairs(kobold) do
if k ~= "genes" then cloned[k] = v end
end
cloned.genes = {}
for i, v in ipairs(kobold.genes) do
cloned.genes[i] = v
end
return cloned
end
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 s
end
local function setup(self, mother, father)
create(self, mother, father)
self.decide = decide
self.score = score
self.to_lines = to_lines
end
local function from_lines(s)
kobold = {}
if s[1] ~= file_tag[1] or s[#s] ~= file_tag[2] then
error("Kobold expected. Not a kobold")
end
for i=2,#s - 1,2 do
loadstring("kobold[\""..s[i].."\"] = "..s[i+1])()
end
if type(kobold.genes) ~= "table" then
error("Kobold genes are not a table, but a "..type(kobold.genes))
end
create(kobold)
return kobold
end
kobolds.stats = stats
kobolds.actions = actions
kobolds.create = create
kobolds.setup = setup
kobolds.clone = clone
kobolds.file_tag = file_tag
kobolds.trained_tag = trained_tag
kobolds.from_lines = from_lines
kobolds.decide = decide
kobolds.score = score
kobolds.to_lines = kobolds.to_lines
adaptive_ai.kobolds = kobolds

View file

@ -0,0 +1,514 @@
dofile(minetest.get_modpath("adaptive_ai").."/kobold/kobold.lua")
local kobolds = adaptive_ai.kobolds
local stats = kobolds.stats
local helper = adaptive_ai.helper
local calc = adaptive_ai.calc
local population = adaptive_ai.population
local random = math.random
local floor = math.floor
local chat_log = adaptive_ai.chat_log
local ai_on_step = adaptive_ai.ai_on_step
local sort_nils_out = helper.sort_nils_out
local get_surface = helper.search_surface
local pop = helper.pop
local shuffle = helper.shuffle
local distance = calc.dist_euclidean
local kobold_from_lines = kobolds.from_lines
local kobold_score = kobolds.score
local kobold_decide = kobolds.decide
local kobold_create = kobolds.create
local get_from_census = population.get_from_census
local create_tribe = population.create_tribe
local add_to_tribe = population.add_to_tribe
local empty_population = population.empty
local do_jump = helper.mobs.do_jump
local set_velocity = helper.mobs.set_velocity
local is_at_cliff = helper.mobs.is_at_cliff
local set_yaw = helper.mobs.set_yaw
local flight_check = helper.mobs.flight_check
local set_animation = helper.mobs.set_animation
local adjacent = helper.adjacent
local file_tag = kobolds.file_tag
local popul_path = population.path
local train_path = adaptive_ai.modpath.."/training/"
local pi = math.pi
local actions = kobolds.actions
local yaws = {
0, pi, 3*pi/2, pi/2, -- North, South, East, West
7*pi/4, 5*pi/4, pi/4, 3*pi/4 -- NE, SE, NW, SW
}
local food_nodes = {"adaptive_ai:moondrum_mushroom"}
local founders = nil
local min_founders = 4
local timer = 0
-- Out of Mobs_Redo
-- Equal to do_states(self, dtime) for self.state == "walk"
-- except without turning to random directions when safe
local function state_move(self, dtime)
local yaw = self.object:get_yaw() or 0
local s = self.object:get_pos()
local lp = nil
-- is there something I need to avoid?
if self.water_damage > 0
and self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
elseif self.water_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:water"})
elseif self.lava_damage > 0 then
lp = minetest.find_node_near(s, 1, {"group:lava"})
end
if lp then
-- if mob in water or lava then look for land
if (self.lava_damage
and minetest.registered_nodes[self.standing_in].groups.lava)
or (self.water_damage
and minetest.registered_nodes[self.standing_in].groups.water) then
lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
"group:sand", node_ice, node_snowblock})
-- did we find land?
if lp then
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
-- look towards land and jump/move in that direction
yaw = set_yaw(self, yaw, 6)
local jumped = do_jump(self)
if jumped then chat_log("Jumping while moving") end
set_velocity(self, self.walk_velocity)
else
yaw = yaw + random(-0.5, 0.5)
end
else
local vec = {
x = lp.x - s.x,
z = lp.z - s.z
}
yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
if lp.x > s.x then yaw = yaw + pi end
end
yaw = set_yaw(self, yaw, 8)
end
-- stand for great fall in front
local temp_is_cliff = is_at_cliff(self)
if self.facing_fence == true
or temp_is_cliff then
set_velocity(self, 0)
self.state = "stand"
set_animation(self, "stand")
else
set_velocity(self, self.walk_velocity)
if flight_check(self)
and self.animation
and self.animation.fly_start
and self.animation.fly_end then
set_animation(self, "fly")
else
set_animation(self, "walk")
end
end
end
local function move_creature(self, yaw_id)
if self.state == "stand" or self.state == "walk" or self.state == "move" then
self.object:set_yaw(yaws[yaw_id])
set_velocity(self, self.walk_velocity)
self.state = "move"
end
return false
end
local function load_founders()
local file = io.open(train_path..kobolds.trained_tag..".txt", "r")
local counter = 0
if file then
founders = {}
local reading, kobold = false, {}
for line in file:lines() do
counter = counter + 1
if not reading then
reading = line == file_tag[1]
if reading then
kobold = {}
table.insert(kobold,line)
end
else
reading = line ~= file_tag[2]
table.insert(kobold,line)
if not reading then
kobold = kobold_from_lines(kobold)
kobold = kobold_create(kobold)
table.insert(founders, kobold)
chat_log("Founder loaded: "..kobold.name)
end
end
end
file:close()
end
if #founders == 0 then
--error("Founders not found")
end
end
load_founders()
local function get_founders(n)
chat_log(#founders.." founders")
founders = founders or {}
local f
while #founders < min_founders do
f = {}
kobold_create(f)
table.insert(founders, f)
end
if #founders > min_founders then
n = n or min_founders
local tribe_founders = {}
shuffle(founders)
for i=1,n do
table.insert(tribe_founders, founders[i])
end
return tribe_founders
else
return founders
end
end
local function load_kobold(self)
chat_log("Loading from census "..(self.nametag and "true" or "false"))
local old_self = get_from_census(self)
if old_self then
self.kobold = old_self
if not old_self.genes then error("No genes") end
chat_log(old_self.name.." loaded "..#old_self.genes.." genes")
self.timer = self.walk_velocity
return true
else
--self.kobold = nil
--chat_log("Nil kobold")
return false
end
end
local function set_kobold(self, kobold)
self.kobold = kobold
self.nametag = kobold.name
end
local function setup_kobold(self, mother, father)
if load_kobold(self) then
return
else
local kobold = {}
kobold_create(kobold, mother, father)
set_kobold(self, kobold)
end
return false
end
local function breed_kobold(self)
local mother = self.kobold
if not mother.tribe_id then
--error("Breeding with no tribe.")
return
end
local tribe_id = mother.tribe_id
local tribe = population.tribes[tribe_id]
if tribe then
local popul_size = tribe:popul_size()
if popul_size < tribe.size * 2 then
local p = mother.pos
p = {x=(p.x), y=(p.y), z=(p.z)}
p.x = p.x + (random(0,1) - 0.5)*2
p.z = p.z + (random(0,1) - 0.5)*2
local father = get_couple(tribe_id, self.kobold)
local child = place_kobold(p)
setup_kobold(child, mother, father)
child = child.kobold
child.tribe_id = tribe_id
population.add_to_tribe(tribe_id, child)
return true
end
end
return false
end
kobolds.breed_kobold = breed_kobold
local kobold_decisions = {
function(self, dtime) -- idle
set_velocity(self, 0)
end,
function(self, dtime) -- wander
local n = random(1,#yaws)
local move = move_creature(self, n)
local kobold = self.kobold
if move then
kobold.hunger = kobold.hunger - 1
self.state = "walk"
end
end,
function(self, dtime) -- eat
local kobold = self.kobold
if kobold.food and #kobold.food > 0 then
local satiate = pop(kobold.food)
kobold.hunger = kobold.hunger + satiate
kobold.hp = self.object:get_hp() + 1
self.object:set_hp(kobold.hp)
end
end,
function(self, dtime) -- breed
local kobold = self.kobold
kobold.fitness = kobold.fitness + 2
kobold.hunger = kobold.hunger - 10
kobolds.breed_kobold(self)
end,
function(self, dtime) -- move North
local move = move_creature(self, 1)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move South
local move = move_creature(self, 2)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move East
local move = move_creature(self, 3)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move West
local move = move_creature(self, 4)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move NE
local move = move_creature(self, 5)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move SE
local move = move_creature(self, 6)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move NW
local move = move_creature(self, 7)
self.state = move and "move" or self.state
end,
function(self, dtime) -- move SW
local move = move_creature(self, 8)
self.state = move and "move" or self.state
end,
}
local manage_kobold = function(self, dtime)
local kobold = self.kobold
if kobold then
if kobold.delete or kobold.hp <= 0 then
self.object:remove()
return
end
kobold.hp = self.object:get_hp()
self.timer = self.timer + dtime
if kobold.hp > 0 then
local p = self.object:get_pos()
p = {x=floor(p.x+0.5), y=floor(p.y+0.5), z=floor(p.z+0.5)}
kobold.pos = p
local food_pos = minetest.find_node_near(self.object:get_pos(), self.reach, food_nodes)
if food_pos then
--chat_log("Pocketing food from "..food_pos.x..","..food_pos.z)
if kobold.closest_food
and kobold.closest_food.x == food_pos.x
and kobold.closest_food.z == food_pos.z then
kobold.closest_food = nil
end
table.insert(kobold.food, random(2,5))
minetest.set_node(food_pos, {name = "air"})
end
food_pos = minetest.find_node_near(self.object:get_pos(), self.view_range, food_nodes)
if food_pos then
--chat_log("Smelling good food")
food_pos = {x=floor(food_pos.x+0.5),y=floor(food_pos.y+0.5),z=floor(food_pos.z+0.5)}
if food_pos and (not kobold.closest_food
or distance(p, food_pos) < distance(p, kobold.closest_food)) then
kobold.closest_food = food_pos
end
end
if not kobold.action or self.timer >= self.walk_velocity then
self.timer = 0
--chat_log("Time to decide for "..kobold.name)
kobold.hunger = kobold.hunger - dtime
local neighbors = {}
local pos, pos2 = {}
for i=1,#adjacent do
pos.x = p.x + adjacent[i].x
pos.y = p.y
pos.z = p.z + adjacent[i].z
pos2 = get_surface(pos)
neighbors[i] = pos2 and {x=pos2.x,y=pos2.y,z=pos2.z} or {x=pos.x, y=self.kobold.climb + 1,z=pos.z}
end
chat_log("Closest food "..(kobold.closest_food and kobold.closest_food.x-p.x..","..kobold.closest_food.z-p.z or "none"))
local decision, d_id = kobold_decide(kobold, neighbors)
chat_log(kobold.name.." decides "..(decision and actions[decision] or "nothing").." -- "..(d_id or "error"))
kobold.action = decision
if decision then
kobold_decisions[decision](self, dtime)
else
if not kobold.action then
kobold.action = 1
kobold_decisions[1](self, dtime)
end
end
kobold.fitness = kobold.fitness + kobold_score(kobold)
end
if kobold.hunger < 0 then
kobold.hunger = 0
kobold.hp = self.object:get_hp() - 1
self.object:set_hp(kobold.hp)
end
end
else
chat_log("No kobold")
end
end
local function custom_on_step(self, dtime)
manage_kobold(self, dtime)
if self.state == "move" then
state_move(self, dtime)
end
do_jump(self, dtime)
--ai_on_step(self, dtime)
return false
end
local function place_kobold(p)
local ent = adaptive_ai:place_ai({
name = "adaptive_ai:kobold",
pos = p,
})
return ent
end
local function place_tribe(p)
local roots = {x=p.x, y=p.y-2, z=p.z}
local ents = {}
local popul = create_tribe(p, "adaptive_ai:kobold", get_founders(), kobolds.stats.view_range, kobolds.clone, 5)
local ent, pos, c
c = 0
for i,k in ipairs(popul) do
pos = get_surface(k.pos)
ent = place_kobold(pos)
set_kobold(ent, k)
--chat_log("Placing "..ent.nametag.."!")
ent.kobold.pos = pos
end
chat_log("Placed tribe!")
minetest.set_node(roots, {name = "adaptive_ai:moondrum_roots"})
end
adaptive_ai:register_ai("adaptive_ai:kobold", {
stepheight = 0.8,
hp_min = kobolds.stats.hp_max,
hp_max = kobolds.stats.hp_max,
armor = 100,
walk_velocity = 1.5,
run_velocity = 2,
jump = true,
jump_height = kobolds.stats.jump,
view_range = kobolds.stats.view_range,
damage = 2,
knock_back = true,
fear_height = kobolds.stats.fear_height,
floats = 1,
reach = 1,
attack_type = "dogfight",
makes_footstep_sound = true,
visual = "cube",
visual_size = {x=0.8, y=1.6},
textures = {
"aa_imp_top.png",
"aa_imp_top.png",
"aa_imp_side.png",
"aa_imp_side.png",
"aa_imp_face.png",
"aa_imp_side.png",
},
collisionbox = {-0.5,-0.8,-0.5, 0.5,0.8,0.5},
walk_chance = 1,
actions = kobolds.actions,
action_managers = kobold_decisions,
on_spawn = load_kobold,
do_custom = custom_on_step,
asexual = false,
breed = kobold_create,
})
minetest.register_chatcommand("kobold", {
func = function(name, param)
local player = minetest.get_player_by_name(name)
local pos = player and player:getpos() or nil
if pos then
place_tribe(pos)
end
end
})
minetest.register_chatcommand("empty_camps", {
func = function(name, param)
empty_population()
end
})
minetest.register_chatcommand("founders", {
func = function(name, param)
for i,f in ipairs(founders) do
chat_log("Founder "..f.name)
end
end
})

189
moondrum.lua Normal file
View file

@ -0,0 +1,189 @@
-- Vegetable food for adaptive AI
-- Mushroom
local helper = adaptive_ai.helper
local moondrum = {}
-- local round = adaptive_ai.calc.round
local ceil = math.ceil
local floor = math.floor
local range = adaptive_ai.population.default_range
local chat_log = adaptive_ai.chat_log
local spread = {-floor(range/2), floor(range/2)}
minetest.register_node("adaptive_ai:moondrum_mushroom", {
drawtype = "plantlike",
description = "Moondrum Mushroom",
tiles = {"aa_moondrum_mushroom.png"},
inventory_image = "aa_moondrum_mushroom.png",
wield_image = "aa_moondrum_mushroom.png",
paramtype = "light",
sunlight_propagates = true,
walkable = false,
buildable_to = true,
groups = {snappy = 3, attached_node = 1, flammable = 1},
sounds = default.node_sound_leaves_defaults(),
on_use = minetest.item_eat(2),
selection_box = {
type = "fixed",
fixed = {-4 / 16, -0.5, -4 / 16, 4 / 16, -1 / 16, 4 / 16},
},
})
minetest.register_node("adaptive_ai:moondrum_roots", {
description = "Moondrum Mycelium",
tiles = {"aa_moondrum_roots.png"},
groups = {cracky = 2},
drop = "adaptive_ai:moondrum_roots 1"
})
local airlike_groups = {"flora"}
local soil_groups = {"soil", "sand", "wood"}
local soil_nodes = {"group:soil", "group:sand", "group:wood"}
local search_surface = function(pos)
return helper.search_surface(pos, airlike_groups)
end
local is_soil = function(name)
return helper.get_node_group(name, soil_groups)
end
local is_airlike = function(name)
return name == "air" or helper.get_node_group(name, airlike_groups)
end
local plant_moondrum = function(pos)
if not pos then return end -- skip if nil
local soil = minetest.get_node(pos)
--chat_log("Planting moondrum on "..soil.name.."...")
if is_soil(soil.name) then
pos.y = pos.y + 1
minetest.swap_node(pos, {name = "adaptive_ai:moondrum_mushroom"})
--chat_log("Planting succesful!")
return true
end
--chat_log("Planting failed!")
return false
end
local plant_random_moondrum = function(pos)
-- local new_pos = {x = pos.x + math.random(-10, 10), y = pos.y, z = pos.z + math.random(-10, 10)}
local minp = {x = pos.x + spread[1], y = pos.y + spread[1]*2, z = pos.z + spread[1]}
local maxp = {x = pos.x + spread[2], y = pos.y + spread[2]*2, z = pos.z + spread[2]}
local nodelist = minetest.find_nodes_in_area_under_air(minp, maxp, soil_nodes)
local new_pos = nodelist and nodelist[math.random(#nodelist)] or nil
return plant_moondrum(new_pos)
end
local spawn_moondrum_mushroom = function(pos)
--chat_log("Spawning mushroom from roots...")
local p = {x = pos.x, y = pos.y, z = pos.z}
p = search_surface(p, airlike_groups)
p.y = p.y-1
local top = minetest.get_node(p) --{x=p.x, y=p.y-1, z=p.z}
if not top then
return
elseif top.name == "adaptive_ai:moondrum_mushroom" then
plant_random_moondrum(pos)
return
else -- if is_airlike
if plant_moondrum({x=p.x, y=p.y-1, z=p.z}) then
return
else
plant_random_moondrum(pos)
return
end
end
end
minetest.register_abm({
label = "Moondrum sprouting",
nodenames = {"adaptive_ai:moondrum_roots"},
interval = 5.0,
chance = 10,
action = function(pos, node, active_object_count, active_object_count_wider)
local minp = {x = pos.x + spread[1], y = pos.y + spread[1]*2, z = pos.z + spread[1]}
local maxp = {x = pos.x + spread[2], y = pos.y + spread[2]*2, z = pos.z + spread[2]}
local nearby_mushrooms = minetest.find_nodes_in_area_under_air(minp, maxp, "adaptive_ai:moondrum_mushroom")
if #nearby_mushrooms < 20 then
for i = 1,20-#nearby_mushrooms do
spawn_moondrum_mushroom(pos)
end
else
spawn_moondrum_mushroom(pos)
end
end
})
minetest.register_abm({
label = "Moondrum spreading",
nodenames = {"adaptive_ai:moondrum_mushroom"},
interval = 30.0,
chance = 20,
action = function(pos, node, active_object_count, active_object_count_wider)
local minp = {x = pos.x + spread[1], y = pos.y + spread[1]*2, z = pos.z + spread[1]}
local maxp = {x = pos.x + spread[2], y = pos.y + spread[2]*2, z = pos.z + spread[2]}
local nodelist = minetest.find_nodes_in_area_under_air(minp, maxp, soil_groups)
local new_pos = nodelist and nodelist[math.random(#nodelist)] or nil
plant_moondrum(new_pos)
--chat_log("Spreading moondrum from spores!")
end
})
--[[
minetest.register_abm({
label = "Moondrum sprouting near water",
nodenames = {"adaptive_ai:moondrum_roots"},
neighbors = {"default:water_source", "default:water_flowing"},
interval = 30.0,
chance = 1,
action = spawn_moondrum_mushroom
})
minetest.register_abm({
label = "Moondrum spreading near water"
nodenames = {"adaptive_ai:moondrum_mushroom"},
neighbors = {"default:water_source", "default:water_flowing"},
interval = 30.0,
chance = 2,
action = function(pos, node, active_object_count, active_object_count_wider)
new_pos = {x = pos.x + math.random(-2, 2), y = pos.y, z = pos.z + math.random(-2, 2)}
new_pos = search_surface(new_pos, valid_air)
plant_moondrum(new_pos)
--chat_log("Spreading moondrum from fertile mushroom spores!")
end
})
minetest.register_abm({
nodenames = {"adaptive_ai:moondrum_mushroom"},
interval = 10.0,
chance = 1,
action = function(pos, node, active_object_count, active_object_count_wider)
local objs = minetest.get_objects_inside_radius(pos, imps.vision)
local imp = {}
for _,obj in ipairs(objs) do
imp = obj:get_luaimp()
if imp and imp.name == "adaptive_ai:imp" then
--chat_log("Imp found")
if not imp.stats.closest_food_pos or distance(obj:getpos(), imp.stats.closest_food_pos) > distance(obj:getpos(), pos) then
imp.stats.closest_food_pos = pos
end
end
end
end
})
--]]
adaptive_ai.moondrum = moondrum

239
population.lua Normal file
View file

@ -0,0 +1,239 @@
local chat_log = adaptive_ai.chat_log
local breeding = adaptive_ai.breeding
local random = math.random
local floor = math.floor
local pop = adaptive_ai.helper.pop
local load_file = adaptive_ai.helper.load_file
local default_size = 10
local default_range = 20
local timer = 0
local dirty = false
population = {}
tribes = {}
populs = {}
census = {}
--local lfs = require "lfs"
--local mkdir = minetest.mkdir or lfs.mkdir
local mkdir = minetest.mkdir or function(path)
os.execute("mkdir \"" .. path .. "\"")
end
local path = adaptive_ai.path.."population_"
local file_tag = {"tribe_list", "census"}
local function save_census()
local file, to_file, count, gene_copy
count = 0
for _,c in pairs(census) do
count = count + 1
gene_copy = c.genes
c.genes = nil
to_file = (minetest.serialize(c))
file = io.open(path..file_tag[2].."\\"..c.name..".txt", "w")
if not file then
mkdir(path..file_tag[2])
file = io.open(path..file_tag[2].."\\"..c.name..".txt", "w")
end
file:write(to_file)
file:close()
c.genes = gene_copy
to_file = (minetest.serialize(gene_copy))
file = io.open(path..file_tag[2].."\\"..c.name.." genes.txt", "w")
file:write(to_file)
file:close()
end
chat_log("Census: "..count)
end
local function get_from_census(entity)
if not entity.nametag then return nil end
local name = entity.nametag--:gsub("%s", "_")
local from_file = load_file(path..file_tag[2].."\\"..name..".txt")
local genes_file = load_file(path..file_tag[2].."\\"..name.." genes.txt")
local creature = from_file or nil
if creature then
creature.genes = genes_file
table.insert(census, creature)
local t_id = creature.tribe_id
if not tribes[t_id] then
local tribe = {}
tribe.pos = creature.pos
tribe.name = entity.name
tribe.range = default_range
tribe.size = default_size
tribe.id = t_id
tribes[t_id] = tribe
end
if not populs[t_id] then
populs[t_id] = {creature}
else
table.insert(populs[t_id], creature)
end
end
return creature
end
local function save_to_file()
local file = io.open(path..file_tag[1]..".txt", "w")
local to_file = (minetest.serialize(tribes))
file:write(to_file)
file:close()
save_census()
timer = 0
dirty = false
end
local function load_tribes()
local from_file = load_file(path..file_tag[1]..".txt")--, true)
tribes = from_file or {}
--error(#tribes > 0 and "Tribes loaded succesfully" or "Tribes not loaded")
--error(#census > 0 and "Census loaded succesfully" or "Census not loaded")
end
local function add_to_census(creature)
local c_name, c_tag, disambiguate = creature.name, creature.tag, 1
while census[creature.name] ~= nil do
creature.name = c_name.." "..disambiguate
disambiguate = disambiguate + 1
end
census[creature.name] = creature
--chat_log("New censed creature: "..creature.name)
end
local function fill_tribe(tribe, popul)
local breed, asexual = breeding[tribe.name].breed, breeding[tribe.name].asexual
local pos, t_id, range = tribe.pos, tribe.id, tribe.range
--math.randomseed(os.time())
local creature, p, i, j
while #popul < tribe.size do
p = {x=floor(pos.x+0.5), y=floor(pos.y+1), z=floor(pos.z+0.5)}
p.x = p.x + random(0,range) - floor(range/2 + 0.5)
p.z = p.z + random(0,range) - floor(range/2 + 0.5)
i = random(#popul)
if asexual then
creature = {}
creature = breed(creature, popul[i])
else
repeat
j = random(#popul)
until i ~= j
creature = {}
creature = breed(creature, popul[i], popul[j])
end
creature.pos = p
creature.tribe_id = t_id
table.insert(popul, creature)
add_to_census(creature)
end
populs[t_id] = popul
end
local function create_tribe(pos, name, founders, range, clone, size)
if not founders or #founders == 0 then
error("Not enough creatures in starting population.")
end
local tribe = {}
tribe.pos = pos
tribe.name = name
tribe.range = range
tribe.size = size or default_size
tribe.id = #tribes + 1
local popul = {}
local p
for _,father in ipairs(founders) do
f = clone(father, f)
f.tribe_id = tribe.id
p = {x=floor(pos.x+0.5), y=floor(pos.y+0.5), z=floor(pos.z+0.5)}
p.x = p.x + random(0,range) - floor(range/2 + 0.5)
p.z = p.z + random(0,range) - floor(range/2 + 0.5)
f.pos = p
table.insert(popul, f)
add_to_census(f)
end
fill_tribe(tribe, popul)
table.insert(tribes, tribe)
save_to_file()
tribe.popul_size = function(self)
return #populs[tribe.tribe_id]
end
return popul
end
local function get_couple(t_id, creature)
local p = populs[creature.tribe_id]
local i
repeat
i = random(#t)
until t[i].name ~= creature.name
return t[i]
end
local function empty()
for i,t in ipairs(tribes) do
t = {}
end
for i,c in ipairs(populs) do
c.delete = true
end
tribes = {}
census = {}
save_to_file()
end
local function add_to_tribe(t_id, creature)
creature.tribe_id = t_id
table.insert(populs[t_id], creature)
census[creature.name] = creature
dirty = true
end
minetest.register_globalstep(function(dtime)
timer = timer + dtime;
if timer >= 30 and dirty then
save_to_file()
end
end)
minetest.register_on_shutdown(function()
adaptive_ai.population.save_to_file()
end)
population.tribes = tribes
population.load_tribes = load_tribes
population.create_tribe = create_tribe
population.get_from_census = get_from_census
population.add_to_tribe = add_to_tribe
population.empty = empty
population.get_couple = get_couple
population.default_size = default_size
population.default_range = default_range
population.save_to_file = save_to_file
adaptive_ai.population = population

BIN
textures/aa_imp_face.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
textures/aa_imp_side.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
textures/aa_imp_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
textures/tnt_smoke.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

BIN
textures/transparent.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

182
training/noise.lua Normal file
View file

@ -0,0 +1,182 @@
--[[---------------------------------------------
**********************************************************************************
Simplex Noise Module, Translated by Levybreak
Modified by Jared "Nergal" Hewitt for use with MapGen for Love2D
Original Source: http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
The code there is in java, the original implementation by Ken Perlin
**********************************************************************************
--]]---------------------------------------------
require "bit"
local LuaBit = bit
SimplexNoise = {}
SimplexNoise.Gradients3D = {{1,1,0},{-1,1,0},{1,-1,0},{-1,-1,0},
{1,0,1},{-1,0,1},{1,0,-1},{-1,0,-1},
{0,1,1},{0,-1,1},{0,1,-1},{0,-1,-1}}
for i=1,#SimplexNoise.Gradients3D do
SimplexNoise.Gradients3D[i-1] = SimplexNoise.Gradients3D[i]
SimplexNoise.Gradients3D[i] = nil
end
function SimplexNoise.seedP(seed)
s2 = seed * 1234567
-- reset all the things
SimplexNoise.p = {}
SimplexNoise.Prev2D = {}
SimplexNoise.PrevBlur2D = {}
local r = 0
for i=1, 256 do
SimplexNoise.p[i] = (s2+math.floor(s2/i)) % 256
end
-- To remove the need for index wrapping, double the permutation table length
for i=1,#SimplexNoise.p do
SimplexNoise.p[i-1] = SimplexNoise.p[i]
SimplexNoise.p[i] = nil
end
SimplexNoise.perm = {}
for i=0,255 do
SimplexNoise.perm[i] = SimplexNoise.p[i]
SimplexNoise.perm[i+256] = SimplexNoise.p[i]
end
end
-- just to have some data
--SimplexNoise.seedP(101)
SimplexNoise.Dot2D = function(tbl, x, y)
return tbl[1]*x + tbl[2]*y
end
SimplexNoise.Prev2D = {}
-- 2D simplex noise
SimplexNoise.Noise2D = function(xin, yin)
if SimplexNoise.Prev2D[xin] and SimplexNoise.Prev2D[xin][yin] then return SimplexNoise.Prev2D[xin][yin] end
local n0, n1, n2 -- Noise contributions from the three corners
-- Skew the input space to determine which simplex cell we're in
local F2 = 0.5*(math.sqrt(3.0)-1.0)
local s = (xin+yin)*F2 -- Hairy factor for 2D
local i = math.floor(xin+s)
local j = math.floor(yin+s)
local G2 = (3.0-math.sqrt(3.0))/6.0
local t = (i+j)*G2
local X0 = i-t -- Unskew the cell origin back to (x,y) space
local Y0 = j-t
local x0 = xin-X0 -- The x,y distances from the cell origin
local y0 = yin-Y0
-- For the 2D case, the simplex shape is an equilateral triangle.
-- Determine which simplex we are in.
local i1, j1; -- Offsets for second (middle) corner of simplex in (i,j) coords
if(x0>y0) then
i1=1
j1=0 -- lower triangle, XY order: (0,0)->(1,0)->(1,1)
else
i1=0
j1=1 -- upper triangle, YX order: (0,0)->(0,1)->(1,1)
end
-- A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
-- a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
-- c = (3-sqrt(3))/6
local x1 = x0 - i1 + G2 -- Offsets for middle corner in (x,y) unskewed coords
local y1 = y0 - j1 + G2
local x2 = x0 - 1.0 + 2.0 * G2 -- Offsets for last corner in (x,y) unskewed coords
local y2 = y0 - 1.0 + 2.0 * G2
-- Work out the hashed gradient indices of the three simplex corners
local ii = LuaBit.band(i , 255)
local jj = LuaBit.band(j , 255)
local gi0 = SimplexNoise.perm[ii+SimplexNoise.perm[jj]] % 12
local gi1 = SimplexNoise.perm[ii+i1+SimplexNoise.perm[jj+j1]] % 12
local gi2 = SimplexNoise.perm[ii+1+SimplexNoise.perm[jj+1]] % 12
-- Calculate the contribution from the three corners
local t0 = 0.5 - x0*x0-y0*y0
if t0<0 then
n0 = 0.0;
else
t0 = t0 * t0
n0 = t0 * t0 * SimplexNoise.Dot2D(SimplexNoise.Gradients3D[gi0], x0, y0) -- (x,y) of Gradients3D used for 2D gradient
end
local t1 = 0.5 - x1*x1-y1*y1;
if (t1<0) then
n1 = 0.0
else
t1 = t1*t1
n1 = t1 * t1 * SimplexNoise.Dot2D(SimplexNoise.Gradients3D[gi1], x1, y1)
end
local t2 = 0.5 - x2*x2-y2*y2;
if (t2<0) then
n2 = 0.0
else
t2 = t2*t2
n2 = t2 * t2 * SimplexNoise.Dot2D(SimplexNoise.Gradients3D[gi2], x2, y2)
end
-- Add contributions from each corner to get the final noise value.
-- The result is scaled to return values in the localerval [-1,1].
local retval = 70.0 * (n0 + n1 + n2)
if not SimplexNoise.Prev2D[xin] then SimplexNoise.Prev2D[xin] = {} end
SimplexNoise.Prev2D[xin][yin] = retval
return retval
end
SimplexNoise.e = 2.71828182845904523536
SimplexNoise.PrevBlur2D = {}
SimplexNoise.GBlur2D = function(x,y,stdDev)
if SimplexNoise.PrevBlur2D[x] and SimplexNoise.PrevBlur2D[x][y] and SimplexNoise.PrevBlur2D[x][y][stdDev] then return SimplexNoise.PrevBlur2D[x][y][stdDev] end
local pwr = ((x^2+y^2)/(2*(stdDev^2)))*-1
local ret = ((1/(2*math.pi*(stdDev^2)))*e)^pwr
if not SimplexNoise.PrevBlur2D[x] then PrevBlur2D[x] = {} end
if not SimplexNoise.PrevBlur2D[x][y] then PrevBlur2D[x][y] = {} end
SimplexNoise.PrevBlur2D[x][y][stdDev] = ret
return ret
end
SimplexNoise.FractalSum2DNoise = function(x,y,itier) --very expensive, much more so that standard 2D noise.
local ret = SimplexNoise.Noise2D(x,y)
for i=1,itier do
local itier = 2^itier
ret = ret + (i/itier)*(Noise2D(x*(itier/i),y*(itier/i)))
end
return ret
end
SimplexNoise.FractalSumAbs2DNoise = function(x,y,itier) --very expensive, much more so that standard 2D noise.
local ret = math.abs(SimplexNoise.Noise2D(x,y))
for i=1,itier do
local itier = 2^itier
ret = ret + (i/itier)*(math.abs(SimplexNoise.Noise2D(x*(itier/i),y*(itier/i))))
end
return ret
end
SimplexNoise.Turbulent2DNoise = function(x,y,itier) --very expensive, much more so that standard 2D noise.
local ret = math.abs(SimplexNoise.Noise2D(x,y))
for i=1,itier do
local itier = 2^itier
ret = ret + (i/itier)*(math.abs(SimplexNoise.Noise2D(x*(itier/i),y*(itier/i))))
end
return math.sin(x+ret)
end
--return SimplexNoise

View file

@ -0,0 +1,112 @@
KOBOLD
genes
{5,11,6,4,12,8,8,4,3,1,2,5,5,2,6,7,11,12,2,11,5,7,2,4,10,3,8,5,4,4,1,8,10,7,12,9,6,11,8,8,8,3,6,1,4,9,6,7,4,8,1,8,5,9,12,10,10,10,1,9,1,6,12,8,11,9,4,1,5,12,2,1,11,8,4,9,5,7,7,2,2,9,8,9,6,3,10,2,5,8,3,1,12,10,11,8,10,7,3,12,4,4,1,2,3,11,12,8,12,10,7,9,10,9,3,10,1,4,11,9,8,10,8,8,6,10,3,6,2,12,9,4,1,7,11,11,7,9,5,12,11,9,12,5,6,7,4,3,11,12,11,5,11,10,12,9,5,11,11,11,2,11,9,10,12,7,7,6,4,6,2,12,8,1,7,10,6,2,3,1,4,8,10,5,12,12,3,9,11,12,4,7,4,8,7,11,2,8,11,7,11,2,9,11,4,12,6,10,10,6,11,4,11,3,7,2,8,12,1,7,6,12,3,2,2,8,6,12,11,11,8,1,11,4,10,5,12,11,11,4,4,5,3,6,5,4,8,11,9,8,7,7,4,3,7,8,7,7,11,2,12,3,2,5,3,2,12,12,8,8,6,8,9,3,9,12,11,8,1,11,5,2,1,2,2,7,3,4,10,6,9,6,10,2,12,1,1,1,12,5,8,4,3,1,9,8,7,8,2,2,4,5,2,6,10,7,12,6,6,5,11,9,8,12,11,8,6,11,12,4,3,5,7,4,6,11,4,9,5,3,8,4,3,1,3,11,1,6,12,12,3,5,1,11,9,12,9,3,8,8,7,6,7,9,11,11,3,11,8,8,5,12,4,6,4,9,1,2,12,8,2,6,5,9,10,3,11,1,3,4,4,7,9,2,2,2,8,7,9,10,9,10,9,9,4,12,8,7,12,2,12,9,2,3,6,8,6,6,8,11,6,2,12,12,6,10,9,9,2,12,8,7,12,3,2,9,6,6,10,2,12,8,5,10,10,3,4,2,10,3,2,5,12,6,8,4,4,5,4,10,1,7,4,4,9,4,1,6,10,12,1,6,5,6,7,7,4,10,7,4,4,3,2,9,10,3,1,6,8,1,10,2,3,2,12,6,7,3,10,6,1,8,10,7,11,5,12,2,9,5,4,5,5,4,11,5,3,1,8,7,8,5,10,12,8,8,12,9,4,4,4,9,3,9,7,2,3,1,6,12,7,9,7,4,6,3,5,12,7,5,3,6,10,5,5,8,9,5,8,3,11,9,12,11,6,3,4,4,6,10,1,8,2,8,12,12,4,5,6,4,9,8,10,1,2,3,12,7,7,9,3,6,12,1,12,5,1,11,3,2,3,4,3,3,4,6,11,10,3,1,8,1,1,1,8,3,6,4,4,5,11,8,8,3,6,4,1,1,8,9,5,2,9,8,6,11,3,12,8,6,12,9,11,11,7,10,7,6,3,12,8,12,2,6,3,8,4,8,7,5,5,7,2,12,2,1,1,4,10,8,5,4,1,1,3,5,1,8,9,1,2,2,1,5,4,5,2,9,1,10,1,11,11,8,5,9,11,3,10,2,11,2,4,7,7,2,7,6,8,9,3,1,11,9,9,9,10,8,11,7,10,8,1,11,5,7,1,6,8,11,6,10,8,4,2,6,4,5,7,2,9,11,3,7,2,12,5,8,7,9,5,11,1,4,8,6,4,5,5,3,1,3,5,11,6,7,9,11,10,8,6,9,4,1,11,11,9,5,4,4,4,12,4,10,3,11,11,1,12,12,5,7,9,4,8,1,12,10,2,11,11,3,3,11,2,3,9,12,1,1,10,5,1,5,6,12,5,12,4,11,5,1,10,10,6,4,12,5,3,7,6,6,11,7,4,4,4,7,5,12,5,12,9,3,11,5,4,6,6,5,5,2,5,4,1,6,3,4,2,10,11,7,1,4,9,11,1,6,8,10,1,9,6,7,5,3,9,1,1,8,4,5,10,1,3,9,12,6,6,7,10,7,4,12,5,5,5,7,9,5,5,11,10,7,4,6,3,12,9,8,3,11,12,11,12,3,5,3,6,1,1,9,11,11,3,7,7,7,10,10,6,12,10,11,9,2,1,2,12,1,1,1,6,4,9,3,11,12,10,2,1,4,2,5,4,3,2,1,3,12,7,5,12,6,7,4,6,11,11,6,10,2,6,3,6,10,12,1,11,10,1,12,2,8,10,7,7,1,9,6,1,8,3,9,4,11,8,4,10,12,1,4,3,6,11,9,9,6,3,8,4,9,2,5,1,5,1,1,7,11,7,8,12,3,10,9,1,1,3,11,4,3,4,8,6,3,12,2,8,8,4,12,4,12,7,10,4,1,9,10,3,3,1,10,5,8,11,10,5,7,4,5,9,10,5,9,5,6,4,10,1,9,8,9,5,3,3,5,2,7,5,1,12,4,2,9,2,1,9,4,5,4,5,2,6,4,8,2,8,7,2,8,2,12,8,1,2,8,7,6,9,8,10,10,11,8,6,10,8,9,9,11,7,12,9,9,2,4,10,4,11,9,7,9,5,2,9,7,4,5,12,12,12,5,9,10,4,11,7,4,6,12,7,3,8,9,6,1,2,8,12,9,12,6,9,2,10,12,3,3,12,2,9,3,1,7,12,9,9,3,7,8,8,6,3,5,12,4,1,7,12,2,5,10,7,2,11,6,1,11,2,9,5,4,3,3,8,5,10,5,9,8,3,1,9,3,4,8,3,10,5,2,6,12,8,12,2,7,9,9,12,6,2,3,3,10,1,10,6,12,6,9,7,6,11,4,4,1,11,7,1,8,9,11,6,8,1,11,10,8,9,5,6,3,10,3,9,11,12,3,7,6,8,1,8,7,10,10,9,12,4,1,6,10,6,7,1,11,11,3,4,2,11,6,5,5,3,6,10,8,4,6,8,10,3,7,5,2,1,7,10,7,9,12,8,10,1,12,4,5,8,9,3,12,4,2,2,2,1,12,2,12,4,1,4,12,1,3,8,6,12,7,1,4,7,9,4,2,9,12,8,6,7,8,10,9,6,8,4,3,1,7,4,6,12,2,10,6,4,10,6,2,9,3,6,9,4,12,8,9,10,8,9,4,1,12,3,12,8,3,9,4,4,7,1,2,7,8,12,8,7,4,5,9,11,2,6,12,11,6,4,8,8,10,3,2,2,8,2,6,11,2,9,11,11,11,3,10,5,7,11,1,12,6,11,2,6,1,4,2,6,10,3,3,2,6,9,6,1,7,9,3,4,1,9,6,4,6,3,3,7,8,10,7,10,1,9,2,12,10,3,9,8,2,5,3,8,2,1,9,9,11,1,3,2,5,9,4,6,7,9,5,3,7,12,2,12,4,6,6,10,5,4,9,8,9,10,1,9,11,8,10,9,7,9,3,7,8,3,9,3,7,4,4,10,11,7,4,6,3,10,10,1,7,6,6,6,1,10,5,12,4,11,2,2,8,8,6,6,10,7,6,7,6,9,6,9,4,7,4,4,9,8,3,6,11,10,10,3,10,5,9,5,12,12,9,6,11,7,2,3,4,10,11,9,9,9,6,3,6,9,12,6,1,4,2,11,8,2,6,7,3,8,3,12,8,9,9,12,9,1,1,3,10,1,4,8,10,9,5,10,10,6,1,12,1,11,6,9,3,6,11,11,2,3,9,1,7,6,2,11,11,6,7,4,12,12,5,7,12,8,9,5,11,3,1,12,1,6,5,7,1,1,7,3,8,3,9,8,5,8,9,11,5,10,3,1,3,1,7,5,10,8,11,11,9,1,7,5,3,7,1,7,5,9,11,2,7,10,3,1,6,8,12,1,5,1,11,5,12,6,7,11,7,1,1,12,8,7,8,8,5,6,9,2,2,5,4,6,1,5,6,2,11,5,5,11,3,9,4,12,7,12,2,3,8,6,10,4,11,6,7,1,6,10,10,4,8,6,1,6,7,1,3,7,9,2,8,4,8,12,5,12,12,2,9,7,7,1,4,8,5,9,3,10,10,11,7,3,2,8,11,6,8,2,3,5,4,6,1,6,5,4,8,2,7,12,2,5,2,1,11,10,6,5,11,8,6,7,10,7,9,11,12,8,4,11,9,4,4,2,2,6,11,5,5,2,10,9,3,8,10,5,8,4,5,1,4,8,2,11,2,6,2,5,1,7,11,5,12,3,10,8,7,3,12,1,12,1,9,8,5,5,5,11,3,8,1,7,8,7,7,7,3,1,4,10,3,6,1,3,12,1,3,7,5,10,12,2,5,2,1,7,8,7,9,2,4,2,2,11,6,6,7,8,10,9,9,1,10,3,5,7,10,4,2,3,7,3,5,10,6,8,1,8,10,8,8,4,11,11,2,4,8,}
focus
0.23404331377891
firstname
"Yunuta"
midname
"Patachpuanuy"
family
"Kukipuinzik"
food
{}
END_KOBOLD
KOBOLD
genes
{5,5,7,4,7,11,7,11,6,4,6,8,12,2,6,11,3,12,2,5,4,4,2,1,9,7,8,2,11,5,4,8,4,11,11,2,6,11,8,8,4,3,11,2,1,5,2,7,12,2,10,1,11,9,7,10,11,7,12,9,6,6,12,12,6,11,1,6,4,8,3,1,10,6,4,3,3,4,7,1,7,2,6,1,12,9,4,8,5,10,3,5,11,5,11,8,9,10,3,3,4,9,8,11,4,12,5,5,2,8,10,9,11,2,3,12,1,6,10,9,9,1,8,7,10,10,10,6,2,10,11,12,3,1,3,1,9,9,4,12,11,11,12,11,3,12,9,8,11,3,7,5,12,4,5,1,4,2,11,11,2,1,6,4,10,2,7,7,4,1,2,11,2,2,6,1,12,1,11,3,7,8,10,3,7,3,6,11,11,10,4,1,4,8,11,11,8,8,10,7,10,3,2,11,3,1,3,10,11,6,1,12,9,11,11,2,5,6,1,11,4,12,10,2,3,4,10,12,5,11,9,2,4,5,4,6,2,2,2,12,12,5,3,9,8,11,12,11,5,1,12,2,12,3,6,3,4,9,12,11,11,3,2,5,8,6,2,12,1,1,2,10,5,9,12,12,4,12,11,11,5,1,1,2,6,9,3,4,4,6,9,6,3,9,7,1,4,5,5,5,5,2,9,3,10,6,1,9,12,8,12,12,5,5,2,7,7,6,8,5,10,3,8,8,11,12,10,7,12,3,7,2,4,11,2,9,4,3,7,11,2,9,10,12,11,10,6,8,12,12,9,8,2,5,8,1,6,5,3,8,7,7,12,8,10,9,3,10,1,8,2,12,10,12,1,4,11,2,12,12,6,12,4,1,1,1,11,1,3,1,4,2,8,2,3,9,2,4,2,1,4,9,11,11,4,6,6,11,5,7,1,2,10,3,11,8,3,8,9,7,10,12,7,1,11,12,3,5,12,6,8,12,11,9,8,1,5,1,10,4,7,3,5,7,9,2,11,11,10,3,4,8,6,7,11,1,10,6,1,7,9,4,1,8,6,1,1,4,7,2,6,10,7,12,2,7,5,12,7,5,2,12,2,8,11,1,6,6,5,5,4,10,4,2,12,6,5,5,1,9,4,12,10,12,6,4,1,3,1,1,4,2,5,4,9,8,11,5,4,11,4,3,10,6,8,9,9,9,6,7,8,12,6,9,9,3,3,5,2,2,5,4,12,8,1,3,6,12,12,11,7,1,10,5,12,4,5,10,11,7,1,7,6,2,9,10,7,4,2,12,1,12,2,8,7,12,5,1,12,4,4,4,4,10,12,9,10,10,12,5,3,1,10,12,12,12,1,1,2,6,11,2,11,8,12,8,11,10,8,7,8,12,3,8,2,10,9,10,10,7,8,8,10,9,5,5,4,1,9,8,6,4,7,3,2,6,5,7,8,9,4,3,11,12,7,11,7,9,2,12,11,8,3,3,3,1,12,5,4,5,9,9,4,4,2,1,11,3,10,1,8,8,9,3,7,5,12,8,7,10,5,3,5,12,12,6,8,8,11,2,7,11,3,9,5,6,9,3,1,8,11,5,4,5,7,5,11,1,2,1,6,6,3,7,7,12,6,1,9,10,12,7,10,11,5,8,3,6,5,12,6,10,4,4,6,6,4,5,7,3,8,11,3,7,4,6,1,8,11,1,6,11,4,2,8,12,3,4,2,3,5,10,8,8,2,9,9,12,7,9,5,6,1,1,10,6,8,6,3,11,11,9,2,10,4,4,11,1,7,3,2,3,4,11,3,5,7,11,3,9,11,9,5,4,5,3,9,4,1,7,9,5,2,6,1,10,5,5,11,11,2,2,7,10,6,3,4,4,4,2,5,12,9,11,6,4,11,9,5,6,10,3,3,3,11,8,11,6,9,2,1,10,11,5,11,5,11,4,6,11,5,7,5,7,7,2,1,7,7,4,1,10,10,7,6,7,3,2,12,7,4,5,10,8,2,3,6,3,6,11,8,6,1,12,6,1,11,7,7,3,5,8,4,10,6,10,3,7,9,8,9,9,3,3,1,6,10,7,8,2,6,12,11,7,7,8,7,7,2,10,6,10,2,12,12,2,9,5,3,7,8,5,5,4,8,3,11,12,12,1,4,11,2,5,4,12,8,1,5,12,7,1,11,11,6,6,4,1,4,6,1,6,6,4,8,5,2,1,10,12,11,8,12,3,10,1,9,12,2,8,6,8,10,9,6,11,10,6,10,7,11,5,11,6,1,12,4,1,4,3,1,8,5,5,1,12,12,4,7,1,5,8,12,11,8,12,6,8,12,11,4,11,4,7,4,3,12,12,3,12,4,8,3,9,7,10,4,3,2,12,7,1,1,1,2,8,7,7,12,2,12,6,3,10,9,1,11,5,7,10,1,7,9,12,5,3,1,5,11,2,3,1,4,2,5,1,2,9,2,11,10,4,2,6,6,10,6,4,8,7,2,11,9,12,8,5,4,2,9,11,3,1,4,10,8,4,6,5,8,9,9,11,1,12,12,9,6,4,5,3,4,2,6,8,7,5,6,3,4,8,4,12,8,12,3,6,1,11,1,4,6,12,6,12,1,8,10,5,2,3,12,9,3,6,2,12,8,2,4,3,6,8,11,11,1,7,10,9,2,10,1,12,2,6,4,12,2,4,1,12,7,4,5,10,3,11,11,1,6,6,10,9,7,8,3,10,9,6,10,8,12,11,3,9,9,8,3,2,3,10,5,4,5,4,9,12,8,1,8,10,3,6,2,6,3,9,2,10,7,6,2,2,4,11,7,8,6,12,1,10,7,2,11,11,1,3,5,11,9,7,10,5,1,9,12,12,12,11,12,10,4,11,8,2,10,3,2,7,9,2,2,2,6,9,9,11,11,10,3,6,5,12,5,2,1,6,2,10,2,8,10,7,2,10,1,7,12,10,7,2,6,7,12,8,12,11,9,6,11,5,11,8,5,7,5,10,6,4,11,5,2,12,9,7,7,7,5,10,3,5,7,10,8,4,7,9,1,6,8,12,1,4,2,12,2,10,5,1,5,3,6,9,8,3,12,1,6,7,8,10,6,12,9,2,10,4,4,2,8,3,11,9,11,3,1,12,3,12,5,3,9,4,8,7,3,2,6,3,11,4,9,9,4,12,9,6,1,7,11,4,5,1,6,5,3,2,2,4,4,10,8,1,12,2,11,11,12,7,12,9,11,8,2,1,11,1,6,10,6,7,10,9,11,2,12,6,5,6,4,6,9,4,4,4,2,3,9,9,12,5,3,8,5,2,10,10,9,2,4,9,11,10,6,7,3,4,1,2,4,9,11,4,10,4,10,6,4,5,8,7,2,12,10,7,8,2,12,4,9,6,8,5,6,5,1,9,2,11,9,11,1,10,9,7,5,8,6,8,9,11,8,2,2,4,10,5,12,5,2,6,6,12,11,9,4,7,6,1,8,11,12,1,10,2,7,6,11,3,6,3,5,5,12,11,9,12,9,2,7,1,11,9,7,11,6,3,10,5,3,5,5,12,10,10,12,8,3,12,12,7,6,12,10,12,9,11,2,5,9,6,12,4,6,1,9,8,5,8,4,6,5,1,7,3,10,11,8,3,11,9,8,4,6,10,11,10,5,3,6,3,1,6,12,8,6,2,11,1,9,4,1,9,3,11,2,5,2,7,10,2,1,2,6,7,3,8,7,11,1,10,8,8,6,1,8,7,12,11,9,11,6,7,8,4,2,3,12,9,10,7,12,1,3,7,5,10,2,8,7,11,5,11,8,6,9,3,11,1,12,8,12,6,7,5,5,3,2,6,10,1,5,12,2,1,12,6,1,12,9,10,10,5,3,6,9,10,7,5,1,9,6,2,6,4,6,4,6,9,5,6,9,7,4,11,6,5,9,11,11,6,3,11,9,8,3,2,6,3,6,8,4,8,1,1,6,12,4,10,11,8,6,6,8,12,7,8,4,7,5,9,12,5,1,12,9,1,4,10,8,6,12,1,4,3,10,1,10,7,2,3,6,9,6,9,2,3,12,8,1,11,6,6,9,3,1,1,12,9,5,7,6,5,6,11,5,1,2,9,1,1,8,9,10,6,8,8,11,9,10,4,9,5,1,9,9,11,5,12,12,10,12,10,8,9,9,11,10,10,8,5,11,1,9,6,5,1,5,11,9,12,8,6,10,4,3,11,1,2,2,11,3,8,5,8,8,8,11,5,7,9,7,6,1,1,10,4,7,10,12,7,1,5,3,7,2,6,10,9,10,8,8,2,7,6,11,9,9,4,8,7,7,6,4,3,2,10,5,1,2,8,9,10,7,1,4,7,4,11,3,5,12,12,12,1,2,2,12,1,11,7,10,4,1,8,}
focus
0.33772664250454
firstname
"Kafaki"
midname
"Patachpuanuy"
family
"Kukipuinzik"
food
{4,}
END_KOBOLD
KOBOLD
genes
{5,11,3,6,2,3,12,11,3,11,2,11,10,3,12,9,10,3,9,8,1,3,4,3,10,6,8,4,3,5,10,9,4,10,9,7,12,12,3,5,11,3,12,8,2,9,11,4,12,7,12,7,8,10,10,7,7,1,2,7,10,7,10,4,11,9,12,9,7,1,1,8,1,3,3,10,2,10,2,8,1,5,8,9,11,9,8,3,8,11,4,3,3,12,2,10,5,8,6,3,5,12,7,10,4,11,5,11,8,4,7,11,7,4,8,8,9,10,3,3,6,9,5,5,11,7,12,6,7,1,2,5,12,10,3,4,9,11,6,1,2,7,12,10,3,8,5,3,11,1,10,9,11,5,11,1,8,5,10,7,7,10,5,2,12,10,9,3,7,8,5,1,2,7,5,8,12,9,3,11,5,8,6,4,2,9,10,9,12,7,10,7,3,4,5,3,1,3,10,12,5,12,2,8,4,4,7,7,11,6,1,11,8,11,4,8,5,5,7,10,5,9,8,10,3,10,8,1,8,4,2,9,11,1,2,5,2,10,7,6,12,9,3,5,1,8,4,3,10,6,12,1,6,1,12,8,2,11,4,6,11,3,1,2,1,6,7,10,10,1,10,8,9,8,4,12,11,4,8,6,4,8,10,9,1,9,7,12,6,8,4,12,10,1,9,8,11,11,3,2,6,11,12,3,3,12,9,2,3,12,3,6,8,7,2,4,11,4,11,3,7,4,3,4,11,2,5,10,6,6,11,12,5,9,11,6,12,3,5,3,5,1,9,1,4,9,1,7,8,12,12,10,1,2,5,8,1,3,6,1,7,2,5,2,8,12,1,11,5,7,4,12,6,1,5,5,8,5,6,4,8,1,11,8,8,11,10,8,10,3,10,8,6,5,12,9,6,10,8,1,5,1,9,12,5,9,5,2,10,5,12,1,10,7,7,7,2,3,2,4,11,6,5,12,12,9,1,7,8,10,10,1,7,7,9,5,4,5,3,9,11,1,8,7,9,1,8,11,3,1,7,1,2,11,1,1,12,4,1,10,10,5,3,1,6,2,12,11,2,12,6,2,7,7,3,9,6,11,10,12,6,9,6,11,4,10,3,12,5,7,3,4,3,7,2,8,8,10,12,6,5,3,8,10,12,5,3,12,1,2,5,11,8,4,4,10,3,9,7,8,9,7,10,1,4,1,9,7,6,1,10,9,12,3,3,1,12,11,7,8,12,9,11,9,6,4,6,5,1,11,1,11,9,2,9,8,2,10,10,7,6,5,3,6,4,8,7,8,1,8,6,8,7,4,6,5,2,7,1,8,8,12,2,4,1,4,8,4,12,3,11,11,5,10,3,8,9,8,7,7,2,10,2,8,6,7,6,12,12,1,11,4,6,12,4,10,11,6,10,8,4,4,7,11,8,1,7,3,9,7,4,12,4,12,5,7,4,6,8,10,10,6,6,12,7,11,7,4,9,10,11,11,12,5,5,11,10,12,1,12,1,6,1,5,2,1,3,9,5,4,4,9,1,2,7,1,4,10,7,11,7,2,7,1,12,5,7,7,11,12,5,3,2,1,8,12,4,3,11,7,6,8,12,3,2,2,3,9,3,12,10,8,7,3,12,11,7,10,6,1,3,11,4,3,2,1,6,1,9,6,1,5,9,6,10,2,5,10,5,2,4,6,9,6,10,10,5,4,8,5,5,2,9,2,1,8,10,3,7,5,2,9,10,7,5,2,11,9,8,2,4,2,12,3,4,6,6,11,5,11,6,11,7,3,11,12,4,8,11,9,12,3,10,7,7,2,5,3,3,5,5,11,7,10,11,11,12,5,2,7,9,9,10,10,12,11,2,7,3,11,8,10,2,4,2,2,9,1,3,9,6,5,1,6,10,7,2,10,3,12,4,8,5,12,10,2,12,4,1,12,4,10,2,10,6,2,10,6,2,12,11,9,3,3,10,7,8,7,11,1,1,12,7,5,6,1,12,6,8,6,2,7,4,3,7,3,10,7,12,11,11,12,11,7,2,2,8,11,8,3,7,1,9,8,9,5,10,8,9,1,1,1,8,7,1,5,10,3,5,7,2,3,8,12,6,9,1,6,9,4,3,2,12,9,12,2,9,9,12,7,8,11,11,11,10,5,1,1,5,4,1,12,8,4,4,9,1,6,8,5,8,12,2,7,2,3,7,9,2,12,6,1,12,8,10,11,7,11,6,4,9,6,4,5,6,8,3,1,4,6,12,5,5,5,7,7,7,10,8,6,7,7,8,5,11,10,12,9,9,9,9,11,5,9,1,9,5,6,6,1,10,9,12,6,11,3,12,4,7,10,4,9,10,7,11,8,8,10,11,3,4,8,1,8,1,10,10,9,11,9,5,12,7,1,3,6,4,6,12,10,8,3,8,6,4,1,11,5,8,3,9,11,6,2,10,1,6,1,4,10,5,4,7,12,3,11,10,10,10,11,11,6,10,12,5,3,6,3,2,8,10,11,1,9,7,11,3,1,10,12,2,1,7,6,1,8,11,6,1,8,9,10,4,2,7,10,10,4,7,5,12,4,11,11,6,12,7,12,5,9,9,11,2,11,10,3,1,11,7,9,2,9,4,1,4,11,2,6,4,6,5,5,6,7,9,10,3,1,3,9,8,7,9,10,6,11,5,9,9,12,8,3,6,3,10,7,12,4,11,11,10,10,12,4,4,6,4,3,1,8,9,1,8,8,12,1,9,2,11,4,2,6,8,12,1,6,11,10,2,6,9,11,2,9,5,2,8,5,4,3,6,6,1,6,2,8,2,5,4,6,10,7,7,10,9,6,6,11,1,3,11,1,6,4,9,7,1,8,10,11,8,4,5,6,12,5,7,2,11,1,4,12,1,12,3,7,8,4,9,6,2,5,7,12,4,4,8,2,4,5,4,5,7,6,2,7,11,6,6,8,4,10,3,2,10,10,4,12,4,4,3,5,3,12,9,8,11,5,1,4,6,7,6,3,9,6,6,3,6,10,7,11,3,8,6,2,3,5,6,8,1,10,9,3,11,2,4,4,7,12,3,3,8,7,12,4,6,10,6,1,8,11,10,11,12,8,10,6,8,6,6,5,8,8,10,9,7,4,4,11,8,11,2,5,6,10,3,9,6,11,6,7,9,8,4,12,9,6,11,6,2,6,7,8,11,5,10,8,7,9,5,9,8,11,2,2,7,10,12,8,6,8,5,8,11,11,12,8,2,9,7,2,2,1,6,1,8,1,12,3,4,10,9,2,12,2,10,12,11,7,12,5,2,3,11,5,7,12,3,10,10,1,10,6,7,1,5,9,5,10,1,3,3,1,4,3,11,6,5,11,11,3,11,12,10,6,6,9,4,4,2,10,10,5,11,8,1,1,1,2,4,6,10,10,11,1,7,12,2,1,1,7,10,11,9,10,8,10,6,2,4,6,2,2,1,12,4,11,1,2,12,2,2,2,7,4,1,11,8,11,12,6,3,1,5,1,12,7,10,7,2,12,2,6,2,1,10,2,4,12,1,5,1,6,4,5,3,12,6,11,1,4,8,3,9,10,10,9,2,6,7,11,9,5,3,9,11,9,5,6,4,6,10,10,4,8,7,4,9,11,1,11,7,7,3,8,12,9,2,10,3,2,2,12,6,5,6,8,9,1,6,2,1,1,3,2,1,7,12,11,5,2,4,5,3,4,12,4,5,7,8,4,1,10,6,4,3,2,5,1,2,8,3,1,12,12,10,6,2,1,1,5,10,8,1,8,11,4,4,3,12,4,6,9,9,9,9,2,11,7,4,6,4,5,3,6,10,8,4,3,10,3,3,11,9,11,8,8,1,6,3,10,10,10,5,3,6,9,10,1,7,1,2,3,12,11,6,10,3,2,6,1,6,8,11,1,3,6,2,4,6,12,1,12,6,12,2,11,2,4,11,4,7,11,2,6,6,6,1,4,3,5,1,3,3,1,5,3,3,1,4,7,1,3,10,1,8,2,3,3,12,12,12,12,2,9,9,7,3,3,4,6,1,3,11,2,5,4,1,1,1,9,10,8,7,3,8,5,9,3,4,1,8,2,3,8,10,2,10,10,1,7,7,7,3,5,1,11,9,7,6,5,4,2,6,8,4,11,4,2,2,2,7,6,10,11,2,8,2,3,1,8,5,7,8,2,10,8,11,2,5,8,7,10,5,2,12,8,11,11,8,6,1,5,11,1,2,1,3,6,8,12,5,5,11,4,7,9,10,5,8,12,7,10,1,7,10,2,5,7,2,1,2,12,3,9,11,3,10,8,2,9,1,2,4,12,10,1,4,1,1,6,10,3,8,1,1,2,10,10,12,1,5,8,5,6,6,9,5,7,5,2,11,4,11,3,12,1,5,7,10,11,6,11,}
focus
0.28907365487597
firstname
"Kisasku"
midname
"Znusasianuy"
family
"Tukipusanzik"
food
{5,}
END_KOBOLD
KOBOLD
genes
{8,2,8,9,1,4,8,10,3,4,1,9,1,7,11,11,1,8,12,6,5,5,9,12,10,11,5,3,3,10,6,3,2,9,9,10,11,5,10,6,3,3,4,3,5,9,5,2,5,11,12,4,5,1,9,9,4,10,5,9,1,10,4,8,6,9,8,1,5,12,6,11,11,12,2,1,5,7,7,6,11,8,4,9,6,3,10,2,1,4,11,7,9,1,11,8,10,10,1,12,4,7,2,1,12,4,5,9,3,8,10,9,4,3,9,3,11,4,7,6,2,6,9,9,9,10,9,6,7,7,9,8,11,12,3,6,7,7,7,1,2,6,5,10,3,7,4,6,12,11,5,4,10,6,10,9,1,5,2,11,2,1,5,3,11,12,7,6,7,8,11,1,5,7,12,8,12,7,12,11,5,8,10,2,11,2,11,7,11,12,8,3,3,8,11,2,2,6,11,10,11,2,2,10,4,3,7,10,1,8,11,11,9,3,4,5,8,11,5,3,4,5,3,1,3,10,12,8,11,7,8,6,3,4,4,2,3,5,11,4,10,7,2,6,8,12,1,5,11,8,10,2,4,10,5,5,7,5,3,1,10,2,10,5,9,2,9,7,12,8,6,8,9,10,1,12,11,8,1,10,8,9,7,2,10,9,7,5,10,1,5,6,10,6,10,4,3,6,10,10,3,4,5,1,4,10,1,4,3,2,7,5,11,10,7,6,7,4,6,4,9,9,4,11,11,1,7,7,12,6,8,5,2,4,4,6,10,2,8,7,8,8,9,1,11,5,2,8,10,3,2,5,10,2,10,2,3,12,8,10,7,1,12,1,12,11,8,11,6,9,5,12,10,12,4,9,2,2,12,7,10,11,4,11,4,11,11,12,3,1,7,4,11,10,2,8,8,11,9,10,6,10,12,6,4,5,5,2,4,4,3,1,2,8,5,5,5,5,7,10,4,6,5,12,5,4,10,9,1,6,10,9,7,4,5,8,8,5,3,5,12,9,2,1,8,10,8,1,5,2,5,11,7,12,9,4,4,4,12,2,12,3,8,7,9,4,7,10,10,11,3,2,7,1,3,2,1,10,11,10,3,1,3,9,3,10,2,10,10,1,6,4,1,11,5,6,12,11,10,5,1,6,10,8,6,2,7,2,5,5,3,12,11,8,12,10,10,6,9,7,12,10,8,12,8,9,5,8,9,6,4,9,2,5,5,10,7,3,5,2,8,5,8,8,11,9,10,11,7,3,3,10,10,11,9,11,9,6,10,7,11,4,12,9,8,3,4,8,6,2,9,6,10,2,12,12,11,9,6,9,9,11,2,2,6,3,6,7,7,1,2,9,3,1,12,3,2,7,3,11,3,1,6,3,4,2,5,10,2,7,8,5,10,12,7,1,3,6,2,5,8,4,11,9,1,1,9,1,2,5,5,4,1,8,3,7,6,1,8,6,12,9,4,3,7,11,11,8,12,12,6,12,4,1,3,8,9,4,7,5,8,6,12,12,9,5,1,4,7,8,10,9,1,1,11,1,1,4,5,10,11,11,6,5,10,5,4,9,6,3,2,4,2,2,3,10,2,7,10,9,6,8,12,10,3,7,4,6,8,4,6,1,7,6,9,9,8,2,6,9,11,8,1,8,5,2,7,10,9,11,8,4,11,4,6,3,8,4,6,8,1,11,3,7,1,12,3,8,10,11,3,11,4,12,11,12,12,5,10,3,7,3,12,3,4,5,8,7,10,5,4,12,4,5,7,5,6,10,8,3,1,9,5,5,11,3,9,4,3,11,7,7,8,10,3,9,12,11,11,6,11,12,11,11,4,12,5,12,5,7,12,7,8,5,6,7,10,9,4,11,11,10,6,12,6,4,3,2,6,8,12,11,11,7,12,7,9,5,5,12,10,9,1,3,8,8,4,2,11,9,3,1,1,4,8,6,5,4,11,11,9,4,12,4,10,7,9,7,9,2,6,10,6,5,3,5,2,1,1,4,4,1,10,12,6,1,9,8,12,7,10,6,11,12,6,2,7,7,8,8,4,4,2,10,2,5,5,4,9,8,9,12,1,3,12,3,10,3,2,8,4,2,8,12,9,7,3,7,1,5,1,10,6,9,9,2,2,10,1,7,10,1,11,8,10,3,1,8,10,5,8,2,2,7,11,7,11,6,3,12,3,10,2,6,12,4,7,11,11,6,5,1,9,3,6,10,10,1,12,2,8,8,8,5,2,7,1,8,4,6,12,8,5,9,4,1,9,4,6,9,2,10,8,1,2,10,6,6,10,10,11,4,6,1,12,4,2,4,5,8,7,12,11,3,6,1,1,12,3,3,4,4,1,10,11,3,3,2,8,8,12,6,4,4,2,11,8,7,4,10,6,8,1,3,7,11,10,12,8,5,4,10,12,1,7,6,4,6,9,5,3,9,8,1,5,3,11,11,4,5,10,1,4,5,3,9,5,7,11,8,5,10,4,2,5,7,5,9,12,7,10,8,5,10,1,3,6,8,4,12,9,7,4,2,3,3,1,1,10,8,4,9,9,12,10,10,9,2,10,4,11,4,1,3,4,4,11,9,5,9,9,2,2,1,8,7,4,5,7,8,6,12,3,1,4,11,6,1,1,8,12,8,12,6,7,3,12,1,3,4,12,3,7,11,1,7,3,9,9,3,9,8,7,3,10,5,4,1,9,9,10,10,5,10,12,9,8,1,1,11,5,2,5,1,6,12,8,5,7,9,1,8,8,1,9,1,7,5,10,10,5,2,2,12,8,8,9,5,9,9,2,6,7,3,1,5,10,9,12,2,1,10,9,9,6,11,7,5,5,12,10,5,9,6,10,8,1,8,3,11,9,5,2,6,2,6,8,12,11,3,8,6,11,4,11,7,8,3,11,7,4,4,6,9,6,8,11,6,2,7,8,2,11,6,12,10,3,6,8,1,2,10,5,10,9,7,5,6,12,7,4,11,11,9,12,5,12,12,1,12,4,9,4,10,9,8,3,10,2,2,11,11,12,12,4,7,6,5,2,8,8,8,3,1,2,8,2,5,9,12,9,4,10,12,12,11,6,12,4,7,2,4,3,6,1,5,11,12,4,12,11,2,8,5,4,9,12,7,2,9,6,3,6,5,11,4,9,10,8,3,5,10,11,8,1,12,8,8,4,9,8,4,5,9,5,10,4,4,10,9,3,2,3,9,2,3,2,6,6,9,3,3,12,11,1,8,5,5,12,3,5,3,5,6,11,2,1,1,12,3,2,10,12,4,7,12,12,6,2,3,11,4,4,11,2,1,2,4,5,2,7,10,9,7,2,1,5,9,12,9,4,2,8,3,11,12,6,2,1,7,9,12,1,3,2,8,2,4,6,4,6,11,2,9,5,5,12,6,7,5,6,11,5,2,11,4,9,10,4,7,3,4,1,7,9,6,2,6,8,2,9,4,6,11,4,11,3,5,8,10,10,12,4,9,11,7,6,1,6,2,3,2,3,1,3,7,4,9,6,7,8,9,3,6,7,9,10,4,3,11,4,10,1,5,2,11,3,6,3,2,1,9,12,9,8,11,11,11,4,2,5,3,12,1,5,7,7,5,10,6,8,10,6,10,2,1,5,8,6,3,12,8,8,6,8,2,5,9,2,10,3,1,2,7,1,5,1,8,7,1,9,8,7,1,1,3,11,2,10,1,5,11,9,2,3,4,5,5,5,2,6,1,2,7,1,12,9,5,5,8,11,4,11,1,8,1,7,1,6,5,8,10,9,5,6,2,2,9,8,8,8,9,10,5,12,1,2,8,5,7,5,1,12,2,4,4,11,9,1,3,4,12,12,10,4,11,12,10,11,3,4,6,8,11,8,9,2,8,12,12,5,5,8,3,4,11,11,9,6,11,4,5,5,9,8,8,10,6,10,1,2,2,10,9,7,2,2,3,2,2,7,6,4,11,5,4,6,7,4,10,10,9,4,6,11,9,3,2,5,1,9,11,1,3,5,7,2,12,7,2,7,5,4,3,1,11,4,10,5,7,8,4,9,3,9,8,3,7,4,5,7,7,7,2,12,3,5,1,4,8,7,5,9,2,3,2,12,10,8,10,12,4,12,6,11,1,1,8,7,4,5,6,4,1,7,7,3,9,4,4,10,7,12,11,5,10,2,2,4,7,6,4,3,8,4,11,6,7,6,2,11,2,12,2,5,6,6,3,2,12,2,5,11,7,7,1,8,8,3,9,9,3,9,11,11,12,7,3,1,8,7,12,3,3,1,12,5,7,3,9,2,5,8,3,8,2,5,7,9,10,12,2,7,6,8,5,12,8,3,10,9,6,9,12,8,8,5,9,1,8,4,8,5,7,7,8,3,5,12,7,10,4,8,9,8,10,2,1,3,4,11,2,1,8,}
focus
0.15512212367605
firstname
"Tatakaka"
midname
"Napauuanuy"
family
"Kukipuinzik"
food
{4,}
END_KOBOLD
KOBOLD
genes
{5,11,1,12,7,2,11,12,12,9,2,9,10,3,9,8,8,4,3,1,1,11,9,10,4,2,11,4,1,11,6,10,2,3,11,11,4,2,8,1,8,9,1,10,6,4,8,4,4,8,9,9,1,1,8,10,1,1,1,8,5,2,2,2,6,11,4,7,5,2,4,9,4,7,6,3,4,1,3,9,12,2,6,3,3,7,3,2,5,5,1,3,7,7,5,7,8,12,5,4,8,12,9,10,6,7,12,1,4,7,5,12,8,11,12,12,8,10,7,10,4,8,10,11,11,3,3,2,4,11,12,6,12,12,7,6,8,7,6,3,10,1,6,9,12,12,1,10,12,3,6,10,11,12,6,10,5,5,5,2,5,8,5,6,10,3,1,7,2,7,1,5,2,11,5,12,8,4,2,3,9,9,3,6,7,5,10,7,9,1,1,9,9,4,10,7,1,4,2,10,2,5,4,10,7,1,2,10,3,6,9,5,10,6,1,5,2,8,4,4,7,10,5,10,12,9,7,4,7,2,7,4,1,10,6,6,7,2,10,11,12,5,10,1,9,2,11,10,9,12,1,4,11,6,12,5,2,4,1,3,2,4,3,6,1,5,2,3,1,7,9,7,7,4,1,1,9,9,2,8,10,2,5,7,10,9,6,12,9,5,9,8,7,7,4,5,9,2,1,11,3,6,7,10,7,3,2,9,3,9,10,8,5,5,8,8,9,7,11,9,2,4,9,4,5,9,8,3,9,8,11,3,4,3,11,4,8,8,5,4,6,9,2,12,5,4,12,2,11,5,9,3,3,11,12,3,12,2,4,4,6,6,3,1,2,12,1,7,2,2,8,4,7,6,3,7,5,1,6,2,7,10,10,7,6,3,7,3,3,5,11,6,7,6,4,4,10,11,8,10,11,10,5,5,9,4,5,5,12,4,6,8,5,6,2,1,5,12,10,7,11,2,6,10,10,2,1,3,8,1,8,6,4,2,11,11,4,9,4,5,5,2,8,11,11,7,5,5,10,8,6,1,8,6,8,10,4,12,2,11,9,5,12,6,1,4,10,10,7,12,6,10,7,4,11,10,5,3,1,3,4,8,8,12,8,11,2,9,5,6,2,8,1,1,7,5,2,6,11,2,8,3,12,1,1,8,9,3,8,2,1,12,11,10,3,8,8,11,3,8,12,3,7,10,6,6,10,7,1,12,10,7,11,3,6,6,11,3,4,11,3,11,8,11,10,4,11,5,6,3,5,1,3,5,8,10,3,10,10,6,9,5,8,3,8,1,9,1,7,5,1,5,11,11,5,7,9,4,3,4,5,11,3,2,3,2,3,2,6,2,3,5,7,11,12,9,1,11,3,6,3,6,3,10,6,10,1,3,12,1,6,1,8,9,12,4,11,1,2,11,7,2,1,5,7,12,2,4,5,8,4,11,12,1,8,4,10,10,2,4,4,12,4,3,2,2,11,2,9,11,7,6,11,5,7,8,3,8,7,12,4,3,5,7,7,5,6,7,11,2,4,1,3,5,6,4,1,2,8,6,5,8,3,9,7,2,4,5,4,6,9,1,12,6,1,11,1,8,4,7,10,5,5,2,8,11,9,11,1,6,8,12,1,10,1,10,5,7,12,7,12,7,10,6,12,12,11,4,3,5,1,1,12,2,10,6,1,1,6,8,8,5,6,4,4,1,4,5,7,2,11,3,2,7,10,4,10,11,9,9,12,12,8,6,10,12,7,3,7,6,4,3,5,5,2,1,9,9,10,9,12,11,3,9,3,2,9,7,5,9,3,9,12,5,11,1,8,9,2,7,4,8,3,10,4,9,11,5,12,5,10,11,5,3,6,6,1,2,10,10,3,10,11,5,7,2,11,3,1,1,10,2,12,3,6,3,6,3,8,7,2,10,7,10,2,12,4,1,4,8,11,9,3,12,2,9,3,11,9,4,5,11,5,6,8,2,12,6,7,8,10,1,6,8,11,6,6,12,3,7,11,7,10,9,1,10,6,5,3,11,1,8,3,6,10,4,6,12,2,7,4,2,6,12,2,1,2,9,4,3,3,10,4,3,2,1,5,7,10,8,6,5,2,9,4,1,1,1,9,10,11,10,10,3,1,8,4,12,1,9,3,12,9,12,1,11,5,6,2,3,9,6,9,5,4,7,7,11,9,6,4,11,1,11,6,8,5,2,10,9,11,6,5,7,3,9,12,9,1,9,11,8,9,1,11,11,11,9,4,4,9,3,6,1,10,8,5,1,2,6,4,4,10,11,4,4,11,10,11,9,10,12,4,2,9,9,4,2,7,2,11,10,9,4,8,3,4,8,8,3,8,8,1,8,1,2,2,11,5,4,11,7,1,12,3,8,11,4,7,8,1,3,7,8,4,9,5,9,10,7,12,1,5,2,2,11,6,3,9,4,9,5,2,6,4,1,4,9,7,8,7,11,6,2,5,12,9,3,1,10,3,1,8,6,3,3,4,7,10,10,11,5,3,7,7,9,7,3,12,9,9,2,1,11,3,5,2,4,6,10,11,3,8,12,2,8,8,4,7,2,11,9,7,7,3,5,5,12,8,11,12,2,2,3,10,10,5,2,12,12,5,3,4,4,3,2,6,12,2,1,6,8,11,1,6,10,7,3,4,12,5,8,2,3,1,4,5,7,5,8,12,12,1,1,8,2,7,8,7,1,8,3,5,12,11,10,3,12,1,4,1,10,1,10,10,10,10,2,8,7,7,6,6,1,4,5,1,7,8,1,3,10,8,5,4,8,1,5,7,3,9,6,4,9,6,11,4,11,2,10,4,2,7,12,9,8,6,4,9,12,6,7,3,12,8,8,10,9,12,3,11,2,7,5,10,5,4,2,6,3,11,12,2,6,4,11,4,11,1,4,1,4,7,6,12,9,9,7,5,2,11,9,1,10,8,10,7,5,4,8,6,4,4,11,12,4,6,12,5,11,6,3,5,11,11,6,12,8,7,12,3,12,10,1,6,11,3,7,10,4,10,5,3,4,9,10,6,6,8,10,9,4,7,1,11,2,11,3,11,4,2,3,6,8,9,1,3,8,1,6,9,2,6,5,2,5,10,12,9,10,5,8,9,12,5,9,5,12,3,12,8,1,4,4,10,5,11,10,5,7,4,3,3,5,2,7,7,9,6,8,8,10,6,1,6,5,5,5,6,12,1,8,8,4,4,2,5,9,9,7,5,3,9,4,10,2,2,6,1,4,12,5,6,10,3,11,12,2,1,5,8,6,10,7,7,10,2,4,11,3,4,6,1,3,9,6,10,2,4,5,10,9,8,11,6,8,12,3,2,11,8,7,10,5,1,11,9,1,11,4,6,6,12,7,4,8,2,3,8,12,7,6,8,6,5,12,6,6,12,8,1,8,4,6,11,4,8,5,4,5,10,8,12,5,1,2,12,12,8,1,1,6,7,10,8,4,11,10,8,3,7,10,10,9,7,11,7,9,8,3,6,8,10,1,7,11,3,8,11,1,6,4,1,2,2,8,5,2,4,4,3,4,10,4,8,7,2,5,5,7,8,10,1,9,8,5,5,11,4,4,2,7,5,1,9,8,2,6,8,11,1,10,2,3,9,1,2,3,3,3,11,12,10,4,9,1,12,2,9,3,9,8,9,11,11,3,7,7,10,3,3,12,4,4,7,10,7,11,5,7,2,9,5,8,2,9,1,8,5,8,3,2,1,12,1,1,7,3,4,2,12,12,8,4,5,4,12,11,2,7,3,11,1,6,7,4,9,2,1,5,12,7,9,4,10,1,1,6,2,5,8,1,11,3,1,5,6,10,2,1,6,12,8,3,8,9,1,12,8,6,4,9,12,9,8,9,1,8,8,4,10,8,12,3,9,2,11,7,4,7,3,6,12,2,2,11,11,11,4,7,9,9,11,12,2,3,9,2,5,2,4,8,5,10,12,1,4,11,6,2,1,4,12,5,10,12,5,7,11,1,2,4,1,11,3,4,10,1,7,4,1,7,5,1,3,7,8,5,3,4,12,3,6,6,6,1,6,11,10,4,12,1,7,2,2,3,10,5,10,9,6,8,9,11,10,4,12,2,5,9,9,9,10,11,3,10,6,3,9,10,8,12,6,4,12,1,4,1,8,2,12,4,2,6,1,7,10,9,10,9,11,8,1,3,6,8,7,8,9,7,4,5,4,1,6,3,3,5,8,8,6,4,8,7,11,12,10,3,2,3,4,10,1,5,9,5,3,9,2,6,3,2,3,5,8,7,10,1,1,5,7,11,5,8,9,12,9,11,6,11,8,7,12,2,10,10,4,6,9,1,6,10,3,3,8,8,10,9,9,2,8,10,9,10,3,7,10,3,12,6,6,12,7,10,12,7,4,8,2,11,2,4,5,4,}
focus
0.67610705893124
firstname
"Pusaki"
midname
"Tanysatuanuy"
family
"Ptasfnzik"
food
{}
END_KOBOLD
KOBOLD
genes
{1,7,8,7,8,4,10,8,12,11,11,7,4,3,3,2,7,10,6,3,8,3,9,12,12,8,3,2,2,5,8,2,3,4,2,7,3,4,1,3,5,11,8,8,6,6,9,5,6,9,6,11,3,10,6,3,11,10,7,1,8,6,3,1,7,6,5,1,2,9,11,10,3,4,2,11,2,6,8,10,8,5,6,8,4,7,2,2,12,12,1,3,8,5,7,4,11,4,12,11,10,9,11,2,9,7,6,7,2,12,8,8,11,9,1,2,9,11,1,5,1,8,1,7,3,7,5,3,11,5,4,7,9,1,9,5,9,7,4,12,7,1,11,6,2,2,4,7,6,5,7,4,1,12,10,12,5,10,9,3,5,12,1,6,7,12,10,11,7,2,10,6,2,2,11,3,1,11,1,8,4,2,12,7,3,4,11,11,9,10,10,10,7,9,7,5,4,2,2,8,2,10,1,6,9,10,6,3,4,5,3,4,7,11,7,8,9,10,2,5,1,11,2,12,11,7,6,12,8,8,9,10,11,6,4,11,6,12,9,5,7,8,10,1,11,6,8,12,6,3,9,8,5,8,2,6,5,1,7,8,12,1,10,10,2,10,10,3,7,5,12,8,2,3,1,7,2,3,8,4,1,9,12,8,5,5,1,6,9,11,11,5,2,9,3,5,7,5,7,10,7,6,8,6,7,9,8,8,4,12,7,4,5,12,3,2,12,4,8,8,1,8,2,9,3,9,1,8,1,5,9,2,1,8,2,9,10,9,8,6,3,1,12,7,5,11,3,2,7,8,6,9,4,9,3,5,12,6,10,6,6,10,12,1,11,2,10,3,8,9,7,11,11,4,5,3,4,3,5,10,1,5,3,4,3,6,9,5,6,6,3,4,12,11,12,5,10,10,12,6,9,4,7,9,5,8,12,2,5,3,1,9,9,3,11,3,10,8,2,8,7,10,6,9,10,8,4,3,2,3,7,2,11,8,4,3,10,7,11,9,7,3,4,6,5,12,6,11,11,6,3,10,10,11,3,9,7,10,10,11,2,9,8,1,1,1,2,11,11,6,12,8,9,4,1,6,1,3,10,1,5,12,1,12,12,3,4,7,5,5,5,4,7,2,5,2,7,6,8,3,4,7,11,10,6,11,12,9,2,10,6,10,2,3,12,5,6,12,9,5,12,3,8,10,8,9,10,8,9,7,4,3,5,2,12,2,10,8,3,9,5,12,10,12,3,8,1,3,5,3,7,2,1,7,8,2,10,4,5,4,5,6,2,2,2,8,4,3,12,3,10,3,3,7,5,9,7,3,2,9,3,5,9,6,11,1,10,12,12,12,8,5,10,9,4,9,6,10,4,5,10,5,6,7,7,6,11,12,12,9,12,5,8,6,7,10,10,7,12,4,2,9,6,2,7,2,12,2,6,5,8,3,11,10,3,10,10,11,9,12,10,10,7,1,9,2,11,4,3,7,9,2,10,9,5,7,1,11,4,9,1,5,8,8,9,5,8,6,11,2,10,3,12,5,1,9,1,10,10,9,9,2,7,4,9,7,9,8,8,5,7,5,8,10,2,5,12,4,7,5,9,5,11,7,8,10,1,6,10,5,12,5,2,9,4,1,9,11,10,10,11,1,6,12,10,6,2,2,1,2,6,3,10,8,7,6,9,6,4,11,3,11,4,1,5,10,3,2,4,10,3,11,1,11,7,8,6,10,11,6,12,2,6,6,3,12,4,9,5,10,7,2,8,3,9,6,11,4,1,6,7,6,2,7,10,4,2,11,5,8,10,1,8,12,11,5,7,2,1,3,5,10,12,3,1,5,3,5,11,1,3,11,3,10,3,2,10,5,11,11,10,2,11,2,8,10,1,3,2,11,12,8,11,7,5,2,7,1,1,10,11,8,7,1,8,8,3,1,12,7,7,12,8,1,11,9,12,8,6,2,12,3,4,1,1,1,4,4,4,6,2,4,7,2,5,12,7,9,5,2,5,4,5,3,2,5,3,12,4,8,11,1,12,10,12,6,1,4,8,7,4,5,9,12,12,4,6,3,3,7,6,1,4,7,11,2,9,6,11,3,2,12,11,9,10,5,8,12,8,6,1,1,10,11,2,4,6,1,3,10,8,4,7,5,2,11,11,6,8,10,8,5,10,5,11,3,1,12,11,10,8,2,2,1,9,1,11,9,8,11,12,12,8,7,2,9,1,4,6,12,2,5,9,7,4,11,1,2,12,8,11,10,1,10,5,4,3,8,6,10,9,9,6,10,5,3,3,9,2,8,2,7,5,8,7,2,4,6,11,3,10,7,7,8,5,11,1,1,4,7,1,12,1,2,6,3,6,5,8,3,7,10,6,3,6,6,5,3,11,7,8,2,12,5,3,1,6,8,12,6,7,7,2,9,9,6,9,12,4,10,10,2,1,9,3,3,11,12,6,1,9,4,1,1,4,12,1,12,12,5,11,11,12,3,12,7,7,7,4,3,2,12,9,1,2,5,10,12,8,9,4,12,9,5,7,3,12,8,9,5,7,1,10,6,2,10,6,9,11,5,9,5,8,11,6,3,4,10,10,6,7,8,6,11,11,1,11,5,7,9,2,6,7,10,12,10,1,5,4,5,11,5,9,4,8,10,4,11,3,8,7,9,7,7,4,11,3,11,5,1,5,12,11,3,11,10,7,4,4,6,9,2,7,7,10,5,7,7,2,8,3,7,3,4,6,12,12,2,9,12,7,10,5,4,9,12,7,9,2,1,4,10,9,7,5,2,8,10,4,6,3,6,6,8,2,8,6,4,7,1,3,3,7,10,9,3,5,8,4,11,6,9,9,10,5,8,2,10,9,10,4,6,5,4,5,8,5,4,9,3,4,12,2,9,6,3,12,12,8,1,2,1,3,9,3,1,11,1,1,12,7,11,10,8,3,10,3,8,8,4,7,2,5,1,1,12,11,4,2,12,10,10,4,3,5,8,3,12,9,11,11,4,11,9,6,3,9,10,6,11,9,3,10,5,9,3,7,4,4,3,4,12,4,5,4,4,3,6,11,2,12,7,12,8,5,9,5,8,5,5,2,10,11,4,12,7,5,1,4,3,10,3,10,2,6,2,9,1,3,10,12,8,8,2,4,12,9,8,8,9,7,3,2,6,9,3,12,5,5,12,10,11,9,9,11,11,5,3,2,10,1,7,2,4,4,5,7,7,8,6,11,3,5,8,9,11,9,1,5,8,10,9,5,12,10,2,1,12,1,9,5,12,10,11,8,7,10,6,9,8,3,5,4,2,12,7,2,1,7,2,5,11,10,8,2,8,9,3,2,8,6,4,11,9,12,2,12,6,12,5,4,1,9,4,11,5,1,4,5,5,11,5,6,1,7,10,8,1,4,4,3,12,9,6,12,8,4,10,12,10,5,8,5,8,4,11,3,7,6,4,7,3,10,10,3,6,12,3,4,12,4,11,11,9,2,8,12,3,2,11,6,12,7,10,5,7,4,2,2,4,10,12,12,1,1,11,2,12,10,4,10,8,11,8,5,5,12,10,10,12,9,11,4,5,4,11,10,10,5,8,5,3,4,7,8,2,9,5,8,11,6,2,10,11,7,10,2,6,10,8,3,5,5,3,5,2,1,10,10,7,1,2,11,9,12,8,7,1,7,10,6,1,1,1,6,6,3,1,1,4,2,7,12,7,3,10,5,5,3,11,3,4,9,3,7,7,11,11,6,7,9,8,10,8,6,3,4,10,8,1,9,3,4,11,2,9,6,6,5,9,1,4,3,12,5,6,2,5,9,4,10,4,8,8,1,6,1,7,12,2,9,7,3,5,8,4,9,12,8,11,10,2,3,5,3,8,3,11,7,3,9,1,2,9,6,2,10,4,3,1,12,4,1,3,12,12,7,4,11,10,11,5,2,2,7,11,3,5,11,5,6,10,1,4,11,8,5,4,4,8,5,9,11,5,10,11,8,6,11,4,10,11,4,9,12,7,11,10,3,7,3,2,1,9,8,10,8,4,5,5,7,10,10,7,8,1,3,6,4,11,3,4,5,8,10,11,10,7,2,9,10,6,1,4,2,12,3,2,8,11,7,10,10,3,1,10,12,6,11,6,3,7,12,4,12,5,4,9,11,3,6,12,12,10,12,8,3,8,12,8,9,7,1,6,5,9,3,1,9,1,3,10,10,9,2,1,3,11,11,1,2,1,2,4,10,3,9,3,1,7,9,3,9,8,4,5,10,9,12,11,10,4,6,11,7,12,1,7,11,10,2,3,8,1,12,10,11,11,11,1,9,12,12,11,9,7,8,12,1,3,5,1,8,6,11,11,1,6,11,10,4,12,7,9,3,5,2,2,11,9,12,4,12,12,6,7,5,3,10,6,9,7,11,12,}
focus
0.30954924161504
firstname
"Psuky"
midname
"Nyanynyanuy"
family
"Kipasannzik"
food
{3,2,3,4,}
END_KOBOLD
KOBOLD
genes
{12,7,2,6,10,1,12,12,10,6,6,4,1,3,1,5,11,12,3,1,10,12,9,8,10,8,12,10,1,12,12,10,11,8,3,11,2,12,6,10,5,5,3,4,1,6,6,6,11,5,9,1,12,5,3,2,1,11,6,1,3,1,12,11,5,4,9,9,12,7,8,5,9,6,3,4,4,10,1,7,7,10,2,9,10,9,1,11,7,1,12,5,1,8,5,11,8,10,5,4,5,11,4,8,6,7,12,9,4,2,7,12,4,1,3,4,8,2,6,3,8,8,2,12,11,11,1,8,10,4,4,11,8,11,12,11,8,3,3,5,9,9,1,3,4,6,7,10,8,1,8,4,1,2,6,8,11,1,4,8,12,8,12,8,1,12,5,5,2,7,2,12,4,5,11,12,9,4,5,6,3,1,12,7,8,4,7,2,8,2,7,2,10,8,9,10,1,6,12,2,9,7,6,6,7,11,6,4,3,7,11,12,11,11,2,12,10,8,3,4,7,4,8,8,6,8,2,9,10,8,10,2,11,5,6,12,11,6,11,8,4,10,2,11,3,10,11,3,1,6,10,9,4,2,10,3,11,5,11,2,11,12,3,2,9,10,5,6,8,3,4,10,1,8,3,5,4,10,5,5,1,2,1,1,6,6,10,2,4,3,9,5,10,7,8,8,11,4,6,11,10,12,12,5,4,10,4,4,3,7,12,6,2,4,6,7,2,8,5,7,2,4,5,5,1,2,6,4,5,4,5,8,4,7,12,9,1,3,5,4,7,10,6,8,12,1,11,4,4,8,12,2,7,3,12,4,5,9,2,8,6,1,1,6,1,8,2,6,5,4,1,11,3,7,2,7,1,7,3,10,1,10,3,7,1,9,5,6,11,3,9,6,9,10,4,2,9,9,7,3,11,1,8,5,4,9,9,5,11,1,6,12,2,10,7,9,5,7,5,2,3,6,1,8,11,3,4,12,12,4,12,2,11,12,6,9,9,10,1,3,11,7,7,1,11,10,11,5,11,8,8,5,12,4,10,6,9,5,10,5,10,2,10,12,7,2,11,3,7,5,11,4,11,5,7,11,9,4,5,12,10,11,10,6,9,4,11,8,8,4,8,3,8,9,9,1,3,5,7,11,6,3,6,12,1,7,9,3,6,9,1,5,10,4,7,11,2,2,8,7,6,2,7,2,5,2,3,5,10,10,5,4,1,4,10,10,11,7,10,9,2,6,4,3,4,10,6,1,4,12,3,1,2,4,10,5,3,11,10,9,9,2,9,10,3,8,4,6,1,6,12,2,6,3,5,11,8,7,3,11,10,11,7,10,12,8,12,1,12,9,7,9,11,3,11,5,1,1,10,7,10,11,1,1,11,10,9,1,3,7,4,11,8,9,7,11,8,7,5,11,7,6,3,12,6,11,7,4,1,1,8,1,9,7,8,8,12,10,6,10,12,2,11,2,5,8,4,3,7,4,8,12,4,3,9,8,3,1,10,6,10,12,11,2,11,4,3,7,1,2,7,10,7,8,6,9,3,9,4,11,6,1,1,12,12,12,9,11,6,11,10,11,12,12,10,9,1,11,5,3,3,4,5,2,8,4,5,6,11,3,8,1,5,6,5,2,11,7,1,7,12,5,4,4,9,11,2,12,8,12,4,12,6,11,10,6,3,8,8,6,9,3,3,1,6,12,4,9,6,2,4,2,4,7,8,7,1,10,10,12,12,9,5,5,12,12,7,9,7,11,9,11,9,11,8,1,7,10,4,4,1,8,9,4,6,9,6,12,5,8,8,4,11,2,6,6,8,12,7,5,2,10,7,4,4,10,11,9,4,8,2,1,1,11,6,6,7,2,6,8,12,12,4,1,12,8,2,11,1,10,11,5,12,7,7,9,9,8,9,8,8,2,6,2,7,9,12,3,11,6,7,5,5,8,10,8,11,12,2,7,4,7,5,7,1,1,7,4,8,10,2,7,1,7,5,5,11,5,2,7,11,7,4,8,9,12,2,7,3,12,3,4,7,2,5,6,2,6,10,11,9,7,2,12,4,4,5,5,4,12,7,1,7,3,5,7,3,8,4,2,1,11,10,11,10,8,2,10,7,10,1,1,5,9,12,8,3,7,10,8,11,6,6,2,7,5,1,4,3,3,2,2,9,11,10,3,5,1,2,6,5,5,3,4,6,7,7,12,9,12,11,6,6,11,5,12,5,4,4,7,12,5,9,12,1,5,5,5,4,1,8,11,5,2,2,1,10,11,6,5,5,2,10,7,8,8,10,12,1,12,9,1,8,7,11,3,1,8,5,7,8,2,8,5,3,9,2,1,10,5,8,2,10,8,9,2,7,1,4,8,9,6,2,3,7,7,7,8,8,9,6,12,7,5,2,10,1,9,1,9,12,11,4,6,9,10,1,11,9,6,10,1,8,7,11,1,7,3,2,6,1,4,6,8,1,3,4,7,4,10,9,8,10,1,7,11,1,9,10,9,9,2,11,6,1,11,5,10,8,8,6,10,7,3,4,11,3,9,4,10,11,12,6,5,6,2,3,11,5,4,2,4,7,10,1,7,11,6,1,10,3,5,10,8,3,1,11,6,5,7,8,12,4,4,8,1,1,6,4,11,8,8,12,4,4,5,10,2,12,2,5,5,7,8,9,3,10,1,7,6,2,12,3,4,9,10,9,6,6,1,9,6,7,8,5,10,11,11,8,6,8,10,2,7,10,2,10,2,9,4,4,1,7,9,1,12,4,2,7,6,12,11,11,11,1,8,6,12,10,5,9,9,11,11,12,11,7,7,2,9,2,12,9,9,9,10,10,6,9,2,12,2,12,3,5,11,2,7,2,3,10,8,9,7,1,2,3,2,2,6,8,6,11,4,5,6,10,1,3,4,10,7,3,9,3,4,12,3,6,1,2,9,7,3,4,7,12,5,12,8,6,8,2,7,12,6,1,10,9,12,6,3,5,11,11,6,9,10,1,2,11,8,11,7,6,8,4,12,2,2,6,5,10,8,4,9,12,4,2,10,9,10,1,12,4,6,11,3,2,12,4,1,2,1,9,4,2,1,11,9,9,11,6,6,2,2,8,1,5,10,2,7,8,11,10,8,10,2,2,10,7,3,9,8,5,5,4,6,4,6,12,1,2,5,5,2,11,3,6,4,5,10,4,9,10,12,3,12,1,6,6,4,6,8,4,2,7,1,4,7,11,6,10,7,7,5,2,4,10,3,7,1,10,7,2,2,6,4,8,3,11,10,12,7,8,4,7,6,11,11,2,5,10,3,1,9,2,2,7,10,7,7,2,3,3,11,4,7,1,2,7,9,6,5,4,7,12,7,5,12,5,9,3,12,1,8,12,1,8,1,12,7,2,4,5,2,7,6,7,4,7,7,12,3,4,4,1,1,4,10,12,3,1,5,6,7,1,3,10,6,5,10,1,4,6,3,12,1,10,7,2,4,8,8,11,11,7,6,10,3,9,2,6,8,8,9,2,7,5,3,10,1,7,11,2,12,1,5,12,10,3,2,8,4,5,2,7,11,10,3,8,6,5,5,9,7,10,8,10,11,9,7,3,1,8,5,10,3,8,3,10,10,3,9,12,8,9,1,8,9,6,11,7,4,4,12,9,7,10,6,1,8,9,5,12,12,3,9,5,5,12,11,5,12,1,2,4,12,3,9,3,12,11,12,2,9,11,11,1,1,10,12,12,4,7,10,8,4,8,9,7,7,10,5,6,4,6,1,12,11,11,9,7,5,6,9,12,10,12,11,10,10,7,10,10,12,8,7,10,11,8,7,10,9,1,11,4,1,10,9,6,3,8,7,8,9,5,2,8,6,11,6,10,9,12,7,8,8,2,4,3,9,3,12,11,7,9,9,1,3,10,5,1,7,2,12,1,10,2,1,12,5,6,8,4,2,4,12,8,7,1,1,7,10,1,11,8,1,10,1,3,5,2,12,3,3,1,6,3,1,1,6,4,4,1,4,12,10,9,1,8,10,10,6,5,8,4,3,4,6,5,1,1,6,10,12,3,2,1,4,2,9,4,8,10,9,5,9,1,6,11,6,1,4,7,3,7,2,10,9,2,9,1,7,1,9,3,7,5,5,3,8,9,1,9,12,8,2,2,2,12,7,4,7,3,10,8,10,4,4,9,2,12,6,6,7,6,3,2,6,7,4,2,7,3,10,9,5,9,8,10,1,4,3,10,9,4,3,9,12,11,1,10,11,7,9,6,5,12,8,6,4,6,5,8,9,2,8,2,10,2,9,8,6,12,12,3,1,7,5,4,7,9,6,12,8,9,1,5,3,5,2,8,6,10,10,4,3,4,5,2,4,7,9,11,7,4,2,11,11,8,9,11,2,2,2,10,5,1,4,12,6,9,8,}
focus
0.094868987074194
firstname
"Itaaki"
midname
"Natanyianuy"
family
"Ptasfnzik"
food
{}
END_KOBOLD
KOBOLD
genes
{5,7,10,6,4,9,11,3,12,1,4,5,2,12,12,12,5,8,4,2,6,1,3,11,5,9,5,11,4,8,9,1,4,12,1,8,2,10,6,12,5,9,8,6,2,1,10,11,5,9,10,5,6,6,7,10,5,4,7,12,8,7,9,6,7,7,8,6,6,7,2,10,10,10,4,10,11,5,12,1,7,9,5,6,3,6,7,1,7,12,4,8,1,1,11,6,1,8,12,11,2,2,12,12,9,7,7,11,7,8,6,4,8,9,5,5,9,5,10,1,1,6,5,4,2,11,8,7,3,11,6,11,12,10,1,11,6,12,11,6,5,9,3,5,8,10,7,9,9,3,9,11,8,3,4,2,6,5,9,3,10,1,5,3,8,1,5,5,7,9,10,7,12,8,9,1,1,6,3,1,3,3,2,12,3,4,2,10,9,10,9,8,9,11,2,11,8,3,1,8,7,11,5,7,6,8,6,10,10,8,1,2,10,12,11,8,3,11,8,10,6,3,1,5,4,1,7,4,1,1,10,4,1,10,4,8,6,12,10,4,8,11,6,7,6,1,3,6,7,2,5,10,12,8,6,4,12,6,5,7,10,12,5,5,7,3,9,2,10,3,4,1,6,9,2,8,12,1,10,9,1,7,12,9,10,2,2,12,10,8,10,7,1,7,3,11,3,3,3,6,10,8,3,6,12,9,2,11,4,6,12,9,5,8,12,5,1,1,8,9,1,1,2,10,7,6,7,2,8,12,3,9,8,8,3,6,12,4,4,11,2,2,8,3,8,11,3,12,4,1,9,7,10,8,3,3,7,9,3,2,7,2,10,5,8,1,11,10,2,2,7,11,9,2,8,5,12,1,11,2,7,3,3,8,12,3,10,9,11,6,11,6,2,4,2,7,6,10,10,9,10,11,2,7,7,8,11,3,7,10,11,6,6,4,10,7,6,4,8,10,5,10,10,7,9,12,10,4,5,8,11,3,4,5,5,9,10,4,7,12,11,7,5,2,4,8,1,4,6,2,4,11,11,10,4,6,5,12,12,11,10,6,8,4,5,9,1,9,10,4,8,6,9,6,10,5,2,12,1,9,7,5,5,5,4,8,1,8,12,6,7,5,7,9,5,9,6,6,6,8,12,3,7,9,1,8,9,3,5,5,2,9,7,7,10,5,12,2,9,12,12,6,11,2,12,8,10,8,5,9,11,2,10,9,6,12,4,10,5,8,8,3,1,9,1,11,12,2,9,11,2,6,10,11,1,11,5,11,8,3,9,2,5,9,7,6,5,6,11,5,1,3,11,10,12,7,5,9,6,7,9,1,11,9,3,7,6,6,8,10,3,9,10,1,10,11,10,10,7,3,8,10,8,11,7,12,11,10,1,12,10,5,4,3,12,4,9,1,12,1,12,7,9,4,10,4,6,2,2,7,1,5,3,1,11,5,4,4,10,8,2,2,9,9,2,12,9,4,5,1,1,1,8,4,4,11,8,8,1,8,1,6,12,2,6,4,4,11,9,9,6,9,8,9,7,1,3,10,7,4,12,2,5,12,7,5,2,11,1,7,11,1,2,1,6,3,12,10,1,6,9,12,4,9,5,8,5,6,3,5,2,2,5,10,2,10,2,3,4,7,7,11,8,1,4,5,8,8,6,8,3,8,6,9,4,2,5,8,7,2,6,10,5,3,4,9,6,1,9,10,8,9,12,9,5,6,10,6,7,8,6,2,9,8,3,7,12,1,10,3,8,3,4,5,11,10,6,4,4,2,5,6,11,5,12,7,11,10,8,3,3,11,9,12,3,1,4,7,5,1,12,5,9,1,2,11,6,12,2,1,4,10,7,7,12,5,11,7,9,2,11,4,8,3,10,11,9,6,8,5,12,6,4,2,10,9,12,11,3,7,4,3,3,10,9,2,9,3,10,4,8,3,11,9,8,8,12,8,9,8,5,2,11,8,9,8,7,6,12,8,4,2,7,12,9,12,5,9,3,12,5,5,4,10,10,12,2,12,1,9,1,8,10,12,7,3,4,8,3,9,2,12,6,2,1,12,11,2,12,2,4,8,10,4,3,2,7,12,6,1,7,10,8,8,3,8,10,1,7,12,10,8,1,4,11,12,7,6,11,2,1,4,5,3,2,12,2,7,12,11,5,5,5,2,12,2,7,3,9,12,2,12,2,8,5,8,10,6,4,9,12,2,1,6,12,2,7,11,1,12,4,7,11,4,2,4,8,7,11,11,1,7,5,3,3,2,3,6,7,8,7,9,12,2,7,8,4,5,7,8,5,11,8,1,5,5,2,1,3,4,5,11,3,1,10,2,8,1,8,3,6,8,3,4,8,11,10,4,12,10,7,6,7,1,5,3,5,10,4,8,7,2,10,8,10,5,9,5,7,9,11,3,9,2,10,5,10,12,1,12,1,3,6,5,10,8,6,6,8,4,9,12,11,4,1,1,9,8,7,3,5,11,9,10,12,9,8,4,4,4,3,6,1,2,2,10,5,8,1,1,1,2,1,3,11,4,5,2,12,11,12,7,10,6,5,1,9,6,5,10,4,12,5,7,3,11,3,12,1,1,3,5,10,3,6,10,5,11,12,2,3,8,1,9,4,4,8,8,1,5,7,9,9,9,2,10,1,6,8,8,3,8,12,7,7,10,7,1,5,3,10,9,11,2,7,4,3,6,9,1,9,9,6,5,7,2,8,8,11,7,1,3,11,12,6,1,1,5,6,2,5,6,5,11,8,8,7,9,7,12,9,8,7,9,6,5,1,10,9,12,12,12,3,7,2,7,12,5,4,2,9,3,1,9,1,5,7,12,2,8,10,6,2,7,10,8,6,9,5,5,9,4,8,6,11,6,11,9,3,11,9,9,5,6,2,5,8,2,7,1,11,10,9,12,2,8,6,2,7,2,9,10,7,1,6,10,6,10,2,1,8,4,6,4,6,4,9,1,5,1,10,3,8,1,6,9,9,11,7,1,3,12,1,9,12,5,7,12,7,1,1,6,1,9,2,1,11,9,3,6,6,10,11,11,5,10,10,4,6,12,8,1,12,3,5,3,4,7,1,9,5,4,3,11,5,8,3,4,1,12,12,7,8,2,3,9,9,5,6,12,3,11,5,9,1,9,5,5,8,10,2,3,5,9,7,9,11,9,2,2,1,1,12,3,10,8,5,9,2,2,4,2,2,1,1,12,7,6,4,8,9,11,1,8,11,12,1,7,9,2,1,1,6,3,1,4,9,2,2,7,7,6,10,8,5,12,2,5,11,8,8,12,7,12,1,3,10,8,12,7,12,6,12,7,6,6,9,1,2,6,3,7,12,10,9,5,11,10,1,10,11,9,9,5,6,1,6,4,3,11,5,8,10,6,11,2,4,10,12,7,11,12,9,5,7,4,12,7,5,3,9,10,3,10,8,4,10,4,4,4,12,1,2,5,2,7,9,1,12,1,2,7,6,6,5,8,7,8,2,9,8,10,12,8,2,11,3,8,9,6,10,3,12,2,2,2,10,5,5,8,10,4,1,4,6,1,10,12,2,8,7,11,5,9,9,3,5,6,8,6,7,4,1,5,8,5,2,8,12,7,5,1,5,10,12,8,3,2,9,4,4,11,3,7,11,3,6,4,9,12,1,3,11,7,3,2,2,12,7,10,5,5,10,7,10,5,12,11,1,2,4,4,8,7,5,7,5,4,4,12,4,7,10,7,8,10,5,7,11,6,12,11,8,4,7,12,5,1,5,7,7,3,7,7,11,5,10,8,7,2,5,5,4,10,11,2,8,12,4,7,11,7,12,6,11,4,1,5,7,11,11,7,2,10,5,8,9,10,3,2,6,6,8,3,5,2,2,12,12,4,11,9,2,2,7,11,2,11,1,7,1,5,9,1,10,10,8,6,6,12,12,5,4,5,1,11,12,3,10,1,5,7,7,10,7,10,10,5,3,12,2,5,2,5,2,10,1,2,5,5,5,6,1,3,5,4,4,1,2,9,7,7,5,9,8,4,3,6,3,10,7,5,6,7,5,12,1,8,10,10,3,9,12,6,12,3,1,11,1,2,1,6,7,5,8,9,7,6,10,6,8,12,11,7,8,8,8,1,2,9,5,5,7,3,4,1,1,7,5,9,11,3,3,12,6,1,11,1,4,1,8,7,4,7,12,9,9,8,7,4,4,12,12,12,9,12,1,11,6,1,11,6,4,6,9,5,11,1,4,3,8,9,9,11,10,10,10,1,1,3,10,2,3,5,9,3,9,2,4,11,7,5,10,10,1,5,9,9,4,8,2,9,6,7,9,5,10,10,5,3,1,3,10,3,4,11,11,8,3,9,11,12,7,1,3,12,7,11,2,11,2,6,4,9,3,1,9,11,5,1,5,1,9,4,}
focus
0.081013066505835
firstname
"Zkuki"
midname
"Nykukuzanuy"
family
"Kipasannzik"
food
{}
END_KOBOLD

300
training/trainer.lua Normal file
View file

@ -0,0 +1,300 @@
adaptive_ai = not adaptive_ai and {} or adaptive_ai
dofile("../helper.lua")
dofile("./noise.lua")
trainer = {}
math.randomseed(os.time())
local random = math.random
local helper = adaptive_ai.helper
local calc = adaptive_ai.calc
local ceil = math.ceil
local avg = calc.average
local round = calc.round
local adjacent = helper.adjacent
local conditions = {}
local gen_stats, popul, top_size -- condition values
local fill_creature, reset_creature, manage_creature, fill_cell -- 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
end
end
-- TERRAIN CONTROL
local 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
end
end
-- Fill the training area
-- Cell[1] stores the height. Cell[2] is the space to store custom metadata
local 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 map
end
-- POPULATION CONTROL
local function fill_popul(popul, cull)
console_log("Populating...")
math.randomseed(os.time())
local creature
if cull then
creature = {}
local j = random(1,cull)
fill_creature(creature, popul[1], popul[j])
table.insert(popul, creature)
end
local popul_size = conditions.popul_max
while #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(popul, creature)
end
console_log("Success populating.\n")
end
local function fitness_sort(a, b)
return a.fitness > b.fitness
end
-- TURN MANAGEMENT
local 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
end
end
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 last
end
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_modifier then
creature.fitness = creature.fitness * conditions.fitness_modifier(turn, max_turn)
end
end
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 PROCESS
local 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_modifier = options.fitness_modifier,
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_gen
end
trainer.train = train
trainer.valid = valid
trainer.console_log = console_log
trainer.fitness_sort = fitness_sort
trainer.move_creature = move_creature
adaptive_ai.trainer = trainer

305
training/trainer_goblin.lua Normal file
View file

@ -0,0 +1,305 @@
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()

305
training/trainer_kobold.lua Normal file
View file

@ -0,0 +1,305 @@
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.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_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]
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
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 modifier = function(turn, max_turn) return (1 + turn/max_turn) 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 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: "..kobolds.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_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 = true
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_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_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()