Module chillow.controller.ai_evaluation_controller

Expand source code
from contextlib import closing
from datetime import datetime, timedelta, timezone
from random import randint
import sqlite3
from typing import List

from chillow.controller import OfflineController
from chillow.model.cell import Cell
from chillow.model.direction import Direction
from chillow.model.game import Game
from chillow.model.player import Player
from chillow.service.ai import NotKillingItselfAI, PathfindingSearchTreeAI, PathfindingAI, SearchTreeAI, \
    SearchTreePathfindingAI, RandomAI, AIOptions
from chillow.service.ai.artificial_intelligence import ArtificialIntelligence
from chillow.view.headless_view import HeadlessView

# These AIs are considered as the top 25 after 1000 simulated game in the first part of the evaluation
best_ais_configurations = [
    (PathfindingSearchTreeAI.__name__, (1, 50, 2, 0.75, 30)),
    (SearchTreePathfindingAI.__name__, (1, 25, 2, 20)),
    (PathfindingSearchTreeAI.__name__, (1, 25, 2, 0.75, 10)),
    (PathfindingSearchTreeAI.__name__, (1, 75, 3, 0.75, 10)),
    (PathfindingSearchTreeAI.__name__, (2, 75, 3, 0.75, 20)),
    (PathfindingSearchTreeAI.__name__, (1, 75, 2, 0.75, 10)),
    (PathfindingAI.__name__, (1, 50)),
    (PathfindingSearchTreeAI.__name__, (1, 25, 2, 0.75, 20)),
    (PathfindingSearchTreeAI.__name__, (2, 50, 2, 0.75, 20)),
    (PathfindingSearchTreeAI.__name__, (1, 50, 2, 0.75, 20)),
    (NotKillingItselfAI.__name__, ([AIOptions.max_distance], 1, 0, 1)),
    (NotKillingItselfAI.__name__, ([AIOptions.max_distance], 2, 0, 3)),
    (PathfindingSearchTreeAI.__name__, (1, 50, 3, 0.75, 10)),
    (PathfindingSearchTreeAI.__name__, (1, 75, 3, 0.75, 30)),
    (SearchTreePathfindingAI.__name__, (1, 75, 2, 10)),
    (PathfindingAI.__name__, (1, 75)),
    (PathfindingSearchTreeAI.__name__, (1, 75, 3, 0.75, 20)),
    (SearchTreePathfindingAI.__name__, (2, 50, 2, 20)),
    (SearchTreePathfindingAI.__name__, (1, 25, 2, 10)),
    (PathfindingSearchTreeAI.__name__, (1, 75, 2, 0.75, 20)),
    (PathfindingAI.__name__, (1, 25)),
    (PathfindingSearchTreeAI.__name__, (1, 50, 3, 0.75, 30)),
    (PathfindingSearchTreeAI.__name__, (1, 50, 3, 0.75, 20)),
    (PathfindingSearchTreeAI.__name__, (2, 75, 2, 0.75, 30)),
    (SearchTreePathfindingAI.__name__, (1, 50, 2, 10))
]


