image Привет, Хаброжители! Вы наверняка слышали о книге Майкла Доусона (Michael Dawson), в которой он учит языку программирования Python тем же самым путем, то есть через программирование несложных игр. Учиться, создавая свои собственные развлекательные программы.

Несмотря на развлекательный характер примеров, демонстрируется вполне серьезная техника программирования. Ниже приведен отрывок из главы «Объектно-ориентированное программирование. Игра «Блек-джек»»

Знакомство с игрой «Блек-джек»


Проект, над разработкой которого мы потрудимся в этой главе, представляет собой упрощенную версию карточной игры «Блек-джек». Игровой процесс идет так: участники получают карты, с которыми связаны определенные числовые значения — очки, и каждый участник стремится набрать 21 очко, но не больше. Количество очков, соответствующих карте с цифрой, равно ее номиналу; валет, дама и король идут за 10 очков, а туз — за 1 или 11 (в зависимости от того, как выгоднее для игрока).

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

Затем каждому игроку по очереди предоставляется возможность тянуть дополнительные карты. Игрок может брать их из перемешанной колоды до тех пор, пока ему угодно и пока сумма очков на руках у него не превысила 21. При превышении, которое называется перебором, участник проигрывает. Если все перебрали, то компьютер выводит свою вторую карту и начинает новый раунд. Если же один или несколько участников остались в игре, то раунд еще не закончен. Дилер открывает свою вторую карту и, по общему правилу «Блек-джека», тянет дополнительные карты для себя до тех пор, пока сумма его очков не будет равна 17 или больше. Если дилер, в нашем случае — компьютер, совершает перебор, то победу одерживают все участники, оставшиеся в игре. Если нет, то сумма очков каждого из участников сравнивается с очками, которые набрал компьютер. Набравший больше очков (человек или компьютер) побеждает. При одинаковой сумме очков объявляется ничья между компьютером и одним или несколькими участниками.

Игровой процесс отражен на рис. 9.1.

image

Отправка и прием сообщений


Объектно-ориентированная программа — это своего рода экологическая система, в которой объекты — живые организмы. Чтобы поддерживать биоценоз в порядке, его обитатели должны взаимодействовать; так же происходит и в ООП. Программа не может быть полезна, если объекты в ней не взаимодействуют каким-либо удачно заданным способом. В терминах ООП такое взаимодействие называется отправкой сообщений. На практике объекты всего лишь вызывают методы друг друга. Это хотя и не совсем вежливо, но, во всяком случае, лучше, чем если бы объектам пришлось напрямую обращаться к атрибутам друг друга.

Знакомство с программой «Гибель пришельца»


Программа изображает ситуацию из компьютерной игры, в которой игрок стреляет в инопланетного агрессора. Все буквально так и происходит: игрок стреляет, а пришелец умирает (но, правда, успевает напоследок произнести несколько высокопарных слов). Это реализовано с помощью посылки сообщения от одного объекта другому. Результат работы программы отражен на рис. 9.2.

image

Говоря технически, программа создает hero — экземпляр класса Player и invader — экземпляр класса Alien. При вызове метода blast() объекта hero с аргументом invader этот объект вызывает метод die() объекта invader. Другими словами, когда герой стреляет в пришельца, это значит, что объект «герой» посылает объекту «пришелец» сообщение с требованием умереть. Этот обмен сообщениями показан на рис. 9.3.
image

Код этой программы вы можете найти на сайте-помощнике (courseptr.com/downloads) в папке Chapter 9. Файл называется alien_blaster.py:

# Гибель пришельца
# Демонстрирует взаимодействие объектов
class Player(object):
        """ Игрок в экшен-игре. """
        def blast(self, enemy):
             print("Игрок стреляет во врага.\n")
             enemy.die()
class Alien(object):
        """ Враждебный пришелец-инопланетянин в экшен-игре. """
        def die(self):
             print("Тяжело дыша, пришелец произносит: 'Ну, вот и все. Спета моя песенка. \n"             "Уже и в глазах темнеет… Передай полутора миллионам моих личинок, что я любил их… \n"             "Прощай, безжалостный мир.'")
# Основная часть программы
print("\t\tГибель пришельца\n")
hero = Player()
invader = Alien()
hero.blast(invader)
input("\n\nНажмите Enter, чтобы выйти.")

Отправка сообщения


