#include #include #include #include #include #include using namespace std; class Map { public: Map(); Map(string seed); int width; int height; string values; char value_at(int x, int y); void set_value_at(int x, int y, char val); bitset<24> observe(int x, int y); }; Map::Map(string seed) { string delim = ":"; int start = 0U; int end = seed.find(delim); string rows = seed.substr(start, end - start); height = stoi(rows); start = end + delim.length(); end = seed.find(delim, start); string cols = seed.substr(start, end-start); width = stoi(cols); start = end + delim.length(); end = seed.find(delim, start); values = seed.substr(start, end); } void Map::set_value_at(int x, int y, char val) { if (x < 0 || y < 0 || x >= height || y >= width){ return; } values[x*width + y] = val; return; } char Map::value_at(int x, int y) { if (x < 0 || y < 0 || x >= height || y >= width){ char temp = '*'; return temp; } return values.at(x*width + y); } bitset<24> Map::observe(int x, int y) { /* * * There are 8 zones around the rat, four close zones and four far zones * The close zones are the squares in a given quadrant that are 1 away * (e.g. to my left, to my top-left, and above me are the one away squares in * the -x, -y or NW quadrant) * The far zones are the squares from each quadrant that are 2 or 3 moves away * (e.g. the squares with distance (-3,0), (0, -3), (-3, -3) are the outer * corners of the NW quadrant. * The quadrants are always ordered NW then NE then SW then SE first * close and then far. * This method outputs 24 0/1 values, first the 8 zones are asked if they have * an obstacle "*" those 8 answers will be senses[0] through senses[7] in the * above mentioned order * Then each of the 8 zones is asked is they have food, "$", again 8 answers. * Finally each zone is asked if it contains a pit "X". * These will act as the rat's "senses". */ string targets = "*$X"; string close[4] = {"","","",""}; string far[4] = {"","","",""}; int quadrant[4][2] = {{-1,-1},{-1,1},{1,-1},{1,1}}; for (int dx = 0; dx < 4; dx++) { for (int dy = 0; dy < 4; dy++) { for (int qi = 0; qi < 4; qi++) { int sign_x = quadrant[qi][0]; int sign_y = quadrant[qi][1]; if (dx == 0 && dy == 0){ break; } else if (dx > 1 || dy > 1){ far[qi] += value_at(x + sign_x*dx, y + sign_y*dy); } else { close[qi] += value_at(x + sign_x*dx, y + sign_y*dy); } } } } int counter = 0; bitset<24> senses; bool bit_value = 0; size_t found; for (int i = 0; i < 3; i++) { char target = targets.at(i); for (int qi = 0; qi < 4; qi++) { bit_value = 0; found = close[qi].find(target); if (string::npos != found) { bit_value = 1; } senses.set(counter, bit_value); counter++; } for (int qi = 0; qi < 4; qi++) { bit_value = 0; found = far[qi].find(target); if (string::npos != found) { bit_value = 1; } senses.set(counter, bit_value); counter++; } } return senses; } class NeuralNet { public: NeuralNet(); void setWeights(vector weights); static vector translateGenome(string genome); static string translateWeights(vector weights); static const int n_i = 25; static const int n_h = 10; static const int n_o = 4; bitset makeChoices(bitset inputs); double W_ih[n_i][n_h]; double W_ho[n_h][n_o]; }; NeuralNet::NeuralNet(){}; void NeuralNet::setWeights(vector weights) { /* * Takes in n_i*n_h + n_h*n_o double values where n_i is the number of input * neurons (25 in our case the first is "pain" (did the rat just hit an obstacle?) * the other 24 are explained above in Map::observe) * The n_h (10 by my settings) hidden neurons are there for complex behavior, * they can kind of be thought of as emotional states. * The n_o outputs will be an encoding of the rat's next movement choice. */ int pos = 0; int tpos = 0; int W_ih_size = n_i*n_h; int x, y; for (vector::iterator it = weights.begin(); it != weights.end(); ++it){ if (pos < W_ih_size){ x = int(pos/n_h); y = int(pos % n_h); W_ih[x][y] = *it; pos++; } else { tpos = pos - W_ih_size; x = int(tpos / n_o); y = int(tpos % n_o); W_ho[x][y] = *it; pos++; } } } bitset<4> NeuralNet::makeChoices(bitset<25> inputs) { bitset hidden; for (int i = 0; i < n_h; i++) { double res = 0.0; for (int j = 0; j < n_i; j++) { double tval = double(inputs[j])*double(W_ih[j][i]); res += tval; } /* After checking which weights from inputs affect a given hidden neuron * If there is more positive than negative we fire it. */ hidden[i] = res > 0; } bitset output; for (int i = 0; i < n_o; i++) { double res = 0.0; for (int j = 0; j < n_h; j++) { double tval = double(hidden[j])*double(W_ho[j][i]); res += tval; } output[i] = res > 0; } return output; } vector NeuralNet::translateGenome(string genome) { vector temp(0); for (int i = 0; i < genome.length(); i++) { char c = genome.at(i); double temp_val = double(c) - 82.0; temp.push_back(temp_val/40.0); } return temp; } string NeuralNet::translateWeights(vector weights){ string answer = ""; int pos = 0; double temp = 0.0; for (vector::iterator it = weights.begin(); it != weights.end(); ++it){ temp = *it; temp *= 40; temp += 82; temp = round(temp); answer += char(temp); } return answer; } class Rat { public: Rat(int start_x, int start_y, string genome); int isDead(); void changeEnergy(int delta); bitset<24> observeWorld(Map world); bitset<4> makeChoices(bitset<24> senses); void enactChoices(bitset<4> choices); int x, y; bool hit_obstacle; int speed_y, speed_x, energy; private: string genome; NeuralNet brain; }; Rat::Rat(int start_x, int start_y, string genome) { x = start_x; y = start_y; speed_x = 1; speed_y = 1; energy = 30; hit_obstacle = 0; genome = genome; brain.setWeights(NeuralNet::translateGenome(genome)); } int Rat::isDead() { if (energy <= 0){ return 1; } else { return 0; } } void Rat::changeEnergy(int delta) { energy = energy + delta; } void Rat::enactChoices(bitset<4> choices) { speed_x = choices[0] - choices[1]; speed_y = choices[2] - choices[3]; } bitset<24> Rat::observeWorld(Map world) { bitset<24> observations = world.observe(x, y); return observations; } bitset<4> Rat::makeChoices(bitset<24> observations) { bitset<25> pain_and_observations; for (int i = 1; i < 25; i++) { pain_and_observations[i] = observations[i-1]; } pain_and_observations[0] = hit_obstacle; bitset<4> choices = brain.makeChoices(pain_and_observations); /* To see input observations and output decisions uncomment the below code cout << "observed "<< pain_and_observations.template to_string, std::allocator >() << endl; cout << "chose to do: "<< choices.template to_string, std::allocator >() << endl;*/ /* choices[0] is the choice to move down by 1, choices[1] is to move up by 1, * choices[2] is to move right by 1, choices[3] is to move left by 1 * if all four are 1 then the rat doesn't move for example, while if it is * choices[0] = 1, choices[1] = 0, choices[2] = 0 and choices[3] = 1 then it moves * down and left */ return choices; } int simulator(string mapseed, string genome, int start_x, int start_y) { Map board(mapseed); Rat arat(start_x, start_y, genome); int cur_x, cur_y; int target_x, target_y; int dx, dy; char next_spot; int moves = 0; while (!arat.isDead()) { moves++; cur_x = arat.x; cur_y = arat.y; cout << "rat at "<< cur_x << " by " << cur_y << " on move " << moves << endl; arat.enactChoices(arat.makeChoices(arat.observeWorld(board))); target_x = cur_x + arat.speed_x; target_y = cur_y + arat.speed_y; next_spot = board.value_at(target_x, target_y); arat.hit_obstacle = 0; if (char(next_spot) == char('$')) { arat.changeEnergy(10); board.set_value_at(target_x, target_y, '.'); } else if (char(next_spot) == char('X')) { arat.energy = 0; } else if (char(next_spot) == char('*')) { arat.hit_obstacle = 1; arat.changeEnergy(-10); target_x = cur_x; target_y = cur_y; } arat.changeEnergy(-1); arat.x = target_x; arat.y = target_y; } return moves; } int main(void){ string mapseed = "25:25:..$.$.X.............X....$X.X*..X$..X...*X$..$...X$.$......X.$.X...XX.$.X*.*.*..X..X.**.......X..$$$...........XX.....................$...X...*.$..X..$X..........$.*..X.....$.X..$*.$X......$...X.*X$......$.**.X.X..XX$X..*....*..X.X....$...X...X........$.X....$...*...X$*........X..$*$$......$$...$*..X.$.$......$.$.$...$..X.*.....X..$......$.XX*..X.$.X......X$*.**.....X*...$..XX..X.....$....X....X...X....X.$X$..X..........$...*.X$..X...$*...........*....XXX$$.$.$..*$XX..XX..*.....$......X.XX$..$$..X$.XX.$$..X.*..*......X......$..$.$$..*...X.........$X....$X.$$.*.$.$.$..**.....X.$.$X.*.$.........$**..X.X.X$X.$.*X.X*..$*."; string genome = "Sfz2smu:zej,9j:R\\[thR73RzRdP0U.v2TF9-X/PTlOPdYF+k1N;o3`[uTfT>RTq:rsTBk:gYlNq-[@OmMrvVo^U\\z=JV>l.tAq*\\BLM3vJQLfnnpr_mN-7RN;^.-9j4ddZKXN,gvH;x:qYAgPz+tb29Li[qJ/jH+UepmPpV0qC^u2tpvl_/Z-`OTmijR@XW5iJRk0/\\ztnzDHBu+HmsY*,AlZy`aJZ?WFQf*?>3`z6\\TiU+O,MvIH\\7zL?:=/.w^V/XH-`[X+8L7.+zxDBEm;jwW29jp/oj