Compare commits

...

72 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
233a3e8134 +3.5_06 2024-04-13 15:54:27 +03:00
046db9e827 fix links 2024-04-13 15:14:48 +03:00
af2733cc02 + 3.5_05 2024-04-13 14:57:17 +03:00
5adf7ea91d + 3.4_12 2024-04-13 13:37:01 +03:00
1fe3c920cb +3.4_11 2024-04-12 16:28:22 +03:00
8ca6581991 + 3.4_07 2024-04-12 14:16:47 +03:00
360f71e61e + 3.3_07 2024-04-11 16:15:07 +03:00
fb4a932136 + 3.2_10 2024-04-11 14:02:10 +03:00
4a67ceca43 + 3.2_04 2024-04-11 10:19:41 +03:00
a8dca8a40a renames 2024-04-11 10:00:44 +03:00
c20c79ab47 + 3.1_12 2024-04-10 16:50:32 +03:00
6ef8a00edc + 3.1_11 2024-04-10 16:00:37 +03:00
f82ed40c2a + 3.1_7 2024-04-10 12:59:16 +03:00
98102422ec + 3.1.6 2024-04-10 12:06:47 +03:00
6afdca0f3a add book 2024-04-10 11:14:07 +03:00
402db810bc + oop tasks 2024-04-09 17:32:49 +03:00
b4c92e894b add mod_oop\decision_tree sol 2024-04-08 17:23:37 +03:00
73cdb1320e add decision_tree tests 2024-04-08 16:38:37 +03:00
1c24b06d98 add rvector2d.py 2024-04-08 16:22:01 +03:00
942385ae53 stuck in stack 2024-04-08 15:51:13 +03:00
a09dfebaa8 phantomcastle: always convert_alpha 2024-04-07 20:37:02 +03:00
087886955d phantomcastle: automove by mouse click 2024-04-06 23:56:26 +03:00
a985199441 phantomcastle: add background music 2024-04-06 22:50:23 +03:00
ee2c47dc2f phantomcastle: bg move 2024-04-06 14:00:41 +03:00
f0d60d50a8 phantomcastle: universal version + split 2024-04-06 13:30:07 +03:00
6ee4776711 phantomcastle wasm: control by mouse 2024-04-04 23:31:56 +03:00
9482dd6c0d phantomcastle: control by mouse 2024-04-04 23:31:06 +03:00
6c55cc750e phc wasm: fix make 2024-04-04 14:12:01 +03:00
b8ecdd8f23 phantomcastle use overlap 2024-04-04 13:38:19 +03:00
f1b7b2e860 add pygame-wasm/phantomcastle 2024-04-02 22:17:25 +03:00
8c4ec3d4cb phc: refactor 2024-04-02 16:39:39 +03:00
b425c4d7e6 phc: get_assets refactor (no need to copy assets) 2024-04-02 14:09:15 +03:00
c788c311b1 phc: fix speed 2024-04-01 11:09:10 +03:00
71d5115ac1 phc: fix speed propperty 2024-04-01 00:51:52 +03:00
49f15f9a21 phc: + hero move accel 2024-04-01 00:42:29 +03:00
98a2e240e3 phc: key repeat + 30 fps event_loop 2024-04-01 00:09:37 +03:00
81bebce3e9 copy move_img to phantom_castle 2024-03-31 00:01:30 +03:00
1525bd3d79 moveimg: fix comment 2024-03-30 23:54:51 +03:00
52 changed files with 7700 additions and 16 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,9 @@
bank.json
ext/
.vscode/
pygame-wasm/phantomcastle/build/
pygame-wasm/phantomcastle/assets/
.venv*/
__pycache__
*.pyc
*.pyo

BIN
assets/bg.ogg Normal file

Binary file not shown.

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))")

View File

@@ -0,0 +1,100 @@
# https://stepik.org/lesson/701985/step/10?unit=702086
class ValidateString:
def __init__(self, min_length=0, max_length=1000):
self.min_length, self.max_length = min_length, max_length
def validate(self, value):
return (
isinstance(value, str) and self.min_length <= len(value) <= self.max_length
)
class StringValue:
def __init__(self, validator=None):
if validator is None:
validator = ValidateString(min_length=3, max_length=100)
self.validator = validator
def __set_name__(self, owner, name):
self.name = "_" + name
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, value):
if self.validator.validate(value):
setattr(instance, self.name, value)
class RegisterForm:
login = StringValue()
password = StringValue(ValidateString(min_length=3, max_length=100))
email = StringValue()
def __init__(self, login, password, email):
self.login, self.password, self.email = login, password, email
def get_fields(self):
return [self.login, self.password, self.email]
def show(self):
print(f"<form>")
for name, field in zip("Логин Пароль Email".split(), self.get_fields()):
print(f"{name}: {field}")
print(f"</form>")
def tests():
assert hasattr(
ValidateString, "validate"
), "в классе ValidateString отсутствует метод validate"
r = RegisterForm("11111", "1111111", "11111111")
assert (
hasattr(r, "login") and hasattr(r, "password") and hasattr(r, "email")
), "в классе RegisterForm должны быть дескрипторы login, password, email"
assert hasattr(RegisterForm, "show"), "в классе RegisterForm отсутствует метод show"
StringValue.__doc__
frm = RegisterForm("123", "2345", "sc_lib@list.ru")
assert frm.get_fields() == [
"123",
"2345",
"sc_lib@list.ru",
], "метод get_fields вернул неверные данные"
frm.login = "root"
assert frm.login == "root", "дескриптор login вернул неверные данные"
v = ValidateString(5, 10)
assert v.validate("hello"), "метод validate вернул неверное значение"
assert v.validate("hell") == False, "метод validate вернул неверное значение"
assert (
v.validate("hello world!") == False
), "метод validate вернул неверное значение"
class A:
st = StringValue(validator=ValidateString(3, 10))
a = A()
a.st = "hello"
assert a.st == "hello", "дескриптор StringValue вернул неверное значение"
a.st = "d"
assert (
a.st == "hello"
), "дескриптор StringValue сохранил строку длиной меньше min_length"
a.st = "dапарпаропропропропр"
assert (
a.st == "hello"
), "дескриптор StringValue сохранил строку длиной больше max_length"
a.st = "dапарпароп"
assert (
a.st == "dапарпароп"
), "дескриптор StringValue сохранил строку длиной больше max_length"
tests()

108
mod_oop/2.3_11_supershop.py Normal file
View File

@@ -0,0 +1,108 @@
# https://stepik.org/lesson/701985/step/11?unit=702086
from typing import Protocol
class Validator(Protocol):
def validate(self, value: str) -> bool:
...
class ValidatedField:
ValueValidator: Validator
def __init__(self, *args, **kwargs):
self.validator = self.ValueValidator(*args, **kwargs)
def __set_name__(self, owner, name):
self.name = "_" + name
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, value):
if self.validator.validate(value):
setattr(instance, self.name, value)
class StringValue(ValidatedField):
class ValueValidator(Validator):
def __init__(self, min_length, max_length):
self.min_length, self.max_length = min_length, max_length
def validate(self, value: str) -> bool:
return (
isinstance(value, str)
and self.min_length <= len(value) <= self.max_length
)
class PriceValue(ValidatedField):
class ValueValidator(Validator):
def __init__(self, max_value):
self.max_value = max_value
def validate(self, value: str) -> bool:
return isinstance(value, int) and 0 <= value <= self.max_value
class Product:
name = StringValue(min_length=2, max_length=50)
price = PriceValue(max_value=10000)
def __init__(self, name, price):
self.name = name
self.price = price
class SuperShop:
def __init__(self, name):
self.name = name
self.goods: list[Product] = []
def add_product(self, product: Product):
self.goods.append(product)
def remove_product(self, product: Product):
self.goods.remove(product)
shop = SuperShop("У Балакирева")
shop.add_product(Product("Курс по Python", 0))
shop.add_product(Product("Курс по Python ООП", 2000))
for p in shop.goods:
print(f"{p.name}: {p.price}")
def tests():
shop = SuperShop("У Балакирева")
shop.add_product(Product("name", 100))
shop.add_product(Product("name", 100))
assert (
shop.name == "У Балакирева"
), "атрибут name объекта класса SuperShop содержит некорректное значение"
for p in shop.goods:
assert p.price == 100, "дескриптор price вернул неверное значение"
assert p.name == "name", "дескриптор name вернул неверное значение"
t = Product("name 123", 1000)
shop.add_product(t)
shop.remove_product(t)
assert (
len(shop.goods) == 2
), "неверное количество товаров: возможно некорректно работают методы add_product и remove_product"
assert hasattr(shop.goods[0], "name") and hasattr(shop.goods[0], "price")
t = Product(1000, "name 123")
if hasattr(t, "_name"):
assert type(t.name) == str, "типы поля name должнен быть str"
if hasattr(t, "_price"):
assert type(t.price) in (
int,
float,
), "тип поля price должнен быть int или float"
tests()

View File

@@ -0,0 +1,59 @@
# https://stepik.org/lesson/701985/step/12?unit=702086
from typing import List
class Thing:
def __init__(self, name: str, weight: int):
self.name, self.weight = name, weight
def __repr__(self):
return f"{self.__class__.__name__}({self.name!r}, {self.weight!r})"
class Bag:
def __init__(self, max_weight: int):
self.__things: List[Thing] = []
self.max_weight = max_weight
def __repr__(self):
return f"{self.__class__.__name__}({self.max_weight!r})"
@property
def weight(self) -> int:
return sum(t.weight for t in self.__things)
@property
def remaining_weight(self) -> int:
return self.max_weight - self.weight
@property
def things(self) -> List[Thing]:
return self.__things
def add_thing(self, thing: Thing):
if self.remaining_weight >= thing.weight:
self.__things.append(thing)
def remove_thing(self, indx: int):
del self.__things[indx]
def get_total_weight(self) -> int:
return self.weight
bag = Bag(1000)
bag.add_thing(Thing("Книга по Python", 100))
bag.add_thing(Thing("Котелок", 500))
bag.add_thing(Thing("Спички", 20))
bag.add_thing(Thing("Бумага", 100))
w = bag.get_total_weight()
for t in bag.things:
print(f"{t.name}: {t.weight}")
def tests():
...
tests()

148
mod_oop/2.3_13_tvprogram.py Normal file
View File

@@ -0,0 +1,148 @@
"""
https://stepik.org/lesson/701985/step/13?unit=702086
Необходимо написать программу для представления и управления расписанием телевизионного вещания. Для этого нужно объявить класс TVProgram, объекты которого создаются командой:
>>> repr(TVProgram("название канала"))
"TVProgram('название канала')"
где название канала - это строка с названием телеканала.
В каждом объекте класса TVProgram должен формироваться локальный атрибут:
items - список из телепередач (изначально список пуст).
>>> TVProgram("название канала").items
[]
В самом классе TVProgram должны быть реализованы следующие методы:
add_telecast(self, tl) - добавление новой телепередачи в список items;
remove_telecast(self, indx) - удаление телепередачи по ее порядковому номеру (атрибуту __id, см. далее).
>>> {hasattr(TVProgram, m) for m in "add_telecast remove_telecast".split()}
{True}
Каждая телепередача должна описываться классом Telecast, объекты которого создаются командой:
>>> repr(Telecast(1, "название", 1111))
"Telecast(1, 'название', 1111)"
где 1 - порядковый номер - номер телепередачи в сетке вещания (от 1 и далее); "название" - наименование телепередачи; 1111 - длительность - время телепередачи (в секундах - целое число).
Соответственно, в каждом объекте класса Telecast должны формироваться локальные приватные атрибуты:
__id - порядковый номер (целое число);
__name - наименование телепередачи (строка);
__duration - длительность телепередачи в секундах (целое число).
>>> {hasattr(obj, a) for obj in (Telecast(1, "название", 1111),) for a in "__id __name __duration".split()}
{True}
Для работы с этими приватными атрибутами в классе Telecast должны быть объявлены соответствующие объекты-свойства (property):
uid - для записи и считывания из локального атрибута __id;
name - для записи и считывания из локального атрибута __name;
duration - для записи и считывания из локального атрибута __duration.
>>> {hasattr(obj, a) for obj in (Telecast(1, "название", 1111),) for a in "uid name duration".split()}
{True}
Пример использования классов (эти строчки в программе писать не нужно):
>>> def test():
... pr = TVProgram("Первый канал")
... pr.add_telecast(Telecast(1, "Доброе утро", 10000))
... pr.add_telecast(Telecast(2, "Новости", 2000))
... pr.add_telecast(Telecast(3, "Интервью с Балакиревым", 20))
... return [*map(lambda t: f"{t.name}: {t.duration}", pr.items)]
>>> test()
['Доброе утро: 10000', 'Новости: 2000', 'Интервью с Балакиревым: 20']
"""
from typing import List
def make_properties(names: List[str]):
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:
private_name = "__id" if name == "uid" else f"__{name}"
setattr(cls, name, property(*prop(private_name)))
return cls
return decorator
@make_properties("uid name duration".split())
class Telecast:
def __init__(self, uid: int, name: str, duration: int):
self.uid, self.name, self.duration = uid, name, duration
def __repr__(self):
return f"{self.__class__.__name__}{(self.uid, self.name, self.duration)!r}"
class TVProgram:
def __init__(self, name: str):
self.name = name
self.items: List[Telecast] = []
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self.name}')"
def add_telecast(self, telecast: Telecast):
self.items.append(telecast)
def remove_telecast(self, indx: int):
self.items = [item for item in self.items if item.uid != indx]
def tests():
assert hasattr(TVProgram, "add_telecast") and hasattr(
TVProgram, "remove_telecast"
), "в классе TVProgram должны быть методы add_telecast и remove_telecast"
pr = TVProgram("Первый канал")
pr.add_telecast(Telecast(1, "Доброе утро", 10000))
pr.add_telecast(Telecast(3, "Новости", 2000))
t = Telecast(2, "Интервью с Балакиревым", 20)
pr.add_telecast(t)
pr.remove_telecast(3)
assert (
len(pr.items) == 2
), "неверное число телеперач, возможно, некорректно работает метод remove_telecast"
assert (
pr.items[-1] == t
), "удалена неверная телепередача (возможно, вы удаляете не по __id, а по порядковому индексу в списке items)"
assert (
type(Telecast.uid) == property
and type(Telecast.name) == property
and type(Telecast.duration) == property
), "в классе Telecast должны быть объекты-свойства uid, name и duration"
for x in pr.items:
assert hasattr(x, "uid") and hasattr(x, "name") and hasattr(x, "duration")
assert (
pr.items[0].name == "Доброе утро"
), "объект-свойство name вернуло неверное значение"
assert (
pr.items[0].duration == 10000
), "объект-свойство duration вернуло неверное значение"
t = Telecast(1, "Доброе утро", 10000)
t.uid = 2
t.name = "hello"
t.duration = 10
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

65
mod_oop/3.1_05_book.py Normal file
View File

@@ -0,0 +1,65 @@
"""
https://stepik.org/lesson/701986/step/5?unit=702087
Объявите класс Book для представления информации о книге. Объекты этого класса должны создаваться командами:
>>> book = Book()
>>> book = Book("название", "автор", 100, 1984) # 100 - число страниц, 1984 - год издания
В каждом объекте класса Book автоматически должны формироваться следующие локальные свойства:
title - заголовок книги (строка, по умолчанию пустая строка);
author - автор книги (строка, по умолчанию пустая строка);
pages - число страниц (целое число, по умолчанию 0);
year - год издания (целое число, по умолчанию 0).
Объявите в классе Book магический метод __setattr__ для проверки типов присваиваемых данных локальным свойствам title, author, pages и year. Если типы не соответствуют локальному атрибуту (например, title должна ссылаться на строку, а pages - на целое число), то генерировать исключение командой:
raise TypeError("Неверный тип присваиваемых данных.")
>>> book = Book(1)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
Создайте в программе объект book класса Book для книги:
автор: Сергей Балакирев
заголовок: Python ООП
pages: 123
year: 2022
>>> book = Book("Python ООП", "Сергей Балакирев", 123, 2022)
>>> str(book)
'Python ООП, Сергей Балакирев, 123, 2022'
"""
class Book:
title: str = ""
author: str = ""
pages: int = 0
year: int = 0
def __init__(self, *args, **kwargs):
for src in (zip(self.__annotations__, args), kwargs.items()):
for k, v in src:
setattr(self, k, v)
def __str__(self):
return f"{self.title}, {self.author}, {self.pages}, {self.year}"
def __setattr__(self, name: str, value):
if not isinstance(value, self.__annotations__.get(name, object)):
raise TypeError("Неверный тип присваиваемых данных.")
super().__setattr__(name, value)
def tests():
...
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

123
mod_oop/3.1_06_shop.py Normal file
View File

