refactor(ai)

This commit is contained in:
Kristofers Solo 2024-01-07 04:29:01 +02:00
parent c8859bc571
commit 77cd153b10
9 changed files with 82 additions and 41 deletions

8
config
View File

@ -1,7 +1,7 @@
[NEAT]
fitness_criterion = max
fitness_threshold = 0
pop_size = 100
fitness_threshold = 500
pop_size = 50
reset_on_extinction = False
[DefaultGenome]
@ -44,8 +44,8 @@ node_add_prob = 0.2
node_delete_prob = 0.2
# network parameters
num_hidden = 3
num_inputs = 216
num_hidden = 1
num_inputs = 200
num_outputs = 6
# node response options

View File

@ -61,14 +61,13 @@ def main(args: argparse.ArgumentParser) -> None:
elif args.verbose:
CONFIG.log_level = "info"
# import ai
import ai
import game
if args.train is not None:
# ai.log.debug("Training the AI")
# # ai.train(*args.train)
ai.log.debug("Training the AI")
ai.train(*args.train)
# game.Menu(GameMode.AI_TRAINING).run()
pass
else:
game.log.debug("Running the game")
game.Main(GameMode.PLAYER).run()

View File

@ -6,55 +6,59 @@ import numpy as np
import pygame
from game import Main
from game.sprites import Block
from utils import CONFIG
from utils import CONFIG, GameMode
from .fitness import calculate_fitness
# from .fitness import calculate_fitness
from .log import log
from .moves import calculate_fitness
def eval_genome(genome: neat.DefaultGenome, config: neat.Config) -> float:
app = Main()
app = Main(GameMode.AI_TRAINING).play()
app.mute()
game = app.game
tetris = game.tetris
net = neat.nn.FeedForwardNetwork.create(genome, config)
genome.fitness = 0
while not game.game_over:
current_figure: list[int] = [
component
for block in game.tetromino.blocks
for component in (int(block.pos.x), int(block.pos.y))
]
while not tetris.game_over:
# current_figure: list[int] = [
# component
# for block in tetris.tetromino.blocks
# for component in (int(block.pos.x), int(block.pos.y))
# ]
next_figure: list[int] = [
vec
for vec in app.next_figures[0].value.shape
for vec in (int(vec.x), int(vec.y))
]
# next_figure: list[int] = [
# vec
# for vec in game.next_figure.value.shape
# for vec in (int(vec.x), int(vec.y))
# ]
field = np.where(game.field != None, 1, 0)
field = np.where(tetris.field != None, 1, 0)
output = net.activate((*next_figure, *current_figure, *field.flatten()))
for block in tetris.tetromino.blocks:
field[int(block.pos.y), int(block.pos.x)] = 2
output = net.activate(field.flatten())
decision = output.index(max(output))
decisions = {
0: game.move_left,
1: game.move_right,
2: game.move_down,
3: game.rotate,
4: game.rotate_reverse,
5: game.drop,
0: tetris.move_left,
1: tetris.move_right,
2: tetris.move_down,
3: tetris.rotate,
4: tetris.rotate_reverse,
5: tetris.drop,
}
decisions[decision]()
app.run_game_loop()
genome.fitness = calculate_fitness(field)
score, lines, level = app.game.score, app.game.lines, app.game.level
score, lines, level = tetris.score, tetris.lines, tetris.level
log.debug(f"{genome.fitness=:<+6.6}\t{score=:<6} {lines=:<6} {level=:<6}")
game.restart()
tetris.restart()
return genome.fitness

3
src/ai/moves/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .calculate import calculate_fitness
__all__ = ["calculate_fitness"]

35
src/ai/moves/calculate.py Normal file
View File

@ -0,0 +1,35 @@
from typing import Optional
import numpy as np
from ai.log import log
from .bumpiness import bumpiness
from .height import aggregate_height
from .holes import holes
from .lines import complete_lines
def calculate_fitness(field: np.ndarray) -> float:
"""
Calculate the fitness value for the given field.
Args:
field: The game field.
Returns:
The fitness value.
"""
height_w = aggregate_height(field)
holes_w = holes(field)
bumpiness_w = bumpiness(field)
lines_w = complete_lines(field)
fitness = (
-0.510066 * height_w
+ 0.760666 * lines_w
- 0.35663 * holes_w
- 0.184483 * bumpiness_w
)
return fitness

View File

@ -23,7 +23,7 @@ class Game(BaseScreen):
game: Game object.
score: Score object.
preview: Preview object.
next_figures: List of upcoming figures.
next_figure: List of upcoming figures.
music: Music that plays in the background.
"""

View File

@ -13,7 +13,7 @@ from .game import Game
class Main(BaseScreen, SceenElement, TextScreen):
def __init__(self, mode: GameMode) -> None:
log.info("Initializing the game")
# log.info("Initializing the game")
self._initialize_pygame()
self._initialize_surface()
self._initialize_rect()
@ -61,7 +61,7 @@ class Main(BaseScreen, SceenElement, TextScreen):
def exit(self) -> None:
"""Exit the game."""
log.info("Exiting the game")
# log.info("Exiting the game")
pygame.quit()
sys.exit()

View File

@ -150,8 +150,8 @@ class Tetris(BaseScreen):
self._check_finished_rows()
self.game_over: bool = self._check_game_over()
if self.game_over:
self.restart()
# if self.game_over:
# self.restart()
self.tetromino = Tetromino(
self.sprites,
@ -171,13 +171,13 @@ class Tetris(BaseScreen):
"""
for block in self.tetromino.blocks:
if block.pos.y <= 0:
log.info("Game over!")
# log.info("Game over!")
return True
return False
def restart(self) -> None:
"""Restart the game."""
log.info("Restarting the game")
# log.info("Restarting the game")
self._reset_game_state()
self._initialize_field_and_tetromino()
self.game_over = False

View File

@ -21,7 +21,7 @@ class Game:
size: Size = Size(columns * cell.width, rows * cell.width)
pos: Vec2 = Vec2(padding, padding)
offset: Vec2 = Vec2(columns // 2, -1)
initial_speed: float | int = 300
initial_speed: float | int = 100
movment_delay: int = 150
rotation_delay: int = 200
drop_delay: int = 200