2Д консоли робот войны


Я работаю над мини-проектом, чтобы заставить меня изучать ООП в Python и расширить свой набор инструментов. По сути, я просто хочу, чтобы код простого консольного приложения, где я есть 2D 'местности', где будут неодушевленные предметы (например, расходные материалы, деревья, камни) и одушевленных предметов (либо игрок контролируется ввод с клавиатуры или управляемых AI).

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

Некоторые более конкретные вопросы:

  • Это стандартный, много функций с @оформитель собственность ? Он чувствует себя.. странно ?
  • Какие есть хорошие практики в обработки исключений в Python ? Я думал использовать исключения для того, чтобы оживить действия, такие как перемещение действительны (например, не выезжает за пределы поля или перемещение через блокирующий неживой объект, такой как дерево).
  • Стоит ли разделить мой код в пакеты / модули на данный момент ? Я никогда не чувствовал необходимости делать это так далеко, потому что мой код обычно < 150 строк.

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

# Robots

import random

class Position:
    def __init__(self, x, y):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

class Inanimate:
    def __init__(self, walkable):
        self._walkable = walkable

    @property
    def walkable(self):
        return self._walkable

class AnimateManager:
    action_queue = []

    def get_animate_actions(self, terrain):
        for animate_elem in Animate.animate_list:
            action_queue.append((animate_elem, animate_elem.get_action(terrain)))

    def initialize_animates(self, terrain):
        free_placements = terrain.get_free_placements()
        if len(free_placements) < len(Animate.animate_list):
            raise Exception('NOT ENOUGH FREE PLACEMENTS')

        for animate_elem in Animate.animate_list:
            new_position = random.randint(0, len(free_placements))
            coordinates = free_placements[new_position]

            try:
                terrain.place_element(animate_elem, coordinates.x, coordinates.y)
                free_placements = free_placements[:max(new_position - 1, 0)] + free_placements[1 if new_position == 0 else new_position:]
            except Exception as e:
                print('Exception raised: ', e)

class Animate:
    animate_list = []
    new_animate_id = 0

    def __init__(self):
        self._name = 'BLAH'
        self._animate_id = Animate.get_new_animate_id()
        Animate.animate_list.append(self)

    def __del__(self):
        Animate.remove_animate(self._animate_id)

    @staticmethod
    def remove_animate(id):
        for i, x in enumerate(Animate.animate_list):
            if x.animate_id == id:
                Animate.animate_list = Animate.animate_list[:max(i-1, 0)] + Animate.animate_list[1 if i == 0 else i:]
                break

    @staticmethod
    def get_new_animate_id():
        Animate.new_animate_id += 1
        return Animate.new_animate_id - 1

    @property
    def animate_id(self):
        return self._animate_id

    def get_action(self, terrain):
        return 'HELLO ' + str(self.animate_id)

class Terrain:
    def __init__(self, width, height):
        self.generate_terrain(width, height)

    @property
    def width(self):
        return self._width

    @property
    def height(self):
        return self._height

    @property
    def terrain(self):
        return self._terrain

    def generate_terrain(self, width, height):
        inanimate_rate = 0.1
        self._width = width
        self._height = height
        self._terrain = [[] for x in range(self._height)]
        for i, line in enumerate(self._terrain):
            self._terrain[i] = [[Inanimate(False)] if ((random.randint(0, 100) + 1) < (100 * inanimate_rate)) else [] for x in range(self._width)]
        return self._terrain

    def place_element(self, element, location_x, location_y):
        if location_x >= self._width or location_y >= self._height:
            raise Exception('OUT OF BOUNDS')
            pass
        elif self._terrain[location_x][location_y] != []:
            raise Exception('OCCUPIED')
            pass
        else:
            self._terrain[location_x][location_y] = element

    def get_free_placements(self):
        free_placements = []
        for i in range(self._width):
            for j in range(self._height):
                if self._terrain[i][j] == []:
                    free_placements.append(Position(i, j))
        return free_placements