@@ -0,0 +1,123 @@
"""
https://stepik.org/lesson/701986/step/6?unit=702087
Вы создаете интернет-магазин. Для этого нужно объявить два класса:
Shop - класс для управления магазином в целом;
Product - класс для представления отдельного товара.
Объекты класса Shop следует создавать командой:
>>> shop = Shop("название магазина")
В каждом объекте класса Shop должно создаваться локальное свойство:
goods - список товаров (изначально список пустой).
А также в классе объявить методы:
add_product(self, product) - добавление нового товара в магазин (в конец списка goods);
remove_product(self, product) - удаление товара product из магазина (из списка goods);
Объекты класса Product следует создавать командой:
p = Product(название, вес, цена)
>>> p = Product("название", 123, 4.56)
В них автоматически должны формироваться локальные атрибуты:
id - уникальный идентификационный номер товара (генерируется автоматически как целое положительное число от 1 и далее);
name - название товара (строка);
weight - вес товара (целое или вещественное положительное число);
price - цена (целое или вещественное положительное число).
В классе Product через магические методы (подумайте какие) осуществить проверку на тип присваиваемых данных локальным атрибутам объектов класса
(например, id - целое число, name - строка и т.п.). Если проверка не проходит, то генерировать исключение командой:
raise TypeError("Неверный тип присваиваемых данных.")
>>> Product("название", "вес", 4.56)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
>>> Product("название", -1, 4.56)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
>>> Product("название", 1, -4.56)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
Также в классе Product с помощью магического(их) метода(ов) запретить удаление локального атрибута id. При попытке это сделать генерировать исключение:
raise AttributeError("Атрибут id удалять запрещено.")
>>> del Product("название", 123, 4.56).id
Traceback (most recent call last):
...
AttributeError: Атрибут id удалять запрещено.
Пример использования классов (в программе эти строчки не писать):
shop = Shop("Балакирев и К")
book = Product("Python ООП", 100, 1024)
shop.add_product(book)
shop.add_product(Product("Python", 150, 512))
for p in shop.goods:
print(f"{p.name}, {p.weight}, {p.price}")
>>> shop = Shop("Балакирев и К")
>>> book = Product("Python ООП", 100, 1024)
>>> shop.add_product(book)
>>> shop.add_product(Product("Python", 150, 512))
>>> [f"{p.name}, {p.weight}, {p.price}" for p in shop.goods]
['Python ООП, 100, 1024', 'Python, 150, 512']
P.S. На экран ничего выводить не нужно.
"""
from typing import TypeAlias, Union
class Shop:
def __init__(self, name):
self.name, self.goods = name, []
def add_product(self, product):
self.goods.append(product)
def remove_product(self, product):
self.goods.remove(product)
Number: TypeAlias = Union[int, float]
class Product:
__last_id: int = 0
id: int
name: str
weight: Number
price: Number
def __new__(cls, *args, **kwargs):
cls.__last_id += 1
return super().__new__(cls)
def __init__(self, name: str, weight: Number, price: Number):
self.id = self.__last_id
self.name, self.weight, self.price = name, weight, price
def __setattr__(self, name: str, value):
typ = self.__annotations__.get(name, object)
if not isinstance(value, typ) or (typ is Number and value < 1):
raise TypeError("Неверный тип присваиваемых данных.")
super().__setattr__(name, value)
def __delattr__(self, name: str) -> None:
if name == "id":
raise AttributeError("Атрибут id удалять запрещено.")
def tests():
...
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,281 @@
"""
https://stepik.org/lesson/701986/step/7?unit=702087
Необходимо создать программу для обучающего курса. Для этого объявляются три класса:
Course - класс, отвечающий за управление курсом в целом;
Module - класс, описывающий один модуль (раздел) курса;
LessonItem - класс одного занятия (урока).
Объекты класса LessonItem должны создаваться командой:
lesson = LessonItem(название урока, число практических занятий, общая длительность урока)
>>> lesson = LessonItem("название урока", 2, 10)
Соответственно, в каждом объекте класса LessonItem должны создаваться локальные атрибуты:
title - название урока (строка);
practices - число практических занятий (целое положительное число);
duration - общая длительность урока (целое положительное число).
Необходимо с помощью магических методов реализовать следующую логику взаимодействия с объектами класса LessonItem:
1. Проверять тип присваиваемых данных локальным атрибутам. Если типы не соответствуют требованиям, то генерировать исключение командой:
raise TypeError("Неверный тип присваиваемых данных.")
2. При обращении к несуществующим атрибутам объектов класса LessonItem возвращать значение False.
3. Запретить удаление атрибутов title, practices и duration в объектах класса LessonItem.
--- тесты типов ---
>>> LessonItem(1, 2, 10)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
>>> LessonItem("название урока", -2, 10)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
>>> LessonItem("название урока", 2, -10)
Traceback (most recent call last):
...
TypeError: Неверный тип присваиваемых данных.
-------------------
Объекты класса Module должны создаваться командой:
>>> module = Module("название модуля")
Каждый объект класса Module должен содержать локальные атрибуты:
name - название модуля;
lessons - список из уроков (объектов класса LessonItem), входящих в модуль (изначально список пуст).
Также в классе Module должны быть реализованы методы:
add_lesson(self, lesson) - добавление в модуль (в конец списка lessons) нового урока (объекта класса LessonItem);
remove_lesson(self, indx) - удаление урока по индексу в списке lessons.
Наконец, объекты класса Course создаются командой:
course = Course(название курса)
И содержат следующие локальные атрибуты:
name - название курса (строка);
modules - список модулей в курсе (изначально список пуст).
Также в классе Course должны присутствовать следующие методы:
add_module(self, module) - добавление нового модуля в конце списка modules;
remove_module(self, indx) - удаление модуля из списка modules по индексу в этом списке.
Пример использования классов (в программе эти строчки не писать):
course = Course("Python ООП")
module_1 = Module("Часть первая")
module_1.add_lesson(LessonItem("Урок 1", 7, 1000))
module_1.add_lesson(LessonItem("Урок 2", 10, 1200))
module_1.add_lesson(LessonItem("Урок 3", 5, 800))
course.add_module(module_1)
module_2 = Module("Часть вторая")
module_2.add_lesson(LessonItem("Урок 1", 7, 1000))
module_2.add_lesson(LessonItem("Урок 2", 10, 1200))
course.add_module(module_2)
P.S. На экран ничего выводить не нужно.
>>> def make_course():
... course = Course("Python ООП")
... module_1 = Module("Часть первая")
... module_1.add_lesson(LessonItem("Урок 1", 7, 1000))
... module_1.add_lesson(LessonItem("Урок 2", 10, 1200))
... module_1.add_lesson(LessonItem("Урок 3", 5, 800))
... course.add_module(module_1)
... module_2 = Module("Часть вторая")
... module_2.add_lesson(LessonItem("Урок 1", 7, 1000))
... module_2.add_lesson(LessonItem("Урок 2", 10, 1200))
... course.add_module(module_2)
... return course
>>> ((course := make_course()).name, {
... module.name: [
... f"{lesson.title}, {lesson.practices}, {lesson.duration}"
... for lesson in module.lessons
... ]
... for module in course.modules
... })
('Python ООП', {'Часть первая': ['Урок 1, 7, 1000', 'Урок 2, 10, 1200', 'Урок 3, 5, 800'], 'Часть вторая': ['Урок 1, 7, 1000', 'Урок 2, 10, 1200']})
"""
class LessonItem:
title: str
practices: int
duration: int
class Validators:
@staticmethod
def is_positive(value) -> bool:
return value > 0
@staticmethod
def olways_ok(_) -> bool:
return True
__nodel = "title", "practices", "duration"
__validators = {
"practices": Validators.is_positive,
"duration": Validators.is_positive,
}
def __init__(self, title: str, practices: int, duration: int):
self.title, self.practices, self.duration = title, practices, duration
def __getattr__(self, name: str):
return False
def __setattr__(self, name: str, value):
if not (
isinstance(value, self.__annotations__.get(name, object))
and self.__validators.get(name, self.Validators.olways_ok)(value)
):
raise TypeError("Неверный тип присваиваемых данных.")
super().__setattr__(name, value)
def __delattr__(self, name: str):
if name in self.__nodel:
raise AttributeError(f"Нельзя удалить атрибут {name}")
class Module:
def __init__(self, name: str):
self.name, self.lessons = name, []
def add_lesson(self, lesson: LessonItem):
self.lessons.append(lesson)
def remove_lesson(self, indx: int):
del self.lessons[indx]
class Course:
def __init__(self, name: str):
self.name, self.modules = name, []
def add_module(self, module: Module):
self.modules.append(module)
def remove_module(self, indx: int):
del self.modules[indx]
def tests():
# из коммента https://stepik.org/lesson/701986/step/7?discussion=7381286&unit=702087
# TEST-TASK___________________________________
course = Course("Python ООП")
assert type(course.name) is str, "название курса должно быть строкой"
assert (
type(course.modules) is list and len(course.modules) == 0
), "modules должен быть списком, изначально список пуст"
# add_module(self, module) - добавление нового модуля в конце списка modules;
# remove_module(self, indx) - удаление модуля из списка modules по индексу в этом списке.
assert hasattr(course, "add_module"), "add_module необъявлен"
assert hasattr(course, "remove_module"), "remove_module необъявлен"
#
module_1 = Module("Часть первая")
module_2 = Module("Часть вторая")
assert type(module_1.name) is str, "название модуля должно быть строкой"
assert (
type(module_1.lessons) is list and len(module_1.lessons) == 0
), "lesson должен быть списком, изначально список пуст"
# add_lesson(self, lesson) - добавление в модуль (в конец списка lessons) нового урока (объекта класса LessonItem);
# remove_lesson(self, indx) - удаление урока по индексу в списке lessons.
assert hasattr(module_1, "add_lesson"), "add_lesson необъявлен"
assert hasattr(module_1, "remove_lesson"), "remove_lesson необъявлен"
#
les_1 = LessonItem("Урок 1", 7, 1000)
les_2 = LessonItem("Урок 2", 10, 1200)
assert type(les_1.title) is str, "название урока должно быть строкой"
assert (
type(les_1.practices) is int and les_1.practices > 0
), "practices должен быть целым числом больше ноля"
assert (
type(les_1.duration) is int and les_1.practices > 0
), "duration должен быть целым положительным числом"
#
# проверка методов
course.add_module(module_1)
course.add_module(module_2)
assert (
len(course.modules) == 2 and course.modules[1] == module_2
), "некоректно отработал метод add_module"
course.remove_module(0)
assert (
module_1 not in course.modules and len(course.modules) == 1
), "некоректно отработал метод remove_module"
#
module_1.add_lesson(les_1)
module_1.add_lesson(les_2)
assert (
len(module_1.lessons) == 2 and module_1.lessons[1] == les_2
), "некоректно отработал метод add_lesson"
module_1.remove_lesson(0)
assert (
les_1 not in module_1.lessons and len(module_1.lessons) == 1
), "некоректно отработал метод remove_lesson"
#
# проверка методов - LessonItem
# 1. Проверять тип присваиваемых данных локальным атрибутам. Если типы не соответствуют требованиям, то генерировать исключение командой:
# raise TypeError("Неверный тип присваиваемых данных.")
try:
les_3 = LessonItem(3, 8, 900)
except TypeError:
assert True
else:
assert (
False
), "не сгенерировалось исключение TypeError при записи некорректных данных в title"
try:
les_3 = LessonItem("Урок 2", 8.0, 900)
except TypeError:
assert True
else:
assert (
False
), "не сгенерировалось исключение TypeError при записи некорректных данных в practices"
try:
les_3 = LessonItem("Урок 2", 8, 900, 0)
except TypeError:
assert True
else:
assert (
False
), "не сгенерировалось исключение TypeError при записи некорректных данных в duration"
# 2. При обращении к несуществующим атрибутам объектов класса LessonItem возвращать значение False.
les_4 = LessonItem("Урок 2", 8, 900)
assert hasattr(
les_1, "__getattr__"
), "ошибка при обращении к несуществующему локальному атрибуту, метод должен вернуть False"
assert (
les_4.value_not_atr is False
), "ошибка при обращении к несуществующему локальному атрибуту, метод должен вернуть False"
# 3. Запретить удаление атрибутов title, practices и duration в объектах класса LessonItem.
assert hasattr(
les_4, "__delattr__"
), "возможно вы не продумали запрет на удаление локальных атрибутов - title, practices и duration"
try:
del les_4.title
del les_4.practices
del les_4.duration
except:
...
else:
if any(
True if _ not in les_4.__dict__ else False
for _ in ["title", "practices", "duration"]
):
print(
"Ошибка при удалении локальных атрибутов - title, practices и duration"
)
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,165 @@
"""
https://stepik.org/lesson/701986/step/11?unit=702087
Объявите в программе класс Dimensions (габариты) с атрибутами:
MIN_DIMENSION = 10
MAX_DIMENSION = 1000
>>> (Dimensions.MIN_DIMENSION, Dimensions.MAX_DIMENSION)
(10, 1000)
Каждый объект класса Dimensions должен создаваться командой:
d3 = Dimensions(a, b, c) # a, b, c - габаритные размеры
>>> d3 = Dimensions(10, 10, 10)
и содержать локальные атрибуты:
__a, __b, __c - габаритные размеры (целые или вещественные числа).
Для работы с этими локальными атрибутами в классе Dimensions следует прописать следующие объекты-свойства:
a, b, c - для изменения и считывания соответствующих локальных атрибутов __a, __b, __c.
>>> (d3.a, d3.b, d3.c)
(10, 10, 10)
>>> [*map(lambda x: getattr(Dimensions, x).__class__, "abc")]
[<class 'property'>, <class 'property'>, <class 'property'>]
При изменении значений __a, __b, __c следует проверять, что присваиваемое значение число в диапазоне [MIN_DIMENSION; MAX_DIMENSION]. Если это не так, то новое значение не присваивается (игнорируется).
С помощью магических методов данного занятия запретить создание локальных атрибутов MIN_DIMENSION и MAX_DIMENSION в объектах класса Dimensions. При попытке это сделать генерировать исключение:
raise AttributeError("Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено.")
>>> d3.MIN_DIMENSION = 4
Traceback (most recent call last):
...
AttributeError: Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено.
>>> d3.MAX_DIMENSION = 400
Traceback (most recent call last):
...
AttributeError: Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено.
Пример использования класса (эти строчки в программе писать не нужно):
d = Dimensions(10.5, 20.1, 30)
d.a = 8
d.b = 15
a, b, c = d.a, d.b, d.c # a=10.5, b=15, c=30
d.MAX_DIMENSION = 10 # исключение AttributeError
>>> d = Dimensions(10.5, 20.1, 30)
>>> d.a = 8
>>> d.b = 15
>>> (d.a, d.b, d.c)
(10.5, 15, 30)
P.S. В программе нужно объявить только класс Dimensions. На экран ничего выводить не нужно.
"""
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(*"abc")
class Dimensions:
MIN_DIMENSION = 10
MAX_DIMENSION = 1000
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def __setattr__(self, name, value):
if name in ["MIN_DIMENSION", "MAX_DIMENSION"]:
raise AttributeError(
"Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено."
)
if not self.MIN_DIMENSION <= value <= self.MAX_DIMENSION:
return
super().__setattr__(name, value)
def tests():
# из https://stepik.org/lesson/701986/step/11?discussion=7391213&unit=702087
# TEST-TASK___________________________________
x = Dimensions(10.0, 20, 30)
# проверка что в классе прописаны объекты свойства a-b-c
assert type(Dimensions.a) is property, "a - не является объектом свойством property"
assert type(Dimensions.b) is property, "b - не является объектом свойством property"
assert type(Dimensions.c) is property, "c - не является объектом свойством property"
# проверка что в объекте класса существуют 3 приватных локальных атрибута
assert (
"_Dimensions__a" in x.__dict__
and "_Dimensions__b" in x.__dict__
and "_Dimensions__c" in x.__dict__
), "атрибуты не являются приватными"
# проверка что данные считываются с приватных атрибутов
assert (
x.a == 10.0 and x.b == 20 and x.c == 30
), "при обращении к приватным атрибутам значения не получены проверьте объекты свойства"
# проверка что значения являются целым или вещественным числами
assert (
type(x.a) in (int, float)
and type(x.b) in (int, float)
and type(x.c) in (int, float)
), "значения должны быть или целым числом или вещественным"
# проверка на существование атрибутов минимум-максимум
assert hasattr(x, "MIN_DIMENSION"), "не найден атрибут MIN_DIMENSION"
assert hasattr(x, "MAX_DIMENSION"), "не найден атрибут MAX_DIMENSION"
# проверка что значение в диапазоне
x.a = 9
assert (
x.a == 10.0
), "присваиваемое значение должно быть в диапазоне [MIN_DIMENSION; MAX_DIMENSION]"
x.b = -1
assert (
x.b == 20
), "присваиваемое значение должно быть в диапазоне [MIN_DIMENSION; MAX_DIMENSION]"
x.c = 1001
assert (
x.c == 30
), "присваиваемое значение должно быть в диапазоне [MIN_DIMENSION; MAX_DIMENSION]"
# проверка
# С помощью магических методов данного занятия запретить создание локальных атрибутов MIN_DIMENSION и MAX_DIMENSION в объектах класса Dimensions.
# При попытке это сделать генерировать исключение:
# raise AttributeError("Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено.")
try:
x.MIN_DIMENSION = 0
except AttributeError:
assert True
else:
assert False, "не сгенерировалось исключение AttributeError"
try:
x.MAX_DIMENSION = 0
except AttributeError:
assert True
else:
assert False, "не сгенерировалось исключение AttributeError"
assert (
"MIN_DIMENSION" not in x.__dict__
), "запретить создание локальных атрибутов MIN_DIMENSION и MAX_DIMENSION в объектах класса Dimensions"
assert (
"MAX_DIMENSION" not in x.__dict__
), "запретить создание локальных атрибутов MIN_DIMENSION и MAX_DIMENSION в объектах класса Dimensions"
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

