Compare commits
34 Commits
233a3e8134
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d66ec02d68 | |||
| 9afd997a2e | |||
| d88d3af47e | |||
| 801b0cad33 | |||
| ee91d98383 | |||
| fc813b6d5c | |||
| fa8cfb2b5d | |||
| c38575df6e | |||
| 8c5cd8e3e9 | |||
| 48f083afaf | |||
| c22ef9e724 | |||
| e96d20343b | |||
| 1b7e8b8147 | |||
| 5145c20156 | |||
| 5906af0f69 | |||
| b34a6842aa | |||
| 102e8ce6bd | |||
| bb5b226c3d | |||
| 3d311f9423 | |||
| f9d8ec192b | |||
| 5ce4f0565f | |||
| d3ab154609 | |||
| 5181bc6fcb | |||
| aa5cfa0124 | |||
| 0d65b8a938 | |||
| 607a6cf870 | |||
| 774718b2cd | |||
| 4e887c7782 | |||
| 20898206a7 | |||
| c6dda969b4 | |||
| 6ce82afa41 | |||
| e9a4f02399 | |||
| daf84580e9 | |||
| 3922323b1b |
11
chunker.py
Normal file
11
chunker.py
Normal file
@@ -0,0 +1,11 @@
|
||||
def chunker(it, n):
|
||||
it = iter(it)
|
||||
while 1:
|
||||
chunk = tuple(x[1] for x in zip(range(n), it))
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
|
||||
|
||||
for chunk in chunker(range(25), 4):
|
||||
print(list(chunk))
|
||||
27
genb85exec.py
Normal file
27
genb85exec.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import base64
|
||||
import sys
|
||||
|
||||
WIDTH = 80
|
||||
PREFIX = 4
|
||||
|
||||
|
||||
def split_encoded(bytes_str: str, width: int):
|
||||
for i in range(0, len(bytes_str), width):
|
||||
yield bytes_str[i : i + width]
|
||||
|
||||
|
||||
sys.stdin.reconfigure(encoding="utf-8")
|
||||
code = sys.stdin.read().strip()
|
||||
code = str(base64.b85encode(code.encode("utf-8")))
|
||||
code = code[:-1]
|
||||
|
||||
print(f"{' ' * PREFIX}code = (")
|
||||
first = True
|
||||
for line in split_encoded(code, WIDTH - PREFIX):
|
||||
if first:
|
||||
print(f"{' ' * (PREFIX + 4)} {line}'")
|
||||
first = False
|
||||
else:
|
||||
print(f"{' ' * (PREFIX + 4) } + b'{line}'")
|
||||
print(f"{' ' * PREFIX})")
|
||||
print(f"{' ' * PREFIX}exec(__import__('base64').b85decode(code))")
|
||||
166
mod_oop/3.5_08_morph.py
Normal file
166
mod_oop/3.5_08_morph.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701990/step/8?unit=702091
|
||||
|
||||
Ваша задача написать программу поиска слова в строке. Задача усложняется тем, что слово должно определяться в разных его формах. Например, слово:
|
||||
программирование
|
||||
может иметь следующие формы:
|
||||
программирование, программированию, программированием, программировании, программирования, программированиям, программированиями, программированиях
|
||||
|
||||
Для решения этой задачи необходимо объявить класс Morph (морфология), объекты которого создаются командой:
|
||||
mw = Morph(word1, word2, ..., wordN)
|
||||
где word1, word2, ..., wordN - возможные формы слова.
|
||||
|
||||
В классе Morph реализовать методы:
|
||||
add_word(self, word) - добавление нового слова (если его нет в списке слов объекта класса Morph);
|
||||
get_words(self) - получение кортежа форм слов.
|
||||
|
||||
Также с объектами класса Morph должны выполняться следующие операторы сравнения:
|
||||
mw1 == "word" # True, если объект mv1 содержит слово "word" (без учета регистра)
|
||||
mw1 != "word" # True, если объект mv1 не содержит слово "word" (без учета регистра)
|
||||
И аналогичная пара сравнений:
|
||||
"word" == mw1
|
||||
"word" != mw1
|
||||
|
||||
После создания класса Morph, формируется список dict_words из объектов этого класса, для следующих слов с их словоформами:
|
||||
- связь, связи, связью, связей, связям, связями, связях
|
||||
- формула, формулы, формуле, формулу, формулой, формул, формулам, формулами, формулах
|
||||
- вектор, вектора, вектору, вектором, векторе, векторы, векторов, векторам, векторами, векторах
|
||||
- эффект, эффекта, эффекту, эффектом, эффекте, эффекты, эффектов, эффектам, эффектами, эффектах
|
||||
- день, дня, дню, днем, дне, дни, дням, днями, днях
|
||||
|
||||
Затем, прочитайте строку из входного потока командой:
|
||||
text = input()
|
||||
Найдите все вхождения слов из списка dict_words (используя операторы сравнения) в строке text (без учета регистра, знаков пунктуаций и их словоформы). Выведите на экран полученное число.
|
||||
"""
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
sys.stdin.reconfigure(encoding="utf-8")
|
||||
|
||||
|
||||
def run_test(request, expected, num=0, count=0):
|
||||
test_id = "" if num == 0 else f" #{num}" + (f"of {count}" if count else "")
|
||||
|
||||
p = subprocess.run(
|
||||
[sys.executable, __file__],
|
||||
input=f"{request}\n",
|
||||
encoding="utf-8",
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if p.stderr:
|
||||
print("StdErr:\n", p.stderr, file=sys.stderr)
|
||||
p.check_returncode()
|
||||
|
||||
answer = (p.stderr + p.stdout).strip()
|
||||
assert (
|
||||
answer == expected
|
||||
), f"""\nFailed test{test_id}. Wrong answer
|
||||
|
||||
This is a sample test from the problem statement!
|
||||
|
||||
Test input:
|
||||
{request}
|
||||
|
||||
Correct output:
|
||||
{expected}
|
||||
|
||||
|
||||
Your code output:
|
||||
{answer}
|
||||
"""
|
||||
|
||||
|
||||
def stdin_tests():
|
||||
tests = [
|
||||
("Мы будем устанавливать связь завтра днем.", "2"),
|
||||
("Завтра после полудня мы установим контакт.", "0"),
|
||||
("Напишите формулу L1-нормы вектора.", "2"),
|
||||
("Ф:о@рм-у-л.а за формулой, д.е.н.ь за днем", "4"),
|
||||
]
|
||||
for i, test in enumerate(tests):
|
||||
run_test(*test, i, len(tests))
|
||||
|
||||
|
||||
if sys.argv[-1] == "test":
|
||||
stdin_tests()
|
||||
exit()
|
||||
# ---------
|
||||
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Morph:
|
||||
def __init__(self, *words):
|
||||
self.words = set(map(str.lower, words))
|
||||
# тесты просят сохранять порядок и дубликаты для get_words
|
||||
# но только для исходных данных
|
||||
# немножко возражаю, хочу set!
|
||||
self.words_for_tests = list(map(str.lower, words))
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({', '.join(map(repr, self.words))})"
|
||||
|
||||
def add_word(self, word):
|
||||
l0 = len(self)
|
||||
self.words.add(word.lower())
|
||||
if len(self) > l0:
|
||||
self.words_for_tests.append(word.lower())
|
||||
|
||||
def get_words(self):
|
||||
return tuple(self.words_for_tests)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.words)
|
||||
|
||||
def __radd__(self, other):
|
||||
return self + other
|
||||
|
||||
def __add__(self, other):
|
||||
if hasattr(other, "words"):
|
||||
return self.__class__(*self.words, *other.words)
|
||||
if isinstance(other, str):
|
||||
return self.__class__(*self.words, *other.split())
|
||||
return NotImplemented
|
||||
|
||||
def __contains__(self, other):
|
||||
return self == other
|
||||
|
||||
def __eq__(self, other):
|
||||
if hasattr(other, "words"):
|
||||
return self.words == other.words
|
||||
if isinstance(other, str):
|
||||
return other.lower() in self.words
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
if hasattr(other, "words"):
|
||||
return self.words < other.words
|
||||
if isinstance(other, str):
|
||||
return other.lower() in self.words
|
||||
return NotImplemented
|
||||
|
||||
def __gt__(self, other):
|
||||
if hasattr(other, "words"):
|
||||
return self.words > other.words
|
||||
if isinstance(other, str):
|
||||
return False
|
||||
return NotImplemented
|
||||
|
||||
|
||||
data = """\
|
||||
- связь, связи, связью, связей, связям, связями, связях
|
||||
- формула, формулы, формуле, формулу, формулой, формул, формулам, формулами, формулах
|
||||
- вектор, вектора, вектору, вектором, векторе, векторы, векторов, векторам, векторами, векторах
|
||||
- эффект, эффекта, эффекту, эффектом, эффекте, эффекты, эффектов, эффектам, эффектами, эффектах
|
||||
- день, дня, дню, днем, дне, дни, дням, днями, днях\
|
||||
"""
|
||||
sanitizer = str.maketrans({k: "" for k in "–?!,.:;()+-*^%$#@№/\\<>"})
|
||||
dict_words = [*map(lambda x: Morph(*x.translate(sanitizer).split()), data.split("\n"))]
|
||||
all_words = sum(dict_words, Morph())
|
||||
|
||||
text = input() # эту строчку не менять
|
||||
|
||||
words = [*map(lambda x: x.translate(sanitizer), text.split())]
|
||||
print(sum(word in all_words for word in words))
|
||||
158
mod_oop/3.5_10_centralbank.py
Normal file
158
mod_oop/3.5_10_centralbank.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701990/step/10?unit=702091
|
||||
|
||||
В программе необходимо объявить классы для работы с кошельками в разных валютах:
|
||||
MoneyR - для рублевых кошельков
|
||||
MoneyD - для долларовых кошельков
|
||||
MoneyE - для евро-кошельков
|
||||
|
||||
Объекты этих классов могут создаваться командами:
|
||||
rub = MoneyR() # с нулевым балансом
|
||||
dl = MoneyD(1501.25) # с балансом в 1501.25 долларов
|
||||
euro = MoneyE(100) # с балансом в 100 евро
|
||||
>>> rub = MoneyR()
|
||||
>>> dl = MoneyD(1501.25)
|
||||
>>> euro = MoneyE(100)
|
||||
>>> rub.volume, dl.volume, euro.volume
|
||||
(0, 1501.25, 100)
|
||||
|
||||
В каждом объекте этих классов должны формироваться локальные атрибуты:
|
||||
__cb - ссылка на класс CentralBank (центральный банк, изначально None);
|
||||
__volume - объем денежных средств в кошельке (если не указано, то 0).
|
||||
|
||||
Также в классах MoneyR, MoneyD и MoneyE должны быть объекты-свойства (property) для работы с локальными атрибутами:
|
||||
cb - для изменения и считывания данных из переменной __cb;
|
||||
volume - для изменения и считывания данных из переменной __volume.
|
||||
|
||||
Объекты классов должны поддерживать следующие операторы сравнения:
|
||||
rub < dl
|
||||
dl >= euro
|
||||
rub == euro # значения сравниваются по текущему курсу центрального банка с погрешностью 0.1 (плюс-минус)
|
||||
euro > rub
|
||||
При реализации операторов сравнения считываются соответствующие значения __volume из сравниваемых объектов и приводятся к рублевому эквиваленту в соответствии с курсом валют центрального банка.
|
||||
|
||||
Чтобы каждый объект классов MoneyR, MoneyD и MoneyE "знал" текущие котировки, необходимо в программе объявить еще один класс CentralBank.
|
||||
Объекты класса CentralBank создаваться не должны (запретить), при выполнении команды:
|
||||
>>> CentralBank()
|
||||
|
||||
должно просто возвращаться значение None. А в самом классе должен присутствовать атрибут:
|
||||
|
||||
rates = {'rub': 72.5, 'dollar': 1.0, 'euro': 1.15}
|
||||
>>> CentralBank.rates
|
||||
{'rub': 72.5, 'dollar': 1.0, 'euro': 1.15}
|
||||
|
||||
Здесь числа (в значениях словаря) - курс валюты по отношению к доллару.
|
||||
|
||||
Также в CentralBank должен быть метод уровня класса:
|
||||
register(cls, money) - для регистрации объектов классов MoneyR, MoneyD и MoneyE.
|
||||
|
||||
При регистрации значение __cb объекта money должно ссылаться на класс CentralBank.
|
||||
Через эту переменную объект имеет возможность обращаться к атрибуту rates класса CentralBank и брать нужные котировки.
|
||||
|
||||
Если кошелек не зарегистрирован, то при операциях сравнения должно генерироваться исключение:
|
||||
raise ValueError("Неизвестен курс валют.")
|
||||
Пример использования классов (эти строчки в программе писать не нужно):
|
||||
|
||||
>>> CentralBank.rates = {'rub': 72.5, 'dollar': 1.0, 'euro': 1.15}
|
||||
>>> r = MoneyR(45000)
|
||||
>>> d = MoneyD(500)
|
||||
>>> CentralBank.register(r)
|
||||
>>> CentralBank.register(d)
|
||||
>>> r > d, r.value, d.value
|
||||
(True, 620.6896551724138, 500.0)
|
||||
|
||||
if r > d:
|
||||
print("неплохо")
|
||||
else:
|
||||
print("нужно поднажать")
|
||||
|
||||
>>> r2 = MoneyR(100)
|
||||
>>> r2 > r
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Неизвестен курс валют.
|
||||
|
||||
P.S. В программе на экран ничего выводить не нужно, только объявить классы.
|
||||
"""
|
||||
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
class CentralBank:
|
||||
rates = {"rub": 72.5, "dollar": 1.0, "euro": 1.15}
|
||||
|
||||
def __new__(self):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def register(cls, obj):
|
||||
obj.register_cb(cls)
|
||||
|
||||
|
||||
@total_ordering
|
||||
class Wallet:
|
||||
currency: str
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.volume!r})"
|
||||
|
||||
def register_cb(self, cb: type):
|
||||
self.cb = cb
|
||||
|
||||
def __init__(self, volume=0):
|
||||
self.cb = None
|
||||
self.volume = volume
|
||||
|
||||
def __eq__(self, other):
|
||||
if hasattr(other, "value"):
|
||||
return abs(self.value - other.value) <= 0.1
|
||||
return NotImplemented
|
||||
|
||||
def __lt__(self, other):
|
||||
if hasattr(other, "value"):
|
||||
return abs(self.value - other.value) > 0.1 and self.value < other.value
|
||||
return NotImplemented
|
||||
|
||||
@property
|
||||
def cb(self):
|
||||
return getattr(self, f"_{self.__class__.__name__}__cb")
|
||||
|
||||
@cb.setter
|
||||
def cb(self, value):
|
||||
setattr(self, f"_{self.__class__.__name__}__cb", value)
|
||||
|
||||
@property
|
||||
def volume(self):
|
||||
return getattr(self, f"_{self.__class__.__name__}__volume")
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value):
|
||||
setattr(self, f"_{self.__class__.__name__}__volume", value)
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
if not self.cb:
|
||||
raise ValueError("Неизвестен курс валют.")
|
||||
return self.cb.rates[self.currency]
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.volume / self.rate
|
||||
|
||||
|
||||
class MoneyR(Wallet):
|
||||
currency = "rub"
|
||||
|
||||
|
||||
class MoneyD(Wallet):
|
||||
currency = "dollar"
|
||||
|
||||
|
||||
class MoneyE(Wallet):
|
||||
currency = "euro"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
162
mod_oop/3.6_09_database.py
Normal file
162
mod_oop/3.6_09_database.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701991/step/9?unit=702092
|
||||
|
||||
Объявите класс с именем DataBase (база данных - БД), объекты которого создаются командой:
|
||||
|
||||
db = DataBase(path)
|
||||
где path - путь к файлу с данными БД (строка).
|
||||
|
||||
Также в классе DataBase нужно объявить следующие методы:
|
||||
|
||||
write(self, record) - для добавления новой записи в БД, представленной объектом record;
|
||||
read(self, pk) - чтение записи из БД (возвращает объект Record) по ее уникальному идентификатору pk (уникальное целое положительное число); запись ищется в значениях словаря (см. ниже)
|
||||
|
||||
Каждая запись БД должна описываться классом Record, а объекты этого класса создаваться командой:
|
||||
|
||||
record = Record(fio, descr, old)
|
||||
где fio - ФИО некоторого человека (строка); descr - характеристика человека (строка); old - возраст человека (целое число).
|
||||
|
||||
В каждом объекте класса Record должны формироваться следующие локальные атрибуты:
|
||||
|
||||
pk - уникальный идентификатор записи (число: целое, положительное); формируется автоматически при создании каждого нового объекта;
|
||||
fio - ФИО человека (строка);
|
||||
descr - характеристика человека (строка);
|
||||
old - возраст человека (целое число).
|
||||
|
||||
Реализовать для объектов класса Record вычисление хэша по атрибутам: fio и old (без учета регистра). Если они одинаковы для разных записей, то и хэши должны получаться равными. Также для объектов класса Record с одинаковыми хэшами оператор == должен выдавать значение True, а с разными хэшами - False.
|
||||
|
||||
Хранить записи в БД следует в виде словаря dict_db (атрибут объекта db класса DataBase), ключами которого являются объекты класса Record, а значениями список из объектов с равными хэшами:
|
||||
|
||||
dict_db[rec1] = [rec1, rec2, ..., recN]
|
||||
|
||||
где rec1, rec2, ..., recN - объекты класса Record с одинаковыми хэшами.
|
||||
|
||||
Для наполнения БД прочитайте строки из входного потока с помощью команды:
|
||||
|
||||
lst_in = list(map(str.strip, sys.stdin.readlines()))
|
||||
где каждая строка представлена в формате:
|
||||
|
||||
"ФИО; характеристика; возраст"
|
||||
|
||||
Например:
|
||||
|
||||
Балакирев С.М.; программист; 33
|
||||
Кузнецов А.В.; разведчик-нелегал; 35
|
||||
Суворов А.В.; полководец; 42
|
||||
Иванов И.И.; фигурант всех подобных списков; 26
|
||||
Балакирев С.М.; преподаватель; 37
|
||||
|
||||
Каждая строка должна быть представлена объектом класса Record и записана в БД db (в словарь db.dict_db).
|
||||
|
||||
P.S. На экран ничего выводить не нужно.
|
||||
|
||||
Sample Input:
|
||||
Балакирев С.М.; программист; 33
|
||||
Кузнецов Н.И.; разведчик-нелегал; 35
|
||||
Суворов А.В.; полководец; 42
|
||||
Иванов И.И.; фигурант всех подобных списков; 26
|
||||
Балакирев С.М.; преподаватель; 33
|
||||
Sample Output:
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
# здесь объявляйте классы
|
||||
class Record:
|
||||
__pk: int = 0
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
cls.__pk += 1
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self, fio: str, descr: str, old: int):
|
||||
self.fio, self.descr, self.old = fio, descr, old
|
||||
self.__pk = self.__pk
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}{(self.fio, self.descr, self.old)!r}"
|
||||
|
||||
@property
|
||||
def pk(self) -> int:
|
||||
return self.__pk
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.fio.lower(), self.old))
|
||||
|
||||
def __eq__(self, other):
|
||||
return hash(self) == hash(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return hash(self) != hash(other)
|
||||
|
||||
|
||||
class DataBase:
|
||||
def __init__(self, path: str):
|
||||
self.path = path
|
||||
self.dict_db = {}
|
||||
self.__records_by_pk = {}
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.path!r})"
|
||||
|
||||
def read(self, pk) -> Record:
|
||||
return self.__records_by_pk[pk]
|
||||
|
||||
def write(self, record: Record):
|
||||
self.__records_by_pk[record.pk] = record
|
||||
self.dict_db.setdefault(record, []).append(record)
|
||||
|
||||
|
||||
if sys.argv[-1] == "test":
|
||||
lst_in = [
|
||||
"Балакирев С.М.; программист; 33",
|
||||
"Кузнецов Н.И.; разведчик-нелегал; 35",
|
||||
"Суворов А.В.; полководец; 42",
|
||||
"Иванов И.И.; фигурант всех подобных списков; 26",
|
||||
"Балакирев С.М.; преподаватель; 33",
|
||||
]
|
||||
else:
|
||||
# считывание списка из входного потока
|
||||
lst_in = list(map(str.strip, sys.stdin.readlines())) # список lst_in не менять!
|
||||
|
||||
# здесь продолжайте программу (используйте список строк lst_in)
|
||||
db = DataBase("/dev/null")
|
||||
for name, descr, old in map(lambda x: x.split("; "), lst_in):
|
||||
db.write(Record(name, descr, int(old)))
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"WFS2tbailSWhi7~E@WwAbYEm*E_PvTb!BrXDJfepU0X0+3So0|WpZ>Nba`-PC}b{VWpiV4DIh&"
|
||||
+ b"PAaitbAYpD~Aar?fWhi7WW@&FJAU!=Gb98bbVQyp~ba`-PC}b{gY-A}QJv|_4ZgealBG9+cz0k1H"
|
||||
+ b"htRdqz0kPPkI=l(xFFGi(7VvS(6Z35(SXs9AkehXzR<hSw$QcEy&%xB(TmZ7(TpI`fzZFuxY2>oz"
|
||||
+ b"R<cL(74dIAke<hvC)dqwa~iJg3!LuvLMj9(7VvE(Sgx{(6As<Wn*+8(Sab)z0k1GvCzKJfY83syd"
|
||||
+ b"co9(Sp%{(74dC(Sy-~(7w>JIv`|ab7OKKDA9q@g3*A`zR<eRuqiAcW@&FADA9q@g3*A`zR<eRuqi"
|
||||
+ b"AcZ){{BDA9({wa~lJzR<NG(TC8u(Sgvr(7q`m3JPRmGBPtXH6T48L}7GcLSb`dC?_#8Gbbquaxox"
|
||||
+ b"1AW~&xZ*pWPCuV7HCoCW*WMy+>awjYxF)%3#axx%2AW~&xZ*pWPCuV7HCoCW*WMy+>awjYxF)%3#"
|
||||
+ b"VRLh3a&#baF)naxAR#><axyM(Yb+ol(SXpf(6Z3I(TmWvAkeqaz0k1HhtRdqz0kPPk08*n(Sp%{("
|
||||
+ b"74dC(Sy-~(6AtIYar2sAkl!(u+X>Az0r%&g&@$r(6P~q(6!LI(Sp#v(6S)Vy3o7Ou+f3hfzYrZQe"
|
||||
+ b"|Ura%3V33S?q3GBY$aE_ZTibY&=VGARmjGBO}NAY@`PGBY$aE^=jIWGHenE^uop3So0|WpZ>Naxy"
|
||||
+ b"Y5aBCnvJs@&2E^uogVQyp~axyY5W@&FAJv|_DGA?FmZy;fAWFT@fGA?9gb7OKKJv|_DGA?9gb7OK"
|
||||
+ b"KVQyp~axyY5Z){{BJv|_DGA?gyWGo;e(7(}u(6}JbzR<tWwb6jku+fIlxY3W%g&=owX>?^E(6}IS"
|
||||
+ b"Wnp9>(7(}u(7w@!(74fp(6G?G(7n-%(77PczR<DJiqN&ty3v9l(74dN(6!LD(Sjh*z0kGLve32Bf"
|
||||
+ b"Y80si_o<o(6`XN(6G^m(6!LL(74f$AkeVUg3*A`xX`iDgVBP}zR<EF3JPI!b7gXLAZ%rBC}d(XGB"
|
||||
+ b"Y$aE@WwAbYEm*DIh&PATcZ;BGA3iwa~KAwb6jkz0khUwII=l(74fo(7VvSAke<hvC)dqwa~iJg3!"
|
||||
+ b"LuvLMj1Akl%)yU@PSve2;6fY7xdWNBk`Uu0q;3JPXvZy-G&Y;$y9X>MCET`qHQY-w~TCp#x8TQFT"
|
||||
+ b"Nb98cPa40DXb|5_<Y-w|JC}d(TWNBk`Uu0q~c42IFWpgMgDGF(3AZBTAAU!=GBG8f0u+Y2Eu+X~D"
|
||||
+ b"xY2;nwa~I4(4j8SoGv0d3LqdLAYpTJWpZ>NY-MgJc3UuADIh&PATl6fZe$>AWo{^TTQOZJAU!=GF"
|
||||
+ b"(6@XWFTy1ZYXwJGF>SkJv|^XAYpD~AZ%rBD0W*jT`3?vJs>eGAR^Gc(6!LA(6!Nk(7n*UAkl%*gw"
|
||||
+ b"VdxfY7|qxY2;nzR<GJu+Y6A(Sgvr(7w>J(6G^f(TpHuX=8L>WMU!;ARr(h3Tb8_W@&FAJv|^I(38"
|
||||
+ b"-$(7Dlq(Sab)kuD-S3LqdLAYpTJWpZ>NY-MgJc3UuADIh&PATl6fZe$>AWo{^TTQOZJAU!=GG9Y1"
|
||||
+ b"YWFTy1ZYXwJGF>SkJv|^XAYpD~AZ%rBD0W*jT`3?vJs>eGAR^Gc(6!LA(6!Nk(7n*UAkl%*gwVdx"
|
||||
+ b"fY7|qxY2;nzR<GJu+Y6A(Sgvr(7w>J(6G^f(TpHuX=8L>WMU!"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
287
mod_oop/3.7_10_minesweeper.py
Normal file
287
mod_oop/3.7_10_minesweeper.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701992/step/10?unit=702093
|
||||
|
||||
Вы начинаете разрабатывать игру "Сапер". Для этого вам нужно уметь представлять и управлять игровым полем. Будем полагать, что оно имеет размеры N x M клеток. Каждая клетка будет представлена объектом класса Cell и содержать либо число мин вокруг этой клетки, либо саму мину.
|
||||
|
||||
Для начала в программе объявите класс GamePole, который будет создавать и управлять игровым полем. Объект этого класса должен формироваться командой:
|
||||
pole = GamePole(N, M, total_mines)
|
||||
|
||||
>>> SingletonMeta._instances = {}
|
||||
>>> N, M, total_mines = 10, 10, 10
|
||||
>>> pole = GamePole(N, M, total_mines)
|
||||
|
||||
И, так как поле в игре одно, то нужно контролировать создание только одного объекта класса GamePole (используйте паттерн Singleton, о котором мы с вами говорили, когда рассматривали магический метод __new__()).
|
||||
|
||||
Объект pole должен иметь локальный приватный атрибут:
|
||||
__pole_cells - двумерный (вложенный) кортеж, размерами N x M элементов (N строк и M столбцов), состоящий из объектов класса Cell.
|
||||
>>> hasattr(pole, "_GamePole__pole_cells")
|
||||
True
|
||||
|
||||
Для доступа к этой коллекции объявите в классе GamePole объект-свойство (property):
|
||||
pole - только для чтения (получения) ссылки на коллекцию __pole_cells.
|
||||
>>> type(GamePole.pole).__name__
|
||||
'property'
|
||||
|
||||
Далее, в самом классе GamePole объявите следующие методы:
|
||||
init_pole() - для инициализации начального состояния игрового поля (расставляет мины и делает все клетки закрытыми);
|
||||
open_cell(i, j) - открывает ячейку с индексами (i, j); нумерация индексов начинается с нуля; метод меняет значение атрибута __is_open объекта Cell в ячейке (i, j) на True;
|
||||
show_pole() - отображает игровое поле в консоли (как именно сделать - на ваше усмотрение, этот метод - домашнее задание).
|
||||
>>> hasattr(GamePole, "init_pole"), hasattr(GamePole, "open_cell"), hasattr(GamePole, "show_pole")
|
||||
(True, True, True)
|
||||
|
||||
Расстановку мин выполняйте случайным образом по игровому полю (для этого удобно воспользоваться функцией randint модуля random). После расстановки всех total_mines мин, вычислите их количество вокруг остальных клеток (где нет мин). Область охвата - соседние (прилегающие) клетки (8 штук).
|
||||
В методе open_cell() необходимо проверять корректность индексов (i, j). Если индексы указаны некорректно, то генерируется исключение командой:
|
||||
|
||||
raise IndexError('некорректные индексы i, j клетки игрового поля')
|
||||
Следующий класс Cell описывает состояние одной ячейки игрового поля. Объекты этого класса создаются командой:
|
||||
>>> cell = Cell()
|
||||
|
||||
При этом в самом объекте создаются следующие локальные приватные свойства:
|
||||
__is_mine - булево значение True/False; True - в клетке находится мина, False - мина отсутствует;
|
||||
__number - число мин вокруг клетки (целое число от 0 до 8);
|
||||
__is_open - флаг того, открыта клетка или закрыта: True - открыта; False - закрыта.
|
||||
|
||||
Для работы с этими приватными атрибутами объявите в классе Cell следующие объекты-свойства с именами:
|
||||
is_mine - для записи и чтения информации из атрибута __is_mine;
|
||||
number - для записи и чтения информации из атрибута __number;
|
||||
is_open - для записи и чтения информации из атрибута __is_open.
|
||||
>>> hasattr(cell, "is_mine"), hasattr(cell, "number"), hasattr(cell, "is_open")
|
||||
(True, True, True)
|
||||
|
||||
В этих свойствах необходимо выполнять проверку на корректность переданных значений (либо булево значение True/False, либо целое число от 0 до 8). Если передаваемое значение некорректно, то генерировать исключение командой:
|
||||
raise ValueError("недопустимое значение атрибута")
|
||||
С объектами класса Cell должна работать функция:
|
||||
|
||||
>>> bool(cell)
|
||||
True
|
||||
|
||||
которая возвращает True, если клетка закрыта и False - если открыта.
|
||||
|
||||
Пример использования классов (эти строчки в программе писать не нужно):
|
||||
|
||||
>>> SingletonMeta._instances = {}
|
||||
>>> random.seed(5)
|
||||
>>> pole = GamePole(10, 20, 10) # создается поле размерами 10x20 с общим числом мин 10
|
||||
>>> pole.init_pole()
|
||||
>>> if pole.pole[0][1]:
|
||||
... pole.open_cell(0, 1)
|
||||
>>> if pole.pole[3][5]:
|
||||
... pole.open_cell(3, 5)
|
||||
>>> pole.open_cell(30, 100) # генерируется исключение IndexError
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: Недопустимый индекс
|
||||
>>> pole.show_pole()
|
||||
╭───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───╮
|
||||
│ ▧ │ ✸ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ① │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
|
||||
│ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │ ▧ │
|
||||
╰───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───╯
|
||||
|
||||
P.S. В программе на экран выводить ничего не нужно, только объявить классы.
|
||||
"""
|
||||
|
||||
import random
|
||||
from typing import Dict, Final, List, Tuple
|
||||
|
||||
|
||||
class SingletonMeta(type):
|
||||
_instances: Dict[type, type] = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
def make_properties(*names):
|
||||
def decorator(cls):
|
||||
def prop(private_name: str):
|
||||
def getter(self):
|
||||
return getattr(self, private_name)
|
||||
|
||||
def setter(self, value):
|
||||
return setattr(self, private_name, value)
|
||||
|
||||
return getter, setter
|
||||
|
||||
for name in names:
|
||||
setattr(cls, name, property(*prop(f"_{cls.__name__}__{name}")))
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@make_properties("number", "is_mine", "is_open")
|
||||
class Cell:
|
||||
CHARS: Final[str] = "▧✸▢①②③④⑤⑥⑦⑧"
|
||||
|
||||
def __init__(self, number: int = 0, is_mine: bool = False, is_open: bool = False):
|
||||
self.__number, self.__is_mine, self.__is_open = number, is_mine, is_open
|
||||
|
||||
def __bool__(self):
|
||||
return not self.is_open
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}{(self.number, self.is_mine, self.is_open)!r}"
|
||||
|
||||
def __str__(self):
|
||||
return self.CHARS[not self and (self.is_mine or self.number + 2)]
|
||||
|
||||
def __setattr__(self, name: str, value) -> None:
|
||||
if name == "number":
|
||||
if not isinstance(value, int) or not 0 <= value <= 8:
|
||||
raise ValueError("Недопустимое значение атрибута")
|
||||
if name in ("is_mine", "is_open"):
|
||||
if not isinstance(value, bool):
|
||||
raise ValueError("Недопустимое значение атрибута")
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
|
||||
class GamePole(metaclass=SingletonMeta):
|
||||
around_offsets: Final[Tuple[Tuple[int, int], ...]] = tuple(
|
||||
(dx, dy) for dx in range(-1, 2) for dy in range(-1, 2) if (dx, dy) != (0, 0)
|
||||
)
|
||||
|
||||
def __init__(self, N: int, M: int, total_mines: int, pole=None):
|
||||
self.N, self.M, self.total_mines = N, M, total_mines
|
||||
if pole:
|
||||
self.__pole_cells = pole
|
||||
else:
|
||||
self.init_pole()
|
||||
|
||||
@property
|
||||
def pole(self) -> List[List[Cell]]:
|
||||
return self.__pole_cells
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}{(self.N, self.M, self.total_mines, self.pole)!r}"
|
||||
|
||||
def init_pole(self):
|
||||
gen = iter(range(1, self.total_mines + 1))
|
||||
flat_pole = [
|
||||
Cell(0, bool(next(gen, 0))) for _ in range(self.N) for _ in range(self.M)
|
||||
]
|
||||
random.shuffle(flat_pole)
|
||||
self.__pole_cells = [
|
||||
flat_pole[i : i + self.M] for i in range(0, self.N * self.M, self.M)
|
||||
]
|
||||
self.fill_around()
|
||||
|
||||
def fill_around(self):
|
||||
for i in range(self.N):
|
||||
for j in range(self.M):
|
||||
self.pole[i][j].number = self.count_around(i, j)
|
||||
|
||||
def count_around(self, x: int, y: int) -> int:
|
||||
return sum(
|
||||
(
|
||||
self.pole[x + i][y + j].is_mine
|
||||
for i, j in GamePole.around_offsets
|
||||
if 0 <= x + i < self.N and 0 <= y + j < self.M
|
||||
)
|
||||
)
|
||||
|
||||
def cell(self, i: int, j: int) -> Cell:
|
||||
try:
|
||||
return self.pole[i][j]
|
||||
except IndexError as e:
|
||||
raise IndexError("Недопустимый индекс") from e
|
||||
|
||||
def open_cell(self, i: int, j: int):
|
||||
cell = self.cell(i, j)
|
||||
cell.is_open = True
|
||||
|
||||
def open(self):
|
||||
for i in range(self.N):
|
||||
for j in range(self.M):
|
||||
self.open_cell(i, j)
|
||||
|
||||
def open_random(self, count: int):
|
||||
for _ in range(count):
|
||||
i, j = random.randint(0, self.N - 1), random.randint(0, self.M - 1)
|
||||
self.open_cell(i, j)
|
||||
|
||||
def __str__(self):
|
||||
c = self.M - 1
|
||||
result = (
|
||||
f"╭─{'──┬─' * c}──╮\n"
|
||||
+ f"├─{'──┼─' * c}──┤\n".join(
|
||||
map(lambda row: f"│ {' │ '.join(map(str, row))} │\n", self.pole)
|
||||
)
|
||||
+ f"╰─{'──┴─' * c}──╯"
|
||||
)
|
||||
return result
|
||||
|
||||
def show_pole(self):
|
||||
print(self)
|
||||
|
||||
|
||||
pole_game = GamePole(10, 8, 12)
|
||||
pole_game.open_random(30)
|
||||
pole_game.show_pole()
|
||||
pole_game.open()
|
||||
pole_game.show_pole()
|
||||
|
||||
|
||||
def tests():
|
||||
SingletonMeta._instances = {}
|
||||
random.seed()
|
||||
code = (
|
||||
b"a4{e~AV*<sWl(QyWhgN)EFdy4EFdv3DGG2hAUz;QVQpnlZ){~KF)%D3GB7M4F)%3#VRLh3a&#b"
|
||||
+ b"RWGHYkDIh&PAZcVMa55<@AR^I$(7w>O(6rF7(6!Nm(SgyAAke+gwb6mly3oGRyU~o$y3oEL(7w>I"
|
||||
+ b"(TdQu(7Mrr(7w>JAkezdyU?)Ffzg4`upmcaZDmkzY-J(}a3DP(a4`xBV`Xe?AUz;MWo&FHDGFh8b"
|
||||
+ b"7gXLAar?fWhg^sY-}!Rb6;&~Ze=MTJv|_Ba&K^Da&&nhVQyp~ba`-PC_`mzY%XqfZDM6|DIh&PAa"
|
||||
+ b"HVTaAk6Ic_3kKWFT~TaAhb%Wo&FNX>(t1aAj^OAU!=GaB^>OWpZ?REFdD#vLMj9(7VvE(Sgx{(6t"
|
||||
+ b"~#Wo&F9(6rFL(7VvK(7n-%AkeYVi_wD7j3Cgy(6P~q(6!LI(Sp&7EzyC{ve3TJxzT~qg3z+iupnu"
|
||||
+ b"3Uu|h_Wh@|Wb!}p0ax5Teb6;<8Wo{x03S(t#Y%XbYUu|h_WgtBuRC0A?3S(t#Y%XqfZDM6|AUz;8"
|
||||
+ b"3S(t#Y%XbYUvF?_ZXi7%RC0A?3So0|WpZ>NVsCG3C}U-8Y$+f;Js?J5Y;$ESAR^I((Sy*v(7Mrv("
|
||||
+ b"74f$AYyNCY$z!p(6Z3A(SXps(Sy*t(6AuTz0kGLve32BfY80rzR<NG(6`XN(6G^m(6!LL(74dGA_"
|
||||
+ b"@w0a(OxmARr(hV`Xe?E@^XLZE0?0AUz;4FbZXOV`Xr3AXZ^)b!A0za&K}v3LqdLAYpTJWpZ>NRC0"
|
||||
+ b"A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`i"
|
||||
+ b"vj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~|VQh6}MRIa)av}-}ARr(h3UqRLItm~lARuF9Y-}!Wb!}p0"
|
||||
+ b"av(h*F)#{ccw=R7bRbq?Y;|Qta&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@V"
|
||||
+ b"Qh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX"
|
||||
+ b"`sAR$**)WkqswZ*n3E3UDrIZfSI1aBpm7C@BhUAUz;33TAI|AaZYaAZczOa4v9fY-KtMARr(hW^Z"
|
||||
+ b"yJcpzzRAaZYaItm~lARr(hARu9Lb7gXLAZc@HZgX^DZewLAcq|}8Wo&FIEFdD#y3o7Owb6pmy3nx"
|
||||
+ b"DywJEH(74dE(SXpt(6Z3J(6i9KAke?izR<hTk08*r(7w>S(6-RM(TgC^vC)gsg3*j1(7w>I(TdQu"
|
||||
+ b"(7Mrr(TgC^y3o7Ou+f3hfzYrZLuG7iA_^cNARr(hARuXGAb2imb6;&~Ze=<OARr(hARr(hARr(hZ"
|
||||
+ b"6GT>ATbIGVRLh3a&#bVAU!=GF)%D3BGA3iuprRC(7w>S(6u1ZfY7kffzg4{g3z$gve3KGwa~rLz9"
|
||||
+ b"7)O(6!LA(6!Nk(7n*U(6u1Yy3oGRyU@7NhtRdrfzg7{ve3RD(7e#N(7hrGa4v6fWo}<%Wo&FHFf1"
|
||||
+ b"T3DGG2dZ*XO9Ut?u#Y$!P_ATc>93JP>`c{&OpARr)cE^lyUZeL?%Y-}hoFf1T4FewUUcw=R7bRbD"
|
||||
+ b"?WMz0oa&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@VQh0{EFdD#z0kEF(Sgvj"
|
||||
+ b"(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX`sANp56ictvt@Z*n3E3"
|
||||
+ b"JPRpW*}p4b#8QDZE0?0b0~0cY-KDUX)GXWDLM)uARr)aAUz;33LqdLAZBlJAZs9LZXj}DZf9jEEi"
|
||||
+ b"o)0GATL=ARr(hARr(hW^ZyJY#?cFAaY@DXJsfYF)Sc5DLM)uARr(hARr(hARr)VX)GXWY9KuzYb$"
|
||||
+ b"9iAZ#mY3LqdLARr(hARr(hAZcbGX=xxlATS_rav*7GAU+^DAa8OYYHA=nATS_rav*AIAU+^5IXVg"
|
||||
+ b";ARr(hARr(hARr(hARr)PZ*FvHZgph}ARr(hARr(hARr(hX=WgBZ){~-X=z<sYHD3BX>(s~X>MgY"
|
||||
+ b"3LqdLARr(hARr(hARr(hAZ{QlJs>d(ARr(hARr(hARr(hARr(h3LqdLAaZ4Nb#iVXZVCztW^ZyJX"
|
||||
+ b")GXeZ+9SRZXjiDb!}yGVRU6Ea4v9fY-K4r3LqdLAZBlJAZjcicpzzRAZ2cKZDn#{bY&=VZ+9s=3L"
|
||||
+ b"qdLARr(hAZcbGZf|rTa4v9fY-L+%U0Z5hE@^XLZE0?0Itm~lARr(hARr(hARuiZJs@Lmb#8QDZE0"
|
||||
+ b"?0b0}~waBpm7EFfttAZjTJARr(hARr(hARr(hVRLh3a&#bVAU!=Ga4v9fY-L+%U0Z5hE^c*gVr6n"
|
||||
+ b"HAR^Gc(6!LA(6!Nk(7n*UAke?izR<MMfzgN1xY2^pu+Y8Gz97+u(74fo(7VvSAke(fxX`^I(6Z3J"
|
||||
+ b"(7Mrp(Sy*lAkezdyU?}Kg3!9qxFP"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
tests()
|
||||
183
mod_oop/3.8_08_stack.py
Normal file
183
mod_oop/3.8_08_stack.py
Normal file
@@ -0,0 +1,183 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701993/step/8?unit=702094
|
||||
|
||||
Ранее вы уже создавали стек-подобную структуру, когда один объект ссылается на следующий и так по цепочке до последнего:
|
||||
|
||||
Для этого в программе объявлялись два класса:
|
||||
StackObj - для описания объектов стека;
|
||||
Stack - для управления стек-подобной структурой.
|
||||
|
||||
И, далее, объекты класса StackObj следовало создавать командой:
|
||||
obj = StackObj(data)
|
||||
где data - это строка с некоторым содержимым объекта (данными). При этом каждый объект класса StackObj должен иметь следующие локальные атрибуты:
|
||||
data - ссылка на строку с данными, указанными при создании объекта;
|
||||
next - ссылка на следующий объект класса StackObj (при создании объекта принимает значение None).
|
||||
|
||||
Класс Stack предполагается использовать следующим образом:
|
||||
st = Stack() # создание объекта стек-подобной структуры
|
||||
В каждом объекте класса Stack должен быть локальный публичный атрибут:
|
||||
top - ссылка на первый объект стека (если стек пуст, то top = None).
|
||||
|
||||
А в самом классе Stack следующие методы:
|
||||
push(self, obj) - добавление объекта класса StackObj в конец стека;
|
||||
pop(self) - извлечение последнего объекта с его удалением из стека;
|
||||
|
||||
Дополнительно в классе Stack нужно объявить магические методы для обращения к объекту стека по его индексу, например:
|
||||
obj_top = st[0] # получение первого объекта
|
||||
obj = st[4] # получение 5-го объекта стека
|
||||
st[2] = StackObj("obj3") # замена прежнего (3-го) объекта стека на новый
|
||||
Если индекс не целое число или число меньше нуля или больше числа объектов в стеке, то должно генерироваться исключение командой:
|
||||
raise IndexError('неверный индекс')
|
||||
|
||||
Пример использования классов Stack и StackObj (эти строчки в программе не писать):
|
||||
st = Stack()
|
||||
st.push(StackObj("obj1"))
|
||||
st.push(StackObj("obj2"))
|
||||
st.push(StackObj("obj3"))
|
||||
st[1] = StackObj("new obj2")
|
||||
print(st[2].data) # obj3
|
||||
print(st[1].data) # new obj2
|
||||
res = st[3] # исключение IndexError
|
||||
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
class StackObj:
|
||||
def __init__(self, data=None):
|
||||
self.__next = None
|
||||
self.__data = data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self.__data
|
||||
|
||||
@data.setter
|
||||
def data(self, value):
|
||||
self.__data = value
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
return self.__next
|
||||
|
||||
@next.setter
|
||||
def next(self, value):
|
||||
if isinstance(value, (self.__class__, None.__class__)):
|
||||
self.__next = value
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.data!r})"
|
||||
|
||||
def __str__(self):
|
||||
return str(self.data)
|
||||
|
||||
|
||||
class Stack:
|
||||
def __init__(self, data=None):
|
||||
self.top = None
|
||||
if not data:
|
||||
return
|
||||
for x in map(lambda x: isinstance(x, StackObj) and x or StackObj(x), data):
|
||||
self.push(x)
|
||||
|
||||
@property
|
||||
def bottom(self) -> StackObj:
|
||||
curr, last = self.top, None
|
||||
while curr:
|
||||
curr, last = curr.next, curr
|
||||
return last
|
||||
|
||||
def push(self, obj: StackObj):
|
||||
if not self.top:
|
||||
self.top = obj
|
||||
else:
|
||||
self.bottom.next = obj
|
||||
|
||||
def pop(self) -> StackObj:
|
||||
if not self.top:
|
||||
return None
|
||||
|
||||
a, b, c = [self.top] + [None] * 2
|
||||
while a:
|
||||
a, b, c = a.next, a, b
|
||||
|
||||
if c:
|
||||
c.next = None
|
||||
|
||||
if self.top in [b, c]:
|
||||
self.top = None
|
||||
|
||||
return b
|
||||
|
||||
def __len__(self):
|
||||
count, obj = 0, self.top
|
||||
while obj:
|
||||
obj, count = obj.next, count + 1
|
||||
return count
|
||||
|
||||
def __getitem__(self, idx: int) -> StackObj:
|
||||
if not isinstance(idx, int) or not 0 <= idx < len(self):
|
||||
raise IndexError("неверный индекс")
|
||||
count, obj = 0, self.top
|
||||
while obj:
|
||||
if count == idx:
|
||||
return obj
|
||||
obj, count = obj.next, count + 1
|
||||
|
||||
def __setitem__(self, idx: int, value: StackObj):
|
||||
if not isinstance(idx, int) or not 0 <= idx <= len(self):
|
||||
raise IndexError("неверный индекс")
|
||||
|
||||
if not isinstance(value, StackObj):
|
||||
value = StackObj(value)
|
||||
|
||||
if idx == len(self):
|
||||
self.push(value)
|
||||
return
|
||||
|
||||
old = self[idx]
|
||||
value.next = old.next
|
||||
|
||||
if idx > 0:
|
||||
self[idx - 1].next = value
|
||||
else:
|
||||
self.top = value
|
||||
|
||||
def get_data(self) -> List[StackObj]:
|
||||
return [x for x in self]
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.get_data()!r})"
|
||||
|
||||
def __str__(self):
|
||||
return " -> ".join(map(str, self.get_data()))
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"b95j*AX9W<V{0fW3UhQWaCLKNC{uJ{V{1=hYA7OaVrnrlA}J{fb963nb#rJaQ*>ctYfoZoC?ao"
|
||||
+ b"SYB4e*DJcqbbS`jpb7&}2bYWv_Phx5)B5z`9F*715DGGCRTQOZAJs?wbVPk7gVrnQNZ(?dPEpBCZ"
|
||||
+ b"A}I=Cb8}^KbRctdTQFTNWMOn+AU!=GB5z`9F)<<_VQyp~b97rVT`pu{bYUPpJs=`)VrnuiZe@2OE"
|
||||
+ b"FdD#u+f6ifY7+mvC)Ikf*@pJbYURSzR<DJiqN&ty3vBruprR7(7VvE(Sgx{(6As=bYWv_Phx5y(S"
|
||||
+ b"gvu(6rFC(SXpl(74fpAke+gwa~KAwb6jkz0r%%wII;6(6G?G(7n-%(6u583UqRLItm~lARupIY9K"
|
||||
+ b"uzb97rXT?%D*V`Xr3AW3dyWq3t$a&K}v3LqdLAYpTJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4"
|
||||
+ b"yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O"
|
||||
+ b"(6t~*Ze(S6MRIa)av}-}Z(?d7Js@**E^u#fC@BhIb8}^KbRchHYA$49bYUPpJs=`)VrnrnA}k;x("
|
||||
+ b"7e#K(Sp#v(6k_MZ*U;cw9vlLyU@1Kwa~pF(Sy*m(6G?E(T~xB(TpI_ztFzXfzZ3qwa~QCz0kPOxg"
|
||||
+ b"gNK(6P~q(6!LI(Sjh+fzg7{wa~iIuprR5AkebVzR<VOveAIhu+fRou+f6ij3Cgp(6i9KA_@v_AUz"
|
||||
+ b";33TPlbAaissbZ>A9cW7yBWguue3LqdLAYpTJWpZ>NX>)0Ab97;DV`V64EFe>KVPk7gVrnTYAR^G"
|
||||
+ b"d(6P~q(6!LI(Sjh+fzg7{wa~iIuprR1(7w>S(6-RE(7hngvC)gsg3*j1(T&i$(6`XF(7e#U(7Vx("
|
||||
+ b"(SXpt(7Yhfy3o7Ou+f3hfzYrZQ*>ctYfoZoA_^cNARulaD?K1F3LqdLAZQ>xAZRXbWq5Q7ARr(h3"
|
||||
+ b"So0|WpZ>NZXi89ATlf<BGA3iwa~KAwb6jkz0khUwII=l(74fo(7VvSAke<hvC)dqwa~iJg3!LuvL"
|
||||
+ b"Mj1Akl%*g3z_ly3n;CDA2OdzR<VOywJYTw$Q!MzAPZnz0k1HfYF1|h|smrz0j~A(6!LB(7qtifzg"
|
||||
+ b"7|fYF1{y3vBsgVBJ{uqh$"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
169
mod_oop/3.8_10_tictactoe.py
Normal file
169
mod_oop/3.8_10_tictactoe.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701993/step/10?unit=702094
|
||||
|
||||
Вам нужно реализовать в программе игровое поле для игры "Крестики-нолики".
|
||||
|
||||
Для этого требуется объявить класс TicTacToe (крестики-нолики), объекты которого создаются командой:
|
||||
game = TicTacToe()
|
||||
|
||||
Каждый объект game должен иметь публичный атрибут:
|
||||
pole - игровое поле: кортеж размером 3х3 с объектами класса Cell.
|
||||
|
||||
Каждая клетка игрового поля представляется объектом класса Cell и создается командой:
|
||||
cell = Cell()
|
||||
|
||||
Объекты класса Cell должны иметь следующие публичные локальные атрибуты:
|
||||
is_free - True, если клетка свободна; False в противном случае;
|
||||
value - значение поля: 1 - крестик; 2 - нолик (по умолчанию 0).
|
||||
|
||||
Также с каждым объектом класса Cell должна работать функция:
|
||||
bool(cell)
|
||||
которая возвращает True, если клетка свободна (cell.is_free=True) и False в противном случае.
|
||||
|
||||
Класс TicTacToe должен иметь следующий метод:
|
||||
clear() - очистка игрового поля (все клетки заполняются нулями и переводятся в закрытое состояние);
|
||||
|
||||
А объекты этого класса должны иметь следующую функциональность (обращение по индексам):
|
||||
game[0, 0] = 1 # установка нового значения, если поле закрыто
|
||||
res = game[1, 1] # получение значения центральной ячейки поля (возвращается число)
|
||||
|
||||
Если указываются некорректные индексы, то должно генерироваться исключение командой:
|
||||
raise IndexError('неверный индекс клетки')
|
||||
|
||||
Если идет попытка присвоить новое значение в открытую клетку поля, то генерировать исключение:
|
||||
raise ValueError('клетка уже занята')
|
||||
|
||||
Также должны быть реализованы следующие полные срезы при обращении к клеткам игрового поля:
|
||||
slice_1 = game[:, indx] # выбираются все элементы (кортеж) столбца с индексом indx
|
||||
slice_2 = game[indx, :] # выбираются все элементы (кортеж) строки с индексом indx
|
||||
|
||||
Пример использования классов (эти строчки в программе не писать):
|
||||
game = TicTacToe()
|
||||
game.clear()
|
||||
game[0, 0] = 1
|
||||
game[1, 0] = 2
|
||||
# формируется поле:
|
||||
# 1 0 0
|
||||
# 2 0 0
|
||||
# 0 0 0
|
||||
game[3, 2] = 2 # генерируется исключение IndexError
|
||||
if game[0, 0] == 0:
|
||||
game[0, 0] = 2
|
||||
v1 = game[0, :] # 1, 0, 0
|
||||
v2 = game[:, 0] # 1, 2, 0
|
||||
|
||||
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||
P.P.S. При передаче среза в магических методах __setitem__() и __getitem__() параметр индекса становится объектом класса slice.
|
||||
Его можно указывать непосредственно в квадратных скобках упорядоченных коллекций (списков, кортежей и т.п.).
|
||||
"""
|
||||
|
||||
|
||||
class Cell:
|
||||
CHARS = " OX"
|
||||
|
||||
def __init__(self, value=0, is_free=True):
|
||||
self.value, self.is_free = value, is_free
|
||||
|
||||
def __bool__(self):
|
||||
return self.is_free
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}{(self.value, self.is_free)!r}"
|
||||
|
||||
def __str__(self):
|
||||
return self.CHARS[not self and self.value]
|
||||
|
||||
|
||||
class TicTacToe:
|
||||
def __init__(self, pole=None):
|
||||
if pole is None:
|
||||
pole = tuple(tuple(Cell() for _ in range(3)) for _ in range(3))
|
||||
self.pole = pole
|
||||
|
||||
def clear(self):
|
||||
for row in self.pole:
|
||||
for cell in row:
|
||||
cell.is_free = True
|
||||
cell.value = 0
|
||||
|
||||
def _get_key(self, key):
|
||||
if not isinstance(key, tuple) or len(key) != 2:
|
||||
raise IndexError("неверный индекс клетки")
|
||||
return tuple(x if isinstance(x, int) else None for x in key)
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = self._get_key(key)
|
||||
row, col = key
|
||||
if row is None and col is None:
|
||||
return tuple(tuple(c.value for c in r) for r in self.pole)
|
||||
if row is not None and col is not None:
|
||||
return self.pole[row][col].value
|
||||
if row is not None and col is None:
|
||||
return tuple(x.value for x in self.pole[row])
|
||||
return tuple(self.pole[row][col].value for row in range(len(self.pole)))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
key = self._get_key(key)
|
||||
row, col = key
|
||||
if row is None and col is None:
|
||||
for i, row in enumerate(value):
|
||||
for j, v in enumerate(row):
|
||||
self[i, j] = v
|
||||
return
|
||||
if row is not None and col is not None:
|
||||
cell = self.pole[row][col]
|
||||
if not cell.is_free:
|
||||
raise ValueError("клетка уже занята")
|
||||
cell.value = value
|
||||
cell.is_free = False
|
||||
return
|
||||
if row is not None and col is None:
|
||||
for j, v in enumerate(value):
|
||||
self[row, j] = v
|
||||
return
|
||||
for i, v in enumerate(value):
|
||||
self[i, col] = v
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.pole!r})"
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(map(lambda r: " ".join(map(str, r)), self.pole))
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"XCOTwRB2;WVPjNpWhf~MXD(xGWnpqCDGFh8b7gXLAZJ@JEFdslAU!=GFd$)WWFTi-GAtl6T_8O"
|
||||
+ b"@ATTT-BGA3iu+fLmu+Y2FjL^N&i_o<o(6`XN(6G^m(6!LL(74f$AkebWfzY+lg&@$n(7VvJ(Sp#v"
|
||||
+ b"(7GVdw9vlLyU@1Kz0r#x(6P~r(Sp&8Akl!(u+Xy5z0r#xFd_<PTQMvkF<l@%ATbJOTQV#lF<l@%A"
|
||||
+ b"TkPJb8}^KbRcJ2F)Sc4T_8O@ATc0eZe$>5TQV#lF<l@%Js>hHAR^Gc(6!LA(6!Nk(7n*UAke<ig3"
|
||||
+ b"*A`u+Xv4zR`lvu+Y2EuprRB(7({N(SXpf(T32t(T^a|ztMouxY2>ove2;5xX`lDu+Y8GxY3Uw(7n"
|
||||
+ b"*U(6Z5s(S;z;x6r-Nu+fLmwa~rLxX`&E(7MpO(6!Nm(7MpD(7YhfxX`oFfY83sve3TJv(UaE(7({"
|
||||
+ b"W(7Vx(ASlqd(7VvMEFjRm(6!LI(7w@t(SXpk(7Mrr(7n*UAkl!(u+Xv4zR`lvu+X*9f*{d>(TC8u"
|
||||
+ b"(Sp&7(6Z35(7n*O(6u1Yx6r-Nu+fLmwa~rLxX`&NA_@w0a(OxmARr(ha%FQMJs@XWGb|u5T?%D*V"
|
||||
+ b"`Xr3AW3dyWq3t$a&K}v3LqdLAYpTJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6"
|
||||
+ b"u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~*Ze(S6MRI"
|
||||
+ b"a)av;#Z(SXpnAkl%*htRmug3*i6ve2;5z0kPOxFFED(6=Daz0kGMfzgA}iO{vtfzg7{veARlj?sz"
|
||||
+ b"Gwa~dB(T~xG(6!LH(7MpLA_@v1ARr(LbaHt*3LqdLAZJ@MEFdslAUz;83T1d>WpH#LNp56ictvt@"
|
||||
+ b"Z*n>cARr(hVRLh3a&#b6a&=`2Wo&b0Itm~lARu9Lb7gXLAVy(qb7d?bBGA3iwII=f(6i9B(7n*L("
|
||||
+ b"SXpn(SXpt(6Z35(7VvS(Sgy7AketcfzZ0pyU~u(htRdqz0kPOwIE4uWMz0oa&m8SAke?jfY7)g(6"
|
||||
+ b"`XA(7({Q(SgvoAkeZP(7n*L(Sgx}(TUKt(Sgx|(6Z5k(T>rH(Sy;BAkmM}htRdqxzM`NgCYtF3TG"
|
||||
+ b"~3Y-M3`C@BhOTQDpjFkK)$ATbJOTQMvkFkK)$ATkPPTQV#lFkK)$ATtUIVRLh3a&#bPTQDpjI$a<"
|
||||
+ b"=Js>DCEFdr}ATTK)VQyp~XIn8WAUa(jJv|^OGAtl4EFdr`AYpD~AZJ@TEFdslAU!=GC^0M`GAtl7"
|
||||
+ b"DJ&o&(7n*L(7MpR(SXr_(6!LI(Sp#u(7qthzR`lwfY7kevCzKJg3z$gyU@5G(Sgx`(6!LF(TgC^z"
|
||||
+ b"tFzXfzZ3qwII;4(TmWx(7w>J(6AuTywJ7Lg3!Luw9v31V{Bz%awsVv(6}JbztMouxY2>ove2;5xX"
|
||||
+ b"`lDu+Y8GxY3Uw(7n*U(6Z5s(S;z;x6r-Nu+fLmwa~rLxX`&G3JPOoY-}JsAVXzrY$z!TVRLh3a&#"
|
||||
+ b"bLWo&FNc42IFWgtC0ATTT-BGA3iu+fLmu+Y2FjL^N%zR<NG(6`XN(6G^m(6!LL(74dGAkeVUg3*A"
|
||||
+ b"`xX`iDgVBP}upo9}Y;|QI(7MpO(6G^g(SgvgAVXzrY#`9I(7w>S(6-RM(7qthvC)gsg3*j1(SXpf"
|
||||
+ b"(6Z3I(7qrrA_{V4b09q+V`Xe?E@^XLW^!d^3S(t#Y%XbYUuJS;WgtBuRC0A?3So0|WpZ>NVsCG3C"
|
||||
+ b"}U-8Y$+@tBGH7=gV4Ruy3vNvxY3UwVsCG3AkebVwb6jkz0rfvyU?&8MqzAoWgyVB(7Vx(Akl%)ve"
|
||||
+ b"3TJvCzKIw9viKzR<ZK(7MpO(6!Nm(7MpLA^"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
205
mod_oop/3.9_10_stack_iter.py
Normal file
205
mod_oop/3.9_10_stack_iter.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701994/step/10?unit=702095
|
||||
|
||||
Вы несколько раз уже делали стек-подобную структуру, когда объекты последовательно связаны между собой:
|
||||
|
||||
|
||||
|
||||
Доведем ее функционал до конца. Для этого, по прежнему, нужно объявить классы:
|
||||
Stack - для представления стека в целом;
|
||||
StackObj - для представления отдельных объектов стека.
|
||||
|
||||
В классе Stack должны быть методы:
|
||||
push_back(obj) - для добавления нового объекта obj в конец стека;
|
||||
push_front(obj) - для добавления нового объекта obj в начало стека.
|
||||
|
||||
В каждом объекте класса Stack должен быть публичный атрибут:
|
||||
top - ссылка на первый объект стека (при пустом стеке top = None).
|
||||
|
||||
Объекты класса StackObj создаются командой:
|
||||
obj = StackObj(data)
|
||||
где data - данные, хранящиеся в объекте стека (строка).
|
||||
|
||||
Также в каждом объекте класса StackObj должны быть публичные атрибуты:
|
||||
data - ссылка на данные объекта;
|
||||
next - ссылка на следующий объект стека (если его нет, то next = None).
|
||||
|
||||
Наконец, с объектами класса Stack должны выполняться следующие команды:
|
||||
st = Stack()
|
||||
st[indx] = value # замена прежних данных на новые по порядковому индексу (indx); отсчет начинается с нуля
|
||||
data = st[indx] # получение данных из объекта стека по индексу
|
||||
n = len(st) # получение общего числа объектов стека
|
||||
|
||||
for obj in st: # перебор объектов стека (с начала и до конца)
|
||||
print(obj.data) # отображение данных в консоль
|
||||
|
||||
При работе с индексами (indx), нужно проверять их корректность. Должно быть целое число от 0 до N-1, где N - число объектов в стеке. Иначе, генерировать исключение командой:
|
||||
raise IndexError('неверный индекс')
|
||||
|
||||
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
class StackObj:
|
||||
def __init__(self, data=None):
|
||||
self.__next = None
|
||||
self.__data = data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self.__data
|
||||
|
||||
@data.setter
|
||||
def data(self, value):
|
||||
self.__data = value
|
||||
|
||||
def __eq__(self, other):
|
||||
if hasattr(other, "data"):
|
||||
return self.data == other.data
|
||||
return self.data == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
return self.__next
|
||||
|
||||
@next.setter
|
||||
def next(self, value):
|
||||
if isinstance(value, (self.__class__, None.__class__)):
|
||||
self.__next = value
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, value):
|
||||
return value if isinstance(value, cls) else cls(value)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.data!r})"
|
||||
|
||||
def __str__(self):
|
||||
return str(self.data)
|
||||
|
||||
|
||||
class Stack:
|
||||
def __init__(self, data=None):
|
||||
self.top = None
|
||||
self.load(data or [])
|
||||
|
||||
def __iter__(self):
|
||||
obj = self.top
|
||||
while obj:
|
||||
yield obj
|
||||
obj = obj.next
|
||||
|
||||
@property
|
||||
def bottom(self) -> StackObj:
|
||||
for last in self:
|
||||
...
|
||||
return last
|
||||
|
||||
def push_back(self, value):
|
||||
obj = StackObj.wrap(value)
|
||||
if not self.top:
|
||||
self.top = obj
|
||||
else:
|
||||
self.bottom.next = obj
|
||||
|
||||
def push_front(self, value):
|
||||
obj = StackObj.wrap(value)
|
||||
obj.next = self.top
|
||||
self.top = obj
|
||||
|
||||
def pop(self) -> StackObj:
|
||||
if not self.top:
|
||||
return None
|
||||
b, c = None, None
|
||||
for a in self:
|
||||
b, c = a, b
|
||||
if c:
|
||||
c.next = None
|
||||
if b is self.top:
|
||||
self.top = None
|
||||
return b
|
||||
|
||||
def load(self, data):
|
||||
for x in data:
|
||||
self.push_back(x)
|
||||
|
||||
def __len__(self):
|
||||
return sum(1 for _ in self)
|
||||
|
||||
def validate_index(self, idx: int):
|
||||
if not isinstance(idx, int) or not 0 <= idx < len(self):
|
||||
raise IndexError("неверный индекс")
|
||||
|
||||
def __getitem__(self, idx: int):
|
||||
self.validate_index(idx)
|
||||
for i, v in enumerate(self):
|
||||
if idx == i:
|
||||
return v.data
|
||||
|
||||
def __setitem__(self, idx: int, value):
|
||||
self.validate_index(idx)
|
||||
|
||||
if idx == len(self):
|
||||
self.push_back(value)
|
||||
return
|
||||
|
||||
prev = self.top
|
||||
for i, v in enumerate(self):
|
||||
if idx == i:
|
||||
old = v
|
||||
break
|
||||
prev = v
|
||||
|
||||
value = StackObj.wrap(value)
|
||||
value.next = old.next
|
||||
if idx > 0:
|
||||
prev.next = value
|
||||
else:
|
||||
self.top = value
|
||||
|
||||
def get_data(self) -> List[StackObj]:
|
||||
return [x for x in self]
|
||||
|
||||
def __str__(self):
|
||||
return " -> ".join(map(str, self.get_data()))
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.get_data()!r})"
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"b95j*AX9W<V{0fW3UhQWaCLKNUt(cnYbaB6VPk7gVrnQNF(N4`3UhQWaCLKNUuJS|ZgeP9bYWv"
|
||||
+ b"_Phx5)A~GT=DGCZ<b8}^KbRctdTQFT9Jv|^IG9n;hZe$>HbXzf9AU!=GA~7N?AR^Gc(6!LA(6!Nk"
|
||||
+ b"(7n-%(6u1Yx6r-Nu+fLmwa~rLxY3Uw(6rF7(7n*T(TmZAAketbw;<5I(6P~q(6!LI(Sp#v(6S)Wf"
|
||||
+ b"zg7{wa~iIuq+_ZztMouxFFEJ(6P~g(6G^o(6!LL(74dJAkexX(7n*O(7YhfztFxQ(74dO(6rFC(7"
|
||||
+ b"Mrq(Ssri3UhQ@FkK)$AR;g#3So0|WpZ>Nb97rUT_8O@AR;g#EFdD#ztFzWyU~NuhtRdqz0kfO(7n"
|
||||
+ b"*L(6Z3A(SXps(7w>MAkeqaz0k1HhtRdqz0kPOwII;A(6=DazR<DJiqN&ty3vBruprTa(Sp#m(7Mp"
|
||||
+ b"DEFjRb(7w>O(7e#T(6-RM(7r4n(7n*L(7MpR(SXr_(6!LI(Sp#u(7qtifY7kevCzKJg3z$gwb6ng"
|
||||
+ b"(7(}u(74fo(6Z35(74dD(6G?G(74dGAke+gzR<GJzR<JKz97)I(7n*G(TC8r(7n*O(T^a|zR<DJi"
|
||||
+ b"qN&ty3vBsgCNm?(Sp#m(7MpDA_@v-Z*m}SVrn31ZXk1XItm~lARu9Lb7gXLAZc@HZgX^DZewLAZ("
|
||||
+ b"?dJAX9W<V{1=hYAGxrBGA9lfY7)g(7({N(SXpk(6P|I(SXpkAkl%*g3z_ly3nv7(TC8r(SXpk(6="
|
||||
+ b"DbhS0dsy3o5I(6rFL(7VvK(7n-%AkebVzR<VOveAIhu+fRou+f6ijM0J7k08*#(6P~q(6!LI(Sp&"
|
||||
+ b"7AkezdyU?)Ffzg4`upm=(VPk7gVrn7^3UqRLItm~lARu8NJs@**TQgk>Wq4y{aC9I^Ze(S6MRIa)"
|
||||
+ b"aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLAYpTJWpZ>NMqzAoWh@{f(7n*LAkl%)v(UBBz0kGMf"
|
||||
+ b"Y7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T>rF(6!LL(74dGAW3dyWq3t$a&K}X"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
# +
|
||||
st = Stack("123")
|
||||
st[1] = "x"
|
||||
assert "1x3" == "".join(
|
||||
map(str, st)
|
||||
), "неверно отработали операторы присвоения данных по индексу и/или получение данных по индексу"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
143
mod_oop/3.9_11_tablevalues.py
Normal file
143
mod_oop/3.9_11_tablevalues.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701994/step/10?unit=702095
|
||||
|
||||
В программе необходимо реализовать таблицу TableValues по следующей схеме:
|
||||
|
||||
|
||||
Каждая ячейка таблицы должна быть представлена классом Cell. Объекты этого класса создаются командой:
|
||||
cell = Cell(data)
|
||||
где data - данные в ячейке. В каждом объекте класса Cell должен формироваться локальный приватный атрибут __data с соответствующим значением.
|
||||
Для работы с ним в классе Cell должно быть объект-свойство (property):
|
||||
data - для записи и считывания информации из атрибута __data.
|
||||
|
||||
Сам класс TableValues представляет таблицу в целом, объекты которого создаются командой:
|
||||
table = TableValues(rows, cols, type_data)
|
||||
где rows, cols - число строк и столбцов таблицы; type_data - тип данных ячейки (int - по умолчанию, float, list, str и т.п.). Начальные значения в ячейках таблицы равны 0 (целое число).
|
||||
|
||||
С объектами класса TableValues должны выполняться следующие команды:
|
||||
table[row, col] = value# запись нового значения в ячейку с индексами row, col (индексы отсчитываются с нуля)
|
||||
value = table[row, col] # считывание значения из ячейки с индексами row, col
|
||||
|
||||
for row in table: # перебор по строкам
|
||||
for value in row: # перебор по столбцам
|
||||
print(value, end=' ') # вывод значений ячеек в консоль
|
||||
print()
|
||||
|
||||
При попытке записать по индексам table[row, col] данные другого типа (не совпадающего с атрибутом type_data объекта класса TableValues), должно генерироваться исключение командой:
|
||||
raise TypeError('неверный тип присваиваемых данных')
|
||||
|
||||
При работе с индексами row, col, необходимо проверять их корректность. Если индексы не целое число или они выходят за диапазон размера таблицы, то генерировать исключение командой:
|
||||
raise IndexError('неверный индекс')
|
||||
|
||||
P.S. В программе нужно объявить только классы. Выводить на экран ничего не нужно.
|
||||
"""
|
||||
|
||||
|
||||
class Cell:
|
||||
def __init__(self, data=0):
|
||||
self.__data = data
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self.__data
|
||||
|
||||
@data.setter
|
||||
def data(self, value):
|
||||
self.__data = value
|
||||
|
||||
def __eq__(self, other):
|
||||
if hasattr(other, "data"):
|
||||
return self.data == other.data
|
||||
return self.data == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, value):
|
||||
return value if isinstance(value, cls) else cls(value)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.data!r})"
|
||||
|
||||
def __str__(self):
|
||||
return str(self.data)
|
||||
|
||||
|
||||
class TableValues:
|
||||
def __init__(self, rows, cols, type_data=int, data=None):
|
||||
self.rows, self.cols, self.type_data = rows, cols, type_data
|
||||
if data:
|
||||
self.cells = tuple(tuple(Cell.wrap(x) for x in row) for row in data)
|
||||
else:
|
||||
self.cells = tuple(
|
||||
tuple(Cell.wrap(self.type_data()) for _ in range(cols))
|
||||
for _ in range(rows)
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
return self.rows
|
||||
|
||||
def validate_index(self, index):
|
||||
if not isinstance(index, tuple):
|
||||
raise IndexError("неверный индекс")
|
||||
row, col = index
|
||||
if (
|
||||
not isinstance(row, int)
|
||||
or not 0 <= row < self.rows
|
||||
or not isinstance(col, int)
|
||||
or not 0 <= col < self.cols
|
||||
):
|
||||
raise IndexError("неверный индекс")
|
||||
return row, col
|
||||
|
||||
def __getitem__(self, index):
|
||||
row, col = self.validate_index(index)
|
||||
return self.cells[row][col].data
|
||||
|
||||
def __setitem__(self, index, new_value):
|
||||
row, col = self.validate_index(index)
|
||||
if not isinstance(new_value, self.type_data):
|
||||
raise TypeError("неверный тип присваиваемых данных")
|
||||
self.cells[row][col].data = new_value
|
||||
|
||||
def __iter__(self):
|
||||
return (
|
||||
(self.cells[row][col].data for col in range(self.cols))
|
||||
for row in range(self.rows)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.rows!r}, {self.cols!r}, {self.type_data.__name__}, {self.cells!r})"
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"bYdVqAXH&uY-LtqY;|RGC^IY|GARmfAUz;$AUz;33TAI|AaZYaAZczObYeORARr(hZXhc?ATbI"
|
||||
+ b"cARr)SZ*m}ZVQh6}AZczOa&LD!3LqdLARr(hAZ;KkJs>d(ARr(hARr(hVRLh3a&#bcd2nSYc42IF"
|
||||
+ b"Who#%Js@drbRc1FWFU57Y;|QIJv|^WEFdD#ztMouxFFEK(6!Nk(6!L9(7w@t(6u1YzR<DJiqN&ty"
|
||||
+ b"3vBruprR7(7VvE(Sgx{(6As>VPb4$R$**)Wpg0WfgsSo(7w>T(7w@$(TvfKAkebVyU@PSw$QcEz0"
|
||||
+ b"keUi_wK3(T32t(7MpO(7w>JAZBlJEFjRd(7w>S(6-RE(7hnhfzZ9su+fLmu+Y2EuprQ~(7w>O(6Z"
|
||||
+ b"5h(6G^o(6G^h(Tvf7(T^a|xY2^pwb6jku+f6hzR`dn(6rFI(T^a}fzg7|fY83sx-1~juprR4(6G^"
|
||||
+ b"h(6!LKEFjU1(Sp#v(Sjh*xY2^pwb6jku+f6hzR`dn(6rFL(7VvK(6!LLAkebVzR<VOveAIhu+fRo"
|
||||
+ b"u+f6ij3Cj5(6!LJ(TmWvAkl}=xY2>oyU?&8DA2diz0k1HhtRdqz0kPPk08;3(7w>V(Sp#j(6!Nm("
|
||||
+ b"Sgx|(6Z5k(T>rH(74fsAkmM}htRdqwa~gLA_^cNARr(hARr21b8}^KbRcdZJ|Hn5VQyp~Z6H1%F)"
|
||||
+ b"Sb=(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyU@5G(6Z3G(7w>N(6!LL(7n-%(6u1ZhS0"
|
||||
+ b"dsy3o7Piy+Xn(7Vx(Ake?iwb6jkwa~H9zR`ftuprTo(TC8r(6!LIAkl)*u+Xv4yU@7NhS7^63JMB"
|
||||
+ b"zVp}jQATV7ZJs>eK3So0|WpZ>NbYfdDEFdslAU!=GF)%D3BGA3iwII=e(6G?4(7w@v(6G?8(Sjh*"
|
||||
+ b"x6rWAztFhRfzga0(7n*U(6Z3J(6i9KAkeqaz0k1HhtRdqz0kPPk08*pAkmM}htRdqxzM`NgCNm@("
|
||||
+ b"6G?4(7VvM(T35BA_@u$baHt*3LqdLAar6|GAtl4T_8OmH7+s=Wq4y{aC9J4d2nS#a&m8SItm~lAR"
|
||||
+ b"u9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@VQh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w"
|
||||
+ b">J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLxX`sARC#b^MRIa)av}-}3UqRLItm~lARu8NJs@;"
|
||||
+ b"qTQV#lG+hd1cw=R7bRbD?WMz0oa&m8SItm~lARu9Lb7gXLAXIX7WeR0%b7eXTARr(hVRLh3a&#a@"
|
||||
+ b"VQh0{EFdD#z0kEF(Sgvj(6!LL(6!Nk(74fn(7w>J(6G?E(7w@u(TpI_xY2>oy3o7Pj?stEwa~rLx"
|
||||
+ b"X`sANp56ictvt@Z*n3"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
301
mod_oop/3.9_12_matrix.py
Normal file
301
mod_oop/3.9_12_matrix.py
Normal file
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
https://stepik.org/lesson/701994/step/12?unit=702095
|
||||
|
||||
Объявите класс Matrix (матрица) для операций с матрицами.
|
||||
|
||||
Объекты этого класса должны создаваться командой:
|
||||
m1 = Matrix(rows, cols, fill_value)
|
||||
где rows, cols - число строк и столбцов матрицы; fill_value - заполняемое начальное значение элементов матрицы (должно быть число: целое или вещественное).
|
||||
>>> rows, cols, fill_value = 4, 2, 0
|
||||
>>> m1 = Matrix(rows, cols, fill_value)
|
||||
|
||||
Если в качестве аргументов передаются не числа, то генерировать исключение:
|
||||
raise TypeError('аргументы rows, cols - целые числа; fill_value - произвольное число')
|
||||
>>> m1 = Matrix("тест", cols, fill_value)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: аргументы rows, cols - целые числа; fill_value - произвольное число
|
||||
|
||||
Также объекты можно создавать командой:
|
||||
m2 = Matrix(list2D)
|
||||
где list2D - двумерный список (прямоугольный), состоящий из чисел (целых или вещественных).
|
||||
>>> list2D = [[1, 2], [3, 4], [5, 6], [7, 8]]
|
||||
>>> m2 = Matrix(list2D)
|
||||
|
||||
Если список list2D не прямоугольный, или хотя бы один из его элементов не число, то генерировать исключение командой:
|
||||
raise TypeError('список должен быть прямоугольным, состоящим из чисел')
|
||||
>>> m2 = Matrix([[1, 2], [3], [4, 5], [6, 7, 8]])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: список должен быть прямоугольным, состоящим из чисел
|
||||
|
||||
|
||||
Для объектов класса Matrix должны выполняться следующие команды:
|
||||
matrix = Matrix(4, 5, 0)
|
||||
res = matrix[0, 0] # возвращается первый элемент матрицы
|
||||
matrix[indx1, indx2] = value # элементу матрицы с индексами (indx1, indx2) присваивается новое значение
|
||||
>>> matrix = Matrix(4, 5, 0)
|
||||
>>> matrix[0, 0] = 42
|
||||
>>> matrix[0, 0]
|
||||
42
|
||||
|
||||
Если в результате присвоения тип данных не соответствует числу, то генерировать исключение командой:
|
||||
raise TypeError('значения матрицы должны быть числами')
|
||||
Если указываются недопустимые индексы матрицы (должны быть целыми числами от 0 и до размеров матрицы), то генерировать исключение:
|
||||
raise IndexError('недопустимые значения индексов')
|
||||
|
||||
Также с объектами класса Matrix должны выполняться операторы:
|
||||
matrix = m1 + m2 # сложение соответствующих значений элементов матриц m1 и m2
|
||||
matrix = m1 + 10 # прибавление числа ко всем элементам матрицы m1
|
||||
matrix = m1 - m2 # вычитание соответствующих значений элементов матриц m1 и m2
|
||||
matrix = m1 - 10 # вычитание числа из всех элементов матрицы m1
|
||||
|
||||
Во всех этих операция должна формироваться новая матрица с соответствующими значениями.
|
||||
Если размеры матриц не совпадают (разные хотя бы по одной оси), то генерировать исключение командой:
|
||||
raise ValueError('операции возможны только с матрицами равных размеров')
|
||||
|
||||
Пример для понимания использования индексов (эти строчки в программе писать не нужно):
|
||||
|
||||
mt = Matrix([[1, 2], [3, 4]])
|
||||
res = mt[0, 0] # 1
|
||||
res = mt[0, 1] # 2
|
||||
res = mt[1, 0] # 3
|
||||
res = mt[1, 1] # 4
|
||||
|
||||
P.S. В программе нужно объявить только класс. Выводить на экран ничего не нужно.
|
||||
|
||||
---
|
||||
>>> Matrix(2, 2) - 2
|
||||
Matrix([[-2, -2], [-2, -2]])
|
||||
|
||||
>>> Matrix(2, 2) == Matrix([[0, 0], [0, 0]])
|
||||
True
|
||||
>>> Matrix(2, 2, 2) == [[2, 2], [2, 2]]
|
||||
True
|
||||
>>> Matrix(2, 2, 2) == [[2, -2], [2, 2]]
|
||||
False
|
||||
"""
|
||||
|
||||
from functools import singledispatchmethod
|
||||
from operator import add, floordiv, mod, mul, sub, truediv, xor
|
||||
|
||||
|
||||
def add_ops(*ops):
|
||||
def decorator(cls):
|
||||
def make_methods(op):
|
||||
def method_new(self, other):
|
||||
return self.__class__(self.mapped(op, other))
|
||||
|
||||
def method_ip(self, other):
|
||||
self.data = self.mapped(op, other)
|
||||
return self
|
||||
|
||||
def method_r(self, other):
|
||||
if isinstance(other, (int, float)):
|
||||
return self.__class__(
|
||||
[
|
||||
*map(
|
||||
lambda row: [*map(lambda x: op(other, x), row)],
|
||||
self.data,
|
||||
)
|
||||
]
|
||||
)
|
||||
return NotImplemented
|
||||
|
||||
return {
|
||||
f"__{op.__name__}__": method_new,
|
||||
f"__i{op.__name__}__": method_ip,
|
||||
f"__r{op.__name__}__": method_r,
|
||||
}
|
||||
|
||||
for op in ops:
|
||||
for name, method in make_methods(op).items():
|
||||
setattr(cls, name, method)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@add_ops(add, sub, mul, truediv, floordiv, xor, mod, pow)
|
||||
class Matrix:
|
||||
@singledispatchmethod
|
||||
def __init__(self, rows, cols, fill_value=0):
|
||||
args_valid = (
|
||||
isinstance(rows, int)
|
||||
and isinstance(cols, int)
|
||||
and isinstance(fill_value, (int, float))
|
||||
)
|
||||
if not args_valid:
|
||||
raise TypeError(
|
||||
"аргументы rows, cols - целые числа; fill_value - произвольное число"
|
||||
)
|
||||
self.rows, self.cols = rows, cols
|
||||
self.data = [[fill_value for _ in range(cols)] for _ in range(rows)]
|
||||
|
||||
@__init__.register(list)
|
||||
@__init__.register(tuple)
|
||||
def _from_list_tuple(self, data):
|
||||
data = data or [[]]
|
||||
if not isinstance(data[0], (list, tuple)):
|
||||
raise TypeError("список должен быть прямоугольным, состоящим из чисел")
|
||||
|
||||
self.rows, self.cols = len(data), len(data[0])
|
||||
self.data = []
|
||||
for row in data:
|
||||
if len(row) != self.cols:
|
||||
raise TypeError("список должен быть прямоугольным, состоящим из чисел")
|
||||
new_row = []
|
||||
for value in row:
|
||||
if not isinstance(value, (int, float)):
|
||||
raise TypeError(
|
||||
"список должен быть прямоугольным, состоящим из чисел"
|
||||
)
|
||||
new_row.append(value)
|
||||
self.data.append(new_row)
|
||||
|
||||
def validate_index(self, index):
|
||||
if not isinstance(index, tuple):
|
||||
raise IndexError("недопустимые значения индексов")
|
||||
row, col = index
|
||||
if (
|
||||
not isinstance(row, int)
|
||||
or not 0 <= row < self.rows
|
||||
or not isinstance(col, int)
|
||||
or not 0 <= col < self.cols
|
||||
):
|
||||
raise IndexError("недопустимые значения индексов")
|
||||
return row, col
|
||||
|
||||
def __getitem__(self, index):
|
||||
row, col = self.validate_index(index)
|
||||
return self.data[row][col]
|
||||
|
||||
def __setitem__(self, index, new_value):
|
||||
row, col = self.validate_index(index)
|
||||
if not isinstance(new_value, (int, float)):
|
||||
raise TypeError("значения матрицы должны быть числами")
|
||||
self.data[row][col] = new_value
|
||||
|
||||
def validate_size(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
raise NotImplementedError
|
||||
if (self.rows, self.cols) != (other.rows, other.cols):
|
||||
raise ValueError("операции возможны только с матрицами равных размеров")
|
||||
|
||||
def mapped(self, op, other):
|
||||
if isinstance(other, (int, float)):
|
||||
return [*map(lambda row: [*map(lambda x: op(x, other), row)], self.data)]
|
||||
|
||||
self.validate_size(other)
|
||||
return [
|
||||
*map(
|
||||
lambda self_row, other_row: [
|
||||
*map(
|
||||
lambda self_x, other_x: op(self_x, other_x), self_row, other_row
|
||||
)
|
||||
],
|
||||
self.data,
|
||||
other.data,
|
||||
)
|
||||
]
|
||||
|
||||
def __len__(self):
|
||||
return self.rows
|
||||
|
||||
def __iter__(self):
|
||||
return (
|
||||
(self.cells[row][col].data for col in range(self.cols))
|
||||
for row in range(self.rows)
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.data == other.data
|
||||
if isinstance(other, list):
|
||||
return self.data == other
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.data!r})"
|
||||
|
||||
|
||||
def tests():
|
||||
# 10x = https://stepik.org/lesson/701994/step/12?discussion=5990560&unit=702095
|
||||
code = (
|
||||
b"Y-w|JGDILfAX{58EFdynEFfDmEFd&pEFfDoEFd;4AU9oI3UqRLItm~lARu#eAUz;WVRUk7cqnX"
|
||||
+ b"Xb96F9DGFtHV`Xr3AXIs9WkqswZ*n>cARr(hVRLh3a&#b6a&=`2Wo&b0Itm~lARu9Lb7gXLAVy(q"
|
||||
+ b"b7d?bBGA3iwII=f(6i9B(7n*L(SXpn(SXpt(6Z35(7VvS(Sgy7AketcfzZ0pyU~u(htRdqz0kPOw"
|
||||
+ b"IEb^aAieua&K}V(6rFI(T^a|z0kEF(7(}u(T~u)(7w@w(6i9K(7Vx$(7n*U(6i9KAkl%)ztFhRfz"
|
||||
+ b"Z0puprQ~AkezdzR<nVfzg7|fYF1{y3vBrzR`ftwIEGlbaH8UA_@v@X>)WkL?AsNTU#+KAX{BsEFf"
|
||||
+ b"DmEFd&pEFfDoEFd;rT?%w^c{&OpARr)fbRaz-O<{C$X?Q4XX>)WkL@5eocw=R7bRblDaAieua&K}"
|
||||
+ b"v3LqdLAYpTJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jk"
|
||||
+ b"xY2;nzR<GJu+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~`d2nS#a&m8SAkehXyU~vz(Sgvv("
|
||||
+ b"74fo(7MpDAke+gwII;A(6=DbhtRmufzY+kyCBfAAkezdzR<nVfzg7|fYF1{y3vBrzR`ftwIEGlba"
|
||||
+ b"H8UA_@w0a(OxmARr(hb95j*AWdO(a%p%dCov~1ATlf<FewUUcw=R7bRblDaAieua&K}v3LqdLAYp"
|
||||
+ b"TJWpZ>NRC0A?3T13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<G"
|
||||
+ b"Ju+Y2EzR`ivj3Cgs(Sgvq(7Vx&(TC8r(7n*O(6t~`d2nS#a&m8SAkehXyU~vz(7n*LAkl}=xY2>o"
|
||||
+ b"yU@PSveAptg&@$d(SXpi(Sy*u(6!LL(Sp#v(6S)VvLMj9(7w>U(Sgx|(SXr|(7Mrr(7w@t(6t~<V"
|
||||
+ b"RUk7cp?f4Y-w|JGDILfAX{58EFdynEFfDmEFd&pEFfDoEFd;rT?%bsbaH8UAUz;WVRUk7cqnXXb9"
|
||||
+ b"6F9DGFh8b7gXLAZ=lEa%p&5GAtl5T_8O@AT}%@BGA3iwa~KAwb6jkz0kfO(7w@v(SXpf(6P|I(Sp"
|
||||
+ b"#h(7Pbey3oGRz0rZug3*A{gV4Isg3!LvfFRJg(7VvMAYWf+WprtDWo=(yA_@v^VRUk7cpyC>O<{C"
|
||||
+ b"$X?Q3!EFd*3ATcm03So0|WpZ>NZDDkBX?R;REFd&pAU!=GF)%D3BGA3iwa~KAwb6jkz0kfO(7w@v"
|
||||
+ b"(SXpf(6P|I(Sp#h(7Pbey3oGRz0rZug3*A{gV4Isg3!LvfFRJg(7VvMAYWf+WprtDWo=(yA_@w0a"
|
||||
+ b"(OxmARr(hb|5_<ZDDkBX?R;REFdj0T?%D*V`Xr3AW3dyWq3t$a&K}v3LqdLAYpTJWpZ>NRC0A?3T"
|
||||
+ b"13_WjYEVARr)Nb8}^KbRb4yY;$ESAR^Gc(6u1ZfzY$iwa~rLwb6jkxY2;nzR<GJu+Y2EzR`ivj3C"
|
||||
+ b"gs(Sgvq(7Vx&(TC8r(7n*O(6t~*Ze(S6MRIa)av}-}baHt*3LqdLAa)=<AZ=lEa%p&5Com^0AT(V"
|
||||
+ b"HWq4y{aC9I^Ze(S6MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLAYpTJWpZ>NMqzAoWh@{f"
|
||||
+ b"(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T>rF(6!LL(74dGAW3dyW"
|
||||
+ b"q3t$a&K}X3JPsubaH8UTQDpjFkK)$AU6tOb8}^KbRcbEbaH8UTQDpjFkK)$Js>wMAR^Gc(6!LA(6"
|
||||
+ b"!Nk(7n*UAke<ig3*A`u+Xv4zR`lvu+Y08Ute=&bZK;DZC_s^3JP>`c{&OpARr)ZVRUk7cv~<mATV"
|
||||
+ b"7ZJs>AxCkkbFV`Xr3AXIs9WkqswZ*n>cARr(hVRLh3a&#b6a&=`2Wo&b0Itm~lARu9Lb7gXLAVy("
|
||||
+ b"qb7d?bBGA3iwII=f(6i9B(7n*L(SXpn(SXpt(6Z35(7VvS(Sgy7AketcfzZ0pyU~u(htRdqz0kPO"
|
||||
+ b"wIEb^aAieua&K}V(6S(3Uvp)2X>?_6Utb~$3T-hUJs?eCbaH8UC|g@GEFdynEFfDmEFd&pT`3A}G"
|
||||
+ b"9W!5O<{C$X?Q4GTQMvkF<mSmTQMvkF<mSmTQMvkF<o6L3JP>`c{&OpARr)ZVRUk7cpyC>Z80D#AZ"
|
||||
+ b";=VWq4y{aC9J6VQh6}MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLAYpTJWpZ>NMqzAoWh@"
|
||||
+ b"{f(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T>rF(6!LL(74dGAXZ^"
|
||||
+ b")b!A0za&K}V(7(}u(6}JcfzZ3qzR<SNwa~rLxX`#D(7e#F(Sp%{(74ftAkl!(u+X>Az0r%&g&@&@"
|
||||
+ b"(6G?A(7e#K(SXpt(6S;53T-hUJs?eCbaH8UC|g@GEFdynEFfDmEFd&pT`3A}G9W!5O<{C$X?Q4GT"
|
||||
+ b"QMvkF<mSmTQMvkF<o6L3T<I@a%p%VJs@o{AS)nkG74dHb7gXLAZc@HZgX^DZewLAZDDkBX?QFkO<"
|
||||
+ b"{C$X?Q6tAR^Gd(7({N(SXpf(T32t(T^a}fzZ3qzR<SNwa~rLxY3Uw(7e#F(Sp%{(74ftAkehXzR<"
|
||||
+ b"hSw$Q!MuprQ~(7w>O(6Z5h(6G^o(6G^h(TpI`jnKN#x6rlFywJbUyU~x)fFRJi(7VvE(Sgx{(6As"
|
||||
+ b")VRUk7cp?g6b8}^KbRcbEbaH8UTQMvkF<l@%Js>qKAR^Gc(6!LA(6!Nk(7n*UAke<ig3*A`u+Xv4"
|
||||
+ b"zR`lvu+Y2EuprRB(7({N(SXpf(T32t(T^a}fzZ3qzR<SNwa~rLxY3Uw(7e#F(Sp%{(74ftA_`%1b"
|
||||
+ b"7gXLAZ;;QF)Sc4T_8O@AT%IhZe$>BF<US!ATeDaJv|^YAYpD~AZ;>RF)Sc4T_8O@ATc0Z3LqdLAR"
|
||||
+ b"r(hVQyp~Z8BRhEFdslAU!=GF)Sb=(74fo(S^{y(6rFK(TmWvAke(fu+f6ifY7+nhS7^4(7n*LAke"
|
||||
+ b"hXzR<hSw$Q!Niy+Xv(6!LL(T~xB(Tvf7(T^a|ztMouxFFEJ(7({N(SXpf(T32t(6}JcfzZ3qzR<S"
|
||||
+ b"Nwa~rLxY3Uy3JPs8AUz;WVRUk7cqlR~ATlf<F)0dZWM6GDUvF$=AUz;yWGHPhDGF^eAUz;WVRUk7"
|
||||
+ b"cqlR~ATlf<F)0ddF(5r4Z80D#AZ;=VX=Gn*F<)+FcOX3=X=EsEF)0dRb8}^KbRcPDUu`j8Z){{BA"
|
||||
+ b"w3{zWM6GDUv6c0EFdD#vLMlb(6!LF(Sy*t(Tvf8(6G^h(6u1YzR<tWwb6jku+fIlxX`#D(Sgvr(7"
|
||||
+ b"w>N(6!LL(74f$AkehXzR<hSw$QcEy&%zn(7w>O(6rF7(6Z35(Sp&8(SgyAAkdxAp3suetI(Ms(T&"
|
||||
+ b"i$(6`XF(7e#U(7Vx((SRV(y3o7Ou+f3hfzYrZO<{C$X?P+E3T<I@a%p%VJs?eCbaH8UC^9S{GAtl"
|
||||
+ b"4DGF^MJs@pibaH8UAS)m-FbZLFb7gXLAZ=lEa%p&5Ff1T2T_8O@AZ=lEa%p&5F)Sc4T_8O@ATTT-"
|
||||
+ b"BG9<efzgG~zR<MLz0r%%wII;E(6G^h(SXpn(S~6l(7n*LAkehXzR<hSw$Q!MuprR9(6!LL(T~xB("
|
||||
+ b"Tvf7(T^a|ztMouxFFEJ(7({N(SXpf(T32t(6}JcfzZ3qzR<SNwa~rLxY3UwV<6Fo(74fo(7VvS(7"
|
||||
+ b"YlFVRLh3a&#bVTQDpjFkK)$Js>eKEFdD#z0kGLve32BfY80rz97)P(Sp%{(6G?4(7w@v(6G?E(6A"
|
||||
+ b"uTzR<tWwb6jku+fIlxY3Uw(Sgvr(7w>N(6!LL(74f$Ake(fu+f6ifY7+nhS7^4(Sab*htRmufzZ3"
|
||||
+ b"qzR<iP3JPs8AUz;WVRUk7cqlR~ATlf<F)0ddG9W!5O<{C$X?Q4GTQDpjF<mSmTQMvkFkM|K3Tb3z"
|
||||
+ b"ZggpMd0%Z|baH8UAUz;$F(54<Z89JrBOuVU(7w>S(6-RM(6AuTztFzWyU~NuhtRmug3*l8fzgj3("
|
||||
+ b"6!LC(74dO(74fu(7n*G(T^a|ywI@Gg3*A`xY35tunJ*wb7gXLAZ;;QFf1T2T_8O@ATc0eZe$>BF<"
|
||||
+ b"UV#ATeDaJv|^XAYpD~AZ;>RFf1T2T_8O@ATS_Y3LqdLARr(hVQyp~Z8BRhEFdvmAU!=GF)Sb=(74"
|
||||
+ b"fo(S^{y(6rFK(TmWvAke(fu+f6ifY7+nhS7^4(7n*LAkehXzR<hSw$Q!Niy+Xv(6!LL(T~xB(Tvf"
|
||||
+ b"7(T^a|ztMouxFFEJ(7({N(SXpf(T32t(6}JbveApthtRmug3z$gz0kPPk0J_Tb8}^KbRcPDWo~q7"
|
||||
+ b"ba`KGVRUk7cv~<mATV7ZJv|^XAYpD~AZcV}ZggpMd0%Z|baH8UTQMvkF<l@%Js>eGAR^Gc(6!LA("
|
||||
+ b"6!Nk(7n*UAke<ig3*A`u+Xv4zR`lvu+Y2EuprRB(7({N(SXpf(T32t(T^a|veApthtRmug3z$gz0"
|
||||
+ b"kPPk08*z(6G^h(SXpn(S{-l3T<I@a%p%VJs?eCbaH8UC^9S{GAtl5DGF^MJs@pibaH8UAT1y<3So"
|
||||
+ b"0|WpZ>NZDDkBX?R;OEFdslAU!=GZDDkBX?R;PEFdvmAU!=GF)Sb=(74fo(S^{y(6rFK(TmWvAke("
|
||||
+ b"fu+f6ifY7+nhG8Jkz0kEF(6rFL(7VvK(7n*GAke(fwa~rMkI{nBjM0J7k08*$(SXpnAke<hztFYO"
|
||||
+ b"fY7kfhS0dsxFFE7(TmZC(74fp(6G?G(74f$AY&lWhtRmufzZ3qzR<iP3So0|WpZ>NZCfxbATV7ZJ"
|
||||
+ b"v|_8TQMvkF<l@%Js>bFAR^Gc(6!LA(6!Nk(7n*UAke<ig3*A`u+Xv4zR`lvu+Y2EuprRB(7({N(S"
|
||||
+ b"Xpf(T32t(T^a|veApthtRmug3z$gz0kPPk08;9(74fo(7VvEAketbw;<5G(6G^h(SXpn(T35BA^"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
381
mod_oop/3.x_01_tictactoe.py
Normal file
381
mod_oop/3.x_01_tictactoe.py
Normal file
@@ -0,0 +1,381 @@
|
||||
"""
|
||||
https://stepik.org/lesson/717070/step/1?unit=717930
|
||||
|
||||
Испытание магией.
|
||||
|
||||
Вы прошли магические методы.
|
||||
Начальство оценило вашу стойкость, рвение и решило дать вам испытание для подтверждения уровня полученных навыков.
|
||||
Вам выпала великая честь создать полноценную программу игры в "Крестики-нолики".
|
||||
И вот перед вами текст с заданием самого испытания.
|
||||
|
||||
## Техническое задание
|
||||
|
||||
Необходимо объявить класс с именем TicTacToe (крестики-нолики) для управления игровым процессом. Объекты этого класса будут создаваться командой:
|
||||
game = TicTacToe()
|
||||
|
||||
В каждом объекте этого класса должен быть публичный атрибут:
|
||||
pole - двумерный кортеж, размером 3x3.
|
||||
|
||||
Каждый элемент кортежа pole является объектом класса Cell:
|
||||
cell = Cell()
|
||||
|
||||
В объектах этого класса должно автоматически формироваться локальное свойство:
|
||||
value - текущее значение в ячейке: 0 - клетка свободна; 1 - стоит крестик; 2 - стоит нолик.
|
||||
|
||||
Также с объектами класса Cell должна выполняться функция:
|
||||
bool(cell) - возвращает True, если клетка свободна (value = 0) и False - в противном случае.
|
||||
|
||||
К каждой клетке игрового поля должен быть доступ через операторы:
|
||||
res = game[i, j] # получение значения из клетки с индексами i, j
|
||||
game[i, j] = value # запись нового значения в клетку с индексами i, j
|
||||
|
||||
Если индексы указаны неверно (не целые числа или числа, выходящие за диапазон [0; 2]), то следует генерировать исключение командой:
|
||||
raise IndexError('некорректно указанные индексы')
|
||||
|
||||
Чтобы в программе не оперировать величинами: 0 - свободная клетка; 1 - крестики и 2 - нолики, в классе TicTacToe должны быть три публичных атрибута (атрибуты класса):
|
||||
FREE_CELL = 0 # свободная клетка
|
||||
HUMAN_X = 1 # крестик (игрок - человек)
|
||||
COMPUTER_O = 2 # нолик (игрок - компьютер)
|
||||
|
||||
В самом классе TicTacToe должны быть объявлены следующие методы (как минимум):
|
||||
init() - инициализация игры (очистка игрового поля, возможно, еще какие-либо действия);
|
||||
show() - отображение текущего состояния игрового поля (как именно - на свое усмотрение);
|
||||
human_go() - реализация хода игрока (запрашивает координаты свободной клетки и ставит туда крестик);
|
||||
computer_go() - реализация хода компьютера (ставит случайным образом нолик в свободную клетку).
|
||||
|
||||
Также в классе TicTacToe должны быть следующие объекты-свойства (property):
|
||||
is_human_win - возвращает True, если победил человек, иначе - False;
|
||||
is_computer_win - возвращает True, если победил компьютер, иначе - False;
|
||||
is_draw - возвращает True, если ничья, иначе - False.
|
||||
|
||||
Наконец, с объектами класса TicTacToe должна выполняться функция:
|
||||
bool(game) - возвращает True, если игра не окончена (никто не победил и есть свободные клетки) и False - в противном случае.
|
||||
|
||||
Все эти функции и свойства предполагается использовать следующим образом (эти строчки в программе не писать):
|
||||
game = TicTacToe()
|
||||
game.init()
|
||||
step_game = 0
|
||||
while game:
|
||||
game.show()
|
||||
|
||||
if step_game % 2 == 0:
|
||||
game.human_go()
|
||||
else:
|
||||
game.computer_go()
|
||||
|
||||
step_game += 1
|
||||
|
||||
|
||||
game.show()
|
||||
|
||||
if game.is_human_win:
|
||||
print("Поздравляем! Вы победили!")
|
||||
elif game.is_computer_win:
|
||||
print("Все получится, со временем")
|
||||
else:
|
||||
print("Ничья.")
|
||||
|
||||
Вам в программе необходимо объявить только два класса: TicTacToe и Cell так, чтобы с их помощью можно было бы сыграть в "Крестики-нолики" между человеком и компьютером.
|
||||
|
||||
P.S. Запускать игру и выводить что-либо на экран не нужно. Только объявить классы.
|
||||
P.S.S. Домашнее задание: завершите создание этой игры и выиграйте у компьютера хотя бы один раз.
|
||||
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
import random
|
||||
|
||||
|
||||
class GameOverException(Exception):
|
||||
...
|
||||
|
||||
|
||||
class Cell:
|
||||
CHARS = "⬜⭕❌"
|
||||
|
||||
class State(Enum):
|
||||
FREE = 0
|
||||
COMPUTER = 1
|
||||
HUMAN = 2
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, v):
|
||||
return v if isinstance(v, cls) else cls(v)
|
||||
|
||||
def __init__(self, value=State.FREE):
|
||||
self.__state = self.State.wrap(value)
|
||||
|
||||
@property
|
||||
def is_free(self):
|
||||
return self.state is self.State.FREE
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.__state.value
|
||||
|
||||
@value.setter
|
||||
def value(self, v):
|
||||
self.__state = self.State.wrap(v)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.__state
|
||||
|
||||
@state.setter
|
||||
def state(self, v):
|
||||
self.__state = self.State.wrap(v)
|
||||
|
||||
def __bool__(self):
|
||||
return self.is_free
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.value!r})"
|
||||
|
||||
def __str__(self):
|
||||
return self.CHARS[self.value]
|
||||
|
||||
|
||||
class TicTacToe:
|
||||
# для тестов
|
||||
FREE_CELL = Cell.State.FREE.value
|
||||
HUMAN_X = Cell.State.HUMAN.value
|
||||
COMPUTER_O = Cell.State.COMPUTER.value
|
||||
|
||||
class State(Enum):
|
||||
ACTIVE = 0
|
||||
HUMAN_WIN = 1
|
||||
COMPUTER_WIN = 2
|
||||
DRAW = 3
|
||||
|
||||
def is_ended(self):
|
||||
return self is not self.ACTIVE
|
||||
|
||||
def __init__(self, size=3, pole=None):
|
||||
self.size = size
|
||||
self.state = self.State.ACTIVE
|
||||
if pole is None:
|
||||
pole = tuple(
|
||||
tuple(Cell() for _ in range(self.size)) for _ in range(self.size)
|
||||
)
|
||||
self.pole = pole
|
||||
|
||||
def init(self):
|
||||
for row in self.pole:
|
||||
for cell in row:
|
||||
cell.value = Cell.State.FREE.value
|
||||
self.state = self.State.ACTIVE
|
||||
|
||||
def __bool__(self):
|
||||
return not self.state.is_ended()
|
||||
|
||||
def _find_line(self, value):
|
||||
for row in self.pole:
|
||||
if all(cell.value == value for cell in row):
|
||||
return True
|
||||
for col in zip(*self.pole):
|
||||
if all(cell.value == value for cell in col):
|
||||
return True
|
||||
if all(self[i, i] == value for i in range(self.size)):
|
||||
return True
|
||||
if all(self[i, self.size + ~i] == value for i in range(self.size)):
|
||||
return True
|
||||
|
||||
def _update_state(self):
|
||||
if self._find_line(Cell.State.HUMAN.value):
|
||||
self.state = self.State.HUMAN_WIN
|
||||
elif self._find_line(Cell.State.COMPUTER.value):
|
||||
self.state = self.State.COMPUTER_WIN
|
||||
elif not any(cell.is_free for row in self.pole for cell in row):
|
||||
self.state = self.State.DRAW
|
||||
|
||||
@property
|
||||
def is_human_win(self):
|
||||
return self.state is self.State.HUMAN_WIN
|
||||
|
||||
@property
|
||||
def is_computer_win(self):
|
||||
return self.state is self.State.COMPUTER_WIN
|
||||
|
||||
@property
|
||||
def is_draw(self):
|
||||
return self.state is self.State.DRAW
|
||||
|
||||
def _get_key(self, key):
|
||||
if (
|
||||
not isinstance(key, tuple)
|
||||
or len(key) != 2
|
||||
or any(not isinstance(x, int) or x < 0 or x >= len(self.pole) for x in key)
|
||||
):
|
||||
raise IndexError("неверный индекс клетки")
|
||||
return key
|
||||
|
||||
def __getitem__(self, key):
|
||||
row, col = self._get_key(key)
|
||||
return self.pole[row][col].value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if not self:
|
||||
raise GameOverException("игра закончена")
|
||||
|
||||
row, col = self._get_key(key)
|
||||
cell = self.pole[row][col]
|
||||
if not cell.is_free:
|
||||
raise ValueError("клетка уже занята")
|
||||
|
||||
cell.value = value
|
||||
self._update_state()
|
||||
|
||||
def human_go(self):
|
||||
pending = True
|
||||
prompt = f"Введите координаты клетки через пробел (0-{self.size - 1}, сначала строка)\nДля выхода введите Q\nВаш ход: "
|
||||
while pending:
|
||||
answer = input(prompt)
|
||||
if answer.lower() == "q":
|
||||
print("Вы сдались!")
|
||||
self.state = self.State.COMPUTER_WIN
|
||||
pending = False
|
||||
continue
|
||||
try:
|
||||
row, col = map(int, answer.split())
|
||||
except ValueError:
|
||||
print("Неверный формат ввода")
|
||||
continue
|
||||
try:
|
||||
self[row, col] = Cell.State.HUMAN
|
||||
pending = False
|
||||
except IndexError:
|
||||
print("Неверные координаты клетки")
|
||||
except ValueError:
|
||||
print("Клетка уже занята")
|
||||
except GameOverException:
|
||||
print("Эй, что происходит?!")
|
||||
pending = False
|
||||
|
||||
def computer_go(self):
|
||||
free_cells = [
|
||||
(i, j) for i in range(self.size) for j in range(self.size) if not self[i, j]
|
||||
]
|
||||
row, col = random.choice(free_cells)
|
||||
print("Я выбрал клетку", row, col, end="!\n")
|
||||
self[row, col] = Cell.State.COMPUTER
|
||||
|
||||
def show(self):
|
||||
print(self)
|
||||
|
||||
def play(self):
|
||||
human_turn = True
|
||||
turns = self.computer_go, self.human_go
|
||||
while self:
|
||||
if human_turn:
|
||||
self.show()
|
||||
turns[human_turn]()
|
||||
human_turn = not human_turn
|
||||
|
||||
print("Игра окончена!")
|
||||
self.show()
|
||||
|
||||
if self.is_human_win:
|
||||
print("Вы выиграли!")
|
||||
elif self.is_computer_win:
|
||||
print("Ура! Я выиграл!")
|
||||
elif self.is_draw:
|
||||
print("Ничья!")
|
||||
else:
|
||||
print("Что-то пошло не так...")
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
def __str__(self):
|
||||
c = self.size - 1
|
||||
result = (
|
||||
f"╭─{'─┬─' * c}─╮\n"
|
||||
+ f"├─{'─┼─' * c}─┤\n".join(
|
||||
map(lambda row: f"│{'│'.join(map(str, row))}│\n", self.pole)
|
||||
)
|
||||
+ f"╰─{'─┴─' * c}─╯"
|
||||
)
|
||||
return result
|
||||
|
||||
def __repr__(self):
|
||||
args = []
|
||||
if self.size != 3:
|
||||
args.append(f"size={self.size!r}")
|
||||
if sum(self[i, j] for i in range(self.size) for j in range(self.size)):
|
||||
args.append(f"pole={self.pole!r}")
|
||||
args = ", ".join(args)
|
||||
return f"{self.__class__.__name__}({args})"
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
argc = len(sys.argv)
|
||||
if argc > 1 and sys.argv[1] == "play":
|
||||
game = TicTacToe(argc > 2 and int(sys.argv[2]) or 3)
|
||||
game.play()
|
||||
exit()
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"V`Xe?AUz;MWo&FHDGFh8b7gXLAY)~0Y%X?TY;|QIJv|^WEFdD#z0k1HhtROlyU~o$z0khUwII;"
|
||||
+ b"9(7n*G(TC8r(7n*O(6u1Yu+f6ifY7+mvC)Ikg3z!ac42IFWgyVL(6P~q(6!LI(Sp#hAkezdyU?)F"
|
||||
+ b"fzg4`upmQaY-}LVw9vlLyU@1Kz0kfO(6P~r(Sp&8Akl!(u+Xy5z0kfOFd_<Jb8}^KbRc4HZ)_-IW"
|
||||
+ b"o&FIEFdD$gwcc0z0kVRhS0dtk04@iZ)_mYw9vcJk08*#(6P~q(6!LI(Sp#hAkezdyU?)Ffzg4`up"
|
||||
+ b"mQaY-}LVve32BfY80sgV4LsuprRA(6!LA(6!Nk(7n*U(6u1Yx6r-Nu+fLmwa~rLxX`sC3S(t#Y%X"
|
||||
+ b"?TY;|QIJs>d(VRLh3a&#bKZ*OcUV`Xe?DIh&PAVy(qb7d?bBGH7=gV4Ruy3vNvxY3UwVsCG3Akeh"
|
||||
+ b"XyU~vz(7w>I(TdQu(7Mrr(6AuTy3o7Ou+f3hfzYrZLuG7iAkebVwb6jkz0rfvyU?&8(7n*L(6Z3A"
|
||||
+ b"(SXps(7w>MAkeqaz0k1HhtRdqz0kPOwIT`%VRLh3a&#bQVRK=0baE(EX=7AjV^nWtEFdRyXm58XD"
|
||||
+ b"Ij5PWFTl^b76FJawt@3V^m>dRBvT0ASY;bZDDR-XKyDdAYpD~AZTH8VRUqIC{$@<RAFOOZ)GeXCu"
|
||||
+ b"47IaCLNLa$jd}Cn+o-BG9_fyU?)Ffzg2=RB2;WVPjNpWgyVB(7w>S(6-RE(7hngxX`@Nwb6pnj3C"
|
||||
+ b"gw(6!Nm(7w>L(TgB+Xm58cAZT@MVQyb%Z!92VZ*6dObY*g1XKx}33TI($WgtBuRB2;WVPjNpWhf~"
|
||||
+ b"MVRLh3a&#bKZ*OcUXJKt+DJ&o&(S*^1(7n*Q(T32t(T^ZvZ*OcM(6Z3A(SXps(Sy*t(6AuTz0kGL"
|
||||
+ b"ve32BfY80rzR<NG(6`XN(6G^m(6!LL(74f$AkehXyU~vz(7w>I(TdQu(7Mrr(6AuTy3o7Ou+f3hf"
|
||||
+ b"zYrZRB2;WVPjNpWg-e;b8}^KbRcJ8ZDm_9EFdslAU!=GFd$)WWFTi@ZDm_BEFdynAU!=GFf1S<(7"
|
||||
+ b"n*L(6Z3A(SXps(TmWvAkeqaz0k1HhtRdqz0kPPk08;H(TC8r(6!LIEFjRb(6`Z#(Sp&7(6u1YztF"
|
||||
+ b"xQ(74dO(6rFC(7Mrq(6G?FA_`|=ZDm_AEFdvmAUz;dX=7AjV^nWtE=W~PK~7&-3So0|WpZ>NXJKt"
|
||||
+ b"+TQMvkF<l@%Js?zRV^m>dRBvT2NL5WiPG49oAR^Gc(6!LA(6!Nk(7n*UAkl!(u+Xv4zR`lvu+X*9"
|
||||
+ b"f*{bo(7({N(SXpf(Sp#v(SRV(ztMouxY2>ove2;5xX`lDu+Y8GxY3Uw(7n*U(6Z3J(6i9KAkeqaz"
|
||||
+ b"0k1HhtRdqz0kPPk08*pAkmM}htRdqxzM`NgCNkj(6iBi(7w>J(7w>K(7qthztFzWyU~v#3JPaoZD"
|
||||
+ b"m_9EFdslAUz;dX=7AjV^nWtE<;aEP*qe#QeRIBVRLh3a&#bPVQpnwFf1T2T_8O@AXI5%RAFOOZ)G"
|
||||
+ b"k+Pfbu&R7Fx>Pb?rJ(7n*L(6Z3A(SXps(7qtifY7kevCzKJg3z$gwb6ng(7w>W(6!Nk(6G^h(7w@"
|
||||
+ b"tAke?jfY7+nfzYzhu+X^Bve2;5z0kPPk08*!(7w>J(7w>K(7qthx6r-Nu+fLmwa~rLxY3Uw(6S)W"
|
||||
+ b"kI{$Fwa~fHy3vCm(74dE(SXpt(6Z3J(6i9KAke?izR<hTk0J^RXJKt+E@^IQbSNnbVRLh3a&#bPV"
|
||||
+ b"QpnwFf1T2T_8O@AXI5%RAFOOZ)Gk<Qbk2yLq$wXAYpD~AZKB1Wm_>UATeDaJv|^)X=7AjV^nWtE="
|
||||
+ b"E#CMPEZjOiU~wBGA9lfY7)g(74dO(74ft(74dB(7VvM(6`XA(T32t(6}JbxX`oFfY83sve3TJv(U"
|
||||
+ b"aE(7({W(7Vx(AkebWfzY)e(7MpO(6!Nm(7MpLAkehXzR<hSw$Q!Niy+Xy(SXpn(7n*O(7e#F(Sp&"
|
||||
+ b"8Akeqaz0k1HhtRdqz0kPOwII;A(6=Dau+f6ifY7+mvC)Ikg3z!aMp8vZUqeMqOd<*jbaHt*3LqdL"
|
||||
+ b"AZKB1Wm_{WATV7ZJs>m+Wq4y{aC9I^Ze(S6MRIa)aykkiARr)Nb8}^KbRbl6b!7@=Y;$Eg3LqdLA"
|
||||
+ b"YpTJWpZ>NMqzAoWh@{f(7n*LAkl%)v(UBBz0kGMfY7+nfY83sve2;5yU@PTfzga0(74fo(7MpO(T"
|
||||
+ b">rF(6!LL(74dGAW3dyWq3t$a&K}X3JPaoZDlTLZfSHVDGFh8b7gXLAZKB1WiDxRUubo0VQyb{X>K"
|
||||
+ b"4rJs?J5Y;$EGVQyp~XJKt+E@^XLV{dJ6b#!HNUw3J4AU!=GMqzAoWgua0WFTi@ZDlTLb6;d~VRs-"
|
||||
+ b"sJs?J5Y;$ESAR^Ge(SXpnAketbz0kPPhS0dsu+Y2ExX`!Iu+fIlxX`#D(74dE(SXs5AkeVUg3*A`"
|
||||
+ b"xX`iDgVBP~iy&!pUubo0VQyb{X>KeaX>(s=Z*6dObY*g1cWG`cAZc@7WO8A5AkehXzR<hSw$Q!Ni"
|
||||
+ b"y+Xk(TmZ7(TpI`fY7keve3QJiy%f}Y;$ESAkebVzR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfz"
|
||||
+ b"gN1xY2^qi_o&ru+X*9g3*D|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(7(}u(6}JbveApsx6r=Ove3"
|
||||
+ b"04(7e#K(Sp#v(6rF7AZc!CbSNnz3JPaoZDm_9EFdslAUz;dX=7AjV^nWtE=W~PK~7&-3TI($Wm_>"
|
||||
+ b"UATeDaJs?zRV^m>dRBvT2NL5WiPG49GXJKt+TQV#lGF>1&AXI5%RAFOOZ)Gk>RZT%oUswuZb8}^K"
|
||||
+ b"bRcJ8ZDlTLb6;q6ZDDR-cWG`QVQyp~XJKt+E@^XLV{dJ6b#!HNUw3J4AU!=GMqzAoWgua0WFTi@Z"
|
||||
+ b"DlTLb6;d~VRs-sJs?J5Y;$ESAR^Gc(6!LI(7w@t(SXpk(7Mrr(7n*UAke?iwb6jkwb6mmhtRmug3"
|
||||
+ b"*i6ve2;6j?seAfzgj3(6G^h(SXpn(6P~j(Sp&7AZc@7XmxF2ZeMq4ZY&^ab6;a`ZE$sTWpZD4X>K"
|
||||
+ b"eaX>(s>a$$EaAkdP~zR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfzgN1xY2^qi_o&ru+X*9g3*D"
|
||||
+ b"|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(6S)VywJYTywJ7Kz0rao(7(}u(74fo(6Z3J(6!LL(74f$"
|
||||
+ b"Ake+gzR<GKi_wK3(6`XN(6G^m(6!LL(74f$Ake?iz97)J(7n*K(6!LI(Sgvg(7ZYzXJKt+TWKsHY"
|
||||
+ b"F!{bAa-GFb!8$73TI($WiDxMX>=$l3TI($Wm_;TATV7ZJs?zRV^m>dRBvT2Lr+amRa8Y%Ur!2WVQ"
|
||||
+ b"pnwF)Sc3T_8OmRB2;WVPjNpWiCTcO;A--MN(f+3TI($Wm_^VATV7ZJs?zRV^m>dRBvT2Lr+amRa8"
|
||||
+ b"Y%Ur!2Qb8}^KbRcJ8ZDlTLb6;q6ZDDR-cWG`QJv|^sVQh0{AYpD~AZKB1WiDxRUt@1=aCLNLa$k3"
|
||||
+ b"8ZXjW9WFTi@ZDlTLb6;d~VRs-sJs?J5Y;$ESAR^Gc(6!LI(7w@t(SXpk(7Mrr(7n*UAke?iwb6jk"
|
||||
+ b"wb6mmhtRmug3*i6ve2;6j?seAfzgj3(6G^h(SXpn(6P~j(Sp&7AZc@7XmxF2ZeMq4ZY&^ab6;a`Z"
|
||||
+ b"E$sTWpZD4X>KeaX>(s>a$$EaAkdP~zR<VOywJYTw$Q!Mz97)O(6u1YztFYOfY7zkfzgN1xY2^qi_"
|
||||
+ b"o&ru+X*9g3*D|k08;3(Sp#h(Sp%~(Sab)xX`oFfYFN}(6S)VywJYTywJ7Kz0rao(7(}u(74fo(6Z"
|
||||
+ b"3J(6!LL(74f$Ake+gzR<GKi_wK3(6`XN(6G^m(6!LL(74f$Ake?iz97)J(7n*K(6!LI(Sgvg(7ZY"
|
||||
+ b"zXJKt+TWKsHYF!{bAa-GFb!8#"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
424
mod_oop/4.8_01_linked_graph.py
Normal file
424
mod_oop/4.8_01_linked_graph.py
Normal file
@@ -0,0 +1,424 @@
|
||||
"""
|
||||
https://stepik.org/lesson/724551/step/1?unit=725686
|
||||
|
||||
Испытание "Бремя наследия"
|
||||
|
||||
Необходимо написать универсальную основу для представления ненаправленных связных графов и поиска в них кратчайших маршрутов.
|
||||
Далее, этот алгоритм предполагается применять для прокладки маршрутов: на картах, в метро и так далее.
|
||||
|
||||
Для универсального описания графов, вам требуется объявить в программе следующие классы:
|
||||
Vertex - для представления вершин графа (на карте это могут быть: здания, остановки, достопримечательности и т.п.);
|
||||
Link - для описания связи между двумя произвольными вершинами графа (на карте: маршруты, время в пути и т.п.);
|
||||
LinkedGraph - для представления связного графа в целом (карта целиком).
|
||||
|
||||
Объекты класса Vertex должны создаваться командой:
|
||||
>>> v = Vertex()
|
||||
|
||||
и содержать локальный атрибут:
|
||||
_links - список связей с другими вершинами графа (список объектов класса Link).
|
||||
|
||||
Также в этом классе должно быть объект-свойство (property):
|
||||
links - для получения ссылки на список _links.
|
||||
|
||||
Объекты следующего класса Link должны создаваться командой:
|
||||
>>> v1 = Vertex(); v2 = Vertex()
|
||||
>>> link = Link(v1, v2)
|
||||
|
||||
где v1, v2 - объекты класса Vertex (вершины графа). Внутри каждого объекта класса Link должны формироваться следующие локальные атрибуты:
|
||||
|
||||
_v1, _v2 - ссылки на объекты класса Vertex, которые соединяются данной связью;
|
||||
_dist - длина связи (по умолчанию 1); это может быть длина пути, время в пути и др.
|
||||
|
||||
В классе Link должны быть объявлены следующие объекты-свойства:
|
||||
v1 - для получения ссылки на вершину v1;
|
||||
v2 - для получения ссылки на вершину v2;
|
||||
dist - для изменения и считывания значения атрибута _dist.
|
||||
|
||||
Наконец, объекты третьего класса LinkedGraph должны создаваться командой:
|
||||
>>> map_graph = LinkedGraph()
|
||||
|
||||
В каждом объекте класса LinkedGraph должны формироваться локальные атрибуты:
|
||||
_links - список из всех связей графа (из объектов класса Link);
|
||||
_vertex - список из всех вершин графа (из объектов класса Vertex).
|
||||
|
||||
В самом классе LinkedGraph необходимо объявить (как минимум) следующие методы:
|
||||
def add_vertex(self, v): ... - для добавления новой вершины v в список _vertex (если она там отсутствует);
|
||||
def add_link(self, link): ... - для добавления новой связи link в список _links (если объект link с указанными вершинами в списке отсутствует);
|
||||
def find_path(self, start_v, stop_v): ... - для поиска кратчайшего маршрута из вершины start_v в вершину stop_v.
|
||||
|
||||
Метод find_path() должен возвращать список из вершин кратчайшего маршрута и список из связей этого же маршрута в виде кортежа:
|
||||
([вершины кратчайшего пути], [связи между вершинами])
|
||||
|
||||
Поиск кратчайшего маршрута допустимо делать полным перебором с помощью рекурсивной функции (будем полагать, что общее число вершин в графе не превышает 100).
|
||||
Для тех, кто желает испытать себя в полной мере, можно реализовать алгоритм Дейкстры поиска кратчайшего пути в связном взвешенном графе.
|
||||
В методе add_link() при добавлении новой связи следует автоматически добавлять вершины этой связи в список _vertex, если они там отсутствуют.
|
||||
|
||||
Проверку наличия связи в списке _links следует определять по вершинам этой связи. Например, если в списке имеется объект:
|
||||
_links = [Link(v1, v2)]
|
||||
|
||||
то добавлять в него новые объекты Link(v2, v1) или Link(v1, v2) нельзя (обратите внимание у всех трех объектов будут разные id, т.е. по id определять вхождение в список нельзя).
|
||||
Подсказка: проверку на наличие существующей связи можно выполнить с использованием функции filter() и указанием нужного условия для отбора объектов.
|
||||
|
||||
Пример использования классов, применительно к схеме метро (эти строчки в программе писать не нужно):
|
||||
|
||||
>>> Vertex._num = 0
|
||||
>>> map_graph = LinkedGraph()
|
||||
>>> v1 = Vertex()
|
||||
>>> v2 = Vertex()
|
||||
>>> v3 = Vertex()
|
||||
>>> v4 = Vertex()
|
||||
>>> v5 = Vertex()
|
||||
>>> v6 = Vertex()
|
||||
>>> v7 = Vertex()
|
||||
>>> map_graph.add_link(Link(v1, v2))
|
||||
>>> map_graph.add_link(Link(v2, v3))
|
||||
>>> map_graph.add_link(Link(v1, v3))
|
||||
>>> map_graph.add_link(Link(v4, v5))
|
||||
>>> map_graph.add_link(Link(v6, v7))
|
||||
>>> map_graph.add_link(Link(v2, v7))
|
||||
>>> map_graph.add_link(Link(v3, v4))
|
||||
>>> map_graph.add_link(Link(v5, v6))
|
||||
>>> len(map_graph._links)
|
||||
8
|
||||
>>> len(map_graph._vertex)
|
||||
7
|
||||
>>> map_graph.find_path(v1, v6)
|
||||
([Vertex('A'), Vertex('B'), Vertex('G'), Vertex('F')], [Link(Vertex('A'), Vertex('B'), 1), Link(Vertex('B'), Vertex('G'), 1), Link(Vertex('F'), Vertex('G'), 1)])
|
||||
|
||||
Однако, в таком виде применять классы для схемы карты метро не очень удобно.
|
||||
Например, здесь нет указаний названий станций, а также длина каждого сегмента равна 1, что не соответствует действительности.
|
||||
Чтобы поправить этот момент и реализовать программу поиска кратчайшего пути в метро между двумя произвольными станциями, объявите еще два дочерних класса:
|
||||
|
||||
class Station(Vertex): ... - для описания станций метро;
|
||||
class LinkMetro(Link): ... - для описания связей между станциями метро.
|
||||
|
||||
Объекты класса Station должны создаваться командой:
|
||||
>>> st = Station(name := "Домодедовская")
|
||||
|
||||
где name - название станции (строка). В каждом объекте класса Station должен дополнительно формироваться локальный атрибут:
|
||||
name - название станции метро.
|
||||
|
||||
(Не забудьте в инициализаторе дочернего класса вызывать инициализатор базового класса).
|
||||
В самом классе Station переопределите магические методы __str__() и __repr__(), чтобы они возвращали название станции метро (локальный атрибут name).
|
||||
|
||||
Объекты второго класса LinkMetro должны создаваться командой:
|
||||
>>> link = LinkMetro(v1, v2, dist := 2)
|
||||
|
||||
где v1, v2 - вершины (станции метро); dist - расстояние между станциями (любое положительное число).
|
||||
(Также не забывайте в инициализаторе этого дочернего класса вызывать инициализатор базового класса).
|
||||
|
||||
В результате, эти классы должны совместно работать следующим образом (эти строчки в программе писать не нужно):
|
||||
|
||||
>>> map_metro = LinkedGraph()
|
||||
>>> v1 = Station("Сретенский бульвар")
|
||||
>>> v2 = Station("Тургеневская")
|
||||
>>> v3 = Station("Чистые пруды")
|
||||
>>> v4 = Station("Лубянка")
|
||||
>>> v5 = Station("Кузнецкий мост")
|
||||
>>> v6 = Station("Китай-город 1")
|
||||
>>> v7 = Station("Китай-город 2")
|
||||
|
||||
>>> map_metro.add_link(LinkMetro(v1, v2, 1))
|
||||
>>> map_metro.add_link(LinkMetro(v2, v3, 1))
|
||||
>>> map_metro.add_link(LinkMetro(v1, v3, 1))
|
||||
|
||||
>>> map_metro.add_link(LinkMetro(v4, v5, 1))
|
||||
>>> map_metro.add_link(LinkMetro(v6, v7, 1))
|
||||
|
||||
>>> map_metro.add_link(LinkMetro(v2, v7, 5))
|
||||
>>> map_metro.add_link(LinkMetro(v3, v4, 3))
|
||||
>>> map_metro.add_link(LinkMetro(v5, v6, 3))
|
||||
|
||||
>>> print(len(map_metro._links))
|
||||
8
|
||||
>>> print(len(map_metro._vertex))
|
||||
7
|
||||
>>> path = map_metro.find_path(v1, v6) # от сретенского бульвара до китай-город 1
|
||||
>>> print(path[0]) # [Сретенский бульвар, Тургеневская, Китай-город 2, Китай-город 1]
|
||||
[Сретенский бульвар, Тургеневская, Китай-город 2, Китай-город 1]
|
||||
>>> print(sum([x.dist for x in path[1]])) # 7
|
||||
7
|
||||
|
||||
P.S. В программе нужно объявить только классы Vertex, Link, LinkedGraph, Station, LinkMetro. На экран ничего выводить не нужно.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field
|
||||
from functools import total_ordering
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
|
||||
def make_properties_prot(*names):
|
||||
def decorator(cls):
|
||||
def prop(private_name: str):
|
||||
def getter(self):
|
||||
return getattr(self, private_name)
|
||||
|
||||
def setter(self, value):
|
||||
return setattr(self, private_name, value)
|
||||
|
||||
return getter, setter
|
||||
|
||||
for name in names:
|
||||
setattr(cls, name, property(*prop(f"_{name}")))
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class AutoNamed:
|
||||
_num: int = 0
|
||||
name: str
|
||||
|
||||
@staticmethod
|
||||
def column_code(num: int) -> str:
|
||||
"""Имя как код колонки в Excel по порядковому номеру с 1"""
|
||||
|
||||
def gen(n: int):
|
||||
a = ord("A")
|
||||
sz = ord("Z") - a + 1
|
||||
while n:
|
||||
n, mod = divmod(n - 1, sz)
|
||||
yield chr(mod + a)
|
||||
|
||||
return "".join(gen(abs(int(num))))[::-1]
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
obj = super().__new__(cls)
|
||||
cls._num += 1
|
||||
obj.name = cls.column_code(cls._num)
|
||||
return obj
|
||||
|
||||
|
||||
@make_properties_prot("links")
|
||||
class Vertex(AutoNamed):
|
||||
def __init__(self, name: Optional[str] = None):
|
||||
if name:
|
||||
self.name = name
|
||||
self._links = []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}({self.name!r})"
|
||||
|
||||
def add_link(self, link):
|
||||
if link not in self._links:
|
||||
self._links.append(link)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.links)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.links[key]
|
||||
|
||||
|
||||
@make_properties_prot("v1", "v2", "dist")
|
||||
class Link:
|
||||
def __init__(self, v1: Vertex, v2: Vertex, dist: Union[int, float] = 1):
|
||||
self._v1, self._v2, self._dist = v1, v2, dist
|
||||
for x in self:
|
||||
x.add_link(self)
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __getitem__(self, key):
|
||||
return (self.v1, self.v2)[key]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}{(self.v1, self.v2, self.dist)!r}"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(frozenset((self.v1, self.v2, self.dist)))
|
||||
|
||||
def __eq__(self, other):
|
||||
return hash(self) == hash(other)
|
||||
|
||||
|
||||
@dataclass
|
||||
@total_ordering
|
||||
class LinksPath:
|
||||
links: List[Link] = field(default_factory=list)
|
||||
is_start: bool = field(init=False, default=False)
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
if self.is_start:
|
||||
return 0
|
||||
if self.links:
|
||||
return sum([x.dist for x in self.links])
|
||||
return float("inf")
|
||||
|
||||
def add_link(self, link: Link):
|
||||
if link not in self.links:
|
||||
self.links.append(link)
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self.links[:])
|
||||
|
||||
@property
|
||||
def vertex(self):
|
||||
return [*{v: v for lnk in self.links for v in lnk}.keys()]
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.dist == other.dist
|
||||
return self.dist == other
|
||||
|
||||
def __le__(self, other) -> bool:
|
||||
if isinstance(other, self.__class__):
|
||||
return self.dist < other.dist
|
||||
return self.dist < other
|
||||
|
||||
|
||||
@make_properties_prot("links", "vertex")
|
||||
class LinkedGraph:
|
||||
def __init__(self, vertex: Optional[Vertex] = None, links: Optional[Link] = None):
|
||||
self._vertex = vertex or []
|
||||
self._links = links or []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}{(self.vertex, self.links)!r}"
|
||||
|
||||
def add_vertex(self, v: Vertex):
|
||||
if v not in self._vertex:
|
||||
self._vertex.append(v)
|
||||
|
||||
def add_link(self, link: Link):
|
||||
if link not in self._links:
|
||||
self._links.append(link)
|
||||
for v in link:
|
||||
self.add_vertex(v)
|
||||
|
||||
def dijkstras(self, start: Vertex, stop: Optional[Vertex] = None):
|
||||
def walk(remaining, paths, current):
|
||||
while remaining:
|
||||
remaining.discard(current)
|
||||
yield current
|
||||
if current == stop:
|
||||
break
|
||||
if remaining:
|
||||
current = min(remaining, key=lambda x: paths[x])
|
||||
|
||||
paths = defaultdict(LinksPath)
|
||||
paths[start].is_start = True
|
||||
remaining = set(self.vertex)
|
||||
for current in walk(remaining, paths, start):
|
||||
for link in current:
|
||||
for v in filter(lambda v: v in remaining, link):
|
||||
new_path = paths[current].copy().add_link(link)
|
||||
paths[v] = min(paths[v], new_path)
|
||||
return paths
|
||||
|
||||
def find_path(
|
||||
self, start_v: Vertex, stop_v: Vertex
|
||||
) -> Tuple[List[Vertex], List[Link]]:
|
||||
path = self.dijkstras(start_v, stop_v)[stop_v]
|
||||
return path.vertex, path.links
|
||||
|
||||
|
||||
class Station(Vertex):
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class LinkMetro(Link):
|
||||
...
|
||||
|
||||
|
||||
print("-------------------------------------------")
|
||||
|
||||
vA, vB, vC, vD, vE, vF, vG = [Vertex() for _ in range(7)]
|
||||
map_graph = LinkedGraph()
|
||||
map_graph.add_link(Link(vA, vB))
|
||||
map_graph.add_link(Link(vB, vC))
|
||||
map_graph.add_link(Link(vA, vC))
|
||||
|
||||
map_graph.add_link(Link(vD, vE))
|
||||
map_graph.add_link(Link(vF, vG))
|
||||
|
||||
map_graph.add_link(Link(vB, vG))
|
||||
map_graph.add_link(Link(vC, vD))
|
||||
map_graph.add_link(Link(vE, vF))
|
||||
|
||||
print(len(map_graph._links)) # 8 связей
|
||||
print(len(map_graph._vertex)) # 7 вершин
|
||||
print(map_graph.find_path(vA, vG))
|
||||
# ([Vertex('A'), Vertex('B'), Vertex('G')], [Link(Vertex('A'), Vertex('B'), 1), Link(Vertex('B'), Vertex('G'), 1)])
|
||||
|
||||
print("-------------------------------------------")
|
||||
|
||||
map_metro = LinkedGraph()
|
||||
v1 = Station("Сретенский бульвар")
|
||||
v2 = Station("Тургеневская")
|
||||
v3 = Station("Чистые пруды")
|
||||
v4 = Station("Лубянка")
|
||||
v5 = Station("Кузнецкий мост")
|
||||
v6 = Station("Китай-город 1")
|
||||
v7 = Station("Китай-город 2")
|
||||
|
||||
map_metro.add_link(LinkMetro(v1, v2, 1))
|
||||
map_metro.add_link(LinkMetro(v2, v3, 1))
|
||||
map_metro.add_link(LinkMetro(v1, v3, 1))
|
||||
|
||||
map_metro.add_link(LinkMetro(v4, v5, 1))
|
||||
map_metro.add_link(LinkMetro(v6, v7, 1))
|
||||
|
||||
map_metro.add_link(LinkMetro(v2, v7, 5))
|
||||
map_metro.add_link(LinkMetro(v3, v4, 3))
|
||||
map_metro.add_link(LinkMetro(v5, v6, 3))
|
||||
|
||||
print(len(map_metro._links))
|
||||
print(len(map_metro._vertex))
|
||||
path = map_metro.find_path(v1, v6) # от сретенского бульвара до китай-город 1
|
||||
print(path[0]) # [Сретенский бульвар, Тургеневская, Китай-город 2, Китай-город 1]
|
||||
print(sum([x.dist for x in path[1]])) # 7
|
||||
|
||||
print("-------------------------------------------", flush=True)
|
||||
Vertex._num = 0 # naming reset
|
||||
|
||||
# exit(1)
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"ZDDXSAUz;VX>My}WJhvgaA+tg3U)CdJs?(Pa&%>QC@BhdG9W!5R%LQ@Wq2ql3U)IfJs?(Pa&%>"
|
||||
+ b"QC@BhdG$1`7R%LQ@Wq2ql3U)OhJs?(Pa&%>QC@BgGZDDXSE@5P3Uu<b^YbZ=<ZfhuZF)Sc<GAStv"
|
||||
+ b"ZDDXSE@5P3Uu<b^YbZ=<ZfhuZGAtl=Gbt$wZDDXSE@5P3Uu<b^YbZ=<ZfhuZGAtl=G$|<xZDDXSE"
|
||||
+ b"@5P3Uu<b^YbZ=<ZfhuZGb|u>G$|<xZDDXSE@5P3Uu<b^YbZ=<ZfhuZG%O%?H7O|y3So0|WpZ>NY-"
|
||||
+ b"MgJZDDXSE?;bEZfkQXAU!=GH7p<^(7n*L(6Z3A(SXps(7w>MAkl}=xY2>oyU@NM(Sgvi(T~u#(6!"
|
||||
+ b"LHAkeZP(Sgvv(74fo(7MpIAYW{0ZfkQO(7MpO(6G^g(SgvgAWUg)Yh`3da$#_2A_`%1b7gXLAZ%r"
|
||||
+ b"BC~aYIGA>_sWpZ?7cqt$~Js>qKAR^Gc(6!LA(6!Nk(7n*U(6u1ZhtRmufzZ3qz97)D(6!Nk(TLEv"
|
||||
+ b"(7hngvLMlc(7({Q(Sgvq(6u06c4cyOWq2Uay3o7Ou+f3hfzYrZOlfXwWn@QkVQ^?73JPsua564oW"
|
||||
+ b"Mp4#X>MyMOlfXwD0VU|Aa*e+DGFh8b7gXLAZ%rBC~aYIGA>_iX>MzCDIh&PAT=x?BGA0hwb6pmzR"
|
||||
+ b"<KFVPs@qY-w(5C@CP&w9vlLvCy#4ve3BDyCBhl(6Z5w(6`ZyAWUg)YbbUyEFg9<DJ&q-h0wmyg3*"
|
||||
+ b"s4(Sy*o(6u1YxX`@Nwa~TEg3*D|k08;3(6Z5w(6`ZyAWUg)YbbUxEFg9=DIy9AaA9<4AUz;$VQ?}"
|
||||
+ b"oW@&C@UvOb`Xef3uEFg9@DGGBSJs@*+Z75rKE@WwQbRcGLav*phX>K5JVRUF)F<o6L3So0|WpZ>N"
|
||||
+ b"b09rEATul=BGA3iwa~KAwb6jkz0k1Hk08;3(Sy*u(7e#F(SXps(6G^uAkehXyU@7Mz0j~A(7e#F("
|
||||
+ b"SXs2(SXr|(Sp#hEFjRb(7w>O(7e#T(6-RM(7r4n(7n*L(7MpR(SXr_(6!LI(Sp#u(7qtifY7kevC"
|
||||
+ b"zKJg3z$gwb6ng(7w>I(TdQu(7MrrEzyC{ve3TJxzT~qg3z+iz93|2b95pK3So0|WpZ>NX>)URVq<"
|
||||
+ b"J!b8{$DbYXO9Z*D9gR%LQ@Wq2tdVQyp~X>)URVq<J!b8{$6X>MyxWpr|HEFes2ZfhwlAR^GZ(7Vv"
|
||||
+ b"E(Sgx{AX9W<bZKvHAkehXzR<hSw$QcEy&%xN(6G^g(7VvJ(6rFL(6Z35(Sp&8(SgyAAke<if*{bk"
|
||||
+ b"(7VvE(Sgx{(6As@WpZ?7cq|~$uprR7(7VvE(Sgx{AWUg)YfWWza&I8ezR`jp(7MpO(6G^g(SgvgA"
|
||||
+ b"WUg)Ya$8?ZDDXSAUz;VX>My}WJhvgaA+tg3U)CdJs?wbVRUJ4ZYUx#A}I=XG9W!5Q*>c;X>V>QA~"
|
||||
+ b"GT=3U)IfJs?wbVRUJ4ZYUx%A}I=XG$1`7Q*>c;X>V>QA~Yf?3U)OhJs?wbVRUJ4ZYUx(A}I<AZDD"
|
||||
+ b"XSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb}=j<b}}p=F)1kuZDDXSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb}}p"
|
||||
+ b"=b~7v>GAStvZDDXSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb}}p=b~G#?Hz_F!ZDDXSE@5P3Uu<b^YbZ=<"
|
||||
+ b"Zfi|tbaHPfb~7v>b~G#?Gbt$wZDDXSE@5P3Uu<b^YbZ=<Zfi|tbaHPfb~G#?b~P*@F)1ku3So0|W"
|
||||
+ b"pZ>NY-MgJZDDXSE?;bEZfkQXAU!=GH7p<^(7n*L(6Z3A(SXps(7w>MAkl}=xY2>oyU@NM(Sgvi(T"
|
||||
+ b"~u#(6!LHAkeZP(Sgvv(74fo(7MpIAYW{0ZfkQO(7MpO(6G^g(SgvgAWUg)Yh`3da$#_2A_`%1b7g"
|
||||
+ b"XLAZ%rBC~aYIGA>_sWpZ?7cqt$~Js>qKAR^Gc(6!LA(6!Nk(7n*U(6u1ZhtRmufzZ3qz97)D(6!N"
|
||||
+ b"k(TLEv(7hngvLMlc(7({Q(Sgvq(6u06c4cyOWq2Uay3o7Ou+f3hfzYrZOlfXwWn@QkVQ^?73JP#x"
|
||||
+ b"bZ8(wAZ=lAGA?FmZe(9@VRUFHb}=j<b~Pyq3So0|WpZ>Nb98bjaA9<4TQFTIAU!=GCtEQrATlf<G"
|
||||
+ b"b|u9EFd*qCoCXvVRUF)FkK3BAUz;+b!{kHcrIjVb95kPZ*m}bAZczOaA9<4TQOZ-DGFh8b7gXLAa"
|
||||
+ b"fu+Js>wMAR^Gc(6!LA(6!Nk(7n*G(T^a}fzgA|ywJSRu+f0fz0k1Hk08*r(7VvM(7n*GAke(fu+f"
|
||||
+ b"0gh|z%2gVBP}uprR1(7Vx(Akezdu+f0gg3*g0(7e#K(Sp%{(7qx"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
670
mod_oop/5.6_01_sea_battle.py
Normal file
670
mod_oop/5.6_01_sea_battle.py
Normal file
@@ -0,0 +1,670 @@
|
||||
"""
|
||||
https://stepik.org/lesson/727588/step/1?unit=728924
|
||||
|
||||
Посвящение в ООП
|
||||
|
||||
Вы прошли серию испытаний и совершили множество подвигов, чтобы лицом к лицу столкнуться с настоящим вызовом, достойным лишь избранных!
|
||||
Для подтверждения своих знаний и навыков вам предлагается пройти этап посвящения в объектно-ориентированное программирование.
|
||||
И вот задание, которое выпало на вашу долю.
|
||||
|
||||
Руководство компании целыми днями не знает куда себя деть. Поэтому они решили дать задание своим программистам написать программу игры "Морской бой".
|
||||
Но эта игра будет немного отличаться от классической. Для тех, кто не знаком с этой древней, как мир, игрой, напомню ее краткое описание.
|
||||
|
||||
Каждый игрок у себя на бумаге рисует игровое поле 10 х 10 клеток и расставляет на нем десять кораблей: однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1.
|
||||
Корабли расставляются случайным образом, но так, чтобы не выходили за пределы игрового поля и не соприкасались друг с другом (в том числе и по диагонали).
|
||||
|
||||
Затем, игроки по очереди называют клетки, куда производят выстрелы.
|
||||
И отмечают эти выстрелы на другом таком же поле в 10 х 10 клеток, которое представляет поле соперника.
|
||||
Соперник при этом должен честно отвечать: "промах", если ни один корабль не был задет и "попал", если произошло попадание.
|
||||
Выигрывает тот игрок, который первым поразит все корабли соперника.
|
||||
|
||||
Но это была игра из глубокого прошлого.
|
||||
Теперь же, в компьютерную эру, корабли на игровом поле могут перемещаться в направлении своей ориентации на одну клетку после каждого хода соперника, если в них не было ни одного попадания.
|
||||
|
||||
Итак, лично вам поручается сделать важный фрагмент этой игры - расстановку и управление кораблями в этой игре. А само задание звучит так.
|
||||
|
||||
Техническое задание
|
||||
|
||||
В программе необходимо объявить два класса:
|
||||
Ship - для представления кораблей;
|
||||
GamePole - для описания игрового поля.
|
||||
|
||||
Класс Ship
|
||||
Класс Ship должен описывать корабли набором следующих параметров:
|
||||
x, y - координаты начала расположения корабля (целые числа);
|
||||
length - длина корабля (число палуб: целое значение: 1, 2, 3 или 4);
|
||||
tp - ориентация корабля (1 - горизонтальная; 2 - вертикальная).
|
||||
|
||||
Объекты класса Ship должны создаваться командами:
|
||||
>>> ship = Ship(length := 1)
|
||||
>>> ship = Ship(length := 1, tp := 2)
|
||||
>>> ship = Ship(length := 1, tp := 2, x := 1, y := 1)
|
||||
|
||||
По умолчанию (если не указывается) параметр tp = 1, а координаты x, y равны None.
|
||||
|
||||
В каждом объекте класса Ship должны формироваться следующие локальные атрибуты:
|
||||
_x, _y - координаты корабля (целые значения в диапазоне [0; size), где size - размер игрового поля);
|
||||
_length - длина корабля (число палуб);
|
||||
_tp - ориентация корабля;
|
||||
_is_move - возможно ли перемещение корабля (изначально равно True);
|
||||
_cells - изначально список длиной length, состоящий из единиц (например, при length=3, _cells = [1, 1, 1]).
|
||||
|
||||
# доп проверки кораблей ---
|
||||
>>> [*map(len, (Ship(1),Ship(2),Ship(3),Ship(4)))] == [*range(1, 5)]
|
||||
True
|
||||
>>> Ship(5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 5 is not a valid ShipSize
|
||||
>>> Ship(1, 1)._tp, Ship(1, 2)._tp
|
||||
(1, 2)
|
||||
>>> Ship(1, 3)._tp
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 3 is not a valid ShipOrientation
|
||||
>>> s = Ship(1)
|
||||
>>> {s[0] == 1, len(s) == 1}
|
||||
{True}
|
||||
>>> s[0] = 2
|
||||
>>> s[0]
|
||||
2
|
||||
>>> s[0] = 4
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 4 is not a valid DeckStatus
|
||||
>>> s2 = Ship(2)
|
||||
>>> s2._cells = [1, 2]
|
||||
>>> s2._cells
|
||||
[1, 2]
|
||||
>>> s._cells = [1, 2]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: _cells_ must be 1 elements long
|
||||
>>> s[0] = 1
|
||||
>>> s._is_move
|
||||
True
|
||||
>>> s[0] = 2
|
||||
>>> s._is_move
|
||||
False
|
||||
>>> s = Ship(1)
|
||||
>>> s.get_start_coords()
|
||||
(0, 0)
|
||||
>>> s.set_start_coords(2, 3)
|
||||
>>> s.get_start_coords()
|
||||
(2, 3)
|
||||
>>> s.move(1)
|
||||
>>> s.get_start_coords()
|
||||
(3, 3)
|
||||
>>> s.move(-1)
|
||||
>>> s.get_start_coords()
|
||||
(2, 3)
|
||||
>>> s[0] = 2
|
||||
>>> s.move(2)
|
||||
>>> s.get_start_coords()
|
||||
(2, 3)
|
||||
>>> Ship(1).is_collide(Ship(2, 1, 2, 3))
|
||||
False
|
||||
>>> Ship(2, 1, 2, 3).is_collide(Ship(3, 2, 3, 2))
|
||||
True
|
||||
>>> Ship(2, 1, 0, 0).is_collide(Ship(1, 2, 2, 2))
|
||||
False
|
||||
>>> Ship(1).is_out_pole(10)
|
||||
False
|
||||
>>> Ship(3, 1, 8, 1).is_out_pole(10)
|
||||
True
|
||||
>>> Ship(3, 2, 1, 8).is_out_pole(10)
|
||||
True
|
||||
>>> s = Ship(4, 2)
|
||||
>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)])
|
||||
True
|
||||
>>> s.get_start_coords()
|
||||
(0, 1)
|
||||
>>> s.try_move(1, 6, [Ship(1, 1, 1, 6)])
|
||||
False
|
||||
>>> s.get_start_coords()
|
||||
(0, 1)
|
||||
>>> s.try_move(1, 6, [Ship(1, 1, 1, 2)])
|
||||
False
|
||||
>>> s.get_start_coords()
|
||||
(0, 1)
|
||||
>>> s[0] = 2
|
||||
>>> s.try_move(1, 10, [Ship(1, 1, 3, 1)])
|
||||
False
|
||||
>>> s.get_start_coords()
|
||||
(0, 1)
|
||||
>>> s[0] = 1; s.computer_move(10, [Ship(1, 1, 3, 1)])
|
||||
>>> _, y = s.get_start_coords(); 0 <= y <= 2
|
||||
True
|
||||
|
||||
>>> s = Ship(4, 2)
|
||||
>>> s.hit(1, 1)
|
||||
False
|
||||
>>> s._is_move
|
||||
True
|
||||
>>> s.hit(0, 4)
|
||||
False
|
||||
>>> s._is_move
|
||||
True
|
||||
>>> s.hit(0, 3)
|
||||
True
|
||||
>>> s._is_move
|
||||
False
|
||||
>>> s.is_alive
|
||||
True
|
||||
>>> {s.hit(0, x) for x in range(3)} ; s.is_alive
|
||||
{True}
|
||||
False
|
||||
|
||||
>>> sz = 5; pole = [[0 for _ in range(sz)] for _ in range(sz)]
|
||||
>>> s1, s2 = Ship(2, 1), Ship(2, 2, 3, 1)
|
||||
>>> s1.place_to_pole(pole)
|
||||
True
|
||||
>>> pole
|
||||
[[1, 1, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
|
||||
>>> s2.place_to_pole(pole)
|
||||
True
|
||||
>>> pole
|
||||
[[1, 1, 0, 0, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
|
||||
|
||||
|
||||
# ---
|
||||
|
||||
|
||||
Список _cells будет сигнализировать о попадании соперником в какую-либо палубу корабля. Если стоит 1, то попадания не было, а если стоит значение 2, то произошло попадание в соответствующую палубу.
|
||||
|
||||
При попадании в корабль (хотя бы одну его палубу), флаг _is_move устанавливается в False и перемещение корабля по игровому полю прекращается.
|
||||
|
||||
В самом классе Ship должны быть реализованы следующие методы (конечно, возможны и другие, дополнительные):
|
||||
set_start_coords(x, y) - установка начальных координат (запись значений в локальные атрибуты _x, _y);
|
||||
get_start_coords() - получение начальных координат корабля в виде кортежа x, y;
|
||||
move(go) - перемещение корабля в направлении его ориентации на go клеток (go = 1 - движение в одну сторону на клетку; go = -1 - движение в другую сторону на одну клетку); движение возможно только если флаг _is_move = True;
|
||||
is_collide(ship) - проверка на столкновение с другим кораблем ship (столкновением считается, если другой корабль или пересекается с текущим или просто соприкасается, в том числе и по диагонали); метод возвращает True, если столкновение есть и False - в противном случае;
|
||||
is_out_pole(size) - проверка на выход корабля за пределы игрового поля (size - размер игрового поля, обычно, size = 10); возвращается булево значение True, если корабль вышел из игрового поля и False - в противном случае;
|
||||
|
||||
С помощью магических методов __getitem__() и __setitem__() обеспечить доступ к коллекции _cells следующим образом:
|
||||
value = ship[indx] # считывание значения из _cells по индексу indx (индекс отсчитывается от 0)
|
||||
ship[indx] = value # запись нового значения в коллекцию _cells
|
||||
Класс GamePole
|
||||
Следующий класс GamePole должен обеспечивать работу с игровым полем. Объекты этого класса создаются командой:
|
||||
|
||||
>>> pole = GamePole(10)
|
||||
>>> pole = GamePole()
|
||||
|
||||
где size - размеры игрового поля (обычно, size = 10).
|
||||
|
||||
В каждом объекте этого класса должны формироваться локальные атрибуты:
|
||||
_size - размер игрового поля (целое положительное число);
|
||||
_ships - список из кораблей (объектов класса Ship); изначально пустой список.
|
||||
|
||||
В самом классе GamePole должны быть реализованы следующие методы (возможны и другие, дополнительные методы):
|
||||
init() - начальная инициализация игрового поля; здесь создается список из кораблей (объектов класса Ship): однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1 (ориентация этих кораблей должна быть случайной).
|
||||
|
||||
Корабли формируются в коллекции _ships следующим образом: однопалубных - 4; двухпалубных - 3; трехпалубных - 2; четырехпалубный - 1. Ориентация этих кораблей должна быть случайной. Для этого можно воспользоваться функцией randint следующим образом:
|
||||
[Ship(4, tp=randint(1, 2)), Ship(3, tp=randint(1, 2)), Ship(3, tp=randint(1, 2)), ...]
|
||||
Начальные координаты x, y не расставленных кораблей равны None.
|
||||
|
||||
После этого, выполняется их расстановка на игровом поле со случайными координатами так, чтобы корабли не пересекались между собой.
|
||||
|
||||
get_ships() - возвращает коллекцию _ships;
|
||||
move_ships() - перемещает каждый корабль из коллекции _ships на одну клетку (случайным образом вперед или назад) в направлении ориентации корабля; если перемещение в выбранную сторону невозможно (другой корабль или пределы игрового поля), то попытаться переместиться в противоположную сторону, иначе (если перемещения невозможны), оставаться на месте;
|
||||
show() - отображение игрового поля в консоли (корабли должны отображаться значениями из коллекции _cells каждого корабля, вода - значением 0);
|
||||
|
||||
get_pole() - получение текущего игрового поля в виде двумерного (вложенного) кортежа размерами size x size элементов.
|
||||
|
||||
>>> pole = GamePole(5)
|
||||
>>> pole.get_pole()
|
||||
((0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0), (0, 0, 0, 0, 0))
|
||||
>>> pole.show()
|
||||
0 0 0 0 0
|
||||
0 0 0 0 0
|
||||
0 0 0 0 0
|
||||
0 0 0 0 0
|
||||
0 0 0 0 0
|
||||
>>> pole = GamePole(10)
|
||||
>>> pole.init()
|
||||
>>> sum(map(sum, pole.get_pole()))
|
||||
20
|
||||
|
||||
>>> [*map(lambda x: x.__class__.__name__, pole.get_ships())]
|
||||
['Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship', 'Ship']
|
||||
>>> pole.move_ships()
|
||||
|
||||
|
||||
|
||||
Пример отображения игрового поля:
|
||||
0 0 1 0 1 1 1 0 0 0
|
||||
1 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 1 0 0 1
|
||||
0 0 0 0 1 0 1 0 0 1
|
||||
0 0 0 0 0 0 1 0 0 0
|
||||
1 1 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 1 0 0 0
|
||||
0 1 1 1 1 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 1 1 0
|
||||
|
||||
Пример использования классов (эти строчки в программе не писать):
|
||||
|
||||
SIZE_GAME_POLE = 10
|
||||
|
||||
pole = GamePole(SIZE_GAME_POLE)
|
||||
pole.init()
|
||||
pole.show()
|
||||
|
||||
pole.move_ships()
|
||||
print()
|
||||
pole.show()
|
||||
|
||||
В программе требуется только объявить классы Ship и GamePole с соответствующим функционалом. На экран выводить ничего не нужно.
|
||||
|
||||
P.S. Для самых преданных поклонников программирования и ООП. Завершите эту программу, добавив еще один класс SeaBattle для управления игровым процессом в целом.
|
||||
Игра должна осуществляться между человеком и компьютером. Выстрелы со стороны компьютера можно реализовать случайным образом в свободные клетки.
|
||||
Сыграйте в эту игру и выиграйте у компьютера.
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
from enum import Enum
|
||||
from functools import total_ordering
|
||||
from collections import namedtuple
|
||||
import random
|
||||
|
||||
|
||||
@total_ordering
|
||||
class EnumOrdering(Enum):
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.value == other.value
|
||||
return self.value == other
|
||||
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.value < other.value
|
||||
return self.value < other
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return tuple(item.value for item in cls)
|
||||
|
||||
|
||||
class IntEnumField:
|
||||
def __init__(self, enum_cls: Enum):
|
||||
self.enum_cls = enum_cls
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
self.name_orig = name
|
||||
self.name = name + "_"
|
||||
|
||||
def __str__(self):
|
||||
return self.name_orig
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return getattr(instance, self.name).value
|
||||
|
||||
def __set__(self, instance, value):
|
||||
setattr(instance, self.name, self.enum_cls(value))
|
||||
|
||||
|
||||
class ListEnumField:
|
||||
def __init__(self, enum_cls: Enum, length_key):
|
||||
self.enum_cls = enum_cls
|
||||
self.length_key = length_key
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
self.name = name + "_"
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return [x.value for x in getattr(instance, self.name)]
|
||||
|
||||
def get_item(self, instance, index):
|
||||
return getattr(instance, self.name)[index]
|
||||
|
||||
def set_item(self, instance, index, value):
|
||||
getattr(instance, self.name)[index] = self.enum_cls(value)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
length = getattr(instance, str(self.length_key))
|
||||
new_value = list(map(self.enum_cls, value))
|
||||
if len(new_value) != length:
|
||||
raise ValueError(f"{self.name} must be {length} elements long")
|
||||
setattr(instance, self.name, new_value)
|
||||
|
||||
|
||||
class NonNegativeIntField:
|
||||
def __set_name__(self, owner, name):
|
||||
self.name = name + "_"
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return getattr(instance, self.name)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if not isinstance(value, int):
|
||||
raise TypeError(f"{self.name} must be an integer")
|
||||
if value < 0:
|
||||
raise ValueError(f"{self.name} must be non-negative")
|
||||
setattr(instance, self.name, value)
|
||||
|
||||
|
||||
class ShipSize(EnumOrdering):
|
||||
ONE_DECK = 1
|
||||
TWO_DECKS = 2
|
||||
THREE_DECKS = 3
|
||||
FOUR_DECKS = 4
|
||||
|
||||
|
||||
class ShipOrientation(EnumOrdering):
|
||||
HORIZONTAL = 1
|
||||
VERTICAL = 2
|
||||
|
||||
|
||||
class DeckStatus(EnumOrdering):
|
||||
OK = 1
|
||||
DAMAGED = 2
|
||||
|
||||
|
||||
class Ship:
|
||||
_length: int = IntEnumField(ShipSize)
|
||||
_tp: int = IntEnumField(ShipOrientation)
|
||||
_cells: List[int] = ListEnumField(DeckStatus, _length)
|
||||
_x: int = NonNegativeIntField()
|
||||
_y: int = NonNegativeIntField()
|
||||
|
||||
Rect = namedtuple("Rect", ["left", "top", "right", "bottom"])
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
length: int,
|
||||
tp: int = 1,
|
||||
x: int = 0,
|
||||
y: int = 0,
|
||||
cells: Optional[List[DeckStatus]] = None,
|
||||
):
|
||||
self._length, self._tp, self._x, self._y = length, tp, x, y
|
||||
self._cells = cells or [DeckStatus.OK] * self._length
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}{(self._length, self._tp, self._x, self._y, self._cells)!r}"
|
||||
|
||||
def __len__(self):
|
||||
return self._length
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__class__._cells.get_item(self, key).value
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return self.__class__._cells.set_item(self, key, value)
|
||||
|
||||
@property
|
||||
def _is_move(self) -> bool:
|
||||
return all(cell == DeckStatus.OK for cell in self)
|
||||
|
||||
@property
|
||||
def is_alive(self) -> bool:
|
||||
return any(cell == DeckStatus.OK for cell in self)
|
||||
|
||||
def set_start_coords(self, x: int, y: int):
|
||||
self._x, self._y = x, y
|
||||
|
||||
def get_start_coords(self):
|
||||
return self._x, self._y
|
||||
|
||||
@property
|
||||
def is_horizontal(self):
|
||||
return self._tp == ShipOrientation.HORIZONTAL
|
||||
|
||||
def move(self, go: int):
|
||||
if self._is_move:
|
||||
if self.is_horizontal:
|
||||
self._x += go
|
||||
else:
|
||||
self._y += go
|
||||
|
||||
@property
|
||||
def rect(self):
|
||||
x, y = self.get_start_coords()
|
||||
if self.is_horizontal:
|
||||
return self.Rect(x, y, x + self._length, y + 1)
|
||||
else:
|
||||
return self.Rect(x, y, x + 1, y + self._length)
|
||||
|
||||
def is_collide(self, other: "Ship") -> bool:
|
||||
return (
|
||||
self.rect.left <= other.rect.right
|
||||
and self.rect.right >= other.rect.left
|
||||
and self.rect.top <= other.rect.bottom
|
||||
and self.rect.bottom >= other.rect.top
|
||||
or any(
|
||||
all(
|
||||
not (getattr(a.rect, x) - getattr(b.rect, y))
|
||||
for x, y in (("left", "right"), ("top", "bottom"))
|
||||
)
|
||||
for a, b in ((self, other), (other, self))
|
||||
)
|
||||
)
|
||||
|
||||
def is_out_pole(self, size: int) -> bool:
|
||||
return (
|
||||
self.rect.left < 0
|
||||
or self.rect.top < 0
|
||||
or self.rect.right > size
|
||||
or self.rect.bottom > size
|
||||
)
|
||||
|
||||
def try_move(self, go: int, pole_size: int, other_ships: List["Ship"]) -> bool:
|
||||
if not self._is_move:
|
||||
return False
|
||||
|
||||
backup = self.get_start_coords()
|
||||
try:
|
||||
self.move(go)
|
||||
except ValueError:
|
||||
self.set_start_coords(*backup)
|
||||
return False
|
||||
|
||||
if not (
|
||||
self.is_out_pole(pole_size)
|
||||
or any(self.is_collide(ship) for ship in other_ships)
|
||||
):
|
||||
return True
|
||||
self.set_start_coords(*backup)
|
||||
return False
|
||||
|
||||
def computer_move(self, pole_size: int, other_ships: List["Ship"]):
|
||||
go = random.randint(-1, 1)
|
||||
if not go:
|
||||
return # решили не двигать корабль
|
||||
moved = self.try_move(go, pole_size, other_ships)
|
||||
if not moved:
|
||||
go = -go
|
||||
self.try_move(go, pole_size, other_ships)
|
||||
|
||||
def hit(self, x: int, y: int) -> bool:
|
||||
left, top, right, bottom = self.rect
|
||||
if left <= x < right and top <= y < bottom:
|
||||
a, b = ((y, top), (x, left))[self.is_horizontal]
|
||||
self[a - b] = DeckStatus.DAMAGED
|
||||
return True
|
||||
return False
|
||||
|
||||
def place_to_pole(self, pole: List[List[int]]) -> bool:
|
||||
if self.is_out_pole(len(pole)):
|
||||
return False
|
||||
rect = self.rect
|
||||
for i in range(rect.top, rect.bottom):
|
||||
for j in range(rect.left, rect.right):
|
||||
a, b = ((i, rect.top), (j, rect.left))[self.is_horizontal]
|
||||
pole[i][j] = self[a - b]
|
||||
return True
|
||||
|
||||
|
||||
class GamePoleError(Exception):
|
||||
...
|
||||
|
||||
|
||||
class ShipPlacementError(GamePoleError):
|
||||
...
|
||||
|
||||
|
||||
class GamePole:
|
||||
def __init__(self, size: int = 10, ships: Optional[List[Ship]] = None):
|
||||
self._size = size
|
||||
self._ships = ships or []
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}{(self._size, self._ships)!r}"
|
||||
|
||||
def __len__(self):
|
||||
return len(self._ships)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._ships[key]
|
||||
|
||||
def get_pole_list(self) -> List[List[int]]:
|
||||
pole = [[0] * self._size for _ in range(self._size)]
|
||||
for ship in self._ships:
|
||||
ship.place_to_pole(pole)
|
||||
return pole
|
||||
|
||||
def get_pole(self) -> Tuple[Tuple[int, ...], ...]:
|
||||
return tuple(tuple(row) for row in self.get_pole_list())
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(" ".join(map(str, row)) for row in self.get_pole_list())
|
||||
|
||||
def show(self):
|
||||
print(self)
|
||||
|
||||
@staticmethod
|
||||
def _check_free_box(pole_s, i, j) -> bool:
|
||||
"""проверка, что в пределах 9 клеток вокруг координат (включая их самих) нет значений кроме 0"""
|
||||
offsets = [(a, b) for a in range(-1, 2) for b in range(-1, 2)]
|
||||
n, m = len(pole_s), len(pole_s[0])
|
||||
for a, b in map(lambda a, b: (a + i, b + j), *zip(*offsets)):
|
||||
if 0 <= a < n and 0 <= b < m:
|
||||
if pole_s[a][b] != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _find_free_places_for_ship(
|
||||
pole, ship_size: int, tp: int
|
||||
) -> List[Tuple[int, int]]:
|
||||
"""возвращает список возможных координат для нового корабля
|
||||
координаты в виде кортежей в формате (x, y) | (индекс столбца, индекс строки)
|
||||
"""
|
||||
free_places = []
|
||||
is_horizontal = tp == ShipOrientation.HORIZONTAL
|
||||
pole_s = pole
|
||||
if not is_horizontal:
|
||||
pole_s = list(zip(*pole))
|
||||
for i, row in enumerate(pole_s):
|
||||
for j in range(len(row) - ship_size):
|
||||
if all(
|
||||
GamePole._check_free_box(pole_s, i, j + k) for k in range(ship_size)
|
||||
):
|
||||
pos = (j, i) if is_horizontal else (i, j)
|
||||
free_places.append(pos)
|
||||
return free_places
|
||||
|
||||
def _validete_new_ship(self, ship):
|
||||
if ship.is_out_pole(self._size):
|
||||
raise ShipPlacementError(f"Новый корабль выходит за пределы поля: {ship!r}")
|
||||
for other in self._ships:
|
||||
if ship.is_collide(other):
|
||||
raise ShipPlacementError(
|
||||
f"Новый корабль {ship!r} пересекается с {other!r}"
|
||||
)
|
||||
|
||||
def place_new_ship(self, pole, ship_size: int, tp: int):
|
||||
free_places = GamePole._find_free_places_for_ship(pole, ship_size, tp)
|
||||
if not free_places:
|
||||
# должно быть так
|
||||
# raise ShipPlacementError("Нет свободных мест для размещения корабля")
|
||||
# но в тестах есть размер поля 8x8, поэтому просто игнорим
|
||||
return
|
||||
pos = random.choice(free_places)
|
||||
ship = Ship(ship_size, tp, *pos)
|
||||
self._validete_new_ship(ship)
|
||||
self._ships.append(ship)
|
||||
ship.place_to_pole(pole)
|
||||
|
||||
def init(self):
|
||||
self._ships.clear()
|
||||
pole = self.get_pole_list()
|
||||
sizes = ShipSize.values()[::-1]
|
||||
for ship_size, ship_count in zip(sizes, range(1, len(sizes) + 1)):
|
||||
for _ in range(ship_count):
|
||||
tp = random.choice(ShipOrientation.values())
|
||||
self.place_new_ship(pole, ship_size, tp)
|
||||
|
||||
def get_ships(self) -> List[Ship]:
|
||||
return self._ships
|
||||
|
||||
def move_ships(self):
|
||||
for ship in self._ships:
|
||||
ship.computer_move(self._size, self._ships)
|
||||
|
||||
|
||||
pole = GamePole(10)
|
||||
|
||||
pole.init()
|
||||
print(sum(map(sum, pole.get_pole())))
|
||||
print(len(pole))
|
||||
pole.show()
|
||||
|
||||
|
||||
def tests():
|
||||
code = (
|
||||
b"b7*OBAUz;cXlZaLGARmkXlZaDJs?wPX>ceqEFdu{3Ug>_a3DP(Q)p>$C^IY|GAtl4EFdr`3JPI"
|
||||
+ b"!b7gXLAaiJGa4uhLWo~D5Xdpd3ATuCgZe$>HXlZaRUvzLFJv|^YAYpD~AaiJGa4uhXAU!=GFd$)W"
|
||||
+ b"WFT{BX>cxIc_2MKATTT-BGA3iwa~KAwb6jkz0r%%wII;9(7n*G(TC8r(7n*O(T^a|u+f6ifY7+mv"
|
||||
+ b"C)Ikg3!LuvLMjD(6P~q(6!LI(Sp#hAkezdyU?)Ffzg4`upm=tX>cM6VRLh3a&#bbXlZaRUt?u#Y;"
|
||||
+ b"zzzJs?{#EFdu~ATeDmAR^Gc(6!LA(6!Nk(7n-%(77PdfzZFuxY2>ozR<cLUt?u#Y;z(CVRLh3a&#"
|
||||
+ b"bbXlZaRUukn+ZEtpEEFdD#z0kGLve32BfY80rzR<NG(6`XN(6G^m(6!LL(74dGAkeVUg3*A`xX`i"
|
||||
+ b"DgVBP}upnP)b6;(5c4Z<83Ug>_a4vIYbYF9HVRCd|V{dPAWOFDnEFdx|3So0|WpZ>Nb7*OBE?;;c"
|
||||
+ b"Jv|^XAYpD~AaiJGa4uhYAU!=GGAtk>(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyCBfK("
|
||||
+ b"6!Nm(7w>LAaiAOUvqR}a&%u~Z*OvBb0{ey3So0|WpZ>Nb7*OBE@x$QUvqR}a&%u~Z*OvBb0{ewJv"
|
||||
+ b"|^OF)Sc5DJ&o&(7n*L(6Z3A(SXps(7qthzR`lwfY7kevCzKJg3z$gyCBfK(6!Nm(7w>LAZKNCUvq"
|
||||
+ b"R}a&%u~Z*OvBb0{ey3JP;*X>cxWZ+2xUF)0djF(5r4Q)p>$C^Re}F)Sc3EFdr`3Ue|bJs?wPX>ce"
|
||||
+ b"rEFdy0ATTT-FewUiGax-6Q)p>$C^IY|GAtl4EFdx|3JPI!b7gXLAagM;X>(s=Z)|L7WMwFGGAS$|"
|
||||
+ b"BGA3iwa~KAwb6jkz0kfO(SXpf(6P|I(Sp#h(6!NmAke(fwb6pmzR<KFX>(s=Z)|L7WMwERAkehXy"
|
||||
+ b"U~vz(7MpR(SXpf(6P|F(6!LHAX8{*a40k^ATcZ;Ff1T2DIn0eAX8{*a40h@ATlf<Ff1T2DIyACb8"
|
||||
+ b"}^KbRcsvE@^XLV{dG1X=G(6b2BL*Jv|^sVQh0{EFdD#z0kGLve32BfY80rz97+n(6G?4(7w@v(6G"
|
||||
+ b"?8(Sjh*ywJ7Lg3!Luv><77Ut@1<Y-wa=C@CP&w9vcJk08*x(7w@t(6G?4(7VvJ(77N}XlZaLG%O%"
|
||||
+ b"7EFdr}ATTK)(6}H|XlZaLGb|u7EFdr}ATlW;3JP;FAUz;cXlZaLGb|u7EFdu~ATcQlVRLh3a&#bb"
|
||||
+ b"F)nFyUt@1<Y-wa=D04C?EFdD#z0kGLve32BfY80rz97+n(6G?4(7w@v(6G?8(Sjh*ywJ7Lg3!Luv"
|
||||
+ b"><77Ut@1<Y-wa=C@CP&w9vcJk08*x(7w@t(6G?4(7VvJ(77N}XlZaLG%O%7EFdr}ATTK)(6}H|Xl"
|
||||
+ b"ZaLGb|u7EFdu~ATcQ-3JP;FAUz;cXlZaLGb|u6EFd^6ATcQlVRLh3a&#bbGA?OzUvG7EUvO`1Whg"
|
||||
+ b"N)DJ&o&(7n*L(6Z3A(SXps(7qtifY7kevCzKJg3z$gwb6ng(7e#K(Sp#v(6k_Fb6;<DbYF09Y-K1"
|
||||
+ b"ZAkehXyU~vz(7MpR(SXpf(6P|F(T^ZgXlZaLGb|u6EFd^6ATcQ-3JP;FAUz;cXlZaLGb|u7EFdu~"
|
||||
+ b"AT=opVRLh3a&#bbGA?OzUvG7EUvO`1WhgN)DIh&PAVy(qb7d?bBGA3iwa~KAwb6jkz0kfO(SXpf("
|
||||
+ b"6P|I(Sp#h(6!NmAke(fwb6pmzR<KFX>(t1b#z~FZ){~KF)%40(6rFI(T^a|y3oGSfY7kevCzBGk0"
|
||||
+ b"4WMX>cerEFdy0ATcZ;H7Ozr3Ue}BFkK)$ATkPJb8}^KbRcswTQFT9Jv|^YEFdD#z0kGLve32BfY8"
|
||||
+ b"0rz97+n(6G?4(7w@v(6G?8(Sjh*zR<DJfY7kfiO{vsz0kPOwIFk7X>eO<Ze(~}A_@v{AUz;QVQpn"
|
||||
+ b"lZ){~KF)%3#a4u<XX>=$l3TAI|AZ~6TX>K5LVQyz-C^acM3LqdLAZBlJAafvTZXj?jUvp?_aC15e"
|
||||
+ b"ARr(hARr(hVRLh3a&#bbE@^XLZ*_EEaBpm7C^0Z8AU!=GMqzAoWh@{f(7MpR(SXpf(6P|F(6}Jbv"
|
||||
+ b"eApth0wmxw9${zf*{bh(6AuTztMouwa~QCwa~lKiy+Xr(6iBi(7w>J(7w>K(7qthztFzWyU~v#3J"
|
||||
+ b"M?~ARr(hARuOMav*bPX>cHEZXj?jXJvF>b7*OBb0{e~3LqdLARr(hARr(hAZcbGb08r-AaiJGa5@"
|
||||
+ b"SgARr(hARr(hARr(hARr)Nb8}^KbRcssX>(s=Z)|L7WMwFGXlZaMAU!=GMqzAoWh@{f(7MpR(SXp"
|
||||
+ b"f(6P|F(6}Jbz0j~A(74dE(SXpt(6Z3J(7YhfztFzWyU?{D(Sgvu(7(}u(74dL(6G^g(6G^t(Sp%|"
|
||||
+ b"(T^euARr(ha4v0cc4c34XlZbBC@BgcARr(LXK)}rAaE{cWprO~Z){~KDGFh8b7gXLAar?fWhiHGD"
|
||||
+ b"Ih&PAar$bY-J!}Ze$>Id2nSYXK-6ET`3?vJs@;-aBO8PAR^Gb(6!Nm(7w>LAZKNCUvO`1WgyVB(7"
|
||||
+ b"w>S(6-RE(7hngve3TJx6rcDfY7kfiO{gog3*j1(6rF9(Sy*u(6!Nk(7n-%(77Pcy3oGSfYE}`wa~"
|
||||
+ b"UA3So0|WpZ>NY-MgJXK*PXJv|^XFd$)WWFTy1ZYXDPTQFTIAU!=GF)%D3BGA3iwa~KAwb6jkz0r%"
|
||||
+ b"%wII=e(6G?A(7e#K(SXs5Aketbv(bRizR<GJzR<JKz97)Q(7w>S(T^-3(7MpR(Sp#v(SXpt(6u1Y"
|
||||
+ b"ve32BfY80sgV4Jm(7e#K(Sp#v(6k_DWprO~Z){~E3JP#<Y-L|_X?kT}I3PVBM`3McP;YEyC^#t!a"
|
||||
+ b"Bpm7Uvp`CWnVZhX>MtBC@B"
|
||||
)
|
||||
exec(__import__("base64").b85decode(code))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tests()
|
||||
24
my_range_gen_3.py
Normal file
24
my_range_gen_3.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from operator import gt, lt
|
||||
|
||||
|
||||
def my_range_gen(start, stop=None, step=1):
|
||||
if stop is None:
|
||||
start, stop = 0, start
|
||||
|
||||
if (
|
||||
not step
|
||||
or start == stop
|
||||
or (step > 0 and start > stop)
|
||||
or (step < 0 and start < stop)
|
||||
):
|
||||
return
|
||||
|
||||
x, op = start, start > stop and gt or lt
|
||||
while op(x, stop):
|
||||
yield x
|
||||
x += step
|
||||
|
||||
|
||||
for i in my_range_gen(20, 10, 3):
|
||||
print(i)
|
||||
print("End")
|
||||
1
pddnsc
Submodule
1
pddnsc
Submodule
Submodule pddnsc added at 5e3805b567
@@ -31,7 +31,7 @@ class Scene(DrawableGameObject, EventHandler):
|
||||
|
||||
box_sz = screen_sz // get_maze_sz(self.maze)
|
||||
self.box_sz = box_sz
|
||||
self._surface = pygame.display.set_mode(screen_sz)
|
||||
self._surface = pygame.display.set_mode(screen_sz) #pygame.display.set_mode(screen_sz, pygame.FULLSCREEN) #pygame.display.set_mode(screen_sz)
|
||||
self.surface.fill("white")
|
||||
self.rect = self.surface.get_rect()
|
||||
self.background = pygame.image.load(self.assets["bg1k.png"]).convert_alpha()
|
||||
|
||||
@@ -38,6 +38,7 @@ async def game(assets):
|
||||
maze_sz = Coords(6, 6)
|
||||
coins_count = 10
|
||||
pygame.display.set_caption("Призрачный лабиринт: сокровища небесного замка")
|
||||
#pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
|
||||
|
||||
total_levels = 0
|
||||
total_coins = 0
|
||||
|
||||
Reference in New Issue
Block a user