Advertisement
Laupok

mario IA

Jun 5th, 2022 (edited)
17,548
2
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.94 KB | None | 2 0
  1. -- MARIO IA par LAUPOK
  2. -- script à utiliser avec l'emu bizhawk et une rom USA de Super Mario World récupéré totalement légalement
  3.  
  4. -- correction MAJ 1 ATTENTION
  5. -- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  6. -- !! LES ANCIENNES SAUVEGARDES NE SONT PLUS COMPATIBLES !!
  7. -- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  8. -- 1- probleme de crossover (les connexions non actives du nul étaient copiées, pas bonne idée)
  9. -- 2- petit probleme quand sauvegarde de la population finie; si le plus fort avait déjà fini le niveau,
  10. -- les autres individus de la population, meme ceux n'ayant jamais fini le niveau,
  11. -- était sauvegardé et testé. Là les autres individus de la popualtion sont quand même sauvegardé,
  12. -- mais plus testé dans le jeu. Mario finira en boucle le niveau une fois terminé.
  13. -- 3- tjrs en rapport avec la sauvegarde, suppression de la sauvegarde de la valeur du neurone, vu que c'est reset à toutes les frames on s'en fou
  14. -- 4- modif fitnessmax à la fin du niveau (plus ajouté mais ==)
  15. -- 5- augmentation des chances de mutation de neurone (il se peut que ça soit une mauvaise idée mais le bug du crossover que j'ai corrigé rendaient les neurones moins utiles')
  16.  
  17. -- constantes
  18. NOM_JEU = "Super Mario World (USA)"
  19. NOM_SAVESTATE = "debut.state"
  20. NOM_FICHIER_POPULATION = "gen idGen.pop" -- idGen sera remplacé par le nb de gen
  21. TAILLE_FORM_W = 380
  22. TAILLE_FORM_H = 385
  23.  
  24. TAILLE_TILE = 16 -- taille d'une tile DANS LE JEU
  25. TAILLE_VUE_W = TAILLE_TILE * 11 -- taille de ce que je vois le script
  26. TAILLE_VUE_H = TAILLE_TILE * 9
  27. TAILLE_CAMERA_W = 256 -- du jeu
  28. TAILLE_CAMERA_H = 224
  29. NB_TILE_W = TAILLE_VUE_W / TAILLE_TILE -- nombre de tiles scannée par le réseau de neurone en longueur (ça fait 16)
  30. NB_TILE_H = TAILLE_VUE_H / TAILLE_TILE -- nombre de tiles scannée par le réseau de neurone en largeur (ça fait 14)
  31. NB_SPRITE_MAX = 11 -- dans SMW, il y a au maximum 12 sprites à l'écran en meme temps (en fait c'est 11+1 car 0 est un sprite), pour chaque type de sprite (à ne pas modifier)
  32.  
  33. TAILLE_INPUT = 6 -- en pixel, uniquement pour l'affichage
  34. TAILLE_HIDDEN = 4 -- en pixel, uniquement pour l'affichage
  35. TAILLE_OUTPUT_W = 24 -- en pixel, uniquement pour l'affichage
  36. TAILLE_OUTPUT_H = 8 -- en pixel, uniquement pour l'affichage
  37. ENCRAGE_X_INPUT = 20
  38. ENCRAGE_Y_INPUT = 50
  39. ENCRAGE_X_HIDDEN = 100
  40. ENCRAGE_Y_HIDDEN = 50
  41. ENCRAGE_X_OUTPUT = 190
  42. ENCRAGE_Y_OUTPUT = 50
  43. ESPACE_Y_OUTPUT = TAILLE_OUTPUT_H + 5 -- entre chaque output l'espace qu'il y a
  44. NB_HIDDEN_PAR_LIGNE = 10 -- nombre de neurone hidden par ligne (affichage uniquement)
  45.  
  46. FITNESS_LEVEL_FINI = 1000000 -- quand le level est fini, la fitness devient ça
  47. NB_FRAME_RESET_BASE = 33 -- si pendant x frames la fitness n'augmente pas comparé à celle du début, on relance (le jeu tourne à 30 fps au cas où)
  48. NB_FRAME_RESET_PROGRES = 300 -- si il a eu un progrés (diff de la fitness au lancement) on laisse le jeu tourner un peu + longtemps avant le reset
  49. NB_NEURONE_MAX = 100000 -- pour le reseau de neurone, hors input et output
  50. NB_INPUT = NB_TILE_W * NB_TILE_H -- nb de neurones input, c'est chaque case du jeu en fait
  51. NB_OUTPUT = 8 -- nb de neurones output, c'est à dire les touches de la manette
  52. NB_INDIVIDU_POPULATION = 100 -- nombre d'individus créés quand création d'une nouvelle population
  53. -- constante pour trier les especes des populations
  54. EXCES_COEF = 0.50
  55. POIDSDIFF_COEF = 0.92
  56. DIFF_LIMITE = 1.00
  57. -- mutation
  58. CHANCE_MUTATION_RESET_CONNEXION = 0.25 -- % de chance que le poids de la connexion soit totalement reset
  59. POIDS_CONNEXION_MUTATION_AJOUT = 0.80 -- poids ajouté à la mutation de la connexion si pas CHANCE_MUTATION_RESET_CONNEXION. La valeur peut être passée negative
  60. CHANCE_MUTATION_POIDS = 0.95
  61. CHANCE_MUTATION_CONNEXION = 0.85
  62. CHANCE_MUTATION_NEURONE = 0.39
  63.  
  64.  
  65. -- doit correspondre aux inputs de la manette dans l'emulateur
  66. lesBoutons = {
  67. {nom = "P1 A"},
  68. {nom = "P1 B"},
  69. {nom = "P1 X"},
  70. {nom = "P1 Y"},
  71. {nom = "P1 Up"},
  72. {nom = "P1 Down"},
  73. {nom = "P1 Left"},
  74. {nom = "P1 Right"}
  75. }
  76. nbInnovation = 0 -- nombre d'innovation global pour les connexions, important pour le reseau de neurone
  77. fitnessMax = 0 -- fitness max atteinte
  78. nbGeneration = 1 -- pour suivre on est à la cb de generation
  79. idPopulation = 1 -- quel id de la population est en train de passer dans la boucle
  80. marioBase = {} -- position de mario a la base ça va me servir pour voir si il avance de sa position d'origine / derniere pos enregistrée
  81. niveauFini = false
  82. lesAnciennesPopulation = {} -- stock les anciennes population
  83. nbFrame = 0 -- nb de frame actuellement
  84. nbFrameStop = 0 -- permettra de reset le jeu au besoin
  85. fitnessInit = 0 -- fitness à laquelle le reseau actuel commence est init
  86. niveauFiniSauvegarde = false
  87. lesEspeces = {}
  88. laPopulation = {}
  89.  
  90. -- créé une population
  91. function newPopulation()
  92. local population = {}
  93. for i = 1, NB_INDIVIDU_POPULATION, 1 do
  94. table.insert(population, newReseau())
  95. end
  96. return population
  97. end
  98.  
  99.  
  100. -- créé un neurone
  101. function newNeurone()
  102. local neurone = {}
  103. neurone.valeur = 0
  104. neurone.id = 0 -- pas init si à 0, doit être == à l'indice du neurone dans lesNeurones du reseau
  105. neurone.type = ""
  106. return neurone
  107. end
  108.  
  109. -- créé une connexion
  110. function newConnexion()
  111. local connexion = {}
  112. connexion.entree = 0
  113. connexion.sortie = 0
  114. connexion.actif = true
  115. connexion.poids = 0
  116. connexion.innovation = 0
  117. connexion.allume = false -- pour le dessin, si true ça veut dire que le resultat de la connexion est different de 0
  118. return connexion
  119. end
  120.  
  121.  
  122.  
  123. -- créé un reseau de neurone
  124. function newReseau()
  125. local reseau = {nbNeurone = 0, -- taille des neurones rajouté par l'algo (hors input output du coup)
  126. fitness = 1, -- beaucoup de division, pour eviter de faire l irreparable
  127. idEspeceParent = 0,
  128. lesNeurones = {},
  129. lesConnexions = {}}
  130. for j = 1, NB_INPUT, 1 do
  131. ajouterNeurone(reseau, j, "input", 1)
  132. end
  133.  
  134.  
  135. -- ensuite, les outputs
  136. for j = NB_INPUT + 1, NB_INPUT + NB_OUTPUT, 1 do
  137. ajouterNeurone(reseau, j, "output", 0)
  138. end
  139.  
  140.  
  141. return reseau
  142. end
  143.  
  144.  
  145. -- créé une espece (un regroupement de reseaux, d'individus)
  146. function newEspece()
  147. local espece = {nbEnfant = 0, -- combien d'enfant cette espece a créé
  148. fitnessMoyenne = 0, -- fitness moyenne de l'espece
  149. fitnessMax = 0, -- fitness max atteinte par l'espece
  150. lesReseaux = {} }-- tableau qui regroupe les reseaux}
  151.  
  152.  
  153. return espece
  154. end
  155.  
  156.  
  157. -- copie un truc et renvoie le truc copié
  158. -- j'ai copié ce code d'ici http://lua-users.org/wiki/CopyTable c vrai en +
  159. function copier(orig)
  160. local orig_type = type(orig)
  161. local copy
  162. if orig_type == 'table' then
  163. copy = {}
  164. for orig_key, orig_value in next, orig, nil do
  165. copy[copier(orig_key)] = copier(orig_value)
  166. end
  167. setmetatable(copy, copier(getmetatable(orig)))
  168. else -- number, string, boolean, etc
  169. copy = orig
  170. end
  171. return copy
  172. end
  173.  
  174.  
  175. -- ajoute une connexion a un reseau de neurone
  176. function ajouterConnexion(unReseau, entree, sortie, poids)
  177. -- test pour voir si tout va bien et que les neurones de la connexion existent bien
  178. if unReseau.lesNeurones[entree].id == 0 then
  179. console.log("connexion avec l'entree " .. entree .. " n'est pas init ?")
  180. elseif unReseau.lesNeurones[sortie].id == 0 then
  181. console.log("connexion avec la sortie " .. sortie .. " n'est pas init ?")
  182. else
  183. local connexion = newConnexion()
  184. connexion.actif = true
  185. connexion.entree = entree
  186. connexion.sortie = sortie
  187. connexion.poids = genererPoids()
  188. connexion.innovation = nbInnovation
  189. table.insert(unReseau.lesConnexions, connexion)
  190. nbInnovation = nbInnovation + 1
  191. end
  192. end
  193.  
  194.  
  195.  
  196.  
  197. -- ajoute un neurone a un reseau de neurone, fait que pour les neurones qui doivent exister
  198. function ajouterNeurone(unReseau, id, type, valeur)
  199. if id ~= 0 then
  200. local neurone = newNeurone()
  201. neurone.id = id
  202. neurone.type = type
  203. neurone.valeur = valeur
  204. table.insert(unReseau.lesNeurones, neurone)
  205. else
  206. console.log("ajouterNeurone doit pas etre utilise avec un id == 0")
  207. end
  208. end
  209.  
  210.  
  211.  
  212. -- modifie les connexions d'un reseau de neurone
  213. function mutationPoidsConnexions(unReseau)
  214. for i = 1, #unReseau.lesConnexions, 1 do
  215. if unReseau.lesConnexions[i].actif then
  216. if math.random() < CHANCE_MUTATION_RESET_CONNEXION then
  217. unReseau.lesConnexions[i].poids = genererPoids()
  218. else
  219. if math.random() >= 0.5 then
  220. unReseau.lesConnexions[i].poids = unReseau.lesConnexions[i].poids - POIDS_CONNEXION_MUTATION_AJOUT
  221. else
  222. unReseau.lesConnexions[i].poids = unReseau.lesConnexions[i].poids + POIDS_CONNEXION_MUTATION_AJOUT
  223. end
  224. end
  225. end
  226. end
  227. end
  228.  
  229. -- ajoute une connexion entre 2 neurones pas déjà connecté entre eux
  230. -- ça peut ne pas marcher si aucun neurone n'est connectable entre eux (uniquement si beaucoup de connexion)
  231. function mutationAjouterConnexion(unReseau)
  232. local liste = {}
  233.  
  234. -- randomisation + copies des neuronnes dans une liste
  235. for i, v in ipairs(unReseau.lesNeurones) do
  236. local pos = math.random(1, #liste+1)
  237. table.insert(liste, pos, v)
  238. end
  239.  
  240. -- la je vais lister tous les neurones et voir si une pair n'a pas de connexion; si une connexion peut être créée
  241. -- on la créée et on stop
  242. local traitement = false
  243. for i = 1, #liste, 1 do
  244. for j = 1, #liste, 1 do
  245. if i ~= j then
  246. local neurone1 = liste[i]
  247. local neurone2 = liste[j]
  248.  
  249.  
  250. if (neurone1.type == "input" and neurone2.type == "output") or
  251. (neurone1.type == "hidden" and neurone2.type == "hidden") or
  252. (neurone1.type == "hidden" and neurone2.type == "output") then
  253. -- si on en est là, c'est que la connexion peut se faire, juste à tester si y pas deja une connexion
  254. local dejaConnexion = false
  255. for k = 1, #unReseau.lesConnexions, 1 do
  256. if unReseau.lesConnexions[k].entree == neurone1.id
  257. and unReseau.lesConnexions[k].sortie == neurone2.id then
  258. dejaConnexion = true
  259. break
  260. end
  261. end
  262.  
  263.  
  264.  
  265. if dejaConnexion == false then
  266. -- nouvelle connexion, traitement terminé
  267. traitement = true
  268. ajouterConnexion(unReseau, neurone1.id, neurone2.id)
  269. end
  270. end
  271. end
  272. if traitement then
  273. break
  274. end
  275. end
  276. if traitement then
  277. break
  278. end
  279. end
  280.  
  281.  
  282. if traitement == false then
  283. console.log("impossible de recreer une connexion")
  284. end
  285. end
  286.  
  287.  
  288. -- ajoute un neurone (couche caché uniquement) entre 2 neurones déjà connecté. Ne peut pas marcher
  289. -- si il n'y a pas de connexion
  290. function mutationAjouterNeurone(unReseau)
  291. if #unReseau.lesConnexions == 0 then
  292. log("Impossible d'ajouter un neurone entre 2 connexions si pas de connexion")
  293. return nil
  294. end
  295.  
  296. if unReseau.nbNeurone == NB_NEURONE_MAX then
  297. console.log("Nombre de neurone max atteint")
  298. return nil
  299. end
  300.  
  301. -- randomisation de la liste des connexions
  302. local listeIndice = {}
  303. local listeRandom = {}
  304.  
  305. -- je créé une liste d'entier de 1 à la taille des connexions
  306. for i = 1, #unReseau.lesConnexions, 1 do
  307. listeIndice[i] = i
  308. end
  309.  
  310. -- je randomise la liste que je viens de créer dans listeRandom
  311. for i, v in ipairs(listeIndice) do
  312. local pos = math.random(1, #listeRandom+1)
  313. table.insert(listeRandom, pos, v)
  314. end
  315.  
  316. for i = 1, #listeRandom, 1 do
  317. if unReseau.lesConnexions[listeRandom[i]].actif then
  318. unReseau.lesConnexions[listeRandom[i]].actif = false
  319. unReseau.nbNeurone = unReseau.nbNeurone + 1
  320. local indice = unReseau.nbNeurone + NB_INPUT + NB_OUTPUT
  321. ajouterNeurone(unReseau, indice, "hidden", 1)
  322. ajouterConnexion(unReseau, unReseau.lesConnexions[listeRandom[i]].entree, indice, genererPoids())
  323. ajouterConnexion(unReseau, indice, unReseau.lesConnexions[listeRandom[i]].sortie, genererPoids())
  324. break
  325. end
  326. end
  327. end
  328.  
  329.  
  330. -- appelle une des mutations aléatoirement en fonction des constantes
  331. function mutation(unReseau)
  332. local random = math.random()
  333. if random < CHANCE_MUTATION_POIDS then
  334. mutationPoidsConnexions(unReseau)
  335. end
  336. if random < CHANCE_MUTATION_CONNEXION then
  337. mutationAjouterConnexion(unReseau)
  338. end
  339. if random < CHANCE_MUTATION_NEURONE then
  340. mutationAjouterNeurone(unReseau)
  341. end
  342. end
  343.  
  344.  
  345. -- place la population et la renvoie divisée dans une tableau 2D
  346. function trierPopulation(laPopulation)
  347. local lesEspeces = {}
  348. table.insert(lesEspeces, newEspece())
  349.  
  350. -- la premiere espece créée et le dernier element de la premiere population
  351. -- comme ça, j'ai déjà une première espèce créée
  352. table.insert(lesEspeces[1].lesReseaux, copier(laPopulation[#laPopulation]))
  353.  
  354. for i = 1, #laPopulation-1, 1 do
  355. local trouve = false
  356. for j = 1, #lesEspeces, 1 do
  357. local indice = math.random(1, #lesEspeces[j].lesReseaux)
  358. local rep = lesEspeces[j].lesReseaux[indice]
  359. -- il peut être classé
  360. if getScore(laPopulation[i], rep) < DIFF_LIMITE then
  361. table.insert(lesEspeces[j].lesReseaux, copier(laPopulation[i]))
  362. trouve = true
  363. break
  364. end
  365. end
  366.  
  367. -- si pas trouvé, il faut créer une especes pour l'individu
  368. if trouve == false then
  369. table.insert(lesEspeces, newEspece())
  370. table.insert(lesEspeces[#lesEspeces].lesReseaux, copier(laPopulation[i]))
  371. end
  372. end
  373.  
  374. return lesEspeces
  375. end
  376.  
  377.  
  378. -- retourne la difference de poids de 2 réseaux de neurones (uniquement des memes innovations)
  379. function getDiffPoids(unReseau1, unReseau2)
  380. local nbConnexion = 0
  381. local total = 0
  382. for i = 1, #unReseau1.lesConnexions, 1 do
  383. for j = 1, #unReseau2.lesConnexions, 1 do
  384. if unReseau1.lesConnexions[i].innovation == unReseau2.lesConnexions[j].innovation then
  385. nbConnexion = nbConnexion + 1
  386. total = total + math.abs(unReseau1.lesConnexions[i].poids - unReseau2.lesConnexions[j].poids)
  387. end
  388. end
  389. end
  390.  
  391. -- si aucune connexion en commun c'est qu'ils sont trop differents
  392. -- puis si on laisse comme ça on va diviser par 0 et on va lancer mario maker
  393. if nbConnexion == 0 then
  394. return 100000
  395. end
  396.  
  397.  
  398. return total / nbConnexion
  399. end
  400.  
  401.  
  402.  
  403.  
  404. -- retourne le nombre de connexion qui n'ont aucun rapport entre les 2 reseaux
  405. function getDisjoint(unReseau1, unReseau2)
  406. local nbPareil = 0
  407. for i = 1, #unReseau1.lesConnexions, 1 do
  408. for j = 1, #unReseau2.lesConnexions, 1 do
  409. if unReseau1.lesConnexions[i].innovation == unReseau2.lesConnexions[j].innovation then
  410. nbPareil = nbPareil + 1
  411. end
  412. end
  413. end
  414.  
  415. -- oui ça marche
  416. return #unReseau1.lesConnexions + #unReseau2.lesConnexions - 2 * nbPareil
  417. end
  418.  
  419.  
  420.  
  421. -- permet d'obtenir le score d'un reseau de neurone, ce qui va le mettre dans une especes
  422. -- rien à voir avec le fitness
  423. -- unReseauRep et un reseau appartenant deja a une espece
  424. -- et reseauTest et le reseau qui va etre testé
  425. function getScore(unReseauTest, unReseauRep)
  426. return (EXCES_COEF * getDisjoint(unReseauTest, unReseauRep)) /
  427. (math.max(#unReseauTest.lesConnexions + #unReseauRep.lesConnexions, 1))
  428. + POIDSDIFF_COEF * getDiffPoids(unReseauTest, unReseauRep)
  429. end
  430.  
  431. -- genere un poids aléatoire (pour les connexions) egal à 1 ou -1
  432. function genererPoids()
  433. local var = 1
  434. if math.random() >= 0.5 then
  435. var = var * -1
  436. end
  437. return var
  438. end
  439.  
  440.  
  441. -- fonction d'activation
  442. function sigmoid(x)
  443. local resultat = x / (1 + math.abs(x))
  444. if resultat >= 0.5 then
  445. return true
  446. end
  447. return false
  448. end
  449.  
  450.  
  451. -- applique les connexions d'un réseau de neurone en modifiant la valeur des neurones de sortie
  452. function feedForward(unReseau)
  453. -- avant de continuer, je reset à 0 les neurones de sortie
  454. for i = 1, #unReseau.lesConnexions, 1 do
  455. if unReseau.lesConnexions[i].actif then
  456. unReseau.lesNeurones[unReseau.lesConnexions[i].sortie].valeur = 0
  457. unReseau.lesNeurones[unReseau.lesConnexions[i].sortie].allume = false
  458. end
  459. end
  460.  
  461.  
  462. for i = 1, #unReseau.lesConnexions, 1 do
  463. if unReseau.lesConnexions[i].actif then
  464. local avantTraitement = unReseau.lesNeurones[unReseau.lesConnexions[i].sortie].valeur
  465. unReseau.lesNeurones[unReseau.lesConnexions[i].sortie].valeur =
  466. unReseau.lesNeurones[unReseau.lesConnexions[i].entree].valeur *
  467. unReseau.lesConnexions[i].poids +
  468. unReseau.lesNeurones[unReseau.lesConnexions[i].sortie].valeur
  469.  
  470. -- on ""allume"" le lien si la connexion a fait une modif
  471. if avantTraitement ~= unReseau.lesNeurones[unReseau.lesConnexions[i].sortie].valeur then
  472. unReseau.lesConnexions[i].allume = true
  473. else
  474. unReseau.lesConnexions[i].allume = false
  475. end
  476. end
  477. end
  478. end
  479.  
  480.  
  481.  
  482.  
  483. -- retourne un melange des 2 reseaux de neurones
  484. function crossover(unReseau1, unReseau2)
  485. local leReseau = newReseau()
  486.  
  487.  
  488. -- quel est le meilleur des deux ?
  489. local leBon = newReseau()
  490. local leNul = newReseau()
  491.  
  492.  
  493. leBon = unReseau1
  494. leNul = unReseau2
  495. if leBon.fitness < leNul.fitness then
  496. leBon = unReseau2
  497. leNul = unReseau1
  498. end
  499.  
  500. -- le nouveau reseau va hériter de la majorité des attributs du meilleur
  501. leReseau = copier(leBon)
  502.  
  503. -- sauf pour les connexions où y a une chance que le nul lui donne ses genes
  504. for i = 1, #leReseau.lesConnexions, 1 do
  505. for j = 1, #leNul.lesConnexions, 1 do
  506. -- si 2 connexions partagent la meme innovation, la connexion du nul peut venir la remplacer
  507. -- *seulement si nul est actif, sans ça ça créé des neurones hiddens inutiles*
  508. if leReseau.lesConnexions[i].innovation == leNul.lesConnexions[j].innovation and leNul.lesConnexions[j].actif then
  509. if math.random() > 0.5 then
  510. leReseau.lesConnexions[i] = leNul.lesConnexions[j]
  511. end
  512. end
  513. end
  514. end
  515. leReseau.fitness = 1
  516. return leReseau
  517. end
  518.  
  519.  
  520. -- renvoie une copie d'un parent choisis dans une espece
  521. function choisirParent(uneEspece)
  522. if #uneEspece == 0 then
  523. console.log("uneEspece vide dans choisir parent ??")
  524. end
  525. -- il est possible que l'espece ne contienne qu'un seul reseau, dans ce cas là on va pas plus loin
  526. if #uneEspece == 1 then
  527. return uneEspece[1]
  528. end
  529.  
  530. local fitnessTotal = 0
  531. for i = 1, #uneEspece, 1 do
  532. fitnessTotal = fitnessTotal + uneEspece[i].fitness
  533. end
  534. local limite = math.random(0, fitnessTotal)
  535. local total = 0
  536. for i = 1, #uneEspece, 1 do
  537. total = total + uneEspece[i].fitness
  538. -- si la somme des fitness cumulés depasse total, on renvoie l'espece qui a fait depasser la limite
  539. if total >= limite then
  540. return copier(uneEspece[i])
  541. end
  542. end
  543. console.log("impossible de trouver un parent ?")
  544. return nil
  545. end
  546.  
  547.  
  548. -- créé une nouvelle generation, renvoie la population créée
  549. -- il faut que les especes soit triée avant appel
  550. function nouvelleGeneration(laPopulation, lesEspeces)
  551. local laNouvellePopulation = newPopulation()
  552. -- nombre d'indivu à creer au total
  553. local nbIndividuACreer = NB_INDIVIDU_POPULATION
  554. -- indice qui va servir à savoir OU en est le tab de la nouvelle espece
  555. local indiceNouvelleEspece = 1
  556.  
  557. -- il est possible que l'ancien meilleur ait un meilleur fitness
  558. -- que celui de la nouvelle population (une mauvaise mutation ça arrive très souvent)
  559. -- dans ce cas je le supprime par l'ancien meilleur histoire d'être SUR d'avoir des enfants
  560. -- toujours du plus bon
  561. local fitnessMaxPop = 0
  562. local fitnessMaxAncPop = 0
  563. local ancienPlusFort = {}
  564. for i = 1, #laPopulation, 1 do
  565. if fitnessMaxPop < laPopulation[i].fitness then
  566. fitnessMaxPop = laPopulation[i].fitness
  567. end
  568. end
  569. -- on test que si il y a deja une ancienne population evidamment
  570. if #lesAnciennesPopulation > 0 then
  571. -- je vais checker TOUTES les anciennes population pour la fitness la plus élevée
  572. -- vu que les reseaux vont REmuter, il est possible qu'ils fassent moins bon !
  573. for i = 1, #lesAnciennesPopulation, 1 do
  574. for j = 1, #lesAnciennesPopulation[i], 1 do
  575. if fitnessMaxAncPop < lesAnciennesPopulation[i][j].fitness then
  576. fitnessMaxAncPop = lesAnciennesPopulation[i][j].fitness
  577. ancienPlusFort = lesAnciennesPopulation[i][j]
  578. end
  579. end
  580. end
  581. end
  582.  
  583. if fitnessMaxAncPop > fitnessMaxPop then
  584. -- comme ça je suis sur uqe le meilleur dominera totalement
  585. for i = 1, #lesEspeces, 1 do
  586. for j = 1, #lesEspeces[i].lesReseaux, 1 do
  587. lesEspeces[i].lesReseaux[j] = copier(ancienPlusFort)
  588. end
  589. end
  590. console.log("mauvaise population je reprends la meilleur et ça redevient la base de la nouvelle pop")
  591. console.log(ancienPlusFort)
  592. end
  593.  
  594. table.insert(lesAnciennesPopulation, laPopulation)
  595.  
  596. -- calcul fitness pour chaque espece
  597. local nbIndividuTotal = 0
  598. local fitnessMoyenneGlobal = 0 -- fitness moyenne de TOUS les individus de toutes les especes
  599. local leMeilleur = newReseau() -- je dois le remettre avant tout, on va essayer de trouver ou i lest
  600. for i = 1, #lesEspeces, 1 do
  601. lesEspeces[i].fitnessMoyenne = 0
  602. lesEspeces[i].lesReseaux.fitnessMax = 0
  603. for j = 1, #lesEspeces[i].lesReseaux, 1 do
  604. lesEspeces[i].fitnessMoyenne = lesEspeces[i].fitnessMoyenne + lesEspeces[i].lesReseaux[j].fitness
  605. fitnessMoyenneGlobal = fitnessMoyenneGlobal + lesEspeces[i].lesReseaux[j].fitness
  606. nbIndividuTotal = nbIndividuTotal + 1
  607.  
  608. if lesEspeces[i].fitnessMax < lesEspeces[i].lesReseaux[j].fitness then
  609. lesEspeces[i].fitnessMax = lesEspeces[i].lesReseaux[j].fitness
  610. if leMeilleur.fitness < lesEspeces[i].lesReseaux[j].fitness then
  611. leMeilleur = copier(lesEspeces[i].lesReseaux[j])
  612. end
  613. end
  614. end
  615. lesEspeces[i].fitnessMoyenne = lesEspeces[i].fitnessMoyenne / #lesEspeces[i].lesReseaux
  616. end
  617.  
  618. -- si le level a été terminé au moins une fois, tous les individus deviennent le meilleur, on ne recherche plus de mutation là
  619. if leMeilleur.fitness == FITNESS_LEVEL_FINI then
  620. for i = 1, #lesEspeces, 1 do
  621. for j = 1, #lesEspeces[i].lesReseaux, 1 do
  622. lesEspeces[i].lesReseaux[j] = copier(leMeilleur)
  623. end
  624. end
  625. fitnessMoyenneGlobal = leMeilleur.fitness
  626. else
  627. fitnessMoyenneGlobal = fitnessMoyenneGlobal / nbIndividuTotal
  628. end
  629.  
  630. --tri des especes pour que les meilleurs place leurs enfants avant tout
  631. table.sort(lesEspeces, function (e1, e2) return e1.fitnessMax > e2.fitnessMax end )
  632.  
  633. -- chaque espece va créer un certain nombre d'individu dans la nouvelle population en fonction de si l'espece a un bon fitness ou pas
  634. for i = 1, #lesEspeces, 1 do
  635. local nbIndividuEspece = math.ceil(#lesEspeces[i].lesReseaux * lesEspeces[i].fitnessMoyenne / fitnessMoyenneGlobal)
  636. nbIndividuACreer = nbIndividuACreer - nbIndividuEspece
  637. if nbIndividuACreer < 0 then
  638. nbIndividuEspece = nbIndividuEspece + nbIndividuACreer
  639. nbIndividuACreer = 0
  640. end
  641. lesEspeces[i].nbEnfant = nbIndividuEspece
  642.  
  643.  
  644. for j = 1, nbIndividuEspece, 1 do
  645. if indiceNouvelleEspece > NB_INDIVIDU_POPULATION then
  646. break
  647. end
  648.  
  649. local unReseau = crossover(choisirParent(lesEspeces[i].lesReseaux), choisirParent(lesEspeces[i].lesReseaux))
  650.  
  651. -- on stop la mutation à ce stade
  652. if fitnessMoyenneGlobal ~= FITNESS_LEVEL_FINI then
  653. mutation(unReseau)
  654. end
  655.  
  656. unReseau.idEspeceParent = i
  657. laNouvellePopulation[indiceNouvelleEspece] = copier(unReseau)
  658. laNouvellePopulation[indiceNouvelleEspece].fitness = 1
  659. indiceNouvelleEspece = indiceNouvelleEspece + 1
  660. end
  661. if indiceNouvelleEspece > NB_INDIVIDU_POPULATION then
  662. break
  663. end
  664. end
  665.  
  666. -- si une espece n'a pas fait d'enfant, je la delete
  667. for i = 1, #lesEspeces, 1 do
  668. if lesEspeces[i].nbEnfant == 0 then
  669. lesEspeces[i] = nil
  670. end
  671. end
  672.  
  673. return laNouvellePopulation
  674. end
  675.  
  676.  
  677. function getNomFichierSauvegarde()
  678. local str = NOM_FICHIER_POPULATION
  679. str = string.gsub(str, "idGen", nbGeneration)
  680. return str
  681. end
  682.  
  683. -- sauvegarde la population actuelle dans le fichier getNomFichierSauvegarde()
  684. -- le dernier argument est reservé si le script detect que la population a terminée le niveau
  685. function sauvegarderPopulation(laPopulation, estFini)
  686. chemin = getNomFichierSauvegarde()
  687. if estFini then
  688. chemin = "FINI " .. chemin
  689. end
  690.  
  691. local fichier = io.open(chemin, "w+")
  692. io.output(fichier)
  693.  
  694. -- sauvegarde classique de la population
  695. io.write(nbGeneration .. "\n")
  696. io.write(nbInnovation .. "\n")
  697. for i = 1, #laPopulation, 1 do
  698. sauvegarderUnReseau(laPopulation[i], fichier)
  699. end
  700.  
  701. -- et là je sauvegarde le plus fort, c'est important pour pas perdre les progrés
  702. local lePlusFort = newReseau()
  703. for i = 1, #laPopulation, 1 do
  704. if lePlusFort.fitness < laPopulation[i].fitness then
  705. lePlusFort = copier(laPopulation[i])
  706. end
  707. end
  708. -- check aussi dans l'ancienne population (si plus fort, il ne peut etre que là)
  709. if #lesAnciennesPopulation > 0 then
  710. for i = 1, #lesAnciennesPopulation, 1 do
  711. for j = 1, #lesAnciennesPopulation[i], 1 do
  712. if lePlusFort.fitness < lesAnciennesPopulation[i][j].fitness then
  713. lePlusFort = copier(lesAnciennesPopulation[i][j])
  714. end
  715. end
  716. end
  717. end
  718. sauvegarderUnReseau(lePlusFort, fichier)
  719. io.close(fichier)
  720.  
  721. console.log("sauvegarde terminee au fichier " .. chemin)
  722. end
  723.  
  724. -- charge la population sauvegardé
  725. -- renvoie la nouvelle population ou nil si le chemin n'est pas celui d'un fichier pop
  726. function chargerPopulation(chemin)
  727. -- petit test pour voir si le fichier est ok
  728. local test = string.find(chemin, ".pop")
  729. local laPopulation = nil
  730. if test == nil then
  731. console.log("le fichier " .. chemin .. " n'est pas du bon format (.pop) je vais te monter en l'air ")
  732. else
  733. laPopulation = {}
  734. local fichier = io.open(chemin, "r")
  735.  
  736. io.input(fichier)
  737.  
  738. local totalNeurone = 0
  739. local totalConnexion = 0
  740.  
  741. nbGeneration = io.read("*number")
  742. nbInnovation = io.read("*number")
  743. for i = 1, NB_INDIVIDU_POPULATION, 1 do
  744. table.insert(laPopulation, chargerUnReseau(fichier))
  745. laPopulation[i].fitness = 1
  746. end
  747.  
  748. lesAnciennesPopulation = {} -- obligé !
  749. -- en mettant le plus fort ici, i lsera forcement lu dans nouvelleGeneration
  750. table.insert(lesAnciennesPopulation, copier(laPopulation))
  751. lesAnciennesPopulation[1][1] = chargerUnReseau(fichier)
  752.  
  753. console.log("plus fort charge")
  754. console.log(lesAnciennesPopulation[1][1])
  755. -- si le plus fort a fini le niveau, tous les individus de la population deviennent le plus fort
  756. if lesAnciennesPopulation[1][1].fitness == FITNESS_LEVEL_FINI then
  757. for i = 1, NB_INDIVIDU_POPULATION, 1 do
  758. laPopulation[i] = copier(lesAnciennesPopulation[1][1])
  759. end
  760. end
  761. io.close(fichier)
  762. console.log("chargement termine de " .. chemin)
  763. end
  764.  
  765. return laPopulation
  766. end
  767.  
  768. -- sauvegarde un seul reseau
  769. function sauvegarderUnReseau(unReseau, fichier)
  770. io.write(unReseau.nbNeurone .. "\n")
  771. io.write(#unReseau.lesConnexions .. "\n")
  772. io.write(unReseau.fitness .. "\n")
  773. for i = 1, unReseau.nbNeurone, 1 do
  774. local indice = NB_INPUT + NB_OUTPUT + i
  775. -- pas besoin d'écrire le type, je ne sauvegarde que les hiddens
  776. -- *non plus la valeur, car c'est reset toutes les frames en fait
  777. io.write(unReseau.lesNeurones[indice].id .. "\n")
  778. end
  779. for i = 1, #unReseau.lesConnexions, 1 do
  780. -- obligé car actif est un bool
  781. local actif = 1
  782. if unReseau.lesConnexions[i].actif ~= true then
  783. actif = 0
  784. end
  785. io.write(actif .. "\n" ..
  786. unReseau.lesConnexions[i].entree .. "\n" ..
  787. unReseau.lesConnexions[i].sortie .. "\n" ..
  788. unReseau.lesConnexions[i].poids .. "\n" ..
  789. unReseau.lesConnexions[i].innovation .. "\n")
  790. end
  791. end
  792.  
  793.  
  794. -- charge un seul reseau
  795. function chargerUnReseau(fichier)
  796. local unReseau = newReseau()
  797. local nbNeurone = io.read("*number")
  798. local nbConnexion = io.read("*number")
  799. unReseau.fitness = io.read("*number")
  800. unReseau.nbNeurone = nbNeurone
  801. unReseau.lesConnexions = {}
  802. for i = 1, nbNeurone, 1 do
  803. local neurone = newNeurone()
  804. neurone.id = io.read("*number")
  805. neurone.valeur = 0
  806. neurone.type = "hidden"
  807.  
  808. table.insert(unReseau.lesNeurones, neurone)
  809. end
  810.  
  811. for i = 1, nbConnexion, 1 do
  812. local connexion = newConnexion()
  813.  
  814. local actif = io.read("*number")
  815. connexion.entree = io.read("*number")
  816. connexion.sortie = io.read("*number")
  817. connexion.poids = io.read("*number")
  818. connexion.innovation = io.read("*number")
  819.  
  820. if actif == 1 then
  821. connexion.actif = true
  822. else
  823. connexion.actif = false
  824. end
  825.  
  826. table.insert(unReseau.lesConnexions, connexion)
  827. end
  828.  
  829. return unReseau
  830. end
  831.  
  832.  
  833. -- mets à jour un réseau de neurone avec ce qu'il y a a l'écran. A appeler à chaque frame quand on en test un reseau
  834. function majReseau(unReseau, marioBase)
  835. local mario = getPositionMario()
  836.  
  837.  
  838. -- niveau fini ?
  839. if not niveauFini and memory.readbyte(0x0100) == 12 then
  840. unReseau.fitness = FITNESS_LEVEL_FINI -- comme ça l'espece de cette population va dominer les autres
  841. niveauFini = true
  842. -- sinon augmentation de la fitness classique (quand mario va à gauche)
  843. elseif marioBase.x < mario.x then
  844. unReseau.fitness = unReseau.fitness + (mario.x - marioBase.x)
  845. marioBase.x = mario.x
  846. end
  847.  
  848. -- mise à jour des inputs
  849. lesInputs = getLesInputs()
  850. for i = 1, NB_INPUT, 1 do
  851. unReseau.lesNeurones[i].valeur = lesInputs[i]
  852. end
  853. end
  854.  
  855.  
  856. -- renvoie l'indice du tableau lesInputs avec les coordonnées x y, peut être utilisé aussi pour acceder aux inputs du réseau de neurone
  857. function getIndiceLesInputs(x, y)
  858. return x + ((y-1) * NB_TILE_W)
  859. end
  860.  
  861.  
  862. -- renvoie les inputs, sont créées en fonction d'où est mario
  863. function getLesInputs()
  864. local lesInputs = {}
  865. for i = 1, NB_TILE_W, 1 do
  866. for j = 1, NB_TILE_H, 1 do
  867. lesInputs[getIndiceLesInputs(i, j)] = 0
  868. end
  869. end
  870.  
  871. local lesSprites = getLesSprites()
  872. for i = 1, #lesSprites, 1 do
  873. local input = convertirPositionPourInput(getLesSprites()[i])
  874. if input.x > 0 and input.x < (TAILLE_VUE_W / TAILLE_TILE) + 1 then
  875. lesInputs[getIndiceLesInputs(input.x, input.y)] = -1
  876. end
  877. end
  878.  
  879.  
  880.  
  881. local lesTiles = getLesTiles()
  882. for i = 1, NB_TILE_W, 1 do
  883. for j = 1, NB_TILE_H, 1 do
  884. local indice = getIndiceLesInputs(i, j)
  885. if lesTiles[indice] ~= 0 then
  886. lesInputs[indice] = lesTiles[indice]
  887. end
  888. end
  889. end
  890.  
  891.  
  892. return lesInputs
  893. end
  894.  
  895.  
  896.  
  897. -- retourne une liste de taille 10 max de la position (x, y) des sprites à l'écran. (sprite = mechant truc)
  898. function getLesSprites()
  899. local lesSprites = {}
  900. local j = 1
  901. for i = 0, NB_SPRITE_MAX, 1 do
  902. -- si 14C8+i est > 7 il est dans un etat considéré vivant, et si 0x167A == 0 c'est qu'il fait des dégats à Mario
  903. if memory.readbyte(0x14C8+i) > 7 then
  904. -- le sprite existe
  905. lesSprites[j] = {x = memory.readbyte(0xE4+i) + memory.readbyte(0x14E0+i) * 256,
  906. y = math.floor(memory.readbyte(0xD8+i) + memory.readbyte(0x14D4+i) * 256)}
  907. j = j + 1
  908. end
  909. end
  910.  
  911.  
  912. -- ça c'est les extended sprites, c'est d'autres truc du jeu en gros
  913. for i = 0, NB_SPRITE_MAX, 1 do
  914. if memory.readbyte(0x170B+i) ~= 0 then
  915. lesSprites[j] = {x = memory.readbyte(0x171F+i) + memory.readbyte(0x1733+i) * 256,
  916. y = math.floor(memory.readbyte(0x1715+i) + memory.readbyte(0x1729+i) * 256)}
  917. j = j + 1
  918. end
  919. end
  920.  
  921. return lesSprites
  922. end
  923.  
  924.  
  925.  
  926.  
  927. -- renvoie une table qui a la meme taille que lesInputs. On y accède de la meme façon
  928. function getLesTiles()
  929. local lesTiles = {}
  930. local j = 1
  931.  
  932.  
  933. -- les tiles vont etre affiché autour de mario
  934. mario = getPositionMario()
  935. mario.x = mario.x - TAILLE_VUE_W / 2
  936. mario.y = mario.y - TAILLE_VUE_H / 2
  937.  
  938. for i = 1, NB_TILE_W, 1 do
  939. for j = 1, NB_TILE_H, 1 do
  940.  
  941.  
  942. local xT = math.ceil((mario.x + ((i - 1) * TAILLE_TILE)) / TAILLE_TILE)
  943. local yT = math.ceil((mario.y + ((j - 1) * TAILLE_TILE)) / TAILLE_TILE)
  944.  
  945. if xT > 0 and yT > 0 then
  946. -- plus d'info ici pour l'adresse memoire des blocs https://www.smwcentral.net/?p=section&a=details&id=21702
  947. lesTiles[getIndiceLesInputs(i, j)] = memory.readbyte(
  948. 0x1C800 +
  949. math.floor(xT / TAILLE_TILE) *
  950. 0x1B0 +
  951. yT * TAILLE_TILE +
  952. xT % TAILLE_TILE)
  953. else
  954. lesTiles[getIndiceLesInputs(i, j)] = 0
  955. end
  956. end
  957. end
  958.  
  959. return lesTiles
  960. end
  961.  
  962.  
  963.  
  964.  
  965. -- retourne la position de mario (x, y)
  966. function getPositionMario()
  967. local mario = {}
  968. mario.x = memory.read_s16_le(0x94)
  969. mario.y = memory.read_s16_le(0x96)
  970. return mario
  971. end
  972.  
  973.  
  974.  
  975.  
  976. -- retourne la position de la camera (x, y)
  977. function getPositionCamera()
  978. local camera = {}
  979. camera.x = memory.read_s16_le(0x1462)
  980. camera.y = memory.read_s16_le(0x1464)
  981.  
  982. return camera
  983. end
  984.  
  985.  
  986.  
  987. -- permet de convertir une position pour avoir les arguments x et y du tableau lesInputs
  988. function convertirPositionPourInput(position)
  989. local mario = getPositionMario()
  990. local positionT = {}
  991. mario.x = mario.x - TAILLE_VUE_W / 2
  992. mario.y = mario.y - TAILLE_VUE_H / 2
  993.  
  994. positionT.x = math.floor((position.x - mario.x) / TAILLE_TILE) + 1
  995. positionT.y = math.floor((position.y - mario.y) / TAILLE_TILE) + 1
  996.  
  997. return positionT
  998. end
  999.  
  1000.  
  1001. -- applique les boutons aux joypad de l'emulateur avec un reseau de neurone
  1002. function appliquerLesBoutons(unReseau)
  1003. local lesBoutonsT = {}
  1004. for i = 1, NB_OUTPUT, 1 do
  1005. lesBoutonsT[lesBoutons[i].nom] = sigmoid(unReseau.lesNeurones[NB_INPUT + i].valeur)
  1006. end
  1007.  
  1008. -- c'est pour que droit est la prio sur la gauche
  1009. if lesBoutonsT["P1 Left"] and lesBoutonsT["P1 Right"] then
  1010. lesBoutonsT["P1 Left"] = false
  1011. end
  1012. joypad.set(lesBoutonsT)
  1013. end
  1014.  
  1015.  
  1016. function traitementPause()
  1017. local lesBoutons = joypad.get(1)
  1018. if lesBoutons["P1 Start"] then
  1019. lesBoutons["P1 Start"] = false
  1020. else
  1021. lesBoutons["P1 Start"] = true
  1022. end
  1023. joypad.set(lesBoutons)
  1024. end
  1025.  
  1026.  
  1027.  
  1028. -- dessine les informations actuelles
  1029. function dessinerLesInfos(laPopulation, lesEspeces, nbGeneration)
  1030. gui.drawBox(0, 0, 256, 40, "black", "white")
  1031.  
  1032. gui.drawText(0, 4, "Generation " .. nbGeneration .. " Ind:" .. idPopulation .. " nb espece " ..
  1033. #lesEspeces .. "\nFitness:" ..
  1034. laPopulation[idPopulation].fitness .. " (max = " .. fitnessMax .. ")", "black")
  1035. end
  1036.  
  1037.  
  1038.  
  1039.  
  1040. function dessinerUnReseau(unReseau)
  1041. -- je commence par les inputs
  1042. local lesInputs = getLesInputs()
  1043. local camera = getPositionCamera()
  1044. local lesPositions = {} -- va retenir toutes les positions des neurones affichées, ça sera plus facile pour les connexions
  1045.  
  1046. for i = 1, NB_TILE_W, 1 do
  1047. for j = 1, NB_TILE_H, 1 do
  1048. local indice = getIndiceLesInputs(i, j)
  1049.  
  1050. -- le i - 1 et j - 1 c'est juste pour afficher les cases à la position x, y quand ils sont == 0
  1051. local xT = ENCRAGE_X_INPUT + (i - 1) * TAILLE_INPUT
  1052. local yT = ENCRAGE_Y_INPUT + (j - 1) * TAILLE_INPUT
  1053.  
  1054.  
  1055. local couleurFond = "gray"
  1056. if unReseau.lesNeurones[indice].valeur < 0 then
  1057. couleurFond = "black"
  1058. elseif unReseau.lesNeurones[indice].valeur > 0 then
  1059. couleurFond = "white"
  1060. end
  1061.  
  1062. gui.drawRectangle(xT, yT, TAILLE_INPUT, TAILLE_INPUT, "black", couleurFond)
  1063.  
  1064. lesPositions[indice] = {}
  1065. lesPositions[indice].x = xT + TAILLE_INPUT / 2
  1066. lesPositions[indice].y = yT + TAILLE_INPUT / 2
  1067. end
  1068. end
  1069.  
  1070.  
  1071.  
  1072. -- affichage du MARIO sur la grille, MARIO N'EST PAS UNE INPUT OUI C'EST POUR FAIRE JOLIE
  1073. local mario = convertirPositionPourInput(getPositionMario())
  1074.  
  1075. -- je respecte la meme regle qu'au dessus
  1076. mario.x = (mario.x - 1) * TAILLE_INPUT + ENCRAGE_X_INPUT
  1077. mario.y = (mario.y - 1) * TAILLE_INPUT + ENCRAGE_Y_INPUT
  1078. -- mario est 2 fois plus grand que les autres sprites, car sa position est celle qu'il a quand il est grand
  1079. gui.drawRectangle(mario.x, mario.y, TAILLE_INPUT, TAILLE_INPUT * 2, "black", "blue")
  1080.  
  1081. for i = 1, NB_OUTPUT, 1 do
  1082. local xT = ENCRAGE_X_OUTPUT
  1083. local yT = ENCRAGE_Y_OUTPUT + ESPACE_Y_OUTPUT * (i - 1)
  1084. local nomT = string.sub(lesBoutons[i].nom, 4)
  1085. local indice = i + NB_INPUT
  1086.  
  1087. if sigmoid(unReseau.lesNeurones[indice].valeur) then
  1088. gui.drawRectangle(xT, yT, TAILLE_OUTPUT_W, TAILLE_OUTPUT_H, "white", "white")
  1089. else
  1090. gui.drawRectangle(xT, yT, TAILLE_OUTPUT_W, TAILLE_OUTPUT_H, "white", "black")
  1091. end
  1092.  
  1093. xT = xT + TAILLE_OUTPUT_W
  1094. local strValeur = string.format("%.2f", unReseau.lesNeurones[indice].valeur)
  1095. --c'est pour afficher la valeur de l'input stv
  1096. gui.drawText(xT, yT-1, nomT -- .. "(" .. strValeur .. ")" --
  1097. , "white", "black", 10)
  1098. lesPositions[indice] = {}
  1099. lesPositions[indice].x = xT - TAILLE_OUTPUT_W / 2
  1100. lesPositions[indice].y = yT + TAILLE_OUTPUT_H / 2
  1101. end
  1102.  
  1103. for i = 1, unReseau.nbNeurone, 1 do
  1104. local xT = ENCRAGE_X_HIDDEN + (TAILLE_HIDDEN + 1) * (i - (NB_HIDDEN_PAR_LIGNE * math.floor((i-1) / NB_HIDDEN_PAR_LIGNE)))
  1105. local yT = ENCRAGE_Y_HIDDEN + (TAILLE_HIDDEN + 1) * (math.floor((i-1) / NB_HIDDEN_PAR_LIGNE))
  1106. -- tous les 10 j'affiche le restant des neuroens en dessous
  1107.  
  1108. local indice = i + NB_INPUT + NB_OUTPUT
  1109. gui.drawRectangle(xT, yT, TAILLE_HIDDEN, TAILLE_HIDDEN, "black", "white")
  1110.  
  1111. lesPositions[indice] = {}
  1112. lesPositions[indice].x = xT + TAILLE_HIDDEN / 2
  1113. lesPositions[indice].y = yT + TAILLE_HIDDEN / 2
  1114. end
  1115.  
  1116.  
  1117.  
  1118.  
  1119. -- affichage des connexions
  1120. for i = 1, #unReseau.lesConnexions, 1 do
  1121. if unReseau.lesConnexions[i].actif then
  1122. local pixel = 0
  1123. local alpha = 255
  1124. local couleur
  1125. if unReseau.lesConnexions[i].poids > 0 then
  1126. pixel = 255
  1127. end
  1128.  
  1129. if not unReseau.lesConnexions[i].allume then
  1130. alpha = 25
  1131. end
  1132.  
  1133. couleur = forms.createcolor(pixel, pixel, pixel, alpha)
  1134.  
  1135. gui.drawLine(lesPositions[unReseau.lesConnexions[i].entree].x,
  1136. lesPositions[unReseau.lesConnexions[i].entree].y,
  1137. lesPositions[unReseau.lesConnexions[i].sortie].x,
  1138. lesPositions[unReseau.lesConnexions[i].sortie].y,
  1139. couleur)
  1140. end
  1141. end
  1142. end
  1143.  
  1144.  
  1145.  
  1146.  
  1147. event.onexit(function()
  1148. console.log("Fin du script")
  1149. gui.clearGraphics()
  1150. forms.destroy(form)
  1151. end)
  1152.  
  1153. -- pas le choix de passer comme ça pour activer la sauvegarde
  1154. function activerSauvegarde()
  1155. sauvegarderPopulation(laPopulation, false)
  1156. end
  1157.  
  1158. -- pareil pour le chargement
  1159. function activerChargement()
  1160. chemin = forms.openfile()
  1161. -- possible que la fenetre soit fermée donc chemin nil
  1162. if chemin ~= "" then
  1163. local laPopulationT = chargerPopulation(chemin)
  1164. if laPopulationT ~= nil then
  1165. laPopulation = {}
  1166. laPopulation = copier(laPopulationT)
  1167. idPopulation = 1
  1168. lancerNiveau()
  1169. end
  1170. end
  1171. end
  1172.  
  1173. -- relance le niveau et reset tout pour le nouvel individu
  1174. function lancerNiveau()
  1175. savestate.load(NOM_SAVESTATE)
  1176. marioBase = getPositionMario()
  1177. niveauFini = false
  1178. nbFrameStop = 0
  1179. end
  1180.  
  1181. console.clear()
  1182. -- petit check pour voir si c'est bien la bonne rom
  1183. if gameinfo.getromname() ~= NOM_JEU then
  1184. console.log("mauvaise rom (actuellement " .. gameinfo.getromname() .. "), marche uniquement avec " .. nomJeu)
  1185. else
  1186. console.log("lancement du script")
  1187. math.randomseed(os.time())
  1188.  
  1189. lancerNiveau()
  1190.  
  1191. form = forms.newform(TAILLE_FORM_W, TAILLE_FORM_H, "Informations")
  1192. labelInfo = forms.label(form, "a maj", 0, 0, 350, 220)
  1193. estAccelere = forms.checkbox(form, "Accelerer", 10, 220)
  1194. estAfficheReseau = forms.checkbox(form, "Afficher reseau", 10, 240)
  1195. estAfficheInfo = forms.checkbox(form, "Afficher bandeau", 10, 260)
  1196. forms.button(form, "Pause", traitementPause, 10, 285)
  1197. forms.button(form, "Sauvegarder", activerSauvegarde, 10, 315)
  1198. forms.button(form, "Charger", activerChargement, 100, 315)
  1199.  
  1200. laPopulation = newPopulation()
  1201.  
  1202. for i = 1, #laPopulation, 1 do
  1203. mutation(laPopulation[i])
  1204. end
  1205.  
  1206. for i = 2, #laPopulation, 1 do
  1207. laPopulation[i] = copier(laPopulation[1])
  1208. mutation(laPopulation[i])
  1209. end
  1210.  
  1211. lesEspeces = trierPopulation(laPopulation)
  1212. laPopulation = nouvelleGeneration(laPopulation, lesEspeces)
  1213.  
  1214. -- boucle principale
  1215. while true do
  1216.  
  1217. -- ça va permettre de suivre si pendant cette frame il y a du l'evolution
  1218. local fitnessAvant = laPopulation[idPopulation].fitness
  1219. nettoyer = true
  1220.  
  1221.  
  1222. if forms.ischecked(estAccelere) then
  1223. emu.limitframerate(false)
  1224. else
  1225. emu.limitframerate(true)
  1226. end
  1227.  
  1228. if forms.ischecked(estAfficheReseau) then
  1229. dessinerUnReseau(laPopulation[idPopulation])
  1230. nettoyer = false
  1231. end
  1232.  
  1233. if forms.ischecked(estAfficheInfo) then
  1234. dessinerLesInfos(laPopulation, lesEspeces, nbGeneration)
  1235. nettoyer = false
  1236. end
  1237.  
  1238.  
  1239.  
  1240. if nettoyer then
  1241. gui.clearGraphics()
  1242. end
  1243.  
  1244.  
  1245. majReseau(laPopulation[idPopulation], marioBase)
  1246. feedForward(laPopulation[idPopulation])
  1247. appliquerLesBoutons(laPopulation[idPopulation])
  1248.  
  1249.  
  1250. if nbFrame == 0 then
  1251. fitnessInit = laPopulation[idPopulation].fitness
  1252. end
  1253.  
  1254. emu.frameadvance()
  1255. nbFrame = nbFrame + 1
  1256.  
  1257.  
  1258. if fitnessMax < laPopulation[idPopulation].fitness then
  1259. fitnessMax = laPopulation[idPopulation].fitness
  1260. end
  1261.  
  1262. -- si pas d'évolution ET que le jeu n'est pas en pause, on va voir si on reset ou pas
  1263. if fitnessAvant == laPopulation[idPopulation].fitness and memory.readbyte(0x13D4) == 0 then
  1264. nbFrameStop = nbFrameStop + 1
  1265. local nbFrameReset = NB_FRAME_RESET_BASE
  1266. -- si il y a eu progrés ET QUE mario n'est pas MORT
  1267. if fitnessInit ~= laPopulation[idPopulation].fitness and memory.readbyte(0x0071) ~= 9 then
  1268. nbFrameReset = NB_FRAME_RESET_PROGRES
  1269. end
  1270. if nbFrameStop > nbFrameReset then
  1271. nbFrameStop = 0
  1272. lancerNiveau()
  1273. idPopulation = idPopulation + 1
  1274. -- si on en est là, on va refaire une generation
  1275. if idPopulation > #laPopulation then
  1276. -- je check avant tout si le niveau a pas été terminé
  1277. if not niveauFiniSauvegarde then
  1278. for i = 1, #laPopulation, 1 do
  1279. -- le level a été fini une fois,
  1280. if laPopulation[i].fitness == FITNESS_LEVEL_FINI then
  1281. sauvegarderPopulation(laPopulation, true)
  1282. niveauFiniSauvegarde = true
  1283. console.log("Niveau fini apres " .. nbGeneration .. " generation !")
  1284. end
  1285. end
  1286. end
  1287. idPopulation = 1
  1288. nbGeneration = nbGeneration + 1
  1289. lesEspeces = trierPopulation(laPopulation)
  1290. laPopulation = nouvelleGeneration(laPopulation, lesEspeces)
  1291. nbFrame = 0
  1292. fitnessInit = 0
  1293. end
  1294. end
  1295. else
  1296. nbFrameStop = 0
  1297. end
  1298.  
  1299. -- maj du label actuel
  1300. local str = "generation " .. nbGeneration .. " Fitness maximal: " ..
  1301. fitnessMax .. "\nInformations sur l'individu actuel:\n" ..
  1302. "id: " .. idPopulation .. "/" .. #laPopulation .." neurones: " ..
  1303. #laPopulation[idPopulation].lesNeurones .. " connexions: " ..
  1304. #laPopulation[idPopulation].lesConnexions .. " enfant de l'espece " ..
  1305. laPopulation[idPopulation].idEspeceParent ..
  1306. "\n\nInfos sur les especes: " ..
  1307. "\nIl y a " .. #lesEspeces .. " espece(s) "
  1308. for i = 1, #lesEspeces, 1 do
  1309. str = str .. "\nespece " .. i .. " a fait " .. lesEspeces[i].nbEnfant .. " enfant(s)" .. " (fitnessmax " .. lesEspeces[i].fitnessMax .. ") "
  1310. end
  1311. forms.settext(labelInfo, str)
  1312. end
  1313.  
  1314. end
  1315.  
Advertisement
Comments
  • TheAfnoR
    216 days
    # text 0.32 KB | 0 0
    1. Salut Loïc. Est-ce qu'il serait possible d'augmenter la fitness de l'individu lorsque celui-ci effectue une action bénéfique dans le niveau (il tue un ennemi, il récupère un champi, ...) ?
    2. Je ne saurais pas trop ou le mettre dans le code, et le lua c'est pas trop mon truc, trop bas niveau pour moi :P
    3.  
    4. Merci
    5. Guillaume
Add Comment
Please, Sign In to add comment
Advertisement