200
mod_oop/3.1_12_geyser.py Normal file
View File

@@ -0,0 +1,200 @@
"""
https://stepik.org/lesson/701986/step/12?unit=702087
Объявите класс GeyserClassic - фильтр для очистки воды. В этом классе должно быть три слота для фильтров. Каждый слот строго для своего класса фильтра:
Mechanical - для очистки от крупных механических частиц;
Aragon - для последующей очистки воды;
Calcium - для обработки воды на третьем этапе.
Объекты классов фильтров должны создаваться командами:
filter_1 = Mechanical(дата установки)
filter_2 = Aragon(дата установки)
filter_3 = Calcium(дата установки)
Во всех объектах этих классов должен формироваться локальный атрибут:
date - дата установки фильтров (для простоты - положительное вещественное число).
Также нужно запретить изменение этого атрибута после создания объектов этих классов (только чтение). В случае присвоения нового значения, прежнее значение не менять. Ошибок никаких не генерировать.
Объекты класса GeyserClassic должны создаваться командой:
>>> g = GeyserClassic()
А сам класс иметь атрибут:
MAX_DATE_FILTER = 100 - максимальное время работы фильтра (любого)
>>> GeyserClassic.MAX_DATE_FILTER
100
и следующие методы:
add_filter(self, slot_num, filter) - добавление фильтра filter в указанный слот slot_num (номер слота: 1, 2 и 3), если он (слот) пустой (без фильтра). Также здесь следует проверять, что в первый слот можно установить только объекты класса Mechanical, во второй - объекты класса Aragon и в третий - объекты класса Calcium. Иначе слот должен оставаться пустым.
remove_filter(self, slot_num) - извлечение фильтра из указанного слота (slot_num: 1, 2, и 3);
get_filters(self) - возвращает кортеж из набора трех фильтров в порядке их установки (по возрастанию номеров слотов);
water_on(self) - включение воды: возвращает True, если вода течет и False - в противном случае.
>>> {a in b for b in (dir(GeyserClassic),) for a in "add_filter remove_filter get_filters water_on".split()}
{True}
Метод water_on() должен возвращать значение True при выполнении следующих условий:
- все три фильтра установлены в слотах;
- все фильтры работают в пределах срока службы (значение (time.time() - date) должно быть в пределах [0; MAX_DATE_FILTER])
Пример использования классов (эти строчки в программе писать не нужно):
my_water = GeyserClassic()
my_water.add_filter(1, Mechanical(time.time()))
my_water.add_filter(2, Aragon(time.time()))
w = my_water.water_on() # False
my_water.add_filter(3, Calcium(time.time()))
w = my_water.water_on() # True
f1, f2, f3 = my_water.get_filters() # f1, f2, f3 - ссылки на соответствующие объекты классов фильтров
my_water.add_filter(3, Calcium(time.time())) # повторное добавление в занятый слот невозможно
my_water.add_filter(2, Calcium(time.time())) # добавление в "чужой" слот также невозможно
>>> import time
>>> my_water = GeyserClassic()
>>> my_water.add_filter(1, Mechanical(time.time()))
>>> my_water.add_filter(2, Aragon(time.time()))
>>> my_water.water_on()
False
>>> my_water.add_filter(3, Calcium(time.time()))
>>> my_water.water_on()
True
>>> a, b, c = my_water.get_filters()
>>> my_water.add_filter(3, Calcium(time.time()))
>>> my_water.get_filters() == (a, b, c)
True
>>> my_water = GeyserClassic()
>>> my_water.add_filter(2, Calcium(time.time()))
>>> my_water.get_filters() == (None, None, None)
True
P.S. На экран ничего выводить не нужно.
"""
import time
class Filter:
slot: int
def __init__(self, date: float):
self.__date = date
def __init_subclass__(cls, *args, **kwargs):
if "slot" in kwargs:
cls.slot = kwargs["slot"]
@property
def date(self):
return self.__date
def __setattr__(self, name: str, value):
if name in ("slot", "date"):
return
super().__setattr__(name, value)
class Mechanical(Filter, slot=1):
...
class Aragon(Filter, slot=2):
...
class Calcium(Filter, slot=3):
...
class GeyserClassic:
MAX_DATE_FILTER = 100
def __init__(self):
self.__filters = [None, None, None]
def __setattr__(self, name: str, value):
if name == "MAX_DATE_FILTER":
return
super().__setattr__(name, value)
def add_filter(self, slot_num: int, flt: Filter):
if self.__filters[slot_num - 1] or slot_num != flt.slot:
return
self.__filters[slot_num - 1] = flt
def remove_filter(self, slot_num: int):
self.__filters[slot_num - 1] = None
def get_filters(self) -> tuple:
return tuple(self.__filters)
def water_on(self) -> bool:
date = time.time()
for flt in self.__filters:
if not flt:
return False
if not 0 <= (date - flt.date) <= self.MAX_DATE_FILTER:
return False
return True
def tests():
my_water = GeyserClassic()
my_water.add_filter(1, Mechanical(time.time()))
my_water.add_filter(2, Aragon(time.time()))
assert (
my_water.water_on() == False
), "метод water_on вернул True, хотя не все фильтры были установлены"
my_water.add_filter(3, Calcium(time.time()))
assert (
my_water.water_on()
), "метод water_on вернул False при всех трех установленных фильтрах"
f1, f2, f3 = my_water.get_filters()
assert (
isinstance(f1, Mechanical)
and isinstance(f2, Aragon)
and isinstance(f3, Calcium)
), "фильтры должны быть устанлены в порядке: Mechanical, Aragon, Calcium"
my_water.remove_filter(1)
assert (
my_water.water_on() == False
), "метод water_on вернул True, хотя один из фильтров был удален"
my_water.add_filter(1, Mechanical(time.time()))
assert (
my_water.water_on()
), "метод water_on вернул False, хотя все три фильтра установлены"
f1, f2, f3 = my_water.get_filters()
my_water.remove_filter(1)
my_water.add_filter(1, Mechanical(time.time() - GeyserClassic.MAX_DATE_FILTER - 1))
assert (
my_water.water_on() == False
), "метод water_on вернул True, хотя у одного фильтра истек срок его работы"
f1 = Mechanical(1.0)
f2 = Aragon(2.0)
f3 = Calcium(3.0)
assert (
0.9 < f1.date < 1.1 and 1.9 < f2.date < 2.1 and 2.9 < f3.date < 3.1
), "неверное значение атрибута date в объектах фильтров"
f1.date = 5.0
f2.date = 5.0
f3.date = 5.0
assert (
0.9 < f1.date < 1.1 and 1.9 < f2.date < 2.1 and 2.9 < f3.date < 3.1
), "локальный атрибут date в объектах фильтров должен быть защищен от изменения"
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,54 @@
"""
https://stepik.org/lesson/701987/step/4?unit=702088
Объявите класс RandomPassword для генерации случайных паролей. Объекты этого класса должны создаваться командой:
rnd = RandomPassword(psw_chars, min_length, max_length)
где psw_chars - строка из разрешенных в пароле символов; min_length, max_length - минимальная и максимальная длина генерируемых паролей.
>>> rnd = RandomPassword("abcd", 4, 6)
Непосредственная генерация одного пароля должна выполняться командой:
psw = rnd()
где psw - ссылка на строку длиной в диапазоне [min_length; max_length] из случайно выбранных символов строки psw_chars.
>>> psw = rnd()
>>> type(psw), 4 <= len(psw) <= 6, set("abcd").issuperset(psw)
(<class 'str'>, True, True)
С помощью генератора списка (list comprehension) создайте список lst_pass из трех сгенерированных паролей объектом rnd класса RandomPassword, созданного с параметрами:
min_length = 5
max_length = 20
psw_chars = "qwertyuiopasdfghjklzxcvbnm0123456789!@#$%&*"
>>> len(lst_pass)
3
P.S. Выводить на экран ничего не нужно, только создать список из паролей.
P.P.S. Дополнительное домашнее задание: попробуйте реализовать этот же функционал с использованием замыканий функций.
"""
import random
class RandomPassword:
def __init__(self, psw_chars: str, min_length: int, max_length: int):
self.psw_chars, self.min_length, self.max_length = psw_chars, min_length, max_length
def __call__(self):
return ''.join(random.choices(self.psw_chars, k=random.randint(self.min_length, self.max_length)))
RandomPassword = lambda a, b, c: lambda: ''.join(random.choices(a, k=random.randint(b, c)))
rnd = RandomPassword("qwertyuiopasdfghjklzxcvbnm0123456789!@#$%&*", 5, 20)
lst_pass = [rnd() for _ in range(3)]
def tests():
...
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,79 @@
"""
https://stepik.org/lesson/701987/step/4?unit=702088
Необходимо объявить класс-декоратор с именем Handler, который можно было бы применять к функциям следующим образом:
@Handler(methods=('GET', 'POST')) # по умолчанию methods = ('GET',)
def contact(request):
return "Сергей Балакирев"
Здесь аргумент methods декоратора Handler содержит список разрешенных запросов для обработки. Сама декорированная функция вызывается по аналогии с предыдущим подвигом:
res = contact({"method": "POST", "url": "contact.html"})
В результате функция contact должна возвращать строку в формате:
"<метод>: <данные из функции>"
В нашем примере - это будет:
"POST: Сергей Балакирев"
>>> @Handler(methods=('GET', 'POST')) # по умолчанию methods = ('GET',)
... def contact(request):
... return "Сергей Балакирев"
>>> contact({"method": "POST", "url": "contact.html"})
'POST: Сергей Балакирев'
Если ключ method в словаре request отсутствует, то по умолчанию подразумевается GET-запрос. Если ключ method принимает значение отсутствующее в списке methods декоратора Handler, например, "PUT", то декорированная функция contact должна возвращать значение None.
Для имитации GET и POST-запросов в классе Handler необходимо объявить два вспомогательных метода с сигнатурами:
def get(self, func, request, *args, **kwargs) - для имитации обработки GET-запроса
def post(self, func, request, *args, **kwargs) - для имитации обработки POST-запроса
В зависимости от типа запроса должен вызываться соответствующий метод (его выбор в классе можно реализовать методом __getattribute__()). На выходе эти методы должны формировать строки в заданном формате.
P.S. В программе достаточно объявить только класс. Ничего на экран выводить не нужно.
Небольшая справка
Для реализации декоратора с параметрами на уровне класса в инициализаторе __init__(self, methods) прописываем параметр для декоратора, а магический метод __call__() объявляем как полноценный декоратор на уровне функции, например:
class Handler:
def __init__(self, methods):
# здесь нужные строчки
def __call__(self, func):
def wrapper(request, *args, **kwargs):
# здесь нужные строчки
return wrapper
"""
Handler=[(setattr(a,"get",0),setattr(a,"post",0),a)[2]for a in[lambda **b:lambda c:lambda d,*e,**f:[h in(g or["GET"])and f"{h}: {c(d,*e,**f)}"or None for g in[b["methods"]]for h in[d.get("method",g and g[0]or"GET")]][0]]][0]
def tests():
assert hasattr(Handler, 'get') and hasattr(Handler, 'post'), "класс Handler должен содержать методы get и post"
@Handler(methods=('GET', 'POST'))
def contact2(request):
return "контакты"
assert contact2({"method": "POST"}) == "POST: контакты", "декорированная функция вернула неверные данные"
assert contact2({"method": "GET"}) == "GET: контакты", "декорированная функция вернула неверные данные"
assert contact2({"method": "DELETE"}) is None, "декорированная функция вернула неверные данные"
assert contact2({}) == "GET: контакты", "декорированная функция вернула неверные данные при указании пустого словаря"
@Handler(methods=('POST'))
def index(request):
return "index"
assert index({"method": "POST"}) == "POST: index", "декорированная функция вернула неверные данные"
assert index({"method": "GET"}) is None, "декорированная функция вернула неверные данные"
assert index({"method": "DELETE"}) is None, "декорированная функция вернула неверные данные"
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,172 @@
"""
https://stepik.org/lesson/701988/step/7?unit=702089
Объявите класс LinkedList (связный список) для работы со следующей структурой данных:
Здесь создается список из связанных между собой объектов класса ObjList. Объекты этого класса создаются командой:
obj = ObjList(data)
где data - строка с некоторой информацией. Также в каждом объекте obj класса ObjList должны создаваться следующие локальные атрибуты:
__data - ссылка на строку с данными;
__prev - ссылка на предыдущий объект связного списка (если объекта нет, то __prev = None);
__next - ссылка на следующий объект связного списка (если объекта нет, то __next = None).
В свою очередь, объекты класса LinkedList должны создаваться командой:
linked_lst = LinkedList()
и содержать локальные атрибуты:
head - ссылка на первый объект связного списка (если список пуст, то head = None);
tail - ссылка на последний объект связного списка (если список пуст, то tail = None).
А сам класс содержать следующие методы:
add_obj(obj) - добавление нового объекта obj класса ObjList в конец связного списка;
remove_obj(indx) - удаление объекта класса ObjList из связного списка по его порядковому номеру (индексу); индекс отсчитывается с нуля.
Также с объектами класса LinkedList должны поддерживаться следующие операции:
len(linked_lst) - возвращает число объектов в связном списке;
linked_lst(indx) - возвращает строку __data, хранящуюся в объекте класса ObjList, расположенного под индексом indx (в связном списке).
Пример использования классов (эти строчки в программе писать не нужно):
linked_lst = LinkedList()
linked_lst.add_obj(ObjList("Sergey"))
linked_lst.add_obj(ObjList("Balakirev"))
linked_lst.add_obj(ObjList("Python"))
linked_lst.remove_obj(2)
linked_lst.add_obj(ObjList("Python ООП"))
n = len(linked_lst) # n = 3
s = linked_lst(1) # s = Balakirev
P.S. На экран в программе ничего выводить не нужно.
"""
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
def add_obj(self, obj):
if not self.head:
self.head = obj
self.tail = obj
else:
self.tail.next = obj
obj.prev = self.tail
self.tail = obj
def __iter__(self):
obj = self.head
while obj and obj != self.tail.next:
yield obj
obj = obj.next
def __reversed__(self):
obj = self.tail
while obj and obj != self.head.prev:
yield obj
obj = obj.prev
def remove_obj(self, idx):
if not self:
pass
elif idx == 0:
self.head = self.head.next
if self.head:
self.head.prev = None
elif idx == len(self) - 1:
self.tail = self.tail.prev
self.tail.next = None
else:
obj = self.head
for _ in range(idx):
obj = obj.next
obj.prev.next = obj.next
obj.next.prev = obj.prev
if not self:
self.head = self.tail = None
def __len__(self):
return sum(1 for _ in self)
def __getitem__(self, idx):
return next(filter(lambda x: x[0] == idx, enumerate(self)))[1]
def __call__(self, key):
return self[key].data
def get_data(self):
return [*map(ObjList.get_data, self)]
class ObjList:
def __init__(self, data=None):
self.__next = None
self.__prev = None
self.__data = data
def set_next(self, obj):
self.__next = obj
def set_prev(self, obj):
self.__prev = obj
def get_next(self):
return self.__next
def get_prev(self):
return self.__prev
def set_data(self, data):
self.__data = data
def get_data(self):
return self.__data
data = property(get_data, set_data)
next = property(get_next, set_next)
prev = property(get_prev, set_prev)
def tests():
ln = LinkedList()
ln.add_obj(ObjList("Сергей"))
ln.add_obj(ObjList("Балакирев"))
ln.add_obj(ObjList("Python ООП"))
ln.remove_obj(2)
assert (
len(ln) == 2
), "функция len вернула неверное число объектов в списке, возможно, неверно работает метод remove_obj()"
ln.add_obj(ObjList("Python"))
assert ln(2) == "Python", "неверное значение атрибута __data, взятое по индексу"
assert len(ln) == 3, "функция len вернула неверное число объектов в списке"
assert ln(1) == "Балакирев", "неверное значение атрибута __data, взятое по индексу"
n = 0
h = ln.head
while h:
assert isinstance(h, ObjList)
h = h._ObjList__next
n += 1
assert n == 3, "при перемещении по списку через __next не все объекты перебрались"
n = 0
h = ln.tail
while h:
assert isinstance(h, ObjList)
h = h._ObjList__prev
n += 1
assert n == 3, "при перемещении по списку через __prev не все объекты перебрались"
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

144
mod_oop/3.4_07_listmath.py Normal file
View File

