Compare commits

...

34 Commits

Author SHA1 Message Date
d66ec02d68 + etc 2026-01-03 12:35:31 +03:00
9afd997a2e + pddnsc submodule 2026-01-03 12:35:12 +03:00
d88d3af47e phantomcastle: set_mode FULLSCREEN commented 2026-01-03 12:33:56 +03:00
801b0cad33 * 5.6_01 2024-04-29 19:05:48 +03:00
ee91d98383 * 5.6_01 move enums out of Ship class 2024-04-29 18:48:39 +03:00
fc813b6d5c + 5.6_01 sea_battle | accepted! 2024-04-29 18:29:53 +03:00
fa8cfb2b5d > 5.6_01 WIP checkpoint
add ship fields: [is_horisontal, try_move, computer_move, hit, place_to_pole]
2024-04-28 16:39:17 +03:00
c38575df6e > 5.6_01 WIP checkpoint 2024-04-27 13:54:33 +03:00
8c5cd8e3e9 > 5.6_01 WIP checkpoint 2024-04-27 12:27:40 +03:00
48f083afaf + tz 5.6_01 2024-04-26 16:25:54 +03:00
c22ef9e724 * 4.8_01 2024-04-25 10:43:01 +03:00
e96d20343b + 4.8_01 LinkedGraph 2024-04-24 17:14:54 +03:00
1b7e8b8147 fix stack 2024-04-22 11:45:32 +03:00
5145c20156 + 3.x tictactoe (done) 2024-04-17 23:49:24 +03:00
5906af0f69 WIP: 3.x 2024-04-17 17:30:48 +03:00
b34a6842aa * 3.9_12 add eq, ne 2024-04-17 16:02:02 +03:00
102e8ce6bd + 3.9_12 matrix 2024-04-17 15:51:51 +03:00
bb5b226c3d + 3.9_11 2024-04-17 12:57:12 +03:00
3d311f9423 * 3.9_10 fix setitem 2024-04-17 12:15:13 +03:00
f9d8ec192b * 3.9_10 2024-04-17 12:01:38 +03:00
5ce4f0565f * 3.9_10 black 2024-04-17 11:59:29 +03:00
d3ab154609 + 3.9_10 2024-04-17 11:58:56 +03:00
5181bc6fcb + 3.8_10 2024-04-16 14:55:29 +03:00
aa5cfa0124 fix 3.8_08 2024-04-16 13:18:11 +03:00
0d65b8a938 + 3.8_08 2024-04-16 13:15:24 +03:00
607a6cf870 * 3.7_10 fix, reseed before tests 2024-04-15 17:02:25 +03:00
774718b2cd * 3.7_10 fix tests with SingletonMeta 2024-04-15 16:59:30 +03:00
4e887c7782 * 3.7_10 change show 2024-04-15 14:56:04 +03:00
20898206a7 + 3.7_10 minesweeper 2024-04-15 13:37:00 +03:00
c6dda969b4 ren 3.4_12_max_polling.py 2024-04-15 10:20:31 +03:00
6ce82afa41 + 3.6_09 2024-04-14 15:03:37 +03:00
e9a4f02399 + genb85exec 2024-04-14 15:02:34 +03:00
daf84580e9 + 3.5_10 2024-04-13 21:41:09 +03:00
3922323b1b + 3.5_08 2024-04-13 19:46:29 +03:00
19 changed files with 3314 additions and 1 deletions

11
chunker.py Normal file
View 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
View 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
View 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))

View 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
View 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()

View 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
View 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
View 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()

View 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()

View 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
View 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
View 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()

View 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()

View 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
View 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

Submodule pddnsc added at 5e3805b567

View File

@@ -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()

View File

@@ -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