Книга «Программируем на Python» / Блог компании Издательский дом «Питер» / Хабрахабр

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("ttГибель пришельцаn")
hero = Player()
invader = Alien()
hero.blast(invader)
input("nnНажмите 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("nnНажмите Enter, чтобы выйти.")


Закономерно отображается .

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

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

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

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

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

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

» Оглавление

» Отрывок

Для Хаброжителей скидка 25% по купону — Python

Источник