@@ -0,0 +1,144 @@
"""
https://stepik.org/lesson/701989/step/7?unit=702090
Объявите класс с именем ListMath, объекты которого можно создавать командами:
lst1 = ListMath() # пустой список
lst2 = ListMath([1, 2, -5, 7.68]) # список с начальными значениями
В качестве значений элементов списка объекты класса ListMath должны отбирать только целые и вещественные числа, остальные игнорировать (если указываются в списке). Например:
lst = ListMath([1, "abc", -5, 7.68, True]) # ListMath: [1, -5, 7.68]
В каждом объекте класса ListMath должен быть публичный атрибут:
lst_math - ссылка на текущий список объекта (для каждого объекта создается свой список).
Также с объектами класса ListMath должны работать следующие операторы:
lst = lst + 76 # сложение каждого числа списка с определенным числом
lst = 6.5 + lst # сложение каждого числа списка с определенным числом
lst += 76.7 # сложение каждого числа списка с определенным числом
lst = lst - 76 # вычитание из каждого числа списка определенного числа
lst = 7.0 - lst # вычитание из числа каждого числа списка
lst -= 76.3
lst = lst * 5 # умножение каждого числа списка на указанное число (в данном случае на 5)
lst = 5 * lst # умножение каждого числа списка на указанное число (в данном случае на 5)
lst *= 5.54
lst = lst / 13 # деление каждого числа списка на указанное число (в данном случае на 13)
lst = 3 / lst # деление числа на каждый элемент списка
lst /= 13.0
При использовании бинарных операторов +, -, *, / должны формироваться новые объекты класса ListMath с новыми списками, прежние списки не меняются.
При использовании операторов +=, -=, *=, /= значения должны меняться внутри списка текущего объекта (новый объект не создается).
P.S. В программе достаточно только объявить класс. На экран ничего выводить не нужно.
"""
from operator import add, sub, mul, truediv, floordiv, xor
def add_ops(*ops):
def decorator(cls):
def make_methods(op):
def method_new(self, other):
return self.__class__(self.map_op(op, other))
def method_ip(self, other):
self.lst_math = list(self.map_op(op, other))
return self
def method_r(self, other):
return self.__class__(map(lambda x: op(other, x), self.lst_math))
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, pow)
class ListMath:
def __init__(self, lst_math=None):
self.lst_math = [*filter(lambda x: type(x) in (int, float), lst_math or [])]
def __repr__(self):
return f"{self.__class__.__name__}({self.lst_math!r})"
def map_op(self, op, other):
return map(lambda x: op(x, other), self.lst_math)
def tests():
lst1 = ListMath()
lp = [1, False, 2, -5, "abc", 7]
lst2 = ListMath(lp)
lst3 = ListMath(lp)
assert id(lst2.lst_math) != id(
lst3.lst_math
), "внутри объектов класса ListMath должна создаваться копия списка"
assert lst1.lst_math == [] and lst2.lst_math == [
1,
2,
-5,
7,
], "неверные значения в списке объекта класса ListMath"
res1 = lst2 + 76
lst = ListMath([1, 2, 3])
lst += 5
assert lst.lst_math == [6, 7, 8] and res1.lst_math == [
77,
78,
71,
83,
], "неверные значения, полученные при операциях сложения"
lst = ListMath([0, 1, 2])
res3 = lst - 76
res4 = 7 - lst
assert res3.lst_math == [-76, -75, -74] and res4.lst_math == [
7,
6,
5,
], "неверные значения, полученные при операциях вычитания"
lst -= 3
assert lst.lst_math == [
-3,
-2,
-1,
], "неверные значения, полученные при операции вычитания -="
lst = ListMath([1, 2, 3])
res5 = lst * 5
res6 = 3 * lst
lst *= 4
assert res5.lst_math == [5, 10, 15] and res6.lst_math == [
3,
6,
9,
], "неверные значения, полученные при операциях умножения"
assert lst.lst_math == [
4,
8,
12,
], "неверные значения, полученные при операциях умножения"
lst = lst / 2
lst /= 13.0
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,121 @@
"""
https://stepik.org/lesson/701989/step/11?unit=702090
Объявите класс Box3D для представления прямоугольного параллелепипеда (бруска), объекты которого создаются командой:
box = Box3D(width, height, depth)
где width, height, depth - ширина, высота и глубина соответственно (числа: целые или вещественные)
В каждом объекте класса Box3D должны создаваться публичные атрибуты:
width, height, depth - ширина, высота и глубина соответственно.
С объектами класса Box3D должны выполняться следующие операторы:
box1 = Box3D(1, 2, 3)
box2 = Box3D(2, 4, 6)
box = box1 + box2 # Box3D: width=3, height=6, depth=9 (соответствующие размерности складываются)
box = box1 * 2 # Box3D: width=2, height=4, depth=6 (каждая размерность умножается на 2)
box = 3 * box2 # Box3D: width=6, height=12, depth=18
box = box2 - box1 # Box3D: width=1, height=2, depth=3 (соответствующие размерности вычитаются)
box = box1 // 2 # Box3D: width=0, height=1, depth=1 (соответствующие размерности целочисленно делятся на 2)
box = box2 % 3 # Box3D: width=2, height=1, depth=0
При каждой арифметической операции следует создавать новый объект класса Box3D с соответствующими значениями локальных атрибутов.
P.S. В программе достаточно только объявить класс Box3D. На экран ничего выводить не нужно.
"""
from operator import add, sub, mul, truediv, floordiv, mod, xor
def add_ops(*ops):
def decorator(cls):
def make_methods(op):
def method_new(self, other):
return self.__class__(*self.map_op(op, other))
def method_ip(self, other):
self.values = self.map_op(op, other)
return self
def method_r(self, other):
if isinstance(other, (int, float)):
return self.__class__(*map(lambda x: op(other, x), self.values))
return self.__class__(*map(op, other.values, self.values))
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 Box3D:
def __init__(self, width, height, depth):
self.width, self.height, self.depth = width, height, depth
@property
def values(self):
return self.width, self.height, self.depth
@values.setter
def values(self, values):
self.width, self.height, self.depth = values
def __repr__(self):
return f"{self.__class__.__name__}{self.values!r}"
def map_op(self, op, other):
if isinstance(other, (int, float)):
return map(lambda x: op(x, other), self.values)
return map(op, self.values, other.values)
def tests():
box1 = Box3D(1, 2, 3)
box2 = Box3D(2, 4, 6)
assert (
hasattr(box1, "depth") and hasattr(box1, "height") and hasattr(box1, "width")
), "ошибка в атрибутах"
box = box1 + box2
assert (
box.depth == 9 and box.height == 6 and box.width == 3
), "ошибка при операции Box3D(1, 2, 3) + Box3D(2, 4, 6)"
box1 = Box3D(1, 2, 3)
box = box1 * 2
assert (
box.depth == 6 and box.height == 4 and box.width == 2
), "ошибка при операции box1 * 2 (каждая размерность умножается на 2)"
box = 3 * box2
assert (
box.depth == 18 and box.height == 12 and box.width == 6
), "ошибка при операции 3 * box2"
box = box2 - box1
assert (
box.depth == 3 and box.height == 2 and box.width == 1
), "ошибка при операции box2 - box1"
box = box1 // 2
assert (
box.depth == 1 and box.height == 1 and box.width == 0
), "ошибка при операции box1 // 2"
box = box2 % 3
assert (
box.depth == 0 and box.height == 1 and box.width == 2
), "ошибка при операции box2 % 3"
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,128 @@
"""
https://stepik.org/lesson/701989/step/12?unit=702090
В нейронных сетях использую операцию под названием Max Pooling.
Суть ее состоит в сканировании прямоугольной таблицы чисел (матрицы)
окном определенного размера (обычно, 2x2 элемента) и выбора наибольшего значения в пределах этого окна.
Или, если окна выходят за пределы матрицы, то они пропускаются (игнорируются).
Мы повторим эту процедуру. Для этого в программе нужно объявить класс с именем MaxPooling, объекты которого создаются командой:
mp = MaxPooling(step=(2, 2), size=(2,2))
где step - шаг смещения окна по горизонтали и вертикали; size - размер окна по горизонтали и вертикали.
Параметры step и size по умолчанию должны принимать кортеж со значениями (2, 2).
Для выполнения операции Max Pooling используется команда:
res = mp(matrix)
где matrix - прямоугольная таблица чисел; res - ссылка на результат обработки таблицы matrix (должна создаваться новая таблица чисел.
Прямоугольную таблицу чисел следует описывать вложенными списками. Если при сканировании таблицы часть окна выходит за ее пределы, то эти данные отбрасывать (не учитывать все окно).
Если matrix не является прямоугольной таблицей или содержит хотя бы одно не числовое значение, то должно генерироваться исключение командой:
raise ValueError("Неверный формат для первого параметра matrix.")
Пример использования класса (эти строчки в программе писать не нужно):
mp = MaxPooling(step=(2, 2), size=(2,2))
res = mp([[1, 2, 3, 4], [5, 6, 7, 8], [9, 8, 7, 6], [5, 4, 3, 2]]) # [[6, 8], [9, 7]]
Результатом будет таблица чисел:
6 8
9 7
P.S. В программе достаточно объявить только класс. Выводить на экран ничего не нужно.
"""
class MaxPooling:
def __init__(self, step=(2, 2), size=(2, 2)):
self.step = step
self.size = size
def validate_matrix(self, matrix):
if (
any(not isinstance(row, list) for row in matrix)
or any(not isinstance(num, (int, float)) for row in matrix for num in row)
or len(set(len(row) for row in matrix)) != 1
):
raise ValueError("Неверный формат для первого параметра matrix.")
def __call__(self, matrix):
self.validate_matrix(matrix)
N, M = len(matrix), len(matrix[0])
step_x, step_y, size_x, size_y = (*self.step, *self.size)
return [
[
max(
matrix[x][y]
for x in range(i, i + size_x)
for y in range(j, j + size_y)
)
for j in range(0, M - size_y + 1, step_y)
]
for i in range(0, N - size_x + 1, step_x)
]
def tests():
mp = MaxPooling(step=(2, 2), size=(2, 2))
m1 = [[1, 10, 10], [5, 10, 0], [0, 1, 2]]
m2 = [[1, 10, 10, 12], [5, 10, 0, -5], [0, 1, 2, 300], [40, -100, 0, 54.5]]
res1 = mp(m1)
res2 = mp(m2)
assert res1 == [[10]], "неверный результат операции MaxPooling"
assert res2 == [[10, 12], [40, 300]], "неверный результат операции MaxPooling"
mp = MaxPooling(step=(3, 3), size=(2, 2))
m3 = [[1, 12, 14, 12], [5, 10, 0, -5], [0, 1, 2, 300], [40, -100, 0, 54.5]]
res3 = mp(m3)
assert res3 == [
[12]
], "неверный результат операции при MaxPooling(step=(3, 3), size=(2,2))"
try:
res = mp([[1, 2], [3, 4, 5]])
except ValueError:
assert True
else:
assert (
False
), "некорректо отработала проверка (или она отсутствует) на не прямоугольную матрицу"
try:
res = mp([[1, 2], [3, "4"]])
except ValueError:
assert True
else:
assert (
False
), "некорректо отработала проверка (или она отсутствует) на не числовые значения в матрице"
mp = MaxPooling(step=(1, 1), size=(5, 5))
res = mp(
[
[5, 0, 88, 2, 7, 65],
[1, 33, 7, 45, 0, 1],
[54, 8, 2, 38, 22, 7],
[73, 23, 6, 1, 15, 0],
[4, 12, 9, 1, 76, 6],
[0, 15, 10, 8, 11, 78],
]
) # [[88, 88], [76, 78]]
assert res == [
[88, 88],
[76, 78],
], "неверный результат операции MaxPooling(step=(1, 1), size=(5, 5))"
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

132
mod_oop/3.5_05_track.py Normal file
View File

@@ -0,0 +1,132 @@
"""
https://stepik.org/lesson/701990/step/5?unit=702091
Объявите класс Track (маршрут), объекты которого создаются командой:
track = Track(start_x, start_y)
где start_x, start_y - координаты начала маршрута (целые или вещественные числа).
>>> track = Track(0, 0)
Каждый линейный сегмент маршрута определяется классом TrackLine, объекты которого создаются командой:
line = TrackLine(to_x, to_y, max_speed)
где to_x, to_y - координаты следующей точки маршрута (целые или вещественные числа); max_speed - максимальная скорость на данном участке (целое число).
>>> line = TrackLine(1, 2, 3)
Для формирования и работы с маршрутом в классе Track должны быть объявлены следующие методы:
add_track(self, tr) - добавление линейного сегмента маршрута (следующей точки);
get_tracks(self) - получение кортежа из объектов класса TrackLine.
>>> {m in dir(Track) for m in "add_track get_tracks".split()}
{True}
Также для объектов класса Track должны быть реализованные следующие операции сравнения:
track1 == track2 # маршруты равны, если равны их длины
track1 != track2 # маршруты не равны, если не равны их длины
track1 > track2 # True, если длина пути для track1 больше, чем для track2
track1 < track2 # True, если длина пути для track1 меньше, чем для track2
>>> track11 = Track(0, 0)
>>> track12 = Track(0, 0, [TrackLine(2, 2, 3)])
>>> track13 = Track(1, 1, [TrackLine(1, 1, 4)])
>>> track11 == track12
False
>>> track13 != track12
True
>>> track11 == track13
True
>>> track11 < track12
True
>>> track12 > track13
True
И функция:
n = len(track) # возвращает целочисленную длину маршрута (привести к типу int) для объекта track
Создайте два маршрута track1 и track2 с координатами:
>>> len(track11)
0
>>> len(track12)
2
1-й маршрут: (0; 0), (2; 4), (5; -4) и max_speed = 100
2-й маршрут: (0; 1), (3; 2), (10; 8) и max_speed = 90
Сравните их между собой на равенство. Результат сравнения сохраните в переменной res_eq.
>>> len(track1)
13
>>> len(track2)
12
>>> res_eq
False
P.S. На экран в программе ничего выводить не нужно.
"""
from functools import total_ordering
from math import hypot
from operator import sub
class TrackLine:
def __init__(self, to_x, to_y, max_speed):
self.to_x, self.to_y, self.max_speed = to_x, to_y, max_speed
def __repr__(self):
return f"{self.__class__.__name__}{(self.to_x, self.to_y, self.max_speed)!r}"
def __rsub__(self, other):
return self - other
def __sub__(self, other):
if isinstance(other, self.__class__):
return self - (other.to_x, other.to_y)
if isinstance(other, tuple):
a, b, c, d = (self.to_x, self.to_y, *other)
return hypot(c - a, d - b)
return NotImplemented
@total_ordering
class Track:
def __init__(self, start_x, start_y, lines=None):
self.start_x, self.start_y, self.lines = start_x, start_y, lines or []
def __repr__(self):
return f"{self.__class__.__name__}{(self.start_x, self.start_y, self.lines)!r}"
def __len__(self):
left = [(self.start_x, self.start_y), *self.lines[:-1]]
return int(sum(map(sub, left, self.lines)))
def __eq__(self, other):
if isinstance(other, self.__class__):
return len(self) == len(other)
return NotImplemented
def __lt__(self, other):
if isinstance(other, self.__class__):
return len(self) < len(other)
return NotImplemented
def add_track(self, tr):
self.lines.append(tr)
def get_tracks(self):
return tuple(self.lines)
ma, mb = 100, 90
track1 = Track(0, 0, [TrackLine(2, 4, ma), TrackLine(5, -4, ma)])
track2 = Track(0, 1, [TrackLine(3, 2, mb), TrackLine(10, 8, mb)])
res_eq = track1 == track2
def tests():
...
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

View File

