Pac-Man Evolution
Loading...
Searching...
No Matches
EVNTrainer.cpp
1/*----------------------------------------------------------------------
2Pac-Man Evolution - Roberto Prieto
3Copyright (C) 2018-2024 MegaStorm Systems
4contact@megastormsystems.com - http://www.megastormsystems.com
5
6This software is provided 'as-is', without any express or implied
7warranty. In no event will the authors be held liable for any damages
8arising from the use of this software.
9
10Permission is granted to anyone to use this software for any purpose,
11including commercial applications, and to alter it and redistribute it
12freely, subject to the following restrictions:
13
141. The origin of this software must not be misrepresented; you must not
15claim that you wrote the original software. If you use this software
16in a product, an acknowledgment in the product documentation would be
17appreciated but is not required.
182. Altered source versions must be plainly marked as such, and must not be
19misrepresented as being the original software.
203. This notice may not be removed or altered from any source distribution.
21
22------------------------------------------------------------------------
23
24EVolution Neural Trainer
25
26------------------------------------------------------------------------ */
27
28#include "EVNTrainer.h"
29#include "GeneticAlgorithm.h"
30#include "GameField.h"
31#include "BrainsFactory.h"
32#include "Brains.h"
33#include "Objects.h"
34#include "ObjectsPacMan.h"
35#include "Pac-Man_Evolution.h"
36#include <algorithm>
37
38#define PME_EVNTRAINER_EVOLVED "Evolved"
39#define PME_EVNTRAINER_TRAINING "Training-"
40
41// Singleton stuff
42EVNTrainer* EVNTrainer::mInstance = nullptr;
43
44// Create a single instance. Initialize a random seed to our random number generators
45EVNTrainer& EVNTrainer::Instance()
46{
47 if(!mInstance) mInstance = new(std::nothrow) EVNTrainer;
48 return *mInstance;
49}
50
51// Explicit destructor.
52void EVNTrainer::Terminate()
53{
54 if(mInstance) delete mInstance;
55 mInstance = nullptr;
56}
57
58// Constructor
59EVNTrainer::EVNTrainer()
60{
61 dCrossOverRate = PME_GA_CROSSOVER_RATE;
62 dMutationRate = PME_GA_MUTATION_RATE;
63 dMaxPerturbation = PME_GA_MAX_PERTURBATION;
64 iNumElite = PME_GA_NUM_ELITE;
65 iNumCopiesElite = PME_GA_NUM_COPIES_ELITE;
66 iPopulation = PME_GA_POPULATION;
67}
68
69// Destructor
70EVNTrainer::~EVNTrainer()
71{
72}
73
74// Display information of the creature evolution status
75Sint32 EVNTrainer::Creature::info()
76{
77 Log& mLog = *Main::Instance().ILogMgr().get();
78 Sint32 i, k;
79
80 if(iNumBestCreatures > 0)
81 {
82 for(k = 0; k < iNumBestCreatures && k < vBestCreaturesNN.size(); ++k)
83 {
84 mLog.msg(LML_INFO, " BestCreature (%d/%d): ", k + 1, iNumBestCreatures);
85 for(i = 0; i < vBestCreaturesNN[k].vWeights.size(); ++i)
86 {
87 mLog.msg(LML_INFO, "%.6f", vBestCreaturesNN[k].vWeights[i]);
88 if(i != (vBestCreaturesNN[k].vWeights.size() - 1)) mLog.msg(LML_INFO, ",");
89 }
90 mLog.msg(LML_INFO, "\n");
91 }
92 mLog.msg(LML_INFO, "\n");
93 }
94 return 0;
95}
96
97// Creatures constructor
98EVNTrainer::Creature::Creature(Sint32 iIdentifier)
99{
100 iID = iIdentifier;
101 iGeneration = 0;
102 iBestCreatureGeneration = 0;
103 dBestCreatureFitness = 0;
104 iNumBestCreatures = 3; // Minimum must be 1!
105 vBestCreaturesNN.clear();
106 vBrains.clear();
107 pGA = nullptr;
108 vGenomesPopulation.clear();
109}
110
111// Creatures destructor
112EVNTrainer::Creature::~Creature()
113{
114 if(pGA != nullptr) delete pGA;
115 pGA = nullptr;
116}
117
118// Load a Ghost neural network into a ArtificialNeuralNet object
119Sint32 EVNTrainer::load(string& sGhostName, ArtificialNeuralNet* pNN, string& sCDCFile, string& sXMLName)
120{
121 Sint32 idXML = -1;
122 Main& mC64 = Main::Instance();
123 Log& mLog = *Main::Instance().ILogMgr().get();
124 XML* pXML;
125 Sint32 iInputs, iOutputs, iLayers, iNpL, iGeneration, iActivationFunc;
126 double dFitness;
127 vector<double> vWeights;
128 string sName, sType, sBuff;
129 size_t sizePos;
130
131 // With no sCDCFile use default one
132 if(sCDCFile.empty()) sCDCFile = PME_GHOSTS_NN_FILE;
133 // With no sXMLName use default one
134 if(sXMLName.empty()) sXMLName = PME_GHOSTS_NN_BLOCK;
135
136 // Extract name and type from ghost name
137 sizePos = sGhostName.find_first_of("-", 0);
138 if(sizePos == string::npos) return -1;
139 sName = sGhostName.substr(0, sizePos);
140 sType = sGhostName.substr(sizePos + 1, sGhostName.length());
141
142 // First try to open CDC file and load the XML or failback to external XML
143 if(mC64.ITool().fileExists(sCDCFile) == 0) idXML = mC64.IXMLMgr().load(sCDCFile, sXMLName);
144 if(idXML < 0)
145 {
146 idXML = mC64.IXMLMgr().loadFromFile(sXMLName);
147 if(idXML < 0)
148 {
149 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not load XML file '%s' from '%s'\n", sXMLName.c_str(), sCDCFile.c_str());
150 return -1;
151 }
152 }
153 pXML = mC64.IXMLMgr().get(idXML);
154
155 // Point to ghosts node
156 if(pXML->nodePointTo(3, "Ghosts", sName.c_str(), sType.c_str()) != 0)
157 {
158 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not find 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
159 mC64.IXMLMgr().close(idXML);
160 return -1;
161 }
162
163 // Load attributes, any error will cause a failure
164 if(pXML->getAttribute("generation", iGeneration) != 0)
165 {
166 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'generation' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
167 mC64.IXMLMgr().close(idXML);
168 return -1;
169 }
170 if(pXML->getAttribute("fitness", dFitness) != 0)
171 {
172 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'fitness' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
173 mC64.IXMLMgr().close(idXML);
174 return -1;
175 }
176 if(pXML->getAttribute("activation_func", iActivationFunc) != 0)
177 {
178 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'activation_func' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
179 mC64.IXMLMgr().close(idXML);
180 return -1;
181 }
182 if(iActivationFunc < 0) iActivationFunc = ANN_ACTIVATION_LINEAR;
183 else if(iActivationFunc > 2) iActivationFunc = ANN_ACTIVATION_SIGMOID;
184 if(pXML->getAttribute("inputs", iInputs) != 0)
185 {
186 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'inputs' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
187 mC64.IXMLMgr().close(idXML);
188 return -1;
189 }
190 if(pXML->getAttribute("outputs", iOutputs) != 0)
191 {
192 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'outputs' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
193 mC64.IXMLMgr().close(idXML);
194 return -1;
195 }
196 if(pXML->getAttribute("layers", iLayers) != 0)
197 {
198 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'layers' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
199 mC64.IXMLMgr().close(idXML);
200 return -1;
201 }
202 if(pXML->getAttribute("neurons_per_layer", iNpL) != 0)
203 {
204 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'neurons_per_layer' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
205 mC64.IXMLMgr().close(idXML);
206 return -1;
207 }
208
209 // Load neuron weights
210 if(pXML->getText(sBuff) != 0)
211 {
212 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read neuron weights for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
213 mC64.IXMLMgr().close(idXML);
214 return -1;
215 }
216
217 // Close XML
218 mC64.IXMLMgr().close(idXML);
219
220 // Initialize neural network
221 pNN->init(iInputs, iOutputs, iLayers, iNpL);
222 pNN->setActivationFunction(iActivationFunc);
223
224 // Load weights
225 parseWeights((char*)sBuff.c_str(), vWeights);
226 if(pNN->getNumberOfWeights() > vWeights.size())
227 {
228 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read neuron weights for 'Ghosts\\%s\\%s'. Parsed '%d' but needed '%d'\n", sName.c_str(), sType.c_str(), vWeights.size(), pNN->getNumberOfWeights());
229 return -1;
230 }
231 pNN->setWeights(vWeights);
232
233 // Print info
234 #ifdef DEBUG_EVN
235 mLog.msg(LML_INFO, " [EVNTrainer] Info: '%s\\%s' ghost loaded: G('%d') - F('%.6f') - I('%d') - O('%d') - AF('%d')\n",
236 sName.c_str(), sType.c_str(), iGeneration, dFitness, iInputs, iOutputs, iActivationFunc);
237 #endif
238
239 return 0;
240}
241
242// Execute a training based on the globalstatus
243Sint32 EVNTrainer::execute(GlobalStatus& pGlobalStatus, GameField& pGameField)
244{
245 Main& mC64 = Main::Instance();
246 Log& mLog = *mC64.ILogMgr().get();
247 Sint32 i, j, k, iDone, iLifes;
248
249 Actor* pActor;
250 PacMan* pPacMan;
251
252 double dFitness;
253 string sName;
254
255 // 1.Prepare Creatures vector
256 if(initCreatures(pGlobalStatus) != 0)
257 {
258 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: no ghost ready to be trained.\n");
259 return -1;
260 }
261
262 // 2.Initialize Genetics algorithm for each creature
263 for(k = 0; k < (Sint32)vCreatures.size(); ++k)
264 {
265 i = vCreatures[k]->vBrains[0]->getNeuralNet()->getNumberOfWeights();
266 vCreatures[k]->pGA = new(std::nothrow) GeneticAlgorithm(iPopulation, dMutationRate, dCrossOverRate, dMaxPerturbation, iNumElite, iNumCopiesElite, i);
267 for(j = 0; j < iPopulation; ++j)
268 {
269 vCreatures[k]->pGA->init(j, vCreatures[k]->vBrains[j]->getNeuralNet()->getWeights());
270 }
271 vCreatures[k]->vGenomesPopulation = vCreatures[k]->pGA->getChromos();
272 }
273
274 // 3.Main training loop: repeat the process iExecutions times
275 #ifdef DEBUG_EVN
276 Sint32 iTimeStart, iTimeGenerationStart, iTimePopulationStart;
277 mLog.msg(LML_INFO, "\n [EVNTrainer] Starting evolution of '%d' creatures with a population of '%d' during '%d' generations\n", vCreatures.size(), iPopulation, pGlobalStatus.workBench.iExecutions);
278 mLog.msg(LML_INFO, " CrossOver rate of '%.2f' - Mutation rate of '%.2f' with a max perturbation of '%.2f'\n", PME_GA_CROSSOVER_RATE, PME_GA_MUTATION_RATE, PME_GA_MAX_PERTURBATION);
279 FILE* fp = fopen(pGlobalStatus.workBench.szOutputCSV, "wt");
280 fprintf(fp, "BestCreatureFitness,AveragePopulationFitness\n");
281 iTimeStart = mC64.ITimer().getTicksNow();
282 #endif
283 i = 0;
284 pGlobalStatus.iGameType = PME_GAME_WORKBENCH;
285 while(i < pGlobalStatus.workBench.iExecutions)
286 {
287 // Next iteration
288 ++i;
289
290 #ifdef DEBUG_EVN
291 mLog.msg(LML_INFO, " Generation '%d':\n", i);
292 iTimeGenerationStart = mC64.ITimer().getTicksNow();
293 #endif
294 // Population loop. Allow multiples creatures training at the same time
295 for(j = 0; j < iPopulation; ++j)
296 {
297 #ifdef DEBUG_EVN
298 iTimePopulationStart = mC64.ITimer().getTicksNow();
299 #endif
300 // Initialize gamefield
301 iDone = pGameField.init();
302 pGlobalStatus.iPoints = 0;
303
304 // Set training brains for our creatures
305 for(k = 0; k < (Sint32)vCreatures.size(); ++k)
306 {
307 pActor = pGameField.getActor(vCreatures[k]->iID);
308 if(pActor) pActor->setBrain((PME_BRAIN_TYPE_TRAINING0 << j) | vCreatures[k]->iID);
309 }
310
311 // Run the simulation
312 while(iDone == PME_LOOP)
313 {
314 iDone = pGameField.execute();
315 if(iDone == PME_MAZE_END) iDone = pGameField.nextMaze();
316 }
317
318 // Assign fitness to our creatures based on the inverse of PacMan's points plus a modification based on the game end
319 pPacMan = reinterpret_cast<PacMan*>(pGameField.getActor(PME_OBJECT_PACMAN));
320 if(pPacMan != nullptr) iLifes = pPacMan->getLifes();
321 else iLifes = PME_PACMAN_START_LIFES;
322 if(iDone == PME_BREAK) // Aborted by the user or time ran out
323 {
324 pGlobalStatus.iPoints = pGlobalStatus.iPoints * (iLifes * 10);
325 }
326 dFitness = (1.0 / (double)pGlobalStatus.iPoints);
327 for(k = 0; k < (Sint32)vCreatures.size(); ++k) vCreatures[k]->vBrains[j]->setFitness(dFitness);
328
329 // Output intermediate stats for each population
330 #ifdef DEBUG_EVN
331 mLog.msg(LML_INFO, " Population '%d' reached Maze '%d' in %2.2f seconds with a fitness of '%.6f' (",
332 j, pGameField.getMazeNumber(), (float)(mC64.ITimer().getTicksNow() - iTimePopulationStart) / 1000.0f, dFitness);
333 if(iDone == PME_BREAK) mLog.msg(LML_INFO, "Aborted, %d lifes)\n", iLifes);
334 else mLog.msg(LML_INFO, "PacMan died)\n");
335 #endif
336
337 // Close the gamefield
338 pGameField.close();
339 }
340
341 // Apply genetics algorithm to our creatures
342 for(k = 0; k < (Sint32)vCreatures.size(); ++k)
343 {
344 // Loop through the population
345 for(j = 0; j < iPopulation; ++j)
346 {
347 // Get the fitness
348 vCreatures[k]->vGenomesPopulation[j].dFitness = vCreatures[k]->vBrains[j]->getFitness();
349
350 // Update stats for best population
351 if(vCreatures[k]->vBrains[j]->getFitness() > vCreatures[k]->dBestCreatureFitness)
352 {
353 vCreatures[k]->dBestCreatureFitness = vCreatures[k]->vBrains[j]->getFitness();
354 vCreatures[k]->iBestCreatureGeneration = vCreatures[k]->iGeneration;
355 }
356
357 // Gather best creatures genomes
358 if(vCreatures[k]->vBestCreaturesNN.size() < vCreatures[k]->iNumBestCreatures)
359 {
360 // First genomes and keep then sorted
361 vCreatures[k]->vBestCreaturesNN.push_back(vCreatures[k]->vGenomesPopulation[j]);
362 std::sort(vCreatures[k]->vBestCreaturesNN.begin(), vCreatures[k]->vBestCreaturesNN.end());
363 }
364 else if(vCreatures[k]->iNumBestCreatures > 0)
365 {
366 // Compare with the lowest and if it is greater than it, add it!
367 if(vCreatures[k]->vGenomesPopulation[j].dFitness > vCreatures[k]->vBestCreaturesNN[0].dFitness)
368 {
369 vCreatures[k]->vBestCreaturesNN.push_back(vCreatures[k]->vGenomesPopulation[j]);
370 }
371 }
372 }
373
374 // Keep only the best iNumBestCreatures
375 if(vCreatures[k]->iNumBestCreatures > 0)
376 {
377 std::sort(vCreatures[k]->vBestCreaturesNN.rbegin(), vCreatures[k]->vBestCreaturesNN.rend());
378 vCreatures[k]->vBestCreaturesNN.resize(vCreatures[k]->iNumBestCreatures);
379 std::sort(vCreatures[k]->vBestCreaturesNN.begin(), vCreatures[k]->vBestCreaturesNN.end());
380 }
381
382 // Apply a new epoch. Avoid last execution for having rights creatures at saving time
383 if(i < pGlobalStatus.workBench.iExecutions)
384 vCreatures[k]->vGenomesPopulation = vCreatures[k]->pGA->epoch(vCreatures[k]->vGenomesPopulation);
385
386 // Insert new evolved chromosomes(weights) in to the brains
387 for(j = 0; j < iPopulation; ++j)
388 {
389 vCreatures[k]->vBrains[j]->getNeuralNet()->setWeights(vCreatures[k]->vGenomesPopulation[j].vWeights);
390 //vCreatures[k]->vBrains[j]->setFitness(0); // Reset fitness
391 }
392
393 // Increase the generation
394 ++vCreatures[k]->iGeneration;
395 }
396
397 // Export to CSV best fitness and output intermediate stats for each generation
398 #ifdef DEBUG_EVN
399 fprintf(fp, "%.6f,%.6f\n", vCreatures[0]->dBestCreatureFitness, vCreatures[0]->pGA->fitnessAverage());
400 fflush(fp);
401 mLog.msg(LML_INFO, " Done in %2.2f seconds. BestF '%.6f' AverageF '%.6f' Mutations '%d' CrossOvers '%d' - BestF so far '%.6f'\n",
402 (float)(mC64.ITimer().getTicksNow() - iTimeGenerationStart) / 1000.0f, vCreatures[0]->pGA->fitnessBest(), vCreatures[0]->pGA->fitnessAverage(),
403 vCreatures[0]->pGA->getMutation(), vCreatures[0]->pGA->getCrossOver(), vCreatures[0]->dBestCreatureFitness);
404 #endif
405 }
406
407 // 4.Save
408 saveCreatures(PME_GHOSTS_NN_FILE, PME_GHOSTS_NN_BLOCK);
409
410 // 5. Creatures stats, remove them and return
411 #ifdef DEBUG_EVN
412 fclose(fp);
413 mLog.msg(LML_INFO, "\n [EVNTrainer] Evolution finished in %2.2f seconds. Best fitness '%.6f' reached on generation '%d'\n",
414 (float)(mC64.ITimer().getTicksNow() - iTimeStart) / 1000.0f, vCreatures[0]->dBestCreatureFitness, vCreatures[0]->iBestCreatureGeneration + 1);
415 #endif
416 for(k = 0; k < (Sint32)vCreatures.size(); ++k)
417 {
418 #ifdef DEBUG_EVN
419 vCreatures[k]->info();
420 #endif
421 delete vCreatures[k];
422 }
423 vCreatures.clear();
424 return 0;
425}
426
427// Initialize creatures vector
428Sint32 EVNTrainer::initCreatures(GlobalStatus& pGlobalStatus)
429{
430 Creature* pCreature;
431 BrainEvolved* pBrain;
432 Sint32 bProceed = 1;
433 Sint32 i;
434
435 // RedGhost
436 if(pGlobalStatus.workBench.iGhostRedBrain >= PME_BRAIN_TYPE_TRAINING0)
437 {
438 // Assure the XML has the training set
439 if(createDefaultANN("Red", PME_GHOSTS_NN_FILE, PME_GHOSTS_NN_BLOCK) == 0)
440 {
441 // Create a new creature for the the Red Ghost with iPopulation
442 pCreature = new(std::nothrow) Creature(PME_OBJECT_GHOST_RED);
443 for(i = 0; i < iPopulation; ++i)
444 {
445 pBrain = reinterpret_cast<BrainEvolved*>(BrainsFactory::Instance().getBrain((PME_BRAIN_TYPE_TRAINING0 << i) | PME_OBJECT_GHOST_RED));
446 pBrain->load();
447 pCreature->vBrains.push_back(pBrain);
448 }
449 vCreatures.push_back(pCreature);
450 bProceed = 0;
451 }
452 }
453
454 // Pink Ghost
455 if(pGlobalStatus.workBench.iGhostPinkBrain >= PME_BRAIN_TYPE_TRAINING0)
456 {
457 // Assure the XML has the training set
458 if(createDefaultANN("Pink", PME_GHOSTS_NN_FILE, PME_GHOSTS_NN_BLOCK) == 0)
459 {
460 // Create a new creature with iPopulation
461 pCreature = new(std::nothrow) Creature(PME_OBJECT_GHOST_PINK);
462 for(i = 0; i < iPopulation; ++i)
463 {
464 pBrain = reinterpret_cast<BrainEvolved*>(BrainsFactory::Instance().getBrain((PME_BRAIN_TYPE_TRAINING0 << i) | PME_OBJECT_GHOST_PINK));
465 pBrain->load();
466 pCreature->vBrains.push_back(pBrain);
467 }
468 vCreatures.push_back(pCreature);
469 bProceed = 0;
470 }
471 }
472
473 // Blue Ghost
474 if(pGlobalStatus.workBench.iGhostBlueBrain >= PME_BRAIN_TYPE_TRAINING0)
475 {
476 // Assure the XML has the training set
477 if(createDefaultANN("Blue", PME_GHOSTS_NN_FILE, PME_GHOSTS_NN_BLOCK) == 0)
478 {
479 // Create a new creature with iPopulation
480 pCreature = new(std::nothrow) Creature(PME_OBJECT_GHOST_BLUE);
481 for(i = 0; i < iPopulation; ++i)
482 {
483 pBrain = reinterpret_cast<BrainEvolved*>(BrainsFactory::Instance().getBrain((PME_BRAIN_TYPE_TRAINING0 << i) | PME_OBJECT_GHOST_BLUE));
484 pBrain->load();
485 pCreature->vBrains.push_back(pBrain);
486 }
487 vCreatures.push_back(pCreature);
488 bProceed = 0;
489 }
490 }
491
492 // Orange Ghost
493 if(pGlobalStatus.workBench.iGhostOrangeBrain >= PME_BRAIN_TYPE_TRAINING0)
494 {
495 // Assure the XML has the training set
496 if(createDefaultANN("Orange", PME_GHOSTS_NN_FILE, PME_GHOSTS_NN_BLOCK) == 0)
497 {
498 // Create a new creature with iPopulation
499 pCreature = new(std::nothrow) Creature(PME_OBJECT_GHOST_ORANGE);
500 for(i = 0; i < iPopulation; ++i)
501 {
502 pBrain = reinterpret_cast<BrainEvolved*>(BrainsFactory::Instance().getBrain((PME_BRAIN_TYPE_TRAINING0 << i) | PME_OBJECT_GHOST_ORANGE));
503 pBrain->load();
504 pCreature->vBrains.push_back(pBrain);
505 }
506 vCreatures.push_back(pCreature);
507 bProceed = 0;
508 }
509 }
510
511 // With at least one creature, we go ahead
512 return bProceed;
513}
514
515// Create default ANN training set for a given ghost.
516// If any profile is missing, it will be created. The attributes (inputs, output, etc) are based
517// on the Evolved profile and if this one is not available, we first create it.
518Sint32 EVNTrainer::createDefaultANN(const string& sGhostName, string sCDCFile, string sXMLName)
519{
520 Sint32 idXML = -1;
521 Main& mC64 = Main::Instance();
522 Log& mLog = *Main::Instance().ILogMgr().get();
523 XML* pXML;
524 Sint32 iInputs, iOutputs, iLayers, iNpL, iNumWeights, iActivationFunc, i, j;
525 vector<double> vWeights;
526 string sName, sType, sBuff, sNumber;
527 size_t sizePos;
528 char szNumber[32];
529 bool bCDCUsage = false, bSaveXML = false;
530
531 // Extract name and type from ghost name
532 sizePos = sGhostName.find_first_of("-", 0);
533 if(sizePos == string::npos) sName = sGhostName;
534 else sName = sGhostName.substr(0, sizePos);
535 sType = PME_EVNTRAINER_EVOLVED;
536
537 // First try to open CDC file and load the XML or failback to external XML
538 if(mC64.ITool().fileExists(sCDCFile) == 0) idXML = mC64.IXMLMgr().load(sCDCFile, sXMLName);
539 if(idXML < 0)
540 {
541 idXML = mC64.IXMLMgr().loadFromFile(sXMLName);
542 if(idXML < 0)
543 {
544 idXML = mC64.IXMLMgr().create("Ghosts");
545 if(idXML < 0)
546 {
547 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not load XML file '%s' from '%s' and could not create a new one\n", sXMLName.c_str(), sCDCFile.c_str());
548 return -1;
549 }
550 }
551 }
552 else bCDCUsage = true;
553 pXML = mC64.IXMLMgr().get(idXML);
554
555 // Point to ghosts node
556 if(pXML->nodePointTo(1, "Ghosts") != 0)
557 {
558 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: 'Ghosts' node not found, creating it... ");
559 if(pXML->nodeCreate("Ghosts") != 0)
560 {
561 mLog.msg(LML_NORMAL, "error!\n");
562 mC64.IXMLMgr().close(idXML);
563 return -1;
564 }
565 mLog.msg(LML_NORMAL, "done!\n");
566 }
567
568 // Point to ghosts/name subnode
569 if(pXML->nodePointTo(2, "Ghosts", sName.c_str()) != 0)
570 {
571 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: 'Ghosts\\%s' subnode not found, creating it... ", sName.c_str());
572 if(pXML->nodeCreate(sName) != 0)
573 {
574 mLog.msg(LML_NORMAL, "error!\n");
575 mC64.IXMLMgr().close(idXML);
576 return -1;
577 }
578 mLog.msg(LML_NORMAL, "done!\n");
579 }
580
581 // Point to ghosts/name/Evolved subnode
582 if(pXML->nodePointTo(3, "Ghosts", sName.c_str(), sType.c_str()) != 0)
583 {
584 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: 'Ghosts\\%s\\%s' subnode not found, creating it... ", sName.c_str(), sType.c_str());
585 if(pXML->nodeCreate(sType) != 0)
586 {
587 mLog.msg(LML_NORMAL, "error!\n");
588 mC64.IXMLMgr().close(idXML);
589 return -1;
590 }
591 // Creating attributes
592 iOutputs = PME_ANN_GHOST_OUTPUT;
593 if(sName == "Red")
594 {
595 iInputs = PME_ANN_GHOST_RED_INPUT;
596 iNpL = PME_ANN_GHOST_RED_HIDDEN;
597 iActivationFunc = PME_ANN_GHOST_RED_ACTIVATION;
598 }
599 else if(sName == "Pink")
600 {
601 iInputs = PME_ANN_GHOST_PINK_INPUT;
602 iNpL = PME_ANN_GHOST_PINK_HIDDEN;
603 iActivationFunc = PME_ANN_GHOST_PINK_ACTIVATION;
604 }
605 else if(sName == "Blue")
606 {
607 iInputs = PME_ANN_GHOST_BLUE_INPUT;
608 iNpL = PME_ANN_GHOST_BLUE_HIDDEN;
609 iActivationFunc = PME_ANN_GHOST_BLUE_ACTIVATION;
610 }
611 else if(sName == "Orange")
612 {
613 iInputs = PME_ANN_GHOST_ORANGE_INPUT;
614 iNpL = PME_ANN_GHOST_ORANGE_HIDDEN;
615 iActivationFunc = PME_ANN_GHOST_ORANGE_ACTIVATION;
616 }
617 else
618 {
619 mLog.msg(LML_NORMAL, " error! name is unsupported\n");
620 mC64.IXMLMgr().close(idXML);
621 return -1;
622 }
623 if(iNpL > 0)
624 {
625 iNumWeights = (iInputs * iNpL);
626 iNumWeights = iNumWeights + (iNpL * iOutputs);
627 #ifdef ANN_ENABLE_BIAS
628 iNumWeights = iNumWeights + iNpL + iOutputs;
629 #endif
630 iLayers = 1;
631 }
632 else
633 {
634 iNumWeights = (iInputs * iOutputs);
635 #ifdef ANN_ENABLE_BIAS
636 iNumWeights = iNumWeights + iOutputs;
637 #endif
638 iLayers = 0;
639 }
640 pXML->setAttribute("generation", 0);
641 pXML->setAttribute("fitness", 0.0);
642 pXML->setAttribute("activation_func", iActivationFunc);
643 pXML->setAttribute("inputs", iInputs);
644 pXML->setAttribute("outputs", iOutputs);
645 pXML->setAttribute("layers", iLayers);
646 pXML->setAttribute("neurons_per_layer", iNpL);
647
648 // Save random weights
649 sBuff.clear();
650 for(j = 0; j < iNumWeights; j++)
651 {
652 // Weight between ANN_WEIGHT_MIN and ANN_WEIGHT_MAX
653 snprintf(szNumber, sizeof(szNumber), "%.6f", generateWeights());
654 sBuff += szNumber;
655 if(j != (iNumWeights - 1)) sBuff += ",";
656 }
657 pXML->setText(sBuff);
658 mLog.msg(LML_NORMAL, "done!\n");
659 }
660
661 // Load attributes of the Evolved profile for using the same attributes for the training ones. Any error will cause a failure
662 if(pXML->getAttribute("activation_func", iActivationFunc) != 0)
663 {
664 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'activation_func' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
665 mC64.IXMLMgr().close(idXML);
666 return -1;
667 }
668 if(iActivationFunc < 0) iActivationFunc = ANN_ACTIVATION_LINEAR;
669 else if(iActivationFunc > 2) iActivationFunc = ANN_ACTIVATION_SIGMOID;
670 if(pXML->getAttribute("inputs", iInputs) != 0)
671 {
672 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'inputs' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
673 mC64.IXMLMgr().close(idXML);
674 return -1;
675 }
676 if(pXML->getAttribute("outputs", iOutputs) != 0)
677 {
678 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'outputs' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
679 mC64.IXMLMgr().close(idXML);
680 return -1;
681 }
682 if(pXML->getAttribute("layers", iLayers) != 0)
683 {
684 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'layers' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
685 mC64.IXMLMgr().close(idXML);
686 return -1;
687 }
688 if(pXML->getAttribute("neurons_per_layer", iNpL) != 0)
689 {
690 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read 'neurons_per_layer' attribute for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
691 mC64.IXMLMgr().close(idXML);
692 return -1;
693 }
694
695 // Load neuron weights
696 if(pXML->getText(sBuff) != 0)
697 {
698 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not read neuron weights for 'Ghosts\\%s\\%s' node\n", sName.c_str(), sType.c_str());
699 mC64.IXMLMgr().close(idXML);
700 return -1;
701 }
702
703 // Load weights just for getting number of them
704 iNumWeights = parseWeights((char*)sBuff.c_str(), vWeights);
705
706 // Loop checking that the number of generations exist or create it
707 for(i = 0; i < PME_GA_POPULATION; ++i)
708 {
709 sBuff = PME_EVNTRAINER_TRAINING;
710 mC64.ITool().intToStrDec(i, sNumber);
711 sBuff += sNumber;
712
713 // Point to this ghost, if exists do nothing, otherwise create it
714 if(pXML->nodePointTo(3, "Ghosts", sName.c_str(), sBuff.c_str()) != 0)
715 {
716 mLog.msg(LML_NORMAL, " [EVNTrainer] Info: creating '%s' node. Weights in the interval of [%d,%d] with precision '%.6f'\n", sBuff.c_str(), ANN_WEIGHT_MIN, ANN_WEIGHT_MAX, ANN_PRECISION);
717 pXML->nodePointTo(2, "Ghosts", sName.c_str());
718 pXML->nodeCreate(sBuff);
719 pXML->setAttribute("generation", 0);
720 pXML->setAttribute("fitness", 0.0);
721 pXML->setAttribute("activation_func", iActivationFunc);
722 pXML->setAttribute("inputs", iInputs);
723 pXML->setAttribute("outputs", iOutputs);
724 pXML->setAttribute("layers", iLayers);
725 pXML->setAttribute("neurons_per_layer", iNpL);
726
727 sBuff.clear();
728 for(j = 0; j < iNumWeights; j++)
729 {
730 // Weight between ANN_WEIGHT_MIN and ANN_WEIGHT_MAX
731 snprintf(szNumber, sizeof(szNumber), "%.6f", generateWeights());
732 sBuff += szNumber;
733 if(j != (iNumWeights - 1)) sBuff += ",";
734 }
735 pXML->setText(sBuff);
736 bSaveXML = true;
737 }
738 }
739
740 // Update the XML and close it
741 if(bSaveXML)
742 {
743 if(bCDCUsage) pXML->save(sCDCFile);
744 else pXML->saveToFile(sXMLName);
745 }
746 mC64.IXMLMgr().close(idXML);
747
748 return 0;
749}
750
751// Save creatures to XML
752Sint32 EVNTrainer::saveCreatures(string sCDCFile, string sXMLName)
753{
754 Main& mC64 = Main::Instance();
755 Log& mLog = *Main::Instance().ILogMgr().get();
756 XML* pXML;
757 bool bCDCUsage = false;
758 Sint32 idXML = -1, i, k;
759 string sBuff, sNumber, sName;
760
761 // With no sCDCFile use default one
762 if(sCDCFile.empty()) sCDCFile = PME_GHOSTS_NN_FILE;
763 // With no sXMLName use default one
764 if(sXMLName.empty()) sXMLName = PME_GHOSTS_NN_BLOCK;
765
766 // First try to open CDC file and load the XML or failback to external XML
767 if(mC64.ITool().fileExists(sCDCFile) == 0) idXML = mC64.IXMLMgr().load(sCDCFile, sXMLName);
768 if(idXML < 0)
769 {
770 idXML = mC64.IXMLMgr().loadFromFile(sXMLName);
771 if(idXML < 0)
772 {
773 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not load XML file '%s' from '%s'\n", sXMLName.c_str(), sCDCFile.c_str());
774 return -1;
775 }
776 }
777 else bCDCUsage = true;
778 pXML = mC64.IXMLMgr().get(idXML);
779
780 // Creatures loop
781 for(k = 0; k < (Sint32)vCreatures.size(); ++k)
782 {
783 // Red Ghost
784 if(vCreatures[k]->iID == PME_OBJECT_GHOST_RED) sName = "Red";
785 // Pink Ghost
786 else if(vCreatures[k]->iID == PME_OBJECT_GHOST_PINK) sName = "Pink";
787 // Blue Ghost
788 else if(vCreatures[k]->iID == PME_OBJECT_GHOST_BLUE) sName = "Blue";
789 // Orange Ghost
790 else if(vCreatures[k]->iID == PME_OBJECT_GHOST_ORANGE) sName = "Orange";
791 // Error!
792 else
793 {
794 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: internal creature ID is unknown\n");
795 mC64.IXMLMgr().close(idXML);
796 return -1;
797 }
798
799 // Point to evolved node
800 if(pXML->nodePointTo(3, "Ghosts", sName.c_str(), PME_EVNTRAINER_EVOLVED) != 0)
801 {
802 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not find 'Ghosts\\%s\\%s' node\n", sName.c_str(), PME_EVNTRAINER_EVOLVED);
803 mC64.IXMLMgr().close(idXML);
804 return -1;
805 }
806 // Update this node
807 updateNode(pXML, vCreatures[k], -1);
808
809 // Point to training nodes
810 for(i = 0; i < PME_GA_POPULATION; ++i)
811 {
812 sBuff = PME_EVNTRAINER_TRAINING;
813 mC64.ITool().intToStrDec(i, sNumber);
814 sBuff += sNumber;
815
816 // Point to training node number i
817 if(pXML->nodePointTo(3, "Ghosts", sName.c_str(), sBuff.c_str()) != 0)
818 {
819 mLog.msg(LML_NORMAL, " [EVNTrainer] Warning: could not find 'Ghosts\\%s\\%s' node\n", sName.c_str(), sBuff.c_str());
820 mC64.IXMLMgr().close(idXML);
821 return -1;
822 }
823 // Update this node
824 updateNode(pXML, vCreatures[k], i);
825 }
826 }
827
828 // Save and return
829 if(bCDCUsage) pXML->save(sCDCFile);
830 else pXML->saveToFile(sXMLName);
831 mC64.IXMLMgr().close(idXML);
832 return 0;
833}
834
835// Update current pXML node using pCreatures. iNum is set to the brain number or -1 for the best one
836Sint32 EVNTrainer::updateNode(XML* pXML, Creature* pCreature, Sint32 iNum)
837{
838 Sint32 iGeneration, j;
839 double dFitness;
840 char szNumber[32];
841 string sWeights;
842 vector<double> pWeights;
843
844 // Read current node attributes: generation and fitness
845 if(pXML->getAttribute("generation", iGeneration) != 0) return -1;
846 if(pXML->getAttribute("fitness", dFitness) != 0) return -1;
847
848 // Read attributes from our creature
849 if(iNum < 0) // Evolved node that keeps the best creature
850 {
851 // If the fitness is lower, do not modify this node
852 if(dFitness > pCreature->dBestCreatureFitness) return 0;
853 iGeneration = iGeneration + 1 + pCreature->iBestCreatureGeneration;
854 dFitness = pCreature->dBestCreatureFitness;
855 pWeights = pCreature->vBestCreaturesNN[0].vWeights;
856 }
857 else // Training node
858 {
859 iGeneration += pCreature->iGeneration;
860 dFitness = pCreature->vBrains[iNum]->getFitness();
861 pWeights = pCreature->vBrains[iNum]->getNeuralNet()->getWeights();
862 }
863
864 // Save new attributes to current node
865 pXML->setAttribute("generation", iGeneration);
866 dFitness = applyWeightPrecision(dFitness);
867 snprintf(szNumber, sizeof(szNumber), "%.6f", dFitness);
868 sWeights = szNumber;
869 pXML->setAttribute("fitness", sWeights);
870
871 // Save new weights
872 pXML->removeText();
873 sWeights.clear();
874 for(j = 0; j < pWeights.size(); j++)
875 {
876 snprintf(szNumber, sizeof(szNumber), "%.6f", pWeights[j]);
877 sWeights += szNumber;
878 if(j != (pWeights.size() - 1)) sWeights += ",";
879 }
880 pXML->setText(sWeights);
881
882 return 0;
883}
884
885// Parse from a char* of doubles to a vector<double>
886Sint32 EVNTrainer::parseWeights(char* from, vector<double>& to)
887{
888 Sint32 iNum = 0;
889 char seps[] = ",\n\t";
890 char *token;
891 double dTmp;
892
893 token = strtok(from, seps);
894 while(token != NULL)
895 {
896 dTmp = atof(token);
897 to.push_back(dTmp);
898 token = strtok(NULL, seps);
899 ++iNum;
900 }
901 return iNum;
902}