Прежде чем один объект сможет послать сообщение другому, надо, чтобы объектов было два! Столько их и создано в основной части моей программы: сначала объект класса Player, с которым связывается переменная hero, а потом объект класса Alien, с которым связывается переменная invader.

Интересное происходит в строке кода, следующей за этими двумя. Командой hero.blast(invader) я вызываю метод blast() объекта hero и передаю ему как аргумент объект invader. Изучив объявление blast(), вы можете увидеть, что этот метод принимает аргумент в параметр enemy. Поэтому при вызове blast() его внутренняя переменная enemy ссылается на объект класса Alien. Выведя на экран текст, метод blast() командой enemy.die() вызывает метод die() объекта Alien. Таким образом, по существу, экземпляр класса Player посылает экземпляру класса Alien сообщение, которым вызывает его метод die().

Прием сообщения


Объект invader принимает сообщение от объекта hero закономерным образом: вызывается метод die() и пришелец умирает, сказав самому себе душераздирающее надгробное слово.

Сочетание объектов


Обычно в жизни сложные вещи строятся из более простых. Так, гоночную автомашину можно рассматривать как единый объект, который, однако, составлен из других, более простых объектов: корпуса, двигателя, колес и т. д. Иногда встречается важный частный случай: объекты, представляющие собой наборы других объектов. Таков, например, зоопарк, который можно представить как набор животных. Эти типы отношений возможны и между программными объектами в ООП. К примеру, ничто не мешает написать класс Drag_Racer, представляющий гоночный автомобиль; у объектов этого класса будет атрибут engine, ссылающийся на объект Race_Engine (двигатель). Можно написать и класс Zoo, представляющий зоопарк, у объектов которого будет атрибут animals — список животных (объектов класса Animal). Сочетание объектов, как в этих примерах, позволяет строить сложные объекты из простых.

Знакомство с программой «Карты»


В программе «Карты» объекты представляют отдельные игральные карты, которыми можно воспользоваться для любой из игр от «Блек-джека» до «Подкидного дурака» (в зависимости от того, каковы ваши вкусы и денежные активы). Далее в той же программе строится объект «рука» (Hand), представляющий набор карт одного игрока; это не что иное, как список объектов-карт. Результат работы программы показан на рис. 9.4.

image

Я буду разбирать код небольшими порциями, однако вы можете ознакомиться и с целой программой на сайте-помощнике (www.courseptr.com/downloads) в папке Chapter 9. Файл называется playing_cards.py.

Создание класса Card


Первым делом в этой программе я создаю класс Card, объекты которого будут представлять игральные карты.

# Карты
# Демонстрирует сочетание объектов
class Card(object):
        """ Одна игральная карта. """
        RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
        SUITS = ["c", "d", "h", "s"]
        def __init__(self, rank, suit):
             self.rank = rank
             self.suit = suit
        def __str__(self):
             rep = self.rank + self.suit
             return rep

У каждого объекта класса Card есть атрибут rank, значение которого — достоинство карты. Атрибут класса RANKS содержит все возможные значения: туз («A»), карты с номиналами от 2 до 10, валет («J»), дама («Q») и король («K»). У каждой карты есть также атрибут suit, представляющий масть карты. Все его возможные значения содержит атрибут класса SUITS: «c» (clubs) — трефы, «d» (diamonds) — бубны, «h» (hearts) — червы и, наконец, «s» (spades) — пики.

Например, объект со значением rank, равным «A», и значением suit, равным «d», представляет бубновый туз. Значения этих двух атрибутов, соединенных в единую строку, возвращает для вывода на печать специальный метод __str__().

Создание класса Hand


Следующее, что я должен сделать в программе, — создать класс Hand, экземпляры которого будут представлять наборы объектов-карт:

class Hand(object):
        """ 'Рука': набор карт на руках у одного игрока. """
        def __init__(self):
             self.cards = []
        def __str__(self):
             if self.cards:
                rep = ""
                for card in self.cards:
                     rep += str(card) + " "
             else:
                rep = "<пусто>"
             return rep
        def clear(self):
             self.cards = []
        def add(self, card):
             self.cards.append(card)
        def give(self, card, other_hand):
             self.cards.remove(card)
             other_hand.add(card)

У нового объекта класса Hand появляется атрибут cards, представляющий собой список карт. Таким образом, атрибут единичного объекта — список, который может содержать сколь угодно много других объектов.