@@ -0,0 +1,186 @@
"""
https://stepik.org/lesson/701990/step/6?unit=702091
Объявите класс Dimensions (габариты) с атрибутами:
MIN_DIMENSION = 10
MAX_DIMENSION = 10000
>>> Dimensions.MIN_DIMENSION, Dimensions.MAX_DIMENSION
(10, 10000)
Каждый объект класса Dimensions должен создаваться командой:
d3 = Dimensions(a, b, c) # a, b, c - габаритные размеры
Значения a, b, c должны сохраняться в локальных приватных атрибутах __a, __b, __c объектах этого класса.
>>> d3 = Dimensions(10, 20, 30)
>>> d3._Dimensions__a, d3._Dimensions__b, d3._Dimensions__c
(10, 20, 30)
Для изменения и доступа к приватным атрибутам в классе Dimensions должны быть объявлены объекты-свойства (property) с именами: a, b, c.
Причем, в момент присваивания нового значения должна выполняться проверка попадания числа в диапазон [MIN_DIMENSION; MAX_DIMENSION].
Если число не попадает, то оно игнорируется и существующее значение не меняется.
>>> d3.a, d3.b, d3.c
(10, 20, 30)
>>> d3.a = 9; d3.a
10
С объектами класса Dimensions должны выполняться следующие операторы сравнения:
dim1 >= dim2 # True, если объем dim1 больше или равен объему dim2
dim1 > dim2 # True, если объем dim1 больше объема dim2
dim1 <= dim2 # True, если объем dim1 меньше или равен объему dim2
dim1 < dim2 # True, если объем dim1 меньше объема dim2
>>> dim1, dim2, dim3 = Dimensions(10, 20, 30), Dimensions(20, 15, 20), Dimensions(10, 15, 35)
>>> dim1 > dim3, dim1 == dim2, dim2 != dim3, dim3 < dim2, dim2 >= dim3
(True, True, True, True, True)
Объявите в программе еще один класс с именем ShopItem (товар), объекты которого создаются командой:
item = ShopItem(name, price, dim)
где name - название товара (строка); price - цена товара (целое или вещественное число); dim - габариты товара (объект класса Dimensions).
>>> item = ShopItem('Кеды', 1024, Dimensions(40, 30, 120))
В каждом объекте класса ShopItem должны создаваться локальные атрибуты:
name - название товара;
price - цена товара;
dim - габариты товара (объект класса Dimensions).
>>> item.name, item.price, item.dim
('Кеды', 1024, Dimensions(40, 30, 120))
Создайте список с именем lst_shop из четырех товаров со следующими данными:
- кеды; 1024; (40, 30, 120)
- зонт; 500.24; (10, 20, 50)
- холодильник; 40000; (2000, 600, 500)
- табуретка; 2000.99; (500, 200, 200)
>>> lst_shop[:2]
[ShopItem('кеды', 1024, Dimensions(40, 30, 120)), ShopItem('зонт', 500.24, Dimensions(10, 20, 50))]
>>> lst_shop[2:]
[ShopItem('холодильник', 40000, Dimensions(2000, 600, 500)), ShopItem('табуретка', 2000.99, Dimensions(500, 200, 200))]
Сформируйте новый список lst_shop_sorted с упорядоченными по возрастанию объема (габаритов) товаров списка lst_shop,
используя стандартную функцию sorted() языка Python и ее параметр key для настройки сортировки.
Прежний список lst_shop должен оставаться без изменений.
>>> lst_shop_sorted[:2]
[ShopItem('зонт', 500.24, Dimensions(10, 20, 50)), ShopItem('кеды', 1024, Dimensions(40, 30, 120))]
>>> lst_shop_sorted[2:]
[ShopItem('табуретка', 2000.99, Dimensions(500, 200, 200)), ShopItem('холодильник', 40000, Dimensions(2000, 600, 500))]
P.S. На экран в программе ничего выводить не нужно.
"""
from functools import total_ordering
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
@total_ordering
@make_properties(*"abc")
class Dimensions:
MIN_DIMENSION = 10
MAX_DIMENSION = 10000
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def __setattr__(self, name, value):
if name in ["MIN_DIMENSION", "MAX_DIMENSION"]:
raise AttributeError(
"Менять атрибуты MIN_DIMENSION и MAX_DIMENSION запрещено."
)
if not self.MIN_DIMENSION <= value <= self.MAX_DIMENSION:
return
super().__setattr__(name, value)
@property
def volume(self):
return self.a * self.b * self.c
def __len__(self):
return self.volume
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.volume == other.volume
return NotImplemented
def __lt__(self, other):
if isinstance(other, self.__class__):
return self.volume < other.volume
return NotImplemented
def __repr__(self):
return f"{self.__class__.__name__}{(self.a, self.b, self.c)!r}"
@total_ordering
class ShopItem:
def __init__(self, name, price, dim):
self.name, self.price, self.dim = name, price, dim
def __repr__(self):
return f"{self.__class__.__name__}{(self.name, self.price, self.dim)!r}"
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.dim == other.dim
return NotImplemented
def __lt__(self, other):
if isinstance(other, self.__class__):
return self.dim < other.dim
return NotImplemented
data = [
("кеды", 1024, (40, 30, 120)),
("зонт", 500.24, (10, 20, 50)),
("холодильник", 40000, (2000, 600, 500)),
("табуретка", 2000.99, (500, 200, 200)),
]
lst_shop = [ShopItem(name, price, Dimensions(*dim)) for name, price, dim in data]
lst_shop_sorted = sorted(lst_shop)
def tests():
from base64 import b85decode
b = (
b"VRLh3a&#bUWo{^Jb97&GXm4;SAU!=GG%O$@(TC8u(Sgvr(7qtijnKQ$wa~oKwa~rMg3!LuvLMj1AZ&AVUvp@0a3IjV"
+ b"(6u1ZfY7keve3QIz92Lr3JPp<bYF9DAUz;kT?%Y-bYF9DE@5zRWo~3BQ)q8+NpxjxC@0Xm(6!LC(TgW6ATcm9G%O%QX>Db"
+ b"0b7^mGb0{=0EFd#5EFdv5Fexc13T$(9UvqFSVQ_F|Ze%D^Xm4;ybY*QQC(yUhzR<nVf+s8>H83zPGBhk8L}_hhZgXjGZgV"
+ b"IxFf1T4Ff1T7Fexc13T$(9UvqFSVQ_F|Ze%D^Xm4;ybY*QQC((t_zR<hSzR<MLxX`=NjL^N%xX`*MEFd&6FfcGIAVg_xWo"
+ b"~n6Z*FraGB7YOEFd;8Ff1T7Ffb`8DGF?JbYF9DE@5zRWo~3BQ)q8+NpxjxC@0Z^(6G?4(Sy-|(6!Nm(7MpDCoCW`FfcGKI"
+ b"XNsKL}_hhZgXjGZgVI#Ffc42GB7YKATls8DJdxm3T$(9UvqF@b8m8VWn>^dAX_KUx6r=Oz0ravEFdS)y3n=Iw9$(vEFdS*"
+ b"g3z$gvC)IkfY7zkg3!9quqP}aC((t_zR<hSzR<MLxX`=NjL^N%xX`*MT?%s`Js?|nE^c9MWgup6av*phX>K5Fb97&GXm4;"
+ b"|b8m8VWn^6nVRLh3a&#bUb97&Ga9?w8a&%>6AU!=Gb1Wbt(Sgvv(74fo(7w>RAZ&AVUvp@0a9?w8a&%>6Akl%*gwVdxfY7"
+ b"|qxY2;nzR<GJu+Y6A(7n*L(6Z3A(SXps(7qxH3S==LJs?DBZDnqAX>V?GC^RrEATuy5ATcs9DGFpVAUz;NX>Db0b7^mGb0"
+ b"{=0EFd#5EFdv5FewUTGax-6L}_hhZgXjGZgVIzFf1T4Ff1T3Ffb_!VRLh3a&#bMF(5oWAY?KuAR^Gc(6!LA(6!Nk(7n*UA"
+ b"ke<ig3*A`u+Xv4zR`lvu+Y08(7w>W(6!Nk(6G^h(7w@tAUr)H3So0|WpZ>NWHTT<Js@N<EFdD#z0kGLve32BfY80rz97)P"
+ b"(Sp%{(6G?4(7w@v(6G?EAke<hztFYOfY7kfg3!LvfFL|QA_`%1b7gXLAY?NjJRoE;EFdD#z0kGLve32BfY80rz97)P(Sp%"
+ b"{(6G?4(7w@v(6G?EAke<hztFYOfY7kfg3!LvfFL{~3JPR0E@2=&ATclsWHK&dAUz;4FbZTcE@L1)ATclsVRLh3a&#bMG9W"
+ b"x4WHBrtBGA3iwa~KAwb6jkz0kfO(7w@v(SXpf(6P|I(Sp#h(7PbezR<tWwb6jku+f6hzR`dnJRs1&(7w@u(7VvJAketbx6"
+ b"r)Mwa~rLwa~rLxY3Uw(7w>I(TdQu(7e#FAkl}=wb6jkwa~X9(7w>I(TdQu(7Mrr(Tgq7fzYzhzR<bRfzg7{ve2*~VJskGE"
+ b"Ffbd3I"
)
exec(b85decode(b))
if __name__ == "__main__":
import doctest
doctest.testmod()
tests()

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

93
mod_oop/decision_tree.py Normal file
View File

@@ -0,0 +1,93 @@
# https://stepik.org/lesson/701984/step/10?unit=702085
class DecisionTree:
@classmethod
def predict(cls, root, x):
"""для построения прогноза (прохода по решающему дереву) для вектора x из корневого узла дерева root"""
if not root.is_leaf:
return cls.predict(root.left if x[root.indx] else root.right, x)
return root.value
@classmethod
def add_obj(cls, obj, node=None, left=True):
"""для добавления вершин в решающее дерево (метод должен возвращать добавленную вершину - объект класса TreeObj);
В методе add_obj параметры имеют, следующие значения:
obj - ссылка на новый (добавляемый) объект решающего дерева (объект класса TreeObj);
node - ссылка на объект дерева, к которому присоединяется вершина obj;
left - флаг, определяющий ветвь дерева (объекта node), к которой присоединяется объект obj (True - к левой ветви; False - к правой).
"""
if node:
if left:
node.left = obj
else:
node.right = obj
return obj
class TreeObj:
def __init__(self, indx, value=None):
"""indx - проверяемый индекс (целое число);
value - значение с данными (строка);
__left - ссылка на следующий объект дерева по левой ветви (изначально None);
__right - ссылка на следующий объект дерева по правой ветви (изначально None).
"""
self.indx, self.value, self.__left, self.__right = indx, value, None, None
@property
def left(self):
return self.__left
@property
def is_leaf(self):
return self.__left is None and self.__right is None
@left.setter
def left(self, left):
self.value = None
self.__left = left
@property
def right(self):
return self.__right
@right.setter
def right(self, right):
self.value = None
self.__right = right
# --------------------------
def use():
root = DecisionTree.add_obj(TreeObj(0))
v_11 = DecisionTree.add_obj(TreeObj(1), root)
v_12 = DecisionTree.add_obj(TreeObj(2), root, False)
DecisionTree.add_obj(TreeObj(-1, "будет программистом"), v_11)
DecisionTree.add_obj(TreeObj(-1, "будет кодером"), v_11, False)
DecisionTree.add_obj(TreeObj(-1, "не все потеряно"), v_12)
DecisionTree.add_obj(TreeObj(-1, "безнадежен"), v_12, False)
x = [1, 1, 0]
res = DecisionTree.predict(root, x) # будет программистом
print(res)
assert res == "будет программистом", "будет программистом же?"
use()
# --------------------------
import marshal
import base64
import types
# print(base64.b85encode(marshal.dumps(tests1.__code__)))
testsb = b";{X5v000000000000sa600RI30P|`C005T&bO8VW000000049X0000000000WC5!J000000043jbO8VW000000049X0000000000WCE)K000000046bN&sX7s{jB100000f&p{|0000000000bOrzb00000003$T0000000000000000000000652000000049f0000000000YbXE!av*dC0000000000bOrzb00000003$X0000000000000000000000652000000049f0000000000YbXE!a|cQQWCW`K00000004pkbOHbX00000003$a000000000000000000000049c0000000000WCg1M000000065200000004aebOHbX00000003$a000000000000000000000049c0000000000WCp7N00000004Xds{#N300000eF1a=0000000000Y7YPa00000000000000000000bO!(c00000003kMs{sH200000d;nw!s{;T400000eFAg>0000000000Y7YPa00000000000000000000bO!(c00000003kOWD2VS00000004Xes{#N3000000RVIY0000000000Y7YPa00000000000000000000bO!(c00000003kOWDBbT00000004XeWC*JR0000000031bOHbX00000003$a000000000000000000000049c0000000000WC>&ps{#N300000d;+Ti0000000031bOHbX00000003$a000000000000000000000049c0000000000WC>&qs{#N300000d;(+$s{;T4000000RVIY0000000000Y7qbc00000000000000000000d;n(vWDcSMs{#N300000WD0920046bN&sXJs{jB100000f&p{_0000000000Y7qbc00000000000000000000d;n(vWDueOs{#N300000WDRR50046bN&sXJs{jB100000f&p{_0000000000Y7qbc00000000000000000000d;n(vWD%kPs{#N300000WDIL40046bN&sXJs{jB100000f&qB|DH2ZF2VrDnUvFY++6QoQWn^h%boEjI007XkAkezdyU?)Ffzg4`wID=gV`+0~Z*Ej_Wo01Hw9vlLyU@1Kz0r#x(6P~r(Sp&8Ake(fwb6pmzR<MMiy&cSWM6M$Y9P?KAaHVJWNBk`^;-Y{0MN1^(7MpO(6G^g(SgvlAXIW?Wlv&iAkehXzR<hSw$Q!Niy+Xk(TmZ7(TpI_zR<DJiqN&ty3vBsi!ITC(6Z3J(7Dlp(Sp#j(6AtEWoC3B(6}ISX=iA3=>Px#0O<h$008L%0000+>Hq)#|MeCC007Xx(SXpt(6iBi(6G?F(7e#N(Sgx|^$Gw00MNS7zR<MLwb6j}5&!@I(7({W(Sgvs(7w@v(SXpn(7g2q0002cz0kGMf++)X2LJ#7at8na0CENZ008wn0002cz0kGLve32BfY80si_p0s(6Z5s(6Z3J(6k`YfY7zkh|sXnj?szGwa~NBz97)F(6!Nk(6!LA(6A{3as~hZ0CEQa0043Z0000f19Aoc0043a0001T2LJ#7DGAyKXkl|<baZms3`AvPX>)0BZd7t*W!eOEd2nUg2UK!pWlv&i+5~K6W^~#JaB^>OWpZ?R+68iHXJ~YC0ssI2asvPW04W371afb0blL=VUokP-1a@CBGIIj}001B$Ao?^!I$UI7bYWa<VPafxd0cd5b960ZZ*_8WWpi9@Z)9I@Z*W{>Wn*b`X>V>{baG{7E^v9;1$1R|baQeV000090001Ub^!nYfB@VJ5|lik8Pp3av<KV>6p(530|GGy@Bjh@Tmj$*Tl5721zZ922mlEUj8~6biCohN8k}g-2qvsFv<J5hu?^EJKEx=}D@yPK0tICO;0I*%1p)<S0rUs}3>eo7AJGdRqz9-0qYBdtD}Y4Y3m4Z5AJGdRqz9-0qY9`*(+e?xMBED(*9#xf3m>Ehr~#u2s6?<O(+fC&MBD@r*8~{R1Q?tbpc2p}J<}OJzy#C;Mcf1s*8~{R1Q?tbpc0^5(-|_bM7Sl?1VG#b5Z444(F7Qr7oZZ*B{|a>I=DpC1VP*c5Z444(F7Qr7oZZLSkoCUumrGW)C4^A1^@{a*9aWZ2ppJHqB+wDEzmJZ(g;cL0|Es_0pJHj^aTP1MFI2(00|Y>2prJ}9GFz1InxL&uo%(^Iq(Al1w{ei2SoG*0tH0@^aub671szH(Fh!vRH8Z42rbYtMbZdG@B;z`MFHRkMDzs$1w{ea2u1S%0000"
tests = types.FunctionType(marshal.loads(base64.b85decode(testsb)), globals(), "tests")
tests()

72
mod_oop/rvector2d.py Normal file
View File

@@ -0,0 +1,72 @@
class RadiusVector2D:
MIN_COORD = -100
MAX_COORD = 1024
def __init__(self, x=0, y=0):
self.__x, self.__y = 0, 0
self.x, self.y = x, y
def check_value(func):
def check(cls, value) -> bool:
return (
isinstance(value, (int, float))
and cls.MIN_COORD <= value <= cls.MAX_COORD
)
def decorator(func):
def wrapper(self, value):
if check(self.__class__, value):
return func(self, value)
return wrapper
return decorator(func)
@property
def x(self):
return self.__x
@x.setter
@check_value
def x(self, value):
self.__x = value
@property
def y(self):
return self.__y
@y.setter
@check_value
def y(self, value):
self.__y = value
def norm2(self):
return self.x**2 + self.y**2
r1 = RadiusVector2D()
r2 = RadiusVector2D(1)
r3 = RadiusVector2D(4, 5)
assert hasattr(RadiusVector2D, "MIN_COORD") and hasattr(
RadiusVector2D, "MAX_COORD"
), "в классе RadiusVector2D должны присутствовать атрибуты MIN_COORD и MAX_COORD"
assert (
type(RadiusVector2D.x) == property and type(RadiusVector2D.y) == property
), "в классе RadiusVector2D должны присутствовать объекты-свойства x и y"
assert (
r1.x == 0 and r1.y == 0 and r2.x == 1 and r2.y == 0 and r3.x == 4 and r3.y == 5
), "свойства x и y возвращают неверные значения"
assert RadiusVector2D.norm2(r3) == 41, "неверно вычисляется норма вектора"
r4 = RadiusVector2D(4.5, 5.5)
assert (
4.4 < r4.x < 4.6 and 5.4 < r4.y < 5.6
), "свойства x и y возвращают неверные значения"
r5 = RadiusVector2D(-102, 2000)
assert (
r5.x == 0 and r5.y == 0
), "присвоение значений, выходящих за диапазон [-100; 1024] не должно выполняться"
r = RadiusVector2D(10, 20)
r.x = "a"
r.y = (1, 2)
assert r.x == 10 and r.y == 20, "присвоение не числовых значений должно игнорироваться"

