mirror of
https://github.com/kristoferssolo/Tetris.git
synced 2025-10-21 20:00:35 +00:00
refactor(ai)
This commit is contained in:
parent
c8859bc571
commit
77cd153b10
8
config
8
config
@ -1,7 +1,7 @@
|
|||||||
[NEAT]
|
[NEAT]
|
||||||
fitness_criterion = max
|
fitness_criterion = max
|
||||||
fitness_threshold = 0
|
fitness_threshold = 500
|
||||||
pop_size = 100
|
pop_size = 50
|
||||||
reset_on_extinction = False
|
reset_on_extinction = False
|
||||||
|
|
||||||
[DefaultGenome]
|
[DefaultGenome]
|
||||||
@ -44,8 +44,8 @@ node_add_prob = 0.2
|
|||||||
node_delete_prob = 0.2
|
node_delete_prob = 0.2
|
||||||
|
|
||||||
# network parameters
|
# network parameters
|
||||||
num_hidden = 3
|
num_hidden = 1
|
||||||
num_inputs = 216
|
num_inputs = 200
|
||||||
num_outputs = 6
|
num_outputs = 6
|
||||||
|
|
||||||
# node response options
|
# node response options
|
||||||
|
|||||||
7
main.py
7
main.py
@ -61,14 +61,13 @@ def main(args: argparse.ArgumentParser) -> None:
|
|||||||
elif args.verbose:
|
elif args.verbose:
|
||||||
CONFIG.log_level = "info"
|
CONFIG.log_level = "info"
|
||||||
|
|
||||||
# import ai
|
import ai
|
||||||
import game
|
import game
|
||||||
|
|
||||||
if args.train is not None:
|
if args.train is not None:
|
||||||
# ai.log.debug("Training the AI")
|
ai.log.debug("Training the AI")
|
||||||
# # ai.train(*args.train)
|
ai.train(*args.train)
|
||||||
# game.Menu(GameMode.AI_TRAINING).run()
|
# game.Menu(GameMode.AI_TRAINING).run()
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
game.log.debug("Running the game")
|
game.log.debug("Running the game")
|
||||||
game.Main(GameMode.PLAYER).run()
|
game.Main(GameMode.PLAYER).run()
|
||||||
|
|||||||
@ -6,55 +6,59 @@ import numpy as np
|
|||||||
import pygame
|
import pygame
|
||||||
from game import Main
|
from game import Main
|
||||||
from game.sprites import Block
|
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 .log import log
|
||||||
|
from .moves import calculate_fitness
|
||||||
|
|
||||||
|
|
||||||
def eval_genome(genome: neat.DefaultGenome, config: neat.Config) -> float:
|
def eval_genome(genome: neat.DefaultGenome, config: neat.Config) -> float:
|
||||||
app = Main()
|
app = Main(GameMode.AI_TRAINING).play()
|
||||||
|
|
||||||
app.mute()
|
|
||||||
game = app.game
|
game = app.game
|
||||||
|
tetris = game.tetris
|
||||||
net = neat.nn.FeedForwardNetwork.create(genome, config)
|
net = neat.nn.FeedForwardNetwork.create(genome, config)
|
||||||
genome.fitness = 0
|
genome.fitness = 0
|
||||||
|
|
||||||
while not game.game_over:
|
while not tetris.game_over:
|
||||||
current_figure: list[int] = [
|
# current_figure: list[int] = [
|
||||||
component
|
# component
|
||||||
for block in game.tetromino.blocks
|
# for block in tetris.tetromino.blocks
|
||||||
for component in (int(block.pos.x), int(block.pos.y))
|
# for component in (int(block.pos.x), int(block.pos.y))
|
||||||
]
|
# ]
|
||||||
|
|
||||||
next_figure: list[int] = [
|
# next_figure: list[int] = [
|
||||||
vec
|
# vec
|
||||||
for vec in app.next_figures[0].value.shape
|
# for vec in game.next_figure.value.shape
|
||||||
for vec in (int(vec.x), int(vec.y))
|
# 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))
|
decision = output.index(max(output))
|
||||||
|
|
||||||
decisions = {
|
decisions = {
|
||||||
0: game.move_left,
|
0: tetris.move_left,
|
||||||
1: game.move_right,
|
1: tetris.move_right,
|
||||||
2: game.move_down,
|
2: tetris.move_down,
|
||||||
3: game.rotate,
|
3: tetris.rotate,
|
||||||
4: game.rotate_reverse,
|
4: tetris.rotate_reverse,
|
||||||
5: game.drop,
|
5: tetris.drop,
|
||||||
}
|
}
|
||||||
|
|
||||||
decisions[decision]()
|
decisions[decision]()
|
||||||
app.run_game_loop()
|
app.run_game_loop()
|
||||||
|
|
||||||
genome.fitness = calculate_fitness(field)
|
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}")
|
log.debug(f"{genome.fitness=:<+6.6}\t{score=:<6} {lines=:<6} {level=:<6}")
|
||||||
|
|
||||||
game.restart()
|
tetris.restart()
|
||||||
return genome.fitness
|
return genome.fitness
|
||||||
|
|||||||
3
src/ai/moves/__init__.py
Normal file
3
src/ai/moves/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .calculate import calculate_fitness
|
||||||
|
|
||||||
|
__all__ = ["calculate_fitness"]
|
||||||
35
src/ai/moves/calculate.py
Normal file
35
src/ai/moves/calculate.py
Normal 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
|
||||||
@ -23,7 +23,7 @@ class Game(BaseScreen):
|
|||||||
game: Game object.
|
game: Game object.
|
||||||
score: Score object.
|
score: Score object.
|
||||||
preview: Preview object.
|
preview: Preview object.
|
||||||
next_figures: List of upcoming figures.
|
next_figure: List of upcoming figures.
|
||||||
music: Music that plays in the background.
|
music: Music that plays in the background.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from .game import Game
|
|||||||
|
|
||||||
class Main(BaseScreen, SceenElement, TextScreen):
|
class Main(BaseScreen, SceenElement, TextScreen):
|
||||||
def __init__(self, mode: GameMode) -> None:
|
def __init__(self, mode: GameMode) -> None:
|
||||||
log.info("Initializing the game")
|
# log.info("Initializing the game")
|
||||||
self._initialize_pygame()
|
self._initialize_pygame()
|
||||||
self._initialize_surface()
|
self._initialize_surface()
|
||||||
self._initialize_rect()
|
self._initialize_rect()
|
||||||
@ -61,7 +61,7 @@ class Main(BaseScreen, SceenElement, TextScreen):
|
|||||||
|
|
||||||
def exit(self) -> None:
|
def exit(self) -> None:
|
||||||
"""Exit the game."""
|
"""Exit the game."""
|
||||||
log.info("Exiting the game")
|
# log.info("Exiting the game")
|
||||||
pygame.quit()
|
pygame.quit()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|||||||
@ -150,8 +150,8 @@ class Tetris(BaseScreen):
|
|||||||
self._check_finished_rows()
|
self._check_finished_rows()
|
||||||
|
|
||||||
self.game_over: bool = self._check_game_over()
|
self.game_over: bool = self._check_game_over()
|
||||||
if self.game_over:
|
# if self.game_over:
|
||||||
self.restart()
|
# self.restart()
|
||||||
|
|
||||||
self.tetromino = Tetromino(
|
self.tetromino = Tetromino(
|
||||||
self.sprites,
|
self.sprites,
|
||||||
@ -171,13 +171,13 @@ class Tetris(BaseScreen):
|
|||||||
"""
|
"""
|
||||||
for block in self.tetromino.blocks:
|
for block in self.tetromino.blocks:
|
||||||
if block.pos.y <= 0:
|
if block.pos.y <= 0:
|
||||||
log.info("Game over!")
|
# log.info("Game over!")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def restart(self) -> None:
|
def restart(self) -> None:
|
||||||
"""Restart the game."""
|
"""Restart the game."""
|
||||||
log.info("Restarting the game")
|
# log.info("Restarting the game")
|
||||||
self._reset_game_state()
|
self._reset_game_state()
|
||||||
self._initialize_field_and_tetromino()
|
self._initialize_field_and_tetromino()
|
||||||
self.game_over = False
|
self.game_over = False
|
||||||
|
|||||||
@ -21,7 +21,7 @@ class Game:
|
|||||||
size: Size = Size(columns * cell.width, rows * cell.width)
|
size: Size = Size(columns * cell.width, rows * cell.width)
|
||||||
pos: Vec2 = Vec2(padding, padding)
|
pos: Vec2 = Vec2(padding, padding)
|
||||||
offset: Vec2 = Vec2(columns // 2, -1)
|
offset: Vec2 = Vec2(columns // 2, -1)
|
||||||
initial_speed: float | int = 300
|
initial_speed: float | int = 100
|
||||||
movment_delay: int = 150
|
movment_delay: int = 150
|
||||||
rotation_delay: int = 200
|
rotation_delay: int = 200
|
||||||
drop_delay: int = 200
|
drop_delay: int = 200
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user