ИИ построен на аккуратном питоне. Входными данными является игровое поле в виде списка полей (0: незанятые, 1: player1, 2: player2). AI производит 9 выходов, и в качестве поля выбирается индекс максимума (выхода). Он тренируется против бота, который выбирает поля случайным образом. Если поле для перехода уже занято, первое свободное поле устанавливается как поле для перехода.
import neat
import pygame
import os
import json
import random
import checkpoint
import winsound
import time
import matplotlib.pyplot as plt
pygame.init()
cloud = checkpoint.Checkpointer()
p = None
GEN = 0
screen = None
pygame.display.set_caption("Tic Tac Toe")
o_icon = pygame.image.load("o.png")
x_icon = pygame.image.load("x.png")
class Field:
def __init__(self):
self.field = [0,0,0, 0,0,0, 0,0,0]
self.winner = None
def reset(self):
self.field = [0,0,0, 0,0,0, 0,0,0]
self.winner = None
def display(self):
screen.fill([0,0,0])
self.display_pieces()
self.display_board()
pygame.display.update()
def display_pieces(self):
pos_to_pix = {
0: (0,0),
1: (100,0),
2: (200,0),
3: (0,100),
4: (100,100),
5: (200,100),
6: (0, 200),
7: (100, 200),
8: (200, 200)
}
for pos in range(len(self.field)):
if self.field[pos] == 1:
screen.blit(x_icon, pos_to_pix[pos])
elif self.field[pos] == 2:
screen.blit(o_icon, pos_to_pix[pos])
@staticmethod
def pos_to_field(pos):
x = pos[0]
y = pos[1]
if x < 100:
x = 0
elif x < 200:
x = 1
else:
x = 2
if y < 100:
y = 0
elif y < 200:
y = 1
else:
y = 2
return x+y*3
@staticmethod
def display_board():
pygame.draw.rect(screen, "White", [0,99, 300,2])
pygame.draw.rect(screen, "White", [0,199, 300,2])
pygame.draw.rect(screen, "White", [99,0, 2,300])
pygame.draw.rect(screen, "White", [199,0, 2,300])
def action(self, field_id, player_id):
self.field[field_id] = player_id
def field_is_free(self, field):
if self.field[field] == 0:
return True
return False
def check_winner(self):
for line in range(3):
# check horizontal
if self.field[line*3] == self.field[line*3+1] == self.field[line*3+2] != 0:
self.winner = self.field[line*3]
return True, self.winner
if self.field[line] == self.field[line+3] == self.field[line+6] != 0:
self.winner = self.field[line]
return True, self.winner
if self.field[0] == self.field[4] == self.field[8] != 0:
self.winner = self.field[0]
return True, self.winner
if self.field[2] == self.field[4] == self.field[6] != 0:
self.winner = self.field[2]
return True, self.winner
return False
def first_free(self):
for index, value in enumerate(self.field):
if value == 0:
return index
class Player:
def __init__(self, id, being, brain=None):
self.id = id
self.being = being
self.brain = brain
class Graph:
def __init__(self):
self.gens = []
self.maxs = []
self.losts = []
self.drawns = []
self.wons = []
self.average = []
self.last_gens = 50
self.vmax = None
def add(self, gen, vmax_round, lost, drawn, won):
self.gens.append(gen)
self.maxs.append(vmax_round)
self.losts.append(lost)
self.drawns.append(drawn)
self.wons.append(won)
self.average.append(sum(self.maxs[-min(self.last_gens,gen):]) / min(self.last_gens,gen))
self.vmax = max(self.maxs)
def display(self):
plt.figure(figsize=(10,6))
plt.plot(self.gens, self.maxs, label="Best Fitness")
plt.plot(self.gens, self.average, label="Average Fitness last " + str(self.last_gens) + " generations")
plt.plot(self.gens, self.losts, label="Lost")
plt.plot(self.gens, self.drawns, label="Drawn")
plt.plot(self.gens, self.wons, label="Won")
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
plt.grid("on")
plt.tight_layout()
plt.show()
def save(self):
with open('gens.txt', 'w') as filehandle:
json.dump(self.gens, filehandle)
with open('maxs.txt', 'w') as filehandle:
json.dump(self.maxs, filehandle)
with open('average.txt', 'w') as filehandle:
json.dump(self.average, filehandle)
with open('losts.txt', 'w') as filehandle:
json.dump(self.losts, filehandle)
with open('drawns.txt', 'w') as filehandle:
json.dump(self.drawns, filehandle)
with open('wons.txt', 'w') as filehandle:
json.dump(self.wons, filehandle)
def load(self):
with open('gens.txt', 'r') as filehandle:
self.gens = json.load(filehandle)
with open('maxs.txt', 'r') as filehandle:
self.maxs = json.load(filehandle)
with open('average.txt', 'r') as filehandle:
self.average = json.load(filehandle)
with open('losts.txt', 'r') as filehandle:
self.losts = json.load(filehandle)
with open('drawns.txt', 'r') as filehandle:
self.drawns = json.load(filehandle)
with open('wons.txt', 'r') as filehandle:
self.wons = json.load(filehandle)
graph = Graph()
board = Field()
def place_piece(player, field):
board.field[field] = player.id
def sound():
winsound.Beep(440,200)
def train(genomes, config):
global GEN
GEN += 1
best_fitness = 0
players = [None, None]
best_lost, best_drawn, best_won = 0,0,0
for index, genome in genomes:
index -= 1
genome.fitness = 0
net = neat.nn.FeedForwardNetwork.create(genome, config)
players[0] = Player(1, "random")
players[1] = Player(2, "ai")
lost, drawn, won = 0,0,0
for round in range(100):
if round == 50:
players[0] = Player(1, "ai")
players[1] = Player(2, "random")
field_to_go = -1
placed = 0
player = players[0]
while True:
if player.being == "random":
field_to_go = random.randint(0,8)
elif player.being == "ai":
output = net.activate(board.field)
field_to_go = output.index(max(output))
elif player.being == "human":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
break
elif event.type == pygame.MOUSEBUTTONUP:
field_to_go = board.pos_to_field(pygame.mouse.get_pos())
if field_to_go > -1:
if board.field_is_free(field_to_go):
board.action(field_to_go, player.id)
else:
board.action(board.first_free(), player.id)
if player == players[0]:
player = players[1]
else:
player = players[0]
placed += 1
if shown:
board.display()
time.sleep(1)
if board.check_winner() or placed == 9:
if board.winner is None:
genome.fitness += 1
drawn += 1
elif players[board.winner-1].being == "ai":
genome.fitness += 2
won += 1
else:
genome.fitness -= 0
lost += 1
board.reset()
break
if genome.fitness > best_fitness:
best_fitness = genome.fitness
best_lost = lost
best_drawn = drawn
best_won = won
graph.add(GEN, best_fitness, best_lost, best_drawn, best_won)
def test(genome):
result = [0,0,0]
players = [None, None]
for round in range(1000):
placed = 0
if round % 2 == 0:
players[0] = Player(1, "ai")
players[1] = Player(2, "human")
else:
players[0] = Player(1, "human")
players[1] = Player(2, "ai")
player = players[0]
while True:
field_to_go = -1
if player.being == "random":
field_to_go = random.randint(0,8)
elif player.being == "ai":
output = genome.activate(board.field)
field_to_go = output.index(max(output))
elif player.being == "human":
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
break
elif event.type == pygame.MOUSEBUTTONUP:
field_to_go = board.pos_to_field(pygame.mouse.get_pos())
if field_to_go > -1:
if board.field_is_free(field_to_go):
board.action(field_to_go, player.id)
else:
board.action(board.first_free(), player.id)
if player == players[0]:
player = players[1]
else:
player = players[0]
placed += 1
if shown:
pygame.event.get()
board.display()
time.sleep(0.75)
if board.check_winner() or placed == 9:
if board.winner is None:
result[1] += 1
elif players[board.winner-1].being == "ai":
result[2] += 1
else:
result[0] += 1
if shown:
if board.winner is None:
print("None")
else:
print(players[board.winner-1].being + " " + str(player.id))
board.reset()
break
print(result)
def run(config_path):
global shown
global GEN
global screen
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction,
neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path)
nc_name = "population"
testing = False
shown = False
new = True
if testing:
if shown:
screen = pygame.display.set_mode((300, 300))
pygame.display.set_caption("Tic Tac Toe")
test(neat.nn.FeedForwardNetwork.create(cloud.restore_checkpoint("population").population.best_genome, config))
quit()
if new:
p = neat.Population(config)
p.add_reporter(neat.StdOutReporter(True))
p.add_reporter(neat.StatisticsReporter())
else:
p = cloud.restore_checkpoint(nc_name).population
graph.load()
GEN = p.generation
p.run(train,250)
print(time.strftime("%H:%M:%S", time.localtime()))
cloud.save_checkpoint(config, p, p.species, GEN, nc_name)
graph.save()
graph.display()
sound()
if __name__ == "__main__":
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "config_feedforward")
run(config_path)
После 100 игр ИИ относительно быстро набирает 175 очков. Но с этого момента он не улучшается. И все равно проигрывает в играх, чего не бывает, когда вы играете идеально. Что я делаю неправильно? Спасибо за вашу помощь 🙂