Королевская битва!

Введение

На этом занятии мы напишем программу для танка, который сможет принять участие в многопользовательской баталии.

  1. Тестирующая система: https://tanks.geekclass.ru/
  2. Регистрация в игре: https://tanks.geekclass.ru/register
  3. Прямая трансляция: https://tanks.geekclass.ru/game
  4. Статистика и счет: https://tanks.geekclass.ru/stats

Правила и ход игры

Игра, в которой нам предстоит принять участие - это пошаговая стратегия.

Каждый ход танк получает информацию о своем местоположении и положении дел на карте. В ответ он должен выбрать одно из восьми действий: движение в одну из четырех сторон, либо стрельбу в одну из четырех сторон.

В одном раунде побеждает игрок, набравший за 300 ходов наибольшее число очков.

Очки можно получать за попадания по противнику (20 очков за попадание), либо сбор монет (50 очков за монету).

Победитель определяется по итогам трех раундов.

В начале раунда танки появляются в случайном месте, танк изначально имеет 50 очков здоровья, а монеты появляются на карте каждые несколько ходов в случайном месте.

Лимит выполнения программы - полсекунды. Если танк превышает допустимое время работы, или программа срабатывает с ошибкой, танк штрафуется на 5 очков.

В игре одному участнику разрешено иметь только одного бота, любые сетевые взаимодействия запрещены. Разрешается использование только встроенных в питон библиотек.

Структура бота

Программы для танка пишутся на языке Python 3. Файл с кодом должен содержать функцию make_choice, принимающую ровно три аргумента - координаты бота на карте (x и y) и матрицу с информацией о других игроках (field).

В результате работы функция должна вернуть в точку вызова строку - выбор, что делать танку в текущем ходе.

def make_choice(x,y,field):
    #произвольный код

Помимо функции make_choice файл может содержать другие функции, но советуем воздержаться от кода вне функций, а также вывода данных на экран (за исключением случая, описанного в разделе запуск и поиск ошибок).

При каждом запуске функция должна выбрать одно из восьми действий - передвижение на одну клетку в одну из четырех сторон, либо выстрел в одном из четырех направлений.

Для этого функция make_choice должна вернуть одну из следующих строк: "fire_up", "fire_down", "fire_left", "fire_right", "go_up", "go_down", "go_left", "go_right".

Выстрел поражает только одного противника, но на произвольном расстоянии (стены непробиваемы). Ходьба в стену или за границу игнорируется.

В следующем примере бот будет действовать полностью случайно.

import random

def make_choice(x,y,field):
    actions = ["fire_up", "fire_down",
               "fire_left", "fire_right", 
               "go_up","go_down",
               "go_left","go_right"]
    return random.choice(actions)

Структура карты

Рассмотрим параметры нашего бота подробнее.

def make_choice(x,y,field):
    #произвольный код

Переменная field - это матрица, каждая ячейка которой - это клетка на карте. Матрица игры содержит информацию о:

  • расположении игроков, монеток и стен на карте;
  • текущем уровне жизни всех игроков;
  • истории ходов каждого игрока с начала игры.

Технически field - список столбцов, каждый из который - это список клеток. Номер столбца - это координата точки по оси X, а номер элемента в столбце - координата по оси Y.

Например, размеры карты можно определить следующим образом:

def make_choice(x,y,field):
    width = len(field) # число столбцов
    height = len(field[0]) # число клеток в первом столбце

    # код бота

В каждой клетке может находиться:

  • 0, если клетка пустая;
  • 1, если в клетке монетка;
  • -1, если в клетке стена;
  • {"name": "bot name", "life": 10, "history": ["go_right"]}, если в клетке бот (словарь с его именем, здоровьем и историей ходов).

Например, вот так можно проверить, есть ли монетка в клетке (0,0):

def make_choice(x,y,field):
    if field[0][0] == 1:
        # в клетке (0, 0) есть монетка
    # код бота

А чтобы узнать уровень здоровья нашего бота, можно написать:

def make_choice(x,y,field):
    my_life = field[x][y]['life']
    # код бота