Специальный метод __str__() возвращает одной строкой всю «руку». Метод последовательно берет все объекты класса Card и соединяет их строковые представления. Если в составе объекта Hand нет ни одной карты, то будет возвращена строка <пусто>.

Метод clear() очищает список карт: атрибут «руки» cards приравнивается к пустому списку. Метод add() добавляет объект к списку cards. Метод give() удаляет объект из списка cards, принадлежащего данной «руке», и добавляет тот же объект в набор карт другого объекта класса Hand (для этого вызывается его метод add()). Иными словами, первый объект Hand посылает второму объекту Hand сообщение: добавить в атрибут cards данный объект Card.

Применение объектов-карт


В основной части программы я создаю и вывожу на экран пять объектов класса Card:

# основная часть
card1 = Card(rank = "A", suit = "c")
print("Вывожу на экран объект-карту:")
print(card1)
card2 = Card(rank = "2", suit = "c")
card3 = Card(rank = "3", suit = "c")
card4 = Card(rank = "4", suit = "c")
card5 = Card(rank = "5", suit = "c")
print("\nВывожу еще четыре карты:")
print(card2)
print(card3)
print(card4)
print(card5)

У первого из созданных экземпляров класса Card атрибут rank равен «A», а атрибут suit — «c» (туз треф). На экране этот объект отображается в виде Ac; вид других карт аналогичен.

Сочетание объектов-карт в объекте Hand


Теперь я создам экземпляр класса Hand, свяжу его с переменной my_hand и выведу информацию о нем на экран:

my_hand = Hand()
print("\nПечатаю карты, которые у меня на руках до раздачи:")
print(my_hand)

Поскольку атрибут cards этого объекта пока равен пустому списку, на экране будет напечатано <пусто>.

Добавлю в my_hand пять объектов класса Card и снова выведу объект на экран:

my_hand.add(card1)
my_hand.add(card2)
my_hand.add(card3)
my_hand.add(card4)
my_hand.add(card5)
print("\nПечатаю пять карт, которые появились у меня на руках:")
print(my_hand)

На экране отобразится текст Ac 2c 3c 4c 5c.

А сейчас я создам еще один экземпляр класса Hand под названием your_hand. Применив к my_hand метод give(), передам из «своей руки» в «вашу руку» две карты и затем выведу содержимое обеих «рук» на экран:

your_hand = Hand()
my_hand.give(card1, your_hand)
my_hand.give(card2, your_hand)
print("\nПервые две из моих карт я передал вам.")
print("Теперь у вас на руках:")
print(your_hand)
print("А у меня на руках:")
print(my_hand)

Как и следовало ожидать, your_hand имеет вид Ac 2c, а my_hand — 3c 4c 5c.

В конце программы я вызову метод clear() объекта my_hand и напечатаю его на экране еще раз:

my_hand.clear()
print("\nУ меня на руках после того, как я сбросил все карты:")
print(my_hand)
input("\n\nНажмите Enter, чтобы выйти.")

Закономерно отображается <пусто>.

Создание новых классов с помощью наследования


Одна из ключевых идей ООП — наследование. Благодаря ему можно основать вновь создаваемый класс на существующем, причем новый класс автоматически получит (унаследует) все методы и атрибуты старого. Это как если бы за весь тот объем работы, который ушел на создание первоначального класса, не пришлось платить!

Расширение класса через наследование


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

Вообразим, например, что ваш класс Drag_Racer задает объект — гоночный автомобиль с методами stop() и go(). Можно на его основе создать новый класс, описывающий особый тип гоночных машин с функцией очистки ветрового стекла (на скорости до 400 км/ч о стекло будут все время биться насекомые). Этот новый класс автоматически унаследует методы stop() и go() от класса Drag_Racer. Значит, вам останется объявить всего один новый метод для очистки ветрового стекла, и класс будет готов.

» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок

Для Хаброжителей скидка 25% по купону — Python
Поделиться с друзьями
-->