class AIEvaluationController(OfflineController):
    """Executes multiple games after each other with randomly created games and players.

    The result of every game and the execution time for each player in each round is saved in an SQLite database."""

    def __init__(self, runs: int, db_path: str, evaluation_type: int):
        """ Creates a new AI evaluation controller.

        Args:
            runs: The number of games to be simulated.
            db_path: The path of the SQLite database file.
            evaluation_type: Defines which evaluation should be performed
        """
        super().__init__(HeadlessView())
        self.__runs = runs
        self.__db_path = db_path
        if 1 <= evaluation_type <= 2:
            self.__evaluation_type = evaluation_type
        else:
            self.__evaluation_type = 1
        self.__connection = None
        self.__cursor = None
        self.__current_game_id = None

    def play(self):
        """See base class."""
        with closing(sqlite3.connect(self.__db_path)) as connection:
            with closing(connection.cursor()) as cursor:
                self.__connection = connection
                self.__cursor = cursor

                self.__create_db_tables()

                max_game_id = self.__cursor.execute("SELECT MAX(id) FROM games").fetchone()[0]
                if max_game_id is None:
                    max_game_id = 0

                self.__run_simulations(max_game_id)

    def _create_game(self) -> None:
        height = randint(30, 70)
        width = randint(30, 70)

        player_count = randint(3, 6)
        players = []
        occupied_coordinates = []
        for i in range(1, player_count + 1):
            next_coordinate = (randint(0, width - 1), randint(0, height - 1))
            while next_coordinate in occupied_coordinates:
                next_coordinate = (randint(0, width - 1), randint(0, height - 1))
            occupied_coordinates.append(next_coordinate)
            player = Player(i, next_coordinate[0], next_coordinate[1], Direction.get_random_direction(), 1, True,
                            str(i))
            players.append(player)

        cells = [[Cell() for _ in range(width)] for _ in range(height)]
        for player in players:
            cells[player.y][player.x] = Cell([player])

        self._game = Game(width, height, cells, players, 1, True, datetime.now() + timedelta(5, 15))
        self._game_round = 0

        self._ais = []

        if self.__evaluation_type == 1:
            self.__generate_ais_for_first_evaluation(player_count, players)
        elif self.__evaluation_type == 2:
            self.__generate_ais_for_second_evaluation(player_count, players)

    def __generate_ais_for_first_evaluation(self, player_count: int, players: List[Player]) -> None:
        self._ais.append(PathfindingAI(players[0], randint(1, 3), randint(1, 3) * 25))
        self._ais.append(PathfindingSearchTreeAI(players[1], randint(1, 3), randint(1, 3) * 25, randint(2, 3), 0.75,
                                                 randint(1, 3) * 10))
        self._ais.append(SearchTreePathfindingAI(players[2], randint(1, 3), randint(1, 3) * 25, 2,
                                                 randint(1, 3) * 10))
        if player_count > 3:
            self._ais.append(SearchTreeAI(players[3], randint(1, 3), randint(2, 3), True, randint(1, 3) * 10))
            if player_count > 4:
                self._ais.append(NotKillingItselfAI(players[4], [AIOptions.max_distance], randint(1, 3), 0,
                                                    randint(1, 3)))
                if player_count > 5:
                    self._ais.append(RandomAI(players[5], randint(1, 3)))

    def __generate_ais_for_second_evaluation(self, player_count: int, players: List[Player]) -> None:
        used_ai_indices = []
        for i in range(player_count):
            ai_index = randint(0, len(best_ais_configurations) - 1)
            # Prevent that the same AI configuration is used in one game
            while ai_index in used_ai_indices:
                ai_index = randint(0, len(best_ais_configurations) - 1)

            used_ai_indices.append(ai_index)
            ai = best_ais_configurations[ai_index]
            self._ais.append(globals()[ai[0]](players[i], *ai[1]))

    def __run_simulations(self, max_game_id):
        for i in range(self.__runs):
            self.__current_game_id = i + 1 + max_game_id
            super().play()

            self.__cursor.execute("INSERT INTO games VALUES ({}, {}, {}, '{}', NULL)"
                                  .format(self.__current_game_id, self._game.width, self._game.height,
                                          datetime.now(timezone.utc)))

            winner_player = self._game.get_winner()
            for ai in self._ais:
                ai_class = ai.__class__.__name__
                ai_info = ai.get_information()
                player_id = self.__get_player_id(ai_class, ai_info)

                # Save how often an AI configuration participated in a game
                self.__cursor.execute("INSERT INTO participants VALUES ({}, {})"
                                      .format(player_id, self.__current_game_id))

                # Save how often an AI configuration won a game
                if ai.player == winner_player:
                    self.__cursor.execute("UPDATE games SET winner_id = {} WHERE id = {}"
                                          .format(player_id, self.__current_game_id))

            self.__connection.commit()

    def __create_db_tables(self):
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS players ("
                              "id INTEGER NOT NULL PRIMARY KEY,"
                              "class TEXT NOT NULL,"
                              "info TEXT)")
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS games ("
                              "id INTEGER NOT NULL PRIMARY KEY,"
                              "width INTEGER NOT NULL,"
                              "height INTEGER NOT NULL,"
                              "date TEXT NOT NULL,"
                              "winner_id INTEGER,"
                              "FOREIGN KEY (winner_id) REFERENCES players (id))")
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS participants ("
                              "player_id INTEGER NOT NULL,"
                              "game_id INTEGER NOT NULL,"
                              "PRIMARY KEY(player_id, game_id),"
                              "FOREIGN KEY (player_id) REFERENCES players (id),"
                              "FOREIGN KEY (game_id) REFERENCES games (id))")
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS execution_times ("
                              "player_id INTEGER NOT NULL,"
                              "game_id INTEGER NOT NULL,"
                              "game_round INTEGER NOT NULL,"
                              "execution REAL NOT NULL,"
                              "PRIMARY KEY(player_id, game_id, game_round),"
                              "FOREIGN KEY (player_id) REFERENCES players (id),"
                              "FOREIGN KEY (game_id) REFERENCES games (id))")

    def _log_execution_time(self, ai: ArtificialIntelligence, execution_time: float):
        ai_class = ai.__class__.__name__
        ai_info = ai.get_information()
        player_id = self.__get_player_id(ai_class, ai_info)

        self.__cursor.execute("INSERT INTO execution_times VALUES ({}, {}, {}, {})"
                              .format(player_id, self.__current_game_id, self._game_round, execution_time))

    def __get_player_id(self, ai_class: str, ai_info: str) -> int:
        player_id = self.__cursor.execute(
            "SELECT MAX(id) FROM players p WHERE p.class = '{}' AND p.info = '{}'"
            .format(ai_class, ai_info)).fetchone()[0]

        if player_id is None:
            max_player_id = self.__cursor.execute("SELECT MAX(id) FROM players").fetchone()[0]
            if max_player_id is None:
                max_player_id = 0
            player_id = max_player_id + 1
            self.__cursor.execute("INSERT INTO players VALUES ({}, '{}', '{}')".format(player_id, ai_class, ai_info))

        return player_id

