NEAT AI not controlling individual genomes in population - python

I am trying to implement NEAT into a pong game (made with pygame), however the individual paddles (geeks is how they're called here) don't seem to behave the way they should.
I have a population size of 5, with 5 input nodes and 3 output nodes.
Since the balls velocity is constant and so is its angle the only input nodes i have are: balls y/x position, balls y/x direction and the geeks y position.
The outputs are either the geek going up, down or being stationary.
I use only a single ball and everytime the ball gets to the part, where it would hit the geek to shoot it back, instead of using something like colliderect, i check if the ball is outside the Y range covered by the geek. It should get removed from the population of the current gen.
Terminating the geeks from the population seems to be the problem. It looks like i am having issues addressing each geek individually. I checked this by having a geek that is technically being terminated from the generation turn white, replacing their inital green color, but they all turn white but not all get removed. Infact, the first miss 3 will get removed from the population, then 1 more and then 1 more before all are terminated and the next generation starts. This happens everytime
Furthermore, the expected random behavior in earlier gens never occurs. It will always do the same weird movements. They wont split up or anything, they all stay in the same spot.
I tried splitting up the for loops handling the movement, rendering and my pseudo-collision detection but they still seem to all behave as one entity. I am unsure what to do.
This is the link to the full project and config file: https://www.mediafire.com/folder/baqsg6ddv5cv6/Pong
Side Note: I got the initial pong game from geeksforgeeks.org
Snippet of my code handling NEAT:
def remove(index):
AIgeeks.pop(index)
ge.pop(index)
nets.pop(index)
# Game Manager
def eval_genomes(genomes, config):
running = True
global geekAIYFac
geekAIYFac = 0 #sets initial movement of future geeks controlled by NEAT to 0
# Defining the objects
geek1 = Striker(20, 0, 10, 100, 10, GREEN) #purely visual opponent, will lock onto Y of the ball
geekAI = Striker(WIDTH-30, 0, 10, 50, 7, GREEN) #is the object that is the paddles (here they're called geeks) that will be controlled by NEAT
ball = Ball(WIDTH//2, HEIGHT//2, 7, 7, WHITE)
global AIgeeks, nets, ge, previous_time, current_time
AIgeeks = []
nets = []
ge = []
# Initial parameters of the players
for genome_id, genome in genomes: #basic setup to fill AIgeeks array and assign networks
AIgeeks.append(geekAI)
ge.append(genome)
net = neat.nn.FeedForwardNetwork.create(genome, config)
nets.append(net)
genome.fitness = 0
while running:
screen.fill(BLACK)
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
quit()
if len(AIgeeks) == 0: #checks if the list of geeks is empty -> breaks, starts new generation
previous_time = pygame.time.get_ticks() #gets time at the end of generation so it can be subtracted from the total time for fitness evaluation later
break
for i, geekAI in enumerate(AIgeeks):
output = nets[i].activate((ball.posy, ball.posx, geekAI.posy, ball.yFac, ball.xFac)) #takes in the input nodes: geeks y position, balls position (x and y), if ball going left/right or up/down (that's the yFac/xFac, speed is constant, that's why no input node for that needed )
if output[0] > 0.5: #defines first output nodes: output 0 makes geek go up, output 1 makes geek go down, output 2 makes geek stationary
geekAIYFac = -1
if output[1] > 0.5:
geekAIYFac = 1
if output[2] > 0.5:
geekAIYFac = 0
fitness_time = (pygame.time.get_ticks() - previous_time) / 50 #sets the fitness for each geek to the current time subtracted by the time when the generation started, divided by 50 for smaller numbers
ge[i].fitness = fitness_time
for i, geekAI in enumerate(AIgeeks):
if ball.posx == WIDTH - 30: #checks if ball is on same X as geeks
if geekAI.posy >= ball.posy or geekAI.posy <= ball.posy - geekAI.height: #checks if the ball is outside the Y range covered by each geek, if so ball wouldn't be hit in a real game -> geek gets terminated for this generation
geekAI.color = WHITE #sets colour to white before technically removing object
remove(i)
geekAI.update(geekAIYFac) #renders every geek on screen from AIgeeks list
geekAI.display()
for geek in AIgeeks:
print(geek.color)

Related

Improving minimax algorithm for Tic Tac Toe

I have coded a minimax algorithm with alpha-beta pruning for a 4x4 Tic Tac Toe, in which the players who gets 3 marks in a row wins. However, it seems that the first to move wins in this game, such that minimax doesn't even try to make it harder for the player to win, because no other possible move can improve the outcome (since minimax assumes optimal play from both sides). Therefore, I added the condition that the algorithm will choose the best move that will also maximize the game length (while assuming that the other player will try to win with least moves), and I tried to do that by adding 2 more "alpha" and "beta" variables, but for the game length.
My game has worked fine without this new condition, but when I add it, it doesn't work properly, and the algorithm will not avoid losing, which minimax should have prevented. Below is the part of the code which solves for the optimal move.
Note that I used 1 and -1 to represent the players, that is, o = 1 and x = -1, and they are themselves the score of their win. A draw returns 0.
def optimalmove(board, player, alpha, beta,optlength,movementlength,worlength):
stator = checkstate4(board) # Contains the state of the game. stator[0] is whether the game has reached the end or not,
# stator[1] is who has won (or draw), stator[2] are the possible moves
if len(stator[2]) == boardsize ** 2:
# if it is the first move, pick the corner as it has already been calculated
# to be the optimal, in order to reduce time
return([0, [0, 0],movementlength])
if stator[0]: #If at the end return which one has won
return([stator[1], [],movementlength])
movement = [] #best movement
for move in stator[2]: #loops through each possible move
localboard = deepcopy(board) #creates a copy of the board to not change the original
localboard[move[0]][move[1]] = player
quality = optimalmove(localboard, player * (-1), alpha, beta,optlength,movementlength+1,worlength)
#since it made a move, increase the move list length by one
if player == o: #'O' maximizes the score and wants to reduce the length of the game
if (quality[0] > alpha) or ((quality[0] >= alpha) and (quality[2] < worlength)):
# If it finds a better move, so be it. But if it finds a move as good as the previously best seen,
# take the one that will reach the end the fastest
alpha = quality[0]
movement = move
worlength = quality[2] #set the shortest game length seen to the current one
elif player == x: #'X' minimizes the score and wants to increase the length of the game
if (quality[0] < beta) or ((quality[0] <= beta) and (quality[2] > optlength)):
# If it finds a better move, so be it. But if it finds a move as good as the previously best seen,
# take the one that will reach the end the longest
beta = quality[0]
movement = move
optlength = quality[2] #set the longest game length seen to the current one
if alpha >= beta:
# prunes
break
if player == o:
return ([alpha, movement,worlength])
else:
return ([beta, movement,optlength])

Will pygame.draw shapes still remain within the game if size/radius is 0 and stopped pygame.draw or screen.blit (showing on screen)

I have a question regarding FPS and memory, do any of you know if I use list and append pygame.squares/circles/other shapes, if radius or size minimized and became 0 and remove from the current list, stopped pygame.draw function after, but it will still stay in the game right??
So if I'm creating something like fire smoke effect (let's say create radius 12 pygame.draw.circles, append in a list, minimize, remove from list if radius <= 0 and stopped showing on screen), will the removed ones stored alot and slow down the whole speed and takes memory?? Do any of you know if theres any solution to this problem or minimize it?? Thank you, this will help me alot
def createparticle(self):
# make particle settings - original and fade size and add inside a list
self.mouseposition = pygame.mouse.get_pos()
self.x = pygame.mouse.get_pos[0]
self.y = pygame.mouse.get_pos[1]
self.fade_x = 1
self.fade_y = 1
self.radius = 12
self.ball = [[self.x, self.y], self.radius, [self.fade_x, self.fade_y]]
self.particles.append(self.ball)
def emit(self):
# run loop, blit and move particle
for p in self.particles:
p[0][1] += p[2][1]
p[0][0] += p[2][0]
p[1] -= 0.2
pygame.draw.circle(self.screen, pygame.Color('orange'), p[0], int(p[1]))
def deleteparticle(self):
# remove particle if size is smaller than 0
self.particles_copy = [p for p in self.particles if p[1] > 0]
self.particles = self.particles_copy
If they are removed from the list (and that is the only place references are stored of that object) they should get garbage collected and removed from the memory, draw doesn't create any game objects that stay in the game that way. It just simply changes some pixels on the screen and that is it (it does return
pygame.Rect but that can be ignored or if you store those in a list removing them will free the memory), those functions don't create any objects that just stay in the game as some kind of special object, that would be pretty poor design.