Комментарии (21)


  1. tmnhy
    06.04.2017 12:20
    +2

    Код отформатируйте, пожалуйста.

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


    1. StillDreamer
      06.04.2017 13:15
      +1

      Всё содержимое — в таком ключе. На мой взгляд, книга адресована начинающим знакомство с программированием в целом.


      1. tmnhy
        06.04.2017 13:28

        Абстрагировано от ЯП, вот такое не дотягивает до обучающего материала в 2017 году:

        my_hand.add(card1)
        my_hand.add(card2)
        my_hand.add(card3)
        my_hand.add(card4)
        my_hand.add(card5)


        1. kgbplus
          06.04.2017 14:29

          Вам не приходило в голову, что это пример из главы про циклы?


          1. tmnhy
            06.04.2017 14:59

            Если в плане того, как не надо делать, да, наверное в главе про циклы ему и место.


            1. Vladal
              07.04.2017 13:46

              Всё правильно.
              Так делают в некоторых учебниках.
              Сейчас написано так, а в следующем абзаце обучение циклу: «а теперь, дорогие дети, чтобы не писать каждый раз одно и то же, поместим карты в массив и переберем их в цикле, написав вместо 5 строк только одну my_hand.add(item)».


  1. nightvich
    06.04.2017 12:41
    +1

    Вообще, «Питер» переводит хорошие книги, но форматирование кода и опечатки встречаются повсеместно. Понятно, что редактор и переводчик далеки от тематики книг, однако можно нанять человека сведующего в теме для избежания такого количества опечаток и ошибок форматирования. В англоязычных изданиях такого почти не встречается. А у нас — «и так сойдёт».


  1. kgbplus
    06.04.2017 12:52
    +1

    Хорошая книга, один из классических трудов. Рекомендую новичкам


  1. FSA
    06.04.2017 18:23

    Взял бы, но в электронном виде. В бумажном читать неудобно, да ещё и как-то получить надо.


    1. wedoca
      06.04.2017 20:10

      За хорошую и даже среднюю книгу не жалко денег. Но в бумажном варианте вообще не вариант.
      На дворе 2017 однако.


      1. ph_piter
        07.04.2017 13:53

        К сожалению, элеронных прав нет.


  1. mihmig
    07.04.2017 07:21

    Хорошо, с форматированием кода отступами я уже почти смирился, но объясните мне — почему метод класса объявляется с двумя параметрами
    def blast(self, enemy):
    а вызывается с одним:
    hero.blast(invader)


    1. Roman_Kh
      07.04.2017 10:24

      И вызывается с двумя — первым аргументом передается hero.
      При этом бывают и другие методы, которым не передается self.


    1. Dark_kot
      07.04.2017 11:15

      Возможно, планировали что-то вроде


    1. Stalkash
      07.04.2017 11:15

      Потому что это Python — при вызове метода у объекта первым параметром всегда неявно передается ссылка на сам объект, поэтому наличие параметра self обязательно в любом методе.


      1. trnc
        10.04.2017 12:07

        Не совсем правильно. Есть еще статические и методы класса.


    1. noth
      07.04.2017 11:15

      потому что вызов метода у объекта является вызовом метода класса, а не объекта, потому что все определения методов всё-таки хранятся у объекта-класса, а не у кождого объекта по отдельности.

      class SomeClass():
          def some_method(self, param):
              pass
      


      здесь SomeClass — это тоже объект по сути, который представляет собой описание класса, some_method — это атрибут объекта SomeClass.

      создаём экземпляр класса:
      var = SomeClass()
      


      var — это тоже объект — экземпляр класса SomeClass. При этом, описание метода some_method не является атрибутом объкта var.

      Вызов метода some_method происходит следующим образом:
      SomeClass.some_method(var, param='value')
      # по сути - мы обращаемся к атрибуту класса - определению метода, и в качестве первого агрумента передаём экземпляр класса
      # эквивалентная но более простая запись этого:
      var.some_method(param='value')
      


      в питоне всё является объектами, в том числе и описания классов и их методов, и с этой точки зрения определение self-параметра не кажется чем-то странным


  1. tmnhy
    07.04.2017 08:58

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


  1. AbstractGaze
    07.04.2017 11:16

    Подскажите, а эта редакция отличается чем то от издания 2014 года? Или только номер года изменили?


    1. ph_piter
      07.04.2017 13:47

      Не отличается.


  1. hp6812er
    10.04.2017 12:07

    Уважаемое издательство, если у вас пока не свёрстан план на этот год, будьте добры переведите «Automate the Boring Stuff with Python» автор Al Sweigart.
    Куплю с великим удовольствием.