Classes

class AIEvaluationController (runs: int, db_path: str, evaluation_type: int)

Executes multiple games after each other with randomly created games and players.

The result of every game and the execution time for each player in each round is saved in an SQLite database.

Creates a new AI evaluation controller.

Args

runs
The number of games to be simulated.
db_path
The path of the SQLite database file.
evaluation_type
Defines which evaluation should be performed
Expand source code
class AIEvaluationController(OfflineController):
    """Executes multiple games after each other with randomly created games and players.

    The result of every game and the execution time for each player in each round is saved in an SQLite database."""

    def __init__(self, runs: int, db_path: str, evaluation_type: int):
        """ Creates a new AI evaluation controller.

        Args:
            runs: The number of games to be simulated.
            db_path: The path of the SQLite database file.
            evaluation_type: Defines which evaluation should be performed
        """
        super().__init__(HeadlessView())
        self.__runs = runs
        self.__db_path = db_path
        if 1 <= evaluation_type <= 2:
            self.__evaluation_type = evaluation_type
        else:
            self.__evaluation_type = 1
        self.__connection = None
        self.__cursor = None
        self.__current_game_id = None

    def play(self):
        """See base class."""
        with closing(sqlite3.connect(self.__db_path)) as connection:
            with closing(connection.cursor()) as cursor:
                self.__connection = connection
                self.__cursor = cursor

                self.__create_db_tables()

                max_game_id = self.__cursor.execute("SELECT MAX(id) FROM games").fetchone()[0]
                if max_game_id is None:
                    max_game_id = 0

                self.__run_simulations(max_game_id)

    def _create_game(self) -> None:
        height = randint(30, 70)
        width = randint(30, 70)

        player_count = randint(3, 6)
        players = []
        occupied_coordinates = []
        for i in range(1, player_count + 1):
            next_coordinate = (randint(0, width - 1), randint(0, height - 1))
            while next_coordinate in occupied_coordinates:
                next_coordinate = (randint(0, width - 1), randint(0, height - 1))
            occupied_coordinates.append(next_coordinate)
            player = Player(i, next_coordinate[0], next_coordinate[1], Direction.get_random_direction(), 1, True,
                            str(i))
            players.append(player)

        cells = [[Cell() for _ in range(width)] for _ in range(height)]
        for player in players:
            cells[player.y][player.x] = Cell([player])

        self._game = Game(width, height, cells, players, 1, True, datetime.now() + timedelta(5, 15))
        self._game_round = 0

        self._ais = []

        if self.__evaluation_type == 1:
            self.__generate_ais_for_first_evaluation(player_count, players)
        elif self.__evaluation_type == 2:
            self.__generate_ais_for_second_evaluation(player_count, players)

    def __generate_ais_for_first_evaluation(self, player_count: int, players: List[Player]) -> None:
        self._ais.append(PathfindingAI(players[0], randint(1, 3), randint(1, 3) * 25))
        self._ais.append(PathfindingSearchTreeAI(players[1], randint(1, 3), randint(1, 3) * 25, randint(2, 3), 0.75,
                                                 randint(1, 3) * 10))
        self._ais.append(SearchTreePathfindingAI(players[2], randint(1, 3), randint(1, 3) * 25, 2,
                                                 randint(1, 3) * 10))
        if player_count > 3:
            self._ais.append(SearchTreeAI(players[3], randint(1, 3), randint(2, 3), True, randint(1, 3) * 10))
            if player_count > 4:
                self._ais.append(NotKillingItselfAI(players[4], [AIOptions.max_distance], randint(1, 3), 0,
                                                    randint(1, 3)))
                if player_count > 5:
                    self._ais.append(RandomAI(players[5], randint(1, 3)))

    def __generate_ais_for_second_evaluation(self, player_count: int, players: List[Player]) -> None:
        used_ai_indices = []
        for i in range(player_count):
            ai_index = randint(0, len(best_ais_configurations) - 1)
            # Prevent that the same AI configuration is used in one game
            while ai_index in used_ai_indices:
                ai_index = randint(0, len(best_ais_configurations) - 1)

            used_ai_indices.append(ai_index)
            ai = best_ais_configurations[ai_index]
            self._ais.append(globals()[ai[0]](players[i], *ai[1]))

    def __run_simulations(self, max_game_id):
        for i in range(self.__runs):
            self.__current_game_id = i + 1 + max_game_id
            super().play()

            self.__cursor.execute("INSERT INTO games VALUES ({}, {}, {}, '{}', NULL)"
                                  .format(self.__current_game_id, self._game.width, self._game.height,
                                          datetime.now(timezone.utc)))

            winner_player = self._game.get_winner()
            for ai in self._ais:
                ai_class = ai.__class__.__name__
                ai_info = ai.get_information()
                player_id = self.__get_player_id(ai_class, ai_info)

                # Save how often an AI configuration participated in a game
                self.__cursor.execute("INSERT INTO participants VALUES ({}, {})"
                                      .format(player_id, self.__current_game_id))

                # Save how often an AI configuration won a game
                if ai.player == winner_player:
                    self.__cursor.execute("UPDATE games SET winner_id = {} WHERE id = {}"
                                          .format(player_id, self.__current_game_id))

            self.__connection.commit()

    def __create_db_tables(self):
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS players ("
                              "id INTEGER NOT NULL PRIMARY KEY,"
                              "class TEXT NOT NULL,"
                              "info TEXT)")
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS games ("
                              "id INTEGER NOT NULL PRIMARY KEY,"
                              "width INTEGER NOT NULL,"
                              "height INTEGER NOT NULL,"
                              "date TEXT NOT NULL,"
                              "winner_id INTEGER,"
                              "FOREIGN KEY (winner_id) REFERENCES players (id))")
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS participants ("
                              "player_id INTEGER NOT NULL,"
                              "game_id INTEGER NOT NULL,"
                              "PRIMARY KEY(player_id, game_id),"
                              "FOREIGN KEY (player_id) REFERENCES players (id),"
                              "FOREIGN KEY (game_id) REFERENCES games (id))")
        self.__cursor.execute("CREATE TABLE IF NOT EXISTS execution_times ("
                              "player_id INTEGER NOT NULL,"
                              "game_id INTEGER NOT NULL,"
                              "game_round INTEGER NOT NULL,"
                              "execution REAL NOT NULL,"
                              "PRIMARY KEY(player_id, game_id, game_round),"
                              "FOREIGN KEY (player_id) REFERENCES players (id),"
                              "FOREIGN KEY (game_id) REFERENCES games (id))")

    def _log_execution_time(self, ai: ArtificialIntelligence, execution_time: float):
        ai_class = ai.__class__.__name__
        ai_info = ai.get_information()
        player_id = self.__get_player_id(ai_class, ai_info)

        self.__cursor.execute("INSERT INTO execution_times VALUES ({}, {}, {}, {})"
                              .format(player_id, self.__current_game_id, self._game_round, execution_time))

    def __get_player_id(self, ai_class: str, ai_info: str) -> int:
        player_id = self.__cursor.execute(
            "SELECT MAX(id) FROM players p WHERE p.class = '{}' AND p.info = '{}'"
            .format(ai_class, ai_info)).fetchone()[0]

        if player_id is None:
            max_player_id = self.__cursor.execute("SELECT MAX(id) FROM players").fetchone()[0]
            if max_player_id is None:
                max_player_id = 0
            player_id = max_player_id + 1
            self.__cursor.execute("INSERT INTO players VALUES ({}, '{}', '{}')".format(player_id, ai_class, ai_info))

        return player_id

Ancestors

Inherited members