How can I make a number randomly appear a certain percent of the time - repeatedly?

I am making a game where I want fluctuating damage. There are two enemies. For enemy one, they lose a constant amount of health (5 points). For enemy two, there is a chance that the player will inflict a small amount of damage 80% of the time (3 points) and a large amount of damage 20% of the time (10 points).
I tried using choice from a group of numbers, but I found that it will choose one of these random numbers (3 for example) and will stick with it until the game restarts. I want the damage number to change each time the player attacks so that it looks more like "-3, -3, -3, -10, -3" for example.
In the main loop I have:
hits = pg.sprite.groupcollide(self.enemy1s, self.shards, False, True)
for hit in hits:
hit.health -= SHARD_DAMAGE1
hit.vel = vec(0, 0)
hits = pg.sprite.groupcollide(self.enemy2s, self.shards, False, True)
for hit in hits:
hit.health -= SHARD_DAMAGE1
hit.vel = vec(0, 0)
The SHARD_DAMAGE is defined separately in another file as:
CONSTANT = [5, 5, 5, 5, 5]
RAND = [3, 3, 3, 3, 10]
SHARD_DAMAGE1 = choice(CONSTANT)
SHARD_DAMAGE2 = choice(RAND)
At the very top of both files I have: from random import uniform, choice.
Like I said, the game will lock onto 3 or 10 and make that the damage until the entire thing is restarted. Even if I were to have 5 random numbers, it only picks one until I restart.
This happens because when a variable gets assigned a random number, the program is only referring to this stored value that has been picked once. When a choice is made, like here:
SHARD_DAMAGE1 = choice(CONSTANT)
The value stored in SHARD_DAMAGE1 will never get reassigned because SHARD_DAMAGE1 = choice(CONSTANT) is read only once at the beginning.
As suggested in a comment, you would need to make a random choice every time a hit is needed like so:
hit.health -= choice(CONSTANT)
Proceeding this way, a new calculation will be performed each time a hit will be given.
I don't think there's any need to hold a list of pre-defined numbers.
Write a small function that takes into account the type of the enemy. Then for the "other" type, depend the damage calculation around your 20% / 80% split:
def getDamage( enemy_type ):
if ( enemy_type == enemy.TYPE_ONE ):
damage = DAMAGE_NORMAL #5
elif ( enemy_type == enemy.TYPE_TWO ):
if ( random.randint( 1, 100 ) > 80 ): # Critical hit?
damage = DAMAGE_LARGE #10
else:
damage = DAMAGE_SMALL #3
else:
# random damage
damage = random.randint( 1, 5 )
return damage