109
mod_oop/shit_stack.py Normal file
View File

@@ -0,0 +1,109 @@
class Stack:
def __init__(self):
self.top = None
@property
def bottom(self):
curr, last = self.top, None
while curr:
curr, last = curr.next, curr
return last
def push(self, obj):
if not self.top:
self.top = obj
else:
self.bottom.next = obj
def pop(self):
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 c == self.top:
self.top = None
if self.top in [b, c]:
self.top = None
return b
def get_data(self):
result = []
obj = self.top
while obj:
result.append(obj.data)
obj = obj.next
return result
def __str__(self):
return " -> ".join(self.get_data())
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})"
s = Stack()
top = StackObj("obj_1")
s.push(top)
s.push(StackObj("obj_2"))
s.push(StackObj("obj_3"))
s.pop()
res = s.get_data()
assert res == ["obj_1", "obj_2"], f"метод get_data вернул неверные данные: {res}"
assert s.top == top, "атрибут top объекта класса Stack содержит неверное значение"
h = s.top
while h:
res = h.data
h = h.next
s = Stack()
top = StackObj("obj_1")
s.push(top)
s.pop()
assert s.get_data() == [], f"метод get_data вернул неверные данные: {s.get_data()}"
n = 0
h = s.top
while h:
h = h.next
n += 1
assert n == 0, "при удалении всех объектов, стек-подобная стурктура оказалась не пустой"
s = Stack()
top = StackObj("name_1")
s.push(top)
obj = s.pop()
assert obj == top, "метод pop() должен возвращать удаляемый объект"

View File

@@ -1,11 +1,19 @@
"""
3.5 Вставка рисунков. Управление рисунков
https://stepik.org/lesson/937437/step/2?unit=943412
и
https://stepik.org/lesson/937437/step/3?unit=943412
Задача:
Попробуйте сделать управление объектом, который есть не просто прямоугольник, а, например, рисунок.
и
Сделайте программу, чтобы при движении влево было одно изображение, а при движении вправо другое изображение героя.
Решение: призрак в лабиринте, управление стрелками и WASD, выход Esc или Q.
Решение:
Игра "Призрачный лабиринт: сокровища небесного замка".
Призрак в лабиринте, управление стрелками и WASD, выход Esc или Q.
Чтобы пройти уровень, нужно дойти до выхода, желательно собрав все монетки.
После прохождения уровня можно начать новый или выйти из игры.
Картинки берутся из папки assets, если их нет то автоматически скачиваются из репозитория.
Если скачать не получилось то будут нарисованы заглушки.
@@ -134,21 +142,6 @@ class Coords(NamedTuple):
return cls(0, 0)
def choose_plural(amount, declensions):
"""Возвращает количество объектов в виде строки
например 5 копеек, 1 копейка и т.д.
"""
a = amount % 100
i = 2
if 10 < a < 20:
pass
elif a % 10 == 1:
i = 0
elif 1 < a % 10 < 5:
i = 1
return f"{amount} {declensions[i]}"
def maze_gen(row=4, col=4):
"""генератор карты
взял с коментария

View File