История ходов - это все предыдущие ходы танка. Если в течение хода танк сработал с ошибками, в историю будет добавлена строка "crash". Длина списка с историей равна числу ходов, прошедших с начала раунда.

В примере ниже, танк будет ходить вверх, если предыдущий ход был таким же:

def make_choice(x,y,field):
    my_history = field[x][y]['history']
    num_of_steps = field[x][y]['history']

    if (num_of_step > 0 and my_history[-1] == 'go_up'):
        return 'go_up'
    # остальной код

Пример: поиск монеток

Рассмотрим пример: если бот видит монетку слева, он будет перемещаться в ее сторону. Этот пример легко расширить на все остальные направления.

Для поиска монетки, нам нужно перебрать все точки слева от нашей позиции (меняется координата по оси x, координата по оси y остается прежней).

def make_choice(x,y,field):
    width = len(field)
    height = len(field[0])

    for i in range(0, x):
        if field[i][y] == 1:
            return "go_left"

    # остальной код танка

Аналогично можно искать противников:

def make_choice(x,y,field):
    width = len(field)
    height = len(field[0])

    for i in range(0, x):
        # если в клетке не стена, не пусто и не монетка
        if field[i][y] not in [-1, 0 ,1]: 
            return "fire_left"

    # остальной код танка

В реальном коде танка нам также потребуется учитывать наличие стен.

Запуск и поиск ошибок

В течение подготовительного этапа каждый раунд длится 120 ходов, а у ботов по 20 жизни, размеры и конфигурация игрового поля не изменяются.

Чтобы начать игру, вам необходимо зарегистрироваться и получить ключ. Ни в коем случае не сообщайте ваш ключ другим участникам.

После регистрации вы можете загрузить файл с кодом вашего танка на сервер. При первой загрузке ваш танк появится в игре по окончании раунда. При повторной загрузке, измения в коде вступят в силу мнгновенно.

Вся информация о работе вашего танка представлена в статистике:

  • HP - текущий уровень жизни;
  • Качество - наличие ошибок в коде;
  • Действия - количество шагов (ST) / выстрелов (SH);
  • Попадания - количество попаданий по противникам
  • Время жизни - количество ходов, в течение которых танк жив;
  • Монеты - число собранных монет;
  • Счет - счет, с учетом попаданий, монет и штрафов за ошибки.

Виды ошибок:

  • crash - синтаксическая ошибка, ошибка времени выполнения, либо превышение времени работы (timeout);
  • errors - танк вернул несуществующую команду (например, выбранное действие написано с ошибкой / пробелом вместо подчеркивания и т.д.), либо не вернул ничего (None).

Если ваш бот работает с ошибками, можно навести мышку на слово crash и подождать пару секунд - появится подсказка с текстом ошибки.

При наведении на errors появится строка, которую вернул танк.

Качество бота меняется даже при наличии всего одной ошибки. Графа обновится только с наступлением нового раунда, даже если вы обновили код танка.

Если ваш танк работает с ошибкой, проще всего провести отладку локально. Для этого создайте в вашем коде свой пример карты и запустите вашу программу:

def make_choice(x,y,field):
    # код танка
    pass

if __name__ == "__main__":
    T = {"life": 10}
    my_field = [
        [0,0,0,0],
        [0,T,0,1],
        [0,0,T,0],
        [0,0,0,0]
    ]
    my_x = 1
    my_y = 1
    res = make_choice(my_x, my_y, my_field)
    print(res)

Советы

Далее перечислены несколько идей, которые вы можете реализовать в своей программе.

  • Искать вражеские танки в зоне поражения и открывать по ним огонь, учитывать наличие стен.
  • Перед началом атаки проверить свой уровень жизни и уровень жизни противиков, возможно рациональнее уйти от схватки.
  • Искать ближайшую монетку и двигаться в ее сторону.
  • При движении следить, что ход не направлен в стену или границу поля.
  • Реализовать обход в ширину для поиска пути к ближашей монете.
  • При случайном движении стараться удерживать прямую траекторию.

Удачи!