Merge pull request '[1][2] Labs' (#338) from skorohodovsa/2026-rff_mp:main into develop

Reviewed-on: UNN/2026-rff_mp#338
This commit is contained in:
VladimirGub 2026-05-30 11:56:39 +00:00
commit 178a3ac278
105 changed files with 8561 additions and 0 deletions

6
skorohodovsa/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.ruff_cache/
.vscode/*
.idea/
/.vscode
task_1/work.py

View File

@ -0,0 +1,75 @@
# ОТЧЕТ ПО ЛАБОРАТОРНОЙ РАБОТЕ
## Тема: Сравнительный анализ структур данных для телефонной книги
### Цель работы
Реализовать и сравнить производительность трех структур данных: бинарного дерева поиска, хеш-таблицы и связного списка.
### Ход работы
#### 1. Реализованы структуры данных
- **Binary Search Tree (BST)** - бинарное дерево поиска
- **Hash Table** - хеш-таблица с методом цепочек
- **Linked List** - односвязный список
#### 2. Проведены эксперименты
Каждый эксперимент повторен 5 раз, результаты сохранены в файл `results.csv`
**Эксперимент 1:** Вставка элементов (случайные данные)
**Эксперимент 2:** Вставка элементов (отсортированные данные)
**Эксперимент 3:** Поиск 100 элементов
### Результаты измерений (средние значения)
| Размер | BST случайный | BST отсортированный | Хеш-таблица | Связный список |
|--------|---------------|--------------------|-------------|----------------|
| 100 | 0.0001 сек | 0.0004 сек | 0.0002 сек | 0.0005 сек |
| 200 | 0.0002 сек | 0.0023 сек | 0.0006 сек | 0.0020 сек |
| 500 | 0.0007 сек | 0.0259 сек | 0.0100 сек | 0.0123 сек |
| 1000 | 0.0034 сек | 0.0910 сек | 0.0250 сек | 0.0340 сек |
| 2000 | 0.0075 сек | 0.3500 сек | 0.0580 сек | 0.0820 сек |
**Поиск 100 элементов (2000 записей):**
- BST: 0.0012 сек
- Хеш-таблица: 0.00055 сек
- Связный список: 0.0032 сек
### Графики
*(вставьте сюда graphics.png)*
**График 1:** Сравнение скорости вставки
**График 2:** Деградация BST на отсортированных данных
**График 3:** Сравнение скорости поиска
**График 4:** Во сколько раз BST медленнее на отсортированных данных
### Анализ результатов
**1. Влияние порядка данных на BST**
При вставке отсортированных данных BST вырождается в связный список. На 2000 записях замедление составило 46.7 раз. Сложность падает с O(log n) до O(n).
**2. Хеш-таблица и порядок данных**
Хеш-таблица не чувствительна к порядку, так как хеш-функция вычисляет позицию напрямую, без сравнения с другими элементами.
**3. Связный список при поиске**
Всегда медленен при поиске, так как требует последовательного перебора O(n) до нахождения элемента.
**4. Удаление элементов**
- **BST:** требует поиска узла и перестроения поддеревьев (сложный случай с двумя детьми)
- **Хеш-таблица:** помечает элемент как deleted, при переполнении делает рехеширование
- **Связный список:** перелинковывает указатели предыдущего и следующего узлов
### Вывод
**Рекомендации по выбору структуры данных:**
| Задача | Рекомендуемая структура | Причина |
|--------|------------------------|---------|
| Частый поиск | Хеш-таблица | O(1) в среднем |
| Частые вставки | Хеш-таблица | O(1) в среднем |
| Нужна сортировка | BST | автоматическая сортировка |
| Мало данных (<100) | Связный список | простая реализация |
| Максимальная скорость | Хеш-таблица | лучшая производительность |
**Итог:** Для телефонной книги с большим количеством записей и частым поиском оптимальна **хеш-таблица**. Если требуется выводить контакты в алфавитном порядке - **BST**. Связный список подходит только для учебных целей или очень малых объемов данных.

View File

@ -0,0 +1,149 @@
class BSTNode:
def __init__(self, name: str, phone: str):
self.name = name
self.phone = phone
self.left = None
self.right = None
class BinarySearchTree:
def __init__(self):
self.root = None
def insert(self, name: str, phone: str) -> None:
if self.root is None:
self.root = BSTNode(name, phone)
return
current = self.root
while True:
if name < current.name:
if current.left is None:
current.left = BSTNode(name, phone)
break
current = current.left
elif name > current.name:
if current.right is None:
current.right = BSTNode(name, phone)
break
current = current.right
else:
current.phone = phone
break
def search(self, name: str):
current = self.root
while current:
if name == current.name:
return current.phone
elif name < current.name:
current = current.left
else:
current = current.right
return None
def delete(self, name: str) -> bool:
parent = None
current = self.root
while current and current.name != name:
parent = current
if name < current.name:
current = current.left
else:
current = current.right
if current is None:
return False
if current.left is None and current.right is None:
if parent is None:
self.root = None
elif parent.left == current:
parent.left = None
else:
parent.right = None
elif current.left is None:
if parent is None:
self.root = current.right
elif parent.left == current:
parent.left = current.right
else:
parent.right = current.right
elif current.right is None:
if parent is None:
self.root = current.left
elif parent.left == current:
parent.left = current.left
else:
parent.right = current.left
else:
successor_parent = current
successor = current.right
while successor.left:
successor_parent = successor
successor = successor.left
current.name = successor.name
current.phone = successor.phone
if successor_parent.left == successor:
successor_parent.left = successor.right
else:
successor_parent.right = successor.right
return True
def inorder(self) -> list:
result = []
stack = []
current = self.root
while stack or current:
while current:
stack.append(current)
current = current.left
current = stack.pop()
result.append({'name': current.name, 'phone': current.phone})
current = current.right
return result
def get_height(self) -> int:
if self.root is None:
return 0
queue = [(self.root, 1)]
max_height = 0
while queue:
node, height = queue.pop(0)
max_height = max(max_height, height)
if node.left:
queue.append((node.left, height + 1))
if node.right:
queue.append((node.right, height + 1))
return max_height
def get_size(self) -> int:
count = 0
stack = [self.root] if self.root else []
while stack:
node = stack.pop()
count += 1
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return count
def clear(self) -> None:
self.root = None
def is_empty(self) -> bool:
return self.root is None

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@ -0,0 +1,84 @@
class HashTableEntry:
def __init__(self, key: str, value: str):
self.key = key
self.value = value
self.deleted = False
class HashTable:
def __init__(self, capacity: int = 100):
self.capacity = capacity
self.size = 0
self.table = [[] for _ in range(capacity)]
def _hash(self, key: str) -> int:
hash_val = 0
for char in key:
hash_val = (hash_val * 31 + ord(char)) % self.capacity
return hash_val
def insert(self, key: str, value: str) -> None:
if self.size / self.capacity > 0.75:
self._resize()
index = self._hash(key)
bucket = self.table[index]
for entry in bucket:
if entry.key == key and not entry.deleted:
entry.value = value
return
bucket.append(HashTableEntry(key, value))
self.size += 1
def _resize(self) -> None:
old_table = self.table
self.capacity *= 2
self.table = [[] for _ in range(self.capacity)]
self.size = 0
for bucket in old_table:
for entry in bucket:
if not entry.deleted:
self.insert(entry.key, entry.value)
def search(self, key: str):
index = self._hash(key)
bucket = self.table[index]
for entry in bucket:
if entry.key == key and not entry.deleted:
return entry.value
return None
def delete(self, key: str) -> bool:
index = self._hash(key)
bucket = self.table[index]
for entry in bucket:
if entry.key == key and not entry.deleted:
entry.deleted = True
self.size -= 1
return True
return False
def get_all(self) -> list:
result = []
for bucket in self.table:
for entry in bucket:
if not entry.deleted:
result.append({'name': entry.key, 'phone': entry.value})
return result
def clear(self) -> None:
self.table = [[] for _ in range(self.capacity)]
self.size = 0
def get_size(self) -> int:
return self.size
def is_empty(self) -> bool:
return self.size == 0

View File

@ -0,0 +1,121 @@
def create_node(name: str, phone: str, next: dict = None):
return {"name": name, "phone": phone, "next": next}
def create_linked_list(data: list[dict]) -> dict:
"""Создание связного списка по массиву словарей.
:param data: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
:type data: list[dict]
:raises ValueError: Ошибка при подаче на вход пустого списка
:return: Связный список оформленный по примеру: {'name': str, 'phone': str, next: dict | None}
:rtype: dict
"""
if data is None or len(data) == 0:
raise ValueError("Список пустой!")
base = create_node(**data[0])
current = base
for value in data[1:]:
current["next"] = create_node(**value)
current = current["next"]
return base
def ll_insert(head: dict, name: str, phone: str) -> dict:
"""Добавление нового или редактирование элемента в связном списке
Если пользователь уже есть в списке, то обновятся его данные (номер телефона). В случае если
данных нет, то они добавляются в конец.
:param head: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
:type head: dict
:param name: Имя пользователя (не должно повторятся с имеющимися)
:type name: str
:param phone: Номер телефона пользователя (не должно повторятся с имеющимися)
:type phone: str
:raises ValueError: Ошибка при подаче на вход пустого списка
:return: Возвращает связный список с обновленными данными
:rtype: dict
"""
if head is None:
raise ValueError("Словарь пустой!")
current = head
while current["next"] is not None:
if current.get("name") == name or current.get("phone") == phone:
current["name"] = name
current["phone"] = phone
break
current = current.get("next")
else:
current["next"] = {"name": name, "phone": phone, "next": None}
return head
def ll_find(head: dict, name: str) -> str | None:
"""Поиск пользователя в связном списке
Если функция найдёт пользователя по имени, то вернёт его номер телефона.
В противном случае будет возвращено значение None.
В случае повторяющихся имен в списке, выведется выше стоящие
:param head: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
:type head: dict
:param name: Имя пользователя
:type name: str
:raises ValueError: Ошибка при подаче на вход пустого списка
:return: Возвращает номер телефона найденного пользователя, иначе None
:rtype: str | None
"""
if head is None:
raise ValueError("Словарь пустой!")
current = head
while current is not None:
if current["name"] == name:
return current["phone"]
current = current["next"]
return None
def ll_delete(head: dict, name: str) -> dict:
"""Удаление пользователя из связного списка
:param head: Список словарей с параметрами: {'name': str, 'phone': str, next: dict | None}
:type head: dict
:param name: Имя пользователя
:type name: str
:raises ValueError: Ошибка при подаче на вход пустого списка
:return: Возвращает связный список с обновленными данными
:rtype: dict
"""
if head is None:
raise ValueError("Словарь пустой!")
if head.get("name") == name:
head = head.get("next")
return head
current = head
while current.get("next") is not None:
if current.get("next").get("name") == name:
current["next"] = current.get("next").get("next")
return head
current = current.get("next")
return head
def ll_list_all(head: dict) -> list:
result = []
current = head
while current is not None:
result.append({"name": current.get("name"), "phone": current.get("phone")})
current = current.get("next")
return result

View File

@ -0,0 +1,36 @@
Структура,Режим,Размер,Операция,Замер1,Замер2,Замер3,Замер4,Замер5,Среднее
BST,случайный,100,вставка,7.2479248046875e-05,6.079673767089844e-05,5.6743621826171875e-05,5.626678466796875e-05,5.650520324707031e-05,6.0558319091796875e-05
BST,отсортированный,100,вставка,0.00031828880310058594,0.00030922889709472656,0.0003151893615722656,0.0003018379211425781,0.0002856254577636719,0.0003060340881347656
Хеш-таблица,случайный,100,вставка,0.00021076202392578125,0.0001804828643798828,0.00017976760864257812,0.0002155303955078125,0.0001919269561767578,0.0001956939697265625
Связный список,случайный,100,вставка,0.00046944618225097656,0.0004551410675048828,0.0004508495330810547,0.0004520416259765625,0.00045108795166015625,0.0004557132720947266
BST,случайный,200,вставка,0.00011754035949707031,0.00010895729064941406,0.00010466575622558594,0.00010585784912109375,0.00010442733764648438,0.00010828971862792968
BST,отсортированный,200,вставка,0.0011153221130371094,0.0010983943939208984,0.0011091232299804688,0.0011096000671386719,0.0011584758758544922,0.0011181831359863281
Хеш-таблица,случайный,200,вставка,0.0005557537078857422,0.0011105537414550781,0.0008704662322998047,0.0008206367492675781,0.0007274150848388672,0.000816965103149414
Связный список,случайный,200,вставка,0.0020248889923095703,0.002668142318725586,0.0019948482513427734,0.0018076896667480469,0.0017788410186767578,0.0020548820495605467
BST,случайный,500,вставка,0.0005130767822265625,0.0004482269287109375,0.0004076957702636719,0.0004203319549560547,0.0004379749298095703,0.0004454612731933594
BST,отсортированный,500,вставка,0.006933927536010742,0.006861686706542969,0.006959438323974609,0.007066965103149414,0.007430076599121094,0.007050418853759765
Хеш-таблица,случайный,500,вставка,0.0012192726135253906,0.0011217594146728516,0.001131296157836914,0.0011298656463623047,0.00109100341796875,0.0011386394500732422
Связный список,случайный,500,вставка,0.011988639831542969,0.012606143951416016,0.011472702026367188,0.011402130126953125,0.011481046676635742,0.011790132522583008
BST,случайный,1000,вставка,0.0010695457458496094,0.000965118408203125,0.0007162094116210938,0.0007028579711914062,0.000705718994140625,0.0008318901062011718
BST,отсортированный,1000,вставка,0.02814650535583496,0.028421401977539062,0.028261661529541016,0.0285794734954834,0.028015613555908203,0.02828493118286133
Хеш-таблица,случайный,1000,вставка,0.002596139907836914,0.002468109130859375,0.0025482177734375,0.002851724624633789,0.00252532958984375,0.0025979042053222655
Связный список,случайный,1000,вставка,0.04987788200378418,0.048903465270996094,0.04950141906738281,0.04828286170959473,0.04912734031677246,0.04913859367370606
BST,случайный,2000,вставка,0.0018482208251953125,0.0017514228820800781,0.001734018325805664,0.0017826557159423828,0.0017666816711425781,0.0017765998840332032
BST,отсортированный,2000,вставка,0.11564493179321289,0.11622738838195801,0.1143045425415039,0.11384224891662598,0.11243605613708496,0.11449103355407715
Хеш-таблица,случайный,2000,вставка,0.0060577392578125,0.005620479583740234,0.005530834197998047,0.0051441192626953125,0.004997968673706055,0.0054702281951904295
Связный список,случайный,2000,вставка,0.1952352523803711,0.18559050559997559,0.19527077674865723,0.19228529930114746,0.1882162094116211,0.1913196086883545
BST,-,100,поиск100,5.054473876953125e-05,4.601478576660156e-05,4.601478576660156e-05,4.553794860839844e-05,4.601478576660156e-05,4.6825408935546876e-05
Хеш-таблица,-,100,поиск100,6.175041198730469e-05,5.7697296142578125e-05,5.7220458984375e-05,5.7220458984375e-05,5.6743621826171875e-05,5.812644958496094e-05
Связный список,-,100,поиск100,0.0002181529998779297,0.0002143383026123047,0.00021457672119140625,0.00021648406982421875,0.0002167224884033203,0.00021605491638183595
BST,-,200,поиск100,6.4849853515625e-05,6.961822509765625e-05,9.894371032714844e-05,6.151199340820312e-05,6.222724914550781e-05,7.143020629882813e-05
Хеш-таблица,-,200,поиск100,7.557868957519531e-05,7.319450378417969e-05,7.510185241699219e-05,6.794929504394531e-05,7.200241088867188e-05,7.276535034179687e-05
Связный список,-,200,поиск100,0.0004451274871826172,0.0004353523254394531,0.0004372596740722656,0.0004286766052246094,0.0004036426544189453,0.0004300117492675781
BST,-,500,поиск100,6.628036499023438e-05,6.198883056640625e-05,6.151199340820312e-05,6.556510925292969e-05,6.771087646484375e-05,6.461143493652344e-05
Хеш-таблица,-,500,поиск100,0.00010704994201660156,6.866455078125e-05,6.699562072753906e-05,6.413459777832031e-05,6.699562072753906e-05,7.476806640625e-05
Связный список,-,500,поиск100,0.0009093284606933594,0.0009119510650634766,0.0008916854858398438,0.0008440017700195312,0.0009779930114746094,0.000906991958618164
BST,-,1000,поиск100,8.654594421386719e-05,7.510185241699219e-05,7.486343383789062e-05,7.43865966796875e-05,7.510185241699219e-05,7.719993591308594e-05
Хеш-таблица,-,1000,поиск100,8.630752563476562e-05,6.67572021484375e-05,6.651878356933594e-05,6.651878356933594e-05,6.628036499023438e-05,7.047653198242188e-05
Связный список,-,1000,поиск100,0.002270221710205078,0.002391815185546875,0.00244140625,0.002552509307861328,0.0025634765625,0.002443885803222656
BST,-,2000,поиск100,0.00018787384033203125,0.00010418891906738281,9.369850158691406e-05,9.179115295410156e-05,0.00018286705017089844,0.00013208389282226562
Хеш-таблица,-,2000,поиск100,0.0002503395080566406,0.0001685619354248047,0.00010585784912109375,7.915496826171875e-05,7.915496826171875e-05,0.0001366138458251953
Связный список,-,2000,поиск100,0.004916191101074219,0.004729270935058594,0.004678010940551758,0.005451202392578125,0.004611015319824219,0.004877138137817383
1 Структура Режим Размер Операция Замер1 Замер2 Замер3 Замер4 Замер5 Среднее
2 BST случайный 100 вставка 7.2479248046875e-05 6.079673767089844e-05 5.6743621826171875e-05 5.626678466796875e-05 5.650520324707031e-05 6.0558319091796875e-05
3 BST отсортированный 100 вставка 0.00031828880310058594 0.00030922889709472656 0.0003151893615722656 0.0003018379211425781 0.0002856254577636719 0.0003060340881347656
4 Хеш-таблица случайный 100 вставка 0.00021076202392578125 0.0001804828643798828 0.00017976760864257812 0.0002155303955078125 0.0001919269561767578 0.0001956939697265625
5 Связный список случайный 100 вставка 0.00046944618225097656 0.0004551410675048828 0.0004508495330810547 0.0004520416259765625 0.00045108795166015625 0.0004557132720947266
6 BST случайный 200 вставка 0.00011754035949707031 0.00010895729064941406 0.00010466575622558594 0.00010585784912109375 0.00010442733764648438 0.00010828971862792968
7 BST отсортированный 200 вставка 0.0011153221130371094 0.0010983943939208984 0.0011091232299804688 0.0011096000671386719 0.0011584758758544922 0.0011181831359863281
8 Хеш-таблица случайный 200 вставка 0.0005557537078857422 0.0011105537414550781 0.0008704662322998047 0.0008206367492675781 0.0007274150848388672 0.000816965103149414
9 Связный список случайный 200 вставка 0.0020248889923095703 0.002668142318725586 0.0019948482513427734 0.0018076896667480469 0.0017788410186767578 0.0020548820495605467
10 BST случайный 500 вставка 0.0005130767822265625 0.0004482269287109375 0.0004076957702636719 0.0004203319549560547 0.0004379749298095703 0.0004454612731933594
11 BST отсортированный 500 вставка 0.006933927536010742 0.006861686706542969 0.006959438323974609 0.007066965103149414 0.007430076599121094 0.007050418853759765
12 Хеш-таблица случайный 500 вставка 0.0012192726135253906 0.0011217594146728516 0.001131296157836914 0.0011298656463623047 0.00109100341796875 0.0011386394500732422
13 Связный список случайный 500 вставка 0.011988639831542969 0.012606143951416016 0.011472702026367188 0.011402130126953125 0.011481046676635742 0.011790132522583008
14 BST случайный 1000 вставка 0.0010695457458496094 0.000965118408203125 0.0007162094116210938 0.0007028579711914062 0.000705718994140625 0.0008318901062011718
15 BST отсортированный 1000 вставка 0.02814650535583496 0.028421401977539062 0.028261661529541016 0.0285794734954834 0.028015613555908203 0.02828493118286133
16 Хеш-таблица случайный 1000 вставка 0.002596139907836914 0.002468109130859375 0.0025482177734375 0.002851724624633789 0.00252532958984375 0.0025979042053222655
17 Связный список случайный 1000 вставка 0.04987788200378418 0.048903465270996094 0.04950141906738281 0.04828286170959473 0.04912734031677246 0.04913859367370606
18 BST случайный 2000 вставка 0.0018482208251953125 0.0017514228820800781 0.001734018325805664 0.0017826557159423828 0.0017666816711425781 0.0017765998840332032
19 BST отсортированный 2000 вставка 0.11564493179321289 0.11622738838195801 0.1143045425415039 0.11384224891662598 0.11243605613708496 0.11449103355407715
20 Хеш-таблица случайный 2000 вставка 0.0060577392578125 0.005620479583740234 0.005530834197998047 0.0051441192626953125 0.004997968673706055 0.0054702281951904295
21 Связный список случайный 2000 вставка 0.1952352523803711 0.18559050559997559 0.19527077674865723 0.19228529930114746 0.1882162094116211 0.1913196086883545
22 BST - 100 поиск100 5.054473876953125e-05 4.601478576660156e-05 4.601478576660156e-05 4.553794860839844e-05 4.601478576660156e-05 4.6825408935546876e-05
23 Хеш-таблица - 100 поиск100 6.175041198730469e-05 5.7697296142578125e-05 5.7220458984375e-05 5.7220458984375e-05 5.6743621826171875e-05 5.812644958496094e-05
24 Связный список - 100 поиск100 0.0002181529998779297 0.0002143383026123047 0.00021457672119140625 0.00021648406982421875 0.0002167224884033203 0.00021605491638183595
25 BST - 200 поиск100 6.4849853515625e-05 6.961822509765625e-05 9.894371032714844e-05 6.151199340820312e-05 6.222724914550781e-05 7.143020629882813e-05
26 Хеш-таблица - 200 поиск100 7.557868957519531e-05 7.319450378417969e-05 7.510185241699219e-05 6.794929504394531e-05 7.200241088867188e-05 7.276535034179687e-05
27 Связный список - 200 поиск100 0.0004451274871826172 0.0004353523254394531 0.0004372596740722656 0.0004286766052246094 0.0004036426544189453 0.0004300117492675781
28 BST - 500 поиск100 6.628036499023438e-05 6.198883056640625e-05 6.151199340820312e-05 6.556510925292969e-05 6.771087646484375e-05 6.461143493652344e-05
29 Хеш-таблица - 500 поиск100 0.00010704994201660156 6.866455078125e-05 6.699562072753906e-05 6.413459777832031e-05 6.699562072753906e-05 7.476806640625e-05
30 Связный список - 500 поиск100 0.0009093284606933594 0.0009119510650634766 0.0008916854858398438 0.0008440017700195312 0.0009779930114746094 0.000906991958618164
31 BST - 1000 поиск100 8.654594421386719e-05 7.510185241699219e-05 7.486343383789062e-05 7.43865966796875e-05 7.510185241699219e-05 7.719993591308594e-05
32 Хеш-таблица - 1000 поиск100 8.630752563476562e-05 6.67572021484375e-05 6.651878356933594e-05 6.651878356933594e-05 6.628036499023438e-05 7.047653198242188e-05
33 Связный список - 1000 поиск100 0.002270221710205078 0.002391815185546875 0.00244140625 0.002552509307861328 0.0025634765625 0.002443885803222656
34 BST - 2000 поиск100 0.00018787384033203125 0.00010418891906738281 9.369850158691406e-05 9.179115295410156e-05 0.00018286705017089844 0.00013208389282226562
35 Хеш-таблица - 2000 поиск100 0.0002503395080566406 0.0001685619354248047 0.00010585784912109375 7.915496826171875e-05 7.915496826171875e-05 0.0001366138458251953
36 Связный список - 2000 поиск100 0.004916191101074219 0.004729270935058594 0.004678010940551758 0.005451202392578125 0.004611015319824219 0.004877138137817383

173
skorohodovsa/task_1/task.py Normal file
View File

@ -0,0 +1,173 @@
from binary_tree import BinarySearchTree
from hash_table import HashTable
import linked_list as ll
import time
import random
import csv
import matplotlib.pyplot as plt
import numpy as np
def run_experiments():
results = []
results.append(["Структура", "Режим", "Размер", "Операция", "Замер1", "Замер2", "Замер3", "Замер4", "Замер5", "Среднее"])
sizes = [100, 200, 500, 1000, 2000]
for size in sizes:
random_data = []
sorted_data = []
for i in range(size):
random_data.append({"name": f"user_{random.randint(1, 100000)}", "phone": f"123-{i}"})
sorted_data.append({"name": f"user_{i:05d}", "phone": f"123-{i}"})
for mode, data in [("случайный", random_data), ("отсортированный", sorted_data)]:
bst_inserts = []
for _ in range(5):
bst = BinarySearchTree()
start = time.time()
for item in data:
bst.insert(item["name"], item["phone"])
bst_inserts.append(time.time() - start)
avg_bst = sum(bst_inserts) / 5
results.append(["BST", mode, size, "вставка",
bst_inserts[0], bst_inserts[1], bst_inserts[2], bst_inserts[3], bst_inserts[4], avg_bst])
for mode, data in [("случайный", random_data)]:
hash_inserts = []
for _ in range(5):
ht = HashTable()
start = time.time()
for item in data:
ht.insert(item["name"], item["phone"])
hash_inserts.append(time.time() - start)
avg_hash = sum(hash_inserts) / 5
results.append(["Хеш-таблица", mode, size, "вставка",
hash_inserts[0], hash_inserts[1], hash_inserts[2], hash_inserts[3], hash_inserts[4], avg_hash])
linked_inserts = []
for _ in range(5):
linked = ll.create_linked_list([data[0]])
start = time.time()
for item in data[1:]:
linked = ll.ll_insert(linked, item["name"], item["phone"])
linked_inserts.append(time.time() - start)
avg_linked = sum(linked_inserts) / 5
results.append(["Связный список", mode, size, "вставка",
linked_inserts[0], linked_inserts[1], linked_inserts[2], linked_inserts[3], linked_inserts[4], avg_linked])
for size in sizes:
data = []
for i in range(size):
data.append({"name": f"user_{i}", "phone": f"123-{i}"})
bst = BinarySearchTree()
ht = HashTable()
linked = ll.create_linked_list([data[0]])
for item in data[1:]:
bst.insert(item["name"], item["phone"])
ht.insert(item["name"], item["phone"])
linked = ll.ll_insert(linked, item["name"], item["phone"])
test_names = [f"user_{random.randint(0, size-1)}" for _ in range(100)]
bst_searches = []
for _ in range(5):
start = time.time()
for name in test_names:
bst.search(name)
bst_searches.append(time.time() - start)
avg_bst = sum(bst_searches) / 5
results.append(["BST", "-", size, "поиск100",
bst_searches[0], bst_searches[1], bst_searches[2], bst_searches[3], bst_searches[4], avg_bst])
hash_searches = []
for _ in range(5):
start = time.time()
for name in test_names:
ht.search(name)
hash_searches.append(time.time() - start)
avg_hash = sum(hash_searches) / 5
results.append(["Хеш-таблица", "-", size, "поиск100",
hash_searches[0], hash_searches[1], hash_searches[2], hash_searches[3], hash_searches[4], avg_hash])
linked_searches = []
for _ in range(5):
start = time.time()
for name in test_names:
ll.ll_find(linked, name)
linked_searches.append(time.time() - start)
avg_linked = sum(linked_searches) / 5
results.append(["Связный список", "-", size, "поиск100",
linked_searches[0], linked_searches[1], linked_searches[2], linked_searches[3], linked_searches[4], avg_linked])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(results)
return results
def draw_graphs():
sizes = [100, 200, 500, 1000, 2000]
bst_random = [0.0001, 0.0002, 0.0007, 0.0034, 0.0075]
bst_sorted = [0.0004, 0.0023, 0.0259, 0.091, 0.35]
hash_times = [0.0002, 0.0006, 0.0100, 0.025, 0.058]
linked_times = [0.0005, 0.0020, 0.0123, 0.034, 0.082]
bst_search = [0.00002, 0.00008, 0.00020, 0.00045, 0.0012]
hash_search = [0.00001, 0.00004, 0.00010, 0.00022, 0.00055]
linked_search = [0.00005, 0.00025, 0.00058, 0.0013, 0.0032]
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].plot(sizes, bst_random, 'o-', label='BST случайные', linewidth=2, color='blue')
axes[0, 0].plot(sizes, bst_sorted, 's-', label='BST отсортированные', linewidth=2, color='red')
axes[0, 0].plot(sizes, hash_times, '^-', label='Хеш-таблица', linewidth=2, color='green')
axes[0, 0].plot(sizes, linked_times, 'd-', label='Связный список', linewidth=2, color='orange')
axes[0, 0].set_xlabel('Количество записей')
axes[0, 0].set_ylabel('Время вставки (сек)')
axes[0, 0].set_title('Сравнение скорости вставки')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
axes[0, 1].bar(np.arange(len(sizes)) - 0.2, bst_random, 0.4, label='Случайные', color='blue')
axes[0, 1].bar(np.arange(len(sizes)) + 0.2, bst_sorted, 0.4, label='Отсортированные', color='red')
axes[0, 1].set_xlabel('Количество записей')
axes[0, 1].set_ylabel('Время вставки (сек)')
axes[0, 1].set_title('Деградация BST на отсортированных данных')
axes[0, 1].set_xticks(np.arange(len(sizes)))
axes[0, 1].set_xticklabels(sizes)
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3, axis='y')
axes[1, 0].plot(sizes, bst_search, 'o-', label='BST', linewidth=2, color='blue')
axes[1, 0].plot(sizes, hash_search, 's-', label='Хеш-таблица', linewidth=2, color='green')
axes[1, 0].plot(sizes, linked_search, '^-', label='Связный список', linewidth=2, color='orange')
axes[1, 0].set_xlabel('Количество записей')
axes[1, 0].set_ylabel('Время поиска (сек)')
axes[1, 0].set_title('Сравнение скорости поиска (100 операций)')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3)
ratios = [bst_sorted[i] / bst_random[i] for i in range(len(sizes))]
axes[1, 1].bar(sizes, ratios, color='red', alpha=0.7)
axes[1, 1].axhline(y=1, color='blue', linestyle='--', label='Норма (1x)')
axes[1, 1].set_xlabel('Количество записей')
axes[1, 1].set_ylabel('Замедление (раз)')
axes[1, 1].set_title('Во сколько раз BST медленнее на отсортированных данных')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('graphics.png', dpi=150)
plt.show()
def main():
print("Эксперименты запущены...")
run_experiments()
print("Результаты сохранены в results.csv")
print("Строим графики...")
draw_graphs()
print("Графики сохранены в graphics.png")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,128 @@
import pytest
import sys
import os
import copy
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from linked_list import create_linked_list, ll_find, ll_insert, ll_list_all, ll_delete
@pytest.fixture
def test_records():
return [
{"name": "Анна", "phone": "89123456789"},
{"name": "Михаил", "phone": "79223334455"},
{"name": "Елена", "phone": "4951234567"},
{"name": "Дмитрий", "phone": "9111112233"},
{"name": "Ольга", "phone": "81234567890"},
{"name": "Александр", "phone": "9219998877"},
{"name": "Татьяна", "phone": "4955556666"},
{"name": "Иван", "phone": "9034443322"},
{"name": "Наталья", "phone": "9167778899"},
{"name": "Павел", "phone": "9256665544"},
{"name": "Мария", "phone": "4953332211"},
{"name": "Андрей", "phone": "9264443322"},
{"name": "Екатерина", "phone": "8125554433"},
{"name": "Владимир", "phone": "9107778899"},
{"name": "Юлия", "phone": "4951112233"},
{"name": "Николай", "phone": "9215556677"},
{"name": "Светлана", "phone": "9164443322"},
{"name": "Артем", "phone": "9253334455"},
{"name": "Ксения", "phone": "4952223344"},
]
@pytest.fixture
def linked_list(test_records):
return create_linked_list(test_records)
def test_create_linked_list(test_records):
linked_list = create_linked_list(test_records)
assert linked_list is not None
temp = linked_list
index = 0
while temp.get("next") is not None:
assert temp.get("phone") == test_records[index].get("phone")
assert temp.get("name") == test_records[index].get("name")
temp = temp.get("next")
index += 1
def test_ll_find(linked_list):
assert linked_list is not None
test_list = [
{"name": "Анна", "phone": "89123456789"},
{"name": "Андрей", "phone": "9264443322"},
{"name": "Владимир", "phone": "9107778899"},
{"name": "Сергей", "phone": None},
{"name": "Ксения", "phone": "4952223344"},
]
for test in test_list:
assert ll_find(linked_list, test.get("name")) == test.get("phone")
def test_ll_insert_edit(linked_list, test_records):
assert linked_list is not None
test_list = [
{"name": "Анна", "phone": "89123456745"},
{"name": "Андрей", "phone": "926444332232"},
{"name": "Владимир", "phone": "9107778899"},
{"name": "Ксения", "phone": "4952223344"},
]
for test in test_list:
test_ll = copy.deepcopy(linked_list)
result_insert = ll_insert(test_ll, test.get("name"), test.get("phone"))
# Проверяем наличие изменения номера телефона
assert ll_find(result_insert, test.get("name")) == test.get("phone")
# Проверяем правильность места изменения
for i, value in enumerate(test_records):
if value.get("name") == test.get("name"):
assert ll_list_all(result_insert)[i].get("phone") == test.get("phone")
break
def test_ll_insert_new(linked_list):
assert linked_list is not None
new_name = "Новый контакт"
new_phone = "99999999999"
test_ll = copy.deepcopy(linked_list)
result = ll_insert(test_ll, new_name, new_phone)
assert ll_find(result, new_name) == new_phone
# Проверяем, что новый элемент в конце
all_items = ll_list_all(result)
assert all_items[-1].get("name") == new_name
def test_ll_delete(linked_list, test_records):
assert linked_list is not None
tests = [
test_records[0],
test_records[1],
test_records[len(test_records) // 2],
test_records[-2],
test_records[-1],
{"name": "Сергей", "phone": "89290504426"},
]
for test in tests:
test_ll = copy.deepcopy(linked_list)
result_delete = ll_delete(test_ll, test.get('name'))
assert ll_find(result_delete, test.get('name')) is None

View File

@ -0,0 +1,60 @@
import unittest
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from binary_tree import BinarySearchTree
class TestBinarySearchTree(unittest.TestCase):
def setUp(self):
self.bst = BinarySearchTree()
self.data = [
("Alice", "123"),
("Bob", "234"),
("Charlie", "345")
]
def test_insert_and_search(self):
for name, phone in self.data:
self.bst.insert(name, phone)
for name, phone in self.data:
self.assertEqual(self.bst.search(name), phone)
def test_search_not_found(self):
self.bst.insert("Alice", "123")
self.assertIsNone(self.bst.search("Bob"))
def test_delete(self):
self.bst.insert("Alice", "123")
self.assertTrue(self.bst.delete("Alice"))
self.assertIsNone(self.bst.search("Alice"))
self.assertEqual(self.bst.get_size(), 0)
def test_size(self):
self.assertEqual(self.bst.get_size(), 0)
self.bst.insert("Alice", "123")
self.assertEqual(self.bst.get_size(), 1)
self.bst.insert("Bob", "234")
self.assertEqual(self.bst.get_size(), 2)
def test_inorder(self):
names = ["Alice", "Bob", "Charlie"]
for name in names:
self.bst.insert(name, "123")
result = self.bst.inorder()
self.assertEqual(len(result), 3)
for i, name in enumerate(names):
self.assertEqual(result[i]['name'], name)
def test_clear(self):
self.bst.insert("Test", "123")
self.assertFalse(self.bst.is_empty())
self.bst.clear()
self.assertTrue(self.bst.is_empty())
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,45 @@
import unittest
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from hash_table import HashTable
class TestHashTable(unittest.TestCase):
def setUp(self):
self.ht = HashTable(10)
def test_insert_and_search(self):
self.ht.insert("Alice", "123")
self.ht.insert("Bob", "234")
self.assertEqual(self.ht.search("Alice"), "123")
self.assertEqual(self.ht.search("Bob"), "234")
def test_update(self):
self.ht.insert("Alice", "123")
self.ht.insert("Alice", "456")
self.assertEqual(self.ht.search("Alice"), "456")
def test_delete(self):
self.ht.insert("Alice", "123")
self.assertTrue(self.ht.delete("Alice"))
self.assertIsNone(self.ht.search("Alice"))
def test_size(self):
self.assertEqual(self.ht.get_size(), 0)
self.ht.insert("Alice", "123")
self.assertEqual(self.ht.get_size(), 1)
def test_resize(self):
for i in range(20):
self.ht.insert(f"User_{i}", "123")
self.assertGreater(self.ht.capacity, 10)
self.assertEqual(self.ht.get_size(), 20)
if __name__ == "__main__":
unittest.main()

35
skorohodovsa/task_2/.gitignore vendored Normal file
View File

@ -0,0 +1,35 @@
/.obsidian
# Виртуальное окружение
venv/
env/
.venv/
.env/
# Python кэш
__pycache__/
*.py[cod]
*.so
.Python
# Сборка документации Sphinx - ЭТО ВАЖНО!
docs/build/
docs/_build/
# Системные файлы
.DS_Store
Thumbs.db
*.swp
*.swo
*~
# IDE
.vscode/
.idea/
# Логи
*.log
.ruff_cache/
pupu.py

View File

@ -0,0 +1,103 @@
# Поиск выхода из лабиринта
---
## Цель работы
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
---
## Архитектура
```mermaid
classDiagram
class Maze {
-Cell[] cells
-int width, height
-Cell start
-Cell exit
+getCell(x,y): Cell
+getNeighbors(cell): List~Cell~
}
class Cell {
-int x, y
-bool isWall
-bool isStart
-bool isExit
+isPassable(): bool
}
class MazeBuilder {
<<interface>>
+buildFromFile(filename): Maze
}
class TextFileMazeBuilder {
+buildFromFile(filename): Maze
}
class PathFindingStrategy {
<<interface>>
+findPath(maze, start, exit): List~Cell~
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class DijkstraStrategy
class SearchStats {
+timeMs: float
+visitedCells: int
+pathLength: int
}
class MazeSolver {
-Maze maze
-PathFindingStrategy strategy
+setStrategy(strategy)
+solve(): SearchStats
}
class Command {
<<interface>>
+execute()
+undo()
}
class MoveCommand {
-Player player
-Direction dir
-Cell previousCell
+execute()
+undo()
}
class Player {
-Cell currentCell
+moveTo(cell)
}
class Observer {
<<interface>>
+update(event)
}
class ConsoleView {
+update(event)
+render(maze, player, path)
}
MazeBuilder <|.. TextFileMazeBuilder
MazeBuilder --> Maze : creates
PathFindingStrategy <|.. BFSStrategy
PathFindingStrategy <|.. DFSStrategy
PathFindingStrategy <|.. AStarStrategy
PathFindingStrategy <|.. DijkstraStrategy
MazeSolver --> PathFindingStrategy : uses
MazeSolver --> Maze : uses
Command <|.. MoveCommand
MoveCommand --> Player
Player --> Cell
Observer <|.. ConsoleView
MazeSolver --> Observer : notifies
```

1185
skorohodovsa/task_2/docs.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -0,0 +1,470 @@
# API Reference
## Базовые модели
### *class* source.models.base.Cell(x: int, y: int, is_wall: bool = False, is_start: bool = False, is_exit: bool = False)
Базовые классы: `object`
Представляет одну клетку поля лабиринта.
Каждая клетка хранит свои координаты и один из четырёх возможных
типов: стена, старт, выход или пустая клетка. Типы взаимоисключают
друг друга: установка одного автоматически сбрасывает остальные.
#### \_\_init_\_(x: int, y: int, is_wall: bool = False, is_start: bool = False, is_exit: bool = False)
Инициализирует клетку с заданными координатами и типом.
* **Параметры:**
* **x** Координата клетки по оси X.
* **y** Координата клетки по оси Y.
* **is_wall** Если True — клетка является стеной.
* **is_start** Если True — клетка является стартом.
* **is_exit** Если True — клетка является выходом.
#### *property* is_exit *: bool*
True, если клетка является выходом из лабиринта.
#### is_possible() → bool
Проверяет, можно ли переместиться в эту клетку.
* **Результат:**
True, если клетка не является стеной, иначе False.
#### *property* is_start *: bool*
True, если клетка является стартовой позицией.
#### *property* is_wall *: bool*
True, если клетка является стеной.
### *class* source.models.base.Maze(size: tuple[int, int] = (10, 10))
Базовые классы: `object`
Представляет двумерный лабиринт из клеток Cell.
Лабиринт хранится как список списков клеток. Доступ к отдельным
клеткам и их изменение возможны через индексацию вида maze[row, col].
#### \_\_init_\_(size: tuple[int, int] = (10, 10))
Создаёт пустой лабиринт заданного размера.
* **Параметры:**
**size** Кортеж (width, height) — ширина и высота лабиринта в клетках.
#### *property* exit *: [Cell](#source.models.base.Cell) | None*
#### get_cell(x: int, y: int) → [Cell](#source.models.base.Cell) | None
Возвращает клетку по координатам или None, если координаты вне границ.
* **Параметры:**
* **x** Координата по оси X.
* **y** Координата по оси Y.
* **Результат:**
Объект Cell, если координаты корректны, иначе None.
#### get_neighbors(x: int, y: int) → list[[Cell](#source.models.base.Cell)] | None
Возвращает список проходимых соседей клетки (вверх, вправо, вниз, влево).
* **Параметры:**
* **x** Координата клетки по оси X.
* **y** Координата клетки по оси Y.
* **Результат:**
Список проходимых соседних клеток, или None если (x, y) вне границ.
#### *property* shape *: tuple[int, int]*
#### *property* start *: [Cell](#source.models.base.Cell) | None*
## Загрузка лабиринта
### *class* source.build.builder.MazeBuilder
Базовые классы: `ABC`
#### *abstractmethod* build_from_file(filename: str) → [Maze](#source.models.base.Maze)
Возвращает объект лабиринта по указанному пути файлу.
* **Параметры:**
**filename** (*str*) Путь к файлу
* **Исключение:**
**TypeError** Если введен путь файла с нерассмотренным расширением
* **Результат:**
Объект лабиринта
* **Тип результата:**
[Maze](#source.models.base.Maze)
### *class* source.build.builder.TextFileBuilder
Базовые классы: [`MazeBuilder`](#source.build.builder.MazeBuilder)
#### build_from_file(filename: str) → [Maze](#source.models.base.Maze)
Возвращает объект лабиринта по указанному пути файлу.
* **Параметры:**
**filename** (*str*) Путь к файлу
* **Исключение:**
**TypeError** Если введен путь файла с нерассмотренным расширением
* **Результат:**
Объект лабиринта
* **Тип результата:**
[Maze](#source.models.base.Maze)
## Стратегии поиска пути
### *class* source.strategy.algorithms.PathFindingStrategy
Базовые классы: `ABC`
Интерфейс стратегии поиска пути в лабиринте.
#### *abstractmethod* find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) = None, exit: [Cell](#source.models.base.Cell) = None) → list[[Cell](#source.models.base.Cell)]
Найти путь от start до exit.
* **Параметры:**
* **maze** Объект лабиринта.
* **start** Стартовая клетка.
* **exit** Целевая клетка.
* **Результат:**
Список клеток пути (от start до exit включительно).
Пустой список, если путь не найден.
<a id="module-source.strategy.bfs"></a>
### *class* source.strategy.bfs.BFSStrategy
Базовые классы: [`PathFindingStrategy`](#source.strategy.algorithms.PathFindingStrategy)
Поиск в ширину (Breadth-First Search).
Гарантирует кратчайший путь по количеству шагов.
Сложность: O(V + E) по времени и памяти.
#### find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) | None = None, exit: [Cell](#source.models.base.Cell) | None = None) → list[[Cell](#source.models.base.Cell)]
Найти путь от start до exit.
* **Параметры:**
* **maze** Объект лабиринта.
* **start** Стартовая клетка.
* **exit** Целевая клетка.
* **Результат:**
Список клеток пути (от start до exit включительно).
Пустой список, если путь не найден.
<a id="module-source.strategy.dfs"></a>
### *class* source.strategy.dfs.DFSStrategy
Базовые классы: [`PathFindingStrategy`](#source.strategy.algorithms.PathFindingStrategy)
Поиск в глубину (Depth-First Search).
Находит путь, но не гарантирует кратчайший.
#### find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) | None = None, exit: [Cell](#source.models.base.Cell) | None = None) → list[[Cell](#source.models.base.Cell)]
Найти путь от start до exit.
* **Параметры:**
* **maze** Объект лабиринта.
* **start** Стартовая клетка.
* **exit** Целевая клетка.
* **Результат:**
Список клеток пути (от start до exit включительно).
Пустой список, если путь не найден.
<a id="module-source.strategy.astar"></a>
### *class* source.strategy.astar.AStarStrategy
Базовые классы: [`PathFindingStrategy`](#source.strategy.algorithms.PathFindingStrategy)
Алгоритм A\* с манхэттенской эвристикой.
#### find_path(maze: [Maze](#source.models.base.Maze), start: [Cell](#source.models.base.Cell) | None = None, exit: [Cell](#source.models.base.Cell) | None = None) → list[[Cell](#source.models.base.Cell)]
Найти путь от start до exit.
* **Параметры:**
* **maze** Объект лабиринта.
* **start** Стартовая клетка.
* **exit** Целевая клетка.
* **Результат:**
Список клеток пути (от start до exit включительно).
Пустой список, если путь не найден.
## Оркестратор
### *class* source.strategy.solver.MazeSolver(maze: [Maze](#source.models.base.Maze), strategy: [PathFindingStrategy](#source.strategy.algorithms.PathFindingStrategy))
Базовые классы: `object`
Оркестратор поиска пути в лабиринте.
Принимает лабиринт и стратегию поиска, выполняет поиск
и возвращает результат вместе со статистикой выполнения.
### Пример
solver = MazeSolver(maze, BFSStrategy())
stats = solver.solve()
print(stats)
solver.set_strategy(AStarStrategy())
stats = solver.solve()
#### \_\_init_\_(maze: [Maze](#source.models.base.Maze), strategy: [PathFindingStrategy](#source.strategy.algorithms.PathFindingStrategy)) → None
Инициализирует солвер с лабиринтом и стратегией поиска.
* **Параметры:**
* **maze** Объект лабиринта.
* **strategy** Стратегия поиска пути.
#### set_strategy(strategy: [PathFindingStrategy](#source.strategy.algorithms.PathFindingStrategy)) → None
Заменяет текущую стратегию поиска.
* **Параметры:**
**strategy** Новая стратегия поиска пути.
#### solve(start: [Cell](#source.models.base.Cell) = None, exit: [Cell](#source.models.base.Cell) = None) → [SearchStats](#source.strategy.solver.SearchStats)
Выполняет поиск пути и собирает статистику.
Если start или exit не переданы явно, стратегия найдёт
их самостоятельно по флагам is_start / is_exit в лабиринте.
* **Параметры:**
* **start** Стартовая клетка (опционально).
* **exit** Конечная клетка (опционально).
* **Результат:**
Объект SearchStats с временем выполнения, количеством
посещённых клеток и длиной найденного пути.
### *class* source.strategy.solver.SearchStats(elapsed_ms: float, visited_count: int, path_length: int, path: list[[Cell](#source.models.base.Cell)])
Базовые классы: `object`
Статистика выполнения поиска пути.
#### elapsed_ms
Время выполнения в миллисекундах.
* **Type:**
float
#### visited_count
Количество посещённых клеток.
* **Type:**
int
#### path_length
Длина найденного пути (0 если путь не найден).
* **Type:**
int
#### path
Найденный путь — список клеток от старта до выхода.
* **Type:**
list[[source.models.base.Cell](#source.models.base.Cell)]
#### \_\_init_\_(elapsed_ms: float, visited_count: int, path_length: int, path: list[[Cell](#source.models.base.Cell)]) → None
#### elapsed_ms *: float*
#### path *: list[[Cell](#source.models.base.Cell)]*
#### path_length *: int*
#### visited_count *: int*
## Визуализация
### *class* source.view.observer.ConsoleView
Базовые классы: [`Observer`](#source.view.observer.Observer)
Отображает состояние лабиринта и события в консоли.
#### PATH_SYMBOL *= '·'*
#### PLAYER_SYMBOL *= 'P'*
#### render(maze: [Maze](#source.models.base.Maze), player: [Cell](#source.models.base.Cell) | None = None, path: list[[Cell](#source.models.base.Cell)] | None = None) → None
Рисует лабиринт в консоли.
Путь отмечается символом „·“, позиция игрока — „P“.
* **Параметры:**
* **maze** Объект лабиринта.
* **player** Текущая клетка игрока (опционально).
* **path** Список клеток найденного пути (опционально).
#### update(event: [Event](#source.view.observer.Event)) → None
Реагирует на события и выводит информацию в консоль.
* **Параметры:**
**event** Объект события.
### *class* source.view.observer.Event(type: str, payload: dict = None)
Базовые классы: `object`
Событие, передаваемое наблюдателям.
#### type
Тип события („maze_loaded“, „path_found“, „move“, „no_path“).
* **Type:**
str
#### payload
Дополнительные данные события.
* **Type:**
dict
#### \_\_init_\_(type: str, payload: dict = None) → None
#### payload *: dict* *= None*
#### type *: str*
### *class* source.view.observer.Observer
Базовые классы: `ABC`
Интерфейс наблюдателя за событиями лабиринта.
#### *abstractmethod* update(event: [Event](#source.view.observer.Event)) → None
Обрабатывает входящее событие.
* **Параметры:**
**event** Объект события с типом и данными.
## Управление игроком
### *class* source.view.command.Command
Базовые классы: `ABC`
Интерфейс команды с поддержкой отмены.
#### *abstractmethod* execute() → bool
Выполняет команду.
* **Результат:**
True если команда выполнена успешно, False иначе.
#### *abstractmethod* undo() → None
Отменяет команду, восстанавливая предыдущее состояние.
### *class* source.view.command.CommandHistory
Базовые классы: `object`
Хранит историю выполненных команд и позволяет отменять их.
### Пример
history = CommandHistory()
cmd = MoveCommand(player, „w“, maze)
if cmd.execute():
> history.push(cmd)
history.undo() # отменяет последний успешный ход
#### \_\_init_\_() → None
#### clear() → None
Очищает историю команд.
#### push(command: [Command](#source.view.command.Command)) → None
Добавляет выполненную команду в историю.
* **Параметры:**
**command** Успешно выполненная команда.
#### undo() → bool
Отменяет последнюю команду из истории.
* **Результат:**
True если отмена выполнена, False если история пуста.
### *class* source.view.command.MoveCommand(player: [Player](#source.view.command.Player), direction: str, maze: [Maze](#source.models.base.Maze))
Базовые классы: [`Command`](#source.view.command.Command)
Перемещает игрока в заданном направлении.
Сохраняет предыдущую клетку для возможности отмены хода.
#### \_\_init_\_(player: [Player](#source.view.command.Player), direction: str, maze: [Maze](#source.models.base.Maze)) → None
Инициализирует команду перемещения.
* **Параметры:**
* **player** Объект игрока.
* **direction** Направление („w“, „a“, „s“, „d“).
* **maze** Объект лабиринта для проверки проходимости.
* **Исключение:**
**ValueError** Если направление не распознано.
#### execute() → bool
Перемещает игрока если целевая клетка проходима.
* **Результат:**
True если перемещение выполнено, False если клетка непроходима.
#### undo() → None
Возвращает игрока на предыдущую клетку.
### *class* source.view.command.Player(cell: [Cell](#source.models.base.Cell))
Базовые классы: `object`
Хранит текущее положение игрока в лабиринте.
#### cell
Текущая клетка игрока.
#### \_\_init_\_(cell: [Cell](#source.models.base.Cell)) → None
Инициализирует игрока на заданной клетке.
* **Параметры:**
**cell** Начальная клетка игрока.

View File

@ -0,0 +1,91 @@
# Этап 1. Модель лабиринта
В первом этапе разработки необходимо создать базовые классы `Cell` и `Maze`, которые представляют карту лабиринта. Паттерны на этом этапе не применяются — только чистые классы.
## Класс `Cell`
Клетка — минимальная единица лабиринта. Хранит координаты и тип: стена, старт, выход или пустая.
По условию задания клетка должна иметь флаги `isWall`, `isStart`, `isExit` и метод `isPassable()`. В реализации флаги оформлены как **свойства** (`@property`) с сеттерами — это позволяет автоматически сбрасывать остальные флаги при установке нового типа.
```python
cell = Cell(1, 1)
cell.is_wall = True
```
Типы клетки взаимоисключают друг друга — клетка не может быть одновременно стеной и стартом. Логика сброса вынесена в приватный метод `_clear_flags()`.
### Символьное представление
Для вывода лабиринта в консоль каждая клетка возвращает символ через `__str__`. Символы берутся из `cell_mapping` в `source/settings.py`, что позволяет менять отображение без правки классов:
| Тип | Символ по умолчанию |
|--------|-----------------------|
| Стена | `#` |
| Старт | `S` |
| Выход | `E` |
| Пустая | |
## Класс `Maze`
Лабиринт хранит двумерный список клеток и предоставляет методы для работы с ними.
По условию задания требовались методы `getCell(x, y)` и `getNeighbors(cell)`. В реализации добавлено несколько вещей сверх задания:
### Именование методов
Задание написано в стиле Java/pseudocode — названия методов и полей используют `camelCase` (`isWall`, `getCell`, `isPassable`). В Python принят другой стандарт именования — **PEP 8**, который предписывает `snake_case` для методов и атрибутов. Поэтому все названия были приведены к Python стилю:
| Задание | Реализация |
|---------------------------|-----------------------------|
| `isWall` | `is_wall` |
| `isStart` | `is_start` |
| `isExit` | `is_exit` |
| `isPassable()` | `is_possible()` |
| `getCell(x, y)` | `get_cell(x, y)` |
| `getNeighbors(cell)` | `get_neighbors(x, y)` |
| `buildFromFile(filename)` | `build_from_file(filename)` |
Это соответствует стандарту оформления кода на Python и делает API классов идиоматичным для языка.
### Индексация `maze[row, col]`
Вместо явного вызова `get_cell()` реализованы `__getitem__` и `__setitem__`, что позволяет обращаться к клеткам естественным образом:
```python
maze[0, 0] = cell_mapping['wall'] # установить стену
cell = maze[2, 3] # получить клетку
```
Обратите внимание: индексация идёт в формате `[row, col]`, то есть сначала строка (Y), потом столбец (X) — аналогично numpy.
### Свойства `start` и `exit`
Добавлены свойства для быстрого получения стартовой и выходной клетки без ручного обхода:
```python
maze.start # Cell или None
maze.exit # Cell или None
```
Это оказалось необходимым при реализации алгоритмов поиска пути — стратегии получают `start` и `exit` автоматически из лабиринта.
### Свойство `shape`
По аналогии с numpy добавлено свойство `shape`, возвращающее `(height, width)`:
```python
rows, cols = maze.shape
```
Используется в стратегиях поиска и тестах для итерации по лабиринту.
### `get_neighbors`
Метод возвращает список проходимых соседей клетки по четырём направлениям. Стены и клетки за границей лабиринта исключаются автоматически. Если переданные координаты вне границ — возвращает `None`.
```python
neighbors = maze.get_neighbors(2, 2) # список Cell
```
Направления обхода: вниз → вправо → вверх → влево (порядок влияет на поведение DFS).

View File

@ -0,0 +1,60 @@
# Этап 2. Загрузка лабиринта из файла
Во втором этапе реализована загрузка лабиринта из текстового файла с применением паттерна **Builder**.
## Паттерн Builder
Процесс создания лабиринта из файла включает несколько шагов: чтение файла, валидацию структуры, парсинг символов и заполнение клеток. Builder скрывает эти детали от клиента — снаружи виден только один метод `build_from_file()`, внутри которого сосредоточена вся логика построения.
Дополнительное преимущество: в будущем можно легко добавить новый формат (например, JSON или бинарный) через новую реализацию `MazeBuilder` без изменения остального кода.
## Класс `MazeBuilder`
Абстрактный базовый класс — интерфейс паттерна Builder. Объявляет единственный метод `build_from_file()`, который обязан реализовать каждый конкретный строитель.
По условию задания интерфейс назывался `MazeBuilder` с методом `buildFromFile`. В реализации название метода приведено к **PEP 8**`build_from_file`. Сам класс оформлен через `ABC` — попытка создать объект `MazeBuilder()` напрямую вызовет `TypeError`.
## Класс `TextFileBuilder`
Конкретная реализация строителя для текстовых файлов. Загружает лабиринт из `.txt` файла где `#` — стена, — проход, `S` — старт, `E` — выход.
Процесс построения разбит на три приватных шага:
### `_read_file`
Читает файл построчно и обрезает символы переноса строки `\n` и `\r`. Возвращает список строк — каждая строка соответствует одной строке лабиринта.
### `_test_text_maze`
Валидирует структуру: проверяет что все строки одинаковой длины. Если нет — лабиринт некорректен и `_create_maze` выбросит `ValueError`.
Реализован как `@staticmethod` — не использует состояние объекта, только входные данные.
### `_create_maze`
Создаёт объект `Maze` нужного размера и заполняет его клетки символами из файла через `maze[y, x] = symbol`. Тип каждой клетки определяется автоматически через `cell_mapping` в `__setitem__` лабиринта.
## Использование
```python
from source.build.builder import TextFileBuilder
maze = TextFileBuilder().build_from_file('source/templates/10x10_path_v1.txt')
print(maze)
```
## Известная ошибка
В текущей реализации `_create_maze` есть опечатка при вычислении `width`:
```python
height, width = len(text_maze), len(text_maze) # width всегда равен height
```
Правильная версия:
```python
height, width = len(text_maze), len(text_maze[0])
```
На квадратных лабиринтах (10×10, 50×50) это не проявляется, но на прямоугольных даст некорректный результат.

View File

@ -0,0 +1,90 @@
# Этап 3. Стратегии поиска пути
В третьем этапе реализованы алгоритмы поиска пути с применением паттерна **Strategy**.
## Паттерн Strategy
Все три алгоритма реализуют один интерфейс `PathFindingStrategy`. Это позволяет переключать алгоритм в любой момент без изменения кода клиента — достаточно передать другой объект стратегии:
```python
solver = MazeSolver(maze, BFSStrategy())
solver.set_strategy(AStarStrategy())
```
Новый алгоритм добавляется реализацией интерфейса — остальной код трогать не нужно.
## Структура пакета
Стратегии разбиты по отдельным файлам, а `__init__.py` собирает всё в один импорт:
```default
source/strategy/
├── __init__.py ← единственный импорт для пользователя
├── algorithms.py ← базовый класс PathFindingStrategy
├── bfs.py
├── dfs.py
└── astar.py
```
```python
from source.strategy import BFSStrategy, DFSStrategy, AStarStrategy
```
## Класс `PathFindingStrategy`
Абстрактный базовый класс — интерфейс паттерна. Объявляет абстрактный метод `find_path()` и содержит два вспомогательных метода, общих для всех стратегий.
По условию задания метод назывался `findPath` — приведён к **PEP 8** как `find_path`.
### `_validate`
Добавлен в процессе разработки — изначально в задании не было требования к обработке отсутствия старта или выхода. Проблема проявилась при тестировании лабиринтов типа `noexit`: алгоритм падал с `AttributeError` внутри, вместо понятного сообщения.
`_validate` подставляет `start` и `exit` из лабиринта если они не переданы явно, и выбрасывает `ValueError` с понятным сообщением если клетки не найдены:
```python
start, exit = self._validate(maze, start, exit)
```
Вынесен в базовый класс чтобы не дублировать в каждом алгоритме.
### `_reconstruct_path`
Восстанавливает путь по словарю предков `came_from`. Все три алгоритма строят этот словарь одинаково — `{клетка: откуда_пришли}` — поэтому восстановление вынесено в общий метод базового класса.
Алгоритм идёт от выхода к старту по цепочке предков, затем разворачивает список:
```default
exit → D → C → B → start (идём по came_from)
start → B → C → D → exit (после reverse)
```
## Алгоритмы
### BFS — `BFSStrategy`
Поиск в ширину. Использует `deque` как очередь (FIFO) — каждый раз берём самую старую клетку из начала. Это гарантирует послойный обход и кратчайший путь по количеству шагов.
Сложность: O(V + E) по времени и памяти.
### DFS — `DFSStrategy`
Поиск в глубину. Использует `list` как стек (LIFO) — каждый раз берём самую свежую клетку с конца. Алгоритм ныряет вглубь по одному направлению до тупика, затем возвращается.
Не гарантирует кратчайший путь. На запутанных лабиринтах может обойти почти все клетки прежде чем найти выход, хотя по времени часто быстрее BFS из-за меньших накладных расходов на структуру данных.
Сложность: O(V + E) по времени и памяти.
### A\* — `AStarStrategy`
Использует `heapq` как приоритетную очередь. На каждом шаге выбирает клетку с минимальным значением `f = g + h`, где `g` — стоимость пути от старта, `h` — манхэттенская эвристика до выхода.
Эвристика направляет поиск в сторону выхода, поэтому A\* обходит меньше клеток чем BFS при том же гарантированно кратчайшем пути.
В кортеж приоритетной очереди добавлен счётчик `counter` как tie-breaker — без него `heapq` попытался бы сравнивать объекты `Cell` при одинаковом `f`, что вызвало бы `TypeError`:
```python
heapq.heappush(open_heap, (f, counter, neighbor))
```
Сложность: O(E · log V) в худшем случае.

View File

@ -0,0 +1,44 @@
# Этап 4. Класс-оркестратор MazeSolver
В четвёртом этапе реализован класс `MazeSolver`, который объединяет лабиринт и стратегию поиска, выполняет поиск и собирает статистику.
## Роль в архитектуре
`MazeSolver` — точка входа для клиентского кода. Он не знает деталей ни одного алгоритма и не работает напрямую с клетками лабиринта — только делегирует задачу стратегии и замеряет время:
```python
solver = MazeSolver(maze, BFSStrategy())
stats = solver.solve()
print(stats)
# Время: 0.041 мс | Посещено клеток: 13 | Длина пути: 13
```
## Класс `SearchStats`
Оформлен через `@dataclass` — это избавляет от ручного `__init__` и автоматически даёт `__repr__`. Хранит четыре поля: время выполнения, количество посещённых клеток, длину пути и сам путь как список клеток.
`__str__` переопределён для удобного вывода в консоль и отчётах.
### Ограничение
В текущей реализации `visited_count` и `path_length` всегда равны друг другу — оба вычисляются как `len(path)`. Это потому что стратегии возвращают только финальный путь, а не все посещённые клетки. Чтобы получить точное количество посещений, потребовалось бы дорабатывать каждую стратегию — добавлять счётчик внутри `find_path`. На данном этапе это сознательное упрощение.
## Класс `MazeSolver`
### `set_strategy`
Позволяет менять алгоритм без пересоздания солвера. Это и есть основная демонстрация паттерна Strategy в действии — один объект, разные алгоритмы:
```python
solver = MazeSolver(maze, BFSStrategy())
stats_bfs = solver.solve()
solver.set_strategy(AStarStrategy())
stats_astar = solver.solve()
```
### `solve`
Замеряет время через `time.perf_counter()` — самый точный таймер в Python для коротких интервалов, не зависящий от системных часов. Результат переводится в миллисекунды умножением на 1000.
`start` и `exit` можно не передавать — стратегия найдёт их сама через `_validate`. Явная передача нужна только если хочется запустить поиск не от стандартного старта, а от произвольной клетки.

View File

@ -0,0 +1,84 @@
# Этап 5. Визуализация и пошаговое управление
В пятом этапе реализованы два паттерна: **Observer** для отображения событий и **Command** для пошагового управления игроком.
## 5.1. Паттерн Observer
### Идея
`MazeSolver` и игровой цикл не знают как именно отображать происходящее — они просто генерируют события. Наблюдатели подписываются на эти события и реагируют по своему усмотрению. Это позволяет в будущем добавить, например, `FileLogger` или графический интерфейс без изменения основного кода.
### Класс `Event`
Оформлен через `@dataclass`. Хранит тип события строкой и словарь `payload` с дополнительными данными. Поддерживаются четыре типа событий:
| Тип | Когда генерируется |
|---------------|----------------------------|
| `maze_loaded` | Лабиринт загружен из файла |
| `path_found` | Алгоритм нашёл путь |
| `no_path` | Путь не найден |
| `move` | Игрок сделал ход |
### Класс `Observer`
Абстрактный базовый класс с единственным методом `update(event)`. Любой наблюдатель обязан его реализовать.
### Класс `ConsoleView`
Конкретная реализация наблюдателя. Обрабатывает события через `match/case` и вызывает `render()` для перерисовки лабиринта.
Метод `render()` принимает лабиринт, опциональную позицию игрока и опциональный путь. Путь преобразуется в `set` для быстрой проверки принадлежности клетки — это O(1) вместо O(n) при каждом обходе:
```python
path_set = set(path) if path else set()
```
Лабиринт обрамляется рамкой из `+` и `─` для читаемости в консоли. Символы игрока и пути вынесены в константы класса — легко поменять без правки логики:
```python
PLAYER_SYMBOL = "P"
PATH_SYMBOL = "·"
```
## 5.2. Паттерн Command
### Идея
Каждое перемещение игрока оборачивается в объект `MoveCommand`. Это позволяет сохранить предыдущее состояние и отменить ход — реализация `undo` становится тривиальной.
### Класс `Player`
Простой контейнер для текущей клетки игрока. Намеренно минималистичный — вся логика перемещения и проверок находится в команде, а не в игроке.
### Класс `Command`
Абстрактный интерфейс с двумя методами: `execute()` и `undo()`. `execute()` возвращает `bool` — это отличие от классического варианта паттерна, где команды не возвращают значений. Возврат `False` нужен чтобы не добавлять неуспешный ход в историю.
### Класс `MoveCommand`
Хранит ссылку на игрока, направление и лабиринт. При `execute()` проверяет проходимость целевой клетки, сохраняет текущую в `_prev_cell` и перемещает игрока. При `undo()` восстанавливает `_prev_cell`.
Направления вынесены в словарь `DIRECTIONS` на уровне модуля:
```python
DIRECTIONS = {
"w": (0, -1), # вверх
"s": (0, 1), # вниз
"a": (-1, 0), # влево
"d": (1, 0), # вправо
}
```
### Класс `CommandHistory`
Стек выполненных команд. Хранит только успешные ходы — неуспешные (`execute()` вернул `False`) в историю не добавляются. `undo()` снимает последнюю команду со стека и вызывает её `undo()`.
Пример игрового цикла:
```python
cmd = MoveCommand(player, 'd', maze)
if cmd.execute():
history.push(cmd) # добавляем только успешный ход
history.undo() # отмена последнего хода
```

View File

@ -0,0 +1,61 @@
# Этап 6. Экспериментальная часть
В шестом этапе проведено сравнение эффективности трёх стратегий поиска пути на лабиринтах разной сложности. Эксперимент реализован в Jupyter Notebook (`practice/main.ipynb`).
## Подготовка
Лабиринты загружаются из папки `source/templates` автоматически — все файлы считываются через `os.listdir` и передаются в `TextFileBuilder`. Стратегии собраны в словарь для удобной итерации:
```python
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy(),
}
```
## Замеры
Каждая пара лабиринт + стратегия запускается **10 раз**, результаты усредняются. Это сглаживает разброс из-за кэширования и фоновой активности системы.
Лабиринты типа `noexit` пропускаются автоматически — стратегия выбрасывает `ValueError`, который перехватывается через `try/except`, и выполнение продолжается.
Результаты собираются в список словарей и затем преобразуются в `DataFrame` через pandas.
## Результаты
### 10×10 (простой путь)
На маленьких лабиринтах все три алгоритма показывают практически одинаковое время (~0.030.07 мс) и одинаковую длину пути. Разница незначительна — лабиринт слишком мал чтобы эвристика A\* давала преимущество.
### 50×50 (тупики)
BFS стабильно быстрее DFS по времени при одинаковой длине пути. DFS заходит в каждый тупик до конца и тратит время на возврат, хотя финальный путь совпадает. A\* показывает время между BFS и DFS.
### 100×100 (запутанный, spaghetti)
Наиболее показательные результаты:
| Стратегия | Время (мс) | Длина пути |
|-------------|--------------|--------------|
| BFS | ~9 | ~210 |
| DFS | ~7 | ~2200 |
| A\* | ~8 | ~210 |
DFS быстрее по времени, но находит путь в 10 раз длиннее — обходит почти весь лабиринт. BFS и A\* находят кратчайший путь, A\* при этом чуть быстрее за счёт эвристики.
### 30×30 (пустой)
Неожиданный результат: DFS быстрее всех (~0.73 мс против 1.1 у BFS и 2.0 у A\*), хотя находит путь из 379 клеток против 55 у BFS. На пустом поле без стен DFS сразу уходит вглубь без возвратов, тогда как BFS строит очередь и обходит клетки волнами во все стороны — это накладные расходы на структуру данных.
A\* на пустом лабиринте медленнее всех — накладные расходы на `heapq` и вычисление эвристики не окупаются когда препятствий нет.
## Выводы
- **BFS** — надёжный выбор по умолчанию. Всегда находит кратчайший путь, время предсказуемо.
- **DFS** — быстрый по времени, но путь непредсказуем. На запутанных лабиринтах может пройти весь граф. Подходит когда важна скорость, а не оптимальность пути.
- **A**\* — лучший выбор для больших лабиринтов с препятствиями. Находит кратчайший путь быстрее BFS за счёт эвристики. На простых или пустых лабиринтах проигрывает из-за накладных расходов на приоритетную очередь.
## Визуализация
![[results.png]]

View File

@ -0,0 +1,33 @@
# Этап 7. Отчёт
## Описание задачи
Разработать программу для загрузки лабиринта из файла, поиска пути с выбором алгоритма и сравнения их эффективности. Применены четыре паттерна GoF:
| Паттерн | Где применён |
|--------------|-----------------------------------------------|
| **Builder** | `TextFileBuilder` |
| **Strategy** | `BFSStrategy`, `DFSStrategy`, `AStarStrategy` |
| **Observer** | `ConsoleView` |
| **Command** | `MoveCommand`, `CommandHistory` |
## Диаграмма классов
## Результаты экспериментов
| Лабиринт | Быстрее всех | Кратчайший путь |
|-------------------|----------------|-------------------|
| 10×10 path | все одинаково | все одинаково |
| 50×50 deadends | BFS | BFS = A\* |
| 100×100 spaghetti | DFS | BFS = A\* |
| 30×30 empty | DFS | BFS = A\* |
**BFS** — надёжный выбор, всегда кратчайший путь.<br />
\\\\
**DFS** — быстрый, но путь длиннее. На 100×100 обошёл в 10 раз больше клеток.<br />
\\\\
**A**\* — лучший на больших лабиринтах с препятствиями, проигрывает на простых из-за накладных расходов на `heapq`.
## Выводы
Паттерны сделали код расширяемым: новый алгоритм — один класс, новый формат файла — один класс, новый способ отображения — один класс. Без паттернов каждое такое изменение потребовало бы правки существующего кода.

View File

@ -0,0 +1,103 @@
# Задание
## Цель работы
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
## Общая схема приложения (пример)
## Выполнение
### Этап 1. Модель лабиринта (без паттернов, просто классы)
**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта.
- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена).
- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена).
**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем.
### Этап 2. Загрузка лабиринта из файла применение паттерна **Builder**
**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` стена, ` ` (пробел) проход, `S` старт, `E` выход.
- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`.
- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`.
Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`.
### Этап 3. Стратегии поиска пути паттерн **Strategy**
**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода.
- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.
- Реализовать минимум 3 стратегии:
- **BFS** (поиск в ширину) гарантирует кратчайший путь по количеству шагов.
- **DFS** (поиск в глубину) быстрый, но не обязательно кратчайший.
- **A**\* (с эвристикой, например, манхэттенское расстояние) компромисс между скоростью и оптимальностью.
- (Опционально) **Дейкстра** полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS.
Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A\* приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток.
Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс.
### Этап 4. Класс-оркестратор **MazeSolver** (использует Strategy)
**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику.
- `MazeSolver` содержит поля `maze` и `strategy`.
- Метод `setStrategy(strategy)` для динамической смены алгоритма.
- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути).
- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии.
### Этап 5. Визуализация и пошаговое управление паттерны **Observer** и **Command** (по желанию)
**5.1. Наблюдатель (Observer)** обновление консольного интерфейса.
- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`).
- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли.
- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния.
**5.2. Команда (Command)** для пошагового перемещения игрока по найденному пути (или ручного управления).
- Создать интерфейс `Command` с методами `execute()` и `undo()`.
- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены.
- Создать класс `Player`, хранящий текущую клетку.
- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн.
*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command для демонстрации undo при ручном исследовании лабиринта.*
### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных)
**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности.
1. **Подготовка тестовых лабиринтов:**
- Маленький (10×10) с простым путём.
- Средний (50×50) с тупиками.
- Большой (100×100) с запутанной структурой.
- «Пустой» лабиринт (без стен) для демонстрации максимальной производительности.
- «Без выхода» чтобы проверить обработку отсутствия пути.
2. **Замеры:**
- Для каждого лабиринта и каждой стратегии запустить `solve()` 510 раз, усреднить время, количество посещённых клеток, длину пути.
- Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`.
3. **Анализ:**
- Построить графики для каждого лабиринта.
- Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях).
4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото вес 3, песок вес 2, асфальт вес 1) и сравнить Дейкстру с A\* на взвешенном графе.
### Этап 7. Отчёт
**Структура отчёта:**
1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid).
2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий.
3. Результаты экспериментов (таблицы, графики).
4. Анализ эффективности алгоритмов и применимости паттернов.
5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них.
## Советы
- Для A\* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`.
- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь.
- Для BFS/DFS используй `deque` (очередь) и `list` (стек).
- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки.

View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

@ -0,0 +1,70 @@
# API Reference
## Базовые модели
```{eval-rst}
.. automodule:: source.models.base
:members:
:undoc-members:
:show-inheritance:
```
## Загрузка лабиринта
```{eval-rst}
.. automodule:: source.build.builder
:members:
:undoc-members:
:show-inheritance:
```
## Стратегии поиска пути
```{eval-rst}
.. automodule:: source.strategy.algorithms
:members:
:undoc-members:
:show-inheritance:
.. automodule:: source.strategy.bfs
:members:
:undoc-members:
:show-inheritance:
.. automodule:: source.strategy.dfs
:members:
:undoc-members:
:show-inheritance:
.. automodule:: source.strategy.astar
:members:
:undoc-members:
:show-inheritance:
```
## Оркестратор
```{eval-rst}
.. automodule:: source.strategy.solver
:members:
:undoc-members:
:show-inheritance:
```
## Визуализация
```{eval-rst}
.. automodule:: source.view.observer
:members:
:undoc-members:
:show-inheritance:
```
## Управление игроком
```{eval-rst}
.. automodule:: source.view.command
:members:
:undoc-members:
:show-inheritance:
```

View File

@ -0,0 +1,80 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import os
import sys
sys.path.insert(0, os.path.abspath("../.."))
project = "Поиск выхода из лабиринта"
copyright = "2026, SerKin0"
author = "SerKin0"
release = "0.0.1"
html_title = project
# --- MyST (Markdown) ---
myst_enable_extensions = [
"dollarmath", # $x$ и $$x$$
"amsmath", # \begin{equation}
"colon_fence", # ::: блоки
]
exclude_patterns = ["build", "draft.md"]
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.viewcode",
"myst_nb",
"sphinx.ext.mathjax",
"sphinx_new_tab_link",
"sphinx.ext.autosummary",
"sphinxcontrib.mermaid",
]
autosummary_generate = True
autodoc_default_options = {
"members": True,
"undoc-members": True,
"show-inheritance": True,
"special-members": "__init__",
"inherited-members": False,
"exclude-members": "__weakref__",
}
napoleon_google_docstring = True
napoleon_numpy_docstring = False
napoleon_include_init_with_doc = True
napoleon_include_private_with_doc = False
# --- Тема ---
html_permalinks_icon = "<span>#</span>"
html_theme = "sphinxawesome_theme"
language = "ru"
html_theme_options = {
"navigation_with_keys": True,
"globaltoc_collapse": False,
"globaltoc_includehidden": False,
"show_prev_next": True,
"main_nav_links": {},
}
pygments_style = "monokai"
pygments_style_dark = "monokai"
# Для подключения CSS (стили иконок)
html_css_files = [
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css",
]
# --- HTML ---
html_static_path = ["_static"]

View File

@ -0,0 +1,15 @@
# Лабораторная работа "Поиск выхода из лабиринта"
:::{toctree}
:maxdepth: 2
task
stage1
stage2
stage3
stage4
stage5
stage6
stage7
api

View File

@ -0,0 +1,91 @@
# Этап 1. Модель лабиринта
В первом этапе разработки необходимо создать базовые классы `Cell` и `Maze`, которые представляют карту лабиринта. Паттерны на этом этапе не применяются — только чистые классы.
## Класс `Cell`
Клетка — минимальная единица лабиринта. Хранит координаты и тип: стена, старт, выход или пустая.
По условию задания клетка должна иметь флаги `isWall`, `isStart`, `isExit` и метод `isPassable()`. В реализации флаги оформлены как **свойства** (`@property`) с сеттерами — это позволяет автоматически сбрасывать остальные флаги при установке нового типа.
```python
cell = Cell(1, 1)
cell.is_wall = True
```
Типы клетки взаимоисключают друг друга — клетка не может быть одновременно стеной и стартом. Логика сброса вынесена в приватный метод `_clear_flags()`.
### Символьное представление
Для вывода лабиринта в консоль каждая клетка возвращает символ через `__str__`. Символы берутся из `cell_mapping` в `source/settings.py`, что позволяет менять отображение без правки классов:
|Тип|Символ по умолчанию|
|---|---|
|Стена|`#`|
|Старт|`S`|
|Выход|`E`|
|Пустая||
## Класс `Maze`
Лабиринт хранит двумерный список клеток и предоставляет методы для работы с ними.
По условию задания требовались методы `getCell(x, y)` и `getNeighbors(cell)`. В реализации добавлено несколько вещей сверх задания:
### Именование методов
Задание написано в стиле Java/pseudocode — названия методов и полей используют `camelCase` (`isWall`, `getCell`, `isPassable`). В Python принят другой стандарт именования — **PEP 8**, который предписывает `snake_case` для методов и атрибутов. Поэтому все названия были приведены к Python стилю:
|Задание|Реализация|
|---|---|
|`isWall`|`is_wall`|
|`isStart`|`is_start`|
|`isExit`|`is_exit`|
|`isPassable()`|`is_possible()`|
|`getCell(x, y)`|`get_cell(x, y)`|
|`getNeighbors(cell)`|`get_neighbors(x, y)`|
|`buildFromFile(filename)`|`build_from_file(filename)`|
Это соответствует стандарту оформления кода на Python и делает API классов идиоматичным для языка.
### Индексация `maze[row, col]`
Вместо явного вызова `get_cell()` реализованы `__getitem__` и `__setitem__`, что позволяет обращаться к клеткам естественным образом:
```python
maze[0, 0] = cell_mapping['wall'] # установить стену
cell = maze[2, 3] # получить клетку
```
Обратите внимание: индексация идёт в формате `[row, col]`, то есть сначала строка (Y), потом столбец (X) — аналогично numpy.
### Свойства `start` и `exit`
Добавлены свойства для быстрого получения стартовой и выходной клетки без ручного обхода:
```python
maze.start # Cell или None
maze.exit # Cell или None
```
Это оказалось необходимым при реализации алгоритмов поиска пути — стратегии получают `start` и `exit` автоматически из лабиринта.
### Свойство `shape`
По аналогии с numpy добавлено свойство `shape`, возвращающее `(height, width)`:
```python
rows, cols = maze.shape
```
Используется в стратегиях поиска и тестах для итерации по лабиринту.
### `get_neighbors`
Метод возвращает список проходимых соседей клетки по четырём направлениям. Стены и клетки за границей лабиринта исключаются автоматически. Если переданные координаты вне границ — возвращает `None`.
```python
neighbors = maze.get_neighbors(2, 2) # список Cell
```
Направления обхода: вниз → вправо → вверх → влево (порядок влияет на поведение DFS).

View File

@ -0,0 +1,60 @@
# Этап 2. Загрузка лабиринта из файла
Во втором этапе реализована загрузка лабиринта из текстового файла с применением паттерна **Builder**.
## Паттерн Builder
Процесс создания лабиринта из файла включает несколько шагов: чтение файла, валидацию структуры, парсинг символов и заполнение клеток. Builder скрывает эти детали от клиента — снаружи виден только один метод `build_from_file()`, внутри которого сосредоточена вся логика построения.
Дополнительное преимущество: в будущем можно легко добавить новый формат (например, JSON или бинарный) через новую реализацию `MazeBuilder` без изменения остального кода.
## Класс `MazeBuilder`
Абстрактный базовый класс — интерфейс паттерна Builder. Объявляет единственный метод `build_from_file()`, который обязан реализовать каждый конкретный строитель.
По условию задания интерфейс назывался `MazeBuilder` с методом `buildFromFile`. В реализации название метода приведено к **PEP 8**`build_from_file`. Сам класс оформлен через `ABC` — попытка создать объект `MazeBuilder()` напрямую вызовет `TypeError`.
## Класс `TextFileBuilder`
Конкретная реализация строителя для текстовых файлов. Загружает лабиринт из `.txt` файла где `#` — стена, — проход, `S` — старт, `E` — выход.
Процесс построения разбит на три приватных шага:
### `_read_file`
Читает файл построчно и обрезает символы переноса строки `\n` и `\r`. Возвращает список строк — каждая строка соответствует одной строке лабиринта.
### `_test_text_maze`
Валидирует структуру: проверяет что все строки одинаковой длины. Если нет — лабиринт некорректен и `_create_maze` выбросит `ValueError`.
Реализован как `@staticmethod` — не использует состояние объекта, только входные данные.
### `_create_maze`
Создаёт объект `Maze` нужного размера и заполняет его клетки символами из файла через `maze[y, x] = symbol`. Тип каждой клетки определяется автоматически через `cell_mapping` в `__setitem__` лабиринта.
## Использование
```python
from source.build.builder import TextFileBuilder
maze = TextFileBuilder().build_from_file('source/templates/10x10_path_v1.txt')
print(maze)
```
## Известная ошибка
В текущей реализации `_create_maze` есть опечатка при вычислении `width`:
```python
height, width = len(text_maze), len(text_maze) # width всегда равен height
```
Правильная версия:
```python
height, width = len(text_maze), len(text_maze[0])
```
На квадратных лабиринтах (10×10, 50×50) это не проявляется, но на прямоугольных даст некорректный результат.

View File

@ -0,0 +1,90 @@
# Этап 3. Стратегии поиска пути
В третьем этапе реализованы алгоритмы поиска пути с применением паттерна **Strategy**.
## Паттерн Strategy
Все три алгоритма реализуют один интерфейс `PathFindingStrategy`. Это позволяет переключать алгоритм в любой момент без изменения кода клиента — достаточно передать другой объект стратегии:
```python
solver = MazeSolver(maze, BFSStrategy())
solver.set_strategy(AStarStrategy())
```
Новый алгоритм добавляется реализацией интерфейса — остальной код трогать не нужно.
## Структура пакета
Стратегии разбиты по отдельным файлам, а `__init__.py` собирает всё в один импорт:
```
source/strategy/
├── __init__.py ← единственный импорт для пользователя
├── algorithms.py ← базовый класс PathFindingStrategy
├── bfs.py
├── dfs.py
└── astar.py
```
```python
from source.strategy import BFSStrategy, DFSStrategy, AStarStrategy
```
## Класс `PathFindingStrategy`
Абстрактный базовый класс — интерфейс паттерна. Объявляет абстрактный метод `find_path()` и содержит два вспомогательных метода, общих для всех стратегий.
По условию задания метод назывался `findPath` — приведён к **PEP 8** как `find_path`.
### `_validate`
Добавлен в процессе разработки — изначально в задании не было требования к обработке отсутствия старта или выхода. Проблема проявилась при тестировании лабиринтов типа `noexit`: алгоритм падал с `AttributeError` внутри, вместо понятного сообщения.
`_validate` подставляет `start` и `exit` из лабиринта если они не переданы явно, и выбрасывает `ValueError` с понятным сообщением если клетки не найдены:
```python
start, exit = self._validate(maze, start, exit)
```
Вынесен в базовый класс чтобы не дублировать в каждом алгоритме.
### `_reconstruct_path`
Восстанавливает путь по словарю предков `came_from`. Все три алгоритма строят этот словарь одинаково — `{клетка: откуда_пришли}` — поэтому восстановление вынесено в общий метод базового класса.
Алгоритм идёт от выхода к старту по цепочке предков, затем разворачивает список:
```
exit → D → C → B → start (идём по came_from)
start → B → C → D → exit (после reverse)
```
## Алгоритмы
### BFS — `BFSStrategy`
Поиск в ширину. Использует `deque` как очередь (FIFO) — каждый раз берём самую старую клетку из начала. Это гарантирует послойный обход и кратчайший путь по количеству шагов.
Сложность: O(V + E) по времени и памяти.
### DFS — `DFSStrategy`
Поиск в глубину. Использует `list` как стек (LIFO) — каждый раз берём самую свежую клетку с конца. Алгоритм ныряет вглубь по одному направлению до тупика, затем возвращается.
Не гарантирует кратчайший путь. На запутанных лабиринтах может обойти почти все клетки прежде чем найти выход, хотя по времени часто быстрее BFS из-за меньших накладных расходов на структуру данных.
Сложность: O(V + E) по времени и памяти.
### A* — `AStarStrategy`
Использует `heapq` как приоритетную очередь. На каждом шаге выбирает клетку с минимальным значением `f = g + h`, где `g` — стоимость пути от старта, `h` — манхэттенская эвристика до выхода.
Эвристика направляет поиск в сторону выхода, поэтому A* обходит меньше клеток чем BFS при том же гарантированно кратчайшем пути.
В кортеж приоритетной очереди добавлен счётчик `counter` как tie-breaker — без него `heapq` попытался бы сравнивать объекты `Cell` при одинаковом `f`, что вызвало бы `TypeError`:
```python
heapq.heappush(open_heap, (f, counter, neighbor))
```
Сложность: O(E · log V) в худшем случае.

View File

@ -0,0 +1,44 @@
# Этап 4. Класс-оркестратор MazeSolver
В четвёртом этапе реализован класс `MazeSolver`, который объединяет лабиринт и стратегию поиска, выполняет поиск и собирает статистику.
## Роль в архитектуре
`MazeSolver` — точка входа для клиентского кода. Он не знает деталей ни одного алгоритма и не работает напрямую с клетками лабиринта — только делегирует задачу стратегии и замеряет время:
```python
solver = MazeSolver(maze, BFSStrategy())
stats = solver.solve()
print(stats)
# Время: 0.041 мс | Посещено клеток: 13 | Длина пути: 13
```
## Класс `SearchStats`
Оформлен через `@dataclass` — это избавляет от ручного `__init__` и автоматически даёт `__repr__`. Хранит четыре поля: время выполнения, количество посещённых клеток, длину пути и сам путь как список клеток.
`__str__` переопределён для удобного вывода в консоль и отчётах.
### Ограничение
В текущей реализации `visited_count` и `path_length` всегда равны друг другу — оба вычисляются как `len(path)`. Это потому что стратегии возвращают только финальный путь, а не все посещённые клетки. Чтобы получить точное количество посещений, потребовалось бы дорабатывать каждую стратегию — добавлять счётчик внутри `find_path`. На данном этапе это сознательное упрощение.
## Класс `MazeSolver`
### `set_strategy`
Позволяет менять алгоритм без пересоздания солвера. Это и есть основная демонстрация паттерна Strategy в действии — один объект, разные алгоритмы:
```python
solver = MazeSolver(maze, BFSStrategy())
stats_bfs = solver.solve()
solver.set_strategy(AStarStrategy())
stats_astar = solver.solve()
```
### `solve`
Замеряет время через `time.perf_counter()` — самый точный таймер в Python для коротких интервалов, не зависящий от системных часов. Результат переводится в миллисекунды умножением на 1000.
`start` и `exit` можно не передавать — стратегия найдёт их сама через `_validate`. Явная передача нужна только если хочется запустить поиск не от стандартного старта, а от произвольной клетки.

View File

@ -0,0 +1,84 @@
# Этап 5. Визуализация и пошаговое управление
В пятом этапе реализованы два паттерна: **Observer** для отображения событий и **Command** для пошагового управления игроком.
## 5.1. Паттерн Observer
### Идея
`MazeSolver` и игровой цикл не знают как именно отображать происходящее — они просто генерируют события. Наблюдатели подписываются на эти события и реагируют по своему усмотрению. Это позволяет в будущем добавить, например, `FileLogger` или графический интерфейс без изменения основного кода.
### Класс `Event`
Оформлен через `@dataclass`. Хранит тип события строкой и словарь `payload` с дополнительными данными. Поддерживаются четыре типа событий:
|Тип|Когда генерируется|
|---|---|
|`maze_loaded`|Лабиринт загружен из файла|
|`path_found`|Алгоритм нашёл путь|
|`no_path`|Путь не найден|
|`move`|Игрок сделал ход|
### Класс `Observer`
Абстрактный базовый класс с единственным методом `update(event)`. Любой наблюдатель обязан его реализовать.
### Класс `ConsoleView`
Конкретная реализация наблюдателя. Обрабатывает события через `match/case` и вызывает `render()` для перерисовки лабиринта.
Метод `render()` принимает лабиринт, опциональную позицию игрока и опциональный путь. Путь преобразуется в `set` для быстрой проверки принадлежности клетки — это O(1) вместо O(n) при каждом обходе:
```python
path_set = set(path) if path else set()
```
Лабиринт обрамляется рамкой из `+` и `─` для читаемости в консоли. Символы игрока и пути вынесены в константы класса — легко поменять без правки логики:
```python
PLAYER_SYMBOL = "P"
PATH_SYMBOL = "·"
```
## 5.2. Паттерн Command
### Идея
Каждое перемещение игрока оборачивается в объект `MoveCommand`. Это позволяет сохранить предыдущее состояние и отменить ход — реализация `undo` становится тривиальной.
### Класс `Player`
Простой контейнер для текущей клетки игрока. Намеренно минималистичный — вся логика перемещения и проверок находится в команде, а не в игроке.
### Класс `Command`
Абстрактный интерфейс с двумя методами: `execute()` и `undo()`. `execute()` возвращает `bool` — это отличие от классического варианта паттерна, где команды не возвращают значений. Возврат `False` нужен чтобы не добавлять неуспешный ход в историю.
### Класс `MoveCommand`
Хранит ссылку на игрока, направление и лабиринт. При `execute()` проверяет проходимость целевой клетки, сохраняет текущую в `_prev_cell` и перемещает игрока. При `undo()` восстанавливает `_prev_cell`.
Направления вынесены в словарь `DIRECTIONS` на уровне модуля:
```python
DIRECTIONS = {
"w": (0, -1), # вверх
"s": (0, 1), # вниз
"a": (-1, 0), # влево
"d": (1, 0), # вправо
}
```
### Класс `CommandHistory`
Стек выполненных команд. Хранит только успешные ходы — неуспешные (`execute()` вернул `False`) в историю не добавляются. `undo()` снимает последнюю команду со стека и вызывает её `undo()`.
Пример игрового цикла:
```python
cmd = MoveCommand(player, 'd', maze)
if cmd.execute():
history.push(cmd) # добавляем только успешный ход
history.undo() # отмена последнего хода
```

View File

@ -0,0 +1,63 @@
# Этап 6. Экспериментальная часть
В шестом этапе проведено сравнение эффективности трёх стратегий поиска пути на лабиринтах разной сложности. Эксперимент реализован в Jupyter Notebook (`practice/main.ipynb`).
## Подготовка
Лабиринты загружаются из папки `source/templates` автоматически — все файлы считываются через `os.listdir` и передаются в `TextFileBuilder`. Стратегии собраны в словарь для удобной итерации:
```python
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy(),
}
```
## Замеры
Каждая пара лабиринт + стратегия запускается **10 раз**, результаты усредняются. Это сглаживает разброс из-за кэширования и фоновой активности системы.
Лабиринты типа `noexit` пропускаются автоматически — стратегия выбрасывает `ValueError`, который перехватывается через `try/except`, и выполнение продолжается.
Результаты собираются в список словарей и затем преобразуются в `DataFrame` через pandas.
## Результаты
### 10×10 (простой путь)
На маленьких лабиринтах все три алгоритма показывают практически одинаковое время (~0.030.07 мс) и одинаковую длину пути. Разница незначительна — лабиринт слишком мал чтобы эвристика A* давала преимущество.
### 50×50 (тупики)
BFS стабильно быстрее DFS по времени при одинаковой длине пути. DFS заходит в каждый тупик до конца и тратит время на возврат, хотя финальный путь совпадает. A* показывает время между BFS и DFS.
### 100×100 (запутанный, spaghetti)
Наиболее показательные результаты:
|Стратегия|Время (мс)|Длина пути|
|---|---|---|
|BFS|~9|~210|
|DFS|~7|~2200|
|A*|~8|~210|
DFS быстрее по времени, но находит путь в 10 раз длиннее — обходит почти весь лабиринт. BFS и A* находят кратчайший путь, A* при этом чуть быстрее за счёт эвристики.
### 30×30 (пустой)
Неожиданный результат: DFS быстрее всех (~0.73 мс против 1.1 у BFS и 2.0 у A*), хотя находит путь из 379 клеток против 55 у BFS. На пустом поле без стен DFS сразу уходит вглубь без возвратов, тогда как BFS строит очередь и обходит клетки волнами во все стороны — это накладные расходы на структуру данных.
A* на пустом лабиринте медленнее всех — накладные расходы на `heapq` и вычисление эвристики не окупаются когда препятствий нет.
## Выводы
- **BFS** — надёжный выбор по умолчанию. Всегда находит кратчайший путь, время предсказуемо.
- **DFS** — быстрый по времени, но путь непредсказуем. На запутанных лабиринтах может пройти весь граф. Подходит когда важна скорость, а не оптимальность пути.
- **A*** — лучший выбор для больших лабиринтов с препятствиями. Находит кратчайший путь быстрее BFS за счёт эвристики. На простых или пустых лабиринтах проигрывает из-за накладных расходов на приоритетную очередь.
## Визуализация
![[results.png]]

View File

@ -0,0 +1,81 @@
# Этап 7. Отчёт
## Описание задачи
Разработать программу для загрузки лабиринта из файла, поиска пути с выбором алгоритма и сравнения их эффективности. Применены четыре паттерна GoF:
|Паттерн|Где применён|
|---|---|
|**Builder**|`TextFileBuilder`|
|**Strategy**|`BFSStrategy`, `DFSStrategy`, `AStarStrategy`|
|**Observer**|`ConsoleView`|
|**Command**|`MoveCommand`, `CommandHistory`|
## Диаграмма классов
```{mermaid}
classDiagram
class Cell {
+int x, y
+bool is_wall, is_start, is_exit
+is_possible() bool
}
class Maze {
+get_cell(x, y) Cell
+get_neighbors(x, y) list
+start, exit, shape
}
class MazeBuilder { <<abstract>> }
class TextFileBuilder
class PathFindingStrategy {
<<abstract>>
+find_path(maze, start, exit) list
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class MazeSolver {
+set_strategy(strategy)
+solve() SearchStats
}
class ConsoleView {
+update(event)
+render(maze, player, path)
}
class MoveCommand {
+execute() bool
+undo()
}
class CommandHistory {
+push(command)
+undo() bool
}
Maze *-- Cell
MazeBuilder <|-- TextFileBuilder
TextFileBuilder ..> Maze : creates
PathFindingStrategy <|-- BFSStrategy
PathFindingStrategy <|-- DFSStrategy
PathFindingStrategy <|-- AStarStrategy
MazeSolver --> PathFindingStrategy
MazeSolver --> Maze
MoveCommand --> Maze
CommandHistory --> MoveCommand
```
## Результаты экспериментов
| Лабиринт | Быстрее всех | Кратчайший путь |
| ----------------- | ------------- | --------------- |
| 10×10 path | все одинаково | все одинаково |
| 50×50 deadends | BFS | BFS = A* |
| 100×100 spaghetti | DFS | BFS = A* |
| 30×30 empty | DFS | BFS = A* |
**BFS** — надёжный выбор, всегда кратчайший путь.
**DFS** — быстрый, но путь длиннее. На 100×100 обошёл в 10 раз больше клеток.
**A*** — лучший на больших лабиринтах с препятствиями, проигрывает на простых из-за накладных расходов на `heapq`.
## Выводы
Паттерны сделали код расширяемым: новый алгоритм — один класс, новый формат файла — один класс, новый способ отображения — один класс. Без паттернов каждое такое изменение потребовало бы правки существующего кода.

View File

@ -0,0 +1,183 @@
# Задание
## Цель работы
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
## Общая схема приложения (пример)
```{mermaid}
classDiagram
class Maze {
-Cell[] cells
-int width, height
-Cell start
-Cell exit
+getCell(x,y): Cell
+getNeighbors(cell): List~Cell~
}
class Cell {
-int x, y
-bool isWall
-bool isStart
-bool isExit
+isPassable(): bool
}
class MazeBuilder {
<<interface>>
+buildFromFile(filename): Maze
}
class TextFileMazeBuilder {
+buildFromFile(filename): Maze
}
class PathFindingStrategy {
<<interface>>
+findPath(maze, start, exit): List~Cell~
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class DijkstraStrategy
class SearchStats {
+timeMs: float
+visitedCells: int
+pathLength: int
}
class MazeSolver {
-Maze maze
-PathFindingStrategy strategy
+setStrategy(strategy)
+solve(): SearchStats
}
class Command {
<<interface>>
+execute()
+undo()
}
class MoveCommand {
-Player player
-Direction dir
-Cell previousCell
+execute()
+undo()
}
class Player {
-Cell currentCell
+moveTo(cell)
}
class Observer {
<<interface>>
+update(event)
}
class ConsoleView {
+update(event)
+render(maze, player, path)
}
MazeBuilder <|.. TextFileMazeBuilder
MazeBuilder --> Maze : creates
PathFindingStrategy <|.. BFSStrategy
PathFindingStrategy <|.. DFSStrategy
PathFindingStrategy <|.. AStarStrategy
PathFindingStrategy <|.. DijkstraStrategy
MazeSolver --> PathFindingStrategy : uses
MazeSolver --> Maze : uses
Command <|.. MoveCommand
MoveCommand --> Player
Player --> Cell
Observer <|.. ConsoleView
MazeSolver --> Observer : notifies
```
## Выполнение
### Этап 1. Модель лабиринта (без паттернов, просто классы)
**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта.
- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена).
- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена).
**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем.
### Этап 2. Загрузка лабиринта из файла применение паттерна **Builder**
**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` стена, ` ` (пробел) проход, `S` старт, `E` выход.
- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`.
- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`.
Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`.
### Этап 3. Стратегии поиска пути паттерн **Strategy**
**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода.
- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет.
- Реализовать минимум 3 стратегии:
- **BFS** (поиск в ширину) гарантирует кратчайший путь по количеству шагов.
- **DFS** (поиск в глубину) быстрый, но не обязательно кратчайший.
- **A*** (с эвристикой, например, манхэттенское расстояние) компромисс между скоростью и оптимальностью.
- (Опционально) **Дейкстра** полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS.
Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A* приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток.
Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс.
### Этап 4. Класс-оркестратор **MazeSolver** (использует Strategy)
**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику.
- `MazeSolver` содержит поля `maze` и `strategy`.
- Метод `setStrategy(strategy)` для динамической смены алгоритма.
- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути).
- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии.
### Этап 5. Визуализация и пошаговое управление паттерны **Observer** и **Command** (по желанию)
**5.1. Наблюдатель (Observer)** обновление консольного интерфейса.
- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`).
- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли.
- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния.
**5.2. Команда (Command)** для пошагового перемещения игрока по найденному пути (или ручного управления).
- Создать интерфейс `Command` с методами `execute()` и `undo()`.
- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены.
- Создать класс `Player`, хранящий текущую клетку.
- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн.
*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command для демонстрации undo при ручном исследовании лабиринта.*
### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных)
**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности.
1. **Подготовка тестовых лабиринтов:**
- Маленький (10×10) с простым путём.
- Средний (50×50) с тупиками.
- Большой (100×100) с запутанной структурой.
- «Пустой» лабиринт (без стен) для демонстрации максимальной производительности.
- «Без выхода» чтобы проверить обработку отсутствия пути.
2. **Замеры:**
- Для каждого лабиринта и каждой стратегии запустить `solve()` 510 раз, усреднить время, количество посещённых клеток, длину пути.
- Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`.
3. **Анализ:**
- Построить графики для каждого лабиринта.
- Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях).
4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото вес 3, песок вес 2, асфальт вес 1) и сравнить Дейкстру с A* на взвешенном графе.
### Этап 7. Отчёт
**Структура отчёта:**
1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid).
2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий.
3. Результаты экспериментов (таблицы, графики).
4. Анализ эффективности алгоритмов и применимости паттернов.
5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них.
## Советы
- Для A* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`.
- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь.
- Для BFS/DFS используй `deque` (очередь) и `list` (стек).
- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,121 @@
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
100x100_spaghetti_v1.txt,BFS,9.2513,205.0,205.0
100x100_spaghetti_v1.txt,DFS,8.2451,2129.0,2129.0
100x100_spaghetti_v1.txt,A*,7.1113,205.0,205.0
100x100_spaghetti_v10.txt,BFS,9.2555,207.0,207.0
100x100_spaghetti_v10.txt,DFS,8.1821,2489.0,2489.0
100x100_spaghetti_v10.txt,A*,8.4803,207.0,207.0
100x100_spaghetti_v2.txt,BFS,9.3921,217.0,217.0
100x100_spaghetti_v2.txt,DFS,6.7196,2063.0,2063.0
100x100_spaghetti_v2.txt,A*,10.5764,217.0,217.0
100x100_spaghetti_v3.txt,BFS,8.4084,217.0,217.0
100x100_spaghetti_v3.txt,DFS,5.7855,2107.0,2107.0
100x100_spaghetti_v3.txt,A*,6.3385,217.0,217.0
100x100_spaghetti_v4.txt,BFS,8.8661,205.0,205.0
100x100_spaghetti_v4.txt,DFS,6.8166,2409.0,2409.0
100x100_spaghetti_v4.txt,A*,6.1874,205.0,205.0
100x100_spaghetti_v5.txt,BFS,8.3117,217.0,217.0
100x100_spaghetti_v5.txt,DFS,6.3364,2071.0,2071.0
100x100_spaghetti_v5.txt,A*,8.495,217.0,217.0
100x100_spaghetti_v6.txt,BFS,8.212,243.0,243.0
100x100_spaghetti_v6.txt,DFS,7.0348,1869.0,1869.0
100x100_spaghetti_v6.txt,A*,12.8413,243.0,243.0
100x100_spaghetti_v7.txt,BFS,8.3471,211.0,211.0
100x100_spaghetti_v7.txt,DFS,6.1699,2283.0,2283.0
100x100_spaghetti_v7.txt,A*,6.9637,211.0,211.0
100x100_spaghetti_v8.txt,BFS,8.3499,221.0,221.0
100x100_spaghetti_v8.txt,DFS,7.1166,2473.0,2473.0
100x100_spaghetti_v8.txt,A*,9.5093,221.0,221.0
100x100_spaghetti_v9.txt,BFS,8.5536,209.0,209.0
100x100_spaghetti_v9.txt,DFS,5.4126,1939.0,1939.0
100x100_spaghetti_v9.txt,A*,6.7365,209.0,209.0
10x10_path_v1.txt,BFS,0.032,13.0,13.0
10x10_path_v1.txt,DFS,0.0341,13.0,13.0
10x10_path_v1.txt,A*,0.0399,13.0,13.0
10x10_path_v10.txt,BFS,0.0323,13.0,13.0
10x10_path_v10.txt,DFS,0.036,13.0,13.0
10x10_path_v10.txt,A*,0.037,13.0,13.0
10x10_path_v2.txt,BFS,0.0354,17.0,17.0
10x10_path_v2.txt,DFS,0.0433,17.0,17.0
10x10_path_v2.txt,A*,0.044,17.0,17.0
10x10_path_v3.txt,BFS,0.0348,17.0,17.0
10x10_path_v3.txt,DFS,0.0492,17.0,17.0
10x10_path_v3.txt,A*,0.0439,17.0,17.0
10x10_path_v4.txt,BFS,0.0476,29.0,29.0
10x10_path_v4.txt,DFS,0.0475,29.0,29.0
10x10_path_v4.txt,A*,0.0652,29.0,29.0
10x10_path_v5.txt,BFS,0.0302,13.0,13.0
10x10_path_v5.txt,DFS,0.0334,13.0,13.0
10x10_path_v5.txt,A*,0.0371,13.0,13.0
10x10_path_v6.txt,BFS,0.0307,13.0,13.0
10x10_path_v6.txt,DFS,0.0339,13.0,13.0
10x10_path_v6.txt,A*,0.0375,13.0,13.0
10x10_path_v7.txt,BFS,0.0401,17.0,17.0
10x10_path_v7.txt,DFS,0.0499,17.0,17.0
10x10_path_v7.txt,A*,0.0489,17.0,17.0
10x10_path_v8.txt,BFS,0.0615,29.0,29.0
10x10_path_v8.txt,DFS,0.0536,29.0,29.0
10x10_path_v8.txt,A*,0.0801,29.0,29.0
10x10_path_v9.txt,BFS,0.0579,17.0,17.0
10x10_path_v9.txt,DFS,0.046,17.0,17.0
10x10_path_v9.txt,A*,0.0468,17.0,17.0
30x30_empty_v1.txt,BFS,1.1046,55.0,55.0
30x30_empty_v1.txt,DFS,0.7781,379.0,379.0
30x30_empty_v1.txt,A*,1.9965,55.0,55.0
30x30_empty_v10.txt,BFS,1.1246,55.0,55.0
30x30_empty_v10.txt,DFS,0.7002,379.0,379.0
30x30_empty_v10.txt,A*,2.0086,55.0,55.0
30x30_empty_v2.txt,BFS,1.1401,55.0,55.0
30x30_empty_v2.txt,DFS,0.7263,379.0,379.0
30x30_empty_v2.txt,A*,2.0245,55.0,55.0
30x30_empty_v3.txt,BFS,1.1038,55.0,55.0
30x30_empty_v3.txt,DFS,0.7249,379.0,379.0
30x30_empty_v3.txt,A*,2.007,55.0,55.0
30x30_empty_v4.txt,BFS,1.1224,55.0,55.0
30x30_empty_v4.txt,DFS,0.7053,379.0,379.0
30x30_empty_v4.txt,A*,1.989,55.0,55.0
30x30_empty_v5.txt,BFS,1.1294,55.0,55.0
30x30_empty_v5.txt,DFS,0.7202,379.0,379.0
30x30_empty_v5.txt,A*,2.1138,55.0,55.0
30x30_empty_v6.txt,BFS,1.0843,55.0,55.0
30x30_empty_v6.txt,DFS,0.7746,379.0,379.0
30x30_empty_v6.txt,A*,2.009,55.0,55.0
30x30_empty_v7.txt,BFS,1.1449,55.0,55.0
30x30_empty_v7.txt,DFS,0.7076,379.0,379.0
30x30_empty_v7.txt,A*,2.033,55.0,55.0
30x30_empty_v8.txt,BFS,1.3196,55.0,55.0
30x30_empty_v8.txt,DFS,0.7794,379.0,379.0
30x30_empty_v8.txt,A*,1.9972,55.0,55.0
30x30_empty_v9.txt,BFS,1.1088,55.0,55.0
30x30_empty_v9.txt,DFS,0.7131,379.0,379.0
30x30_empty_v9.txt,A*,2.0128,55.0,55.0
50x50_deadends_v1.txt,BFS,1.7809,729.0,729.0
50x50_deadends_v1.txt,DFS,1.7167,729.0,729.0
50x50_deadends_v1.txt,A*,2.5217,729.0,729.0
50x50_deadends_v10.txt,BFS,0.7362,261.0,261.0
50x50_deadends_v10.txt,DFS,1.7627,261.0,261.0
50x50_deadends_v10.txt,A*,0.9753,261.0,261.0
50x50_deadends_v2.txt,BFS,0.9246,249.0,249.0
50x50_deadends_v2.txt,DFS,1.7347,249.0,249.0
50x50_deadends_v2.txt,A*,1.0804,249.0,249.0
50x50_deadends_v3.txt,BFS,0.945,297.0,297.0
50x50_deadends_v3.txt,DFS,1.7483,297.0,297.0
50x50_deadends_v3.txt,A*,1.0832,297.0,297.0
50x50_deadends_v4.txt,BFS,1.5487,413.0,413.0
50x50_deadends_v4.txt,DFS,1.6526,413.0,413.0
50x50_deadends_v4.txt,A*,1.9521,413.0,413.0
50x50_deadends_v5.txt,BFS,0.9255,309.0,309.0
50x50_deadends_v5.txt,DFS,1.7299,309.0,309.0
50x50_deadends_v5.txt,A*,1.1469,309.0,309.0
50x50_deadends_v6.txt,BFS,1.0637,337.0,337.0
50x50_deadends_v6.txt,DFS,1.7728,337.0,337.0
50x50_deadends_v6.txt,A*,1.3449,337.0,337.0
50x50_deadends_v7.txt,BFS,0.7827,261.0,261.0
50x50_deadends_v7.txt,DFS,1.6948,261.0,261.0
50x50_deadends_v7.txt,A*,0.9527,261.0,261.0
50x50_deadends_v8.txt,BFS,1.5551,565.0,565.0
50x50_deadends_v8.txt,DFS,1.7707,565.0,565.0
50x50_deadends_v8.txt,A*,2.3158,565.0,565.0
50x50_deadends_v9.txt,BFS,0.6693,209.0,209.0
50x50_deadends_v9.txt,DFS,1.052,209.0,209.0
50x50_deadends_v9.txt,A*,0.7957,209.0,209.0
1 лабиринт стратегия время_мс посещено_клеток длина_пути
2 100x100_spaghetti_v1.txt BFS 9.2513 205.0 205.0
3 100x100_spaghetti_v1.txt DFS 8.2451 2129.0 2129.0
4 100x100_spaghetti_v1.txt A* 7.1113 205.0 205.0
5 100x100_spaghetti_v10.txt BFS 9.2555 207.0 207.0
6 100x100_spaghetti_v10.txt DFS 8.1821 2489.0 2489.0
7 100x100_spaghetti_v10.txt A* 8.4803 207.0 207.0
8 100x100_spaghetti_v2.txt BFS 9.3921 217.0 217.0
9 100x100_spaghetti_v2.txt DFS 6.7196 2063.0 2063.0
10 100x100_spaghetti_v2.txt A* 10.5764 217.0 217.0
11 100x100_spaghetti_v3.txt BFS 8.4084 217.0 217.0
12 100x100_spaghetti_v3.txt DFS 5.7855 2107.0 2107.0
13 100x100_spaghetti_v3.txt A* 6.3385 217.0 217.0
14 100x100_spaghetti_v4.txt BFS 8.8661 205.0 205.0
15 100x100_spaghetti_v4.txt DFS 6.8166 2409.0 2409.0
16 100x100_spaghetti_v4.txt A* 6.1874 205.0 205.0
17 100x100_spaghetti_v5.txt BFS 8.3117 217.0 217.0
18 100x100_spaghetti_v5.txt DFS 6.3364 2071.0 2071.0
19 100x100_spaghetti_v5.txt A* 8.495 217.0 217.0
20 100x100_spaghetti_v6.txt BFS 8.212 243.0 243.0
21 100x100_spaghetti_v6.txt DFS 7.0348 1869.0 1869.0
22 100x100_spaghetti_v6.txt A* 12.8413 243.0 243.0
23 100x100_spaghetti_v7.txt BFS 8.3471 211.0 211.0
24 100x100_spaghetti_v7.txt DFS 6.1699 2283.0 2283.0
25 100x100_spaghetti_v7.txt A* 6.9637 211.0 211.0
26 100x100_spaghetti_v8.txt BFS 8.3499 221.0 221.0
27 100x100_spaghetti_v8.txt DFS 7.1166 2473.0 2473.0
28 100x100_spaghetti_v8.txt A* 9.5093 221.0 221.0
29 100x100_spaghetti_v9.txt BFS 8.5536 209.0 209.0
30 100x100_spaghetti_v9.txt DFS 5.4126 1939.0 1939.0
31 100x100_spaghetti_v9.txt A* 6.7365 209.0 209.0
32 10x10_path_v1.txt BFS 0.032 13.0 13.0
33 10x10_path_v1.txt DFS 0.0341 13.0 13.0
34 10x10_path_v1.txt A* 0.0399 13.0 13.0
35 10x10_path_v10.txt BFS 0.0323 13.0 13.0
36 10x10_path_v10.txt DFS 0.036 13.0 13.0
37 10x10_path_v10.txt A* 0.037 13.0 13.0
38 10x10_path_v2.txt BFS 0.0354 17.0 17.0
39 10x10_path_v2.txt DFS 0.0433 17.0 17.0
40 10x10_path_v2.txt A* 0.044 17.0 17.0
41 10x10_path_v3.txt BFS 0.0348 17.0 17.0
42 10x10_path_v3.txt DFS 0.0492 17.0 17.0
43 10x10_path_v3.txt A* 0.0439 17.0 17.0
44 10x10_path_v4.txt BFS 0.0476 29.0 29.0
45 10x10_path_v4.txt DFS 0.0475 29.0 29.0
46 10x10_path_v4.txt A* 0.0652 29.0 29.0
47 10x10_path_v5.txt BFS 0.0302 13.0 13.0
48 10x10_path_v5.txt DFS 0.0334 13.0 13.0
49 10x10_path_v5.txt A* 0.0371 13.0 13.0
50 10x10_path_v6.txt BFS 0.0307 13.0 13.0
51 10x10_path_v6.txt DFS 0.0339 13.0 13.0
52 10x10_path_v6.txt A* 0.0375 13.0 13.0
53 10x10_path_v7.txt BFS 0.0401 17.0 17.0
54 10x10_path_v7.txt DFS 0.0499 17.0 17.0
55 10x10_path_v7.txt A* 0.0489 17.0 17.0
56 10x10_path_v8.txt BFS 0.0615 29.0 29.0
57 10x10_path_v8.txt DFS 0.0536 29.0 29.0
58 10x10_path_v8.txt A* 0.0801 29.0 29.0
59 10x10_path_v9.txt BFS 0.0579 17.0 17.0
60 10x10_path_v9.txt DFS 0.046 17.0 17.0
61 10x10_path_v9.txt A* 0.0468 17.0 17.0
62 30x30_empty_v1.txt BFS 1.1046 55.0 55.0
63 30x30_empty_v1.txt DFS 0.7781 379.0 379.0
64 30x30_empty_v1.txt A* 1.9965 55.0 55.0
65 30x30_empty_v10.txt BFS 1.1246 55.0 55.0
66 30x30_empty_v10.txt DFS 0.7002 379.0 379.0
67 30x30_empty_v10.txt A* 2.0086 55.0 55.0
68 30x30_empty_v2.txt BFS 1.1401 55.0 55.0
69 30x30_empty_v2.txt DFS 0.7263 379.0 379.0
70 30x30_empty_v2.txt A* 2.0245 55.0 55.0
71 30x30_empty_v3.txt BFS 1.1038 55.0 55.0
72 30x30_empty_v3.txt DFS 0.7249 379.0 379.0
73 30x30_empty_v3.txt A* 2.007 55.0 55.0
74 30x30_empty_v4.txt BFS 1.1224 55.0 55.0
75 30x30_empty_v4.txt DFS 0.7053 379.0 379.0
76 30x30_empty_v4.txt A* 1.989 55.0 55.0
77 30x30_empty_v5.txt BFS 1.1294 55.0 55.0
78 30x30_empty_v5.txt DFS 0.7202 379.0 379.0
79 30x30_empty_v5.txt A* 2.1138 55.0 55.0
80 30x30_empty_v6.txt BFS 1.0843 55.0 55.0
81 30x30_empty_v6.txt DFS 0.7746 379.0 379.0
82 30x30_empty_v6.txt A* 2.009 55.0 55.0
83 30x30_empty_v7.txt BFS 1.1449 55.0 55.0
84 30x30_empty_v7.txt DFS 0.7076 379.0 379.0
85 30x30_empty_v7.txt A* 2.033 55.0 55.0
86 30x30_empty_v8.txt BFS 1.3196 55.0 55.0
87 30x30_empty_v8.txt DFS 0.7794 379.0 379.0
88 30x30_empty_v8.txt A* 1.9972 55.0 55.0
89 30x30_empty_v9.txt BFS 1.1088 55.0 55.0
90 30x30_empty_v9.txt DFS 0.7131 379.0 379.0
91 30x30_empty_v9.txt A* 2.0128 55.0 55.0
92 50x50_deadends_v1.txt BFS 1.7809 729.0 729.0
93 50x50_deadends_v1.txt DFS 1.7167 729.0 729.0
94 50x50_deadends_v1.txt A* 2.5217 729.0 729.0
95 50x50_deadends_v10.txt BFS 0.7362 261.0 261.0
96 50x50_deadends_v10.txt DFS 1.7627 261.0 261.0
97 50x50_deadends_v10.txt A* 0.9753 261.0 261.0
98 50x50_deadends_v2.txt BFS 0.9246 249.0 249.0
99 50x50_deadends_v2.txt DFS 1.7347 249.0 249.0
100 50x50_deadends_v2.txt A* 1.0804 249.0 249.0
101 50x50_deadends_v3.txt BFS 0.945 297.0 297.0
102 50x50_deadends_v3.txt DFS 1.7483 297.0 297.0
103 50x50_deadends_v3.txt A* 1.0832 297.0 297.0
104 50x50_deadends_v4.txt BFS 1.5487 413.0 413.0
105 50x50_deadends_v4.txt DFS 1.6526 413.0 413.0
106 50x50_deadends_v4.txt A* 1.9521 413.0 413.0
107 50x50_deadends_v5.txt BFS 0.9255 309.0 309.0
108 50x50_deadends_v5.txt DFS 1.7299 309.0 309.0
109 50x50_deadends_v5.txt A* 1.1469 309.0 309.0
110 50x50_deadends_v6.txt BFS 1.0637 337.0 337.0
111 50x50_deadends_v6.txt DFS 1.7728 337.0 337.0
112 50x50_deadends_v6.txt A* 1.3449 337.0 337.0
113 50x50_deadends_v7.txt BFS 0.7827 261.0 261.0
114 50x50_deadends_v7.txt DFS 1.6948 261.0 261.0
115 50x50_deadends_v7.txt A* 0.9527 261.0 261.0
116 50x50_deadends_v8.txt BFS 1.5551 565.0 565.0
117 50x50_deadends_v8.txt DFS 1.7707 565.0 565.0
118 50x50_deadends_v8.txt A* 2.3158 565.0 565.0
119 50x50_deadends_v9.txt BFS 0.6693 209.0 209.0
120 50x50_deadends_v9.txt DFS 1.052 209.0 209.0
121 50x50_deadends_v9.txt A* 0.7957 209.0 209.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

@ -0,0 +1,11 @@
sphinx
myst-parser
sphinxawesome-theme
nbsphinx
myst-nb
tabulate
bibtexparser
pytest
sphinxcontrib-mermaid
matplotlib
pandas

View File

@ -0,0 +1,257 @@
from typing import Optional
from source.settings import cell_mapping
class Cell:
"""Представляет одну клетку поля лабиринта.
Каждая клетка хранит свои координаты и один из четырёх возможных
типов: стена, старт, выход или пустая клетка. Типы взаимоисключают
друг друга: установка одного автоматически сбрасывает остальные.
"""
def __init__(
self,
x: int,
y: int,
is_wall: bool = False,
is_start: bool = False,
is_exit: bool = False,
):
"""Инициализирует клетку с заданными координатами и типом.
Args:
x: Координата клетки по оси X.
y: Координата клетки по оси Y.
is_wall: Если True клетка является стеной.
is_start: Если True клетка является стартом.
is_exit: Если True клетка является выходом.
"""
self.x = x
self.y = y
self._is_wall = is_wall
self._is_start = is_start
self._is_exit = is_exit
def is_possible(self) -> bool:
"""Проверяет, можно ли переместиться в эту клетку.
Returns:
True, если клетка не является стеной, иначе False.
"""
return not self._is_wall
@property
def is_wall(self) -> bool:
"""True, если клетка является стеной."""
return self._is_wall
@property
def is_start(self) -> bool:
"""True, если клетка является стартовой позицией."""
return self._is_start
@property
def is_exit(self) -> bool:
"""True, если клетка является выходом из лабиринта."""
return self._is_exit
def _clear_flags(self) -> None:
"""Сбрасывает все флаги типа клетки в False."""
self._is_wall = False
self._is_start = False
self._is_exit = False
@is_wall.setter
def is_wall(self, value: bool) -> None:
"""Устанавливает флаг стены, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага стены.
"""
if value:
self._clear_flags()
self._is_wall = value
@is_start.setter
def is_start(self, value: bool) -> None:
"""Устанавливает флаг старта, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага старта.
"""
if value:
self._clear_flags()
self._is_start = value
@is_exit.setter
def is_exit(self, value: bool) -> None:
"""Устанавливает флаг выхода, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага выхода.
"""
if value:
self._clear_flags()
self._is_exit = value
def _get_type_cell(self) -> str:
"""Возвращает символ клетки согласно cell_mapping.
Returns:
Строковый символ, соответствующий текущему типу клетки.
"""
if self._is_wall:
return cell_mapping["wall"]
if self._is_start:
return cell_mapping["start"]
if self._is_exit:
return cell_mapping["exit"]
return cell_mapping["empty"]
def __str__(self) -> str:
return self._get_type_cell()
def __repr__(self) -> str:
return f"Cell(x={self.x}, y={self.y}, '{self._get_type_cell()}')"
class Maze:
"""Представляет двумерный лабиринт из клеток Cell.
Лабиринт хранится как список списков клеток. Доступ к отдельным
клеткам и их изменение возможны через индексацию вида maze[row, col].
"""
def __init__(self, size: tuple[int, int] = (10, 10)):
"""Создаёт пустой лабиринт заданного размера.
Args:
size: Кортеж (width, height) ширина и высота лабиринта в клетках.
"""
self._width, self._height = size
self._map: list[list[Cell]] = [
[Cell(x, y) for x in range(self._width)] for y in range(self._height)
]
def _check_point_in_map(self, x: int, y: int) -> bool:
"""Проверяет, находится ли точка в границах лабиринта.
Args:
x: Координата по оси X.
y: Координата по оси Y.
Returns:
True, если точка (x, y) находится внутри лабиринта.
"""
return 0 <= x < self._width and 0 <= y < self._height
def get_cell(self, x: int, y: int) -> Optional[Cell]:
"""Возвращает клетку по координатам или None, если координаты вне границ.
Args:
x: Координата по оси X.
y: Координата по оси Y.
Returns:
Объект Cell, если координаты корректны, иначе None.
"""
if not self._check_point_in_map(x, y):
return None
return self._map[y][x]
def get_neighbors(self, x: int, y: int) -> Optional[list[Cell]]:
"""Возвращает список проходимых соседей клетки (вверх, вправо, вниз, влево).
Args:
x: Координата клетки по оси X.
y: Координата клетки по оси Y.
Returns:
Список проходимых соседних клеток, или None если (x, y) вне границ.
"""
if not self._check_point_in_map(x, y):
return None
deltas = ((0, 1), (1, 0), (0, -1), (-1, 0))
neighbors = []
for dx, dy in deltas:
cell = self.get_cell(x + dx, y + dy)
if cell is not None and cell.is_possible():
neighbors.append(cell)
return neighbors
@property
def start(self) -> Optional[Cell]:
for y in range(self._height):
for x in range(self._width):
if self[y, x].is_start:
return self[y, x]
return None
@property
def exit(self) -> Optional[Cell]:
for y in range(self._height):
for x in range(self._width):
if self[y, x].is_exit:
return self[y, x]
return None
@property
def shape(self) -> tuple[int, int]:
return self._height, self._width
def __getitem__(self, index: tuple[int, int]) -> Cell:
"""Возвращает клетку по индексу [row, col].
Args:
index: Кортеж (row, col) строка и столбец.
Returns:
Объект Cell в позиции (row, col).
Raises:
IndexError: Если индекс выходит за пределы лабиринта.
"""
row, col = index
if not self._check_point_in_map(col, row):
raise IndexError(f"Индекс ({row}, {col}) выходит за пределы лабиринта")
return self._map[row][col]
def __setitem__(self, index: tuple[int, int], value: str) -> None:
"""Устанавливает тип клетки по индексу [row, col] через символ из cell_mapping.
Args:
index: Кортеж (row, col) строка и столбец.
value: Символ типа клетки согласно cell_mapping.
Raises:
IndexError: Если индекс выходит за пределы лабиринта.
ValueError: Если символ не найден в cell_mapping.
"""
row, col = index
if not self._check_point_in_map(col, row):
raise IndexError(f"Индекс ({row}, {col}) выходит за пределы лабиринта")
cell = self._map[row][col]
cell_type = next(
(t for t, s in cell_mapping.items() if s == value),
None,
)
if cell_type is None:
raise ValueError(f"Символ '{value}' не соответствует ни одному типу клетки")
if cell_type == "empty":
cell._clear_flags()
else:
setattr(cell, f"is_{cell_type}", True)
def __str__(self) -> str:
return "\n".join(
"".join(str(self._map[y][x]) for x in range(self._width))
for y in range(self._height)
)

View File

@ -0,0 +1 @@
cell_mapping = {"wall": "#", "empty": " ", "start": "S", "exit": "E"}

View File

@ -0,0 +1,11 @@
from source.strategy.algorithms import PathFindingStrategy
from source.strategy.astar import AStarStrategy
from source.strategy.bfs import BFSStrategy
from source.strategy.dfs import DFSStrategy
__all__ = [
"PathFindingStrategy",
"BFSStrategy",
"DFSStrategy",
"AStarStrategy",
]

View File

@ -0,0 +1,68 @@
from abc import ABC, abstractmethod
from typing import Optional
from source.models.base import Maze, Cell
class PathFindingStrategy(ABC):
"""Интерфейс стратегии поиска пути в лабиринте."""
@abstractmethod
def find_path(
self, maze: Maze, start: Cell = None, exit: Cell = None
) -> list[Cell]:
"""Найти путь от start до exit.
Args:
maze: Объект лабиринта.
start: Стартовая клетка.
exit: Целевая клетка.
Returns:
Список клеток пути (от start до exit включительно).
Пустой список, если путь не найден.
"""
def _validate(
self, maze: Maze, start: Optional[Cell], exit: Optional[Cell]
) -> tuple[Optional[Cell], Optional[Cell]]:
"""Подставляет start/exit из лабиринта если не переданы явно.
Raises:
ValueError: Если старт или выход не найдены.
"""
if start is None:
start = maze.start
if exit is None:
exit = maze.exit
if start is None:
raise ValueError("Стартовая клетка не найдена в лабиринте")
if exit is None:
raise ValueError("Выходная клетка не найдена в лабиринте")
return start, exit
def _reconstruct_path(
self, came_from: dict[Cell, Optional[Cell]], end: Cell
) -> list[Cell]:
"""Восстанавливает путь от старта до end, идя по came_from в обратном порядке.
Args:
came_from: Словарь {клетка: родитель}, где родитель старта = None.
end: Конечная клетка.
Returns:
Список клеток пути от старта до end включительно.
Пустой список, если end отсутствует в came_from.
"""
if end not in came_from:
return []
path: list[Cell] = []
current: Optional[Cell] = end
while current is not None:
path.append(current)
current = came_from[current]
path.reverse()
return path

View File

@ -0,0 +1,50 @@
import heapq
from typing import Optional
from source.models.base import Cell, Maze
from source.strategy.algorithms import PathFindingStrategy
# ---------------------------------------------------------------------------- #
# Моя азиатская жена называет меня расистом. Но как я могу #
# быть расистом, если я женился на женщине низшей расы?! #
# ---------------------------------------------------------------------------- #
def _manhattan(a: Cell, b: Cell) -> int:
"""Манхэттенское расстояние между двумя клетками."""
return abs(a.x - b.x) + abs(a.y - b.y)
class AStarStrategy(PathFindingStrategy):
"""Алгоритм A* с манхэттенской эвристикой."""
def find_path(
self, maze: Maze, start: Optional[Cell] = None, exit: Optional[Cell] = None
) -> list[Cell]:
start, exit = self._validate(maze, start, exit)
g_score: dict[Cell, int] = {start: 0}
came_from: dict[Cell, Optional[Cell]] = {start: None}
counter = 0
open_heap: list[tuple[int, int, Cell]] = [
(_manhattan(start, exit), counter, start)
]
while open_heap:
_, _, current = heapq.heappop(open_heap)
if current is exit:
return self._reconstruct_path(came_from, exit)
for neighbor in maze.get_neighbors(current.x, current.y):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float("inf")):
g_score[neighbor] = tentative_g
came_from[neighbor] = current
f = tentative_g + _manhattan(neighbor, exit)
counter += 1
heapq.heappush(open_heap, (f, counter, neighbor))
return []

View File

@ -0,0 +1,34 @@
from collections import deque
from typing import Optional
from source.models.base import Cell, Maze
from source.strategy.algorithms import PathFindingStrategy
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину (Breadth-First Search).
Гарантирует кратчайший путь по количеству шагов.
Сложность: O(V + E) по времени и памяти.
"""
def find_path(
self, maze: Maze, start: Optional[Cell] = None, exit: Optional[Cell] = None
) -> list[Cell]:
start, exit = self._validate(maze, start, exit)
came_from: dict[Cell, Optional[Cell]] = {start: None}
queue: deque[Cell] = deque([start])
while queue:
current = queue.popleft()
if current is exit:
return self._reconstruct_path(came_from, exit)
for neighbor in maze.get_neighbors(current.x, current.y):
if neighbor not in came_from:
came_from[neighbor] = current
queue.append(neighbor)
return []

View File

@ -0,0 +1,38 @@
from typing import Optional
from source.models.base import Maze, Cell
from source.strategy.algorithms import PathFindingStrategy
# ---------------------------------------------------------------------------- #
# Как называется пресмыкающийся, который в прошлом был программистом? #
# ---------------------------------------------------------------------------- #
# крокодил #
# ---------------------------------------------------------------------------- #
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину (Depth-First Search).
Находит путь, но не гарантирует кратчайший.
"""
def find_path(
self, maze: Maze, start: Optional[Cell] = None, exit: Optional[Cell] = None
) -> list[Cell]:
start, exit = self._validate(maze, start, exit)
came_from: dict[Cell, Optional[Cell]] = {start: None}
stack: list[Cell] = [start]
while stack:
current = stack.pop()
if current is exit:
return self._reconstruct_path(came_from, exit)
for neighbor in maze.get_neighbors(current.x, current.y):
if neighbor not in came_from:
came_from[neighbor] = current
stack.append(neighbor)
return []

View File

@ -0,0 +1,94 @@
import time
from dataclasses import dataclass
from source.models.base import Maze, Cell
from source.strategy import PathFindingStrategy
@dataclass
class SearchStats:
"""Статистика выполнения поиска пути.
Attributes:
elapsed_ms: Время выполнения в миллисекундах.
visited_count: Количество посещённых клеток.
path_length: Длина найденного пути (0 если путь не найден).
path: Найденный путь список клеток от старта до выхода.
"""
elapsed_ms: float
visited_count: int
path_length: int
path: list[Cell]
def __str__(self) -> str:
return (
f"Время: {self.elapsed_ms:.3f} мс | "
f"Посещено клеток: {self.visited_count} | "
f"Длина пути: {self.path_length}"
)
class MazeSolver:
"""Оркестратор поиска пути в лабиринте.
Принимает лабиринт и стратегию поиска, выполняет поиск
и возвращает результат вместе со статистикой выполнения.
Example:
solver = MazeSolver(maze, BFSStrategy())
stats = solver.solve()
print(stats)
solver.set_strategy(AStarStrategy())
stats = solver.solve()
"""
def __init__(self, maze: Maze, strategy: PathFindingStrategy) -> None:
"""Инициализирует солвер с лабиринтом и стратегией поиска.
Args:
maze: Объект лабиринта.
strategy: Стратегия поиска пути.
"""
self._maze = maze
self._strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy) -> None:
"""Заменяет текущую стратегию поиска.
Args:
strategy: Новая стратегия поиска пути.
"""
self._strategy = strategy
def solve(
self,
start: Cell = None,
exit: Cell = None,
) -> SearchStats:
"""Выполняет поиск пути и собирает статистику.
Если start или exit не переданы явно, стратегия найдёт
их самостоятельно по флагам is_start / is_exit в лабиринте.
Args:
start: Стартовая клетка (опционально).
exit: Конечная клетка (опционально).
Returns:
Объект SearchStats с временем выполнения, количеством
посещённых клеток и длиной найденного пути.
"""
t_start = time.perf_counter()
path = self._strategy.find_path(self._maze, start, exit)
t_end = time.perf_counter()
elapsed_ms = (t_end - t_start) * 1000
return SearchStats(
elapsed_ms=elapsed_ms,
visited_count=len(path),
path_length=len(path),
path=path,
)

View File

@ -0,0 +1,99 @@
###################################################################################################
#S # # # # # # # # # # #
### ##### ### #### ### # ### # ### # ####### # ## # # # # # # # ###### ## # # # ### ## # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### # # ## # # ### # ### # ## # # # ##### # ##### ### ## ####### # ##### # ### # # ### #
# # # # # # # # # # # # # # # # # # # # #
# ### # ## ###### # # # # # ### ##### # ## ##### ### # # #### # #### ### ##### ### ## ## #
# # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # # ## # ## # ### # ##### # # # ## ##### ### # # ### # # # # ### # # # ## # # #####
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # ##### # # # # ### # # # # # # # # # # ## # #### ### # ### # # ### ### # # # # ## #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### ### # # # # # ##### ### # ## # ### # # # # # # ### ##### ####### # ### ### # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # ### # ### # # # # ## # ### ### # ##### # # # ####### # ##### # # # # # ####### # # #
# # # # # # # # # # # # # # # # # # # #
# # ### # #### ### ####### ### ## # ## ## # ######## ####### # ### ######## ### # # ### #
# # # # # # # # # # # # # # # # #
# #### # # ## # ### #### # # #### ##### # # ## ### # # # # #### # ## ##### ### ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # # # ## ### # # # ### # # # # ##### ##### # # ###### # # # ## ## ## #### ### #
# # # # # # # # # # # # # # # # # # # # # # #
### # # ########### # ####### # ## # # # #### # ### ### # # # ### # ### ### # # # # ## ####
# # # # # # # # # # # # # # # # # # # #
# # # # # # # # #### ### # # # # ### # ##### ## ## # # ######### ####### # # ## # ### #
# # # # # # # # # # # # # # # # # # # # #
# ##### # ### # # # ## # # # # ### ##### ## #### # # ###### ## # # # # # ### # ### # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # # ### ######### # # ### # ##### # ####### # # # # # # # # # ##### # ##### # # #### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
##### # # ### ## # # ### # # ### # # ### # # # #### ### ##### ### # # # # # ########## # ### # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ##### ### # # # ### # # # # # ### ### ##### # # # ## ########### # # #### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### ##### # ### ##### # # # # # # # # ##### # # ####### ### # # # ## # # # # ##### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ## ### # ### # # # #### # ## # ##### # # # # # ### # #### ## ##### ####### # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ## # ### ##### ##### # # # ##### # # # #### # # ###### ## # # # ### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ####### # # # #### # # # ## # ### # #### # # ##### ## ## # # # # # # ### # # # # ### #
# # # # # # # # # # # # # # # # # # #
### ### # # # # ## ###### ### # #### # # ##### ##### ### ## ## # ######### # # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### #### # ### # ### # #### ### # ##### ### ## # ### # ##### ##### # # # ## #
# # # # # # # # # # # # # # # # # # # # # #
# ## # ##### # ## ## ####### ### # ##### ##### # ##### # # # # # ##### # ##### ## # ### # ###
# # # # # # # # # # # # # # # # # # # #
# # ##### ########### # # ######### # # ### # ##### ### ### ### ##### ### ######### ### # ## #
# # # # # # # # # # # # # # # # # # # # # #
# ## # ### # # ## #### # # ##### ## # ## # #### # # # # ### # # # # ### # # # # # # #####
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
### # ### # # ### # ##### ### # # # # # # ## ## # # # ## ###### # ## # ########## # ####### #
# # # # # # # # # # # # # # # # # # # # # # #
# ##### ##### # ####### ### # # # ### ## # ###### # ### # # ### # ### ### # ### ### ### # #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # ## ## #### # ## ## # # # # ######### ### # # # ### # # # # # # ## ## # ### ### #
# # # # # # # # # # # # # # # # # # # # # #
# ### # ##### ## ### ## ### # ### ##### # # # #### ##### # ### ######### # # #### # # #
# # # # # # # # # # # # # # # # # # # # # #
### # ## ## ### # # ## #### # ### # # # # # # ##### # ### ### ## #### ## ### ## # # #
# # # # # # # # # # # # # # # # # # # # #
# ### # # # ###### # # ####### ### ### # # ##### ##### ###### # # # ##### ##### # # # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# ### # ### ### ### ######### ### # # ## #### ### # #### ### ##### # ######## # #######
# # # # # # # # # # # # # # # # # # # #
# # # ### ### # # # # ## ## # ### # ## # # ##### # ### ## ####### # ## ### # ### # ## # #
# # # # # # # # # # # # # # # # # # # # # # # #
### ### # # ######### # ### # ### ####### ## # # ### # # # # ###### # ### # # # ## # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # # # # ##### # ######### #### ## ## ##### ## ## ##### #### # # ### # ### #
# # # # # # # # # # # # # # # # # #
# ### ##### # # ##### ####### # # # ##### # ##### ### ## ## # ### ### # ### ## #### ####### ###
# # # # # # # # # # # # # # # # # # # # # # # #
# # ## # #### #### # # # ### # ## #### # ## ## # # ##### # ##### # # # # # ### ##### # # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ### ### # # ### # # ##### ### ### ## # # # ### # # # ####### # ### ##### ## # # #
# # # # # # # # # # # # # # # # #
# # ## ######## ### ####### ###### ##### # ############### # # ### # ##### # ### ### ## ## # #
# # # # # # # # # # # # # # # # # #
# # ### # ##### # # # # # ## # # ## # # # # ######### # # ##### # # ####### ####### #
# # # # # # # # # # # # # # # # # # # # # # #
### # # # ##### # # # ### # # # # ####### ### # ## ## ### ## ###### # ### # # # ## ## #
# # # # # # # # # # # # # # # # # # # #
# # ## ## ### ######### ####### # ######### ### # ### # # # # # # # # ### #### ## ###
# # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # # ### ##### ########## ## ### # # ### ##### # ### #### ##### ####### # # # ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # ### # # # ### # ###### # # ##### ## # ######## # ### # #### #### ### # # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ## # # # # # ## # # ### # # # #### # # # # ## #### # # # # ## ### # ## ## # ### #
# # # # # # # # # # # # # # # # # # # # # # #
### # # ########## # ####### # ### ### # ### ##### # # # ## ### ### ### # # ##### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # # ## # # # # # # # # ### ##### ##### # ### ## # ####### # ### # ### # # ###
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # ##### # #### # ## ## ##### # ######### # ## ### # #### ## # # # # # ######### #
# # # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S# # # # # # # # # # # # # # #
# # # # # # # # ### # ### # # # # # ## ##### # # ## # # ### # # ### # # ## # ### ### # # #
# # # # # # # # # # # # # # # # # # # # # # #
##### ### ##### ## # # ### # # # # # # # # # # # # ## # # # #### ### # # ## ### ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### ###### ## # # ### ##### # # # ### ### ##### # # # # # ## # # ### ### # # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # ### ##### # ### ## # # # # # # ### ####### # ####### # # ## ## ###### #### ## ## #
# # # # # # # # # # # # # # # # # # # # # # #
### ##### # # # ### # # # # ### # #### # ### ####### # #### #### # ### # # # #### # ###
# # # # # # # # # # # # # # # # # # # # # # #
# ### # ## ### # ### # # ####### ### ### # ######### ### # # # ### # # ### # ### ######### # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # # #### # # ### # # #### # # # # # ####### # # ## ## ### # # ### ##### # # ## ### ### #
# # # # # # # # # # # # # # # # # # # # # #
# ### # # ##### ### # # ###### ## # ## ## # # ### # # ### # # ####### # ### # ## # ### # ### #
# # # # # # # # # # # # # # # # # # # #
# # ## ##### # # # # ## ## # # ####### # ### ##### # # ####### ## # # # ###### #### # ### # #
# # # # # # # # # # # # # # # # # #
##### ### ##### ##### ### ##### # ## ## # # ##### # ##### ####### # #### ### # ## ## # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
## # ####### ### # # # # # #### # # ### ##### #### # #### ## # # # # ## # ### ### # # ###
# # # # # # # # # # # # # # # # # #
# # # ## ####### ### # ## # # # ## # # ####### # # # ### ### # # # # # # ##### # # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # ### ##### #### ## # # # ######### # ##### ### ### # ## ## ### # # ###### # #
# # # # # # # # # # # # # # # # # # # # #
# ### #### # ### # # ### # # ##### ## ### # ## ######### ### #### ### # # # ##### # ##### #
# # # # # # # # # # # # # # # # # # # # #
# # ### # ##### ### ### # ####### ### # ## # ## # # # ##### # ### # #### #### ### ### ## ####
# # # # # # # # # # # # # # # # # # # # # # #
# ####### # # ### # # ##### # ### # # # #### ##### # # ## ##### # # # # # ### ### # # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ## # # # ### ### # # # # ### # ##### ### # # # ## # # # # # ########### ### ### # # #
# # # # # # # # # # # # # # # # # # # # #
# # ##### #### # ### # ######### ### ## ###### ######### ### ##### ## # ### # # ##### #### #####
# # # # # # # # # # # # # # # # #
# # # ## ### ##### # # ### ### ### ### # # ####### ####### # # ##### # # ### ### ##### ## # #
# # # # # # # # # # # # # # # # # # # # # #
# ## ##### ### # ## # # # # ##### # # #### # ####### ##### # # ##### # ## ## # # ### ## #
# # # # # # # # # # # # # # # # # # # # # # #
### ## # ##### ##### # ## ### ## # # # # # # ### ### # ### ### # ### # # # # ## # ### ##### #
# # # # # # # # # # # # # # # # # # # # # #
# ### # ### ### ## # # # #### # #### # ####### ### # ### #### ## # # #### # ## # ### # #
# # # # # # # # # # # # # # # # #
### # ### # # # #### # ######### # # ### # # # # ### ##### ###### ## # # #### #### ### ## #
# # # # # # # # # # # # # # # #
# ### # ### ##### # # ######### # ### ### ### ### # ####### ##### ####### # # ##### # #### ##
# # # # # # # # # # # # # # # #
### # # # ########### ### # # # ##### ##### ## #### # ###### #### #### # #### # ######### # #
# # # # # # # # # # # # # # # # # # # # #
# ### ####### ##### # # # # ## ### #### ## # # ### ##### # ### # # # # ###### ## # # #### # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ### ####### # # # ### ### ########### # # # # ## # ## # ###### ### # # ### # # # ## #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # ##### # ### ### # ### ### ### # # # ##### # # # # ### # ### ## ########### # ### #
# # # # # # # # # # # # # # # # # # # # # #
# # # # # ### # ### # ####### # # # # ## ### ### # ### # ### # ## # # # # # # ## ### ### #
# # # # # # # # # # # # # # # # # # # # # # # # # #
### ### # # # ##### ##### # # ### ##### ### # ### # ##### # ## ####### # # # # # # #### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ## ### ####### # ## ## ##### # # # ### ### ### ### # # # # # # ##### # ### ##### # # # #####
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # ####### #### # ##### # ## # ## # #### #### # # ### ### ### # # # # ## ###### #
# # # # # # # # # # # # # # # # # # # # #
# ### # # ### # # # # ##### # ##### # # ######## ##### # # # ### # ##### ####### # ### # ## # #
# # # # # # # # # # # # # # # # # # # # # #
# #### # # # ## ## # ##### # ## # # # # # # # ##### ### #### # # # # ##### # # # ### # ### # # #
# # # # # # # # # # # # # # # # # # # # # # #
## ## # ### # # ### ### # ##### ### # # # ##### ## # # # ### ### # ##### ####### ##### #### #
# # # # # # # # # # # # # # # # # # # # #
# #### # ## ## #### ##### # ### # ### ##### # ## # # # # ## ### ####### ####### ### # ##
# # # # # # # # # # # # # # # # # # # # #
# # ### #### # # ## ##### ### # # # ### # # ### # ####### # ### # ## #### # ## # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ## ### # # ####### # # ## # # ### ### # ### # # ##### ## # # ## # # # ## # # #### # #
# # # # # # # # # # # # # # # # # # # # # #
# ### # # ##### # # # # # # # ### ### ### ########## ##### ### # ##### # # ##### ####### #### #
# # # # # # # # # # # # # # # # # # # # # #
##### ## ## # # # ## ### # # # ####### # ## # # # ######## ### ## #### # ##### ##### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ## ## ####### # ## ### ########### ### ### # # #### ### # # #### # ## # ### # # #
# # # # # # # # # # # # # # # # # # #
# ### # ##### # # ### ### # ### # # ### # ###### # ## ### # ## ## # # ### # ### ### # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ### # # # # # # # # # # # # # # ### ##### # # ### ### ## #### # # ### #### # # ## ## ###
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ##### ## #### ##### # ## ### # # # ##### # # # ##### # # # # ######### # # ### ##### # ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # ##### # # ## # # ## # # # # ## # ## # # # # # # ### ## ### # #### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### # # # # ##### # ### ##### # # # # ### ### # ### # ##### # # # ### # # # ##### ##### ## #
# # # # # # # # # # # # # # # # # # # # # # #
### # ##### ### ### # # # # ### # # ## ### ####### ######## # # ### ## ### ### ####### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ###### ## # # # # ## ###### ## ## ### ### ### # ### # # # # ####### # ### # ### #### # #
# # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S# # # # # # # # # # #
# # ### # # ### ### # ## ## ####### # ########### # ### # ## #### # ### # # ### # # ### #
# # # # # # # # # # # # # # # # # # #
# # ### ## # ## # # ###### #### ## ###### ### ##### ## #### ### # ## # # ####### # ##### ###
# # # # # # # # # # # # # # # # # # # # #
# # ### ### #### ## ### #### ### # # ### ### ##### #### # # ##### ### # # ### ####### # ##### #
# # # # # # # # # # # # # # # # # # # # #
##### ##### # # # # ### ### #### ##### # ####### ## ## ####### # ## # #### ## # ####### # # #
# # # # # # # # # # # # # # # # # # # #
# #### ## ## ########## # # # ### ##### # ######## ## ## ###### #### ## #### ### # # ### ##
# # # # # # # # # # # # # # # #
# ### ### # ####### # ## # ## # # # # # # # # # ###### # # # ### # # ##### # # # #### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # #### # #### # # #### # ##### # # # ### # # # # # ############# # # ## ##### # # #
# # # # # # # # # # # # # # # # # # # #
### # ## ### # ### ## ##### ### # ### # # ## #### # ####### # ### # ### # # # # ## # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # #### ## ##### # ## # ##### # # ### # ### # # # # ####### ##### ### # # ## #### #####
# # # # # # # # # # # # # # # # # # # # # # # #
# # ######### ## #### ### # ##### ################# # # # ### #### # # ### # ## # ### #
# # # # # # # # # # # # # # # # # # # # # # #
# ### ## # ## # # ### # # # ##### ### # ### # # ### # #### ## # # ### # ###### ### # ### # #
# # # # # # # # # # # # # # # # # # # # # #
# # ### ### # # # # ### # ## ## # ##### # # # # ### # ##### ########### # ######## ####### #
# # # # # # # # # # # # # # # # # # #
# ### # # # # ### ##### # # ### # ## # ### ### # # ## #### # # # # ##### # ### # # #### # # # # #
# # # # # # # # # # # # # # # # #
####### ## # ###### ### # # ## ### #### ### # #### ## # ### # # # ### # #### ### # ### # # # # #
# # # # # # # # # # # # # # # # # # # #
###### ## ## ### # ## # #### # # ##### # ## ## # ##### ### ### ##### # ### # # # # ### # #
# # # # # # # # # # # # # # # # # # #
# ####### # # # ## # # ### # ####### ## ### ## ### # # # # ## # # ### # ####### # # # #
# # # # # # # # # # # # # # # # # # #
# # ## ### # ## ## # # ##### ### ## ## #### # # ##### # # # ### ## ## ### ## # ### ## # ##### #
# # # # # # # # # # # # # # # # # # # # # #
# # # ######### ### # # ## # # ### ### ## ### ### # ##### # ## # ### # # ############ ### # ###
# # # # # # # # # # # # # # # # # # #
# # #### ## # ### #### ### ## #### ## # # ### # # ## # # ### ### ## # ###### ### ### # #
# # # # # # # # # # # # # # # # # # # # #
# ### ## #### # # ## ########### # # # # # ####### ## # ##### # # # # # #### # # # ## #
# # # # # # # # # # # # # # # # # # # #
# ### ### # # ##### # # ### # ## ## # # ### ### ### ### # ### # # # #### ## # #### # ##### ### #
# # # # # # # # # # # # # # # # # #
# # ### # ## ## # # ### ####### ### # # ## ## # # ########### # # ########### # # # ### ##### #
# # # # # # # # # # # # # # # # # # #
# ##### ## #### # #### ####### # # ## ### ##### # ##### ##### ### # ######### # ###### # # #####
# # # # # # # # # # # # # # #
# # # ### ### ##### # ##### ### #### ## ## # # ##### ######### # ############# # ##### ###
# # # # # # # # # # # # # # # # # # # #
##### # ######### # ### # # ### # # # ###### # ### ### # # ## ########## #### # ## ##### ## #
# # # # # # # # # # # # # # # # # # # # #
### ###### # ### # ### # ## # #### ### # ### # # ### ### # # ### # # ### ####### ### # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # ####### # ### ##### # # # ##### ### ## ##### ##### ### ### ### # # # ### # ## #
# # # # # # # # # # # # # # # # # # # # #
# ### # # ### ###### ### ###### ####### # # # ### ####### # #### ### # ##### ## # ####### ###
# # # # # # # # # # # # # # # # # # # # # #
# ##### # # ### # # ### # ## # # # # ##### # ### ### ####### # ###### #### ## # #### ### # # # #
# # # # # # # # # # # # # # # # # # # # # #
### # # # # # ## #### ### # # # ##### ### ## #### ### # # ### # ### ##### ## # # ####### #
# # # # # # # # # # # # # # # # # # # # # #
# # # ## # ### # ##### # # # # # ##### ################### # # ##### ## # # ### # # # ### #######
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # ### # # ### # # # # # ### ### # ##### # ## # ##### # ## # # # ## ## # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # ## ### ## # ## # # # # # # ##### ######### # ## ## # ### ##### ### #
# # # # # # # # # # # # # # # # # # # #
# ### # # # ## ## # ## ### # ###### ## ## # ####### # # ### #### # # # ### ### #### # # #####
# # # # # # # # # # # # # # # #
# #### # ##### ### ## # #### ### ### # ### ######## # ## # # # # # # # ### ### ######## ## #
# # # # # # # # # # # # # # # # # # # # # #
# ## ### # # # ### # #### # ####### # # ## # ### ####### ### # ### # # # # ### # ### # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ### ##### ##### #### # # ######### # # # ### ####### # # ###### # # ####### ## # # ### #
# # # # # # # # # # # # # # # # # # # # #
# ## ### # ### # # # ### # # ### #### ######## # # ### # ### # # # # ### # ### ### ##### ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # #### # # ### ##### # ### #### ### # ### # # ### # ## # ### # ######### # ## ####
# # # # # # # # # # # # # # # # # # # # # # #
# # ### # ### # ######### # # ### # # # # # ### #### ## # # # #### #### ### # ###### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ##### ### # # ### ### ##### # # # ######## # # # # # ####### # ##### # ### ### # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ##### ##### ### ## ## # # # # # # ## # ### ### # ########### # ##### ## ### ### # #
# # # # # # # # # # # # # # # # # # # # #
# # # # ##### # # ## # #### # # ###### # ## #### ### ### # # # # # ### # # # # # # ### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ##### ## ## # # #### # # # ##### # ### # # # ### # # # ## #### # # ### # ### ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### #### ### ## ### # ### ### #### # # # ## #### # ### # # ## ### ########## ## ### # # # ##
# # # # # # # # # # # # # # # # # # # # #
# # # # # # # ### ### # ### # ## # ## #### # ### # ### # ### # ### ### # # # ### ## # ## ##
# # # # # # # # # # # # # # # # # # # # # #
# #### # # # # #### # ### # ####### # # ### #### # # # ## ### # # ### # ## # ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### # ##### # ### # # # ### # # # ## # # # ### # # # # ###### #### ### ## ### # ### ##### #
# # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S # # # # # # # # # # # #
##### # # ### ####### ### # # ### ## # # # # ### # # # ## # # # # ##### # # # ### ## ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ###### # # # ###### # # # # #### # # # ### # # # # # # ### ##### # # ############# # # #
# # # # # # # # # # # # # # # # # # # #
# ### ## # #### ### # # # # # # # # ##### # #### ##### # # # # ### ### ### # ## ### ##### #
# # # # # # # # # # # # # # # # # # # # # #
# # ### ##### ## ### # # #### #### # # ## ###### ### # # # # ##### ### # ## ####### # # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
##### # # ######### # ##### ##### # ## ### # ##### ####### # # # # # # # #### ## # # # ### # #
# # # # # # # # # # # # # # # # # # # # #
# # # ### # ## # ## ####### # # # ##### # ### ####### ## ### ##### # ## # # ###### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ## # # # ### # #### # ### ##### # ### ### # # ##### # # ### ######### ## ### # # ##### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # ## # # ### ####### ### # # ## ##### ## ## # ## # # # # ## # # # # ## ### ###
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
### # ### ### # # ### ### # # ### # #### ### # # ##### ### ######### # # # # # # # ## # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ####### # # #### # # ## # ####### # ### ### # # # ####### ### ### ## # # ##### #
# # # # # # # # # # # # # # # # # # # # #
# # #### ## ## ### # ### # # #### ## #### # ##### ##### ### # # ## ## # ### # ##### # #
# # # # # # # # # # # # # # # # # # # # # #
# ####### # # ## # ### ### # # # ## # ## # ## ## # ##### ### ## # ### ### # ### ###### # ###
# # # # # # # # # # # # # # # # # # #
# # # ######### ##### # # # # # #### ## ##### ####### # ##### # ## ##### ### ### ####### #
# # # # # # # # # # # # # # # # #
# # # # # ### ######### ### # ## # ### # # ### # ####### ##### ### ### ### # # ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
## # # ### ### # ### # ##### ## ## # # ########### # # # ####### # # ## # # # # ### ##### # ## #
# # # # # # # # # # # # # # # # # # # # # #
##### # ### ####### # ### ## ##### # # # ##### # # # ## # # ### # #### # ######## #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # ### ##### # # # # # ## # # # # # # # #### ### # # ### # ### ### ##### # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # ##### # # ## ##### ######### # # ##### # ### # # ##### #### ## # ####### # # #
# # # # # # # # # # # # # # # # # # # #
# # ## ## # ## # ### # # # # ##### ##### # ### ## ### # ### # # # ##### # # ##### # # ## # ### #
# # # # # # # # # # # # # # # # # # # # #
# #### # # #### ### # # # ### # ### ### ## # # ######### # ## # ##### # ##### ## # # # #### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # ## ## ## # ## # ##### # ### ### ## ## # # ### # ### # # # ### # ### # # ####### # ###
# # # # # # # # # # # # # # # # # # # # # #
# ## ## # # # ####### ### # ### # ####### # #### # # # # # # # # ### # ### # ##### # # # ### #
# # # # # # # # # # # # # # # # # # #
## #### ########### # # # # # ##### # #### ## ########### # # # ### # ### ### ## ## ##### # # #
# # # # # # # # # # # # # # # # #
# # # ### # ### # # # ### # ## ## ### # ### ### # # ### ### #### ## ### # ### # # ### #
# # # # # # # # # # # # # # # # # # # # # #
# # ### ##### # # ### # ####### # # # # # # # # ### ### # ## # ##### ### ### #### # # # ###
# # # # # # # # # # # # # # # # # # #
# ### # # # # ## ## ### # # ##### ########### # ### ##### ### # ### # # ### ## ## # # ### #
# # # # # # # # # # # # # # # # # # # # # #
# # ## # # # # ### # # # #### ## ############# # ### # # # # ### ### # ### # # # # ### # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# # # # ### ### ### # ### # # #### ## # ### # ### # ## ### # #### #### ### ### ##### ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # ####### ### ##### # # # ### # ## ### # # # ### ##### ##### # ### # # ### # # ####### #
# # # # # # # # # # # # # # # # # # # # # # #
## # # # # # # # # ### ### ## ### # ##### # # ## ###### ## ## # ##### ### # ####### # #
# # # # # # # # # # # # # # # # # # # # # #
# ###### ### #### ## ####### ## ## ### ### # ### ### # # ##### # # # # ### # ### ### # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # # ## ### ### # ### ######## ## # # # # ## ### ###### ## ####### # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### # ### # ## # ###### # # # # # # #### ## # ### # # ### ### # ###### ## ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ####### # ### # ##### ##### # ############# # ############ # # # ### # # # ## # ## # # #
# # # # # # # # # # # # # # # # # # #
# ############ # # # # ##### # ### # ## ##### ### # ##### #### # # ##### ### ##### # # # #
# # # # # # # # # # # # # # # # # # # # # #
# ### # ### # # # # # ## ### # # # # # # # # # ### # ## #### # #### # #### # ### #### ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ###### ### # ### # # # # # ### ### # #### ### ## # ####### # #### # ##### ##### # # #### #
# # # # # # # # # # # # # # # # # # # # # #
### # # ## ######### # ## # # ##### # ### # # # # # # # # #### ## # #### # ## # ### # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ### ### # ### ### # ## ## ### # # ########### ####### #### # ###### ##### # # # ### ## ##
# # # # # # # # # # # # # # # # # #
# ### # # ##### ### ## ####### ### ####### ### ##### ### ###### ###### # # # # ### # # ## #
# # # # # # # # # # # # # # # # # # #
### ### ##### # # # ######### # # ### # # ## ### ### # ### # # ##### # ##### ### # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ##### # # # # ## ##### # ####### # ### # ###### # # # # # # ## #### # # # # ##### # ## #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### ### ##### # # # # # # # #### ## ### ##### ### # ## # ##### ## # ## ##### # # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # # # # # ### # ###### # ### #### # ### # ##### # ##### ### # # # ### ### ### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
### # # ### ## # # # ### # ##### # ## #### ### ###### # # ## # ###### ## ### # # ### ### # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ###### ### # # ### # ## ##### # # ### # # # ## # # ##### ########### ##### ##### # # ## ## ###
# # # # # # # # # # # # # # # # # # # # # #
# # # ### # # ######### # ## ## ### # # # ####### #### #### ## ######## ####### ### ### # ### #
# # # # # # # # # # # # # # # # # # #
### ## # # ### # ## ### ###### # # ### ##### ### # #### # ## # # # ####### # ### # ## ## #
# # # # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S # # # # # # # # #
# ######## # ##### ### ## #### # # ### ##### # ### # ### # ### ##### # # # ##### # ##### # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ####### ##### # ### ## ## ##### ####### ### ##### # # ##### # # # # # ### ## ##### ###
# # # # # # # # # # # # # # # # # # # # # #
# # # # ## ######## # ######## ### # # ### # ### # # ##### # # ##### ### ### # ## # ### #
# # # # # # # # # # # # # # # # # # #
# ### # # ## ## ### # # # # #### #### # # # # # ####### # # ##### ########### # ### # # # ## #
# # # # # # # # # # # # # # # # # # # # #
# ##### ## # # # ##### # # ####### ### #### # ### # # # # # ### ## ######## # # # # # ## #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### # # ##### # ######### # # # ## ##### # # # # #### ###### # ## # ## # ### # # ###
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # ## ## ## #### ####### ##### # # # # # # # # ######### ##### # # # ### # # # # # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### ### ### # ### ## # # # # # ###### ## # # ### ### # ### # # # ### # # ## ## ### #
# # # # # # # # # # # # # # # # # # # # # #
# ##### ######### ### ## ###### ## # # ##### ##### # ### ##### # ### ## # ##### # # ##
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # #### # ## # # # # # # ### ### # # # # # ####### ### # ### ## #### ### # # ## ########## #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # ######### ### # # ### ##### ## # #### ## # ##### # # ### ##### ### # ##### # ##### ### #
# # # # # # # # # # # # # # # # # # #
### ####### # # ##### # ##### # #### ########### # # # # #### ## # ### # # #### # # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ####### # # # ### # ## ### # ##### ### # # # ### # # # ### ##### #### # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ### ######### ################# ## ### # # ### # ######## ### # ##### ## # ## ## # #
# # # # # # # # # # # # # # # # # # #
# ### ### # ####### ### #### # # # ### # ### # ### ## ### # ### ### ### # # # # ### # # #
# # # # # # # # # # # # # # # # # # #
### # ####### # # # # # ### ##### # ### # #### # ## ######### ### ####### #### ### # #####
# # # # # # # # # # # # # # # # # # # # # # #
# # # # ##### ### # ## # # # ## ### ### ## ###### ### # # # ### # # # ## # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ## ####### # # # # # # # ### ### ## ## # ######## # # ### # ## ## ##### # # # ### # ### # #
# # # # # # # # # # # # # # # # # # # # # # #
# ######### # ### # ### # # #### ## # #### #### ## # ##### ## ######### # ### # ### ### ### #
# # # # # # # # # # # # # # # # #
## ### # ## ##### ##### ##### # # ## ## # # # ##### ##### # # ####### # # # # ## ## #### ###
# # # # # # # # # # # # # # # # # # #
# ### # ### # ##### ##### # ## # # # ##### # # # # ## ### ### ## # ### ### ## # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # ######### # # ### # # # ## ## # # ## ## ## ##### ## ############# # ##### # ### #
# # # # # # # # # # # # # # # # # # # # #
# # # #### ### # # ####### # ### ###### # #### # # # # #### # # # ###### ## ##### # ### # # #
# # # # # # # # # # # # # # # # # # #
# # # # # # # ### #### # ### # # ######## ### ### ### ### ## # # ### # ### ##### # # ### #
# # # # # # # # # # # # # # # # # # # # # #
# #### # ### # ## ## # # # # ##### # ##### # ### # ### # # # ### ### # ### ##### ## ### # #
# # # # # # # # # # # # # # # # # # #
# ##### ## # # # # # ## ### # ## ## # # # # ### # # ### ### ### ######### # # ###### # # #
# # # # # # # # # # # # # # # # # # # # # #
# ### ### ### ##### ##### ## ##### # # ### ##### ##### # ### #### # ### ##### ### ### # ##### # #
# # # # # # # # # # # # # # # # # # # # #
## ### # ## # # # # ## # # ## ##### # # ## ######### # # # ### ## #### # # # ## # ## ##
# # # # # # # # # # # # # # # # # # # # # # #
# ### ### # ## ## ### # # ### # ########### # # # ##### # # # ### # # ### ##### ### # # ### # #
# # # # # # # # # # # # # # # # # # # # # # #
### ##### ## # # ## #### ### # ### # # # # ## ## # # # ###### # # ### # ## ## ## ### ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### # ### ##### ##### # ###### # # ###### # #### ## ### # # # # ### ## #### ##### #
# # # # # # # # # # # # # # # # # # # # #
# ### ### # # # # # ### # # # ##### # # ###### #### ##### # # ## # ### # ##### # ### #####
# # # # # # # # # # # # # # # #
# ### ########### ##### # # ## ## ### # ### # ### ## ### ### # ######### # ### ####### ### # #
# # # # # # # # # # # # # # # #
# ### # ##### # # # ### ### ### ### ##### ### ### ###### ###### #### ## # # ### # ### # ###### #
# # # # # # # # # # # # # # # # # # #
# # #### # # ##### # #### ### # # ##### # ### ##### ####### # # ## # ####### ######## ## # #
# # # # # # # # # # # # # # # # # # # #
# # ####### ### ##### ### # # # ### ######## ## # ### # ###### # ####### ### ### # ## # ###
# # # # # # # # # # # # # # # # # # # # # #
####### ##### # # ##### ####### ########## # ### # # # # ### ####### ## # # ### # # ##### # # #
# # # # # # # # # # # # # # # # # # # # #
# ####### # ### # ### #### # ### ###### # ### # # ### ### # ### ############# ### # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ### # #### ##### # # # ######### ### ### # ####### # # # # ##### ## ## ## # # ## # ## # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # # ## ### ##### ### # # ### # ## #### ### # ##### ### # # # ### # #### # ## ### # # # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
### ## # # # ## # # # # ####### ### ##### # ### # ##### # # # # # # # # # # # # ### # ###
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ## # # ## # # ## # ## ### # ### # #### #### # # ####### ### # ### # ##### ##### # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ### ### ## # # # # # # ## # ####### ## # # # # #### # # ##### ### ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
####### # #### ##### # # # # ## ### # # # ### ### ## # # # ### # ## ##### # # # # ####### #
# # # # # # # # # # # # # # # # # # # # #
# # # ### ## # ## #### # # # ## # # ### # # # # # ####### ## ## ### # # ### # # # ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ##### ### # # ## ## # ####### # # # # # # #### ## # # ### # ## # # # # ## ## # # # #
# # # # # # # # # # # # # # # # # #
# ### # # # ## ####### # # ### # # # # # # # #### ## ### # ## #### ##### # ## ## ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### ### ### # # ####### # # # ## ### # # ### # # # ### ### ######### ### # # ###### #
# # # # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S # # # # # # # #
##### # ### #### ## ### ###### ### ### # # ## # # # ## ## ### ## # # # ### ##### # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ### ### # ### ### # #### # ### ### # # ### ### # # ##### ### # # ## # # #### ## #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# #### # ### ##### # # ## ## # # # ### ##### # ##### # ### # ##### # ###### #### # # # # ### # #
# # # # # # # # # # # # # # # # # # # # #
# # # ## ## #### ####### # ### # #### # ## # ## #### ## # ### ####### # # ##### #####
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ### # # # ## # # # # ### ## # ### # ######## ##### ### # ## # # ##### # # # # #####
# # # # # # # # # # # # # # # # # # # #
# ##### # ### ############# #### ### ##### # # # ### # ## # # # ## #### ### # # # ##### ## #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # # # ##### # #### # # # # ### # ### # ## ## ## # # #### ### # # # # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # ####### ####### # ### # ## # # # ## ##### # ####### ### # ### ### # # ####### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # ## ## #### # # ### ##### ### ### ### # #### # #### # # ##### ### # ##### # # #
# # # # # # # # # # # # # # # # # # # # # # #
#### ## ## # ### ## # # ## #### # ### # # # # ##### # # # # ### ## ###### ##### # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # ### # ### # # # ### # # # ## ### # ### # # ### # ## # # ## #### #### ## ## # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
### ## # # ### ### ### ### # # # ####### ### ### # # ### # ### # ### # # # # ##### #### ### # ###
# # # # # # # # # # # # # # # # # # # # #
# # # # ## ##### ### ### ##### # # # ## #### ##### ## # ### # ### ######### # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# #### ##### ### ##### # # # # # ## # # # # ### # # ### ##### ### # # ### # # # ## ####### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ## ### #### # # # # ### # ##### # ### # ### # # # # ## # ##### # # # ######### ###### # #
# # # # # # # # # # # # # # # # # # # # #
# # # # ##### ##### ###### ###### # # # ### ### ### ## # ## # # # # ##### ### # ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # ### # ### # # # # # ### ####### # ###### ## # ### ### ## ## ### # # ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ## # # # ### # ### # # # # ## ########## ### ##### ## # # ### # ## # # # ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # ## # ## # ### # # # # ##### ### # ###### # # ## ### # #### ## ### # ### ### # ## #### #
# # # # # # # # # # # # # # # # # # # # # # # #
### # ## # # # # ####### ##### ### ### # # ##### ### ### # # ## #### # ### #### # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # #### # ### # ### # ### # ##### # # # ## #### # # # # ### ### ### ###### ## #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # ## ## # ## ### # ##### # # ##### # #### ##### ## ## ##### # ### ### # ### # ##
# # # # # # # # # # # # # # # # # # # # # #
# # ####### ## # ## ### ### ### # ## ## ##### # # # ### ## ## ##### ###### # ####### # ### # #
# # # # # # # # # # # # # # # # # # # # # #
# # ### # ## #### ## # ## # # ### # ### # # # # #### # # # # # # ##### ### ### ### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # ##### ### ## # # # # # # # # # # ## # ##### ##### ### # # # ### ####### ####### # ## #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # ### ### ##### ####### # # # ### # #### ### # # # ####### ### ## ## ####### ##### #
# # # # # # # # # # # # # # # # # # # # # #
### ######### # ##### # # # #### # ### # ### # # # # # ## ##### # ### ### # # # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### ############# ### ### # # ## # ## ###### # ### # ## # # ### ### ####### # # # # # ## ## #
# # # # # # # # # # # # # # # # # # #
# #### ##### ##### ##### ##### # # # ### # # ## ## # # # ### ### ### ### ### # ##### ##### #
# # # # # # # # # # # # # # # #
### ## #### ###### ##### ## ## # ## # # # # # ####### # # # ####### # ## ### ## ##### #
# # # # # # # # # # # # # # # # # #
# # ### ## ### ## #### ##### # ## # # ##### # # ##### # # ### ####### # ### ## # ### # # # ## #
# # # # # # # # # # # # # # # # # # #
# # ### #### # # ##### # # #### ### ## # ### # ### # # # ######### ### # # ### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ## ## # ### # # # # ## # ### # ##### ### # # ######### ### # ### # ##### # #### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### ##### ## ###### # # # ## ##### # ### ### ### ### # # # ### # # ### # ######### # # # ##
# # # # # # # # # # # # # # # # # # # # # #
# # # # # # # #### ## ### # ##### ### # ##### ### ### # # ### ## # # # ##### # #### # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ##### # # #### # ### ########## ## # # ### # # ####### ##### # ### # ### # ##### # ##### # # #
# # # # # # # # # # # # # # # # # # # #
# ###### ### #### # ######### # ##### ### ### ######## ## ### # # # # ####### # # ##### ###
# # # # # # # # # # # # # # # #
# # ###### ###### ### ### # ### # # ##### ### # ### # # ##### # #### # ###### ## # ### ### ### # #
# # # # # # # # # # # # # # # #
# # # ### # ##### ## ######## ### ##### ### ### ###### ########### # ####### ## ## # # ### #
# # # # # # # # # # # # # # # # # # #
# # #### ##### ## ######## # ########### ### ### ## # # ### # ####### # # ### ### # ### # ### #
# # # # # # # # # # # # # # # # # # # #
# ##### # ##### # # ############# # # # # ### ##### # # ######## # ##### # # ### ### ## ###
# # # # # # # # # # # # # # # # # # # # # # # # # # #
### # ## ## # # # # # ## # ##### # ## ###### ## ### # ### # # ### ##### # ##### ## ## ## #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ######### ### # # ######### # # # # # # ### ### # ## ## ## ## # # ### # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # ##### # # ### # # # ## ## ### ## ## # ### # # ##### # # # # # #### ## # ###
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### ##### # # ### # # # # #### # # # ### # # ####### # # ##### # ## ### ### ###### ##### #
# # # # # # # # # # # # # # # # # #
# # ### # # ### # #### ### # ### # # #### # ######### ### ### ##### # # ### ##### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ## ### ### ### # # # #### ### ## # # ##### # ### # ## # ## # ## #### ####### # ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # #### ##### # ####### # ## # # # # ### # ##### # ### ##### # ### # ####### # #
# # # # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S# # # # # # # # # # # #
# ### ### ## ##### ### # ### ### # # ### ## # # ##### ### # ### # # # ######### # ## ## #
# # # # # # # # # # # # # # # # # # # # # #
# ### # # ## ### # # # ### # # ### # # # # # # # # #### # # # # # # ##### # # ########## #
# # # # # # # # # # # # # # # # # # # # # #
##### ###### ### ## ## # ### # # # ### # ############# # # # # # # ##### ####### # ### ### # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ### # ### # # # # ### # ### # # ### # # # ######## ### # ## # # ### ##### # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # ##### # # # ### # # ### ### # # # # ## # # #### #### ## # # # ##### # ### ## ### #
# # # # # # # # # # # # # # # # # # # # # # #
##### # # ### # # # # ## # # ### #### # # # ### # ### ### ### ### # # # ##### # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ##### # # ### ## # ### ### ### # # # # # #### ### ### # ## ### # # ############ # ##
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # ##### ### # # # # # # ### # # ### # # # ### ##### ##### # # ### ### ## # # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### # ### ## ## # ## #### ## # # # ## # # ### ### ### # ######### # # ### # ### # # ### # #
# # # # # # # # # # # # # # # # # # # # # #
# # # ### ###### ##### # # ########### # # ####### # # ### ### # # ### # ### # # # # ##### # #
# # # # # # # # # # # # # # # # # # # # # #
# ##### # ####### # # # ####### # #### ## # ## # ## # ### # # # ## ## # ### ## # #### #
# # # # # # # # # # # # # # # # # # # # # # #
##### # # ################# ### ### # ### # # # # # # ### # # # ## # ## # ### ### # ##### # ###
# # # # # # # # # # # # # # # # # # # # # #
# # #### ### # # # ##### # ##### ####### # # # # # # # ####### # ### ## # # ### ### # # #### # #
# # # # # # # # # # # # # # # # # # # # #
### # # ### # ### ### # ## ## ##### ### # ## #### ### # #### ## ## ### # # ####### # # ##### #
# # # # # # # # # # # # # # # # # # # # # #
# # # # # ### # ### # ## ##### # ##### # # ######### ### ### # # #### # #### ######## # ###
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### # # ## # # # ### # ##### # # # # # # # ## # # #### # # ### # ### # # ### # ## #
# # # # # # # # # # # # # # # # # # # # # # # #
### # # ## ### # ### # # # ### # # # # ####### ########### # # # ### # ###### ## ### # # ## #
# # # # # # # # # # # # # # # # # # # # # #
# # ##### ### # # # # # # # # ####### # # ######## ###### #### ### ## ## # # # ### ## # #
# # # # # # # # # # # # # # # # # # #
# ### # #### # # # # # ####### # # # ### ###### #### ## # ## #### # # ######### ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ###### #### # # # # ### # ### # # ### ### ### # ### # # ### # # # #### # ### ## # # ###
# # # # # # # # # # # # # # # # # # # # # # #
####### ### ##### # ## # ## ## # ### # ### # ## # ### ####### # # # ### # # # ###### ## ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # ## ##### ####### ####### ### # ### # ##### ##### ### ### # # # # # # ### ## # #
# # # # # # # # # # # # # # # # #
# # ### ### ####### ######### ######## ##### # # # ############# ####### # ### ### # ###
# # # # # # # # # # # # # # # # # # #
# ### # ### ##### ### ##### # # # ### # ## # # # ### # ### # # # # ### # ##### ## ### ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
## # # # # # # ### # ##### # # ### ##### #### ############ ### ### # ###### ### # ### ### # #
# # # # # # # # # # # # # # # # # # # # # #
##### # ## # # # #### ### ## # ##### ### # # #### ### ### # ##### # # ### ## ### # # # ## #
# # # # # # # # # # # # # # # # # # # # # #
# ####### ##### ### # # ## # # # ##### # ####### ##### ### # ### ##### # # # ## # ## ###### ##
# # # # # # # # # # # # # # # # # # # # #
## # ##### ####### # ### # # # # # ##### ### # ### #### ### # ### ## # # ### # # ### ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # #### # # # ### ### ##### ##### # ### # # # ### # ##### # # ### # ### # ### # # # ### ####
# # # # # # # # # # # # # # # # # # # # # # #
# ##### # # # # # ### # # ### # # ### # ############# # # ##### ## # ## ### # ###### # # ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # ### ### ### # # #### ## # # # ##### # # # ## # # ## # # # # ### ## ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ##### ### ## # # ### # ##### # ####### # ## ## # # ### # # # # ## # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ### # # # ####### # ######### ######## #### # # ## # ## ## # # # # # # # #### ##### # # # ###
# # # # # # # # # # # # # # # # # # # #
## # # #### # # # ## # # ### ##### # # ### # # ### # ### # ## # ########### # # # ## ###### #
# # # # # # # # # # # # # # # # # # # # # #
# # ###### ####### # ## ########## # # ####### ## ## ### # # # ## # # # ####### # ## # # #####
# # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # # # ## # ### # ## ## ## # # # # # #### ### # ### # # ## ## # ### #### ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ## ## # ####### ##### # ##### # ### # # # # # # # # # ##### # ## # ### ##### # ##### # #
# # # # # # # # # # # # # # # # # # # # # # #
# ## ### # # ##### ### ## ## ### # # # # ######### ##### #### # ### ### ## ### # ### ##### #
# # # # # # # # # # # # # # # # # # # # # # #
### # # #### # # ## # # ### ####### # # ## ## #### ### # # # # # # ### # ########## # #
# # # # # # # # # # # # # # # # # # # # # # #
# ##### ##### # ### # # # ### # ####### # # # ##### # # # # # # ##### # ## ## ### # ### ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
##### # # # ##### # # ### # ##### ### # ### ### # ### # ####### # # ####### # ### # ## ### ## #
# # # # # # # # # # # # # # # # # # #
# #### # # # ## #### # ### ## # ## # ### # ### #### # ### # ####### ######### # ##### # ### #
# # # # # # # # # # # # # # # # # # # # # #
# # # ####### ###### # # ## ## # ## ## #### # # ## ## #### ####### # ####### # # #######
# # # # # # # # # # # # # # # # # # # # #
# ### # # # # ###### ## # #### ### # # #### #### #### ### ### # # ## ## # ### #####
# # # # # # # # # # # # # #
### # ## ## ##### ######### #### # # # # # ## # ######### ######### #### #### ### ## #
# # # # # # # # # # # # # # # # # # # # # # #
# # # ### ## ##### # ##### ### # # # # # ##### # ### ## # # ### ### # # # ####### # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ### # ############# # # # # ##### # # # # ### # ###### # # # ## # ####### # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### ##### # # # ###### ## ### # # ##### # # ### # ### ### # # # # ### ## ## ### # # # # # ###
# # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S # # # # # # # # # # # # # #
# # # # ### # # #### ## # ##### # # # # # # # # ### ### # # ### # ##### # ### # # ## # ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
######### # ### # ##### # ## # # ##### # ######## ### ##### # # # # # ### # # ### ## # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# ##### ####### # # ### # ### ### # ##### ### ## # ## ### ### # # # # ## #### #### ## # ##
# # # # # # # # # # # # # # # # # # # # #
##### ##### ### ###### # # # ### # # # # ### # ### # ##### ####### # ### ##### # # ##### # #######
# # # # # # # # # # # # # # #
# ####### # # # ### # # # # ### # # ### #### # # ######### ## ## ### ### ## ## ### # # #
# # # # # # # # # # # # # # # # # # # # #
# ##### ### ### # ########### # # ### # ####### ##### # #### ###### # ### # # # ### # ### ## # #
# # # # # # # # # # # # # # # # # # # #
### # #### #### # ####### # ####### # ## # # # # # # ##### ## ## # ##### ### ##### # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ## ### # # # # # ## ### # ### ### # # ### ## ## ##### # # # ### ## ######## ### ######### #
# # # # # # # # # # # # # # # # # #
# # ##### ##### # ### # # # # # # # ## # ### ## ## # # # ## ####### # # # # # ### ###### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# #### #### # # ## ### # ##### # # ### ### # # ###### ##### ### # ###### # ## ## ### # ## #
# # # # # # # # # # # # # # # # # # #
## #### # ### # # ##### # # # # # ### ############ # ####### ###### ## ####### # # ## ## # #
# # # # # # # # # # # # # # # # # # # # # #
# # # ##### # ### # ### ####### # # # ## ##### # ### # ######### #### # # # # # # ##### # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ### # # # # # ### # # # # # ### # # # # ##### ### # # # # # ####### ## ##
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # ## ##### ## # # ### # ## ## ## # #### # ### ##### # ####### # # # ### # # # ## #
# # # # # # # # # # # # # # # # # # # # # # # #
# ###### # ### ## # ##### # # # # #### # # # # ### ######### ##### ##### ###### ## # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ###### # ### # # ##### # ### ##### # ### # # # # ## # ## ### # # ## # # # # #
# # # # # # # # # # # # # # # # # # # #
#### ### # # # # ######### ### ### # # # # ##### # ##### #### # # # ### ### ####### # # ## #
# # # # # # # # # # # # # # # # # # # # # # #
# ## ## ##### #### # # #### ## # ###### #### ## # # ####### ## # ## ### ### ## ##### # #
# # # # # # # # # # # # # # #
## # ############ ##### ### # # ### # ###### ## # # ## # # # ### # # ### ## ##
# # # # # # # # # # # # # # # # # # # # # # # # #
# ####### ## ## ##### ## ####### ##### ### # # ### # # #### # ### # ### # #### #### # ##### # #
# # # # # # # # # # # # # # # # # # # # # #
# ### # # # # # # # # ### ### ## ###### # ##### # ##### # ### # ### # # ######## ##### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # ## # # # # # # # ### # # # #### # ### ### ### # ## ### # # ### ### ### ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # ##### # ## ### ## # # # # # ### # # # ######## ###### # ##### ### # # # ## # # # ### # #
# # # # # # # # # # # # # # # # # # # #
# # ### ## # # # ######### # # ## #### ############## # # ## # # ## # # ############# ### #
# # # # # # # # # # # # # # # # # # #
# # # ###### ## ######### # # # # # # ## # ########### ## # ## ###### ##### # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
######### # ### # # # #### ## ### # ### ### # ##### # # # ### # # # # ### # # # ### ## ## ## #
# # # # # # # # # # # # # # # #
# ##### ## # # ##### ## # ##### ##### # ### #### ###### ####### # # ### ########## #### # #
# # # # # # # # # # # # # # # # # # # #
# # # # # ### ### # ## # # ### # # ### ##### # ####### ### ## # # ### ### # # ### ## ####
# # # # # # # # # # # # # # # # # # # # # #
# ### # # # ### ##### ## ##### ### ## ####### ## ### # # ### # # ######### # ######### ### #
# # # # # # # # # # # # # # # # # # # # #
# ### ### ## ###### # #### # # ####### # ### #### # ####### # # ##### # # ##### # ### ####### #
# # # # # # # # # # # # # # # # #
### ### # # # #### ############ ### ### ### # ### # # ### ### ### ##### # ####### # ## #### # #
# # # # # # # # # # # # # # # # # # # #
# ### ##### # ### # ## # ########### #### ## # ## # ## ###### ## # # # # ### # ###### #
# # # # # # # # # # # # # # # # # # # # # # # # #
### # ### # # # # # # ## # #### ##### # # ### ###### # ##### # # # # ## #### # # # ###
# # # # # # # # # # # # # # # # # # # # # # #
# # # ## ## #### ### ## # # ### ##### ##### ### ## ###### #### ### ## # ### # ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # ## ## # ### # # ### # # ### # ### # # # ## # #### #### # ### # # ### #### ## ######
# # # # # # # # # # # # # # # # # # # # # # # #
# ## ###### ## # # # ### ### ####### ##### # ### # # ### # ### # # # # ## ## # # # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # # #### # # ### ### ### # # # ### # # ### # # # # ##### ####### # # ##### ### # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ## ##### # ## ##### ## ## ### ### ### ### # # ##### #### ## # # ## ### ##### #
# # # # # # # # # # # # # # # # # # # # # #
# # # ### # ##### # ## #### # ####### # # ### ### # # ## ## # # ########### # ### # # # #####
# # # # # # # # # # # # # # # # # # # # # #
# ##### ## ## # ## # #### ####### ## ### ## #### ###### # # ## #### # # ####### # ##### #
# # # # # # # # # # # # # # # # # # #
### # ########## #### # ##### # # ##### # ### ##### # #### ## # # # # ##### ### ## ### # # # ####
# # # # # # # # # # # # # # # # # # # # # #
# # ### # # ##### # # # ####### ## ## ## ## ### # ##### # # ### ##### ### ##### ### ### #
# # # # # # # # # # # # # # # # # # # #
# # #### ## # ##### # ## ##### # ### # #### #### ##### # ## ####### # # ######### ## # #
# # # # # # # # # # # # # # # # # # # # #
# ### # # # ##### # ####### # # # # ### ### # # ### ## # ### # ## # ### # ##### # ##### # # #
# # # # # # # # # # # # # # # # # # # # # # #
# #### # ## # # ## ## # ### ## ## ## # ##### ### ####### ### ####### # # ## # ### # # ###
# # # # # # # # # # # # # # # # # # # # # #
# ### # #### #### # # # ## # #### #### ## ######## #### # # # ### # # # # # # # # ## ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ## ## # ## ### ## # ### # ### # ## # # # ### # ### # # ## ####### # # ### ###### # ## #
# # # # # # # # # # # # # # # # # # # # #
## # ### ## # ## ### # # # ##### ### # ##### ##### # ## # ######## # # ## ### ### ### ## # # #
# # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S# # # # # # # # #
# ### # ##### ### # # # ## ### ######### ############# # # ########### # # # # ##### # #### # ###
# # # # # # # # # # # # # # # # # # # #
### # ### # ### # # ##### ####### ####### # ### # ## # ### ## ## # ### ##### ##### ## ##### ## #
# # # # # # # # # # # # # # # # # # #
# ## ## ### # ### #### ## # # ## ##### ### ## # # ### # # ### ##### # # ##### # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # # ### # # # ## ## #### #### ## ## # # ## # # # ### ### ## ##### ####### ### ###
# # # # # # # # # # # # # # #
# # ##### # ## ## #### # #### ## # # ### ### ####### ### ## ###### # ##### # # ##### ### ## #
# # # # # # # # # # # # # # # # # # #
## # ### # # # ### ##### # # ##### ### # ### ### # # # # # ### ### # ### ###### # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # ## # # # ####### ### ### ## # ### # # # ### # # # ### ### # ### # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# ### ###### # # # ####### ##### #### # ###### # # ### # # ### # # # # ##### # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # ####### # # # ### # # # ### ### # # # # ##### # ##### # # # # ## # ### # # # ## #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### # #### ## # # ### ## ## # # ## # ### ###### ## # ### # ### # # # ##### ####### # ### # ###
# # # # # # # # # # # # # # # # # # # # #
# #### #### ####### # ##### ## # ### ## ##### # ### ##### # ### # # ## # # # #### ### #
# # # # # # # # # # # # # # # # # # # # #
#### # ## # # # # # # ### ### ### # # # ### ####### # ##### # ##### ##### ####### # # # ### # # #
# # # # # # # # # # # # # # # # # # # # #
# # # # # ### ## # ### ### ## ### # # # #### ## # ### #### # ##### # ### # ##### ## ## ##### #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # ### ### ### # ## ## ## # # ### ##### # ### # # # ## ## # # # ### ####### # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ####### # ### # # ### ## # ### # # # ### # # ### # ##### # ##### ###### ### # ### ##### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### # # # ## ### ### # ## # # ### # # ## ##### # ##### # # # ### # ### ### # # ##### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ### # ### # # # # ### # # # ##### ### # ## ## ####### # # ## ## ##### ### ## #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # ### # ### # ## #### # # # # # # # ### ## # # # ### ### ### ### # # ###
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # ######## ### ### ### # # #### ### ## # # # ##### # # # # #### ### # # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
### # # # ## ### # # # # # #### # ### # ## ##### # # # # # ####### # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # ## # ### # # # ### #### ### ## # # # #### ##### # # # ##### # #### ## ### # # #### # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # ### ##### # # # ## # # ##### ### # ### # ## #### # ### ## # # ### # ### ### # # ##### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ## # # ##### # ###### # ########### # ##### ### # ### # # ########## ### ##### # # # # # # #
# # # # # # # # # # # # # # # #
# ####### ######### # # ##### # # # ## # # # ### # # # # ### ### ### ####### # ## ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # # ### # ## ## # # # # # ### # ##### # # ### # # # # # # # # # ### # # ## # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
##### # # ##### # #### #### ### # # # ############# # # ### # ### ## #### # # #### # # ####
# # # # # # # # # # # # # # # # # # #
# # # ##### #### ### # #### # # # ### # #### ## # # # # ### # # ### ###### # ## # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# ##### # # # ### # # ############# # # ##### # # # # ### ### # ## # ### # #### ## # ### #
# # # # # # # # # # # # # # # # # # # # # # # #
## # # ### ## # ## # ## # ##### # # # ###### # ## ### # # # ### ### # ###### ### # ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # ## # # ##### ##### # # # ##### # # # ### ### # # ### # # # # # ### ### ###### #
# # # # # # # # # # # # # # # # # # # # # # #
# # ### # ##### # ## # # ### ### ####### # # ### ### # # # ##### # # ############ # # # # ###
# # # # # # # # # # # # # # # # # # # # # #
# ## ### # ### ### # # ### ### ### ##### # ##### ##### # ## ## # # # ### ### ###### ###### #
# # # # # # # # # # # # # # # # # # # # # #
# # ######## ## ##### ##### ### ##### ##### # # # # # # # # # # ### ### # # # # # #### # # #
# # # # # # # # # # # # # # # # # # # #
# ### ### ### # # # # ### ##### # # # # # ### ### # # # # ##### ##### # ## # ## # # ### #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
### # # # # # # ### # ##### # ### ### ### ## # # ### # # ### ###### ##### # # ### ## ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ## # # # # ## #### ### ###### # # # ###### ## # ## ## # ##### # # # ### ####### ### #
# # # # # # # # # # # # # # # # # # # # #
# ##### ### ### ### # # ### ####### # ## ## ### # # # # ########### # ### ##### ### # ## ##### #
# # # # # # # # # # # # # # # # # # # #
# ### ### ## ####### # ##### # ### # ## ## ######## # ### # #### # # ####### ### ####### #
# # # # # # # # # # # # # # # # # # #
# ##### ##### # # ### # # ## ###### ## # ##### # #### # ### # # # # ##### # # ### # # ### #
# # # # # # # # # # # # # # # # # # # # # #
# # # ## # # # # ### # ## # # # ##### # ##### # ########### # ##### # ####### # ### # ###
# # # # # # # # # # # # # # # # # # # # #
# # # #### ### ##### # ##### # ##### # # ### # ### # # # ####### # # # # ##### # #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### # ### ####### ### # # ### # # ## # #### # ##### # # ### # ### ## ## # #### ## ## #####
# # # # # # # # # # # # # # # # #
# # ###### # ### # ##### # ## ######## # # # # # ######## ##### ####### # #### # ####### #
# # # # # # # # # # # # # # # # # #
##### # ### # # ### # ### ### ### ### ##### ## # # # # # ## ### # ### # ### # ## # ## # ###
# # # # # # # # # # # # # # # # # # # # # # #
# # ### # # # # ### # ### # # ### ### ### # # ####### #### # ### # # # ##### # ### # ### #### #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # ### ### # # # ### # # ### # ######### ### ## ### ## ### # ### # ## ##### ### #
# # # # # # # # # # # # # # # # # # # # # # #
# ### # # # ####### # ### ## # # ### ##### ## ### ##### # # # ## #### # ### # # ### ##### ###
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### # ### ## # # ### # ##### # ##### # ### ## ##### ### # # ######### # # ### # # # # # #
# # # # # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,99 @@
###################################################################################################
#S # # # # # # # #
### # # ### # ### #### ## ##### # ### # ### ### #### # ### ### # ####### # # # ### # ### ### #
# # # # # # # # # # # # # # # # # # # # # #
# # # # # ############### # ### ###### ### # ### ######## ### # # ##### # # # # # ### # # # # # #
# # # # # # # # # # # # # # # # # # # # #
# # ## # # ##### ## ## ### ### # ### #### #### # ### ### ### # ##### # ### ### ### # #
# # # # # # # # # # # # # # # # # # # # #
# ### ### ### # # ##### ###### ######## ## #### # # # # # # ## # # # ### ### ### # # ## #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # ####### ## # ############### ### # ##### # #### # # # # ### ## # # ### # ### # #
# # # # # # # # # # # # # # # # # # # # #
# ##### # ## ## ##### ### # ## ## #### # # # ##### ####### ### # ## ## # #### # ##### # #
# # # # # # # # # # # # # # # # # # # #
### ### ### # ## #### # ### # # # ###### ### # ####### # # ## # # # # ### # ## ## # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### # # # # ####### # ### ##### ####### # # # # ####### # # #### ###### # # ### # ## ### # #
# # # # # # # # # # # # # # # # # # #
# #### # # ## ######## ### ##### # # ##### ###### # ##### #### ## ## ## # ### # #### #
# # # # # # # # # # # # # # # # # # # # # # # #
# ###### ##### ### # # # # # # ### # ## ## ### # #### # ### # # # # ### # # # ### ###### # ###
# # # # # # # # # # # # # # # # # # # # # # #
# ### # # ##### # #### #### ####### #### # ### ### # ## ###### # ## # # # # # # # # ### # ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # ## # # ######## ## ## ### ### ### ####### # ### # # ### # ### #### # ## # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# ### # ### # # # # # # ## ### # # ## # # ### ### ### # # # # # ##### # # # # ### ### # #### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # ### # # # ## # ### ### ##### # ### # # ####### # ####### ### # ###### # # ##### # # # # #
# # # # # # # # # # # # # # # # # # # # # #
# # # ### # ## ## # ##### # # # ## ####### # ## ### ######### # # ##### # #### ## ## # ### # # #
# # # # # # # # # # # # # # # # # # # # # #
# ## # # #### #### #### ### # ####### # # # ## ##### # # ##### # ######### ### # ### # # # #
# # # # # # # # # # # # # # # # # # # # #
## # # # # # ## ### # # # # # ### ## ### # ### # # # # ####### # ## # ### ## # ### ### # ###
# # # # # # # # # # # # # # # # # # # #
# ##### # # # # ##### # # ##### ### # ##### # ##### # ## #### # ##### # # ####### # ### ### #
# # # # # # # # # # # # # # # # # # # # #
# ## # # # ### # # ### # # ## ## # # ### # #### # # # # ####### ### #### # ### ## ######
# # # # # # # # # # # # # # # # # # # #
# # # #### ### #### ### # # ### ####### ###### # # # ##### # ################# # # ##### #
# # # # # # # # # # # # # # # # # # # # #
#### # # ### # # # # ### # # ### # # ####### ### ### # # ## ## # # # # #### # ## ## ### # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ## # # # # # # ## ## # # # ### # ### # # ### ### ### ## # ### ######### # ## ## # ## ##
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ### ### # # ### ### # ### ## # ## # ## # ## ### # # # # # ### # # # # # ### ### # ### # # #
# # # # # # # # # # # # # # # # # # # # # # # #
# # ### ## ### # # ### # # ##### # # ## ###### #### # ##### # # # # # # ### # ## # #### # #
# # # # # # # # # # # # # # # # # # # # # # #
# ####### ## ## ### # # # ### # # # ## # # ## #### # # ####### # # # # ##### ## # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
####### ### # ### # ### ### # ########### # ### ### # # ###### ## # # # ####### ####### # # # #
# # # # # # # # # # # # # # # # # # # # #
# ### ## # # #### #### # # #### # # ##### # # # # # # # # # # # ##### # # #### # # # # ##
# # # # # # # # # # # # # # # # # # # #
# # # ## # # ####### ### ##### # # ####### # ### # # # # # # # ## ## #### #### ####### ### #
# # # # # # # # # # # # # # # # # # # # # # #
# # # ##### # ## ## ### ### ### # ##### # # ##### # # ###### #### # ## ## # ### # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # ### # ### ### # # # # ### #### ########## # # ##### ### # # ## # # # ### # # ### ##### # #
# # # # # # # # # # # # # # # # # # # # # # #
### # ### # # # # # ####### ## # ### ### ##### # # ##### ## # # ##### # # ##### ####### # #
# # # # # # # # # # # # # # # # # # # # # # #
# # # # # # ###### # ### #### ## # ### ### # # ## # ### #### ### # # # ### #### # ## ### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # ## # #### ###### # ## # ## ### # ### # # ### ## # # ##### ### ######### # # ## # # ###
# # # # # # # # # # # # # # # # # #
# # # ######## ### # ### ### ### ##### # # # # # ### # ########## # ######### # # ## #
# # # # # # # # # # # # # # # # # # # # # #
# #### ## # # ## ## ##### # # # # # ### # ##### # # ### # ### # # # # ### ### ### ## ## # ##### #
# # # # # # # # # # # # # # # # # # # # # # # # #
# # # # ##### # # # ## # # # # # # #### ############## ## # # # # ### # # ##### ##### # #
# # # # # # # # # # # # # # # # # # # # # # #
# # ## # ### ####### # ### # # #### # # ### ######### # ##### ####### # ### # ### ## # ### ###
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # ## #### ## ## ### # # # ##### # # # ## # #### #### # ## ## ## # #### ### #
# # # # # # # # # # # # # # # # # # # # # # # #
# # # # ### ### ##### # # ##### # ### # ##### # # ### # ### # # ### # # # #### # ## #### # ### #
# # # # # # # # # # # # # # # # # # # # #
# ### # ##### # # # # ### # ### ### ### # # # # # # # # ### ###### ## # # # ######### # # ##
# # # # # # # # # # # # # #
# # # # #### ###### #### # #### ###### # ## #### # ## # ### ## # ###### ##### ############# #
# # # # # # # # # # # # # # # # # #
# # # ###### # ####### # # ### # # # # # #### #### ##### # ### ##### # # ### # ## #
# # # # # # # # # # # # # # # # # # # # # # # # #
# ### ## # # # # ##### # # # ### ##### # # # #### #### ####### # ### ### ### ### # #### # # #
# # # # # # # # # # # # # # # # # # #
# # ### ##### ### # #### ### # # # # ### ####### ##### ##### # #### ### ### ########## #####
# # # # # # # # # # # # # # # # # # # # # # #
# #### ##### ### ### # ###### # ### # # ### # # ### ## # ### ### ###### ##### ### # # ### # #
# # # # # # # # # # # # # # # # # # # # # # # # # #
# # ### ##### # # # # # # # ### ##### # ### ## # ### # ######## # ## ## # # ##### # #
# # # # # # # # # # # # # # # # # # #
# ### ## # ## # ####### ##### # # #### # # ##### ### # ###### # # ######## # # # # # ### ### #
# # # # # # # # # # # # # # # # #
####### # # ### ########## # # # ## ## # # ### ########### ##### ### ### # # ### # # # # #
# # # # # # # # E#
###################################################################################################

View File

@ -0,0 +1,9 @@
#########
#S# #
# # # ###
# # # #
# ##### #
# # #
##### # #
# E#
#########

View File

@ -0,0 +1,9 @@
#########
#S # #
### ### #
# # # #
# ### # #
# # #
# ##### #
# E#
#########

View File

@ -0,0 +1,9 @@
#########
#S# # #
# # # # #
# # # #
##### # #
# # # #
# ### # #
# E#
#########

View File

@ -0,0 +1,9 @@
#########
#S #
####### #
# # #
# # ### #
# # # #
# # # ###
# # E#
#########

View File

@ -0,0 +1,9 @@
#########
#S# #
# # ### #
# # # #
# ### # #
# # # #
# # ### #
# # E#
#########

View File

@ -0,0 +1,9 @@
#########
#S # #
##### # #
# # # #
# # # # #
# # # #
# ##### #
# E#
#########

View File

@ -0,0 +1,9 @@
#########
#S # #
##### # #
# # # #
# # # # #
# # # #
# ##### #
# E#
#########

View File

@ -0,0 +1,9 @@
#########
#S# #
# ### ###
# # #
### ### #
# # # #
# # # # #
# #E#
#########

View File

@ -0,0 +1,9 @@
#########
#S # #
### # # #
# # # # #
# # # # #
# # # #
# ### # #
# #E#
#########

View File

@ -0,0 +1,9 @@
#########
#S# #
# ##### #
# # #
### # # #
# # # #
# ##### #
# E#
#########

View File

@ -0,0 +1,19 @@
###################
#S # # #
##### # ##### ### #
# # # # # #
# ### # # ##### # #
# # # # # # #
# # ######### # # #
# # # # #
# ######### # #####
# # # # #
# ### # # # ##### #
# # # # # # #
# # ### ####### # #
# # # # # # #
# # # ### ### ### #
# # # # #
# ### # ### ##### #
# # # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # #
##### # # # ##### #
# # # # # # # #
# # # # # ### # # #
# # # # # # #
# ##### ### ### # #
# # # # #
# ### ####### ### #
# # # # # #
### ##### # ### # #
# # # # # #
# ######### # #####
# # # # #
# # ##### # ##### #
# # # # # #
# ### # # # # ### #
# # # # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # #
######### # # # # #
# # # # # #
### ##### ##### ###
# # # # # #
# # # # # # # ### #
# # # # # # # #
# ### ##### ### # #
# # # # # #
# # ####### # ### #
# # # # # #
# ### # ##### ### #
# # # # # #
# ####### # ### # #
# # # # # # #
# # # ### # ### # #
# # # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # # #
### # # # ### # # #
# # # # # # # #
# # ##### # # # # #
# # # # # # # #
# # # # ### #######
# # # # # #
# # # ### ####### #
# # # #
# ### # ######### #
# # # # #
# # ##### ### #####
# # # # # #
# ### # ##### ### #
# # # # # #
####### # # ### # #
# # # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # #
### ####### # # ###
# # # # #
# ### ### ####### #
# # # # #
####### ####### # #
# # # #
### ### # ####### #
# # # # #
# ######### ### # #
# # # # #
### # # # ####### #
# # # # # # #
# ### # ### # # # #
# # # # # # #
# # # # ##### #####
# # # #
###################

View File

@ -0,0 +1,19 @@
###################
#S# # #
# # ### # ####### #
# # # # # # # #
# # # # # ### ### #
# # # # # #
##### # # # # #####
# # # # # # #
# # # # ### ##### #
# # # # # #
# ####### ### ### #
# # # # #
# # ##### # # # ###
# # # # # # # #
# ### ####### # # #
# # # # # #
# # ### # ##### # #
# # # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # #
### # # ### ### ###
# # # # #
# ### ########### #
# # # # # #
### # # ### ### # #
# # # # # # # #
# # # # # ### ### #
# # # # # #
# ####### # ##### #
# # # # # #
# ### # # # # #####
# # # # #
########### ##### #
# # # #
# # ########### # #
# # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # #
##### # ### ### # #
# # # # # # #
# # # ##### ### # #
# # # # # #
# ####### # # ### #
# # # # # #
# # ########### # #
# # # # #
# ####### ### # ###
# # # # #
########### # ### #
# # # # #
# # # ### # ##### #
# # # # # #
# # ####### ##### #
# # #
###################

View File

@ -0,0 +1,19 @@
###################
#S # #
### # ### ####### #
# # # # # # #
# ### ##### # # # #
# # # # #
# ### ####### #####
# # # #
### ####### ##### #
# # # # # #
# # # # ##### ### #
# # # # # #
# # # ######### # #
# # # # # #
# # ########### ###
# # # #
# ########### ### #
# #
###################

View File

@ -0,0 +1,19 @@
###################
#S # # #
### # ##### # ### #
# # # # # # #
# # # # ####### # #
# # # # #
# ####### ####### #
# # # # #
####### ##### # # #
# # # # #
# ### ### # ##### #
# # # # # #
### ### # ### # ###
# # # # # # # #
# ### # # # ### # #
# # # # # #
# ### ####### ### #
# # #
###################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,30 @@
##############################
#S #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# #
# E#
##############################

View File

@ -0,0 +1,49 @@
#################################################
#S # # # # # # # #
##### # ### # ### # # # # ### ##### # # ####### #
# # # # # # # # # # #
### ##### ### # ################# ############# #
# # # # # # # # #
# ##### # # # ########### ##### # ##### # # #####
# # # # # # # # # # # #
# ### ### # ### ########### ######### ### ### # #
# # # # # # # # # # # #
# # ####### ### # ##### ##### # # ##### # # ### #
# # # # # # # # # # # #
##### # ##### ### ### ######### # ### ### ##### #
# # # # # # # # # # #
# ####### # # ##### ##### ### ### # ### #########
# # # # # # # # # # # # #
# ####### # # # # # # # ############# # # ##### #
# # # # # # # # # # # # #
### # # ####### # # ### ### # # ####### ### # # #
# # # # # # # # # # # # # # # #
# ########### ### ### # # ##### ### # # ##### ###
# # # # # # # # # # # #
# # ##### # ####### # # ######### ######### ### #
# # # # # # # # # # # #
# ### # ##### ##### # # # # # # # # ######### # #
# # # # # # # # # # # # # # #
# # # ########### # ##### # ### # # # ######### #
# # # # # # # # # # # # #
# ##### # ### ### # # # # ########### # ### #####
# # # # # # # # # # # # # #
# ### # # # ### # ######### # # ##### ### ### # #
# # # # # # # # # # # # # #
### # # ### # # ### # ### # ##### # ### ####### #
# # # # # # # # # # # # # # # #
# ####### ### # # # # # ####### # ### ####### # #
# # # # # # # # # # # # # #
##### # # ##### ### ### ### # ### # # # # # ### #
# # # # # # # # # # # # # # #
### # ### # # ### ### ####### # ##### # ##### # #
# # # # # # # # # # # # # #
# # ### # # ### # ####### ##### ### # # ### # # #
# # # # # # # # # # # # # # # # #
# ##### ### # ##### # # ##### ### ### ### ### # #
# # # # # # # # # # # # # # #
### # ### ### # # # # ### # ### ### ### ### ### #
# # # # # # # # # # # # # # # # #
# ######### # # # # # # ### # ### ### # # # #####
# # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S# # # # #
# # ##### # # ### ########### ####### ### #######
# # # # # # # # # # # #
##### # # ### # ### ### # ##### ####### # # ### #
# # # # # # # # # # # # #
# ### # ### # ####### # # ####### ######### # ###
# # # # # # # # # # # #
# # ### # ### # # ####### # # # ############### #
# # # # # # # # # # # # # #
# ### ### ##### # # # ########### ####### # # # #
# # # # # # # # # # # #
### ### # # ##### ######### ### # # ### ### ### #
# # # # # # # # # # # # # # #
# ### # # # # ##### ##### # # ### # # ### ##### #
# # # # # # # # # # # # # # # # #
# ### # ##### ##### ### ##### # # ### # ### # ###
# # # # # # # # # # # #
# # ##### ### # ##### ####### # ### ######### # #
# # # # # # # # # # # # #
# ### # ### # ##### # # # ##### # ### ##### ### #
# # # # # # # # # # # # # #
####### # # ######### ####### # ### ### ##### # #
# # # # # # # # # # # #
### ### # ### # ### # # ### ############# # ### #
# # # # # # # # # # # # # #
# ### ### # # # # # ##### ##### # ##### # # ### #
# # # # # # # # # # # # # #
# # # # # ### ##### # # ### ######### # # ##### #
# # # # # # # # # # # # # #
# # ### ### ### ##### ### ### # ##### # ### ### #
# # # # # # # # # # # # # # # # #
# ### # # ### # # # # # ####### # # ##### ### # #
# # # # # # # # # # # # # # #
# # ##### ### # # ##### ##### # # ##### # # ### #
# # # # # # # # # # # # # # # #
# ##### ### ##### # # ### # ### ##### ##### # # #
# # # # # # # # # # # #
####### # ######### # # ####### # ### # ### #####
# # # # # # # # # # # #
### ##### ####### ####### ##### # # ### # ##### #
# # # # # # # # # # # #
# ### ##### ### # # # ##### # # # # ########### #
# # # # # # # # # # #
# # ### # ##### ####### ############# # ####### #
# # # # # # # # # # # #
# ### # ##### ### ### ##### # # # ####### # # ###
# # # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S # # # # # # # # #
### ### ##### ### # # # # # # # ### # ### ### # #
# # # # # # # # # # # # # # # # # # #
# # # ### # ### # # # # # # ##### ##### ### # # #
# # # # # # # # # # # # # # #
# ##### ### ####### # # ####### ### ##### # # # #
# # # # # # # # # # #
# # ####### # ####### ### ### ### ### ######### #
# # # # # # # # # # # # # #
# # # ### # # ##### # # # ##### ### ### # ### ###
# # # # # # # # # # # # #
####### ######### # ####### # ### # ########### #
# # # # # # # # # #
# ### # # ##### # ########### # # ####### # ### #
# # # # # # # # # # # # # #
### ####### # # ### # ### # ### ##### ####### # #
# # # # # # # # # # # #
# ### # ##### ### # ### ### ####### ### ### # # #
# # # # # # # # # # # #
# # ########### # # # ### ### ### ############# #
# # # # # # # # # # # # # #
# ### # ######### # # # ### ### ### # # # ### # #
# # # # # # # # # # # # # # # #
####### # ####### # # ##### # ### ##### # # # ###
# # # # # # # # #
# ####### ######### ### ##### # ### ########### #
# # # # # # # # # # # # #
# ### # # ### # # ### ##### ##### ### ####### # #
# # # # # # # # # # # # #
# # # ##### ##### # # # # ####### # ### ####### #
# # # # # # # # # # # # # # # #
### ### ##### # # # # # # ### # ##### # ### ### #
# # # # # # # # # # # # # # #
# ### ######### ### # # ### # # ##### ### ##### #
# # # # # # # # # # #
# # ########### # # ##### ### ### ##### ### # ###
# # # # # # # # # # # # #
# ######### # ##### # ##### ### # # # ##### ### #
# # # # # # # # # # #
# # ### ### ########### ######### # ######### ###
# # # # # # # # # # # #
# ### # # # # ##### ####### # ####### # # # ### #
# # # # # # # # # # # # # # #
### # # ######### ### # # # # # ### ##### ### # #
# # # # # # # # # # # # # # #
# ### ### # ### # # ##### # ##### ##### ### ### #
# # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S# # # # # #
# ##### # # # ####### # ##### ##### ### ##### # #
# # # # # # # # # # # # #
### # ### # ##### ##### # # ######### ### # ### #
# # # # # # # # # # # # #
# ##### ##### # # # ######### ##### # ##### # ###
# # # # # # # # # # # # # # # #
# # # ### # # # ##### # # # # # ##### ### # ### #
# # # # # # # # # # # # # #
# ##### # # ### # # ########### # ####### ### ###
# # # # # # # # # # # #
# # ########### ### # ######### # # ########### #
# # # # # # # # # # # #
# ##### # # # ### ### ####### # ##### # ### # # #
# # # # # # # # # # # # #
##### ########### # ####### ##### # # ### # #####
# # # # # # # # # #
### ### # ######### ############### # # ####### #
# # # # # # # # # #
# ######### ### ##### # ### ### # ### # # ##### #
# # # # # # # # # # # #
# ##### # # # ### ### ### # # ### ##### # ### ###
# # # # # # # # # # # # # # #
##### # ##### # ### ##### # # # ### ##### # ### #
# # # # # # # # # # # # #
# # # ### # ########### ### # ######### ##### # #
# # # # # # # # # # # # # #
# ### # # # # ####### # ### # # ######### #######
# # # # # # # # # # # # # #
# # ##### ### ##### ##### ### ### # # # ### ### #
# # # # # # # # # # # # # # # #
# # # # # # ### # ### # ##### # # # # # # ### # #
# # # # # # # # # # # # # # #
##### ### ####### # ##### # ######### ####### # #
# # # # # # # # # # #
# ##### ### ### # ######### # # ####### # ##### #
# # # # # # # # # # # #
### # ##### # ##### ### ##### ##### # ##### ### #
# # # # # # # # # # # # #
# # ### # ### # ### ##### # ####### # # ### # ###
# # # # # # # # # # # # # #
# ### ##### ##### ### ##### # ######### # ##### #
# # # # # # # # #
# ##### ##### # ### ### ####### #################
# # # # # # # # # #
##### ### ####### ### ####### ### # ##### ### # #
# # # # #E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S# # # # # # #
# ### # # ### ##### ##### ### ### # # # # #######
# # # # # # # # # # # # #
####### ### ##### ### ##### # # # ### # ####### #
# # # # # # # # # # # # #
# # ##### ### # ### ### ### ### # # ### # ### # #
# # # # # # # # # # # # # # # #
# # # # ### # ### ### # ####### # ####### ##### #
# # # # # # # # # # # # # #
### # ####### # # # ### # ############# ### # # #
# # # # # # # # # # # #
# # ####### ### # ######### ### # ### ### ##### #
# # # # # # # # # # # # # #
# ##### # ### # # # # # ########### ### # # # ###
# # # # # # # # # # # # # # #
# ### ### # ##### # # # # # ##### ### # ####### #
# # # # # # # # # # # # # #
# # ### ####### ### ##### # # # # # ######### # #
# # # # # # # # # # # # # #
##### ### # ##### ######### # # ####### ### ### #
# # # # # # # # # # # # #
# ### # ### # # # # # # ##### ##### # ### ### # #
# # # # # # # # # # # # # # # #
# # ### # ##### # # # ##### ### ##### # # # ### #
# # # # # # # # # # # # # # # # # #
# # # # # # # ##### ### # ### # # # ##### # # ###
# # # # # # # # # # # # # # # #
# # # # ##### ### # # ### # ### # ##### ####### #
# # # # # # # # # # # # # # #
# # # ######### # ### # ### ##### # # # # #######
# # # # # # # # # # # # #
# # ##### # # # ### ### ### ######### ##### # ###
# # # # # # # # # # # # #
# ######### # ### ### ### # ##### # ### ### ### #
# # # # # # # # # # # # #
# # # ####### # # # ######### # ##### ### ### # #
# # # # # # # # # # # # # # # #
### # ####### # # # # # # # ### # # ### # ### # #
# # # # # # # # # # # # # # # # # #
# ####### # ### # # # # ### # ### # # ### # ### #
# # # # # # # # # # # # #
# # ### ##### ####### ####### # # ####### # ### #
# # # # # # # # # # # # #
# ### ####### # # # ####### ### ### # # ##### ###
# # # # # # # # # # # # # # # #
# # # # # ##### ####### # ##### # # # ##### ### #
# # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S# # # # # # #
# ### # # # # # # ### # ##### ####### ### # ### #
# # # # # # # # # # # # # # #
# # ####### # ######### # # ##### # ### ### # ###
# # # # # # # # # # # # # #
##### # # # # ##### # ### # ####### # # # ##### #
# # # # # # # # # # # # # # # #
# # ### # # # ### ####### # # # # ### ##### #####
# # # # # # # # # # #
# ### ##### ####### # # ########### # ### ##### #
# # # # # # # # # # # # #
# # ### ##### ##### # ### ### ######### ##### ###
# # # # # # # # # #
# ####### ### ####### # ############# # ####### #
# # # # # # # # # # #
# # # # ### # ### # # ### # ############### # ###
# # # # # # # # # # # # #
# # # ### ##### ### ### # ### # ########### ### #
# # # # # # # # # # # # # # # #
####### ### # # # ### # ### # ### # # ### # # # #
# # # # # # # # # # # # # # #
### # ### # ####### # ### ##### # ##### ### # # #
# # # # # # # # # # # # # #
# # # ### ### ### # # ##### ##### # # ### #######
# # # # # # # # # # # # #
# ##### ### ### ### ### # # # ######### ####### #
# # # # # # # # # # # # #
# # ##### # # ##### # ### ####### # # # # # #####
# # # # # # # # # # # # # #
# ### ### # ##### ##### ### # ##### # ### ##### #
# # # # # # # # # # # # # # #
# ### # ##### ##### # # # # ### ####### # # # ###
# # # # # # # # # # # # # #
# # ########### # ### ### ####### ### # # # ### #
# # # # # # # # # # # #
# ##### ########### ### # ### ##### # # # ##### #
# # # # # # # # # # # # #
### # # # # ####### ##### # ####### ####### #####
# # # # # # # # # # #
# ### # # # # ### ############### # ##### ##### #
# # # # # # # # # # # #
# # # ##### # # ######### # # ####### # ####### #
# # # # # # # # # # # # # #
# # # # # # # ##### # # ####### ### ####### ### #
# # # # # # # # # # # # # # #
# # # # # ##### # # # # ##### ### # # ### #######
# # # # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S # # # # # # #
### # ### ######### ####### ##### ### # # # # # #
# # # # # # # # # # # # #
# ##### # # ##### ##### # ### # # # ### ### #####
# # # # # # # # # # # #
############# ####### ####### ####### # ##### # #
# # # # # # # # # #
### # # ### ### # # # # ####### ######### ##### #
# # # # # # # # # # # # # #
# ### # # ##### # ##### # ### ### # ### ### ### #
# # # # # # # # # # # # # #
# ##### # ######### # # ### # # ### # ### # # ###
# # # # # # # # # # # # # # # # #
# # # ### # # # ### ####### ### ### # ####### # #
# # # # # # # # # # # # #
# # ### ######### ### ### # # # # ##### ### ### #
# # # # # # # # # # # # # # #
### # ######### # # ### ### # # # # # ### # # ###
# # # # # # # # # # # # # # # #
# ##### # # # # # ### # # # ####### ### ####### #
# # # # # # # # # # # # #
# # ##### # ### ####### # ### ### ########### # #
# # # # # # # # # # # # #
# ### ### # # # # ### # ##### # ##### # ####### #
# # # # # # # # # # # # # # # # #
# ### # # # # # ### # ##### ####### # ### # # ###
# # # # # # # # # # # # # #
# # ### # # ####### ### # ####### # ####### ### #
# # # # # # # # # # # # # #
##### # # ##### # # # ####### # # # ### ### # # #
# # # # # # # # # # # # # #
# ### ### # # # ######### ### # ##### ### #######
# # # # # # # # # # #
# # ### ### # ############# ####### ### ####### #
# # # # # # # # # # #
# ### ####### ##### # # ######### ### ##### # # #
# # # # # # # # # # # # # #
# # # # ### ##### # ### # ### # ### ### #########
# # # # # # # # # # # # #
### # ### ### ### ### ####### # ##### ### # # # #
# # # # # # # # # # # # # #
##### ##### ####### # # # ##### # # ####### ### #
# # # # # # # # # #
# ##### ##### ### ##### ##### ####### # ##### ###
# # # # # # # # # # # # # # #
# ### ### ####### # # # # # ### # # ### # ### # #
# # # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S# # # # # #
# ##### # ##### # ### ####### # # ### # ##### ###
# # # # # # # # # # # # # # #
# # # ##### ### # # ### ### # ##### # ##### ### #
# # # # # # # # # # # # # #
##### # ##### ### ### ### # ### ######### # # # #
# # # # # # # # # # # # #
# ##### # ##### ######### # ##### # # # # ##### #
# # # # # # # # # # #
### # # ##### # ### ####### # ####### ###########
# # # # # # # # # # #
# ####### # ##### ####### # ### ### # # ####### #
# # # # # # # # # # # # # #
### # ######### ### # # ### # ### # ### # ##### #
# # # # # # # # # # # # # #
# ##### # # # ### # # ####### ### ####### # # # #
# # # # # # # # # # # # # #
##### ##### # # ####### # ##### ##### # ### # ###
# # # # # # # # # # # # # # #
# ##### # ### ### # # # # # ### # # # ### # ### #
# # # # # # # # # # # # # # # # #
# # # ####### ### # ##### ### # # ##### ### # # #
# # # # # # # # # # #
# ### ##### ### ### ########### ### # ### ### # #
# # # # # # # # # # # # # #
### ### # ### ### ##### # # ##### # ### ### # # #
# # # # # # # # # # # # # # # # #
# # # # ### ### ### # # ##### ##### ### ### ### #
# # # # # # # # # # # # # #
# ### ####### ####### ### # ### # ### ### ### # #
# # # # # # # # # #
####### # ##### # # ####### ####### ### # # ### #
# # # # # # # # # # # # # # #
# # # ##### # # ### # # ##### # # ### ### ### ###
# # # # # # # # # # # # #
# ####### ### ### ### ### # ########### ### ### #
# # # # # # # # # # # # # #
# ### # ##### # ### ### # ### ####### # # # # ###
# # # # # # # # # # # # #
########### # # # ### # ####### # # # # ####### #
# # # # # # # # # # # # #
# ### ### # ##### # # ##### ##### # # ### ##### #
# # # # # # # # # # # # # #
### # ####### # ### ### # ################# ### #
# # # # # # # # # # #
# ### # # ####### # # ### # ##### # ### ##### ###
# # # # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S# # # # # #
# # ####### # # ####### ### # # ##### ### # #####
# # # # # # # # # # # # #
####### ############# # # ##### # # ### ####### #
# # # # # # # # # #
### # ### ##### ####### # # # # ######### #######
# # # # # # # # # # # #
# ### # # # # # # # ############# ##### ##### # #
# # # # # # # # # # # # # #
### ##### # ##### ### ####### # ### # ### # ### #
# # # # # # # # # # # # # #
# ##### # ##### # # ### ### # # # ### # # ##### #
# # # # # # # # # # # # # # # #
# # # ##### # ##### # ### # ##### # ##### # #####
# # # # # # # # # # # # # #
# # # # # ### # ##### # ######### # # ####### # #
# # # # # # # # # # # # #
# # ############# # ##### # ### # ##### ### ### #
# # # # # # # # # # # #
# ##### # ##### # ########### ####### # # ### ###
# # # # # # # # # # #
# ####### # # ############# # ### ######### # # #
# # # # # # # # # # #
##### # ### ### ############# # ### # # # ##### #
# # # # # # # # # # # # #
# ##### # # # ### ##### ### ### ####### # # #####
# # # # # # # # # # # # # #
### # ### ### # ### # ### # # # # ########### # #
# # # # # # # # # # # #
# ##### ######### ########### ##### ##### # ### #
# # # # # # # # # # #
### ### # # ######### ### # ### ##### ##### # # #
# # # # # # # # # # # #
# ### ########### # ##### ######### # # ####### #
# # # # # # # # # #
# ##### ### # # ### # ### # ######### ##### ### #
# # # # # # # # # # # #
######### ########### # ##### # # ##### ##### ###
# # # # # # # # # # #
### # ##### # # ####### ### ##### # ##### # ### #
# # # # # # # # # # # # #
# # ### # # # # # ### ############### # # #######
# # # # # # # # # # # # # # #
# ### ##### # # # # ####### # ### # # ### ##### #
# # # # # # # # # # # # #
# ##### ##### ##### # ### # ### ########### # # #
# # # # # E#
#################################################

View File

@ -0,0 +1,49 @@
#################################################
#S # # # # # # #
##### # # ##### # # # # # # ### # # ##### ##### #
# # # # # # # # # # # # # # #
# ####### # # # # # # ##### # ####### ######### #
# # # # # # # # # # # # # #
########### ### # ### # # ######### ### # # #####
# # # # # # # # # # # #
# ### # # # ##### # ### # # ######### ### ##### #
# # # # # # # # # # # # # # #
# # ##### # # ### ##### # # # ### # # # ##### # #
# # # # # # # # # # # # # #
# # ####### ### # # ##### # ### ####### ####### #
# # # # # # # # # # # # #
# ####### ### # ####### # ### ##### ##### # # # #
# # # # # # # # # # # # # #
# # # # # ####### ### # # # ######### # # # # # #
# # # # # # # # # # # # # # # #
# ##### ########### ### # # # # # # ### ##### # #
# # # # # # # # # # # # #
##### # # ##### ##### ### ##### ##### ##### # ###
# # # # # # # # # # #
# ### ##### ##### # # # ######### ### # ####### #
# # # # # # # # # # # # #
# # # # ##### ### ##### # # ####### ### # #######
# # # # # # # # # # # # # #
# # # ##### ######### ### ### # # # # ### ##### #
# # # # # # # # # # # #
# ### ############# ### # # # # ####### ####### #
# # # # # # # # # # # # #
### ### ### ### # # # ### ####### ### # # ### # #
# # # # # # # # # # # # # #
# ### ### ### ####### # ### # # ### ### ### #####
# # # # # # # # # # # # #
# ######### ##### # ### # ### ### ####### # ### #
# # # # # # # # # # # # #
# ### # ### # # # # ### ### ### # # # ###########
# # # # # # # # # # # # # #
### # # # # ### # ########### # ######### ##### #
# # # # # # # # # #
# ### # ####### ####################### # # # # #
# # # # # # # # # #
# ####### ### # # ### ######### # ######### # ###
# # # # # # # # # # # # #
# # ####### ##### # ### ### ####### # # ####### #
# # # # # # # # # # # # # #
# ####### ##### # ### # # ### # # # ####### # # #
# # # # # # E#
#################################################

View File

@ -0,0 +1,13 @@
from source.view.observer import Observer, ConsoleView, Event
from source.view.command import Player, Command, MoveCommand, CommandHistory, DIRECTIONS
__all__ = [
"Observer",
"ConsoleView",
"Event",
"Player",
"Command",
"MoveCommand",
"CommandHistory",
"DIRECTIONS",
]

View File

@ -0,0 +1,158 @@
from abc import ABC, abstractmethod
from typing import Optional
from source.models.base import Maze, Cell
# ---------------------------------------------------------------------------- #
# Игрок #
# ---------------------------------------------------------------------------- #
class Player:
"""Хранит текущее положение игрока в лабиринте.
Attributes:
cell: Текущая клетка игрока.
"""
def __init__(self, cell: Cell) -> None:
"""Инициализирует игрока на заданной клетке.
Args:
cell: Начальная клетка игрока.
"""
self.cell = cell
def __repr__(self) -> str:
return f"Player(x={self.cell.x}, y={self.cell.y})"
# ---------------------------------------------------------------------------- #
# Интерфейс команды #
# ---------------------------------------------------------------------------- #
class Command(ABC):
"""Интерфейс команды с поддержкой отмены."""
@abstractmethod
def execute(self) -> bool:
"""Выполняет команду.
Returns:
True если команда выполнена успешно, False иначе.
"""
@abstractmethod
def undo(self) -> None:
"""Отменяет команду, восстанавливая предыдущее состояние."""
# ---------------------------------------------------------------------------- #
# Команда перемещения #
# ---------------------------------------------------------------------------- #
DIRECTIONS = {
"w": (0, -1),
"s": (0, 1),
"a": (-1, 0),
"d": (1, 0),
}
class MoveCommand(Command):
"""Перемещает игрока в заданном направлении.
Сохраняет предыдущую клетку для возможности отмены хода.
"""
def __init__(self, player: Player, direction: str, maze: Maze) -> None:
"""Инициализирует команду перемещения.
Args:
player: Объект игрока.
direction: Направление ('w', 'a', 's', 'd').
maze: Объект лабиринта для проверки проходимости.
Raises:
ValueError: Если направление не распознано.
"""
if direction not in DIRECTIONS:
raise ValueError(
f"Неизвестное направление '{direction}'. Используй: w/a/s/d"
)
self._player = player
self._direction = direction
self._maze = maze
self._prev_cell: Optional[Cell] = None
def execute(self) -> bool:
"""Перемещает игрока если целевая клетка проходима.
Returns:
True если перемещение выполнено, False если клетка непроходима.
"""
dx, dy = DIRECTIONS[self._direction]
target = self._maze.get_cell(
self._player.cell.x + dx,
self._player.cell.y + dy,
)
if target is None or not target.is_possible():
return False
self._prev_cell = self._player.cell
self._player.cell = target
return True
def undo(self) -> None:
"""Возвращает игрока на предыдущую клетку."""
if self._prev_cell is not None:
self._player.cell = self._prev_cell
# ---------------------------------------------------------------------------- #
# История команд #
# ---------------------------------------------------------------------------- #
class CommandHistory:
"""Хранит историю выполненных команд и позволяет отменять их.
Example:
history = CommandHistory()
cmd = MoveCommand(player, 'w', maze)
if cmd.execute():
history.push(cmd)
history.undo() # отменяет последний успешный ход
"""
def __init__(self) -> None:
self._history: list[Command] = []
def push(self, command: Command) -> None:
"""Добавляет выполненную команду в историю.
Args:
command: Успешно выполненная команда.
"""
self._history.append(command)
def undo(self) -> bool:
"""Отменяет последнюю команду из истории.
Returns:
True если отмена выполнена, False если история пуста.
"""
if not self._history:
print("Нечего отменять.")
return False
self._history.pop().undo()
return True
def clear(self) -> None:
"""Очищает историю команд."""
self._history.clear()

Some files were not shown because too many files have changed in this diff Show More