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