mirror of
https://github.com/kristoferssolo/2048.git
synced 2025-10-21 15:20:35 +00:00
adjust the weights
This commit is contained in:
parent
b10c90cbdf
commit
cb54859b6c
BIN
best_genome
BIN
best_genome
Binary file not shown.
16
config.txt
16
config.txt
@ -1,14 +1,14 @@
|
|||||||
[NEAT]
|
[NEAT]
|
||||||
fitness_criterion = mean
|
fitness_criterion = max
|
||||||
fitness_threshold = 400
|
fitness_threshold = 32768
|
||||||
pop_size = 1000
|
pop_size = 5000
|
||||||
reset_on_extinction = False
|
reset_on_extinction = False
|
||||||
|
|
||||||
[DefaultGenome]
|
[DefaultGenome]
|
||||||
# node activation options
|
# node activation options
|
||||||
activation_default = relu
|
activation_default = sigmoid
|
||||||
activation_mutate_rate = 1.0
|
activation_mutate_rate = 0.0
|
||||||
activation_options = relu
|
activation_options = sigmoid
|
||||||
|
|
||||||
# node aggregation options
|
# node aggregation options
|
||||||
aggregation_default = sum
|
aggregation_default = sum
|
||||||
@ -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 = 2
|
num_hidden = 4
|
||||||
num_inputs = 17
|
num_inputs = 16
|
||||||
num_outputs = 4
|
num_outputs = 4
|
||||||
|
|
||||||
# node response options
|
# node response options
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import neat
|
import neat
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from py2048 import Menu
|
from py2048 import Menu
|
||||||
|
|
||||||
|
from .fitness import calculate_fitness
|
||||||
|
|
||||||
|
|
||||||
def eval_genomes(genomes, config: neat.Config):
|
def eval_genomes(genomes, config: neat.Config):
|
||||||
app = Menu()
|
app = Menu()
|
||||||
@ -12,22 +15,17 @@ def eval_genomes(genomes, config: neat.Config):
|
|||||||
for genome_id, genome in genomes:
|
for genome_id, genome in genomes:
|
||||||
genome.fitness = 0
|
genome.fitness = 0
|
||||||
net = neat.nn.FeedForwardNetwork.create(genome, config)
|
net = neat.nn.FeedForwardNetwork.create(genome, config)
|
||||||
start_time = time.perf_counter()
|
|
||||||
|
|
||||||
|
start_time = time.perf_counter()
|
||||||
while True:
|
while True:
|
||||||
output = net.activate(
|
output = net.activate((*app.game.board.matrix(),))
|
||||||
(
|
|
||||||
*app.game.board.matrix(),
|
|
||||||
app.game.board.score,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
decision = output.index(max(output))
|
decision = output.index(max(output))
|
||||||
|
|
||||||
decisions = {
|
decisions = {
|
||||||
0: app.game.move_up,
|
0: app.game.move_left,
|
||||||
1: app.game.move_down,
|
1: app.game.move_down,
|
||||||
2: app.game.move_left,
|
2: app.game.move_up,
|
||||||
3: app.game.move_right,
|
3: app.game.move_right,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,23 +33,15 @@ def eval_genomes(genomes, config: neat.Config):
|
|||||||
|
|
||||||
app._hande_events()
|
app._hande_events()
|
||||||
app.game.draw(app._surface)
|
app.game.draw(app._surface)
|
||||||
max_val = app.game.board.max_val()
|
|
||||||
|
|
||||||
time_passed = time.perf_counter() - start_time
|
time_passed = time.perf_counter() - start_time
|
||||||
score = app.game.board.score
|
|
||||||
if max_val >= 32:
|
if app.game.board.is_game_over():
|
||||||
calculate_fitness(genome, max_val)
|
max_tile, score = calculate_fitness(genome, app)
|
||||||
logger.info(f"{max_val=}\t{score=:_}\t{genome_id=}")
|
|
||||||
|
logger.info(f"{max_tile=}\t{score=:_}\t{genome_id=}")
|
||||||
app.game.restart()
|
app.game.restart()
|
||||||
break
|
break
|
||||||
elif app.game.board.is_game_over() or (
|
elif app.game.board._is_full() and time_passed >= 0.1:
|
||||||
app.game.board._is_full() and time_passed >= 0.1
|
decisions[random.choice((0, 1, 2, 3))]()
|
||||||
):
|
max_tile, score = calculate_fitness(genome, app)
|
||||||
calculate_fitness(genome, -max_val)
|
|
||||||
logger.info(f"{max_val=}\t{score=:_}\t{genome_id=}")
|
|
||||||
app.game.restart()
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_fitness(genome: neat.DefaultGenome, score: int):
|
|
||||||
genome.fitness += score
|
|
||||||
|
|||||||
64
src/ai/fitness.py
Normal file
64
src/ai/fitness.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import neat
|
||||||
|
from py2048 import Menu
|
||||||
|
from py2048.utils import Position
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_fitness(genome: neat.DefaultGenome, app: Menu) -> tuple[int, int]:
|
||||||
|
board = app.game.board
|
||||||
|
score = board.score
|
||||||
|
max_tile = board.max_val()
|
||||||
|
empty_cells = 16 - len(board.sprites())
|
||||||
|
smoothness = calc_smoothness(app)
|
||||||
|
monotonicity = calc_monotonicity(app)
|
||||||
|
|
||||||
|
genome.fitness = score + max_tile**3 + smoothness + monotonicity
|
||||||
|
|
||||||
|
return max_tile, score
|
||||||
|
|
||||||
|
|
||||||
|
def calc_smoothness(app: Menu) -> int:
|
||||||
|
smoothness = 0
|
||||||
|
|
||||||
|
for row in range(4):
|
||||||
|
for col in range(4):
|
||||||
|
current_value = app.game.board.get_tile_value(Position(row, col))
|
||||||
|
if current_value:
|
||||||
|
right_value = app.game.board.get_tile_value(Position(row, col + 1))
|
||||||
|
if right_value:
|
||||||
|
smoothness -= abs(current_value - right_value)
|
||||||
|
left_value = app.game.board.get_tile_value(Position(row, col - 1))
|
||||||
|
if left_value:
|
||||||
|
smoothness -= abs(current_value - left_value)
|
||||||
|
|
||||||
|
for col in range(4):
|
||||||
|
for row in range(4):
|
||||||
|
current_value = app.game.board.get_tile_value(Position(row, col))
|
||||||
|
if current_value:
|
||||||
|
up_value = app.game.board.get_tile_value(Position(row - 1, col))
|
||||||
|
if up_value:
|
||||||
|
smoothness -= abs(current_value - up_value)
|
||||||
|
|
||||||
|
down_value = app.game.board.get_tile_value(Position(row + 1, col))
|
||||||
|
if down_value:
|
||||||
|
smoothness -= abs(current_value - down_value)
|
||||||
|
|
||||||
|
return smoothness
|
||||||
|
|
||||||
|
|
||||||
|
def calc_monotonicity(app: Menu):
|
||||||
|
monotonicity = 0
|
||||||
|
for row in range(4):
|
||||||
|
row_values = [
|
||||||
|
app.game.board.get_tile_value(Position(row, col)) for col in range(4)
|
||||||
|
]
|
||||||
|
|
||||||
|
monotonicity += sum(sorted(row_values))
|
||||||
|
|
||||||
|
for col in range(4):
|
||||||
|
col_values = [
|
||||||
|
app.game.board.get_tile_value(Position(row, col)) for row in range(4)
|
||||||
|
]
|
||||||
|
|
||||||
|
monotonicity += sum(sorted(col_values))
|
||||||
|
|
||||||
|
return monotonicity
|
||||||
@ -11,12 +11,10 @@ def train(generations: int) -> None:
|
|||||||
"""Train the AI for a given number of generations."""
|
"""Train the AI for a given number of generations."""
|
||||||
config = get_config()
|
config = get_config()
|
||||||
population = neat.Population(config)
|
population = neat.Population(config)
|
||||||
population.add_reporter(neat.StdOutReporter(True))
|
|
||||||
stats = neat.StatisticsReporter()
|
population.add_reporter(neat.Checkpointer(None))
|
||||||
population.add_reporter(stats)
|
|
||||||
population.add_reporter(neat.Checkpointer(1))
|
|
||||||
|
|
||||||
winner = population.run(eval_genomes, generations)
|
winner = population.run(eval_genomes, generations)
|
||||||
|
|
||||||
logger.info(winner)
|
logger.info(winner)
|
||||||
save_genome(winner, BASE_PATH / "best_genome")
|
save_genome(winner)
|
||||||
|
|||||||
@ -129,6 +129,13 @@ class Board(pygame.sprite.Group):
|
|||||||
return tile
|
return tile
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_tile_value(self, position: Position) -> int:
|
||||||
|
"""Return the value of the tile at the specified position."""
|
||||||
|
tile = self.get_tile(position)
|
||||||
|
if tile:
|
||||||
|
return tile.value
|
||||||
|
return 0
|
||||||
|
|
||||||
def matrix(self) -> list[int]:
|
def matrix(self) -> list[int]:
|
||||||
"""Return a 1d matrix of values of the tiles."""
|
"""Return a 1d matrix of values of the tiles."""
|
||||||
matrix: list[int] = []
|
matrix: list[int] = []
|
||||||
|
|||||||
@ -46,9 +46,9 @@ class Game:
|
|||||||
"""Moved the board in the given direction and updates the score."""
|
"""Moved the board in the given direction and updates the score."""
|
||||||
self.board.move(direction)
|
self.board.move(direction)
|
||||||
self.update_score(self.board.score)
|
self.update_score(self.board.score)
|
||||||
if self.board.is_game_over():
|
# if self.board.is_game_over():
|
||||||
logger.info("Game over!")
|
# logger.info(f"Game over! Score was {self.board.score}.")
|
||||||
self.restart()
|
# self.restart()
|
||||||
|
|
||||||
def move_up(self) -> None:
|
def move_up(self) -> None:
|
||||||
self.move(Direction.UP)
|
self.move(Direction.UP)
|
||||||
|
|||||||
@ -91,7 +91,7 @@ class Menu:
|
|||||||
elif event.type == pygame.KEYDOWN:
|
elif event.type == pygame.KEYDOWN:
|
||||||
if event.key == pygame.K_q:
|
if event.key == pygame.K_q:
|
||||||
self.exit()
|
self.exit()
|
||||||
if self._game_active:
|
if self._game_active or self._ai_active:
|
||||||
self.game.handle_events(event)
|
self.game.handle_events(event)
|
||||||
|
|
||||||
def play(self) -> None:
|
def play(self) -> None:
|
||||||
@ -114,12 +114,7 @@ class Menu:
|
|||||||
3: self.game.move_right,
|
3: self.game.move_right,
|
||||||
}
|
}
|
||||||
|
|
||||||
output = self.network.activate(
|
output = self.network.activate((*self.game.board.matrix(),))
|
||||||
(
|
|
||||||
*self.game.board.matrix(),
|
|
||||||
self.game.board.score,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
decision = output.index(max(output))
|
decision = output.index(max(output))
|
||||||
|
|
||||||
decisions[decision]()
|
decisions[decision]()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user