Pygame Raycasting for line of sight

I am making a 2d top-down shooter game and ideally I would like the enemies to only shoot at the player when they see him/her (so the player could hide behind a crate etc.)
I have done research and I think the best way to do this would be raycasting. I have not been able to find a good example of raycasting in pygame.
Alternatively, I saw this piece of code on a different stackoverflow question ( Pygame Line of Sight from Fixed Position )
def isInLine(player, person):
deltaX = person[0] - player[0]
deltaY = person[1] - player[1]
if (person[0] == player[0]) or (person[1] == player[1]) or (abs(deltaX) == abs(deltaY)):
return true
but I am not sure if it would accomplsih the kind of thing I want to and if it is I'm not sure how I would implement it.
What I am asking is firstly, would the code I am using accomplish what I wanted to do and if so how would I implement it and is there a better way to do it.
I am assuming the variables 'player' and 'person' are the positions of the player and enemy? If so, the code you have added will check if either the two objects:
are in the same x position (person[0] == player[0])
are in the same y position (person[1] == player[1])
have equal x and y differences, i.e. the objects are at 45 degrees to each other ( abs(deltaX) == abs(deltaY) ).
This doesn't seem like what you want, however.
What might work is if you check if :
the angle between the enemy and barrier is equal to the angle between the enemy and the player. One way to do that is to use tan(angle) = opposite / adjacent, or deltaY / deltaX.
the enemy is further from the player than from the barricade. This can be done using pythagoras.
Here is a function for this which might help:
import math
def isInLine(enemy_pos, player_pos, barrier_pos):
# get x and y displacements from enemy to objects
playerDX = player_pos[0] - enemy_pos[0]
playerDY = player_pos[1] - enemy_pos[1]
barrierDX = barrier_pos[0] - enemy_pos[0]
barrierDY = barrier_pos[1] - enemy_pos[1]
# have to convert to degrees, as math uses radians
playerAngle = math.degrees( math.atan(playerDY / playerDX) )
barrierAngle = math.degrees( math.atan(barrerDY / barrierDX) )
# use pythagoras to find distances
playerDist = math.sqrt( (playerDX)**2 + (playerDY)**2 )
barrierDist = math.sqrt( (barrierDX)**2 + (barrierDY)**2 )
return (playerAngle == barrierAngle) and (playerDist > barrierDist)
So if the angles of the player and barrier from the enemy are equal, that are along the same line. If the enemy is also further from the player than from the barricade, the player is behind the barricade compared to the enemy.
EDIT: Actually this will only work if the line from the enemy to the barrier is exactly equal to the line from the enemy to the player. This might need editing to take into account the range of the barrier.