terrain = Terrain(10, 10)
a = Animate()
b = Animate()
a_mgr = AnimateManager()
a_mgr.initialize_animates(terrain)

print(terrain.terrain)


741
10
задан 18 февраля 2018 в 03:02 Источник Поделиться
Комментарии
2 ответа

Приятно видеть, что некоторые хорошо написанный код приходит на проверку, а кого-то принимая время, чтобы дело о проекте, прежде чем они ныряют слишком глубоко!

Используя @property чтобы сделать "только для чтения" классы-это хорошая идея, - он делает типа "больше", но это делает интерфейс понятным и объяснимым.

Обратите внимание, что если у вас есть недвижимость, например width тогда это хорошая идея, чтобы использовать его как внутренними, так и внешними по консистенции, если у вас есть веская причина этого не делать. Например, когда вы делаете if location_x >= self._width: нормально, но если width собственность позже делает некоторые проверки, или Модификация _width ваш код может начать вести себя неожиданно.

Использование исключений для ограничения обработки-это хороший вариант тоже - хотя вам не нужно pass после их повышения. Я также предлагаю создать свои собственные конкретные типы исключений, например:

class OccupiedException(Exception):
"""Square is already occupied."""

Таким образом, вы никогда не делать except Exception и поймать вещи, которые вы не хотели.

Расщепление является хорошей идеей, если код становится слишком большим, или если вы хотите использовать только подмножество пакета где-то (т. е. from x import y). pylint считает, что 1000 строк-это слишком много для одного файла, но в равной степени я бы не сделать 20 файлов, каждый с 5 линиями, как это делает следующий код сложнее. На данный момент она кажется большой, разделить его, но не волнуйтесь, пока он получает там.

Несколько других вещей:

for i, line in enumerate(self._terrain):
self._terrain[i] = ...

Вам не нужно использовать здесь перечислять, поскольку self._terrain[i] и line это одно и то же. Однако также необходимо учитывать, что изменение списка при переборе он потенциально опасен - вы лучше делать что-то вроде создания нового списка, как вы перебираете, а потом делаю перезапись оригинала, когда цикл завершен.

Я не копнул глубоко в логику различных методов, но я замечаю, что Animate имеет 2 класса атрибуты, на которые можно подняться и изменять в этом классе, и от AnimateManager:

animate_list = []
new_animate_id = 0

Просто отметить, что такой подход к ведению глобальный идентификатор регистр не будет работать, если вы хотите сделать что-нибудь с потоками позже - см. https://stackoverflow.com/questions/1071935/i-need-a-python-class-that-keep-tracks-of-how-many-times-it-is-instantiated#1071939 для некоторых идей о работе с этим.

Небольшие чаевые, но вы, возможно, захотите рассмотреть вопрос об использовании pprint вместо print для того, чтобы вывести местности во время отладки, так как это даст лучшее представление о том, что сетка выглядит. Установка __repr__ на занятия также помогут, чтобы было понятнее - например, что-то вроде следующего в Animate класс:

def __repr__(self):
return "ANIMATE:{}".format(self._animate_id)

Дайте мне знать, если что-то непонятно, или если вы имеете любые вопросы о все, что я предложил.

8
ответ дан 18 февраля 2018 в 04:02 Источник Поделиться

матч был очень хороший выбег, и я хотел бы добавить одну вещь. У вас есть позиция, которая имеет две неизменные числа, как своих членов. Если вы не собираетесь нужны более сложные функции (или даже если вы не уверены, я бы с YAGNI на этом), вы можете использовать namedtuple:

from collections import namedtuple

Position = namedtuple('Position', ('x','y'))

p = Position(5, 3)

print(p.x) # 5
print(p.y) # 3
print(p) # Position(x=5, y=3)

q = Position(5, 3)
print(p == q) # True

p.x = 6 # AttributeError: can't set attribute

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

6
ответ дан 19 февраля 2018 в 04:02 Источник Поделиться