forked from UNN/2026-rff_mp
Merge pull request '[1,2] 1 и 2 лабы' (#173) from musinaa/2026-rff_mp:task-1 into develop
Reviewed-on: UNN/2026-rff_mp#173
This commit is contained in:
commit
e87bdd2808
2
MusinAA/.gitignore
vendored
Normal file
2
MusinAA/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.vscode/
|
||||
*/tests/
|
||||
349
MusinAA/docs/Report 1.ipynb
Normal file
349
MusinAA/docs/Report 1.ipynb
Normal file
File diff suppressed because one or more lines are too long
903
MusinAA/docs/Report 2.ipynb
Normal file
903
MusinAA/docs/Report 2.ipynb
Normal file
File diff suppressed because one or more lines are too long
BIN
MusinAA/docs/data/task1/performance_plot.png
Normal file
BIN
MusinAA/docs/data/task1/performance_plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
31
MusinAA/docs/data/task1/results.csv
Normal file
31
MusinAA/docs/data/task1/results.csv
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
Структура,Режим,Вставка,Поиск,Удаление
|
||||
Связанный список,Cлучайный,0.09829891600020346,0.0007068659997457871,0.0005386180000641616
|
||||
Хэш-таблица,Cлучайный,0.04794800999934523,0.00069890399936412,0.00033887100016727345
|
||||
Бинарное дерево,Cлучайный,0.014146466999591212,0.00019723300010809908,0.00022258500030147843
|
||||
Связанный список,Отсортированный,0.16592630900049699,0.0017924130006576888,0.001010537000183831
|
||||
Хэш-таблица,Отсортированный,0.04675658399992244,0.000497691000418854,0.00027706199944077525
|
||||
Бинарное дерево,Отсортированный,0.098506346999784,0.001621370999600913,0.0008596789994044229
|
||||
Связанный список,Cлучайный,0.07528530299987324,0.0006713170005241409,0.0004351130000941339
|
||||
Хэш-таблица,Cлучайный,0.04169118899972091,0.0004370679998828564,0.0002442360000713961
|
||||
Бинарное дерево,Cлучайный,0.009762656000020797,0.0001406600003974745,8.869900011632126e-05
|
||||
Связанный список,Отсортированный,0.15083865700034949,0.001965620000191848,0.0009268670000892598
|
||||
Хэш-таблица,Отсортированный,0.04658651899990218,0.0004731760000140639,0.00026295399948139675
|
||||
Бинарное дерево,Отсортированный,0.10888835700006894,0.0032681640004739165,0.0010110960001838976
|
||||
Связанный список,Cлучайный,0.09252672599996004,0.0014638780003224383,0.0009516599993730779
|
||||
Хэш-таблица,Cлучайный,0.04701576600018598,0.0004413979995661066,0.00024472499990224605
|
||||
Бинарное дерево,Cлучайный,0.010519597999518737,0.00015120700027182465,0.00012815900026907912
|
||||
Связанный список,Отсортированный,0.15883956299967394,0.001480011000239756,0.0007378059999609832
|
||||
Хэш-таблица,Отсортированный,0.043343710000044666,0.0005192710004848777,0.0002623249993121135
|
||||
Бинарное дерево,Отсортированный,0.19170180800028902,0.0011184409995621536,0.0008248280000771047
|
||||
Связанный список,Cлучайный,0.09595573600017815,0.0009538959993733442,0.0004928719999952591
|
||||
Хэш-таблица,Cлучайный,0.04453241200008051,0.000944256999900972,0.0005029280000599101
|
||||
Бинарное дерево,Cлучайный,0.011908257000868616,0.0001221530001203064,0.00011502899997140048
|
||||
Связанный список,Отсортированный,0.16769071699945926,0.0015361639998445753,0.0011414199998398544
|
||||
Хэш-таблица,Отсортированный,0.05018426599963277,0.0006002179998176871,0.000283696000224154
|
||||
Бинарное дерево,Отсортированный,0.09999411199987662,0.0010742320000645122,0.0009550129998388002
|
||||
Связанный список,Cлучайный,0.08812657299949933,0.0006700599997202517,0.0006053869992683758
|
||||
Хэш-таблица,Cлучайный,0.042967892999513424,0.0005705349994968856,0.0002917279998655431
|
||||
Бинарное дерево,Cлучайный,0.01326883900037501,0.00013954399946669582,0.00013297800069267396
|
||||
Связанный список,Отсортированный,0.16893773900028464,0.0017602859998078202,0.0007569420004074345
|
||||
Хэш-таблица,Отсортированный,0.05997269399995275,0.000543855999239895,0.0002741980006248923
|
||||
Бинарное дерево,Отсортированный,0.11176624800009449,0.0010512540002309834,0.0007160159993873094
|
||||
|
16
MusinAA/docs/data/task2/results.csv
Normal file
16
MusinAA/docs/data/task2/results.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути
|
||||
BFS,10x10,0.4038135000882903,53,23
|
||||
BFS,5x5,0.07533170064562,8,7
|
||||
BFS,100x100,17.14356810080062,2495,1171
|
||||
BFS,50x50,3.010086300491821,640,427
|
||||
BFS,25x25,1.0405578999780118,232,173
|
||||
DFS,10x10,0.07943829987198114,35,31
|
||||
DFS,5x5,0.018403499416308478,8,7
|
||||
DFS,100x100,8.430859900545329,3219,1243
|
||||
DFS,50x50,2.0664067997131497,995,435
|
||||
DFS,25x25,0.5787261994555593,316,173
|
||||
AStar,10x10,0.0671462003083434,23,23
|
||||
AStar,5x5,0.022370600345311686,8,7
|
||||
AStar,100x100,4.951790099585196,1286,1171
|
||||
AStar,50x50,2.081632300541969,496,427
|
||||
AStar,25x25,0.5791453000711044,186,177
|
||||
|
7
MusinAA/docs/data/task2/results2.csv
Normal file
7
MusinAA/docs/data/task2/results2.csv
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Алгоритм,Лабиринт,Время (мс),Посещённые клетки,Длинна пути
|
||||
BFS,maze_25x25_wo_exit,1.9682294001540868,338,-1
|
||||
BFS,maze_25x25_empty,4.574537699954817,625,49
|
||||
DFS,maze_25x25_wo_exit,0.719102000221028,338,-1
|
||||
DFS,maze_25x25_empty,0.903778699648683,625,337
|
||||
AStar,maze_25x25_wo_exit,1.0117966015968705,338,-1
|
||||
AStar,maze_25x25_empty,0.21763520016975235,49,49
|
||||
|
0
MusinAA/task1/__init__.py
Normal file
0
MusinAA/task1/__init__.py
Normal file
88
MusinAA/task1/structures/BinaryTree.py
Normal file
88
MusinAA/task1/structures/BinaryTree.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
"""
|
||||
Двоичное дерево поиска
|
||||
|
||||
Узел — словарь:
|
||||
{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.
|
||||
"""
|
||||
|
||||
def bst_insert(root: dict|None, name: str, phone: str) -> dict:
|
||||
"""Итеративно вставляет, возвращает новый корень (если корень меняется)."""
|
||||
if root == None:
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
# '674' < '722' == True, lol
|
||||
current = root
|
||||
while True:
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
return root
|
||||
elif name < current['name']:
|
||||
if current['left'] == None:
|
||||
current['left'] = bst_insert(None, name, phone)
|
||||
return root
|
||||
else:
|
||||
current = current['left']
|
||||
else:
|
||||
if current['right'] == None:
|
||||
current['right'] = bst_insert(None, name, phone)
|
||||
return root
|
||||
else:
|
||||
current = current['right']
|
||||
# Увы, это самый лаконичный вариант, который я придумал.
|
||||
|
||||
|
||||
def bst_find(root: dict|None, name: str) -> str|None:
|
||||
"""Поиск в ширину."""
|
||||
node = find_node_to_delete(root, name)
|
||||
if node != None:
|
||||
return node['phone']
|
||||
|
||||
def find_node_to_delete(root: dict|None, name: str) -> dict|None:
|
||||
"""Поиск в ширину."""
|
||||
while root != None:
|
||||
if root['name'] == name:
|
||||
return root
|
||||
elif name < root['name']:
|
||||
root = root['left']
|
||||
else:
|
||||
root = root['right']
|
||||
return None
|
||||
|
||||
def find_minimal_child(root: dict) -> dict|None:
|
||||
while root['left']:
|
||||
root = root['left']
|
||||
return root
|
||||
|
||||
def bst_delete(root: dict, name: str) -> None:
|
||||
"""Удаляет узел и возвращает новый корень."""
|
||||
if root is None:
|
||||
return None
|
||||
|
||||
if name < root['name']:
|
||||
root['left'] = bst_delete(root['left'], name)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_delete(root['right'], name)
|
||||
else:
|
||||
# Случай 1: нет детей или один ребенок
|
||||
if root['left'] is None:
|
||||
return root['right']
|
||||
elif root['right'] is None:
|
||||
return root['left']
|
||||
|
||||
# Случай 2: два ребенка
|
||||
min_node = find_minimal_child(root['right'])
|
||||
root['name'] = min_node['name']
|
||||
root['phone'] = min_node['phone']
|
||||
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_list_all(root: dict) -> list:
|
||||
"""Центрированный обход.
|
||||
Рекурсивно собирает записи в отсортированном порядке."""
|
||||
|
||||
if root is None:
|
||||
return []
|
||||
node_values = {"name": root['name'], "phone": root['phone']}
|
||||
return bst_list_all(root['left']) + [node_values] + bst_list_all(root['right'])
|
||||
59
MusinAA/task1/structures/HashTable.py
Normal file
59
MusinAA/task1/structures/HashTable.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
Хеш-таблица
|
||||
|
||||
Хранится как список buckets фиксированной длины,
|
||||
каждый элемент — голова связного списка (или None).
|
||||
"""
|
||||
|
||||
from task1.structures.LinkedList import *
|
||||
|
||||
def hash_fun(name: str, size: int) -> int:
|
||||
"""Принимает имя и возвращает индекс бакета для него."""
|
||||
if size <= 0:
|
||||
raise ValueError("size должен быть больше 0")
|
||||
|
||||
hashSum = 0
|
||||
n = size+1
|
||||
base = 1103 # ord('я')
|
||||
for letter in name:
|
||||
hashSum += ord(letter) * pow(base, n)
|
||||
n -= 1
|
||||
return int(hashSum) % size
|
||||
|
||||
def ht_insert(buckets: list|None, name: str, phone: str, blen:int = 50) -> list:
|
||||
"""Возвращает новый массив бакетов
|
||||
Вычисляет индекс, вызывает ll_insert для соответствующего бакета.
|
||||
Функция не меняет размер массива бакетов автоматически!"""
|
||||
if buckets == [] or buckets == None:
|
||||
buckets = [None] * blen
|
||||
# raise ValueError("Длинна buckets должна быть больше 0")
|
||||
|
||||
size = len(buckets)
|
||||
index = hash_fun(name, size)
|
||||
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||
return buckets
|
||||
|
||||
def ht_delete(buckets: list, name: str) -> list:
|
||||
"""Возвращает новый массив бакетов без элемента с именем name"""
|
||||
if buckets == []:
|
||||
raise ValueError("Длинна buckets должна быть больше 0")
|
||||
|
||||
size = len(buckets)
|
||||
index = hash_fun(name, size)
|
||||
buckets[index] = ll_delete(buckets[index], name)
|
||||
return buckets
|
||||
|
||||
def ht_find(buckets: list|None, name: str) -> str|None:
|
||||
if buckets == [] or buckets == None:
|
||||
raise ValueError("Длинна buckets должна быть больше 0")
|
||||
|
||||
size = len(buckets)
|
||||
index = hash_fun(name, size)
|
||||
return ll_find(buckets[index], name)
|
||||
|
||||
def ht_list_all(buckets):
|
||||
"""Собирает все записи из всех бакетов и сортирует"""
|
||||
allRecords = []
|
||||
for bucket in buckets:
|
||||
allRecords.extend(ll_list_all(bucket))
|
||||
return sorted(allRecords, key=lambda x: x[0])
|
||||
63
MusinAA/task1/structures/LinkedList.py
Normal file
63
MusinAA/task1/structures/LinkedList.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"""
|
||||
Связный список (LinkedListPhoneBook)
|
||||
|
||||
Узел представляется словарём:
|
||||
{'name': 'Имя', 'phone': '123', 'next': None}.
|
||||
"""
|
||||
|
||||
|
||||
def ll_insert(head : dict|None, name: str, phone: str) -> dict:
|
||||
"""
|
||||
Проходит до конца (или сразу добавляет в конец) и возвращает новую
|
||||
голову (если вставка в начало) или изменяет список по ссылке.
|
||||
Удобнее возвращать новую голову, если вставка может быть в начало.
|
||||
"""
|
||||
|
||||
newNode = {'name': name, 'phone': phone, 'next': None}
|
||||
if head == None:
|
||||
return newNode
|
||||
|
||||
currentNode = head
|
||||
while currentNode['next'] != None:
|
||||
if currentNode['name'] == name:
|
||||
currentNode['phone'] = phone
|
||||
return head
|
||||
currentNode = currentNode['next']
|
||||
currentNode['next'] = newNode
|
||||
return head
|
||||
|
||||
def ll_find(head : dict|None, name: str) -> str|None:
|
||||
"""Ищет узел, возвращает телефон или None."""
|
||||
currentNode = head
|
||||
while currentNode != None:
|
||||
if currentNode['name'] == name:
|
||||
return currentNode['phone']
|
||||
currentNode = currentNode['next']
|
||||
return None
|
||||
|
||||
def ll_delete(head : dict|None, name: str) -> dict|None:
|
||||
"""Удаляет узел, возвращает новую голову."""
|
||||
if head == None:
|
||||
return None
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
currentNode = head
|
||||
while currentNode['next'] != None:
|
||||
if currentNode['next']['name'] == name:
|
||||
currentNode['next'] = currentNode['next']['next']
|
||||
return head
|
||||
currentNode = currentNode['next']
|
||||
return head
|
||||
|
||||
def ll_list_all(head: dict|None) -> list:
|
||||
"""Cобирает все записи в список и сортирует.
|
||||
сортировка вынесена отдельно)."""
|
||||
records = []
|
||||
currentNode = head
|
||||
while currentNode != None:
|
||||
records.append((currentNode['name'], currentNode['phone']))
|
||||
currentNode = currentNode['next']
|
||||
records.sort(key=lambda item: item[0])
|
||||
return records
|
||||
0
MusinAA/task1/structures/__init__.py
Normal file
0
MusinAA/task1/structures/__init__.py
Normal file
0
MusinAA/task1/util/__init__.py
Normal file
0
MusinAA/task1/util/__init__.py
Normal file
55
MusinAA/task1/util/randomNames.py
Normal file
55
MusinAA/task1/util/randomNames.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import random
|
||||
|
||||
names_pool = (
|
||||
"Иван", "Мария", "Петр", "Анна", "Сергей", "Елена", "Алексей", "Ольга",
|
||||
"Дмитрий", "Татьяна", "Михаил", "Наталья", "Андрей", "Ирина", "Николай",
|
||||
"Светлана", "Владимир", "Екатерина", "Александр", "Юлия", "Павел", "Ксения",
|
||||
"Виктор", "Анастасия", "Артем", "Виктория", "Максим", "Полина", "Даниил",
|
||||
"София", "Евгений", "Алиса", "Станислав", "Дарья", "Георгий", "Вероника",
|
||||
"Кирилл", "Маргарита", "Тимофей", "Арина", "Руфина", "Илларион", "Стелла",
|
||||
"Роман", "Валерия", "Игорь", "Алина", "Олег", "Диана", "Юрий", "Милана",
|
||||
"Василий", "Ева", "Никита", "Алиса", "Константин", "Кира", "Денис", "Ангелина",
|
||||
"Вячеслав", "Мирослава", "Григорий", "Эмилия", "Леонид", "Василиса", "Руслан",
|
||||
"Стефания", "Арсений", "Есения", "Антон", "Яна", "Матвей", "Любовь", "Семен",
|
||||
"Надежда", "Федор", "Софья", "Лев", "Варвара", "Егор", "Амелия", "Борис",
|
||||
"Агата", "Захар", "Камилла", "Давид", "Олеся", "Ярослав", "Людмила", "Данила",
|
||||
"Регина", "Марк", "Каролина", "Артур", "Нелли", "Глеб", "Инна", "Платон",
|
||||
"Нина", "Святослав", "Римма", "Родион", "Лидия", "Эдуард", "Жанна", "Вадим",
|
||||
"Рената", "Савелий", "Алла", "Назар", "Снежана", "Демид", "Лариса", "Филипп",
|
||||
"Злата", "Тимур", "Майя", "Клим", "Эльвира", "Дамир", "Таисия", "Илья",
|
||||
"Роза", "Виталий", "Азалия", "Степан", "Лиана", "Богдан", "Инесса", "Эрик",
|
||||
"Ариана", "Алан", "Юлиана", "Лука", "Антонина", "Мирон", "Клавдия", "Гордей",
|
||||
"Руслана", "Макар", "Елизавета", "Северин", "Александра", "Моисей", "Агафья",
|
||||
"Наум", "Серафима", "Влад", "Фаина", "Кузьма", "Пелагея", "Ермак", "Ульяна",
|
||||
"Тарас", "Марианна", "Остап", "Бронислава", "Архип", "Владислава", "Фома",
|
||||
"Станислава", "Еремей", "Зинаида", "Прохор", "Раиса", "Мстислав", "Галина",
|
||||
"Ростислав", "Валентина", "Серафим", "Евдокия", "Лаврентий", "Кристина",
|
||||
"Никон", "Анфиса", "Феликс", "Лия", "Иннокентий", "Роксана", "Всеволод",
|
||||
"Эвелина", "Модест", "Юнона", "Трофим", "Изабелла", "Аполлон", "Глория",
|
||||
"Касьян", "Аврора", "Любомир", "Адель", "Бронислав", "Доминика", "Афанасий",
|
||||
"Фрида", "Евстафий", "Ассоль", "Венедикт", "Цветана", "Епифан", "Мелисса",
|
||||
"Добрыня"
|
||||
)
|
||||
|
||||
_non_existent_names = [
|
||||
"Ноль", "Целковый", "Полушка", "Четвертушка", "Осьмушка",
|
||||
"Пудовичок", "Медячок", "Серебрячок", "Золотничок", "Девятичок"
|
||||
]
|
||||
assert set(names_pool).isdisjoint(set(_non_existent_names)), \
|
||||
"В списке несуществующих имён существуют существующие имена сущностей"
|
||||
names_pool_to_find = random.choices(names_pool, k=100) + _non_existent_names
|
||||
|
||||
def generate_phone(phone_len=11) -> str:
|
||||
# 88005553535
|
||||
return str(random.randint(10**phone_len, 10**(phone_len+1)-1))
|
||||
|
||||
def generate_test_data(N=10000, _sorted=False):
|
||||
records = []
|
||||
for i in range(N):
|
||||
name = random.choice(names_pool)
|
||||
phone = generate_phone()
|
||||
records.append((name, phone))
|
||||
|
||||
if _sorted:
|
||||
return sorted(records)
|
||||
return records
|
||||
37
MusinAA/task1/util/timeTester.py
Normal file
37
MusinAA/task1/util/timeTester.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import time
|
||||
import random
|
||||
from typing import Callable, Any
|
||||
from task1.util.randomNames import names_pool_to_find, names_pool
|
||||
|
||||
def test(records: list,
|
||||
insert_func: Callable[[Any, str, str], Any],
|
||||
find_func: Callable[[Any, str], Any],
|
||||
delete_func: Callable[[Any, str], Any]) -> dict:
|
||||
data = None
|
||||
|
||||
# Вставка всех записей
|
||||
start = time.perf_counter()
|
||||
for item in records:
|
||||
data = insert_func(data, item[0], item[1])
|
||||
end = time.perf_counter()
|
||||
insert_time = end - start
|
||||
|
||||
# Поиск 110 случайных записей
|
||||
start = time.perf_counter()
|
||||
for name in names_pool_to_find:
|
||||
find_func(data, name)
|
||||
end = time.perf_counter()
|
||||
find_time = end - start
|
||||
|
||||
# Удаление 50 случайных записей
|
||||
start = time.perf_counter()
|
||||
for name in random.choices(names_pool, k = 50):
|
||||
data = delete_func(data, name)
|
||||
end = time.perf_counter()
|
||||
delete_time = end - start
|
||||
|
||||
return {
|
||||
"insert_time" : insert_time ,
|
||||
"find_time" : find_time ,
|
||||
"delete_time": delete_time
|
||||
}
|
||||
1
MusinAA/task2/.gitignore
vendored
Normal file
1
MusinAA/task2/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
maze_generator.py
|
||||
0
MusinAA/task2/__init__.py
Normal file
0
MusinAA/task2/__init__.py
Normal file
94
MusinAA/task2/consoleView.py
Normal file
94
MusinAA/task2/consoleView.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
Реализовать класс ConsoleView, который отображает лабиринт,
|
||||
текущее положение игрока (если реализован пошаговый режим) и найденный путь.
|
||||
Метод render(maze, player_position, path) рисует карту в консоли."""
|
||||
|
||||
import os
|
||||
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.path import Path
|
||||
from task2.observerSubject import MazeEvent, MazeEventType, Observer
|
||||
|
||||
SBROS = "\033[0m"
|
||||
|
||||
WALL = "#"
|
||||
EXIT = "E"
|
||||
START = "S"
|
||||
PATH_SYMBOL = "+"
|
||||
SPACE_SYMBOL = " "
|
||||
|
||||
# Я убрал аргументы из render(), чтобы не передавать их при каждом запуске.
|
||||
# И работать внутри класса приятнее, чем тянуть эти аргументы туда-сюда
|
||||
class ConsoleView(Observer):
|
||||
maze:Maze|None
|
||||
path:Path|None
|
||||
|
||||
def __init__(self, maze:Maze|None=None, path:Path|None=None):
|
||||
super().__init__()
|
||||
self.maze = maze
|
||||
self.path = path
|
||||
|
||||
def _getCellColored(self, cell:Cell) -> str:
|
||||
if cell.isWall:
|
||||
# Белый
|
||||
return self._fmt_str(7, 7, WALL)
|
||||
elif cell.isExit:
|
||||
# Кислотно-зелёный
|
||||
return self._fmt_str(12, 7, EXIT)
|
||||
elif cell.isStart:
|
||||
# Кислотно-красный
|
||||
return self._fmt_str(9, 7, START)
|
||||
elif self.path and self.path.array:
|
||||
if cell in self.path.array:
|
||||
# Градиент
|
||||
percent = self.path.array.index(cell) / len(self.path.array)
|
||||
n = self._ANSICalculator(*self._getGradient(percent))
|
||||
return self._fmt_str(n, 10, PATH_SYMBOL)
|
||||
return SPACE_SYMBOL
|
||||
|
||||
def _fmt_str(self, bg:int, fg:int, symbol:str) -> str:
|
||||
return f"\033[48;5;{bg}m\033[38;5;{fg}m{symbol}{SBROS}"
|
||||
|
||||
def _ANSICalculator(self, r:int, g:int, b:int):
|
||||
r = max(0, min(5, r))
|
||||
g = max(0, min(5, g))
|
||||
b = max(0, min(5, b))
|
||||
return 16 + 36*r + 6*g + b
|
||||
|
||||
def _getGradient(self, percent:float):
|
||||
r = 5 * (1-percent)
|
||||
g = 0
|
||||
b = 5 * percent
|
||||
return int(round(r)), int(round(g)), int(round(b))
|
||||
|
||||
def render(self, player_position=None):
|
||||
"""
|
||||
Печатем ячейку.
|
||||
Цвет зависит от индекса ячейчки в массиве path.
|
||||
Если в массиве нет - просто белый.
|
||||
"""
|
||||
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
if not self.maze:
|
||||
print("Лабиринт ещё не загружен")
|
||||
return None
|
||||
|
||||
output = ""
|
||||
for y in range(self.maze.height):
|
||||
for x in range(self.maze.width):
|
||||
cell = self.maze.getCell(x, y)
|
||||
output += self._getCellColored(cell)
|
||||
output += "\n"
|
||||
print(output)
|
||||
|
||||
def update(self, event: MazeEvent):
|
||||
if event.evtype in (MazeEventType.MAZE_LOADED, MazeEventType.PATH_FOUND, MazeEventType.MOVE):
|
||||
if event.evtype == MazeEventType.PATH_FOUND:
|
||||
if not event.data: raise ValueError
|
||||
self.path = event.data
|
||||
if event.evtype == MazeEventType.MAZE_LOADED:
|
||||
if not event.data: raise ValueError
|
||||
self.maze = self.maze
|
||||
self.render()
|
||||
72
MusinAA/task2/mazeBuilder.py
Normal file
72
MusinAA/task2/mazeBuilder.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from itertools import product
|
||||
import sys
|
||||
import os.path as path
|
||||
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.observerSubject import MazeEvent, MazeEventType, Subject
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
"""Интерфейс MazeBuilder с методом buildFromFile(filename)"""
|
||||
@abstractmethod
|
||||
def buildFromFile(self, filename: str):
|
||||
"""Создание лабиринта из файла."""
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
"""Читает файл, парсит символы,
|
||||
создаёт объекты Cell,
|
||||
задаёт координаты и флаги,
|
||||
после чего возвращает готовый Maze."""
|
||||
|
||||
start:dict
|
||||
end:dict
|
||||
|
||||
def _cellStrategy(self, letter: str) -> Cell:
|
||||
if letter == '#':
|
||||
return Cell(isWall=True)
|
||||
elif letter == ' ':
|
||||
return Cell()
|
||||
elif letter == 'S':
|
||||
return Cell(isStart=True)
|
||||
elif letter == 'E':
|
||||
return Cell(isExit=True)
|
||||
else:
|
||||
sys.stderr.write(f"Неизвестный символ '{letter}' при загрузке из файла\n")
|
||||
return Cell()
|
||||
|
||||
def _updateStartEnd(self, letter: str, x:int, y:int) -> None:
|
||||
if letter == 'S':
|
||||
self.start = {'x': x, 'y': y}
|
||||
elif letter == 'E':
|
||||
self.end = {'x': x, 'y': y}
|
||||
|
||||
def _generate_row_from_txt(self, filename: str) -> list[str]:
|
||||
with open(filename) as file:
|
||||
text = file.read()
|
||||
text = text.strip()
|
||||
if not text:
|
||||
raise ValueError(f"Файл \"{filename}\" пуст")
|
||||
text = text.split('\n')
|
||||
return text
|
||||
|
||||
def buildFromFile(self, filename: str):
|
||||
rows = self._generate_row_from_txt(filename)
|
||||
height = len(rows)
|
||||
width = len(rows[0])
|
||||
array = [[Cell() for j in range(width)] for i in range(height)]
|
||||
|
||||
try:
|
||||
for x, y in product(range(width), range(height)):
|
||||
cell = self._cellStrategy(rows[y][x])
|
||||
self._updateStartEnd(rows[y][x], x, y)
|
||||
cell.x = x
|
||||
cell.y = y
|
||||
array[y][x] = cell
|
||||
except IndexError:
|
||||
raise ValueError(f"В файле {filename}: Строка {y+1} имеет длину {len(rows[y])}, ожидалось {width}")
|
||||
|
||||
maze_name, _ = path.splitext(path.basename(filename))
|
||||
|
||||
return Maze(array, self.start, self.end, name=maze_name)
|
||||
|
||||
100
MusinAA/task2/mazeExamples/100x100.txt
Normal file
100
MusinAA/task2/mazeExamples/100x100.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
S # # # # # # # # # # # # # #
|
||||
# # ####### ### # ##### # # # # # # # # ### # # ### # # ### # ##### # ##### ### ### ### ######### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ##### ### ### ##### ##### ####### # ##### ####### # # # ####### ##### # ######### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### # # ##### # # ####### # ### ##### # # # # ### ### ######### ##### # ##### # # ### ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # # # ### ### ####### # # ### ######### ### ### ### ### # # # # # ### ##### # # ### # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # ### # ##### ##### ######### ######### # ##### ### # ####### # ##### ### ####### # ### # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
# ################# # ### # # # # # ########### ############# # ##### ##### ##### # ##### ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
### # ### # ### ####### # ##### ######### ### # ### ### # ####### # ### ####### ##### # ### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
######### ### ### # ### # ####### ##### # # # ####### ##### ####### # ########### ####### #########
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
## # ####### ### ##### # ####### ### # # # # ##### ### ########### # # # ### # ##### # # ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # # ########### ######### # ### # # ### ### ####### ### # # # # ##### # ### ##### # # ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## ##### # # # # # ### # ### ##### # # # ##### # ### # ### # # # # # # # ##### ### ####### # ### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### ### # # # # # ######### ### ### ### # ##### ##### ### # ##### # ######### ############# ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### # # ### ### ##### # ### ### ##### # # ### # ##### ##### ### ### # ##### ### # # ### # # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ####### ####### ### ### ######### ### # # ##### # ### ########### ### # ### ####### ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
################### # # # # ### # # # ######### # # ### ############### # ### ##### ### ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
##### # # ### # # ### # ####### # # ##### # # ### ##### # ####### # # # ######### # ######### #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
#### # # ### # # ### # ### ####### ##### # # ### # # ##### # # ####### # # # ##### ### # # ####### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## # # # # ### ### # ### ### # # # ### # ####### ### # ########### # ##### ##### # # ### ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
########### # # ########### ### # # # # # ### # # ### # ####### # ######### # # ### # ######### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
######## # # # ### # ########### # # ####### ### # # # ####### ### # # # ### # ### ######### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### ##### ### # # # ####### ### ### # # # # ############# # ##### # ### ##### ######### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ### # ### # ##### # # # ### # # # ### # ### ### # # # # ########### ####### # ##### ####### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## ####### # ##### # ### ### # # # # # # # ####### ### # # # # # # # # ### # # ##### # # # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # # # ##### ####### # ### # ### ### ####### # # ### ####### ### ####### # ### ####### # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ##### # # ### ### # ######### ### ### ### ### ####### ##### # ######### ##### # ### # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ####### # ##### ### # # # # # ### ### # ### ### ######### # ####### # ####### ### ######### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
######### # ### ### # # # # # # ####### ##### ### # # ##### # ### # # ####### # # # ### # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## # # # ### ##### # ######### # # ####### ### ### # ##### ######### # # # ####### ### # # # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ######### ####### # # ######### # # ### ### ### ##### ### # ########### ######### # # # ### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## ######### # ### # ####### ### # ### ### # # ####### # ### ##### ### # # ### # ### ##### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### ### ### # # # ##### # ### # ### ### # # # ####### ##### ### ### # ##### # ##### # ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ### # ### # ######### # ######### # ### # # # ### ##### ### # ### # # ### # ##### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # ### ##### ####### ### # ##### # # # ### ### # ##### # # ### # ####### ##### # # # ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # ### ######### # ##### ### # ### # # # ############# ### ### # ##### # ### ######### # ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## # # # ### ##### # ##### # ### ### # # # ### # # ######### # # ##### ### ####### ### # ######### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ############### ### ####### ### # ### ####### # # # # ######### # ### # # # ##### ##### # # #####
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # # ### # ### ######### # ######### # # ### # # ####### # ########### # # # ##### ####### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
## # ####### ### # # # ############# ### ### ### ### ##### # # ### ########### ##### # ### ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ### # # # ##### ### # # ##### ####### # # ### ### # # ##### # # ####### ##### # # # # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
###### ##### ### # ### ### # # ########### # # # ##### # ### ##### # # ### ######### ### ##### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # ### ####### ######### # # ######### ### # ##### ##### ##### ### # # # # # # ### ####### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
#### # ##### # # # ##### ### # ### # ##### ### # ##### ### ######### ### ### ########### # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
######### ##### # ####### # # # ##### # ### # # # # ####### ##### # # ### ### # # # # ### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ##### # # # ############### ### ######### ### # ##### # # ######### # ### # ### # # # # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ####### # ### # ##### ##### # # ### # ### # # ##### # ### # # # ####### # ##### # # # ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # ### # # # ##### ##### # # # ### ### # # # # ######### # ########### # # ##### ####### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
## # # # # ##### # ### ####### ########### ### # # ####### # # ##### # # # ### ##### ##### # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
### ##### ########### # ### # # ####### # # # ############# # ### ### # ### ######### # ### ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # # # ##### ####### # ### # # ##### # ### ####### ######### # ########### ### # ######### # #
|
||||
# # # # # # # # #
|
||||
################################################################################################## E
|
||||
10
MusinAA/task2/mazeExamples/10x10.txt
Normal file
10
MusinAA/task2/mazeExamples/10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
S #
|
||||
### ### #
|
||||
# # #
|
||||
## # # ###
|
||||
# # #
|
||||
####### #
|
||||
# #
|
||||
## # #####
|
||||
|
||||
######## E
|
||||
25
MusinAA/task2/mazeExamples/25x25.txt
Normal file
25
MusinAA/task2/mazeExamples/25x25.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
S # # #
|
||||
# ##### # # # ####### #
|
||||
# # # # # #
|
||||
#### # ##### # # #######
|
||||
# # # # # # #
|
||||
## # # # ##### ### # # #
|
||||
# # # # # # # # #
|
||||
### # # # # ### # # # #
|
||||
# # # # # # # #
|
||||
# ### # # ### ### # ####
|
||||
# # # # # # #
|
||||
### # # # ####### #####
|
||||
# # # #
|
||||
# ################# ###
|
||||
# # # #
|
||||
# # # ####### ####### ##
|
||||
# # # # #
|
||||
### ##### # ### ### ###
|
||||
# # # # # #
|
||||
# # # ##### # # #######
|
||||
# # # # # #
|
||||
##### # ####### # ### ##
|
||||
# # # # # # #
|
||||
#### # # # ### ##### # #
|
||||
# # # E
|
||||
50
MusinAA/task2/mazeExamples/50x50.txt
Normal file
50
MusinAA/task2/mazeExamples/50x50.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
S # # # # # # #
|
||||
### # ##### # ### ### # # ### # # ### # ### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
####### # # ####### ##### # ##### ####### ### ###
|
||||
# # # # # # # # # #
|
||||
### # ##### ### # ### # # ##### # ############# #
|
||||
# # # # # # # # # # # # #
|
||||
# ### # # ### ############# # # ##### ##### # # #
|
||||
# # # # # # # # # # # # # #
|
||||
#### ##### # ### # ##### # # ##### # ### # ##### #
|
||||
# # # # # # # # # # # #
|
||||
### ####### ####### # ####### # ##### # ### ### #
|
||||
# # # # # # # # # # # #
|
||||
####### # # # ### ##### # ##### ### # ### #######
|
||||
# # # # # # # # # # # # #
|
||||
## ### ####### ### # # ### # # # # ### # ### ### #
|
||||
# # # # # # # # # # # #
|
||||
####### # ##### ####### ### ######### ##### ### #
|
||||
# # # # # # # # # # #
|
||||
#### # # ### ##### ####### # # # ### # # # ### # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # ### # # # # # # ### ####### # ##### # ### #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # ### ######### # # ### # # # ##### # # # ###
|
||||
# # # # # # # # # # # # #
|
||||
####### ### # ### ####### # # ##### # ######### #
|
||||
# # # # # # # # # # # # #
|
||||
########### ### ### ### # ### # # ##### # ### # #
|
||||
# # # # # # # # # # # # # #
|
||||
### # ### ### ####### ### # ### # # # ####### # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
## # ### # # ### # # # # # # # # # ### # ### ### #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
##### # # ##### # # # ####### # ### # # # ### # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # # ### # ### ########### # ##### # ### # # ###
|
||||
# # # # # # # # # # # # #
|
||||
#### ##### # # ### ### # ##### # ##### # #########
|
||||
# # # # # # # # # # #
|
||||
# ### ##### ### ### # ### ########### ######### #
|
||||
# # # # # # # # # #
|
||||
### # ##### # # ##### # ####### # ### ### # #####
|
||||
# # # # # # # # # # #
|
||||
## ##### # ### ### # ##### ####### # # ######### #
|
||||
# # # # # # # # # # # #
|
||||
### # ##### # # ##### # ### ##### ##### ####### #
|
||||
# # # # # # # # # # #
|
||||
# ########### ########### ##### ####### # ### # #
|
||||
# # # #
|
||||
################################################ E
|
||||
5
MusinAA/task2/mazeExamples/5x5.txt
Normal file
5
MusinAA/task2/mazeExamples/5x5.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#####
|
||||
# S #
|
||||
# ###
|
||||
# E
|
||||
#####
|
||||
25
MusinAA/task2/mazeExamplesSpeical/maze_25x25_empty.txt
Normal file
25
MusinAA/task2/mazeExamplesSpeical/maze_25x25_empty.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
S
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
E
|
||||
25
MusinAA/task2/mazeExamplesSpeical/maze_25x25_wo_exit.txt
Normal file
25
MusinAA/task2/mazeExamplesSpeical/maze_25x25_wo_exit.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
S # # #
|
||||
# ##### # # # ####### #
|
||||
# # # # # #
|
||||
#### # ##### # # #######
|
||||
# # # # # # #
|
||||
## # # # ##### ### # # #
|
||||
# # # # # # # # #
|
||||
### # # # # ### # # # #
|
||||
# # # # # # # #
|
||||
# ### # # ### ### # ####
|
||||
# # # # # # #
|
||||
### # # # ####### #####
|
||||
# # # #
|
||||
# ################# ###
|
||||
# # # #
|
||||
# # # ####### ####### ##
|
||||
# # # # #
|
||||
### ##### # ### ### ###
|
||||
# # # # # #
|
||||
# # # ##### # # #######
|
||||
# # # # # #
|
||||
##### # ####### # ### ##
|
||||
# # # # # # #
|
||||
#### # # # ### ##### # #
|
||||
# # # #
|
||||
0
MusinAA/task2/mazeObjects/__init__.py
Normal file
0
MusinAA/task2/mazeObjects/__init__.py
Normal file
13
MusinAA/task2/mazeObjects/cell.py
Normal file
13
MusinAA/task2/mazeObjects/cell.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class Cell:
|
||||
"""Хранит координаты (x, y)
|
||||
флаги isWall, isStart, isExit
|
||||
метод isPassable() (возвращает True для прохода, если не стена)."""
|
||||
def __init__(self, x: int = 0, y: int = 0, isWall:bool = False, isStart:bool = False, isExit:bool = False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.isWall = isWall
|
||||
self.isStart = isStart
|
||||
self.isExit = isExit
|
||||
|
||||
def isPassable(self):
|
||||
return not self.isWall
|
||||
41
MusinAA/task2/mazeObjects/maze.py
Normal file
41
MusinAA/task2/mazeObjects/maze.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from task2.mazeObjects.cell import Cell
|
||||
|
||||
class Maze:
|
||||
"""Хранит двумерный массив клеток,
|
||||
ширину, высоту, ссылки на стартовую и выходную клетку.
|
||||
Методы:
|
||||
getCell(x, y), getNeighbors(cell) – возвращает список соседних проходимых клеток
|
||||
(вверх, вниз, влево, вправо, если в пределах границ и не стена)."""
|
||||
|
||||
def __init__(self, mazeArray: list[list[Cell]], start: dict, end: dict, name:str="") -> None:
|
||||
self.mazeArray = mazeArray
|
||||
self.height = len(mazeArray) # X
|
||||
self.width = len(mazeArray[0]) # Y
|
||||
|
||||
self.startCell = self.getCell(start['x'], start['y'])
|
||||
self.endCell = self.getCell(end['x'], end['y'])
|
||||
self.name = name
|
||||
|
||||
def getCell(self, x: int, y: int):
|
||||
return self.mazeArray[y][x]
|
||||
|
||||
def checkCell(self, x: int, y: int):
|
||||
if not(0 <= x and x < self.width):
|
||||
return False
|
||||
if not(0 <= y and y < self.height):
|
||||
return False
|
||||
return self.getCell(x, y).isPassable()
|
||||
|
||||
def getNeighbors(self, cell: Cell):
|
||||
point = (cell.x, cell.y)
|
||||
offsets = ((0, 1),
|
||||
(0, -1),
|
||||
(-1, 0),
|
||||
(1, 0))
|
||||
passableCells = []
|
||||
for ofst in offsets:
|
||||
x = point[0]+ofst[0]
|
||||
y = point[1]+ofst[1]
|
||||
if self.checkCell(x, y):
|
||||
passableCells.append(self.getCell(x, y))
|
||||
return passableCells
|
||||
6
MusinAA/task2/mazeObjects/path.py
Normal file
6
MusinAA/task2/mazeObjects/path.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from task2.mazeObjects.cell import Cell
|
||||
|
||||
class Path:
|
||||
def __init__(self, array:list[Cell]|None, visited_cells:int):
|
||||
self.array = array
|
||||
self.visited_cells = visited_cells
|
||||
66
MusinAA/task2/mazeSolver.py
Normal file
66
MusinAA/task2/mazeSolver.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.observerSubject import MazeEvent, MazeEventType, Subject
|
||||
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
|
||||
from task2.strategyObjects.BFS import BFS
|
||||
|
||||
|
||||
import time
|
||||
|
||||
class SearchStats:
|
||||
maze_name:str = "None"
|
||||
"""Время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути"""
|
||||
def __init__(self, path: list[Cell]|None, duration:float, visited_cells:int, path_len:int, strategy_name:str):
|
||||
self.duration = duration
|
||||
self.visited_cells = visited_cells
|
||||
self.path_len = path_len
|
||||
self.path = path
|
||||
self.strategy_name = strategy_name
|
||||
|
||||
def toDict(self,):
|
||||
return {
|
||||
"strategy_name" : self.strategy_name,
|
||||
"maze_name" : self.maze_name,
|
||||
"duration" : self.duration,
|
||||
"visited_cells" : self.visited_cells,
|
||||
"path_len" : self.path_len
|
||||
}
|
||||
|
||||
class MazeSolver(Subject):
|
||||
"""
|
||||
MazeSolver содержит поля maze и strategy.
|
||||
Метод setStrategy(strategy) для динамической смены алгоритма.
|
||||
Метод solve() вызывает strategy.findPath(...) и возвращает объект SearchStats (время выполнения в миллисекундах,
|
||||
количество посещённых клеток, длина найденного пути).
|
||||
Для замера времени используйте time.perf_counter() до и после вызова стратегии.
|
||||
"""
|
||||
|
||||
def __init__(self, strategy:PathFindingStrategy, maze:Maze|None=None):
|
||||
super().__init__()
|
||||
self._maze = maze
|
||||
self.strategy = strategy
|
||||
|
||||
def setMaze(self, maze: Maze|None):
|
||||
self._maze = maze
|
||||
self.notify(MazeEvent(MazeEventType.MAZE_LOADED, data=maze))
|
||||
|
||||
def setStrategy(self, strategy:PathFindingStrategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def getStrategyName(self):
|
||||
return self.strategy.__class__.__name__
|
||||
|
||||
def solve(self):
|
||||
if not self._maze:
|
||||
raise ValueError
|
||||
|
||||
t_start = time.perf_counter()
|
||||
path = self.strategy.findPath(self._maze, self._maze.startCell, self._maze.endCell)
|
||||
duration = (time.perf_counter() - t_start) * 1000
|
||||
|
||||
path_len = len(path.array) if path.array else -1
|
||||
strategy_name = self.getStrategyName()
|
||||
|
||||
stats = SearchStats(path.array, duration, path.visited_cells, path_len, strategy_name)
|
||||
self.notify(MazeEvent(MazeEventType.PATH_FOUND, data=path))
|
||||
return stats
|
||||
43
MusinAA/task2/observerSubject.py
Normal file
43
MusinAA/task2/observerSubject.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""
|
||||
Создать интерфейс Observer с методом update(event),
|
||||
где event может быть строкой или объектом с типом события ("path_found", "move", "maze_loaded").
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
class MazeEventType(Enum):
|
||||
PATH_FOUND = "path_found"
|
||||
MOVE = "move"
|
||||
MAZE_LOADED = "maze_loaded"
|
||||
|
||||
class MazeEvent:
|
||||
data=None
|
||||
def __init__(self, evtype: MazeEventType, data=None):
|
||||
if not isinstance(evtype, MazeEventType):
|
||||
raise TypeError(f"evtype must be an EventType, got {type(evtype)}")
|
||||
self.evtype = evtype
|
||||
self.data = data
|
||||
|
||||
class Observer(ABC):
|
||||
@abstractmethod
|
||||
def update(self, event: MazeEvent):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Subject(ABC):
|
||||
"""Издатель: управляет подписчиками и отправляет им уведомления."""
|
||||
def __init__(self):
|
||||
self._observers:set[Observer] = set()
|
||||
|
||||
def attach(self, obs:Observer):
|
||||
"Подписать наблюдателя"
|
||||
self._observers.add(obs)
|
||||
|
||||
def detach(self, obs:Observer):
|
||||
"Отписать наблюдателя"
|
||||
self._observers.discard(obs)
|
||||
|
||||
def notify(self, event:MazeEvent):
|
||||
for obs in self._observers:
|
||||
obs.update(event)
|
||||
46
MusinAA/task2/strategyObjects/AStar.py
Normal file
46
MusinAA/task2/strategyObjects/AStar.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import heapq
|
||||
from itertools import count
|
||||
|
||||
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
|
||||
from task2.strategyObjects.util import restorePath
|
||||
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.mazeObjects.path import Path
|
||||
|
||||
class AStar(PathFindingStrategy):
|
||||
"""Алгоритм с эвристикой (etc. манхэттенское расстояние) – компромисс между скоростью и оптимальностью."""
|
||||
def heuristic(self, first: Cell, second: Cell) -> int:
|
||||
return abs(first.x - second.x) + abs(first.y - second.y)
|
||||
|
||||
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
|
||||
tie_breaker = count()
|
||||
start_heuristic = self.heuristic(start, exit)
|
||||
heap: list[tuple[int, int, int, Cell]] = [
|
||||
(start_heuristic, start_heuristic, next(tie_breaker), start)
|
||||
]
|
||||
g_score: dict[Cell, int] = {start: 0}
|
||||
parents: dict[Cell, Cell | None] = {start: None}
|
||||
visited: set[Cell] = set()
|
||||
|
||||
while heap:
|
||||
_, _, _, current = heapq.heappop(heap)
|
||||
if current in visited:
|
||||
continue
|
||||
visited.add(current)
|
||||
|
||||
if current.isExit:
|
||||
return Path(restorePath(parents, exit), len(visited))
|
||||
|
||||
for neighbor in maze.getNeighbors(current):
|
||||
tentative_score = g_score[current]
|
||||
if tentative_score < g_score.get(neighbor, 10**12):
|
||||
g_score[neighbor] = tentative_score
|
||||
parents[neighbor] = current
|
||||
heuristic = self.heuristic(neighbor, exit)
|
||||
priority = tentative_score + heuristic
|
||||
heapq.heappush(
|
||||
heap,
|
||||
(priority, heuristic, next(tie_breaker), neighbor),
|
||||
)
|
||||
return Path(None, len(visited))
|
||||
43
MusinAA/task2/strategyObjects/BFS.py
Normal file
43
MusinAA/task2/strategyObjects/BFS.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
|
||||
from task2.strategyObjects.util import restorePath
|
||||
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.mazeObjects.path import Path
|
||||
|
||||
import queue
|
||||
|
||||
class BFS(PathFindingStrategy):
|
||||
"""Поиск в ширину – гарантирует кратчайший путь по количеству шагов.
|
||||
Возвращает None, если пути нет"""
|
||||
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
|
||||
visited = dict()
|
||||
parents = dict()
|
||||
q = queue.Queue()
|
||||
|
||||
q.put(start)
|
||||
visited[start] = 0
|
||||
parents[start] = None
|
||||
|
||||
found_exit = False
|
||||
while not q.empty():
|
||||
current = q.get()
|
||||
|
||||
# Условие нахождение выхода
|
||||
if current.isExit:
|
||||
found_exit = True
|
||||
break
|
||||
|
||||
# Перебор соседей
|
||||
for hood in maze.getNeighbors(current):
|
||||
if hood in visited:
|
||||
continue
|
||||
visited[hood] = visited[current] + 1
|
||||
parents[hood] = current
|
||||
q.put(hood)
|
||||
|
||||
if not found_exit:
|
||||
path_list = None
|
||||
else:
|
||||
path_list = restorePath(parents, exit)
|
||||
return Path(path_list, len(visited))
|
||||
41
MusinAA/task2/strategyObjects/DFS.py
Normal file
41
MusinAA/task2/strategyObjects/DFS.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from task2.strategyObjects.pathFindingStrategy import PathFindingStrategy
|
||||
from task2.strategyObjects.util import restorePath
|
||||
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.mazeObjects.path import Path
|
||||
|
||||
class DFS(PathFindingStrategy):
|
||||
"""Поиск в глубину – быстрый, но не обязательно кратчайший.
|
||||
Возвращает None, если пути нет"""
|
||||
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
|
||||
visited = dict()
|
||||
parents = dict()
|
||||
stack = []
|
||||
|
||||
stack.append(start)
|
||||
visited[start] = 0
|
||||
parents[start] = None
|
||||
|
||||
found_exit = False
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
|
||||
# Условие нахождение выхода
|
||||
if current.isExit:
|
||||
found_exit = True
|
||||
break
|
||||
|
||||
# Перебор соседей
|
||||
for hood in maze.getNeighbors(current):
|
||||
if hood in visited:
|
||||
continue
|
||||
visited[hood] = visited[current] + 1
|
||||
parents[hood] = current
|
||||
stack.append(hood)
|
||||
|
||||
if not found_exit:
|
||||
path_list = None
|
||||
else:
|
||||
path_list = restorePath(parents, exit)
|
||||
return Path(path_list, len(visited))
|
||||
0
MusinAA/task2/strategyObjects/__init__.py
Normal file
0
MusinAA/task2/strategyObjects/__init__.py
Normal file
14
MusinAA/task2/strategyObjects/pathFindingStrategy.py
Normal file
14
MusinAA/task2/strategyObjects/pathFindingStrategy.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
from task2.mazeObjects.path import Path
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
"""Интерфейс PathFindingStrategy с методом findPath(maze, start, exit),
|
||||
возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет."""
|
||||
|
||||
@abstractmethod
|
||||
def findPath(self, maze: Maze, start: Cell, exit: Cell) -> Path:
|
||||
"""Возвращает список клеток пути от старта до выхода включительно. Пути нет - пустой список."""
|
||||
raise NotImplementedError
|
||||
12
MusinAA/task2/strategyObjects/util.py
Normal file
12
MusinAA/task2/strategyObjects/util.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeObjects.cell import Cell
|
||||
|
||||
def restorePath(parents: dict, exit: Cell) -> list[Cell]|None:
|
||||
path = []
|
||||
current = exit
|
||||
while current:
|
||||
path.append(current)
|
||||
if current not in parents:
|
||||
return None
|
||||
current = parents[current]
|
||||
return path[::-1]
|
||||
84
MusinAA/task2/tester.py
Normal file
84
MusinAA/task2/tester.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
from task2.mazeBuilder import MazeBuilder
|
||||
from task2.mazeObjects.maze import Maze
|
||||
from task2.mazeSolver import MazeSolver, SearchStats
|
||||
|
||||
from task2.strategyObjects.BFS import BFS
|
||||
from task2.strategyObjects.DFS import DFS
|
||||
from task2.strategyObjects.AStar import AStar
|
||||
|
||||
import csv
|
||||
import os
|
||||
|
||||
TEST_ITERATIONS = 10
|
||||
|
||||
class Tester():
|
||||
"""Для каждого лабиринта и каждой стратегии запустить solve() 5–10 раз,
|
||||
усреднить время, количество посещённых клеток, длину пути.
|
||||
Записать результаты в CSV:
|
||||
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути."""
|
||||
result:list[SearchStats]
|
||||
def __init__(self, builder:MazeBuilder, writefile:str):
|
||||
self._builder = builder
|
||||
self.writefile = "../" + writefile
|
||||
|
||||
def setTestingDirectory(self, directory:str):
|
||||
if directory[-1] != "/":
|
||||
directory += "/"
|
||||
self._directory = "../" + directory
|
||||
|
||||
def _getMazes(self) -> list[Maze]:
|
||||
arr = []
|
||||
files = os.listdir(self._directory)
|
||||
only_txt_files = [f for f in files if os.path.isfile(os.path.join(self._directory, f)) and os.path.splitext(f)[1] == ".txt"]
|
||||
|
||||
for f in only_txt_files:
|
||||
arr.append(self._builder.buildFromFile(os.path.join(self._directory, f)))
|
||||
return arr
|
||||
|
||||
def _solveAvg(self, solver: MazeSolver):
|
||||
avgtime = 0
|
||||
for i in range(TEST_ITERATIONS):
|
||||
result = solver.solve()
|
||||
# Всё кроме времени будет одинаковым
|
||||
avgtime += result.duration/TEST_ITERATIONS
|
||||
result.duration = avgtime
|
||||
return result
|
||||
|
||||
def saveCSV(self):
|
||||
rows = []
|
||||
for r in self.result:
|
||||
r = r.toDict()
|
||||
row = (r["strategy_name"],
|
||||
r["maze_name"],
|
||||
r["duration"],
|
||||
r["visited_cells"],
|
||||
r["path_len"]
|
||||
)
|
||||
rows.append(row)
|
||||
with open(self.writefile, "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(["Алгоритм", "Лабиринт", "Время (мс)", "Посещённые клетки", "Длинна пути"])
|
||||
writer.writerows(rows)
|
||||
|
||||
def test(self):
|
||||
self.result = []
|
||||
arr = self._getMazes()
|
||||
for algoritm in (BFS, DFS, AStar):
|
||||
solver = MazeSolver(algoritm()) # это прикол
|
||||
for maze in arr:
|
||||
solver.setMaze(maze)
|
||||
self.result.append(self._solveAvg(solver))
|
||||
self.result[-1].maze_name = maze.name
|
||||
return self.result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit()
|
||||
from task2.mazeBuilder import TextFileMazeBuilder
|
||||
|
||||
builder = TextFileMazeBuilder()
|
||||
tester = Tester(builder, "docs/data/task2/results.csv")
|
||||
tester.setTestingDirectory("task2/mazeExamples")
|
||||
tester.test()
|
||||
tester.saveCSV()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user