Python rectangle collision handling with pygame

I've been doing extensive research on this topic for the past few days and I can't seem to find an answer for my exact problem.
So, I have a simple game set up where I have the player at 0, 0 with a width of 10x10
player= pygame.Rect(0, 0, 10, 10)
and aside from that, the player has a velocity of x: 0, y: 10, which will make him fall (y is positive because the origin of the screen is at the top left.)
and I have a tile at 0, 100, as shown:
dirt= pygame.Rect(0, 100, 10, 10)
so, how can I handle collision, I already know I can detect it with Rect.colliderect(Rect).
I've tried a few ways, but encountered some problems:
I can't cut the player's velocity to 0 when he hits something and then move him
back until he's just touching the object because that still causes the problem of walking, when he walks, I apply +10 velocity on x, but unfortunately, the game still processes that he is falling and colliding and moving sideways, so it just moves him back to where he started.
I'm a beginner, so a simple answer would be appreciated, and I would like to not have to use any third party modules other that pygame if I didn't have to.
Update:
Here is some of the rough test code I have tried:
def sim(obj, time, world):
time= time / 1000
obj.physProp['vel']= (obj.physProp['vel'][0] + (accel[0] * time), obj.physProp['vel'][1] + (accel[1] * time))
if obj.physProp['vel'][1] > terminalY:
obj.physProp['vel']= (obj.physProp['vel'][0], terminalY)
obj.pos= (obj.pos[0] + (obj.physProp['vel'][0] * time) + ((accel[0] / 2) * (time ** 2)), obj.pos[1] + (obj.physProp['vel'][1] * time) + ((accel[1] / 2) * (time ** 2)))
for ID in world:
if obj.getRect().colliderect(world[ID].getRect()) == True:
pass
return (obj.pos, obj.physProp['vel'])
Split up x/y movement.
Move x, check if colliding, if so, move back and set xspeed to 0.
Move y, check if colliding, if so, move back and set yspeed to 0.
It does mean two collisions checks per step but it's super smooth. :)
The Pygame API invites you to write all your game subjects in an Object oriented way - so that your falling character will have all the "methods" and "attributes" to properly respond to things on the scenario - like hitting something.
So, if your character is defined for something as simple as:
class Char(object):
# these start as class attributes,
# but whenever they are assigned to with a "self.var = bla" in
# a method, an instance attribute starts existing
x, y = 0,0
vx, vy = 0,0
def update(self):
self.x += self.vx
self.y += self.vy
And your external code, upon detecting a collision, could do just this:
def mainloop():
while True:
...
obj.update()
if obj.getRect().colliderect(world[ID].getRect()): # don't do "== True" in `if's - it is just silly
# take character back to the precious position
obj.x -= obj.vx
obj.y -= obj.vy
# zero out velocities to make it stop:
obj.vx = obj.vy = 0
And so on - you will soon perceive thinking of your game "things" as "objects" as they are used in programing make the code flows quite naturally - as soon as you get the way this works, look at Pygame's sprite module - which allows you to automate a lot of checks, and updates without having to explicitly write for loops for each check

Categories