From d746dbac3c056681feb3756f2c6091ecb8760e7c Mon Sep 17 00:00:00 2001 From: apages Date: Wed, 16 May 2018 15:26:47 +0200 Subject: [PATCH 1/3] working reinforced AI --- .gitignore | 2 + const.py | 13 ++ flappy.py | 350 +++++++++++++++++++++++++---------------------------- ga.py | 33 +++++ nn.py | 124 +++++++++++++++---- player.py | 72 +++++++++++ 6 files changed, 385 insertions(+), 209 deletions(-) create mode 100644 const.py create mode 100644 ga.py create mode 100644 player.py diff --git a/.gitignore b/.gitignore index 0d20b648..8068ffcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.pyc +*.json +.vscode \ No newline at end of file diff --git a/const.py b/const.py new file mode 100644 index 00000000..abfc3259 --- /dev/null +++ b/const.py @@ -0,0 +1,13 @@ +FPS = 30 +SCREENWIDTH = 512 +SCREENHEIGHT = 512 +# amount by which base can maximum shift to left +PIPEGAPSIZE = 110 # gap between upper and lower part of pipe +PIPEHEIGHT = 300 +PIPEWIDTH = 50 +BASEY = SCREENHEIGHT * 0.79 # ground is 21% of the total height +BASEX = 0 +PIPEVELX = -4 +VELYMAX = -18 + +MAX_POPULATION = 350 \ No newline at end of file diff --git a/flappy.py b/flappy.py index 27d3de5d..cf70bb72 100644 --- a/flappy.py +++ b/flappy.py @@ -1,228 +1,204 @@ from itertools import cycle import random import sys - +from nn import * import pygame from pygame.locals import * +from player import Player +import ga +from const import * +import os.path + +previous_score = 0 +savedPlayers = [] +players = [] -FPS = 30 -SCREENWIDTH = 512 -SCREENHEIGHT = 512 -# amount by which base can maximum shift to left -PIPEGAPSIZE = 100 # gap between upper and lower part of pipe -PIPEHEIGHT = 300 -PIPEWIDTH = 50 -BASEY = SCREENHEIGHT * 0.79 -BASEX = 0 - -try: - xrange -except NameError: - xrange = range - -class Player: - def __init__(self): - self.x = int(SCREENWIDTH * 0.2) - self.width = 20 - self.height = 20 - - maxValue = int((SCREENHEIGHT - self.height) / SCREENHEIGHT * 100) - minValue = int(self.height / SCREENHEIGHT * 100) - self.y = int((SCREENHEIGHT - self.height) * random.randint(minValue, maxValue) / 100 ) - - # player velocity, max velocity, downward accleration, accleration on flap - self.velY = -9 # player's velocity along Y, default same as playerFlapped - self.maxVelY = 10 # max vel along Y, max descend speed - self.accY = 1 # players downward accleration - self.flapAcc = -9 # players speed on flapping - self.flapped = False # True when player flaps - - self.score = 0 - - def update(self, event): - if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP): - if self.y > -2 * self.height: - self.velY = self.flapAcc - self.flapped = True - -def main(): - global SCREEN, FPSCLOCK, myfont +def main(arg): + global robotoFont, bestScoreEver, SCREEN, players, pipes, speed, previous_score, filename, trainningMode pygame.init() FPSCLOCK = pygame.time.Clock() SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT)) pygame.display.set_caption('Flappy Bird') - myfont = pygame.font.SysFont("Comic Sans MS", 30) - - while True: - crashInfo = mainGame() - showGameOverScreen(crashInfo) - -def mainGame(): - players = [] - for i in range(0,1): - players.append(Player()) - - # get 2 new pipes to add to upperPipes lowerPipes list - newPipe1 = getRandomPipe() - # newPipe2 = getRandomPipe() - - # list of upper pipes - upperPipes = [ - newPipe1[0], - # newPipe2[0], - ] - - # list of lowerpipe - lowerPipes = [ - newPipe1[1], - # newPipe2[1], - ] - - pipeVelX = -4 - + robotoFont = pygame.font.SysFont("Roboto", 30) + + brain = None + filename = arg[1] if len(arg) > 1 else "goodplayer.json" + if os.path.isfile(filename) : + with open(filename, 'r') as file : + brain = NeuralNetwork.fromjson(file.read()) + + trainningMode = False + # Populate with players + if len(arg) > 2 and arg[2] == "train": + trainningMode = True + for i in range(0, MAX_POPULATION): + p = Player(brain) + if brain is not None : + p.brain.mutate(0.1) + players.append(p) + else : + p = Player(brain) + players.append(p) + + + # Create pipes + newPipe = generatePipe() + pipes = [newPipe] + + bestScoreEver = 0 + speed = 1 while True: - playerEvent = type('', (object,),{ 'type': 0, 'key': 0}) - for event in pygame.event.get(): - if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): - pygame.quit() - sys.exit() - if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP): - playerEvent = event - - # move pipes to left - for uPipe, lPipe in zip(upperPipes, lowerPipes): - uPipe['x'] += pipeVelX - lPipe['x'] += pipeVelX - - # add new pipe when first pipe is about to touch left of screen - if 0 < upperPipes[0]['x'] < 5: - newPipe = getRandomPipe() - upperPipes.append(newPipe[0]) - lowerPipes.append(newPipe[1]) - - # remove first pipe if its out of the screen - if upperPipes[0]['x'] < -PIPEWIDTH: - upperPipes.pop(0) - lowerPipes.pop(0) - - # draw sprites + handleGameEvents() + for i in range(0, speed) : + bestPlayerScore, bestScoreEver = update() + + # draw + # Background SCREEN.fill((0,0,0)) - for uPipe, lPipe in zip(upperPipes, lowerPipes): - pygame.draw.rect(SCREEN,(255,255,255), (uPipe['x'], uPipe['y'],PIPEWIDTH,PIPEHEIGHT)) - pygame.draw.rect(SCREEN,(255,255,255), (lPipe['x'], lPipe['y'],PIPEWIDTH,PIPEHEIGHT)) + # Pipes + for pipe in pipes: + pygame.draw.rect(SCREEN,(255,255,255), (pipe['x'], pipe['top'] - PIPEHEIGHT,PIPEWIDTH,PIPEHEIGHT)) + pygame.draw.rect(SCREEN,(255,255,255), (pipe['x'], pipe['bottom'],PIPEWIDTH,PIPEHEIGHT)) - pygame.draw.rect(SCREEN,(255,255,255), (BASEX, BASEY,SCREENWIDTH,BASEY)) + # Ground + pygame.draw.rect(SCREEN,(255,255,255), (BASEX, BASEY,SCREENWIDTH,SCREENHEIGHT - BASEY)) + # Players for player in players: - player.update(playerEvent) - # check for crash here - crashTest = checkCrash(player, - upperPipes, lowerPipes) - if crashTest[0]: - players.remove(player) - if len(players) ==0: - return { - 'player': player, - 'upperPipes': upperPipes, - 'lowerPipes': lowerPipes, - } - - # check for score - playerMidPos = player.x + player.width / 2 - for pipe in upperPipes: - pipeMidPos = pipe['x'] + PIPEWIDTH / 2 - if pipeMidPos <= playerMidPos < pipeMidPos + 4: - player.score += 1 - - # player's movement - if player.velY < player.maxVelY and not player.flapped: - player.velY += player.accY - if player.flapped: - player.flapped = False - - player.y += min(player.velY, BASEY - player.y - player.height) - - - # print score so player overlaps the score - showScore(player.score) - pygame.draw.ellipse(SCREEN, (255,255,255,200), (player.x, player.y, player.width, player.width), 0) + box_surface_circle = pygame.Surface((50, 50), pygame.SRCALPHA) + pygame.draw.circle(box_surface_circle, (255, 255, 255, 75), (int(player.width/2), int(player.width/2)),int(player.width/2), 0) + SCREEN.blit(box_surface_circle, (player.x, player.y)) + + # Scores + showScore(bestPlayerScore, (10,0)) + showScore(bestScoreEver, (10, 25)) + showScore(len(players), (SCREENWIDTH - 40, 0)) pygame.display.update() FPSCLOCK.tick(FPS) - -def showGameOverScreen(crashInfo): - """crashes the player down ans shows gameover image""" - player = crashInfo['player'] - - upperPipes, lowerPipes = crashInfo['upperPipes'], crashInfo['lowerPipes'] - - while True: - for event in pygame.event.get(): - if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): - pygame.quit() - sys.exit() - if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP): - return - - # draw sprites - SCREEN.fill((0,0,0)) - - for uPipe, lPipe in zip(upperPipes, lowerPipes): - pygame.draw.rect(SCREEN,(255,255,255), (uPipe['x'], uPipe['y'],PIPEWIDTH, PIPEHEIGHT)) - pygame.draw.rect(SCREEN,(255,255,255), (lPipe['x'], lPipe['y'],PIPEWIDTH, PIPEHEIGHT)) - - pygame.draw.rect(SCREEN,(255,255,255), (BASEX, BASEY,SCREENWIDTH,BASEY)) - showScore(player.score) - - pygame.draw.ellipse(SCREEN, (255,255,255,200), (player.x, player.y, player.width, player.width), 0) - - FPSCLOCK.tick(FPS) - pygame.display.update() - -def getRandomPipe(): - """returns a randomly generated pipe""" +def handleGameEvents() : + global speed + for event in pygame.event.get(): + # QUIT + if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE): + pygame.quit() + sys.exit() + + # SPEED UP + if event.type == KEYDOWN and event.key == K_UP: + speed += 1 + print("up") + # SPEED DOWN + elif event.type == KEYDOWN and event.key == K_DOWN: + speed -= 1 + print("down") + + if speed < 1 : + speed = 1 + if speed > 10 : + speed = 10 + +def update(): + global previous_score, bestScoreEver, savedPlayers, players, pipes + # move pipes to left + for pipe in pipes: + pipe['x'] += PIPEVELX + + # add new pipe when last pipe is about to touch left of screen + if 0 < pipes[- 1]['x'] < SCREENWIDTH / 3: + newPipe = generatePipe() + pipes.append(newPipe) + + # remove first pipe if its out of the screen + if pipes[0]['x'] < -PIPEWIDTH: + pipes.pop(0) + + # check crash + i = 0 + while i < len(players) : + player = players[i] + crashTest = checkCrash(player, pipes) + if crashTest: + savedPlayers.append(player) + players.remove(player) + i -= 1 + i += 1 + + bestPlayerScore = 0 + for player in players: + player.think(pipes) + player.update() + bestPlayerScore = max(player.score, bestPlayerScore) + + bestScoreEver = max(bestScoreEver, bestPlayerScore) + + if len(players) == 0: + if max(previous_score, bestScoreEver) > previous_score : + json = savedPlayers[-1].brain.tojson() + with open(filename, 'w') as file : + file.write(json) + + showBestNN() + previous_score = max(previous_score, bestScoreEver) + + if trainningMode : + players = ga.nextGeneration(savedPlayers) + else : + players.append(Player(savedPlayers[-1].brain)) + + savedPlayers = [] + # clear pipes + newPipe = generatePipe() + pipes = [newPipe] + + return bestPlayerScore, bestScoreEver + +def generatePipe(): # y of gap between upper and lower pipe - gapY = random.randrange(0, int(BASEY * 0.6 - PIPEGAPSIZE)) + gapY = random.randrange(0, int(BASEY*0.8 - PIPEGAPSIZE)) gapY += int(BASEY * 0.2) pipeX = SCREENWIDTH + 10 - return [ - {'x': pipeX, 'y': gapY - PIPEHEIGHT}, # upper pipe - {'x': pipeX, 'y': gapY + PIPEGAPSIZE}, # lower pipe - ] + return {'x': pipeX, 'top': gapY, 'bottom' : gapY + PIPEGAPSIZE} +def showBestNN(): + if len(savedPlayers) == 0 : + return -def showScore(score): - """displays score in center of screen""" - label = myfont.render(str(score), 1, (255,255,255)) - SCREEN.blit(label, (10, 10)) + print("BEST Input-Hidden") + print(savedPlayers[len(savedPlayers) - 1].brain.weight_ih) + print("BEST Hidden-Output") + print(savedPlayers[len(savedPlayers) - 1].brain.weight_ho) + print("Score :") + print(savedPlayers[len(savedPlayers) - 1].score) -def checkCrash(player, upperPipes, lowerPipes): - """returns True if player collders with base or pipes.""" +def showScore(score, pos): + label = robotoFont.render(str(score), 1, (255,255,255)) + SCREEN.blit(label, pos) - # if player crashes into ground - if player.y + player.height >= BASEY - 1: - return [True, True] +def checkCrash(player, pipes): + # if player crashes into ground or sky + if player.y + player.width >= BASEY - 1 or player.y <= 0: + return True else: - playerRect = pygame.Rect(player.x, player.y, - player.width, player.height) + playerRect = pygame.Rect(player.x, player.y, player.width, player.width) - for uPipe, lPipe in zip(upperPipes, lowerPipes): + for pipe in pipes: # upper and lower pipe rects - uPipeRect = pygame.Rect(uPipe['x'], uPipe['y'], PIPEWIDTH, PIPEHEIGHT) - lPipeRect = pygame.Rect(lPipe['x'], lPipe['y'], PIPEWIDTH, PIPEHEIGHT) + uPipeRect = pygame.Rect(pipe['x'], pipe['top'] - PIPEHEIGHT, PIPEWIDTH, PIPEHEIGHT) + lPipeRect = pygame.Rect(pipe['x'], pipe['bottom'], PIPEWIDTH, PIPEHEIGHT) # if bird collided with upipe or lpipe uCollide = pixelCollision(playerRect, uPipeRect) lCollide = pixelCollision(playerRect, lPipeRect) if uCollide or lCollide: - return [True, False] + return True - return [False, False] + return False def pixelCollision(rect1, rect2): """Checks if two objects collide and not just their rects""" @@ -234,4 +210,4 @@ def pixelCollision(rect1, rect2): return True if __name__ == '__main__': - main() + main(sys.argv) diff --git a/ga.py b/ga.py new file mode 100644 index 00000000..66c49d1e --- /dev/null +++ b/ga.py @@ -0,0 +1,33 @@ +from player import Player +import const +import random + +def nextGeneration(parents): + calculateFitness(parents) + childs = [] + for i in range(0, const.MAX_POPULATION) : + childs.append(pickOne(parents)) + + return childs + +def pickOne(parents): + index = 0 + r = random.random() + while(r > 0) : + r = r - parents[index].fitness + index += 1 + + index -= 1 + player = parents[index] + child = Player(player.brain) + child.brain.mutate(0.1) + return child + +def calculateFitness(parents): + sum = 0 + for player in parents : + sum += player.score + + for player in parents : + player.fitness = player.score / sum + \ No newline at end of file diff --git a/nn.py b/nn.py index bf4293a0..12518203 100644 --- a/nn.py +++ b/nn.py @@ -1,27 +1,54 @@ -from numpy import exp, array, dot, random as nrandom - +from numpy import exp, array, dot, random as nrandom, vectorize +import numpy +import json class NeuralNetwork: - def __init__(self, input_nodes, hidden_nodes, output_nodes): - global learning_rate - learning_rate = 1 - self.input_nodes = input_nodes - self.hidden_nodes = hidden_nodes - self.output_nodes = output_nodes - # initialize a matrice for weights between INPUTS and HIDDEN layer: - # 2 * Random - 1 enable to have a number into [-1, 1] - self.weight_ih = 2 * nrandom.random((hidden_nodes,input_nodes)) - 1 - - # initialize a matrice for weights between HIDDEN and OUTPUTS layer: - self.weight_ho = 2 * nrandom.random((output_nodes, hidden_nodes)) - 1 + # todo : document what a,b,c are + def __init__(self, a, b = None, c = None, wih = None, who = None, bh = None, bo = None): + numpy.seterr(all='ignore') + if isinstance(a, NeuralNetwork): + self.input_nodes = a.input_nodes + self.hidden_nodes = a.hidden_nodes + self.output_nodes = a.output_nodes + + self.weight_ih = a.weight_ih.copy() + self.weight_ho = a.weight_ho.copy() + self.bias_h = a.bias_h.copy() + self.bias_o = a.bias_o.copy() + + else: + self.input_nodes = a + self.hidden_nodes = b + self.output_nodes = c - # initialize a matrice for biais for the HIDDEN layer: - self.bias_h = 2 * nrandom.random((hidden_nodes, 1)) - 1 + # initialize a matrice for weights between INPUTS and HIDDEN layer: + if wih is not None and len(wih) > 0 : + self.weight_ih = wih + else: + # 2 * Random - 1 enable to have a number into [-1, 1] + self.weight_ih = 2 * nrandom.random((self.hidden_nodes,self.input_nodes)) - 1 + + if who is not None and len(who) > 0 : + self.weight_ho = who + else: + # initialize a matrice for weights between HIDDEN and OUTPUTS layer: + self.weight_ho = 2 * nrandom.random((self.output_nodes, self.hidden_nodes)) - 1 - # initialize a matrice for biais for the OUTPUT layer: - self.bias_o = 2 * nrandom.random((output_nodes, 1)) - 1 + if bh is not None and len(bh) > 0 : + self.bias_h = bh + else: + # initialize a matrice for biais for the HIDDEN layer: + self.bias_h = 2 * nrandom.random((self.hidden_nodes, 1)) - 1 + + if bo is not None and len(bo) > 0 : + self.bias_o = bo + else: + # initialize a matrice for biais for the OUTPUT layer: + self.bias_o = 2 * nrandom.random((self.output_nodes, 1)) - 1 + + self.learning_rate = 1 - def feedforward(self, inputs): + def predict(self, inputs): inputs = inputs.reshape(self.input_nodes, 1) # generating the hidden layer @@ -40,17 +67,17 @@ def feedforward(self, inputs): def train(self, inputs, target_outputs): - hidden, outputs = self.feedforward(inputs) + hidden, outputs = self.predict(inputs) inputs = inputs.reshape(self.input_nodes, 1) target_outputs = target_outputs.reshape(self.output_nodes, 1) # Compute delta HIDDEN - OUTPUT output_errors = target_outputs - outputs - output_delta = self.d_sigmoid(outputs) * output_errors * learning_rate + output_delta = self.d_sigmoid(outputs) * output_errors * self.learning_rate # Compute delta INPUT - HIDDEN hidden_errors = dot(self.weight_ho.T, output_delta) - hidden_delta = self.d_sigmoid(hidden) * hidden_errors * learning_rate + hidden_delta = self.d_sigmoid(hidden) * hidden_errors * self.learning_rate # adjust the weight by deltas self.weight_ho += dot(output_delta, hidden.T) @@ -69,6 +96,59 @@ def sigmoid(self, x): def d_sigmoid(self, x): # derivative of the sigmoid function return x * (1 - x) + + def copy(self) : + return NeuralNetwork(self) + + def mutate(self, rate) : + def mutate(value) : + if nrandom.random() <= rate : + val = nrandom.random() * 2 - 1 + return val + else : + return value + + mutateFunc = vectorize(mutate) + self.weight_ih = mutateFunc(self.weight_ih) + self.weight_ho = mutateFunc(self.weight_ho) + self.bias_h = mutateFunc(self.bias_h) + self.bias_o = mutateFunc(self.bias_o) + + def tojson(self): + result = "{" + result += "\"input_nodes\" : " + str(self.input_nodes) + result += ", " + result += "\"hidden_nodes\" : " + str(self.hidden_nodes) + result += ", " + result += "\"output_nodes\" : " + str(self.output_nodes) + result += ", " + result += "\"weight_ih\" : " + json.dumps(self.weight_ih.tolist()) + result += ", " + result += "\"weight_ho\" : " + json.dumps(self.weight_ho.tolist()) + result += ", " + result += "\"bias_h\" : " + json.dumps(self.bias_h.tolist()) + result += ", " + result += "\"bias_o\" : " + json.dumps(self.bias_o.tolist()) + result += "}" + + return result + + @staticmethod + def fromjson(jsonTxt) : + obj = json.loads(jsonTxt) + input_nodes = obj["input_nodes"] + hidden_nodes = obj["hidden_nodes"] + output_nodes = obj["output_nodes"] + + weight_ih = array(obj["weight_ih"]) + weight_ho = array(obj["weight_ho"]) + bias_h = array(obj["bias_h"]) + bias_o = array(obj["bias_o"]) + + return NeuralNetwork(input_nodes, hidden_nodes, output_nodes, weight_ih, weight_ho, bias_h, bias_o) + + def __str__(self): + return "(" +str(self.input_nodes) + ", " + str(self.hidden_nodes) + ", " + str(self.output_nodes) + ")" class TrainingData(object): pass \ No newline at end of file diff --git a/player.py b/player.py new file mode 100644 index 00000000..cc476ea6 --- /dev/null +++ b/player.py @@ -0,0 +1,72 @@ +from nn import NeuralNetwork +from numpy import array +from const import * + +class Player: + def __init__(self, brain = None): + self.x = 64 + self.y = int(SCREENHEIGHT / 2) + self.width = 20 + + # player velocity, max velocity, downward accleration, accleration on flap + self.velY = 0 + self.gravity = 0.7 + self.lift = -12 + self.flapped = False + + self.score = 0 + self.fitness = 0 + + if(brain) : + self.brain = brain.copy() + else: + self.brain = NeuralNetwork(6,6,2) + + def think(self, pipes) : + closestIndex = 0 + closestDistance = SCREENWIDTH + for i in range (0,len(pipes)) : + # d1 = pipes[i]["x"] - self.x + d = (pipes[i]["x"] + PIPEWIDTH) - self.x + # d = (d1 + d2) / 2 + if d < closestDistance and d > 0: + closestIndex = i + closestDistance = d + + inputs = [] + inputs.append(float(self.y / SCREENHEIGHT)) + # the y of the lower side of the upper pipe is + # the actual Y of the pipes - the PIPEHEIGHT + inputs.append(float(pipes[closestIndex]["top"] / SCREENHEIGHT)) + inputs.append(float(pipes[closestIndex]["bottom"] /SCREENHEIGHT)) + inputs.append(float(pipes[closestIndex]["x"] / SCREENWIDTH)) + inputs.append(float(self.velY / VELYMAX)) + inputs.append(float((pipes[closestIndex]["x"] + PIPEWIDTH) / SCREENWIDTH)) + # print(inputs) + + h, output = self.brain.predict(array(inputs)) + # print(str(output[0][0]) + " > " +str(output[1][0])) + if output[0][0] > output[1][0]: + self.jump() + + def update(self): + self.score += 1 + + self.velY += self.gravity + self.y += self.velY + + if self.y > BASEY: + self.y = BASEY + self.velY = 0 + + if self.y < 0 : + self.y = 0 + self.velY = 0 + + self.flapped = False + + def jump(self) : + if not self.flapped and self.velY + self.lift > VELYMAX: + self.velY += self.lift + self.flapped = True + \ No newline at end of file From 1303b033b03e6e50758a2a674272d7657119bc0f Mon Sep 17 00:00:00 2001 From: apages Date: Wed, 16 May 2018 15:45:49 +0200 Subject: [PATCH 2/3] update readme file --- README.md | 17 +++++++++++++++++ flappy.py | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dad83b51..5296079c 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,29 @@ FlappyBirdLike and AI Base on this flappy bird like using pygame[1]: https://github.com/sourabhv/FlapPyBird Follow his instruction to be able to run the game. +You need `numpy` and `pygame` to be able to launch flappy.py. (they are both instalable with `pip`) Why --- This is a simple flappybird without any images or welcome screen or what ever. This is made to be able to use AI on it. +I have used a custom and simple neural network. And based the flappy bird on a reinforcement method. + + +How +--- + +command : +`python flappy.py ` + `` specify if you want to launch the game in training mode or not. + ``specify the json file where you want to save the best player training brain. + +example : +`python flappy.py train goodplayer.json` will use the goodplayer.json (or create it if it doesn't exist) to populate players based on the same brain and train this population to provide a better brain. This new brain will be stocked in the same file. + +`python flappy.py notrain goodplayer.json` will use the goodplayer.json (or create it if it doesn't exist) to create a player with the same brain and just play. + ScreenShot ---------- diff --git a/flappy.py b/flappy.py index cf70bb72..1543cb28 100644 --- a/flappy.py +++ b/flappy.py @@ -22,14 +22,14 @@ def main(arg): robotoFont = pygame.font.SysFont("Roboto", 30) brain = None - filename = arg[1] if len(arg) > 1 else "goodplayer.json" + filename = arg[2] if len(arg) > 2 else "goodplayer.json" if os.path.isfile(filename) : with open(filename, 'r') as file : brain = NeuralNetwork.fromjson(file.read()) trainningMode = False # Populate with players - if len(arg) > 2 and arg[2] == "train": + if len(arg) > 1 and arg[1] == "train": trainningMode = True for i in range(0, MAX_POPULATION): p = Player(brain) From 2e3364b24850646ea5ea3c169a18053a1c08b8f5 Mon Sep 17 00:00:00 2001 From: apages Date: Wed, 16 May 2018 18:38:53 +0200 Subject: [PATCH 3/3] adding some command and tips --- README.md | 10 ++++++++++ const.py | 4 ++-- flappy.py | 48 ++++++++++++++++++++++++++---------------------- player.py | 17 +++++++---------- 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 5296079c..f5ea02ff 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,16 @@ example : `python flappy.py notrain goodplayer.json` will use the goodplayer.json (or create it if it doesn't exist) to create a player with the same brain and just play. +Tips : + +You can speeding up or slowing down the game by using the arrow key : +- key up : speed up +- key down : slow down + +By a simple mouse click, you can save your actual runner. + +Enjoy + ScreenShot ---------- diff --git a/const.py b/const.py index abfc3259..27e424dd 100644 --- a/const.py +++ b/const.py @@ -2,8 +2,8 @@ SCREENWIDTH = 512 SCREENHEIGHT = 512 # amount by which base can maximum shift to left -PIPEGAPSIZE = 110 # gap between upper and lower part of pipe -PIPEHEIGHT = 300 +PIPEGAPSIZE = 90 # gap between upper and lower part of pipe +PIPEHEIGHT = 320 PIPEWIDTH = 50 BASEY = SCREENHEIGHT * 0.79 # ground is 21% of the total height BASEX = 0 diff --git a/flappy.py b/flappy.py index 1543cb28..bbb2835b 100644 --- a/flappy.py +++ b/flappy.py @@ -14,12 +14,13 @@ players = [] def main(arg): - global robotoFont, bestScoreEver, SCREEN, players, pipes, speed, previous_score, filename, trainningMode + global robotoFont, bestScoreEver, SCREEN, players, pipes, speed, previous_score, filename, trainningMode, trainningGen pygame.init() FPSCLOCK = pygame.time.Clock() SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT)) pygame.display.set_caption('Flappy Bird') robotoFont = pygame.font.SysFont("Roboto", 30) + trainningGen = 0 brain = None filename = arg[2] if len(arg) > 2 else "goodplayer.json" @@ -73,8 +74,8 @@ def main(arg): # Scores showScore(bestPlayerScore, (10,0)) showScore(bestScoreEver, (10, 25)) - showScore(len(players), (SCREENWIDTH - 40, 0)) - + showScore(len(players), (SCREENWIDTH - 40, 0)) + showScore(trainningGen, (SCREENWIDTH - 40, 25)) pygame.display.update() FPSCLOCK.tick(FPS) @@ -89,19 +90,23 @@ def handleGameEvents() : # SPEED UP if event.type == KEYDOWN and event.key == K_UP: speed += 1 - print("up") + print(speed) # SPEED DOWN elif event.type == KEYDOWN and event.key == K_DOWN: speed -= 1 - print("down") + print(speed) + + if event.type == MOUSEBUTTONUP: + save(players[-1]) + showBestNN(players[- 1]) if speed < 1 : speed = 1 - if speed > 10 : - speed = 10 + if speed > 100 : + speed = 100 def update(): - global previous_score, bestScoreEver, savedPlayers, players, pipes + global previous_score, bestScoreEver, savedPlayers, players, pipes, trainningGen # move pipes to left for pipe in pipes: pipe['x'] += PIPEVELX @@ -136,14 +141,12 @@ def update(): if len(players) == 0: if max(previous_score, bestScoreEver) > previous_score : - json = savedPlayers[-1].brain.tojson() - with open(filename, 'w') as file : - file.write(json) - - showBestNN() + save(savedPlayers[-1]) + showBestNN(savedPlayers[- 1]) previous_score = max(previous_score, bestScoreEver) if trainningMode : + trainningGen += 1 players = ga.nextGeneration(savedPlayers) else : players.append(Player(savedPlayers[-1].brain)) @@ -157,22 +160,24 @@ def update(): def generatePipe(): # y of gap between upper and lower pipe - gapY = random.randrange(0, int(BASEY*0.8 - PIPEGAPSIZE)) - gapY += int(BASEY * 0.2) + gapY = random.randrange(0, int(BASEY - PIPEGAPSIZE)) + # gapY += int(BASEY * 0.2) pipeX = SCREENWIDTH + 10 return {'x': pipeX, 'top': gapY, 'bottom' : gapY + PIPEGAPSIZE} -def showBestNN(): - if len(savedPlayers) == 0 : - return +def save(player) : + json = player.brain.tojson() + with open(filename, 'w') as file : + file.write(json) +def showBestNN(player): print("BEST Input-Hidden") - print(savedPlayers[len(savedPlayers) - 1].brain.weight_ih) + print(player.brain.weight_ih) print("BEST Hidden-Output") - print(savedPlayers[len(savedPlayers) - 1].brain.weight_ho) + print(player.brain.weight_ho) print("Score :") - print(savedPlayers[len(savedPlayers) - 1].score) + print(player.score) def showScore(score, pos): label = robotoFont.render(str(score), 1, (255,255,255)) @@ -183,7 +188,6 @@ def checkCrash(player, pipes): if player.y + player.width >= BASEY - 1 or player.y <= 0: return True else: - playerRect = pygame.Rect(player.x, player.y, player.width, player.width) for pipe in pipes: diff --git a/player.py b/player.py index cc476ea6..bd4c0acf 100644 --- a/player.py +++ b/player.py @@ -33,19 +33,16 @@ def think(self, pipes) : closestIndex = i closestDistance = d + # the important thing in reinforcment : the inputs inputs = [] - inputs.append(float(self.y / SCREENHEIGHT)) - # the y of the lower side of the upper pipe is - # the actual Y of the pipes - the PIPEHEIGHT - inputs.append(float(pipes[closestIndex]["top"] / SCREENHEIGHT)) - inputs.append(float(pipes[closestIndex]["bottom"] /SCREENHEIGHT)) - inputs.append(float(pipes[closestIndex]["x"] / SCREENWIDTH)) - inputs.append(float(self.velY / VELYMAX)) - inputs.append(float((pipes[closestIndex]["x"] + PIPEWIDTH) / SCREENWIDTH)) - # print(inputs) + inputs.append(float(self.y / SCREENHEIGHT)) # the position of the bird + inputs.append(float(pipes[closestIndex]["top"] / SCREENHEIGHT)) # the position of the top pipe (the lower Y of the top pipe) + inputs.append(float(pipes[closestIndex]["bottom"] /SCREENHEIGHT)) # the position of the bottom pipe (the higher Y of the bottom pipe) + inputs.append(float(pipes[closestIndex]["x"] / SCREENWIDTH)) # the X position of the closest pipe + inputs.append(float((pipes[closestIndex]["x"] + PIPEWIDTH) / SCREENWIDTH)) # the X position of the closest pipe INCLUDING the width of the pipe + inputs.append(float(self.velY / VELYMAX)) # the current player velocity h, output = self.brain.predict(array(inputs)) - # print(str(output[0][0]) + " > " +str(output[1][0])) if output[0][0] > output[1][0]: self.jump()