During coding, I found that I was unable to have a ship fire a ‘bullet’ using NEAT, and even worse, I found that the bullet class required several things that I have no idea how to implement.
main.py:
import pygame
import math
import sys
import neat
pygame.init()
# Define Constants
SCALE = 1/2
SCREEN_WIDTH = 1634*2
SCREEN_HEIGHT = 842*2
SCALED_SCREEN = pygame.display.set_mode(
(SCREEN_WIDTH * SCALE, SCREEN_HEIGHT * SCALE))
SCREEN = pygame.surface.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
TEXT_SCREEN = pygame.surface.Surface((SCREEN_WIDTH, SCREEN_HEIGHT),pygame.SRCALPHA, 32)
FONT = pygame.font.Font('img/FreeMono.ttf', 25)
DRAWING = SCREEN.copy()
show_debug = True
pressed = True
pygame.display.set_caption('Naval Simulation')
BULLET_SPEED = 500
BULLET_LIFETIME = 1000
BULLET_RATE = 150
class Bullet(pygame.sprite.Sprite):
def __init__(self, game, pos, dir):
super().__init__()
self.groups = all_sprites
pygame.sprite.Sprite.__init__(self, self.groups)
self.image = pygame.image.load('img/bullet.png')
self.pos = pos
self.rect = self.image.get_rect(center = (590, 670))
self.vel_vector = pygame.math.Vector2(5, 0)
self.spawn_time = pygame.time.get_ticks()
def update(self):
self.pos += self.vel * (pygame.time.Clock(FPS) / 1000)
self.rect.center = self.pos
if pygame.time.get_ticks() - self.spawn_time > BULLET_LIFETIME:
self.kill()
class Ship(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.original_image = pygame.image.load('img/Fletcher-class DD.png')
self.original_image = pygame.transform.rotozoom(self.original_image, 0, 0.1)
self.image = self.original_image
self.rect = self.image.get_rect(center=(590, 670))
self.vel_vector = pygame.math.Vector2(0.8, 0)
self.angle = 0
self.roatation_vel = 5
self.direction = 0
self.alive = True
self.radars = []
self.last_shot = 0
def update(self):
self.radars.clear()
self.drive()
self.rotate()
for radar_angle in (-60, -30, 0, 30, 60):
self.radar(radar_angle)
self.collision()
self.data()
def drive(self):
self.rect.center += self.vel_vector * 3
def rotate(self):
if self.direction == 1:
self.angle -= self.roatation_vel
self.vel_vector.rotate_ip(self.roatation_vel)
elif self.direction == -1:
self.angle += self.roatation_vel
self.vel_vector.rotate_ip(-self.roatation_vel)
self.image = pygame.transform.rotate(self.original_image,self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
def radar(self, radar_angle):
length = 0
x = int(self.rect.center[0])
y = int(self.rect.center[1])
try:
while not SCREEN.get_at((x, y)) == pygame.Color(2, 105, 31, 255) and length < 200:
length += 1
x = int(self.rect.center[0] +
math.cos(math.radians(self.angle + radar_angle)) * length)
y = int(self.rect.center[1] -
math.sin(math.radians(self.angle + radar_angle)) * length)
except IndexError:
pass
if show_debug:
pygame.draw.line(SCREEN, (225, 225, 225, 225), self.rect.center,
(x, y), 1)
pygame.draw.circle(SCREEN, (0, 225, 0, 0), (x, y), 3)
dist = int(
math.sqrt(
math.pow(self.rect.center[0] - x, 2) +
math.pow(self.rect.center[1] - y, 2)))
self.radars.append([radar_angle, dist])
def collision(self):
length = 40
collision_point_right = [
int(self.rect.center[0] +
math.cos(math.radians(self.angle + 18)) * length),
int(self.rect.center[1] -
math.sin(math.radians(self.angle + 18)) * length)
]
collision_point_left = [
int(self.rect.center[0] +
math.cos(math.radians(self.angle - 18)) * length),
int(self.rect.center[1] -
math.sin(math.radians(self.angle - 18)) * length)
]
try:
coll_right = SCREEN.get_at((collision_point_right))
except IndexError:
coll_right = pygame.Color(2, 105, 31,255)
try:
coll_left = SCREEN.get_at((collision_point_left))
except IndexError:
coll_left = pygame.Color(2, 105, 31,255)
if coll_right == pygame.Color(2, 105, 31,255) or coll_left == pygame.Color(2, 105, 31, 255):
self.alive = False
if show_debug:
pygame.draw.circle(SCREEN, (0, 255, 255, 0), collision_point_right, 4)
pygame.draw.circle(SCREEN, (0, 255, 255, 0), collision_point_left, 4)
pygame.draw.circle(SCREEN, (0, 0, 0, 0), self.rect.center, 400, width=5)
def data(self):
input = [0, 0, 0, 0, 0]
for i, radar in enumerate(self.radars):
input[i] = int(radar[1])
return input
def remove(index):
ships.pop(index)
ge.pop(index)
nets.pop(index)
def eval_genomes(genomes, config):
global ships, ge, nets, bullets, show_debug, pressed, SCALED_SCREEN
ships = []
ge = []
nets = []
bullets = []
for genome_id, genome in genomes:
ships.append(pygame.sprite.GroupSingle(Ship()))
ge.append(genome)
net = neat.nn.FeedForwardNetwork.create(genome, config)
nets.append(net)
bullets.append(pygame.sprite.GroupSingle(Bullet()))
genome.fitness = 0
run = True
while run:
text1 = FONT.render("Training...", True, (225, 225, 225))
text2 = FONT.render(f"Generation: {pop.generation+1}", True, (225, 225, 225))
text3 = FONT.render(f"Toggle Debug lines with \"H\"", True, (225, 225, 225))
TEXT_SCREEN.fill((0,0,0,0))
TEXT_SCREEN.blit(text1, (5,0))
TEXT_SCREEN.blit(text2, (5,25))
TEXT_SCREEN.blit(text3, (5,50))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(1)
keys = pygame.key.get_pressed()
if not pressed:
if keys[pygame.K_h]:
show_debug = not show_debug
pressed = True
else:
if not keys[pygame.K_h]:
pressed = False
SCREEN.blit(DRAWING, (0, 0))
if len(ships) == 0: break
for i, ship in enumerate(ships):
ge[i].fitness += 1
if not ship.sprite.alive:
remove(i)
for i, ship in enumerate(ships):
output = nets[i].activate(ship.sprite.data())
if output[0] > 0.7:
ship.sprite.direction = 1
if output[1] > 0.7:
ship.sprite.direction = -1
if output[0] <= 0.7 and output[1] <= 0.7:
ship.sprite.direction = 0
# Update ship
for ship in ships:
ship.draw(SCREEN)
ship.update()
render()
def render():
SCALED_SCREEN.blit(pygame.transform.scale(SCREEN,SCALED_SCREEN.get_rect().size), (0, 0))
SCALED_SCREEN.blit(pygame.transform.scale(TEXT_SCREEN,SCALED_SCREEN.get_rect().size), (0, 0))
pygame.display.update()
def draw():
global DRAWING, SCALED_SCREEN
SCREEN.fill((24, 62, 122))
TEXT_SCREEN.fill((0,0,0,0))
pygame.draw.circle(SCREEN, (24, 62, 122), (590, 670), 100)
text1 = FONT.render("", True, (225, 225, 225))
text2 = FONT.render("Press \"C\" to continue.", True, (225, 225, 225))
TEXT_SCREEN.blit(text1, (5,0))
TEXT_SCREEN.blit(text2, (5,25))
while True:
m_pressed = pygame.mouse.get_pressed()[0]
m_pos = list(pygame.mouse.get_pos())
m_pos[0] /= SCALE
m_pos[1] /= SCALE
if m_pressed:
pygame.draw.circle(SCREEN, (24, 62, 122), m_pos, 60)
keys = pygame.key.get_pressed()
if keys[pygame.K_c]:
DRAWING = SCREEN.copy()
TEXT_SCREEN.fill((0,0,0,0))
return
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(1)
render()
def run(config_path):
global pop
config = neat.config.Config(
neat.DefaultGenome,
neat.DefaultReproduction,
neat.DefaultSpeciesSet,
neat.DefaultStagnation,
config_path
)
pop = neat.Population(config)
pop.run(eval_genomes, 50)
if __name__ == '__main__':
draw()
run('config.txt')
config.txt:
[NEAT]
fitness_criterion = max
fitness_threshold = 10000
pop_size = 15
reset_on_extinction = False
[DefaultGenome]
node activation options
activation_default = tanh
activation_mutate_rate = 0.0
activation_options = tanh
node aggregation options
aggregation_default = sum
aggregation_mutate_rate = 0.0
aggregation_options = sum
node bias options
bias_init_mean = 0.0
bias_init_stdev = 1.0
bias_max_value = 30.0
bias_min_value = -30.0
bias_mutate_power = 0.5
bias_mutate_rate = 0.7
bias_replace_rate = 0.1
genome compatibility options
compatibility_disjoint_coefficient = 1.0
compatibility_weight_coefficient = 0.5
connection add/remove rates
conn_add_prob = 0.5
conn_delete_prob = 0.5
connection enable options
enabled_default = True
enabled_mutate_rate = 0.01
feed_forward = True
initial_connection = full
node add/remove rates
node_add_prob = 0.2
node_delete_prob = 0.2
network parameters
num_hidden = 0
num_inputs = 5
num_outputs = 2
node response options
response_init_mean = 1.0
response_init_stdev = 0.0
response_max_value = 30.0
response_min_value = -30.0
response_mutate_power = 0.0
response_mutate_rate = 0.0
response_replace_rate = 0.0
connection weight options
weight_init_mean = 0.0
weight_init_stdev = 1.0
weight_max_value = 30
weight_min_value = -30
weight_mutate_power = 0.5
weight_mutate_rate = 0.8
weight_replace_rate = 0.1
[DefaultSpeciesSet]
compatibility_threshold = 3.0
[DefaultStagnation]
species_fitness_func = max
max_stagnation = 20
species_elitism = 2
[DefaultReproduction]
elitism = 2
survival_threshold = 0.2
When I tried to run my code, this error message popped up: ``` bullets.append(pygame.sprite.GroupSingle(Bullet())) TypeError: Bullet.__init__() missing 3 required positional arguments: 'game', 'pos', and 'dir' ```
How do I resolve this issue? Before this, I tried to automate it by manually pressing a key to fire the bullet, but that ended up breaking the program as the simulation paused the moment I pressed it.