├── imgs ├── dev.PNG ├── egg.gif ├── msonic.gif ├── control.gif └── sonicrun.gif ├── src └── geneticSonic │ ├── main.py │ ├── enderecos.py │ ├── solucao.py │ ├── Sonic.py │ ├── keyHardwareInput.py │ ├── KeyController.py │ ├── MemoryHandler.py │ └── GerenciadorJogo.py ├── LICENSE └── README.md /imgs/dev.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danilo94/GeneticSonic/HEAD/imgs/dev.PNG -------------------------------------------------------------------------------- /imgs/egg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danilo94/GeneticSonic/HEAD/imgs/egg.gif -------------------------------------------------------------------------------- /imgs/msonic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danilo94/GeneticSonic/HEAD/imgs/msonic.gif -------------------------------------------------------------------------------- /imgs/control.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danilo94/GeneticSonic/HEAD/imgs/control.gif -------------------------------------------------------------------------------- /imgs/sonicrun.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danilo94/GeneticSonic/HEAD/imgs/sonicrun.gif -------------------------------------------------------------------------------- /src/geneticSonic/main.py: -------------------------------------------------------------------------------- 1 | from GerenciadorJogo import * 2 | 3 | 4 | 5 | 6 | 7 | run = GerenciadorJogo("gens.exe") 8 | 9 | run.iniciar() -------------------------------------------------------------------------------- /src/geneticSonic/enderecos.py: -------------------------------------------------------------------------------- 1 | # Endereços de memória 2 | 3 | 4 | ANEIS = 0x005C4DE0 5 | VIDAS = 0x005C4DD3 6 | POSX = 0x005C1FC8 7 | POXY = 0x005C1FCC 8 | MINUTOS = 0x005C4DE2 9 | SEGUNDOS =0x005C4DE5 10 | ZONA = 0X005C4DD0 11 | ACT = 0X005C4DD1 12 | FLAGBOSS = 0X005C28E0 13 | VELOCIDADE = 0X0051FD0 14 | 15 | 16 | # Endereços teclado 17 | 18 | DOWN = 0x25 19 | LEFT =0x24 20 | RIGHT = 0x26 21 | JUMP = 0x1E 22 | CARREGARSAVESTATE = 0x42 23 | SALVARSAVESTATE = 0x43 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/geneticSonic/solucao.py: -------------------------------------------------------------------------------- 1 | from random import * 2 | 3 | 4 | MAXIMOCOMANDOS = 550 5 | 6 | 7 | class solucao(object): 8 | 9 | geracao = 0 10 | qualidade = 0 11 | comandos = [] 12 | tempoComandos = [] 13 | 14 | 15 | def __init__(self): 16 | self.geracao = 0 17 | self.qualidade = 0 18 | self.comandos = [] 19 | self.tempoComandos = [] 20 | 21 | def gerarSolucaoAleatoria(self): 22 | 23 | for i in range (0,MAXIMOCOMANDOS): 24 | indice = randint(0, 3) 25 | self.comandos.append(indice) 26 | self.tempoComandos.append(uniform(0.5,1.5)) -------------------------------------------------------------------------------- /src/geneticSonic/Sonic.py: -------------------------------------------------------------------------------- 1 | from enderecos import * 2 | 3 | class Sonic(object): 4 | 5 | vidas=None 6 | aneis=None 7 | posX=None 8 | posY=None 9 | velocidade=None 10 | ato = None 11 | zona = None 12 | 13 | gerenciadorMemoria=None 14 | def __init__(self,gerenciadorMemoria): 15 | self.vidas = None 16 | self.aneis = None 17 | self.posX = None 18 | self.posY = None 19 | self.velocidade = None 20 | self.ato = None 21 | self.zona = None 22 | self.gerenciadorMemoria = gerenciadorMemoria 23 | 24 | 25 | 26 | 27 | 28 | def atualizarPersoangem(self): 29 | self.vidas = self.gerenciadorMemoria.lerByte(VIDAS) 30 | self.aneis = self.gerenciadorMemoria.lerPalavra(ANEIS) 31 | self.posX = self.gerenciadorMemoria.lerPalavra(POSX) 32 | self.posY = self.gerenciadorMemoria.lerPalavra(POXY) 33 | self.velocidade = self.gerenciadorMemoria.lerPalavra(VELOCIDADE) 34 | self.ato = self.gerenciadorMemoria.lerByte(ACT) 35 | self.zona = self.gerenciadorMemoria.lerByte(ZONA) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Danilo Almeida 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/geneticSonic/keyHardwareInput.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | 4 | 5 | SendInput = ctypes.windll.user32.SendInput 6 | 7 | PUL = ctypes.POINTER(ctypes.c_ulong) 8 | 9 | 10 | class KeyBdInput(ctypes.Structure): 11 | _fields_ = [("wVk", ctypes.c_ushort), 12 | ("wScan", ctypes.c_ushort), 13 | ("dwFlags", ctypes.c_ulong), 14 | ("time", ctypes.c_ulong), 15 | ("dwExtraInfo", PUL)] 16 | 17 | 18 | class HardwareInput(ctypes.Structure): 19 | _fields_ = [("uMsg", ctypes.c_ulong), 20 | ("wParamL", ctypes.c_short), 21 | ("wParamH", ctypes.c_ushort)] 22 | 23 | 24 | class MouseInput(ctypes.Structure): 25 | _fields_ = [("dx", ctypes.c_long), 26 | ("dy", ctypes.c_long), 27 | ("mouseData", ctypes.c_ulong), 28 | ("dwFlags", ctypes.c_ulong), 29 | ("time", ctypes.c_ulong), 30 | ("dwExtraInfo", PUL)] 31 | 32 | 33 | class Input_I(ctypes.Union): 34 | _fields_ = [("ki", KeyBdInput), 35 | ("mi", MouseInput), 36 | ("hi", HardwareInput)] 37 | 38 | 39 | class Input(ctypes.Structure): 40 | _fields_ = [("type", ctypes.c_ulong), 41 | ("ii", Input_I)] -------------------------------------------------------------------------------- /src/geneticSonic/KeyController.py: -------------------------------------------------------------------------------- 1 | from keyHardwareInput import * 2 | from time import * 3 | from enderecos import * 4 | class keyController(object): 5 | 6 | def __init__(self): 7 | sleep(5) 8 | 9 | 10 | def pressionar(self,tecla,tempo): 11 | 12 | if (tecla==0): # Andar pra frente 13 | self.pressKey(LEFT) 14 | sleep(tempo) 15 | self.releaseKey(LEFT) 16 | pass 17 | elif(tecla==1): # Andar para trás 18 | self.pressKey(RIGHT) 19 | sleep(tempo) 20 | self.releaseKey(RIGHT) 21 | pass 22 | 23 | elif(tecla==2): # Pular 24 | self.pressKey(JUMP) 25 | sleep(tempo) 26 | self.releaseKey(JUMP) 27 | pass 28 | elif(tecla==6): 29 | self.pressKey(CARREGARSAVESTATE) 30 | sleep(tempo) 31 | self.releaseKey(CARREGARSAVESTATE) 32 | 33 | 34 | 35 | def pressKey(self,hexKeyCode): 36 | extra = ctypes.c_ulong(0) 37 | ii_ = Input_I() 38 | ii_.ki = KeyBdInput(0, hexKeyCode, 0x0008, 0, ctypes.pointer(extra)) 39 | x = Input(ctypes.c_ulong(1), ii_) 40 | ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) 41 | 42 | def releaseKey(self,hexKeyCode): 43 | extra = ctypes.c_ulong(0) 44 | ii_ = Input_I() 45 | ii_.ki = KeyBdInput(0, hexKeyCode, 0x0008 | 0x0002, 0, 46 | ctypes.pointer(extra)) 47 | x = Input(ctypes.c_ulong(1), ii_) 48 | ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) -------------------------------------------------------------------------------- /src/geneticSonic/MemoryHandler.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.wintypes import * 3 | import psutil 4 | from time import * 5 | 6 | PROCESS_QUERY_INFORMATION = 0x0400 7 | PROCESS_VM_READ = 0x0010 8 | ANEIS = 0x5C4DE0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | class MemoryHandler(object): 17 | 18 | pid= 0 19 | openProcess = None 20 | readProcessMemory = None 21 | CloseHandle = None 22 | gerenciadorProcesso = None 23 | 24 | def __init__(self,nomeProcesso): 25 | for processo in psutil.process_iter(): 26 | if processo.name() == nomeProcesso: 27 | self.pid = processo.pid 28 | print("Processo encontrado, inicializando coleta de dados") 29 | print(self.pid) 30 | self.conectarProcesso() 31 | pass 32 | 33 | 34 | 35 | def conectarProcesso(self): 36 | self.abrirProcesso = windll.kernel32.OpenProcess 37 | self.lerMemoriaProcesso = windll.kernel32.ReadProcessMemory 38 | self.fecharGerenciador = windll.kernel32.CloseHandle 39 | self.gerenciadorProcesso = self.abrirProcesso(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,False,self.pid) 40 | 41 | 42 | 43 | def lerPalavra(self,endereco): 44 | if (self.gerenciadorProcesso!= None): 45 | buffer = ctypes.c_uint16() 46 | bytread = ctypes.c_uint16() 47 | self.lerMemoriaProcesso(self.gerenciadorProcesso,endereco, ctypes.byref(buffer),ctypes.sizeof(buffer),ctypes.byref(bytread)) 48 | return buffer.value 49 | 50 | else: 51 | print("Gerenciador de processo não foi inicializado, abortando código") 52 | exit(0) 53 | 54 | def lerByte(self,endereco): 55 | if (self.gerenciadorProcesso!= None): 56 | buffer = ctypes.c_ubyte() 57 | bytread = ctypes.c_ubyte() 58 | self.lerMemoriaProcesso(self.gerenciadorProcesso,endereco, ctypes.byref(buffer),ctypes.sizeof(buffer),ctypes.byref(bytread)) 59 | return buffer.value 60 | 61 | else: 62 | print("Gerenciador de processo não foi inicializado, abortando código") 63 | exit(0) 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Sonic](https://github.com/danilo94/GeneticSonic/blob/master/imgs/msonic.gif) Genetic Sonic 2 | Um Algoritmo Genético para o jogo Sonic The Hedgehog 1. 3 | 4 | 5 | [![Sonic](https://github.com/danilo94/GeneticSonic/blob/master/imgs/dev.PNG)](https://www.youtube.com/watch?v=Ipv6g3z4RNo) 6 | ## Motivação 7 | Implementação de um algoritmo genético capaz de jogar e eventualmente, passar as fases do jogo sonic the hedgehog 1. Se trata de uma implementação baseada no conceito de algoritmos genéticos, que utiliza o conceito da evolução dos seres vivos para que eventualmente o algoritmo consiga vencer os desafios da fase e chegar até o final. 8 | ### Pre-requisitos 9 | Este programa é composto por um Dashboard, que foi implementado em Electron, e pela implementação do algoritmo genético que foi implementada em python. Com isso será necessária a instalação das seguintes dependências para execução do algoritmo: 10 | 11 | 12 | ``` 13 | * [Python 3](https://www.python.org/downloads/) - Implementação do algoritmo Genético 14 | * [Node JS](https://nodejs.org/) - Implementação do Dashboard (Opcional) 15 | 16 | * [Gens](https://segaretro.org/Gens/GS) - Emulador Utilizado para rodar o jogo 17 | * [Alguma Hack Rom do jogo] - ¯\_(ツ)_/¯ 18 | 19 | ``` 20 | 21 | ### ![Sonic](https://github.com/danilo94/GeneticSonic/blob/master/imgs/control.gif) Configurando o Emulador 22 | 23 | Para que o algoritmo genético consiga jogar o jogo normalmente, será necessário utilizar o buffer do teclado diretamente, logo este ficará impossibilitado de ser utilizado. Também será necessário configurar as teclas do teclado na seguinte ordem: I ( Cima ), K (Baixo), L (Direita), J (Esquerda). Será também necessário criar um ponto de partida na fase em que deseja jogar. Para isso basta pressionar F5 no inicio da fase que deseja começar :) . 24 | 25 | 26 | ### ![Sonic](https://github.com/danilo94/GeneticSonic/blob/master/imgs/sonicrun.gif) Rodando pela primeira vez 27 | 28 | Ao rodar o algoritmo deve se levar em consideração que como utilizamos o buffer do teclado para injetar os comandos, é necessário que o emulador esteja SEMPRE em foco na tela. Sabendo disso, para executar tanto o dashboard, quanto o algoritmo genético basta executar o seguinte conjunto de passos no prompt de comando do windows. 29 | 30 | 31 | ``` 32 | npm install - Deve ser executado uma única vez para baixar as dependências do projeto 33 | npm start - Irá inicializar o dashboard. 34 | Abrir o Gens e carregar a rom do jogo. 35 | python main.py - Dentro da pasta do emulador 36 | ``` 37 | O código irá aguardar cerca de 3 segundos para que o usuário posicione a tela do emulador em foco e o processo evolutivo irá iniciar. 38 | 39 | 40 | ### ![eggman](https://github.com/danilo94/GeneticSonic/blob/master/imgs/egg.gif) Contribua ! 41 | 42 | Se você gostou do projeto, faça um fork e contribua adicionando novas funcionalidades e corrigindo bugs tanto no algoritmo genético quanto no dashboard. 43 | 44 | 45 | ### Assista em tempo real 46 | Se você curtiu o projeto se inscreva nos dois canais e ajude o projeto a alcançar mais pessoas :D. 47 | 48 | * [Dan Maker](https://www.youtube.com/channel/UCZbZ0IEMOoLiDxAGM7KBXwA?view_as=subscriber) - Canal do criador do projeto 49 | 50 | -------------------------------------------------------------------------------- /src/geneticSonic/GerenciadorJogo.py: -------------------------------------------------------------------------------- 1 | from Sonic import * 2 | from KeyController import * 3 | from MemoryHandler import * 4 | from solucao import * 5 | from operator import * 6 | 7 | 8 | TAMANHOGER = 5 9 | MELHORES = 0.6 10 | MUTACAO = 0.05 11 | RESETGERACOES = 4 12 | class GerenciadorJogo(object): 13 | 14 | geracao = 0 15 | gerenciadorMemoria = None 16 | controladorTeclado = None 17 | sonic = None 18 | vetorSolucoes = [] 19 | tempoLimite = 3 20 | 21 | melhorSolucaoGerAnterior = 0 22 | resetSolucoes=0 23 | 24 | 25 | maxDistance = 0 26 | 27 | melhorSolucao = None 28 | 29 | auxSolucao = None 30 | 31 | indiceSolucao = 0 32 | 33 | def __init__(self,nomeProcesso): 34 | self.geracao = 0 35 | self.gerenciadorMemoria = MemoryHandler(nomeProcesso) 36 | self.controladorTeclado = keyController() 37 | self.sonic = Sonic(self.gerenciadorMemoria) 38 | 39 | self.melhorSolucao = solucao() 40 | 41 | for i in range (0,TAMANHOGER): 42 | individuo = solucao() 43 | individuo.geracao=0 44 | individuo.gerarSolucaoAleatoria() 45 | self.vetorSolucoes.append(individuo) 46 | 47 | 48 | 49 | 50 | 51 | 52 | def iniciar(self): 53 | 54 | while True: 55 | self.executarsolucao() 56 | pass 57 | 58 | 59 | 60 | 61 | def executarsolucao(self): 62 | self.resetGame() 63 | if (self.indiceSolucao>=TAMANHOGER): 64 | self.proximaSolucao() 65 | self.indiceSolucao=0 66 | 67 | solucao = self.vetorSolucoes[self.indiceSolucao] 68 | quantidadeComandos = len(solucao.comandos) 69 | comandos = solucao.comandos 70 | tempoComandos = solucao.tempoComandos 71 | self.sonic.atualizarPersoangem() 72 | tempoPassado = 0 73 | for i in range (0,quantidadeComandos): 74 | self.sonic.atualizarPersoangem() 75 | qualidadeAntiga = solucao.qualidade 76 | tempoInicial = time() 77 | vidaAnterior = self.sonic.vidas 78 | distanciaAntiga = self.sonic.posX 79 | quantidadeAneisAntiga = self.sonic.aneis 80 | 81 | self.controladorTeclado.pressionar(comandos[i],tempoComandos[i]) 82 | self.sonic.atualizarPersoangem() 83 | vidaAtual = self.sonic.vidas 84 | distanciaAtual = self.sonic.posX 85 | quantidadeAneisNova = self.sonic.aneis 86 | tempoFinal = time() 87 | self.fitnessAneis(solucao,quantidadeAneisAntiga,quantidadeAneisNova) 88 | self.fitnessDistancia(solucao,distanciaAntiga,distanciaAtual) 89 | novaQualidade = solucao.qualidade 90 | 91 | if (novaQualidade-qualidadeAntiga<=0): 92 | tempoPassado+= tempoFinal-tempoInicial 93 | else: 94 | tempoPassado=0 95 | 96 | if (tempoPassado>=2): 97 | if (self.melhorSolucaoGerAnterior <= solucao.qualidade): 98 | self.melhorSolucaoGerAnterior = solucao.qualidade 99 | solucao.qualidade = solucao.qualidade - solucao.qualidade*0.5-2 100 | self.verificaSolucao(solucao) 101 | self.resetGame() 102 | self.proximaSolucao() 103 | return 104 | 105 | 106 | if (vidaAtual=0): 152 | solucao.qualidade+=(0.05*delta)*solucao.qualidade 153 | else: 154 | solucao.qualidade-=(0.25*solucao.qualidade) 155 | 156 | 157 | 158 | 159 | def fitnessDistancia(self,solucao,antigo,novo): 160 | delta = novo - antigo 161 | 162 | 163 | if (delta>0 and novo > self.maxDistance): 164 | self.maxDistance = novo 165 | 166 | solucao.qualidade+=(delta) 167 | 168 | 169 | def proximaSolucao(self): 170 | if (self.indiceSolucao=0.5): 224 | elementoAuxiliar.comandos.append(solucaoAuxiliar.comandos[j]) 225 | elementoAuxiliar.tempoComandos.append(solucaoAuxiliar.tempoComandos[j]) 226 | else: 227 | elementoAuxiliar.comandos.append(self.melhorSolucao.comandos[j]) 228 | elementoAuxiliar.tempoComandos.append(self.melhorSolucao.tempoComandos[j]) 229 | pass 230 | 231 | vetorCruzamento.append(elementoAuxiliar) 232 | 233 | resto = TAMANHOGER-melhores 234 | 235 | for i in range (0,resto): 236 | elementoAuxiliar = solucao() 237 | elementoAuxiliar.geracao=self.geracao 238 | elementoAuxiliar.tempoComandos=[] 239 | elementoAuxiliar.comandos=[] 240 | elementoAuxiliar.qualidade=0 241 | elementoAuxiliar.gerarSolucaoAleatoria() 242 | vetorCruzamento.append(elementoAuxiliar) 243 | 244 | self.vetorSolucoes.clear() 245 | 246 | for solucoesCruzadas in vetorCruzamento: 247 | self.vetorSolucoes.append(solucoesCruzadas) --------------------------------------------------------------------------------