From 2ca7f8872189125209062eeb035ae81b6204c934 Mon Sep 17 00:00:00 2001 From: sachy Date: Sat, 3 Dec 2011 20:13:08 +0100 Subject: [PATCH 01/12] Patches, patches, patches... --- viewer.nlogo | 52 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/viewer.nlogo b/viewer.nlogo index 2c7af9d..2299f5b 100644 --- a/viewer.nlogo +++ b/viewer.nlogo @@ -1,20 +1,21 @@ -globals [w h] +globals [w h symbol herbs] to init clear-all; file-close-all; set w 0; set h 0; - + set herbs 0; + set symbol ""; file-open "rawio_cfg"; resize-world 0 ((read-from-string file-read-line) - 1) (1 - (read-from-string file-read-line)) 0; file-close-all end - + to go file-open "rawio_map"; while [file-at-end? = false] [ - let symbol file-read-characters 1; + set symbol file-read-characters 1; if (symbol = "\n" or symbol = "\r") and file-at-end? = false [ set symbol file-read-characters 1; @@ -24,20 +25,29 @@ to go if symbol = "x" [ ask patch w h [set pcolor green ]; + set herbs herbs + 1 ] if symbol = "." [ - ask patch w h [set pcolor black ] + ask patch w h [set pcolor black ]; + ] + if member? symbol ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"] + [ + ask patch w h [set pcolor red ]; ] set w w + 1; ] file-close-all; + + set-current-plot "herbs"; + set-current-plot-pen "herbs"; + plot count patches with [pcolor = green]; + tick; set w 0; set h 0; wait 1; -end - +end @#$#@#$#@ GRAPHICS-WINDOW 111 @@ -97,6 +107,34 @@ NIL NIL NIL +PLOT +91 +332 +291 +482 +herbs +NIL +NIL +0.0 +10.0 +0.0 +10.0 +true +false +PENS +"herbs" 1.0 0 -16777216 true + +MONITOR +17 +411 +74 +456 +herbs +count patches with [pcolor = green] +17 +1 +11 + @#$#@#$#@ WHAT IS IT? ----------- From 61cc3ef015c614e5f6a7cfbce0f82f5f18009b70 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 7 Dec 2011 23:28:11 +0100 Subject: [PATCH 02/12] connection::senses(): Clean up visual sending --- connection.cc | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/connection.cc b/connection.cc index 9e99822..95e3a43 100644 --- a/connection.cc +++ b/connection.cc @@ -18,41 +18,26 @@ connection::senses(int tick_id, class agent &a) { assert(!negotiation); + int dirs[][2] = { + {0,-1}, {1,-1}, {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, + {0,-2}, {1,-2}, {2,-2}, {2,-1}, {2,0}, {2,1}, {2,2}, {1,2}, {0,2}, {-1,2}, {-2,2}, {-2,1}, {-2,0}, {-2,-1}, {-2,-2}, {-1,-2}, + }; + int dir_n = sizeof(dirs) / sizeof(dirs[0]); + char buf[1024]; char *bufp = buf; bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "tick %d\r\n", tick_id); if (a.dead) bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "DEAD\r\n"); bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "energy %d\r\n", a.energy); - bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "visual %s %s %s %s %s %s %s %s" " %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s" "\r\n", - a.tile->tile_in_dir(0, -1).str(), - a.tile->tile_in_dir(1, -1).str(), - a.tile->tile_in_dir(1, 0).str(), - a.tile->tile_in_dir(1, 1).str(), - a.tile->tile_in_dir(0, 1).str(), - a.tile->tile_in_dir(-1, 1).str(), - a.tile->tile_in_dir(-1, 0).str(), - a.tile->tile_in_dir(-1, -1).str(), - - a.tile->tile_in_dir(0, -2).str(), - a.tile->tile_in_dir(1, -2).str(), - a.tile->tile_in_dir(2, -2).str(), - a.tile->tile_in_dir(2, -1).str(), - a.tile->tile_in_dir(2, 0).str(), - a.tile->tile_in_dir(2, 1).str(), - a.tile->tile_in_dir(2, 2).str(), - a.tile->tile_in_dir(1, 2).str(), - a.tile->tile_in_dir(0, 2).str(), - a.tile->tile_in_dir(-1, 2).str(), - a.tile->tile_in_dir(-2, 2).str(), - a.tile->tile_in_dir(-2, 1).str(), - a.tile->tile_in_dir(-2, 0).str(), - a.tile->tile_in_dir(-2, -1).str(), - a.tile->tile_in_dir(-2, -2).str(), - a.tile->tile_in_dir(-1, -2).str()); + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "visual"); + for (int i = 0; i < dir_n; i++) { + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), " %s", a.tile->tile_in_dir(dirs[i][0], dirs[i][1]).str()); + } bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "\r\n"); + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "\r\n"); pthread_mutex_lock(&buf_lock); out_buf.append(buf); pthread_mutex_unlock(&buf_lock); From a821e726612c6f2a97d3ceeadb88656f0bb6b6ff Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 7 Dec 2011 23:54:03 +0100 Subject: [PATCH 03/12] Introduce pheromones --- Makefile | 2 +- agent.cc | 13 +++++++++++++ agent.h | 4 ++++ connection.cc | 21 +++++++++++++++++++++ map.cc | 2 ++ map.h | 3 +++ pheromone.cc | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ pheromone.h | 35 +++++++++++++++++++++++++++++++++++ world.h | 6 ++++++ 9 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 pheromone.cc create mode 100644 pheromone.h diff --git a/Makefile b/Makefile index df473ef..3a51ce3 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CFLAGS=-Wall -g -pthread LDFLAGS=-pthread -OBJS=main.o map.o agent.o connection.o +OBJS=main.o map.o agent.o connection.o pheromone.o brmlife: $(OBJS) $(CXX) $(LDFLAGS) -o $@ $^ diff --git a/agent.cc b/agent.cc index 61fcef8..fefe291 100644 --- a/agent.cc +++ b/agent.cc @@ -91,6 +91,15 @@ agent::attack_dir(int dir_x, int dir_y) return attack_roll >= defense_roll; } +bool +agent::secrete(int id, double intensity) +{ + pheromone p(id, intensity); + pheromones.secrete(p); + chenergy(intensity * world::pheromone_cost); + return true; +} + void agent::chenergy(int delta) { @@ -129,9 +138,13 @@ agent::on_action_takes(void) void agent::on_tick(void) { + pheromones.decay(world::phdecay_gamma, world::phdecay_delta); + if (!tile) return; + pheromones.seep(tile->pheromones, world::phseep_alpha, world::phseep_beta); + if (!dead) { chenergy(world::sun_energy); chenergy(attr.move * world::move_idle_cost); diff --git a/agent.h b/agent.h index 911a46e..5687d1f 100644 --- a/agent.h +++ b/agent.h @@ -2,6 +2,7 @@ #define BRMLIFE__AGENT_H #include "map.h" +#include "pheromone.h" #include "world.h" class connection; @@ -23,6 +24,8 @@ public: int energy; bool dead; + class pheromones pheromones; + agent(int id_, class connection *conn_, class map &map_) : id (id_), conn (conn_), map (map_), tile (NULL) { @@ -37,6 +40,7 @@ public: bool move_dir(int dir_x, int dir_y); bool attack_dir(int dir_x, int dir_y); + bool secrete(int id, double intensity); void chenergy(int delta); void die(void); diff --git a/connection.cc b/connection.cc index 95e3a43..c283e36 100644 --- a/connection.cc +++ b/connection.cc @@ -37,6 +37,19 @@ connection::senses(int tick_id, class agent &a) } bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "\r\n"); + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "pheromones"); + for (int i = 0; i < dir_n; i++) { + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), " ("); + class pheromones &ps = a.tile->tile_in_dir(dirs[i][0], dirs[i][1]).pheromones; + for (std::list::iterator pi = ps.spectrum.begin(); pi != ps.spectrum.end(); pi++) { + if (pi != ps.spectrum.begin()) + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), ","); + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "%d:%f", pi->id, pi->val); + } + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), ")"); + } + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "\r\n"); + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "\r\n"); pthread_mutex_lock(&buf_lock); out_buf.append(buf); @@ -102,6 +115,14 @@ connection::actions(class agent &agent) if (!agent.attack_dir(x, y)) bump(); mask |= 2; + } else if (!negotiation && !cmd.compare("secrete")) { + int id = 0; double v = 0; + sscanf(line.c_str(), "%d %lf", &id, &v); + if (id < 0) id = 0; if (id >= PH_COUNT) id = PH_COUNT - 1; + if (v < 0) v = 0; + if (!agent.secrete(id, v)) + bump(); + /* We deliberately allow multiple secrete commands. */ } else { std::cout << "unknown line " << cmd << " " << line << " ...\n"; diff --git a/map.cc b/map.cc index ddfe787..43bfe43 100644 --- a/map.cc +++ b/map.cc @@ -39,6 +39,8 @@ tile::on_tick(void) if (tile_in_dir(-1, 0).herb_here()) { tile_in_dir(-1, 0).agent->chenergy(world::soil_energy / herbs); } if (tile_in_dir(0, -1).herb_here()) { tile_in_dir(0, -1).agent->chenergy(world::soil_energy / herbs); } } + + pheromones.decay(world::phdecay_gamma, world::phdecay_delta); } bool diff --git a/map.h b/map.h index 9f7027e..b527f2f 100644 --- a/map.h +++ b/map.h @@ -1,6 +1,8 @@ #ifndef BRMLIFE__MAP_H #define BRMLIFE__MAP_H +#include "pheromone.h" + class agent; class map; @@ -10,6 +12,7 @@ public: class map ↦ class agent *agent; + class pheromones pheromones; tile(int x_, int y_, class map &map_) : x(x_), y(y_), map(map_), agent(NULL) {}; diff --git a/pheromone.cc b/pheromone.cc new file mode 100644 index 0000000..c5c4894 --- /dev/null +++ b/pheromone.cc @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +#include "pheromone.h" +#include "main.h" + + +void +pheromones::secrete(class pheromone &p) +{ + for (std::list::iterator pi = spectrum.begin(); pi != spectrum.end(); pi++) { + if (pi->id < p.id) + continue; + if (pi->id == p.id) { + pi->val += p.val; + return; + } + spectrum.insert(pi, p); + return; + } + spectrum.push_back(p); +} + +void +pheromones::seep(class pheromones &to, double alpha, double beta) +{ + for (std::list::iterator pi = spectrum.begin(); pi != spectrum.end(); pi++) { + class pheromone p(pi->id, pi->val * (alpha * PH_COUNT / pi->id + beta)); + to.secrete(p); + } +} + +void +pheromones::decay(double gamma, double delta) +{ + for (std::list::iterator pi = spectrum.begin(); pi != spectrum.end(); pi++) { +next_p: + pi->val *= gamma; + pi->val -= delta; + if (pi->val < 0.001) { + pi = spectrum.erase(pi); + if (pi != spectrum.end()) + goto next_p; + } + } +} diff --git a/pheromone.h b/pheromone.h new file mode 100644 index 0000000..0783a82 --- /dev/null +++ b/pheromone.h @@ -0,0 +1,35 @@ +#ifndef BRMLIFE__PHEROMONE_H +#define BRMLIFE__PHEROMONE_H + +/* Pheromone spectrum in a particular state. */ + +#include + +#define PH_COUNT 65536 + +class pheromone { +public: + int id; + double val; + + pheromone(int id_ = 0, double val_ = 0) + : id (id_), val (val_) {}; +}; + +class pheromones { +public: + std::list spectrum; + + /* Add a pheromone to the spectrum. */ + void secrete(class pheromone &p); + /* Merge slight trail of spectrum to another spectrum (transfer + * by touching). Pheromones with lower index are transmitted + * better than those with higher index: + * v' = v * (alpha / i + beta) */ + void seep(class pheromones &to, double alpha, double beta); + /* Decay the spectrum, multiplying each value by gamma and + * then reducing it by delta. */ + void decay(double gamma, double delta); +}; + +#endif diff --git a/world.h b/world.h index 47a3b81..a70ea18 100644 --- a/world.h +++ b/world.h @@ -9,6 +9,7 @@ struct world { const static int move_cost = -50; const static int attack_cost = -400; const static int defense_cost = -200; + const static int pheromone_cost = -10; const static int move_idle_cost = -15; /* ... * attr.move */ const static int attack_idle_cost = -15; /* ... * attr.attack */ @@ -21,6 +22,11 @@ struct world { const static int dead_decay = -50; const static int herb_rate = 15; /* initially, one herb per herb_rate tiles */ + + const static double phseep_alpha = 0.1; + const static double phseep_beta = 0.05; + const static double phdecay_gamma = 0.95; + const static double phdecay_delta = 0.01; }; #endif From 29fddde51c76de4bc3417472f1662b710487a6d7 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 7 Dec 2011 23:54:17 +0100 Subject: [PATCH 04/12] Example client: Secrete a pheromone --- client/example.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/client/example.pl b/client/example.pl index 7c589a0..6e852a5 100755 --- a/client/example.pl +++ b/client/example.pl @@ -123,6 +123,7 @@ sub take_action($%) { } else { print $socket "move_dir $max->[0] $max->[1]\r\n"; } + print $socket "secrete 65536 1\r\n"; print $socket "\r\n"; } From 20412421426f19ed1c2f88d86ecc432d2d9e634b Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 7 Dec 2011 23:55:37 +0100 Subject: [PATCH 05/12] Make herbs smell This slightly changes herb growth patterns in forests as well, in order to keep energy balance --- agent.cc | 14 ++++++++++++++ agent.h | 2 ++ world.h | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/agent.cc b/agent.cc index fefe291..a1a401f 100644 --- a/agent.cc +++ b/agent.cc @@ -191,6 +191,15 @@ spawn_herb(class tile &t) h->spawn_at(t); } +void +herb::smell_herb(class tile &t) +{ + /* Herb pheromone ("smell") #32768. */ + class pheromone p(32768, world::herb_phintensity); + t.pheromones.secrete(p); + chenergy(p.val * world::pheromone_cost); +} + void herb::on_tick(void) { @@ -204,5 +213,10 @@ herb::on_tick(void) spawn_herb(tile->tile_in_dir(0, -1)); tile->on_agent_leave(*this); tile = NULL; + } else { + smell_herb(tile->tile_in_dir(1, 0)); + smell_herb(tile->tile_in_dir(0, 1)); + smell_herb(tile->tile_in_dir(-1, 0)); + smell_herb(tile->tile_in_dir(0, -1)); } } diff --git a/agent.h b/agent.h index 5687d1f..aa59e0a 100644 --- a/agent.h +++ b/agent.h @@ -64,6 +64,8 @@ public: } void on_tick(void); +private: + void smell_herb(class tile &); }; #endif diff --git a/world.h b/world.h index a70ea18..7b8334e 100644 --- a/world.h +++ b/world.h @@ -15,13 +15,14 @@ struct world { const static int attack_idle_cost = -15; /* ... * attr.attack */ const static int defense_idle_cost = -15; /* ... * attr.defense */ const static int sun_energy = 10; - const static int soil_energy = 10; /* ... times five for lone herbs, times one for dense forests */ + const static int soil_energy = 20; /* ... times five for lone herbs, times one for dense forests */ const static int dead_body_energy = 2000; const static double dead_body_energy_carryover = 0.5; const static int dead_decay = -50; const static int herb_rate = 15; /* initially, one herb per herb_rate tiles */ + const static double herb_phintensity = 1.0; const static double phseep_alpha = 0.1; const static double phseep_beta = 0.05; From 61cdd422522ed730927116be4743244b6fdd02e6 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Wed, 7 Dec 2011 23:59:28 +0100 Subject: [PATCH 06/12] README: Document pheromones --- README | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README b/README index 6fad823..ca5bd7a 100644 --- a/README +++ b/README @@ -38,6 +38,12 @@ The following inputs (in no particular order) are supported: a dead agent A alive agent x herp + pheromones (,,,...) (...)... + (,...) describes set of pheromones on a tile, + in the same order as visual + format is : + : pheromone id 0..65535 + : floating-point number The following outputs are supported: @@ -47,6 +53,12 @@ The following outputs are supported: attack_dir and are integer offsets relative to the current position; may be just {-1,0,1} + secrete + produce a pheromone; pheromones are initially + associated with an agent and trailed at tiles + visited by the agent; pheromones with lower id + transfer from agent to tile faster + energy required is proportional to phintensity After connecting, the client specifies its desired attributes, From 96208e93ba14e1f8b363d909e32df5ad5cecbb2b Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 8 Dec 2011 00:10:09 +0100 Subject: [PATCH 07/12] Pheromones: Merge tile and agent info in pheromones output --- README | 3 ++- connection.cc | 29 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/README b/README index ca5bd7a..6dd7123 100644 --- a/README +++ b/README @@ -40,7 +40,8 @@ The following inputs (in no particular order) are supported: x herp pheromones (,,,...) (...)... (,...) describes set of pheromones on a tile, - in the same order as visual + in the same order as visual; if agent is on a tile, + its pheromones are merged with tile pheromones format is : : pheromone id 0..65535 : floating-point number diff --git a/connection.cc b/connection.cc index c283e36..e91f914 100644 --- a/connection.cc +++ b/connection.cc @@ -41,10 +41,31 @@ connection::senses(int tick_id, class agent &a) for (int i = 0; i < dir_n; i++) { bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), " ("); class pheromones &ps = a.tile->tile_in_dir(dirs[i][0], dirs[i][1]).pheromones; - for (std::list::iterator pi = ps.spectrum.begin(); pi != ps.spectrum.end(); pi++) { - if (pi != ps.spectrum.begin()) - bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), ","); - bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "%d:%f", pi->id, pi->val); + class agent *ai = a.tile->tile_in_dir(dirs[i][0], dirs[i][1]).agent; + if (ai) { + /* We need to merge pheromones. */ + class pheromones &pt = ai->pheromones; + std::list::iterator pi, pj; + for (pi = ps.spectrum.begin(), pj = pt.spectrum.begin(); + pi != ps.spectrum.end() || pj != pt.spectrum.end(); ) { + if (pi != ps.spectrum.begin() || pj != pt.spectrum.begin()) + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), ","); + int i; double v; + if (pj == pt.spectrum.end() || (pi != ps.spectrum.end() && pi->id < pj->id)) { + i = pi->id; v = pi->val; ++pi; + } else if (pi == ps.spectrum.end() || pj->id < pi->id) { + i = pj->id; v = pj->val; ++pj; + } else { + i = pi->id; v = pi->val + pj->val; ++pi, ++pj; + } + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "%d:%f", i, v); + } + } else { + for (std::list::iterator pi = ps.spectrum.begin(); pi != ps.spectrum.end(); pi++) { + if (pi != ps.spectrum.begin()) + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), ","); + bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), "%d:%f", pi->id, pi->val); + } } bufp += snprintf(bufp, sizeof(buf) - (bufp - buf), ")"); } From 343762c3bb1c2bf526e3eec7e49d4c2808bfc604 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 8 Dec 2011 00:26:20 +0100 Subject: [PATCH 08/12] Make herbs die properly on energy depletion --- agent.cc | 11 +++++++++-- agent.h | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/agent.cc b/agent.cc index a1a401f..da51732 100644 --- a/agent.cc +++ b/agent.cc @@ -181,6 +181,14 @@ agent::~agent() } +void +herb::die(void) +{ + /* No corpse, just clean up tile. */ + tile->on_agent_leave(*this); + tile = NULL; +} + static void spawn_herb(class tile &t) { @@ -211,8 +219,7 @@ herb::on_tick(void) spawn_herb(tile->tile_in_dir(0, 1)); spawn_herb(tile->tile_in_dir(-1, 0)); spawn_herb(tile->tile_in_dir(0, -1)); - tile->on_agent_leave(*this); - tile = NULL; + die(); } else { smell_herb(tile->tile_in_dir(1, 0)); smell_herb(tile->tile_in_dir(0, 1)); diff --git a/agent.h b/agent.h index aa59e0a..f0bdfab 100644 --- a/agent.h +++ b/agent.h @@ -43,7 +43,7 @@ public: bool secrete(int id, double intensity); void chenergy(int delta); - void die(void); + virtual void die(void); void on_action_takes(void); virtual void on_tick(void); @@ -63,6 +63,7 @@ public: energy = world::herb_energy; } + void die(void); void on_tick(void); private: void smell_herb(class tile &); From 417a5235a04f0791a7f12e3e9b3e10f759a0142a Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 8 Dec 2011 00:34:03 +0100 Subject: [PATCH 09/12] Describe agent object lifetime --- agent.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/agent.h b/agent.h index f0bdfab..3648dbf 100644 --- a/agent.h +++ b/agent.h @@ -7,6 +7,21 @@ class connection; +/* Agent object lifetime is slightly complicated, since an agent + * may be tied to a tile or a connection or both: + * + * - tile set, connection set: active client or connected corpse + * - tile set, connection NULL: herb or disconnected corpse + * - tile NULL, connection set: negotiation or zombie connection with no corpse anymore + * + * Agents are created (we may not keep this list up to date) on incoming + * connection, by blooming herb or when converting corpse to herb. Herbs + * are immediately spawned (assigned a tile); clients are spawned only + * after negotiation. + * + * Agents are destroyed in the main loop when they are completely abandoned, + * i.e. both their tile and connection become NULL. */ + class agent { public: int id; From 8ae6051e97f728569c526d5e53fa195bd7ddac49 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 8 Dec 2011 02:01:56 +0100 Subject: [PATCH 10/12] Support reconnecting to agents Agents do not die immediately on disconnect anymore. Instead, agent_id is sent at birth and can be sent during negotiation to attach to a disconnected agent. --- README | 19 ++++++++++++++++--- agent.cc | 1 - client/example.pl | 11 ++++++++--- connection.cc | 28 ++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/README b/README index 6dd7123..d637647 100644 --- a/README +++ b/README @@ -20,6 +20,9 @@ CRLF ("\r\n"), not just LF ("\n")! The following inputs (in no particular order) are supported: + agent_id + unique id of agent; may be sent only once at the beginning + (you can use it to reconnect to the same agent later) tick BUMP if received, the agent's move failed @@ -62,9 +65,10 @@ The following outputs are supported: energy required is proportional to phintensity -After connecting, the client specifies its desired attributes, -in the same format as in normal output (line-based, terminated -by empty line), but with these commands instead: +When new agent connects, the client first enters the "negotiation" +phase, specifying its desired attributes, in the same format as in +normal output (line-based, terminated by empty line), but with +these commands instead: move between 0 and 1, describing probability @@ -76,3 +80,12 @@ by empty line), but with these commands instead: In general, higher rate means higher energy maintenance of the appropriate actuators. + +Alternately, the client may send a single command in the negotiation +phase: + + agent_id + +If the id corresponds to a disconnected agent, the connection +is immediately attached to that agent. Combining this with other +negotiation commands is undefined. diff --git a/agent.cc b/agent.cc index da51732..43e6891 100644 --- a/agent.cc +++ b/agent.cc @@ -126,7 +126,6 @@ agent::on_action_takes(void) return; if (conn->error) { - if (!dead) die(); conn->cancel(); conn = NULL; return; diff --git a/client/example.pl b/client/example.pl index 6e852a5..e4abe45 100755 --- a/client/example.pl +++ b/client/example.pl @@ -143,9 +143,14 @@ $socket = IO::Socket::INET->new( print "[ii] connected\r\n"; # negotiate attributs -print $socket "move 1.0\r\n"; -print $socket "attack 1.0\r\n"; -print $socket "defense 1.0\r\n"; +if ($ARGV[1]) { + print "[ii] recovering agent $ARGV[1]\r\n"; + print $socket "agent_id $ARGV[1]\r\n"; +} else { + print $socket "move 1.0\r\n"; + print $socket "attack 1.0\r\n"; + print $socket "defense 1.0\r\n"; +} print $socket "\r\n"; print "[ii] agent created\r\n"; diff --git a/connection.cc b/connection.cc index e91f914..99cf31e 100644 --- a/connection.cc +++ b/connection.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -9,6 +10,7 @@ #include "agent.h" #include "connection.h" +#include "main.h" #define buf_incomplete(buf) \ (buf.find("\r\n") == std::string::npos || (buf.find("\r\n") > 0 && buf.find("\r\n\r\n") == std::string::npos)) @@ -120,6 +122,28 @@ connection::actions(class agent &agent) if (rate >= 0 && rate <= 1) agent.attr.defense = rate; + } else if (negotiation && !cmd.compare("agent_id")) { + int id = -1; + sscanf(line.c_str(), "%d", &id); + if (id < 0) { +bump_negot: + bump(); out_buf.append("\r\n"); + } else { + class agent *a2 = NULL; + for (std::list::iterator ai = agents.begin(); ai != agents.end(); ai++) { + if ((*ai)->id == id) { + a2 = *ai; + break; + } + } + if (!a2 || a2->conn || !a2->tile || (dynamic_cast (a2))) + goto bump_negot; + /* Round and round she goes, where she stops, nobody knows. */ + a2->conn = this; + agent.conn = NULL; + negotiation = false; + } + } else if (!negotiation && !cmd.compare("move_dir") && !(mask & 1)) { int x = 0, y = 0; sscanf(line.c_str(), "%d %d", &x, &y); @@ -154,6 +178,10 @@ connection::actions(class agent &agent) if (negotiation) { negotiation = false; agent.spawn(); + + std::stringstream s; + s << "agent_id " << agent.id << "\r\n"; + out_buf.append(s.str()); } pthread_mutex_unlock(&buf_lock); From b693b5d6f478ab33cc308bbbca03e6ab20d2414e Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 8 Dec 2011 03:02:08 +0100 Subject: [PATCH 11/12] connection::actions(): agent& -> agent*, more natural agent_id handling --- agent.cc | 2 +- connection.cc | 29 +++++++++++++++-------------- connection.h | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/agent.cc b/agent.cc index 43e6891..6c1fa3b 100644 --- a/agent.cc +++ b/agent.cc @@ -131,7 +131,7 @@ agent::on_action_takes(void) return; } - conn->actions(*this); + conn->actions(this); } void diff --git a/connection.cc b/connection.cc index 99cf31e..ead217b 100644 --- a/connection.cc +++ b/connection.cc @@ -87,7 +87,7 @@ connection::bump(void) } void -connection::actions(class agent &agent) +connection::actions(class agent *agent) { pthread_mutex_lock(&buf_lock); if (buf_incomplete(in_buf)) { @@ -107,20 +107,20 @@ connection::actions(class agent &agent) line.erase(0, spofs + 1); if (negotiation && !cmd.compare("move")) { - double rate = agent.attr.move; + double rate = agent->attr.move; sscanf(line.c_str(), "%lf", &rate); if (rate >= 0 && rate <= 1) - agent.attr.move = rate; + agent->attr.move = rate; } else if (negotiation && !cmd.compare("attack")) { - double rate = agent.attr.attack; + double rate = agent->attr.attack; sscanf(line.c_str(), "%lf", &rate); if (rate >= 0 && rate <= 1) - agent.attr.attack = rate; + agent->attr.attack = rate; } else if (negotiation && !cmd.compare("defense")) { - double rate = agent.attr.defense; + double rate = agent->attr.defense; sscanf(line.c_str(), "%lf", &rate); if (rate >= 0 && rate <= 1) - agent.attr.defense = rate; + agent->attr.defense = rate; } else if (negotiation && !cmd.compare("agent_id")) { int id = -1; @@ -140,8 +140,9 @@ bump_negot: goto bump_negot; /* Round and round she goes, where she stops, nobody knows. */ a2->conn = this; - agent.conn = NULL; - negotiation = false; + agent->conn = NULL; + agent = a2; + negotiation = agent->newborn; } } else if (!negotiation && !cmd.compare("move_dir") && !(mask & 1)) { @@ -149,7 +150,7 @@ bump_negot: sscanf(line.c_str(), "%d %d", &x, &y); if (x < -1) x = -1; if (x > 1) x = 1; if (y < -1) y = -1; if (y > 1) y = 1; - if (!agent.move_dir(x, y)) + if (!agent->move_dir(x, y)) bump(); mask |= 1; } else if (!negotiation && !cmd.compare("attack_dir") && !(mask & 2)) { @@ -157,7 +158,7 @@ bump_negot: sscanf(line.c_str(), "%d %d", &x, &y); if (x < -1) x = -1; if (x > 1) x = 1; if (y < -1) y = -1; if (y > 1) y = 1; - if (!agent.attack_dir(x, y)) + if (!agent->attack_dir(x, y)) bump(); mask |= 2; } else if (!negotiation && !cmd.compare("secrete")) { @@ -165,7 +166,7 @@ bump_negot: sscanf(line.c_str(), "%d %lf", &id, &v); if (id < 0) id = 0; if (id >= PH_COUNT) id = PH_COUNT - 1; if (v < 0) v = 0; - if (!agent.secrete(id, v)) + if (!agent->secrete(id, v)) bump(); /* We deliberately allow multiple secrete commands. */ @@ -177,10 +178,10 @@ bump_negot: if (negotiation) { negotiation = false; - agent.spawn(); + agent->spawn(); std::stringstream s; - s << "agent_id " << agent.id << "\r\n"; + s << "agent_id " << agent->id << "\r\n"; out_buf.append(s.str()); } diff --git a/connection.h b/connection.h index 2102cda..2164b43 100644 --- a/connection.h +++ b/connection.h @@ -28,7 +28,7 @@ public: } void senses(int tick_id, class agent &); - void actions(class agent &); + void actions(class agent *); void cancel(void); From 23f2fce7ff7ddc7c9299af4a4aec403f8a7ac804 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 8 Dec 2011 03:14:57 +0100 Subject: [PATCH 12/12] Support for breeding One party initiates the breeding; the other party exerts most of the energy if its breeding key matches. It is that party's responsibility to set up the newborn agent's connection (but the father can pass it an arbitrary message). Newborn is spawned immediately but can be renegotiated on first connect. --- README | 14 +++++++++++++- agent.cc | 47 +++++++++++++++++++++++++++++++++++++++++++++++ agent.h | 17 +++++++++++------ connection.cc | 26 ++++++++++++++++++++++++-- connection.h | 1 + world.h | 4 ++++ 6 files changed, 100 insertions(+), 9 deletions(-) diff --git a/README b/README index d637647..d29cfe1 100644 --- a/README +++ b/README @@ -27,6 +27,10 @@ The following inputs (in no particular order) are supported: BUMP if received, the agent's move failed (or attack of non-existent agent, etc.) + BRED + a new agent has been spawned, connect using agent_id ; + is arbitrary string passed from the father, + can be used for genetic recombination DEAD if received, the agent is dead! energy @@ -57,6 +61,9 @@ The following outputs are supported: attack_dir and are integer offsets relative to the current position; may be just {-1,0,1} + breed_dir + is arbitrary string passed to the "mother" + (to be passed to the child) secrete produce a pheromone; pheromones are initially associated with an agent and trailed at tiles @@ -77,6 +84,10 @@ these commands instead: between 0 and 1. defense between 0 and 1. + breeding_key + is arbitrary integer number; default is 0; + breeding succeeds only between individuals with key + that is near enough (abs(key1-key2) < kappa). In general, higher rate means higher energy maintenance of the appropriate actuators. @@ -88,4 +99,5 @@ phase: If the id corresponds to a disconnected agent, the connection is immediately attached to that agent. Combining this with other -negotiation commands is undefined. +negotiation commands is undefined, except for newborns - in that case, +negotiation commands must follow after agent_id. diff --git a/agent.cc b/agent.cc index 6c1fa3b..277d81a 100644 --- a/agent.cc +++ b/agent.cc @@ -91,6 +91,53 @@ agent::attack_dir(int dir_x, int dir_y) return attack_roll >= defense_roll; } +bool +agent::breed_dir(int dir_x, int dir_y, std::string info) +{ + if (dead || !tile) + return false; + + class tile *t2 = &tile->tile_in_dir(dir_x, dir_y); + class agent *a = t2->agent; + if (!a || a->dead || !a->conn) + return false; + + /* Self-breeding may not be a bad thing, but there is just + * a technical problem with in/out buf locking. */ + assert(a != this); + + if (abs(a->attr.breeding_key - attr.breeding_key) > world::breeding_kappa) + return false; + + chenergy(world::breed_out_cost); + a->chenergy(world::breed_in_cost); + if (a->dead) + return false; + + /* Grab a tile. */ + int spawn_dirs[][2] = { + {0,-1}, {1,-1}, {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, + }; + int spawn_dir_n = sizeof(spawn_dirs) / sizeof(spawn_dirs[0]); + class tile *tb = NULL; + for (int i = 0; i < spawn_dir_n; i++) { + class tile *t = &tile->tile_in_dir(spawn_dirs[i][0], spawn_dirs[i][1]); + if (!t->agent) { + tb = t; + break; + } + } + if (!tb) + return false; // still-born + + /* New agent, yay. */ + class agent *ab = new class agent(agent_id++, NULL, map); + agents.push_back(ab); + ab->spawn_at(*tb); + a->conn->bred(ab->id, info); + return true; +} + bool agent::secrete(int id, double intensity) { diff --git a/agent.h b/agent.h index 3648dbf..5a33611 100644 --- a/agent.h +++ b/agent.h @@ -1,6 +1,8 @@ #ifndef BRMLIFE__AGENT_H #define BRMLIFE__AGENT_H +#include + #include "map.h" #include "pheromone.h" #include "world.h" @@ -12,12 +14,12 @@ class connection; * * - tile set, connection set: active client or connected corpse * - tile set, connection NULL: herb or disconnected corpse - * - tile NULL, connection set: negotiation or zombie connection with no corpse anymore + * - tile NULL, connection set: negotiating client or zombie connection with no corpse anymore * * Agents are created (we may not keep this list up to date) on incoming - * connection, by blooming herb or when converting corpse to herb. Herbs - * are immediately spawned (assigned a tile); clients are spawned only - * after negotiation. + * connection, by breeding, by blooming herb or when converting corpse + * to herb. Herbs and bred clients are immediately spawned (assigned a tile); + * connecting clients are spawned only after negotiation. * * Agents are destroyed in the main loop when they are completely abandoned, * i.e. both their tile and connection become NULL. */ @@ -34,19 +36,21 @@ public: double move; double attack; double defense; + long breeding_key; } attr; int energy; - bool dead; + bool newborn, dead; class pheromones pheromones; agent(int id_, class connection *conn_, class map &map_) - : id (id_), conn (conn_), map (map_), tile (NULL) + : id (id_), conn (conn_), map (map_), tile (NULL), newborn (true) { attr.move = 1.0; attr.attack = 0.5; attr.defense = 0.5; + attr.breeding_key = 0; energy = world::newborn_energy; dead = false; }; @@ -55,6 +59,7 @@ public: bool move_dir(int dir_x, int dir_y); bool attack_dir(int dir_x, int dir_y); + bool breed_dir(int dir_x, int dir_y, std::string info); bool secrete(int id, double intensity); void chenergy(int delta); diff --git a/connection.cc b/connection.cc index ead217b..9df4ac0 100644 --- a/connection.cc +++ b/connection.cc @@ -79,6 +79,16 @@ connection::senses(int tick_id, class agent &a) pthread_mutex_unlock(&buf_lock); } +void +connection::bred(int agent_id, std::string &info) +{ + pthread_mutex_lock(&buf_lock); + std::stringstream s; + s << "BRED " << agent_id << " " << info << "\r\n"; + out_buf.append(s.str()); + pthread_mutex_unlock(&buf_lock); +} + void connection::bump(void) { @@ -121,6 +131,8 @@ connection::actions(class agent *agent) sscanf(line.c_str(), "%lf", &rate); if (rate >= 0 && rate <= 1) agent->attr.defense = rate; + } else if (negotiation && !cmd.compare("breeding_key")) { + sscanf(line.c_str(), "%ld", &agent->attr.breeding_key); } else if (negotiation && !cmd.compare("agent_id")) { int id = -1; @@ -161,6 +173,15 @@ bump_negot: if (!agent->attack_dir(x, y)) bump(); mask |= 2; + } else if (!negotiation && !cmd.compare("breed_dir") && !(mask & 4)) { + int x = 0, y = 0, len = 0; + sscanf(line.c_str(), "%d %d %n", &x, &y, &len); + line.erase(0, len); + if (x < -1) x = -1; if (x > 1) x = 1; + if (y < -1) y = -1; if (y > 1) y = 1; + if (!agent->breed_dir(x, y, line)) + bump(); + mask |= 4; } else if (!negotiation && !cmd.compare("secrete")) { int id = 0; double v = 0; sscanf(line.c_str(), "%d %lf", &id, &v); @@ -177,8 +198,9 @@ bump_negot: in_buf.erase(0, 2); if (negotiation) { - negotiation = false; - agent->spawn(); + agent->newborn = negotiation = false; + if (!agent->tile) + agent->spawn(); std::stringstream s; s << "agent_id " << agent->id << "\r\n"; diff --git a/connection.h b/connection.h index 2164b43..7c4722f 100644 --- a/connection.h +++ b/connection.h @@ -28,6 +28,7 @@ public: } void senses(int tick_id, class agent &); + void bred(int agent_id, std::string &info); void actions(class agent *); void cancel(void); diff --git a/world.h b/world.h index 7b8334e..ac21459 100644 --- a/world.h +++ b/world.h @@ -9,6 +9,8 @@ struct world { const static int move_cost = -50; const static int attack_cost = -400; const static int defense_cost = -200; + const static int breed_out_cost = -newborn_energy/4; + const static int breed_in_cost = -newborn_energy*3/4; const static int pheromone_cost = -10; const static int move_idle_cost = -15; /* ... * attr.move */ @@ -24,6 +26,8 @@ struct world { const static int herb_rate = 15; /* initially, one herb per herb_rate tiles */ const static double herb_phintensity = 1.0; + const static long breeding_kappa = 10000; + const static double phseep_alpha = 0.1; const static double phseep_beta = 0.05; const static double phdecay_gamma = 0.95;