@@ -0,0 +1,859 @@
"""
3.5 Вставка рисунков. Управление рисунков
https://stepik.org/lesson/937437/step/2?unit=943412
и
https://stepik.org/lesson/937437/step/3?unit=943412
Задача:
Попробуйте сделать управление объектом, который есть не просто прямоугольник, а, например, рисунок.
и
Сделайте программу, чтобы при движении влево было одно изображение, а при движении вправо другое изображение героя.
Решение:
Игра "Призрачный лабиринт: сокровища небесного замка".
Призрак в лабиринте, управление стрелками и WASD, выход Esc или Q.
Чтобы пройти уровень, нужно дойти до выхода, желательно собрав все монетки.
После прохождения уровня можно начать новый или выйти из игры.
Картинки берутся из папки assets, если их нет то автоматически скачиваются из репозитория.
Если скачать не получилось то будут нарисованы заглушки.
Зависимости: pygame
"""
import os
import tempfile
import urllib.request
from abc import ABC, abstractmethod
from contextlib import contextmanager
from enum import Enum
from random import choice, randrange, sample
from typing import NamedTuple, Optional
import pygame
FONT_NAME = "Arial"
FPS = 30
def download_asset(asset, path):
"""
Загрузка картинок из репозитория
"""
prefix = "https://gitea.b4tman.ru/temp/py_stepik/raw/branch/master/assets/"
print("Качаю картинку", asset, end=" ... ", flush=True)
try:
urllib.request.urlretrieve(prefix + asset, path)
except Exception:
print("не смог :(")
return False
print("скачал!")
return True
def make_stub_image(path, name):
"""Создание пустой картинки, на случай если скачать не получилось"""
img = pygame.surface.Surface((200, 200), flags=pygame.SRCALPHA)
img.fill((255, 255, 255, 0))
pygame.draw.line(img, "#ff000065", (5, 5), (195, 195), 2)
pygame.draw.line(img, "#ff000065", (195, 5), (5, 195), 2)
rect = pygame.Rect(5, 5, 190, 190)
pygame.draw.rect(img, "black", rect, 3)
font = pygame.font.SysFont(FONT_NAME, 44)
text1 = font.render(name, True, "blue")
text1_rect = text1.get_rect()
text1_rect.center = img.get_rect().center
img.blit(text1, text1_rect)
pygame.image.save(img, path)
@contextmanager
def get_assets(names):
"""Получение соответствия с расположением файлов картинок"""
assets_dir = "assets"
files = {}
tempfiles = []
for asset in names:
# поиск файлов
filepath = os.path.join(assets_dir, asset)
if os.path.isfile(filepath):
files[asset] = filepath
continue
# создание временного файла
_, ext = os.path.splitext(asset)
fd, temppath = tempfile.mkstemp(suffix=ext)
os.close(fd)
tempfiles.append(temppath)
# попытка загрузки, если не получилось то создание заглушки
if not download_asset(asset, temppath):
make_stub_image(temppath, asset)
files[asset] = temppath
# передача управления
yield files
# очистка
del files
for filename in tempfiles:
try:
os.remove(filename)
except FileNotFoundError:
pass
class Coords(NamedTuple):
"""
Вспомогательный класс для упрощения работы с координатами
"""
x: int | float
y: int | float
def __add__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x + other.x, self.y + other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x + other, self.y + other)
return NotImplemented
def __sub__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x - other.x, self.y - other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x - other, self.y - other)
return NotImplemented
def __floordiv__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x // other.x, self.y // other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x // other, self.y // other)
return NotImplemented
def __mul__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x * other.x, self.y * other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x * other, self.y * other)
return NotImplemented
def transform(self, ref: "Coords"):
return self * ref
def dir_norm(self):
"""нормализация вектора, но только для получения направления
x, y могут быть только -1, 0, 1
может быть только направление по горизонтали либо по вертикали
либо без направления
"""
src = self
if self.x and self.y:
src = (
self.__class__(self.x, 0)
if abs(self.x) > abs(self.y)
else self.__class__(0, self.y)
)
return self.__class__(*((n > 0) - (n < 0) for n in src))
@classmethod
def zero(cls):
return cls(0, 0)
def maze_gen(row=4, col=4):
"""генератор карты
взял с коментария
https://stepik.org/lesson/502494/step/3?discussion=6527620&unit=494196
"""
row = max(2 * row + 1, 3)
col = max(2 * col + 1, 3)
maze = [[2] * col]
maze.extend([[2] + [1] * (col - 2) + [2] for _ in range(row - 2)])
maze.append(maze[0])
curr = (randrange(1, len(maze) - 1, 2), randrange(1, len(maze[0]) - 1, 2))
path = [curr]
maze[curr[0]][curr[1]] = 0
while path:
nexts = [
(r1, c1, r2, c2)
for r, c in zip((1, 0, -1, 0), (0, 1, 0, -1))
if (
(r1 := curr[0] + r) is None
or (c1 := curr[1] + c) is None
or (r2 := curr[0] + 2 * r) is None
or (c2 := curr[1] + 2 * c) is None
or 1 == maze[r1][c1] == maze[r2][c2]
)
]
if nexts:
r1, c1, r2, c2 = choice(nexts)
maze[r1][c1] = maze[r2][c2] = 0
path.append((r2, c2))
else:
curr = path.pop()
upd = {
("22", "20"): (None, "00"),
("02", "22"): ("00", None),
("11101", "00101", "11111", "10100", "10111"): (
None,
None,
None,
"10001",
None,
),
("10111", "10100", "11111", "00101", "11101"): (
None,
"10001",
None,
None,
None,
),
}
for pattern, replacement in upd.items():
for i in range(len(maze) - len(pattern) + 1):
for j in range(len(maze[0]) - len(pattern[0]) + 1):
if all(
maze[i + k][j : j + len(v)] == list(map(int, v))
for k, v in enumerate(pattern)
):
for k, v in filter(lambda x: x[1], enumerate(replacement)):
maze[i + k][j : j + len(v)] = list(map(int, v))
return maze
def get_maze_sz(maze: list[list[int]]) -> Coords:
return Coords(len(maze[0]), len(maze))
class Direction(Enum):
LEFT = 1
RIGHT = 2
UP = 3
DOWN = 4
def as_coords(self):
match self:
case Direction.LEFT:
return Coords(-1, 0)
case Direction.RIGHT:
return Coords(1, 0)
case Direction.UP:
return Coords(0, -1)
case Direction.DOWN:
return Coords(0, 1)
@staticmethod
def from_coords(coords: Coords) -> Optional["Direction"]:
match coords.dir_norm():
case Coords(-1, 0):
return Direction.LEFT
case Coords(1, 0):
return Direction.RIGHT
case Coords(0, -1):
return Direction.UP
case Coords(0, 1):
return Direction.DOWN
class SurfaceWithRect(NamedTuple):
surface: pygame.Surface
rect: pygame.Rect
def draw_to(self, target: pygame.Surface):
target.blit(self.surface, self.rect)
class DrawableGameObject(ABC):
"""обобщение игрового элемента"""
coords = property(
lambda self: self.get_coords(), lambda self, c: self.set_coords(c)
)
def __init__(
self,
coords: Coords,
parent: Optional["DrawableGameObject"] = None,
assets: dict | None = None,
):
self.parent = parent
self.rect = pygame.Rect(coords, coords)
self.assets = assets or (parent.assets if parent else None)
self._surface = None
self._mask = None
def __str__(self):
return f"{self.__class__.__name__}({self.coords})"
@property
def surface(self) -> pygame.Surface | None:
return self._surface or (self.parent.surface if self.parent else None)
@property
def mask(self) -> pygame.Mask | None:
if not self._mask:
self._mask = pygame.mask.from_surface(self.surface.convert_alpha())
return self._mask
def overlap(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
if not self.rect.colliderect(rect):
return False
offset = Coords(*self.rect.topleft) - Coords(*rect.topleft)
overlap = mask.overlap(self.mask, offset)
return overlap is not None
@property
def scene(self):
return self.parent.scene if self.parent else self
def get_coords(self) -> Coords:
return Coords(*self.rect.topleft)
def set_coords(self, coords: Coords):
new_rect = self.rect.copy()
new_rect.topleft = coords
if self.parent:
if self.parent.rect:
if not self.parent.rect.contains(new_rect):
return
self.rect = new_rect
@abstractmethod
def draw(self):
pass
class EventHandler(ABC):
@abstractmethod
def handle_event(self):
pass
class Hero(DrawableGameObject, EventHandler):
"""объект главного героя"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["ghost.png"])
self.rect = self.surface.get_rect()
sf = Coords(0.8, 0.8)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
self.rect.topleft = coords
self.active = True
self.looking_right = False
self._speed = 1
self.direction = Direction.RIGHT
self.mouse_active = False
# картинка изначально влево, а надо бы начинать со взгляда вправо
self.flip()
def draw(self):
self.parent.surface.blit(self.surface, self.rect)
def _check_collision(self, coords):
"""Проверка пересечения со стенами"""
new_rect = self.rect.copy()
new_rect.topleft = coords
return self.scene.walls.check_collision(new_rect, self.mask)
@property
def speed(self):
return max(self._speed, 1)
@speed.setter
def speed(self, value):
self._speed = min(value, 15)
def _reduce_step(self, coords):
"""Уменьшение шага движения, с целью подойти вплотную к стене"""
delta = coords - self.coords
dx, dy = 0, 0
if abs(delta.x) > 1:
dx = 1 * (delta.x < 0 or -1)
if abs(delta.y) > 1:
dy = 1 * (delta.y < 0 or -1)
return coords + Coords(dx, dy)
def set_coords(self, coords: Coords):
# проверка колизии
has_collision = self._check_collision(coords)
if not has_collision:
super().set_coords(coords)
self.scene.coins.collect(self)
return
# уменьшение шага
while has_collision and coords != self.coords:
coords_new = self._reduce_step(coords)
if coords_new == coords:
return # не могу уменьшить шаг
coords = coords_new
has_collision = self._check_collision(coords)
super().set_coords(coords)
self.scene.coins.collect(self)
def flip(self):
self.looking_right = not self.looking_right
self._surface = pygame.transform.flip(self.surface, flip_x=True, flip_y=False)
def update_direction(self, direction: Direction):
if direction in (Direction.LEFT, Direction.RIGHT):
going_right = direction == Direction.RIGHT
if self.looking_right != going_right:
self.flip()
if direction != self.direction:
self.speed = 0
self.direction = direction
else:
self.speed += 1
def move(self, direction: Direction, step: int = 1):
self.update_direction(direction)
self.coords += direction.as_coords() * step * self.speed // 3
def handle_keyboard_event(self, event: pygame.event.Event):
wide, short = 3, 1
if event.type != pygame.KEYDOWN:
return
match event.key:
case pygame.K_UP:
self.move(Direction.UP, wide)
case pygame.K_DOWN:
self.move(Direction.DOWN, wide)
case pygame.K_LEFT:
self.move(Direction.LEFT, wide)
case pygame.K_RIGHT:
self.move(Direction.RIGHT, wide)
case pygame.K_w:
self.move(Direction.UP, short)
case pygame.K_s:
self.move(Direction.DOWN, short)
case pygame.K_a:
self.move(Direction.LEFT, short)
case pygame.K_d:
self.move(Direction.RIGHT, short)
def handle_mouse_event(self, event: pygame.event.Event):
if event.type not in (
pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP,
pygame.MOUSEMOTION,
):
return
match event.type:
case pygame.MOUSEBUTTONDOWN:
self.mouse_active = self.rect.collidepoint(event.pos)
case pygame.MOUSEBUTTONUP:
self.mouse_active = False
case pygame.MOUSEMOTION if self.mouse_active:
rel = Coords(*event.rel)
direction = Direction.from_coords(rel)
if direction:
self.update_direction(direction)
self.coords += rel
def handle_event(self, event: pygame.event.Event):
if not self.active:
return
self.handle_keyboard_event(event)
self.handle_mouse_event(event)
class WallBlock(DrawableGameObject):
"""объект элемента стены"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["brick.png"])
self.rect = self.surface.get_rect()
self.rect.topleft = coords
# уменьшаем размер монетки
sf = Coords(1, 1)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
self._mask = pygame.mask.Mask(self.rect.size, fill=True)
def draw(self):
self.parent.surface.blit(self.surface, self.rect)
class Walls(DrawableGameObject):
"""объект стен"""
def __init__(
self,
parent: DrawableGameObject,
maze: list[list[int]],
box_sz: Coords,
assets: dict | None = None,
):
super().__init__(Coords.zero(), parent, assets)
self.box_sz = box_sz
self.blocks = [
WallBlock(Coords(j, i).transform(box_sz), self, self.assets)
for i, row in enumerate(maze)
for j, item in enumerate(row)
if item > 0
]
def draw(self):
for block in self.blocks:
block.draw()
def check_collision(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
for block in self.blocks:
if block.overlap(rect, mask):
return True
return False
class Coin(DrawableGameObject):
"""объект монетки"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["coin.png"])
self.rect = self.surface.get_rect()
self.rect.topleft = coords
# уменьшаем размер монетки
sf = Coords(0.7, 0.7)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
def draw(self):
self.parent.surface.blit(self.surface, self.rect)
@property
def bounding_rect(self) -> pygame.Rect:
new_rect = self.surface.get_bounding_rect()
new_rect.center = self.rect.center
return new_rect
class Coins(DrawableGameObject):
"""объект коллекции монеток"""
def __init__(
self,
parent: DrawableGameObject,
maze: list[list[int]],
box_sz: Coords,
count: int,
assets: dict | None = None,
):
super().__init__(Coords.zero(), parent, assets)
self.box_sz = box_sz
self._capacity = count
free_points = []
excluded = Coords(0, 1), get_maze_sz(maze) - Coords(1, 2)
for i, row in enumerate(maze):
for j, item in enumerate(row):
p = Coords(j, i)
if item < 1 and p not in excluded:
free_points.append(p)
continue
coin_points = sample(free_points, min(count, len(free_points)))
self.coins = [
Coin(point.transform(box_sz), self, self.assets) for point in coin_points
]
self.collected_coins = []
# Надпись, если все монетки собраны
font = pygame.font.SysFont(FONT_NAME, 30)
text = "Все монетки собраны!"
self.done_txt = font.render(text, 1, "#050366e3")
self.done_txt_rect = self.done_txt.get_rect()
self.done_txt_rect.topleft = Coords(10, 10)
@property
def capacity(self) -> int:
return self._capacity
@property
def coins_left(self) -> int:
return len(self.coins)
@property
def coins_collected(self) -> int:
return self.capacity - self.coins_left
@property
def all_collected(self) -> int:
return self.coins_left == 0
def draw(self):
for coin in self.collected_coins:
coin.draw()
for coin in self.coins:
coin.draw()
if self.all_collected:
self.parent.surface.blit(self.done_txt, self.done_txt_rect)
def add_to_collected(self, coin: Coin):
last_pos = Coords(10, 10)
if self.collected_coins:
last_pos = Coords(*self.collected_coins[-1].rect.topright)
last_pos -= Coords(coin.rect.width // 2, 0)
coin.coords = last_pos
self.collected_coins.append(coin)
def collect(self, actor: DrawableGameObject):
mined = [*filter(lambda coin: coin.overlap(actor.rect, actor.mask), self.coins)]
for coin in mined:
self.coins.remove(coin)
self.add_to_collected(coin)
class EndLevelMenu(DrawableGameObject, EventHandler):
def __init__(self, scene: DrawableGameObject):
super().__init__(Coords.zero(), scene, scene.assets)
self._surface, self.rect = self._create_end_game_label()
self.win_image = self._create_win_image()
self.win_label = self._create_win_label()
self.keys_hint = self._create_keys_hint()
self.stats_label = None
self.active = False
def _create_end_game_label(self) -> SurfaceWithRect:
"""Надпись завершения игры"""
font = pygame.font.SysFont(FONT_NAME, 70)
text = "Конец игры!"
surface = font.render(text, 1, "#1b10a8c4")
rect = surface.get_rect()
rect.center = Coords(*self.parent.rect.center) + Coords(0, 38)
return SurfaceWithRect(surface, rect)
def _create_keys_hint(self) -> SurfaceWithRect:
"""Совет по кнопкам"""
hint_text = "Для новой игры нажмите N, для выхода Q"
font_hint = pygame.font.SysFont(FONT_NAME, 27)
surface = font_hint.render(hint_text, 1, "#24053da4")
rect = surface.get_rect()
rect.center = self.parent.rect.center
rect = rect.move(Coords(0, 220))
return SurfaceWithRect(surface, rect)
def _create_win_label(self) -> SurfaceWithRect:
"""Надпись для хорошего финала"""
font = pygame.font.SysFont(FONT_NAME, 33)
text = "Все монетки собраны!"
surface = font.render(text, 1, "#96081ba4")
rect = surface.get_rect()
rect.center = self.parent.rect.center
rect.move_ip(Coords(0, -200))
return SurfaceWithRect(surface, rect)
def _create_win_image(self) -> SurfaceWithRect:
"""Картинка для хорошего финала"""
surface = pygame.image.load(self.scene.assets["win.png"])
rect = surface.get_rect()
rect.center = self.parent.rect.center
return SurfaceWithRect(surface, rect)
def _create_stats_label(self) -> SurfaceWithRect:
"""Общая статистика игры"""
stats_text = f"Всего пройдено уровней: {self.scene.total_levels}, собрано монет: {self.scene.total_coins}"
stats_font = pygame.font.SysFont(FONT_NAME, 27)
surface = stats_font.render(stats_text, 1, "#031f03a4")
rect = surface.get_rect()
rect.center = Coords(*self.scene.rect.center) + Coords(0, 350)
return SurfaceWithRect(surface, rect)
def draw(self):
if not self.active:
return
if self.scene.coins.all_collected:
self.win_image.draw_to(self.parent.surface)
self.win_label.draw_to(self.parent.surface)
self.parent.surface.blit(self.surface, self.rect)
self.keys_hint.draw_to(self.parent.surface)
# статистика
if self.stats_label is None:
self.stats_label = self._create_stats_label()
self.stats_label.draw_to(self.parent.surface)
def request_new_level(self):
self.scene.want_new_level = True
self.scene.done = True
def handle_keyboard_event(self, event: pygame.event.Event):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_n:
self.request_new_level()
def handle_mouse_event(self, event: pygame.event.Event):
if event.type == pygame.MOUSEBUTTONDOWN and self.keys_hint.rect.collidepoint(
event.pos
):
self.request_new_level()
def handle_event(self, event: pygame.event.Event):
if not self.active:
return
self.handle_keyboard_event(event)
self.handle_mouse_event(event)
class Scene(DrawableGameObject, EventHandler):
"""основной игровой объект"""
# кнопки для выхода из игры
exit_keys = (pygame.K_ESCAPE, pygame.K_q)
def __init__(
self, assets: dict, screen_sz: Coords, maze_sz: Coords, coins_count: int
):
super().__init__(Coords.zero(), None, assets)
self.maze = maze_gen(*maze_sz)
maze_sz = get_maze_sz(self.maze)
box_sz = screen_sz // get_maze_sz(self.maze)
self.box_sz = box_sz
self._surface = 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"])
self.background = pygame.transform.scale(self.background, self.rect.size)
self.total_levels, self.total_coins = 0, 0
hero_sz = Coords(*map(int, box_sz * 0.8))
hero_y_offset = (box_sz.y - hero_sz.y) // 2 + box_sz.y
self.hero = Hero(Coords(0, hero_y_offset), self)
self.done = False
self.level_completed = False
self.maze = maze_gen(6, 6)
self.walls = Walls(self, self.maze, box_sz)
self.coins = Coins(self, self.maze, box_sz, coins_count)
self.end = EndLevelMenu(self)
self.end.active = False
self.want_new_level = False
self.exit_rect = self.get_exit_rect()
def get_exit_rect(self) -> pygame.Rect:
# находим клетку в которой будет выход с карты
maze_sz = get_maze_sz(self.maze)
coords = (maze_sz - Coords(1, 2)) * self.box_sz
rect = pygame.Rect(coords, coords)
rect.width, rect.height = 1, self.box_sz.y
return rect.move((self.box_sz.x, 0))
def check_level_completed(self):
level_completed = self.exit_rect.colliderect(self.hero.rect)
if level_completed and not self.level_completed:
self.total_coins += self.coins.coins_collected
self.total_levels += 1
self.end.active = True
self.hero.active = False
self.level_completed = True
def draw(self):
if self.done:
return
self.surface.fill("white")
self.surface.blit(self.background, self.coords)
if self.level_completed:
self.end.draw()
else:
self.hero.draw()
self.walls.draw()
self.coins.draw()
def scale_box(
self, surface: pygame.Surface, rect: pygame.Rect, scale_factor: Coords
):
rect.size = self.box_sz
rect.scale_by_ip(*scale_factor)
surface = pygame.transform.scale(surface, rect.size)
return surface, rect
def handle_event(self, event: pygame.event.Event):
if self.done:
return
if (
event.type == pygame.QUIT
or event.type == pygame.KEYDOWN
and event.key in self.exit_keys
):
self.done = True
if not self.done:
self.hero.handle_event(event)
self.check_level_completed()
self.end.handle_event(event)
def event_loop(self):
clock = pygame.time.Clock()
pygame.key.set_repeat(50, 30)
while not self.done:
for event in pygame.event.get():
self.handle_event(event)
self.draw()
pygame.display.flip()
clock.tick(FPS)
def game(assets):
screen_sz = Coords(1000, 1000)
maze_sz = Coords(6, 6)
coins_count = 10
pygame.display.set_caption("Призрачный лабиринт: сокровища небесного замка")
total_levels = 0
total_coins = 0
want_new_level = True
while want_new_level:
scene = Scene(assets, screen_sz, maze_sz, coins_count)
scene.total_levels, scene.total_coins = total_levels, total_coins
scene.event_loop()
want_new_level = scene.want_new_level
total_levels = scene.total_levels
total_coins = scene.total_coins
pygame.quit()
def main():
pygame.init()
required_assets = [
"bg1k.png",
"ghost.png",
"brick.png",
"win.png",
"coin.png",
]
with get_assets(required_assets) as assets:
game(assets)
main()

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

@@ -0,0 +1,17 @@
assets_files = assets/bg1k.png assets/brick.png assets/coin.png assets/ghost.png assets/win.png assets/bg.ogg
all: build/web.zip
assets:
mkdir assets
assets/%: assets $(../../assets/*)
cp ../../$@ assets/
build/web: Makefile main.py $(assets_files)
pygbag --width 1000 --height 1000 --can_close 1 --title "Призрачный лабиринт: сокровища небесного замка" --icon assets/ghost.png --build .
sed -i -e "s|https://pygame-web.github.io/archives/0.9/|https://b4tman.ru/phantomcastle/pygbag/0.9/|g" build/web/index.html
sed -i -e "s|en-us|ru-RU|g" build/web/index.html
build/web.zip: build/web
sh -exc "cd build/web && zip -r ../web.zip ."

View File

@@ -0,0 +1,100 @@
from abc import ABC, abstractmethod
from typing import NamedTuple, Optional
FONT_NAME = "Arial"
from sys import platform
IS_WASM = platform == "emscripten"
import pygame
from coords import Coords
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class SurfaceWithRect(NamedTuple):
surface: pygame.Surface
rect: pygame.Rect
def draw_to(self, target: pygame.Surface):
target.blit(self.surface, self.rect)
class DrawableGameObject(ABC):
"""обобщение игрового элемента"""
coords = property(
lambda self: self.get_coords(), lambda self, c: self.set_coords(c)
)
def __init__(
self,
coords: Coords,
parent: Optional["DrawableGameObject"] = None,
assets: dict | None = None,
):
self.parent = parent
self.rect = pygame.Rect(coords, coords)
self.assets = assets or (parent.assets if parent else None)
self._surface = None
self._mask = None
def __str__(self):
return f"{self.__class__.__name__}({self.coords})"
@property
def surface(self) -> pygame.Surface | None:
return self._surface or (self.parent.surface if self.parent else None)
@property
def mask(self) -> pygame.Mask | None:
if not self._mask:
self._mask = pygame.mask.from_surface(self.surface.convert_alpha())
return self._mask
def overlap(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
if not self.rect.colliderect(rect):
return False
offset = Coords(*self.rect.topleft) - Coords(*rect.topleft)
overlap = mask.overlap(self.mask, offset)
return overlap is not None
@property
def scene(self):
return self.parent.scene if self.parent else self
def get_coords(self) -> Coords:
return Coords(*self.rect.topleft)
def set_coords(self, coords: Coords):
new_rect = self.rect.copy()
new_rect.topleft = coords
if self.parent:
if self.parent.rect:
if not self.parent.rect.contains(new_rect):
return
self.rect = new_rect
@abstractmethod
def draw(self):
pass
class EventHandler(ABC):
@abstractmethod
def handle_keys(self, keys_pressed):
pass
@abstractmethod
def handle_event(self):
pass

View File

@@ -0,0 +1,91 @@
from enum import Enum
from typing import NamedTuple, Optional
class Coords(NamedTuple):
"""
Вспомогательный класс для упрощения работы с координатами
"""
x: int | float
y: int | float
def __add__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x + other.x, self.y + other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x + other, self.y + other)
return NotImplemented
def __sub__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x - other.x, self.y - other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x - other, self.y - other)
return NotImplemented
def __floordiv__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x // other.x, self.y // other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x // other, self.y // other)
return NotImplemented
def __mul__(self, other):
if isinstance(other, self.__class__):
return self.__class__(self.x * other.x, self.y * other.y)
if isinstance(other, (int, float)):
return self.__class__(self.x * other, self.y * other)
return NotImplemented
def transform(self, ref: "Coords"):
return self * ref
def dir_norm(self):
"""нормализация вектора, но только для получения направления
x, y могут быть только -1, 0, 1
может быть только направление по горизонтали либо по вертикали
либо без направления
"""
src = self
if self.x and self.y:
src = (
self.__class__(self.x, 0)
if abs(self.x) > abs(self.y)
else self.__class__(0, self.y)
)
return self.__class__(*((n > 0) - (n < 0) for n in src))
@classmethod
def zero(cls):
return cls(0, 0)
class Direction(Enum):
LEFT = 1
RIGHT = 2
UP = 3
DOWN = 4
def as_coords(self):
match self:
case Direction.LEFT:
return Coords(-1, 0)
case Direction.RIGHT:
return Coords(1, 0)
case Direction.UP:
return Coords(0, -1)
case Direction.DOWN:
return Coords(0, 1)
@staticmethod
def from_coords(coords: Coords) -> Optional["Direction"]:
match coords.dir_norm():
case Coords(-1, 0):
return Direction.LEFT
case Coords(1, 0):
return Direction.RIGHT
case Coords(0, -1):
return Direction.UP
case Coords(0, 1):
return Direction.DOWN

View File

@@ -0,0 +1,5 @@
from .scene import Scene
from .hero import Hero
from .wall import Walls
from .coins import Coins
from .endlevelmenu import EndLevelMenu

View File

@@ -0,0 +1,108 @@
from random import sample
import pygame
from coords import Coords
from common import DrawableGameObject, FONT_NAME
from maze import get_maze_sz
class Coin(DrawableGameObject):
"""объект монетки"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["coin.png"]).convert_alpha()
self.rect = self.surface.get_rect()
self.rect.topleft = coords
# уменьшаем размер монетки
sf = Coords(0.7, 0.7)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
def draw(self):
self.parent.surface.blit(self.surface, self.rect)
@property
def bounding_rect(self) -> pygame.Rect:
new_rect = self.surface.get_bounding_rect()
new_rect.center = self.rect.center
return new_rect
class Coins(DrawableGameObject):
"""объект коллекции монеток"""
def __init__(
self,
parent: DrawableGameObject,
maze: list[list[int]],
box_sz: Coords,
count: int,
assets: dict | None = None,
):
super().__init__(Coords.zero(), parent, assets)
self.box_sz = box_sz
self._capacity = count
free_points = []
excluded = Coords(0, 1), get_maze_sz(maze) - Coords(1, 2)
for i, row in enumerate(maze):
for j, item in enumerate(row):
p = Coords(j, i)
if item < 1 and p not in excluded:
free_points.append(p)
continue
coin_points = sample(free_points, min(count, len(free_points)))
self.coins = [
Coin(point.transform(box_sz), self, self.assets) for point in coin_points
]
self.collected_coins = []
# Надпись, если все монетки собраны
font = pygame.font.SysFont(FONT_NAME, 30)
text = "Все монетки собраны!"
self.done_txt = font.render(text, 1, "#050366e3")
self.done_txt_rect = self.done_txt.get_rect()
self.done_txt_rect.topleft = Coords(10, 10)
@property
def capacity(self) -> int:
return self._capacity
@property
def coins_left(self) -> int:
return len(self.coins)
@property
def coins_collected(self) -> int:
return self.capacity - self.coins_left
@property
def all_collected(self) -> int:
return self.coins_left == 0
def draw(self):
for coin in self.collected_coins:
coin.draw()
for coin in self.coins:
coin.draw()
if self.all_collected:
self.parent.surface.blit(self.done_txt, self.done_txt_rect)
def add_to_collected(self, coin: Coin):
last_pos = Coords(10, 10)
if self.collected_coins:
last_pos = Coords(*self.collected_coins[-1].rect.topright)
last_pos -= Coords(coin.rect.width // 2, 0)
coin.coords = last_pos
self.collected_coins.append(coin)
def collect(self, actor: DrawableGameObject):
mined = [*filter(lambda coin: coin.overlap(actor.rect, actor.mask), self.coins)]
for coin in mined:
self.coins.remove(coin)
self.add_to_collected(coin)

View File

@@ -0,0 +1,97 @@
import pygame
from coords import Coords
from common import DrawableGameObject, EventHandler, FONT_NAME, IS_WASM, SurfaceWithRect
class EndLevelMenu(DrawableGameObject, EventHandler):
def __init__(self, scene: DrawableGameObject):
super().__init__(Coords.zero(), scene, scene.assets)
self._surface, self.rect = self._create_end_game_label()
self.win_image = self._create_win_image()
self.win_label = self._create_win_label()
self.keys_hint = self._create_keys_hint()
self.stats_label = None
self.active = False
def _create_end_game_label(self) -> SurfaceWithRect:
"""Надпись завершения игры"""
font = pygame.font.SysFont(FONT_NAME, 70)
text = "Конец игры!"
surface = font.render(text, 1, "#1b10a8c4")
rect = surface.get_rect()
rect.center = Coords(*self.parent.rect.center) + Coords(0, 38)
return SurfaceWithRect(surface, rect)
def _create_keys_hint(self) -> SurfaceWithRect:
"""Совет по кнопкам"""
hint_text = "Для новой игры нажмите N"
if not IS_WASM:
hint_text += ", для выхода Q"
font_hint = pygame.font.SysFont(FONT_NAME, 27)
surface = font_hint.render(hint_text, 1, "#24053da4")
rect = surface.get_rect()
rect.center = self.parent.rect.center
rect = rect.move(Coords(0, 220))
return SurfaceWithRect(surface, rect)
def _create_win_label(self) -> SurfaceWithRect:
"""Надпись для хорошего финала"""
font = pygame.font.SysFont(FONT_NAME, 33)
text = "Все монетки собраны!"
surface = font.render(text, 1, "#96081ba4")
rect = surface.get_rect()
rect.center = self.parent.rect.center
rect.move_ip(Coords(0, -200))
return SurfaceWithRect(surface, rect)
def _create_win_image(self) -> SurfaceWithRect:
"""Картинка для хорошего финала"""
surface = pygame.image.load(self.scene.assets["win.png"]).convert_alpha()
rect = surface.get_rect()
rect.center = self.parent.rect.center
return SurfaceWithRect(surface, rect)
def _create_stats_label(self) -> SurfaceWithRect:
"""Общая статистика игры"""
stats_text = f"Всего пройдено уровней: {self.scene.total_levels}, собрано монет: {self.scene.total_coins}"
stats_font = pygame.font.SysFont(FONT_NAME, 27)
surface = stats_font.render(stats_text, 1, "#031f03a4")
rect = surface.get_rect()
rect.center = Coords(*self.scene.rect.center) + Coords(0, 350)
return SurfaceWithRect(surface, rect)
def draw(self):
if not self.active:
return
if self.scene.coins.all_collected:
self.win_image.draw_to(self.parent.surface)
self.win_label.draw_to(self.parent.surface)
self.parent.surface.blit(self.surface, self.rect)
self.keys_hint.draw_to(self.parent.surface)
# статистика
if self.stats_label is None:
self.stats_label = self._create_stats_label()
self.stats_label.draw_to(self.parent.surface)
def request_new_level(self):
self.scene.want_new_level = True
self.scene.done = True
def handle_keys(self, keys_pressed):
if not self.active:
return
if keys_pressed[pygame.K_n]:
self.request_new_level()
def handle_mouse_event(self, event: pygame.event.Event):
if event.type == pygame.MOUSEBUTTONDOWN and self.keys_hint.rect.collidepoint(
event.pos
):
self.request_new_level()
def handle_event(self, event: pygame.event.Event):
if not self.active:
return
self.handle_mouse_event(event)

View File

@@ -0,0 +1,179 @@
import pygame
from coords import Coords, Direction
from common import DrawableGameObject, EventHandler
def point_at(coords: Coords) -> tuple[pygame.Rect, pygame.Mask]:
rect = pygame.Rect(coords.x, coords.y, 1, 1)
mask = pygame.Mask((1, 1), fill=True)
return rect, mask
def collide_with_walls(coords: Coords, walls: DrawableGameObject) -> bool:
rect, mask = point_at(coords)
return walls.check_collision(rect, mask)
def is_valid_point_to_move(coords: Coords, walls: DrawableGameObject) -> bool:
return not collide_with_walls(coords, walls)
class Hero(DrawableGameObject, EventHandler):
"""объект главного героя"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["ghost.png"]).convert_alpha()
self.rect = self.surface.get_rect()
sf = Coords(0.8, 0.8)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
self.rect.topleft = coords
self.active = True
self.looking_right = False
self._speed = 1
self.direction = Direction.RIGHT
self.mouse_active = False
self.auto_move_target = None
# картинка изначально влево, а надо бы начинать со взгляда вправо
self.flip()
def draw(self):
self.auto_move()
self.parent.surface.blit(self.surface, self.rect)
def _check_collision(self, coords):
"""Проверка пересечения со стенами"""
new_rect = self.rect.copy()
new_rect.topleft = coords
return self.scene.walls.check_collision(new_rect, self.mask)
@property
def speed(self):
return max(self._speed, 1)
@speed.setter
def speed(self, value):
self._speed = min(value, 15)
def _reduce_step(self, coords):
"""Уменьшение шага движения, с целью подойти вплотную к стене"""
delta = coords - self.coords
dx, dy = 0, 0
if abs(delta.x) > 1:
dx = 1 * (delta.x < 0 or -1)
if abs(delta.y) > 1:
dy = 1 * (delta.y < 0 or -1)
return coords + Coords(dx, dy)
def set_coords(self, coords: Coords):
# проверка колизии
has_collision = self._check_collision(coords)
if not has_collision:
super().set_coords(coords)
self.scene.coins.collect(self)
return
self.auto_move_target = None
# уменьшение шага
while has_collision and coords != self.coords:
coords_new = self._reduce_step(coords)
if coords_new == coords:
return # не могу уменьшить шаг
coords = coords_new
has_collision = self._check_collision(coords)
super().set_coords(coords)
self.scene.coins.collect(self)
def flip(self):
self.looking_right = not self.looking_right
self._surface = pygame.transform.flip(self.surface, flip_x=True, flip_y=False)
def update_direction(self, direction: Direction):
if direction in (Direction.LEFT, Direction.RIGHT):
going_right = direction == Direction.RIGHT
if self.looking_right != going_right:
self.flip()
if direction != self.direction:
self.speed = 0
self.direction = direction
else:
self.speed += 1
def move(self, direction: Direction, step: int = 1):
self.update_direction(direction)
self.coords += direction.as_coords() * step * self.speed // 3
def auto_move(self):
if self.auto_move_target is None:
return
rect, mask = point_at(self.auto_move_target)
if self.overlap(rect, mask):
self.auto_move_target = None
return
direction = Direction.from_coords(self.auto_move_target - self.coords)
if direction:
self.move(direction, step=1)
else:
self.auto_move_target = None
def handle_mouse_event(self, event: pygame.event.Event):
if event.type not in (
pygame.MOUSEBUTTONDOWN,
pygame.MOUSEBUTTONUP,
pygame.MOUSEMOTION,
):
return
match event.type:
case pygame.MOUSEBUTTONDOWN:
self.mouse_active = self.rect.collidepoint(event.pos)
if not self.mouse_active and is_valid_point_to_move(
Coords(*event.pos), self.scene.walls
):
self.auto_move_target = Coords(*event.pos)
else:
self.auto_move_target = None
case pygame.MOUSEBUTTONUP:
self.mouse_active = False
case pygame.MOUSEMOTION if self.mouse_active:
rel = Coords(*event.rel)
direction = Direction.from_coords(rel)
if direction:
self.update_direction(direction)
self.coords += rel
def handle_event(self, event: pygame.event.Event):
if not self.active:
return
self.handle_mouse_event(event)
def handle_keys(self, keys_pressed):
if not self.active:
return
wide, short = 3, 1
if keys_pressed[pygame.K_UP]:
self.move(Direction.UP, wide)
if keys_pressed[pygame.K_DOWN]:
self.move(Direction.DOWN, wide)
if keys_pressed[pygame.K_LEFT]:
self.move(Direction.LEFT, wide)
if keys_pressed[pygame.K_RIGHT]:
self.move(Direction.RIGHT, wide)
if keys_pressed[pygame.K_w]:
self.move(Direction.UP, short)
if keys_pressed[pygame.K_s]:
self.move(Direction.DOWN, short)
if keys_pressed[pygame.K_a]:
self.move(Direction.LEFT, short)
if keys_pressed[pygame.K_d]:
self.move(Direction.RIGHT, short)

View File

@@ -0,0 +1,142 @@
import asyncio
import pygame
from maze import maze_gen, get_maze_sz
from coords import Coords
from common import DrawableGameObject, EventHandler, IS_WASM
from game.hero import Hero
from game.wall import Walls
from game.coins import Coins
from game.endlevelmenu import EndLevelMenu
from sound import BackgroundSound
class Scene(DrawableGameObject, EventHandler):
"""основной игровой объект"""
# кнопки для выхода из игры
exit_keys = (pygame.K_ESCAPE, pygame.K_q)
def __init__(
self,
assets: dict,
screen_sz: Coords,
maze_sz: Coords,
coins_count: int,
fps: int,
):
super().__init__(Coords.zero(), None, assets)
self.maze = maze_gen(*maze_sz)
maze_sz = get_maze_sz(self.maze)
box_sz = screen_sz // get_maze_sz(self.maze)
self.box_sz = box_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()
self.background = pygame.transform.scale(self.background, self.rect.size)
double_bg = pygame.Surface((screen_sz.x * 2, screen_sz.y))
double_bg.blit(self.background, (0, 0))
double_bg.blit(self.background, (screen_sz.x, 0))
self.background = double_bg
self.bg_shift = 0
self.total_levels, self.total_coins = 0, 0
hero_sz = Coords(*map(int, box_sz * 0.8))
hero_y_offset = (box_sz.y - hero_sz.y) // 2 + box_sz.y
self.hero = Hero(Coords(0, hero_y_offset), self)
self.done = False
self.level_completed = False
self.maze = maze_gen(6, 6)
self.walls = Walls(self, self.maze, box_sz)
self.coins = Coins(self, self.maze, box_sz, coins_count)
self.end = EndLevelMenu(self)
self.end.active = False
self.want_new_level = False
self.exit_rect = self.get_exit_rect()
self.fps = fps
self.sound = BackgroundSound(self.assets["bg.ogg"])
def get_exit_rect(self) -> pygame.Rect:
# находим клетку в которой будет выход с карты
maze_sz = get_maze_sz(self.maze)
coords = (maze_sz - Coords(1, 2)) * self.box_sz
rect = pygame.Rect(coords, coords)
rect.width, rect.height = 1, self.box_sz.y
return rect.move((self.box_sz.x, 0))
def check_level_completed(self):
level_completed = self.exit_rect.colliderect(self.hero.rect)
if level_completed and not self.level_completed:
self.total_coins += self.coins.coins_collected
self.total_levels += 1
self.end.active = True
self.hero.active = False
self.level_completed = True
def draw(self):
if self.done:
return
self.surface.blit(self.background, (-self.bg_shift, 0))
if self.level_completed:
self.end.draw()
else:
self.hero.draw()
self.walls.draw()
self.coins.draw()
def scale_box(
self, surface: pygame.Surface, rect: pygame.Rect, scale_factor: Coords
):
rect.size = self.box_sz
rect.scale_by_ip(*scale_factor)
surface = pygame.transform.scale(surface, rect.size)
return surface, rect
def handle_keys(self, keys_pressed):
if self.done:
return
if not self.done:
self.hero.handle_keys(keys_pressed)
self.check_level_completed()
self.end.handle_keys(keys_pressed)
def handle_exit(self, event: pygame.event.Event):
if IS_WASM:
return
if (
event.type == pygame.QUIT
or event.type == pygame.KEYDOWN
and event.key in self.exit_keys
):
self.done = True
def handle_event(self, event: pygame.event.Event):
self.handle_exit(event)
if self.done:
return
if event.type == pygame.KEYDOWN and event.key == pygame.K_m:
self.sound.toggle_mute()
self.hero.handle_event(event)
self.check_level_completed()
self.end.handle_event(event)
async def event_loop(self):
self.sound.play()
clock = pygame.time.Clock()
while not self.done:
for event in pygame.event.get():
self.handle_event(event)
self.handle_keys(pygame.key.get_pressed())
self.draw()
self.bg_shift = (self.bg_shift + 1) % self.rect.width
pygame.display.flip()
await asyncio.sleep(0)
clock.tick(self.fps)

View File

@@ -0,0 +1,55 @@
import pygame
from coords import Coords
from common import DrawableGameObject
class WallBlock(DrawableGameObject):
"""объект элемента стены"""
def __init__(
self,
coords: Coords,
parent: DrawableGameObject,
assets: dict | None = None,
):
super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["brick.png"]).convert_alpha()
self.rect = self.surface.get_rect()
self.rect.topleft = coords
# уменьшаем размер монетки
sf = Coords(1, 1)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
self._mask = pygame.mask.Mask(self.rect.size, fill=True)
def draw(self):
self.parent.surface.blit(self.surface, self.rect)
class Walls(DrawableGameObject):
"""объект стен"""
def __init__(
self,
parent: DrawableGameObject,
maze: list[list[int]],
box_sz: Coords,
assets: dict | None = None,
):
super().__init__(Coords.zero(), parent, assets)
self.box_sz = box_sz
self.blocks = [
WallBlock(Coords(j, i).transform(box_sz), self, self.assets)
for i, row in enumerate(maze)
for j, item in enumerate(row)
if item > 0
]
def draw(self):
for block in self.blocks:
block.draw()
def check_collision(self, rect: pygame.Rect, mask: pygame.Mask) -> bool:
for block in self.blocks:
if block.overlap(rect, mask):
return True
return False

View File

@@ -0,0 +1,72 @@
"""
Игра "Призрачный лабиринт: сокровища небесного замка"
Призрак в лабиринте, управление стрелками и WASD, выход Esc или Q.
Чтобы пройти уровень, нужно дойти до выхода, желательно собрав все монетки.
После прохождения уровня можно начать новый (кнопкой N) или выйти из игры.
Также можно управлять мышкой, перетаскивая героя (зажав ЛКМ) и кликая на надпись,
для начала новой игры.
Музыку можно заглушить (или вернуть), нажав на кнопку M.
Это универсальная версия. Подходит для сборки в виде WASM (для браузера) игры,
с помощью pygbag и для запуска напрямую.
pip install pygame pygbag
потом make (в Linux/WSL)
"""
import asyncio
import os
from contextlib import contextmanager
from game import Scene
from coords import Coords
import pygame
FPS = 30
@contextmanager
def get_assets_direct(names):
"""передача файлов картинок 1 в 1"""
assets_dir = "assets"
yield {asset: os.path.join(assets_dir, asset) for asset in names}
async def game(assets):
screen_sz = Coords(1000, 1000)
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
want_new_level = True
while want_new_level:
scene = Scene(assets, screen_sz, maze_sz, coins_count, FPS)
scene.total_levels, scene.total_coins = total_levels, total_coins
await scene.event_loop()
want_new_level = scene.want_new_level
total_levels = scene.total_levels
total_coins = scene.total_coins
pygame.quit()
async def main():
pygame.init()
required_assets = [
"bg1k.png",
"ghost.png",
"brick.png",
"win.png",
"coin.png",
"bg.ogg",
]
with get_assets_direct(required_assets) as assets:
await game(assets)
asyncio.run(main())

View File

@@ -0,0 +1,72 @@
from random import choice, randrange
from coords import Coords
def maze_gen(row=4, col=4):
"""генератор карты
взял с коментария
https://stepik.org/lesson/502494/step/3?discussion=6527620&unit=494196
"""
row = max(2 * row + 1, 3)
col = max(2 * col + 1, 3)
maze = [[2] * col]
maze.extend([[2] + [1] * (col - 2) + [2] for _ in range(row - 2)])
maze.append(maze[0])
curr = (randrange(1, len(maze) - 1, 2), randrange(1, len(maze[0]) - 1, 2))
path = [curr]
maze[curr[0]][curr[1]] = 0
while path:
nexts = [
(r1, c1, r2, c2)
for r, c in zip((1, 0, -1, 0), (0, 1, 0, -1))
if (
(r1 := curr[0] + r) is None
or (c1 := curr[1] + c) is None
or (r2 := curr[0] + 2 * r) is None
or (c2 := curr[1] + 2 * c) is None
or 1 == maze[r1][c1] == maze[r2][c2]
)
]
if nexts:
r1, c1, r2, c2 = choice(nexts)
maze[r1][c1] = maze[r2][c2] = 0
path.append((r2, c2))
else:
curr = path.pop()
upd = {
("22", "20"): (None, "00"),
("02", "22"): ("00", None),
("11101", "00101", "11111", "10100", "10111"): (
None,
None,
None,
"10001",
None,
),
("10111", "10100", "11111", "00101", "11101"): (
None,
"10001",
None,
None,
None,
),
}
for pattern, replacement in upd.items():
for i in range(len(maze) - len(pattern) + 1):
for j in range(len(maze[0]) - len(pattern[0]) + 1):
if all(
maze[i + k][j : j + len(v)] == list(map(int, v))
for k, v in enumerate(pattern)
):
for k, v in filter(lambda x: x[1], enumerate(replacement)):
maze[i + k][j : j + len(v)] = list(map(int, v))
return maze
def get_maze_sz(maze: list[list[int]]) -> Coords:
return Coords(len(maze[0]), len(maze))

View File

@@ -0,0 +1,37 @@
import pygame
from common import SingletonMeta
class BackgroundSound(pygame.mixer.Sound, metaclass=SingletonMeta):
def __init__(self, filename):
super().__init__(filename)
self.set_volume(0.5)
self._muted = False
self._played = False
def play(self):
if not self._played:
super().play(loops=-1)
self._played = True
def mute(self):
self._muted = True
self.set_volume(0)
def unmute(self):
self._muted = False
self.set_volume(0.5)
@property
def muted(self):
return self._muted
@muted.setter
def muted(self, value):
if value:
self.mute()
else:
self.unmute()
def toggle_mute(self):
self.muted = not self.muted