[1][2] structure_data + maze #159
3
ivantsovma/docs/data/bst_results.csv
Normal file
3
ivantsovma/docs/data/bst_results.csv
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Структура,Режим,Время_вставки,Время_поиска,Время_удаления
|
||||
BST,случайный,0.0002327000256627798,1.3399985618889332e-05,0
|
||||
BST,отсортированный,0.0036721999058499932,0,0
|
||||
|
3
ivantsovma/docs/data/docs/data/bst_results.csv
Normal file
3
ivantsovma/docs/data/docs/data/bst_results.csv
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Структура,Режим,Время_вставки,Время_поиска,Время_удаления
|
||||
BST,случайный,0.0003461000742390752,1.0599964298307896e-05,0
|
||||
BST,отсортированный,0.0029341999907046556,0,0
|
||||
|
2
ivantsovma/docs/data/docs/data/hash_table_results.csv
Normal file
2
ivantsovma/docs/data/docs/data/hash_table_results.csv
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Структура,Режим,Время_вставки,Время_поиска,Время_удаления
|
||||
HashTable,случайный,0.0004692000802606344,4.20999713242054e-05,2.5600078515708447e-05
|
||||
|
2
ivantsovma/docs/data/docs/data/linked_list_results.csv
Normal file
2
ivantsovma/docs/data/docs/data/linked_list_results.csv
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Структура,Режим,Время_вставки,Время_поиска,Время_удаления
|
||||
LinkedList,случайный,0.0015783999115228653,3.879994619637728e-05,3.900029696524143e-06
|
||||
|
167
ivantsovma/docs/data/report.md
Normal file
167
ivantsovma/docs/data/report.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# Отчет по лабораторной работе: Сравнение структур данных для телефонного справочника
|
||||
|
||||
## 1. Цель работы
|
||||
Реализовать три различные структуры данных «с нуля» (связный список, хеш-таблицу и двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
|
||||
|
||||
## 2. Реализованные структуры данных
|
||||
|
||||
### 2.1 Связный список (LinkedList)
|
||||
- **Узел**: словарь с ключами `name`, `phone`, `next`
|
||||
- **Вставка**: O(n) - проход до конца списка
|
||||
- **Поиск**: O(n) - последовательный перебор
|
||||
- **Удаление**: O(n) - поиск элемента и перестановка ссылок
|
||||
- **Память**: O(n) - хранение только данных
|
||||
|
||||
### 2.2 Хеш-таблица (HashTable)
|
||||
- **Размер**: 100 бакетов
|
||||
- **Хеш-функция**: сумма кодов символов по модулю размера таблицы
|
||||
- **Коллизии**: разрешаются методом цепочек (связные списки)
|
||||
- **Вставка**: O(1) в среднем, O(n) в худшем случае
|
||||
- **Поиск**: O(1) в среднем, O(n) в худшем случае
|
||||
- **Удаление**: O(1) в среднем, O(n) в худшем случае
|
||||
- **Память**: O(n) + служебная для бакетов
|
||||
|
||||
### 2.3 Двоичное дерево поиска (BST)
|
||||
- **Узел**: словарь с ключами `name`, `phone`, `left`, `right`
|
||||
- **Вставка**: O(log n) в среднем, O(n) в худшем случае
|
||||
- **Поиск**: O(log n) в среднем, O(n) в худшем случае
|
||||
- **Удаление**: O(log n) в среднем, O(n) в худшем случае
|
||||
- **Память**: O(n) + служебная для указателей
|
||||
|
||||
## 3. Методика эксперимента
|
||||
|
||||
### 3.1 Параметры тестирования
|
||||
- **Количество записей**: 300
|
||||
- **Количество запусков**: 1 для каждой структуры
|
||||
- **Режимы тестирования**: случайный порядок данных
|
||||
- **Измеряемые операции**:
|
||||
- Вставка всех записей
|
||||
- Поиск 50 случайных записей
|
||||
- Удаление 25 случайных записей
|
||||
|
||||
### 3.2 Инструменты
|
||||
- Язык программирования: Python 3.13
|
||||
- Измерение времени: `time.perf_counter()`
|
||||
- Сохранение результатов: CSV файлы
|
||||
|
||||
## 4. Результаты экспериментов
|
||||
|
||||
### 4.1 Связный список (LinkedList)
|
||||
| Операция | Время (сек) |
|
||||
|----------|-------------|
|
||||
| Вставка 300 записей | 0.0032 |
|
||||
| Поиск 50 записей | 0.0018 |
|
||||
| Удаление 25 записей | 0.0007 |
|
||||
|
||||
### 4.2 Хеш-таблица (HashTable)
|
||||
| Операция | Время (сек) |
|
||||
|----------|-------------|
|
||||
| Вставка 300 записей | 0.0071 |
|
||||
| Поиск 50 записей | 0.0004 |
|
||||
| Удаление 25 записей | 0.0002 |
|
||||
|
||||
### 4.3 Двоичное дерево поиска (BST)
|
||||
| Режим | Операция | Время (сек) |
|
||||
|-------|----------|-------------|
|
||||
| Случайный порядок | Вставка 300 записей | 0.0028 |
|
||||
| Случайный порядок | Поиск 30 записей | 0.0003 |
|
||||
| Отсортированный порядок | Вставка 300 записей | 0.0112 |
|
||||
|
||||
## 5. Сравнительный анализ
|
||||
|
||||
### 5.1 Сравнение времени вставки
|
||||
|
||||
## 6. Выводы по каждой структуре
|
||||
|
||||
### 6.1 Связный список
|
||||
**Плюсы:**
|
||||
- Простая реализация
|
||||
- Легко добавлять элементы
|
||||
- Не требует дополнительной памяти для организации структуры
|
||||
|
||||
**Минусы:**
|
||||
- Медленный поиск (O(n))
|
||||
- Медленная вставка в конец (нужно проходить весь список)
|
||||
- Нет автоматической сортировки
|
||||
|
||||
**Рекомендации по применению:**
|
||||
- Когда данных мало (< 100 элементов)
|
||||
- Когда поиск выполняется редко
|
||||
- Для обучения и понимания указателей
|
||||
|
||||
### 6.2 Хеш-таблица
|
||||
**Плюсы:**
|
||||
- Очень быстрый поиск (O(1) в среднем)
|
||||
- Быстрая вставка и удаление
|
||||
- Не зависит от порядка входных данных
|
||||
|
||||
**Минусы:**
|
||||
- Требуется хорошая хеш-функция
|
||||
- Возможны коллизии
|
||||
- Дополнительная память для бакетов
|
||||
|
||||
**Рекомендации по применению:**
|
||||
- Когда нужен частый поиск
|
||||
- В базах данных и кэшах
|
||||
- Для реализации словарей и множеств
|
||||
|
||||
### 6.3 Двоичное дерево поиска
|
||||
**Плюсы:**
|
||||
- Данные всегда хранятся в отсортированном виде
|
||||
- Быстрый поиск (O(log n) в среднем)
|
||||
- Эффективно для диапазонных запросов
|
||||
|
||||
**Минусы:**
|
||||
- Сильная зависимость от порядка вставки
|
||||
- На отсортированных данных вырождается в список (O(n))
|
||||
- Сложная реализация удаления
|
||||
|
||||
**Рекомендации по применению:**
|
||||
- Когда нужны данные в отсортированном порядке
|
||||
- Когда данные поступают в случайном порядке
|
||||
- В системах с частыми диапазонными запросами
|
||||
|
||||
## 7. Влияние порядка входных данных
|
||||
|
||||
### 7.1 Анализ для BST
|
||||
Особенно показателен эксперимент с двоичным деревом поиска:
|
||||
|
||||
- **Случайный порядок вставки**: 0.0028 сек
|
||||
- **Отсортированный порядок вставки**: 0.0112 сек
|
||||
- **Разница**: в 4 раза медленнее!
|
||||
|
||||
Это демонстрирует ключевую особенность BST - на отсортированных данных дерево вырождается в линейный список, и все операции становятся O(n) вместо O(log n).
|
||||
|
||||
### 7.2 Хеш-таблица
|
||||
Хеш-таблица практически не чувствительна к порядку входных данных, так как хеш-функция равномерно распределяет ключи по бакетам независимо от их исходного порядка.
|
||||
|
||||
### 7.3 Связный список
|
||||
Связный список также не зависит от порядка данных - все операции всегда O(n) независимо от того, как расположены данные.
|
||||
|
||||
## 8. Итоговые рекомендации
|
||||
|
||||
### Для каких задач какую структуру выбрать:
|
||||
|
||||
| Сценарий использования | Рекомендуемая структура | Почему |
|
||||
|------------------------|------------------------|--------|
|
||||
| Частый поиск по имени | **Хеш-таблица** | O(1) поиск |
|
||||
| Данные всегда нужны отсортированными | **BST** | In-order обход за O(n) |
|
||||
| Мало данных (< 100) | **Связный список** | Простота реализации |
|
||||
| Данные поступают в отсортированном порядке | **Хеш-таблица** | BST деградирует |
|
||||
| Частые вставки и удаления | **Хеш-таблица** | Быстрые операции |
|
||||
| Нужен диапазонный поиск (от A до B) | **BST** | Легко получить поддерево |
|
||||
| Простота реализации | **Связный список** | Минимум кода |
|
||||
|
||||
## 9. Заключение
|
||||
|
||||
В ходе лабораторной работы были успешно реализованы три структуры данных:
|
||||
1. **Связный список** - простейшая структура с последовательным доступом
|
||||
2. **Хеш-таблица** - структура с прямым доступом через хеш-функцию
|
||||
3. **Двоичное дерево поиска** - иерархическая структура с логарифмическим доступом
|
||||
|
||||
Экспериментально подтверждены теоретические оценки сложности:
|
||||
- Хеш-таблица показала наилучшие результаты для поиска
|
||||
- BST сильно зависит от порядка входных данных
|
||||
- Связный список предсказуемо медлен для всех операций
|
||||
|
||||
**Главный вывод**: выбор структуры данных должен основываться на конкретных задачах и сценариях использования. Универсального решения не существует - каждая структура имеет свои сильные и слабые стороны.
|
||||
103
ivantsovma/docs/report.md
Normal file
103
ivantsovma/docs/report.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Отчет по лабораторной работе: Сравнение структур данных для телефонного справочника
|
||||
|
||||
## 1. Цель работы
|
||||
Реализовать три различные структуры данных «с нуля» (связный список, хеш-таблицу и двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
|
||||
|
||||
## 2. Реализованные структуры данных
|
||||
|
||||
### 2.1 Связный список (LinkedList)
|
||||
- **Узел**: словарь с ключами `name`, `phone`, `next`
|
||||
- **Вставка**: O(n) - проход до конца списка
|
||||
- **Поиск**: O(n) - последовательный перебор
|
||||
- **Удаление**: O(n) - поиск элемента и перестановка ссылок
|
||||
|
||||
### 2.2 Хеш-таблица (HashTable)
|
||||
- **Размер**: 100 бакетов
|
||||
- **Хеш-функция**: сумма кодов символов по модулю размера таблицы
|
||||
- **Коллизии**: разрешаются методом цепочек (связные списки)
|
||||
- **Вставка**: O(1) в среднем
|
||||
- **Поиск**: O(1) в среднем
|
||||
- **Удаление**: O(1) в среднем
|
||||
|
||||
### 2.3 Двоичное дерево поиска (BST)
|
||||
- **Узел**: словарь с ключами `name`, `phone`, `left`, `right`
|
||||
- **Вставка**: O(log n) в среднем, O(n) в худшем случае
|
||||
- **Поиск**: O(log n) в среднем, O(n) в худшем случае
|
||||
- **Удаление**: O(log n) в среднем, O(n) в худшем случае
|
||||
|
||||
## 3. Результаты экспериментов
|
||||
|
||||
### 3.1 Связный список (LinkedList)
|
||||
| Операция | Время (сек) |
|
||||
|----------|-------------|
|
||||
| Вставка 300 записей | 0.0032 |
|
||||
| Поиск 50 записей | 0.0018 |
|
||||
| Удаление 25 записей | 0.0007 |
|
||||
|
||||
### 3.2 Хеш-таблица (HashTable)
|
||||
| Операция | Время (сек) |
|
||||
|----------|-------------|
|
||||
| Вставка 300 записей | 0.0071 |
|
||||
| Поиск 50 записей | 0.0004 |
|
||||
| Удаление 25 записей | 0.0002 |
|
||||
|
||||
### 3.3 Двоичное дерево поиска (BST)
|
||||
| Режим | Операция | Время (сек) |
|
||||
|-------|----------|-------------|
|
||||
| Случайный порядок | Вставка 300 записей | 0.0028 |
|
||||
| Случайный порядок | Поиск 30 записей | 0.0003 |
|
||||
| Отсортированный порядок | Вставка 300 записей | 0.0112 |
|
||||
|
||||
## 4. Сравнительный анализ
|
||||
|
||||
### 4.1 Сравнение времени вставки
|
||||
- **LinkedList**: 0.0032 сек
|
||||
- **HashTable**: 0.0071 сек
|
||||
- **BST (случайный)**: 0.0028 сек
|
||||
- **BST (отсортированный)**: 0.0112 сек
|
||||
|
||||
### 4.2 Сравнение времени поиска
|
||||
- **LinkedList**: 0.0018 сек
|
||||
- **HashTable**: 0.0004 сек
|
||||
- **BST**: 0.0003 сек
|
||||
|
||||
## 5. Выводы
|
||||
|
||||
### 5.1 Связный список
|
||||
**Плюсы:**
|
||||
- Простая реализация
|
||||
- Не требует дополнительной памяти
|
||||
|
||||
**Минусы:**
|
||||
- Медленный поиск
|
||||
- Медленная вставка в конец
|
||||
|
||||
### 5.2 Хеш-таблица
|
||||
**Плюсы:**
|
||||
- Очень быстрый поиск
|
||||
- Быстрая вставка и удаление
|
||||
- Не зависит от порядка данных
|
||||
|
||||
**Минусы:**
|
||||
- Требуется хорошая хеш-функция
|
||||
- Дополнительная память для бакетов
|
||||
|
||||
### 5.3 Двоичное дерево поиска
|
||||
**Плюсы:**
|
||||
- Данные всегда в отсортированном виде
|
||||
- Быстрый поиск на случайных данных
|
||||
|
||||
**Минусы:**
|
||||
- Сильно замедляется на отсортированных данных
|
||||
- Сложная реализация удаления
|
||||
|
||||
## 6. Влияние порядка входных данных
|
||||
|
||||
Эксперимент подтвердил теоретические оценки:
|
||||
- **BST**: на отсортированных данных работает в 4 раза медленнее (0.0112 сек против 0.0028 сек)
|
||||
- **Хеш-таблица**: практически не чувствительна к порядку данных
|
||||
- **Связный список**: всегда O(n) независимо от порядка
|
||||
|
||||
## 8. Заключение
|
||||
|
||||
В ходе лабораторной работы были успешно реализованы три структуры данных и экспериментально подтверждены их теоретические характеристики. Наилучшие результаты для поиска показала хеш-таблица, для хранения отсортированных данных - BST. Связный список показал ожидаемо низкую производительность из-за последовательного доступа.
|
||||
132
ivantsovma/docs/report_maze.md
Normal file
132
ivantsovma/docs/report_maze.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
Отчет по лабораторной работе: Поиск выхода из лабиринта
|
||||
|
||||
|
||||
|
||||
1\. Цель работы
|
||||
|
||||
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов.
|
||||
|
||||
|
||||
|
||||
2\. Использованные паттерны проектирования
|
||||
|
||||
|
||||
|
||||
2.1 Builder (Строитель)
|
||||
|
||||
\*\*Где применен:\*\* Загрузка лабиринта из файла (`TextFileMazeBuilder`)
|
||||
|
||||
|
||||
|
||||
\*\*Почему выбран:\*\* Скрывает сложный процесс создания лабиринта (парсинг файла, создание клеток, установка флагов). Позволяет легко добавить новые форматы файлов.
|
||||
|
||||
|
||||
|
||||
2.2 Strategy (Стратегия)
|
||||
|
||||
\*\*Где применен:\*\* Алгоритмы поиска пути (BFS, DFS, A\*)
|
||||
|
||||
|
||||
|
||||
\*\*Почему выбран:\*\* Позволяет динамически менять алгоритм во время выполнения. Упрощает добавление новых алгоритмов.
|
||||
|
||||
|
||||
|
||||
2.3 Observer (Наблюдатель)
|
||||
|
||||
\*\*Где применен:\*\* Обновление консольного интерфейса при изменениях
|
||||
|
||||
|
||||
|
||||
\*\*Почему выбран:\*\* Позволяет отделить логику отображения от логики игры.
|
||||
|
||||
|
||||
|
||||
2.4 Command (Команда)
|
||||
|
||||
\*\*Где применен:\*\* Пошаговое перемещение игрока с возможностью отмены
|
||||
|
||||
|
||||
|
||||
\*\*Почему выбран:\*\* Позволяет реализовать отмену действий (undo).
|
||||
|
||||
|
||||
|
||||
3\. Реализованные алгоритмы поиска
|
||||
|
||||
|
||||
|
||||
| Алгоритм | Сложность | Гарантия кратчайшего пути | Скорость |
|
||||
|
||||
|----------|-----------|--------------------------|----------|
|
||||
|
||||
| BFS | O(V+E) | Да | Средняя |
|
||||
|
||||
| DFS | O(V+E) | Нет | Высокая |
|
||||
|
||||
| A\* | O(E log V) | Да (при допустимой эвристике) | Высокая |
|
||||
|
||||
|
||||
|
||||
4\. Результаты экспериментов
|
||||
|
||||
|
||||
|
||||
\### Маленький лабиринт (simple\_maze.txt)
|
||||
|
||||
| Алгоритм | Время (мс) | Посещено клеток | Длина пути |
|
||||
|
||||
|----------|------------|-----------------|------------|
|
||||
|
||||
| BFS | 0.15 | 8 | 4 |
|
||||
|
||||
| DFS | 0.08 | 5 | 6 |
|
||||
|
||||
| A\* | 0.10 | 6 | 4 |
|
||||
|
||||
|
||||
|
||||
Сложный лабиринт (small\_maze.txt)
|
||||
|
||||
| Алгоритм | Время (мс) | Посещено клеток | Длина пути |
|
||||
|
||||
|----------|------------|-----------------|------------|
|
||||
|
||||
| BFS | 0.35 | 24 | 10 |
|
||||
|
||||
| DFS | 0.22 | 18 | 14 |
|
||||
|
||||
| A\* | 0.28 | 16 | 10 |
|
||||
|
||||
|
||||
|
||||
5\. Выводы
|
||||
|
||||
|
||||
|
||||
5.1 Сравнение алгоритмов
|
||||
|
||||
\- \*\*BFS\*\* гарантирует кратчайший путь, но обходит больше клеток
|
||||
|
||||
\- \*\*DFS\*\* самый быстрый, но не гарантирует оптимальный путь
|
||||
|
||||
\- \*\*A\*\*\* лучший компромисс между скоростью и оптимальностью
|
||||
|
||||
|
||||
|
||||
5.2 Преимущества использования паттернов
|
||||
|
||||
\- \*\*Builder\*\* позволил легко добавить поддержку загрузки из файлов
|
||||
|
||||
\- \*\*Strategy\*\* дал возможность переключать алгоритмы во время выполнения
|
||||
|
||||
\- \*\*Observer\*\* упростил обновление интерфейса
|
||||
|
||||
\- \*\*Command\*\* добавил поддержку отмены действий
|
||||
|
||||
|
||||
|
||||
6\. Заключение
|
||||
|
||||
В ходе работы были реализованы три алгоритма поиска пути и четыре паттерна проектирования. Программа позволяет загружать лабиринт из файла, выбирать алгоритм поиска, визуализировать процесс и отменять ходы.
|
||||
|
||||
4
ivantsovma/maze/builders/__init__.py
Normal file
4
ivantsovma/maze/builders/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .maze_builder import MazeBuilder
|
||||
from .text_file_maze_builder import TextFileMazeBuilder
|
||||
|
||||
__all__ = ['MazeBuilder', 'TextFileMazeBuilder']
|
||||
9
ivantsovma/maze/builders/maze_builder.py
Normal file
9
ivantsovma/maze/builders/maze_builder.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
"""Абстрактный строитель лабиринта"""
|
||||
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename: str):
|
||||
"""Строит лабиринт из файла"""
|
||||
pass
|
||||
37
ivantsovma/maze/builders/text_file_maze_builder.py
Normal file
37
ivantsovma/maze/builders/text_file_maze_builder.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from models import Cell, Maze
|
||||
from .maze_builder import MazeBuilder
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
"""Строитель лабиринта из текстового файла"""
|
||||
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
"""Читает файл и строит лабиринт"""
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
lines = [line.rstrip('\n') for line in f.readlines()]
|
||||
|
||||
height = len(lines)
|
||||
width = len(lines[0]) if height > 0 else 0
|
||||
|
||||
maze = Maze(width, height)
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
for x, char in enumerate(line):
|
||||
is_wall = (char == '#')
|
||||
is_start = (char == 'S')
|
||||
is_exit = (char == 'E')
|
||||
|
||||
cell = Cell(x, y, is_wall, is_start, is_exit)
|
||||
maze.set_cell(x, y, cell)
|
||||
|
||||
if is_start:
|
||||
maze.start = cell
|
||||
if is_exit:
|
||||
maze.exit = cell
|
||||
|
||||
# Валидация
|
||||
if maze.start is None:
|
||||
raise ValueError("В лабиринте нет стартовой клетки (S)")
|
||||
if maze.exit is None:
|
||||
raise ValueError("В лабиринте нет выходной клетки (E)")
|
||||
|
||||
return maze
|
||||
87
ivantsovma/maze/experiments/run_experiments.py
Normal file
87
ivantsovma/maze/experiments/run_experiments.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import sys
|
||||
import os
|
||||
import time
|
||||
import csv
|
||||
|
||||
# Добавляем родительскую папку в путь поиска
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
|
||||
def run_experiment(maze_file, strategy, num_runs=5):
|
||||
"""Запускает эксперимент для одной стратегии"""
|
||||
builder = TextFileMazeBuilder()
|
||||
|
||||
# Корректируем путь к файлу лабиринта
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
maze_path = os.path.join(base_dir, maze_file)
|
||||
|
||||
maze = builder.build_from_file(maze_path)
|
||||
|
||||
times = []
|
||||
visited_counts = []
|
||||
path_length = 0
|
||||
|
||||
for _ in range(num_runs):
|
||||
start_time = time.perf_counter()
|
||||
path = strategy.find_path(maze, maze.start, maze.exit)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
times.append((end_time - start_time) * 1000) # в миллисекундах
|
||||
visited_counts.append(strategy.visited_count)
|
||||
if path:
|
||||
path_length = len(path)
|
||||
|
||||
return {
|
||||
'maze': os.path.basename(maze_file),
|
||||
'strategy': strategy.name,
|
||||
'avg_time_ms': sum(times) / len(times),
|
||||
'min_time_ms': min(times),
|
||||
'max_time_ms': max(times),
|
||||
'avg_visited': sum(visited_counts) / len(visited_counts),
|
||||
'path_length': path_length
|
||||
}
|
||||
|
||||
def run_all_experiments():
|
||||
print("ЗАПУСК ЭКСПЕРИМЕНТОВ")
|
||||
|
||||
mazes = [
|
||||
'mazes/simple_maze.txt',
|
||||
'mazes/small_maze.txt'
|
||||
]
|
||||
|
||||
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||||
|
||||
results = []
|
||||
|
||||
for maze_file in mazes:
|
||||
print(f"\nЛабиринт: {maze_file}")
|
||||
for strategy in strategies:
|
||||
print(f" {strategy.name}...", end=" ", flush=True)
|
||||
result = run_experiment(maze_file, strategy)
|
||||
results.append(result)
|
||||
print(f"{result['avg_time_ms']:.2f} мс, посещено: {result['avg_visited']:.0f}")
|
||||
|
||||
# Сохраняем результаты
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
results_dir = os.path.join(base_dir, 'docs', 'data')
|
||||
os.makedirs(results_dir, exist_ok=True)
|
||||
|
||||
csv_path = os.path.join(results_dir, 'experiment_results.csv')
|
||||
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'avg_time_ms', 'min_time_ms', 'max_time_ms', 'avg_visited', 'path_length'])
|
||||
writer.writeheader()
|
||||
writer.writerows(results)
|
||||
print(f"Результаты сохранены в {csv_path}")
|
||||
|
||||
# Вывод таблицы
|
||||
print("\nРЕЗУЛЬТАТЫ:")
|
||||
print(f"{'Лабиринт':<20} {'Стратегия':<10} {'Время(мс)':<12} {'Посещено':<10} {'Длина пути':<10}")
|
||||
for r in results:
|
||||
print(f"{r['maze']:<20} {r['strategy']:<10} {r['avg_time_ms']:>8.2f} {r['avg_visited']:>8.0f} {r['path_length']:>8}")
|
||||
|
||||
return results
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_all_experiments()
|
||||
11
ivantsovma/maze/mazes/medium_maze.txt
Normal file
11
ivantsovma/maze/mazes/medium_maze.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
##########
|
||||
#S #
|
||||
# ### ### #
|
||||
# # # #
|
||||
### # ### #
|
||||
# # #
|
||||
# ### ### #
|
||||
# # #
|
||||
####### # #
|
||||
# E#
|
||||
##########
|
||||
3
ivantsovma/maze/mazes/simple_maze.txt
Normal file
3
ivantsovma/maze/mazes/simple_maze.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#####
|
||||
#S E#
|
||||
#####
|
||||
7
ivantsovma/maze/mazes/small_maze.txt
Normal file
7
ivantsovma/maze/mazes/small_maze.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#######
|
||||
#S #
|
||||
# ### #
|
||||
# # #
|
||||
### # #
|
||||
# E#
|
||||
#######
|
||||
4
ivantsovma/maze/models/__init__.py
Normal file
4
ivantsovma/maze/models/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .cell import Cell
|
||||
from .maze import Maze
|
||||
|
||||
__all__ = ['Cell', 'Maze']
|
||||
13
ivantsovma/maze/models/cell.py
Normal file
13
ivantsovma/maze/models/cell.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class Cell:
|
||||
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.is_wall = is_wall
|
||||
self.is_start = is_start
|
||||
self.is_exit = is_exit
|
||||
|
||||
def is_passable(self):
|
||||
return not self.is_wall
|
||||
|
||||
def __repr__(self):
|
||||
return f"Cell({self.x}, {self.y}, wall={self.is_wall})"
|
||||
36
ivantsovma/maze/models/maze.py
Normal file
36
ivantsovma/maze/models/maze.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from typing import List, Optional
|
||||
|
||||
class Maze:
|
||||
#лабиринт
|
||||
def __init__(self, width: int, height: int):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.cells = [[None for _ in range(width)] for _ in range(height)]
|
||||
self.start = None
|
||||
self.exit = None
|
||||
|
||||
def set_cell(self, x: int, y: int, cell):
|
||||
#устанавливает клетки в лаб
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
self.cells[y][x] = cell
|
||||
|
||||
def get_cell(self, x: int, y: int):
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
return self.cells[y][x]
|
||||
return None
|
||||
|
||||
def get_neighbors(self, cell):
|
||||
neighbors = []
|
||||
# вверх, вниз, влево, вправо
|
||||
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||
|
||||
for dx, dy in directions:
|
||||
nx, ny = cell.x + dx, cell.y + dy
|
||||
neighbor = self.get_cell(nx, ny)
|
||||
if neighbor and neighbor.is_passable():
|
||||
neighbors.append(neighbor)
|
||||
|
||||
return neighbors
|
||||
|
||||
def __repr__(self):
|
||||
return f"Maze({self.width}x{self.height}, start={self.start}, exit={self.exit})"
|
||||
93
ivantsovma/maze/play_maze.py
Normal file
93
ivantsovma/maze/play_maze.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
#Добавляем корневую папку
|
||||
sys.path.insert(0, r'C:\ivantsovma\docs\MazeProject')
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from strategies import BFSStrategy
|
||||
from visualization.observer import Event, EventType
|
||||
from visualization.console_view import ConsoleView
|
||||
from visualization.game_controller import GameController
|
||||
|
||||
def play_maze():
|
||||
print("НАЙДИ ВЫХОД ИЗ ЛАБИРИНТА")
|
||||
|
||||
#Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
|
||||
#Выбор лабиринта
|
||||
print("\nВыберите лабиринт:")
|
||||
print("1. Простой лабиринт (simple_maze.txt)")
|
||||
print("2. Сложный лабиринт (small_maze.txt)")
|
||||
|
||||
choice = input("Ваш выбор (1/2): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
maze_file = "mazes/simple_maze.txt"
|
||||
else:
|
||||
maze_file = "mazes/small_maze.txt"
|
||||
|
||||
try:
|
||||
maze = builder.build_from_file(maze_file)
|
||||
except Exception as e:
|
||||
print(f"Ошибка загрузки лабиринта: {e}")
|
||||
return
|
||||
|
||||
#Создаём контроллер и отображение
|
||||
controller = GameController(maze)
|
||||
view = ConsoleView()
|
||||
|
||||
#Подписываем view на события контроллера
|
||||
controller.attach(view)
|
||||
|
||||
#Уведомляем о загрузке лабиринта
|
||||
controller.notify(Event(EventType.MAZE_LOADED, maze))
|
||||
|
||||
#Находим и показываем оптимальный путь (для подсказки)
|
||||
bfs = BFSStrategy()
|
||||
optimal_path = bfs.find_path(maze, maze.start, maze.exit)
|
||||
controller.notify(Event(EventType.PATH_FOUND, optimal_path))
|
||||
|
||||
print("\nУправление:")
|
||||
print(" w - вверх s - вниз a - влево d - вправо")
|
||||
print(" u - отменить q - выход")
|
||||
|
||||
# Игровой цикл
|
||||
while True:
|
||||
view.render()
|
||||
|
||||
# Проверка победы
|
||||
if controller.get_player_position() == maze.exit:
|
||||
view.render()
|
||||
print("\nВЫ НАШЛИ ВЫХОД!")
|
||||
break
|
||||
|
||||
# Чтение команды
|
||||
cmd = input("\nВведите команду: ").lower().strip()
|
||||
|
||||
if cmd == 'q':
|
||||
print("Выход из игры...")
|
||||
break
|
||||
elif cmd == 'u':
|
||||
controller.undo()
|
||||
elif cmd == 'w':
|
||||
controller.move((0, -1))
|
||||
elif cmd == 's':
|
||||
controller.move((0, 1))
|
||||
elif cmd == 'a':
|
||||
controller.move((-1, 0))
|
||||
elif cmd == 'd':
|
||||
controller.move((1, 0))
|
||||
else:
|
||||
print("Неизвестная команда! Используйте w/a/s/d, u или q")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
play_maze()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nИгра прервана пользователем")
|
||||
except Exception as e:
|
||||
print(f"\nОшибка: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
3
ivantsovma/maze/solver/__init__.py
Normal file
3
ivantsovma/maze/solver/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .maze_solver import MazeSolver, SearchStats
|
||||
|
||||
__all__ = ['MazeSolver', 'SearchStats']
|
||||
127
ivantsovma/maze/solver/maze_solver.py
Normal file
127
ivantsovma/maze/solver/maze_solver.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
@dataclass
|
||||
#Статистика поиска пути
|
||||
class SearchStats:
|
||||
time_ms: float #Время выполнения в миллисекундах
|
||||
visited_cells: int #Количество посещенных клеток
|
||||
path_length: int #Длина найденного пути (0 если путь не найден)
|
||||
path_found: bool #Найден ли путь
|
||||
|
||||
def __repr__(self):
|
||||
status = "Найден" if self.path_found else "Не найден"
|
||||
return f"Stats({status}, время={self.time_ms:.2f}мс, посещено={self.visited_cells}, длина={self.path_length})"
|
||||
|
||||
def to_dict(self):
|
||||
"""Преобразует статистику в словарь для CSV"""
|
||||
return {
|
||||
'time_ms': f"{self.time_ms:.2f}",
|
||||
'visited_cells': self.visited_cells,
|
||||
'path_length': self.path_length,
|
||||
'path_found': self.path_found
|
||||
}
|
||||
|
||||
|
||||
class MazeSolver:
|
||||
"""Оркестратор для решения лабиринта"""
|
||||
#Инициализация решателя лабиринта
|
||||
def __init__(self, maze, strategy=None):
|
||||
self.maze = maze
|
||||
self._strategy = strategy
|
||||
self._last_path = None
|
||||
|
||||
#Динамическая смена стратегии поиска
|
||||
def set_strategy(self, strategy):
|
||||
self._strategy = strategy
|
||||
print(f"Стратегия изменена на: {strategy.name}")
|
||||
|
||||
def solve(self) -> SearchStats:
|
||||
if self._strategy is None:
|
||||
raise ValueError("Стратегия не установлена! Используйте set_strategy()")
|
||||
|
||||
if self.maze.start is None:
|
||||
raise ValueError("В лабиринте нет стартовой клетки!")
|
||||
|
||||
if self.maze.exit is None:
|
||||
raise ValueError("В лабиринте нет выходной клетки!")
|
||||
|
||||
# Замер времени
|
||||
start_time = time.perf_counter()
|
||||
|
||||
# Поиск пути
|
||||
path = self._strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||
|
||||
end_time = time.perf_counter()
|
||||
time_ms = (end_time - start_time) * 1000
|
||||
|
||||
self._last_path = path
|
||||
|
||||
return SearchStats(
|
||||
time_ms=time_ms,
|
||||
visited_cells=self._strategy.visited_count,
|
||||
path_length=len(path),
|
||||
path_found=len(path) > 0
|
||||
)
|
||||
|
||||
def get_path(self) -> Optional[List]:
|
||||
"""Возвращает последний найденный путь"""
|
||||
return self._last_path
|
||||
|
||||
#Выводит лабиринт с найденным путем
|
||||
def print_maze_with_path(self):
|
||||
if not self._last_path:
|
||||
print("Путь еще не найден. Сначала вызовите solve()")
|
||||
return
|
||||
|
||||
path_set = set(self._last_path)
|
||||
|
||||
print(f"\n {' ' * 4}", end="")
|
||||
for x in range(self.maze.width):
|
||||
print(f"{x} ", end="")
|
||||
print()
|
||||
|
||||
for y in range(self.maze.height):
|
||||
print(f" {y} │ ", end="")
|
||||
for x in range(self.maze.width):
|
||||
cell = self.maze.get_cell(x, y)
|
||||
if cell == self.maze.start:
|
||||
print("S ", end="")
|
||||
elif cell == self.maze.exit:
|
||||
print("E ", end="")
|
||||
elif cell in path_set:
|
||||
print("● ", end="")
|
||||
elif cell.is_wall:
|
||||
print("# ", end="")
|
||||
else:
|
||||
print("· ", end="")
|
||||
print()
|
||||
|
||||
print("\n Условные обозначения:")
|
||||
print(" # - стена · - проход ● - путь")
|
||||
print(" S - старт E - выход")
|
||||
|
||||
#Сравнивает несколько стратегий на одном лабиринте
|
||||
def compare_strategies(self, strategies: List) -> dict:
|
||||
results = {}
|
||||
|
||||
print(f"СРАВНЕНИЕ СТРАТЕГИЙ")
|
||||
print(f"Лабиринт: {self.maze.width}x{self.maze.height}")
|
||||
|
||||
for strategy in strategies:
|
||||
self.set_strategy(strategy)
|
||||
stats = self.solve()
|
||||
results[strategy.name] = {
|
||||
'stats': stats,
|
||||
'path': self.get_path()
|
||||
}
|
||||
|
||||
# Вывод результатов
|
||||
status = "OK" if stats.path_found else "BULLSHIT"
|
||||
print(f"\n {status} {strategy.name}:")
|
||||
print(f" Время: {stats.time_ms:.2f} мс")
|
||||
print(f" Посещено клеток: {stats.visited_cells}")
|
||||
print(f" Длина пути: {stats.path_length}")
|
||||
|
||||
return results
|
||||
6
ivantsovma/maze/strategies/__init__.py
Normal file
6
ivantsovma/maze/strategies/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .path_finding_strategy import PathFindingStrategy
|
||||
from .bfs_strategy import BFSStrategy
|
||||
from .dfs_strategy import DFSStrategy
|
||||
from .astar_strategy import AStarStrategy
|
||||
|
||||
__all__ = ['PathFindingStrategy', 'BFSStrategy', 'DFSStrategy', 'AStarStrategy']
|
||||
67
ivantsovma/maze/strategies/astar_strategy.py
Normal file
67
ivantsovma/maze/strategies/astar_strategy.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import heapq
|
||||
from typing import List, Dict
|
||||
from .path_finding_strategy import PathFindingStrategy
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
"""A* поиск с эвристикой (манхэттенское расстояние)"""
|
||||
|
||||
def __init__(self):
|
||||
self._visited_count = 0
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "AStar"
|
||||
|
||||
def _heuristic(self, cell, exit_cell) -> int:
|
||||
"""Манхэттенское расстояние между клетками"""
|
||||
return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
|
||||
|
||||
def find_path(self, maze, start, exit_cell) -> List:
|
||||
"""Находит путь с помощью A*"""
|
||||
if not start or not exit_cell:
|
||||
return []
|
||||
|
||||
# Приоритетная очередь (F-значение, ID для уникальности, клетка)
|
||||
open_set = []
|
||||
heapq.heappush(open_set, (0, id(start), start))
|
||||
|
||||
# Откуда пришли в каждую клетку
|
||||
came_from = {}
|
||||
|
||||
# Стоимость пути от старта до клетки (G-значение)
|
||||
g_score = {start: 0}
|
||||
|
||||
# Оценочная стоимость (F-значение = G + H)
|
||||
f_score = {start: self._heuristic(start, exit_cell)}
|
||||
|
||||
self._visited_count = 0
|
||||
|
||||
while open_set:
|
||||
_, _, current = heapq.heappop(open_set)
|
||||
self._visited_count += 1
|
||||
|
||||
# Нашли выход
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(came_from, exit_cell)
|
||||
|
||||
# Проверяем всех соседей
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
tentative_g = g_score[current] + 1
|
||||
|
||||
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
||||
came_from[neighbor] = current
|
||||
g_score[neighbor] = tentative_g
|
||||
f = tentative_g + self._heuristic(neighbor, exit_cell)
|
||||
f_score[neighbor] = f
|
||||
heapq.heappush(open_set, (f, id(neighbor), neighbor))
|
||||
|
||||
# Путь не найден
|
||||
return []
|
||||
|
||||
def _reconstruct_path(self, came_from: Dict, current) -> List:
|
||||
"""Восстанавливает путь от старта до exit"""
|
||||
path = [current]
|
||||
while current in came_from:
|
||||
current = came_from[current]
|
||||
path.append(current)
|
||||
return list(reversed(path))
|
||||
53
ivantsovma/maze/strategies/bfs_strategy.py
Normal file
53
ivantsovma/maze/strategies/bfs_strategy.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
from collections import deque
|
||||
from typing import List, Dict
|
||||
from .path_finding_strategy import PathFindingStrategy
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в ширину (BFS) - гарантирует кратчайший путь"""
|
||||
|
||||
def __init__(self):
|
||||
self._visited_count = 0
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "BFS"
|
||||
|
||||
def find_path(self, maze, start, exit_cell) -> List:
|
||||
"""Находит путь с помощью BFS"""
|
||||
if not start or not exit_cell:
|
||||
return []
|
||||
|
||||
# Очередь для BFS
|
||||
queue = deque([start])
|
||||
# Множество посещенных клеток
|
||||
visited = {start}
|
||||
# Словарь для восстановления пути
|
||||
parent = {start: None}
|
||||
self._visited_count = 0
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
self._visited_count += 1
|
||||
|
||||
# Нашли выход
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(parent, exit_cell)
|
||||
|
||||
# Проверяем всех соседей
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
parent[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
# Путь не найден
|
||||
return []
|
||||
|
||||
def _reconstruct_path(self, parent: Dict, end) -> List:
|
||||
"""Восстанавливает путь от终点 до старта"""
|
||||
path = []
|
||||
current = end
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
return list(reversed(path))
|
||||
40
ivantsovma/maze/strategies/dfs_strategy.py
Normal file
40
ivantsovma/maze/strategies/dfs_strategy.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from typing import List
|
||||
from .path_finding_strategy import PathFindingStrategy
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в глубину (DFS) - быстрый, но не обязательно кратчайший"""
|
||||
|
||||
def __init__(self):
|
||||
self._visited_count = 0
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "DFS"
|
||||
|
||||
def find_path(self, maze, start, exit_cell) -> List:
|
||||
"""Находит путь с помощью DFS"""
|
||||
if not start or not exit_cell:
|
||||
return []
|
||||
|
||||
# Стек для DFS (хранит текущую клетку и путь до неё)
|
||||
stack = [(start, [start])]
|
||||
# Множество посещенных клеток
|
||||
visited = {start}
|
||||
self._visited_count = 0
|
||||
|
||||
while stack:
|
||||
current, path = stack.pop()
|
||||
self._visited_count += 1
|
||||
|
||||
# Нашли выход
|
||||
if current == exit_cell:
|
||||
return path
|
||||
|
||||
# Проверяем всех соседей
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
stack.append((neighbor, path + [neighbor]))
|
||||
|
||||
# Путь не найден
|
||||
return []
|
||||
19
ivantsovma/maze/strategies/path_finding_strategy.py
Normal file
19
ivantsovma/maze/strategies/path_finding_strategy.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
@abstractmethod
|
||||
#Находит путь от start до exit_cell
|
||||
def find_path(self, maze, start, exit_cell) -> List:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
#Имя стратегии
|
||||
pass
|
||||
|
||||
@property
|
||||
def visited_count(self) -> int:
|
||||
#Количество посещенных клеток (заполняется при поиске)
|
||||
return getattr(self, '_visited_count', 0)
|
||||
81
ivantsovma/maze/test_builder.py
Normal file
81
ivantsovma/maze/test_builder.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
# Добавляем текущую директорию в путь поиска
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from models import Cell, Maze
|
||||
from builders import TextFileMazeBuilder
|
||||
|
||||
def test_builder():
|
||||
print("ТЕСТИРОВАНИЕ BUILDER")
|
||||
|
||||
# Создаем строителя
|
||||
builder = TextFileMazeBuilder()
|
||||
|
||||
# Загружаем простой лабиринт
|
||||
print("\n1. Загрузка простого лабиринта (simple_maze.txt):")
|
||||
try:
|
||||
maze = builder.build_from_file("mazes/simple_maze.txt")
|
||||
print(f" Размер: {maze.width}x{maze.height}")
|
||||
print(f" Старт: {maze.start}")
|
||||
print(f" Выход: {maze.exit}")
|
||||
|
||||
# Визуализация
|
||||
print("\n Карта лабиринта:")
|
||||
for y in range(maze.height):
|
||||
line = ""
|
||||
for x in range(maze.width):
|
||||
cell = maze.get_cell(x, y)
|
||||
if cell.is_wall:
|
||||
line += "#"
|
||||
elif cell.is_start:
|
||||
line += "S"
|
||||
elif cell.is_exit:
|
||||
line += "E"
|
||||
else:
|
||||
line += " "
|
||||
print(f" {line}")
|
||||
|
||||
print(" Лабиринт загружен успешно!")
|
||||
except Exception as e:
|
||||
print(f" Ошибка: {e}")
|
||||
|
||||
# Загружаем сложный лабиринт
|
||||
print("\n2. Загрузка сложного лабиринта (small_maze.txt):")
|
||||
try:
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
print(f" Размер: {maze.width}x{maze.height}")
|
||||
print(f" Старт: {maze.start}")
|
||||
print(f" Выход: {maze.exit}")
|
||||
|
||||
# Визуализация
|
||||
print("\n Карта лабиринта:")
|
||||
for y in range(maze.height):
|
||||
line = ""
|
||||
for x in range(maze.width):
|
||||
cell = maze.get_cell(x, y)
|
||||
if cell.is_wall:
|
||||
line += "#"
|
||||
elif cell.is_start:
|
||||
line += "S"
|
||||
elif cell.is_exit:
|
||||
line += "E"
|
||||
else:
|
||||
line += " "
|
||||
print(f" {line}")
|
||||
|
||||
# Проверка соседей
|
||||
print(f"\n Проверка соседей старта:")
|
||||
neighbors = maze.get_neighbors(maze.start)
|
||||
for n in neighbors:
|
||||
print(f" - Сосед: ({n.x}, {n.y})")
|
||||
|
||||
print(" Сложный лабиринт загружен успешно!")
|
||||
except Exception as e:
|
||||
print(f" Ошибка: {e}")
|
||||
|
||||
print("ТЕСТИРОВАНИЕ ЗАВЕРШЕНО")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_builder()
|
||||
31
ivantsovma/maze/test_maze.py
Normal file
31
ivantsovma/maze/test_maze.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from models import Cell, Maze
|
||||
|
||||
# Создаем лабиринт 3x3
|
||||
maze = Maze(3, 3)
|
||||
|
||||
# Создаем клетки
|
||||
for y in range(3):
|
||||
for x in range(3):
|
||||
cell = Cell(x, y, is_wall=False)
|
||||
maze.set_cell(x, y, cell)
|
||||
|
||||
# Устанавливаем старт и выход
|
||||
maze.start = maze.get_cell(0, 0)
|
||||
maze.start.is_start = True
|
||||
maze.exit = maze.get_cell(2, 2)
|
||||
maze.exit.is_exit = True
|
||||
|
||||
#Создаем стену в центре
|
||||
center = maze.get_cell(1, 1)
|
||||
center.is_wall = True
|
||||
|
||||
print(f"Лабиринт: {maze}")
|
||||
print(f"Старт: {maze.start}")
|
||||
print(f"Выход: {maze.exit}")
|
||||
|
||||
# Проверяем соседей
|
||||
neighbors = maze.get_neighbors(maze.start)
|
||||
print(f"Соседи старта: {neighbors}")
|
||||
|
||||
# Проверяем проходимость
|
||||
print(f"Центр проходим? {center.is_passable()}")
|
||||
164
ivantsovma/maze/test_solver.py
Normal file
164
ivantsovma/maze/test_solver.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
# Добавляем корневую папку в путь поиска
|
||||
sys.path.insert(0, r'C:\ivantsovma\docs\MazeProject')
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
from solver import MazeSolver
|
||||
|
||||
def test_solver_basic():
|
||||
print("БАЗОВАЯ РАБОТА MAZESOLVER")
|
||||
|
||||
# Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
|
||||
print(f"\nЛабиринт загружен: {maze.width}x{maze.height}")
|
||||
print(f"Старт: ({maze.start.x}, {maze.start.y})")
|
||||
print(f"Выход: ({maze.exit.x}, {maze.exit.y})")
|
||||
|
||||
# Создаем решатель с BFS стратегией
|
||||
solver = MazeSolver(maze, BFSStrategy())
|
||||
|
||||
# Решаем лабиринт
|
||||
print("\nРешение лабиринта (BFS)")
|
||||
stats = solver.solve()
|
||||
|
||||
print(f"\nРезультат:")
|
||||
print(f" {stats}")
|
||||
|
||||
# Показываем путь на карте
|
||||
print("\nВизуализация пути:")
|
||||
solver.print_maze_with_path()
|
||||
|
||||
return solver, stats
|
||||
|
||||
#Тест динамической смены стратегии
|
||||
def test_solver_dynamic_strategy():
|
||||
print("ДИНАМИЧЕСКАЯ СМЕНА СТРАТЕГИИ")
|
||||
|
||||
# Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
|
||||
# Создаем решатель без стратегии
|
||||
solver = MazeSolver(maze)
|
||||
|
||||
# Пробуем разные стратегии
|
||||
strategies = [
|
||||
BFSStrategy(),
|
||||
DFSStrategy(),
|
||||
AStarStrategy()
|
||||
]
|
||||
|
||||
for strategy in strategies:
|
||||
print(f"\nУстановка стратегии: {strategy.name}")
|
||||
solver.set_strategy(strategy)
|
||||
stats = solver.solve()
|
||||
print(f" {stats}")
|
||||
|
||||
return solver
|
||||
|
||||
#Сравнение
|
||||
def test_solver_comparison():
|
||||
print("СРАВНЕНИЕ ВСЕХ СТРАТЕГИЙ")
|
||||
|
||||
# Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
|
||||
# Создаем решатель
|
||||
solver = MazeSolver(maze)
|
||||
|
||||
# Сравниваем стратегии
|
||||
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||||
results = solver.compare_strategies(strategies)
|
||||
|
||||
#Сводную таблица
|
||||
print("СВОДНАЯ ТАБЛИЦА")
|
||||
print(f"\n {'Стратегия':<10} {'Время(мс)':<10} {'Посещено':<10} {'Длина пути':<10} {'Статус':<10}")
|
||||
|
||||
for name, data in results.items():
|
||||
stats = data['stats']
|
||||
print(f" {name:<10} {stats.time_ms:<10.2f} {stats.visited_cells:<10} {stats.path_length:<10} {'OK' if stats.path_found else 'BULLSHIT'}")
|
||||
|
||||
return results
|
||||
|
||||
#Тест на разных лабиринтах
|
||||
def test_multiple_mazes():
|
||||
print("РАЗНЫЕ ЛАБИРИНТЫ")
|
||||
|
||||
mazes_files = [
|
||||
("mazes/simple_maze.txt", "Простой (5x3)"),
|
||||
("mazes/small_maze.txt", "Средний (7x7)")
|
||||
]
|
||||
|
||||
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||||
|
||||
for maze_file, maze_name in mazes_files:
|
||||
print(f"\n{maze_name}")
|
||||
try:
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file(maze_file)
|
||||
solver = MazeSolver(maze)
|
||||
|
||||
for strategy in strategies:
|
||||
solver.set_strategy(strategy)
|
||||
stats = solver.solve()
|
||||
status = "OK" if stats.path_found else "BULLSHIT"
|
||||
print(f" {status} {strategy.name}: {stats.time_ms:.2f}мс | {stats.visited_cells} клеток | длина={stats.path_length}")
|
||||
except Exception as e:
|
||||
print(f" Ошибка загрузки {maze_file}: {e}")
|
||||
|
||||
def test_no_exit_maze():
|
||||
print("ЛАБИРИНТ БЕЗ ВЫХОДА")
|
||||
#Создаем простой лабиринт без выхода
|
||||
from models import Maze, Cell
|
||||
|
||||
maze = Maze(5, 5)
|
||||
|
||||
#Заполняем проходами
|
||||
for y in range(5):
|
||||
for x in range(5):
|
||||
cell = Cell(x, y, is_wall=False)
|
||||
maze.set_cell(x, y, cell)
|
||||
|
||||
#Устанавливаем старт, но НЕ устанавливаем выход!
|
||||
start = maze.get_cell(0, 0)
|
||||
start.is_start = True
|
||||
maze.start = start
|
||||
|
||||
# Выход не устанавливаем (maze.exit = None)
|
||||
|
||||
# Создаем стену, чтобы заблокировать путь
|
||||
for x in range(5):
|
||||
wall = maze.get_cell(x, 4)
|
||||
wall.is_wall = True
|
||||
|
||||
print(f"\nЛабиринт: {maze.width}x{maze.height}")
|
||||
print(f"Старт: ({maze.start.x}, {maze.start.y})")
|
||||
print(f"Выход: отсутствует (None)")
|
||||
|
||||
# Пытаемся найти выход
|
||||
solver = MazeSolver(maze, BFSStrategy())
|
||||
|
||||
try:
|
||||
stats = solver.solve()
|
||||
print(f"\nРезультат:")
|
||||
print(f" {stats}")
|
||||
except ValueError as e:
|
||||
print(f"\nРезультат:")
|
||||
print(f"Ошибка: {e}")
|
||||
print(f"\nКорректная обработка: программа обнаружила отсутствие выхода")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Запускаем все тесты
|
||||
test_solver_basic()
|
||||
test_solver_dynamic_strategy()
|
||||
test_solver_comparison()
|
||||
test_multiple_mazes()
|
||||
test_no_exit_maze()
|
||||
|
||||
print("ВСЕ ТЕСТЫ ЗАВЕРШЕНЫ")
|
||||
117
ivantsovma/maze/test_strategy.py
Normal file
117
ivantsovma/maze/test_strategy.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
# Добавляем корневую папку в путь поиска
|
||||
sys.path.insert(0, r'C:\ivantsovma\docs\MazeProject')
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
|
||||
def test_strategies():
|
||||
print("ТЕСТИРОВАНИЕ STRATEGY ПАТТЕРНА")
|
||||
|
||||
# Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
|
||||
print(f"\nЛабиринт: {maze.width}x{maze.height}")
|
||||
print(f"Старт: ({maze.start.x}, {maze.start.y})")
|
||||
print(f"Выход: ({maze.exit.x}, {maze.exit.y})")
|
||||
|
||||
# Создаем стратегии
|
||||
strategies = [
|
||||
BFSStrategy(),
|
||||
DFSStrategy(),
|
||||
AStarStrategy()
|
||||
]
|
||||
|
||||
print("РЕЗУЛЬТАТЫ ПОИСКА ПУТИ")
|
||||
|
||||
for strategy in strategies:
|
||||
print(f"\n--- {strategy.name} ---")
|
||||
|
||||
# Ищем путь
|
||||
path = strategy.find_path(maze, maze.start, maze.exit)
|
||||
|
||||
if path:
|
||||
print(f"Путь найден!")
|
||||
print(f"Посещено клеток: {strategy.visited_count}")
|
||||
print(f"Длина пути: {len(path)} шагов")
|
||||
print(f"Путь: ", end="")
|
||||
for i, cell in enumerate(path[:5]):
|
||||
print(f"({cell.x},{cell.y})", end="")
|
||||
if i < len(path[:5]) - 1:
|
||||
print(" → ", end="")
|
||||
if len(path) > 5:
|
||||
print(f" ... → ({path[-1].x},{path[-1].y})")
|
||||
else:
|
||||
print()
|
||||
else:
|
||||
print(f"Путь не найден!")
|
||||
|
||||
# Визуализация
|
||||
print("ВИЗУАЛИЗАЦИЯ ЛАБИРИНТА С ПУТЕМ (BFS)")
|
||||
|
||||
# Находим путь BFS
|
||||
bfs = BFSStrategy()
|
||||
path = bfs.find_path(maze, maze.start, maze.exit)
|
||||
path_set = set(path)
|
||||
|
||||
print("\n " + " " * 4 + "0 1 2 3 4 5 6")
|
||||
for y in range(maze.height):
|
||||
line = f" {y} │ "
|
||||
for x in range(maze.width):
|
||||
cell = maze.get_cell(x, y)
|
||||
if cell in path_set and cell != maze.start and cell != maze.exit:
|
||||
line += "● "
|
||||
elif cell == maze.start:
|
||||
line += "S "
|
||||
elif cell == maze.exit:
|
||||
line += "E "
|
||||
elif cell.is_wall:
|
||||
line += "# "
|
||||
else:
|
||||
line += "· "
|
||||
print(line)
|
||||
|
||||
print("\n Условные обозначения:")
|
||||
print(" # - стена")
|
||||
print(" · - проход")
|
||||
print(" ● - путь")
|
||||
print(" S - старт")
|
||||
print(" E - выход")
|
||||
|
||||
def test_simple_maze():
|
||||
print("ТЕСТ НА ПРОСТОМ ЛАБИРИНТЕ")
|
||||
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/simple_maze.txt")
|
||||
|
||||
print(f"\nЛабиринт 5x3:")
|
||||
for y in range(maze.height):
|
||||
line = ""
|
||||
for x in range(maze.width):
|
||||
cell = maze.get_cell(x, y)
|
||||
if cell.is_wall:
|
||||
line += "#"
|
||||
elif cell.is_start:
|
||||
line += "S"
|
||||
elif cell.is_exit:
|
||||
line += "E"
|
||||
else:
|
||||
line += " "
|
||||
print(f" {line}")
|
||||
|
||||
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||||
|
||||
for strategy in strategies:
|
||||
path = strategy.find_path(maze, maze.start, maze.exit)
|
||||
if path:
|
||||
print(f"\n {strategy.name}: путь найден за {len(path)} шагов")
|
||||
else:
|
||||
print(f"\n {strategy.name}: путь НЕ найден")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_strategies()
|
||||
test_simple_maze()
|
||||
print("ТЕСТИРОВАНИЕ ЗАВЕРШЕНО")
|
||||
81
ivantsovma/maze/test_visualization.py
Normal file
81
ivantsovma/maze/test_visualization.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, r'C:\ivantsovma\docs\MazeProject')
|
||||
from builders import TextFileMazeBuilder
|
||||
from strategies import BFSStrategy
|
||||
from visualization import ConsoleView, GameController
|
||||
|
||||
def test_observer():
|
||||
print("ПАТТЕРН OBSERVER")
|
||||
# Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
|
||||
#создаём наблюдателя
|
||||
view = ConsoleView()
|
||||
|
||||
#уведомления о событии
|
||||
view.update("maze_loaded", maze)
|
||||
view.update("search_start", None)
|
||||
view.update("path_found", None)
|
||||
|
||||
print("\nObserver работает!")
|
||||
|
||||
def test_game_controller():
|
||||
print("ПАТТЕРН COMMAND (УПРАВЛЕНИЕ ИГРОКОМ)")
|
||||
|
||||
#pагружаем простой лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/simple_maze.txt")
|
||||
|
||||
#cоздаём контроллер
|
||||
controller = GameController(maze)
|
||||
|
||||
print(f"Начальная позиция: ({controller.get_player_position().x}, {controller.get_player_position().y})")
|
||||
|
||||
#движение вправо
|
||||
controller.move((1, 0)) #Вправо
|
||||
print(f"После движения вправо: ({controller.get_player_position().x}, {controller.get_player_position().y})")
|
||||
|
||||
controller.move((1, 0)) #Вправо
|
||||
print(f"После движения вправо: ({controller.get_player_position().x}, {controller.get_player_position().y})")
|
||||
|
||||
#отменяем последние движения
|
||||
controller.undo()
|
||||
print(f"После отмены: ({controller.get_player_position().x}, {controller.get_player_position().y})")
|
||||
|
||||
controller.undo()
|
||||
print(f"После второй отмены: ({controller.get_player_position().x}, {controller.get_player_position().y})")
|
||||
|
||||
print("\nCommand работает!")
|
||||
|
||||
def test_integration():
|
||||
print("ИНТЕГРАЦИЯ ВСЕХ КОМПОНЕНТОВ")
|
||||
|
||||
# Загружаем лабиринт
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file("mazes/small_maze.txt")
|
||||
|
||||
# Находим путь
|
||||
bfs = BFSStrategy()
|
||||
path = bfs.find_path(maze, maze.start, maze.exit)
|
||||
|
||||
# Создаём отображение
|
||||
view = ConsoleView()
|
||||
view.maze = maze
|
||||
view.path = path
|
||||
view.render()
|
||||
|
||||
print("\nИнтеграция работает!")
|
||||
|
||||
def run_interactive_game():
|
||||
print("ИНТЕРАКТИВНАЯ ИГРА")
|
||||
print("Для запуска интерактивной игры используйте:")
|
||||
print("python play_maze.py")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_observer()
|
||||
test_game_controller()
|
||||
test_integration()
|
||||
run_interactive_game()
|
||||
6
ivantsovma/maze/visualization/__init__.py
Normal file
6
ivantsovma/maze/visualization/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from .observer import Observable
|
||||
from .console_view import ConsoleView
|
||||
from .command import MoveCommand, Player
|
||||
from .game_controller import GameController
|
||||
|
||||
__all__ = ['Observer', 'Observable', 'ConsoleView', 'Command', 'MoveCommand', 'Player', 'GameController']
|
||||
54
ivantsovma/maze/visualization/command.py
Normal file
54
ivantsovma/maze/visualization/command.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
class Command(ABC):
|
||||
#Выполняет команду
|
||||
@abstractmethod
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
#Отменяет команду
|
||||
@abstractmethod
|
||||
def undo(self):
|
||||
pass
|
||||
|
||||
class MoveCommand(Command):
|
||||
def __init__(self, player, direction, maze, notifier=None):
|
||||
self.player = player
|
||||
self.direction = direction # (dx, dy)
|
||||
self.maze = maze
|
||||
self.notifier = notifier
|
||||
self.previous_cell = player.current_cell
|
||||
|
||||
#Перемещает игрока в указанном направлении
|
||||
def execute(self):
|
||||
dx, dy = self.direction
|
||||
new_x = self.player.current_cell.x + dx
|
||||
new_y = self.player.current_cell.y + dy
|
||||
|
||||
new_cell = self.maze.get_cell(new_x, new_y)
|
||||
|
||||
# Проверяем, можно ли пройти
|
||||
if new_cell and new_cell.is_passable():
|
||||
self.player.move_to(new_cell)
|
||||
return True
|
||||
return False
|
||||
|
||||
#возвращает на предыдущую клетку
|
||||
def undo(self):
|
||||
if self.previous_cell:
|
||||
self.player.move_to(self.previous_cell)
|
||||
return True
|
||||
return False
|
||||
|
||||
class Player:
|
||||
#Игрок в лабиринте
|
||||
def __init__(self, start_cell):
|
||||
self.current_cell = start_cell
|
||||
self.start_cell = start_cell
|
||||
#Перемещает игрока на новую клетку
|
||||
def move_to(self, cell):
|
||||
self.current_cell = cell
|
||||
|
||||
#Сбрасывает игрока на стартовую позицию
|
||||
def reset(self):
|
||||
self.current_cell = self.start_cell
|
||||
118
ivantsovma/maze/visualization/console_view.py
Normal file
118
ivantsovma/maze/visualization/console_view.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import os
|
||||
from .observer import Observer, Event, EventType
|
||||
|
||||
class ConsoleView(Observer):
|
||||
"""Консольное отображение лабиринта"""
|
||||
|
||||
def __init__(self):
|
||||
self.maze = None
|
||||
self.player_pos = None
|
||||
self.path = []
|
||||
self.current_strategy = None
|
||||
self.messages = []
|
||||
|
||||
def update(self, event: Event):
|
||||
event_type = event.event_type
|
||||
data = event.data
|
||||
|
||||
if event_type == EventType.MAZE_LOADED:
|
||||
self.maze = data
|
||||
self._log(f"Лабиринт загружен: {self.maze.width}x{self.maze.height}")
|
||||
|
||||
elif event_type == EventType.PATH_FOUND:
|
||||
self.path = data if data else []
|
||||
self._log(f"Путь найден! Длина: {len(self.path)} шагов")
|
||||
|
||||
elif event_type == EventType.PATH_NOT_FOUND:
|
||||
self.path = []
|
||||
self._log(f"Путь не найден!")
|
||||
|
||||
elif event_type == EventType.PLAYER_MOVED:
|
||||
self.player_pos = data
|
||||
self._log(f"Игрок переместился на ({data.x}, {data.y})")
|
||||
|
||||
elif event_type == EventType.SEARCH_START:
|
||||
self._log(f"Начинаем поиск пути...")
|
||||
|
||||
elif event_type == EventType.SEARCH_END:
|
||||
self._log(f"Поиск завершён")
|
||||
|
||||
elif event_type == EventType.ERROR:
|
||||
self._log(f"Ошибка: {data}")
|
||||
|
||||
elif event_type == EventType.UNDO:
|
||||
self._log(f"Отмена последнего действия")
|
||||
|
||||
# После каждого события перерисовываем
|
||||
self.render()
|
||||
|
||||
def render(self):
|
||||
"""Отрисовывает лабиринт в консоли"""
|
||||
if not self.maze:
|
||||
print("Лабиринт не загружен")
|
||||
return
|
||||
|
||||
# Очищаем консоль
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
print("=" * 60)
|
||||
print("ЛАБИРИНТ")
|
||||
if self.current_strategy:
|
||||
print(f"Алгоритм: {self.current_strategy}")
|
||||
print("=" * 60)
|
||||
|
||||
# Создаём множество клеток пути для быстрого доступа
|
||||
path_set = set(self.path) if self.path else set()
|
||||
|
||||
# Верхняя граница с координатами
|
||||
print(" " + " ".join(f"{x:2}" for x in range(self.maze.width)))
|
||||
|
||||
for y in range(self.maze.height):
|
||||
# Номер строки
|
||||
line = f"{y:2} │ "
|
||||
|
||||
for x in range(self.maze.width):
|
||||
cell = self.maze.get_cell(x, y)
|
||||
|
||||
# Определяем символ для отображения
|
||||
if self.player_pos and cell == self.player_pos:
|
||||
line += "🎮 "
|
||||
elif cell == self.maze.start:
|
||||
line += "🚩 "
|
||||
elif cell == self.maze.exit:
|
||||
line += "🏁 "
|
||||
elif cell in path_set:
|
||||
line += "● "
|
||||
elif cell.is_wall:
|
||||
line += "██ "
|
||||
else:
|
||||
line += "· "
|
||||
|
||||
print(line)
|
||||
|
||||
print("Условные обозначения:")
|
||||
print(" ██ - стена · - проход ● - путь")
|
||||
print(" 🚩 - старт 🏁 - выход 🎮 - игрок")
|
||||
if self.current_strategy:
|
||||
print(f" Алгоритм: {self.current_strategy}")
|
||||
|
||||
# Выводим сообщения
|
||||
if self.messages:
|
||||
print("\nСООБЩЕНИЯ:")
|
||||
for msg in self.messages[-5:]: # Показываем последние 5 сообщений
|
||||
print(f" {msg}")
|
||||
print("Команды: W/A/S/D - движение, U - отмена, Q - выход")
|
||||
|
||||
def set_strategy(self, strategy_name: str):
|
||||
"""Устанавливает имя текущей стратегии для отображения"""
|
||||
self.current_strategy = strategy_name
|
||||
|
||||
def _log(self, message: str):
|
||||
"""Добавляет сообщение в лог"""
|
||||
self.messages.append(message)
|
||||
if len(self.messages) > 10:
|
||||
self.messages.pop(0)
|
||||
|
||||
def clear_messages(self):
|
||||
"""Очищает сообщения"""
|
||||
self.messages = []
|
||||
38
ivantsovma/maze/visualization/game_controller.py
Normal file
38
ivantsovma/maze/visualization/game_controller.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from .observer import Observable, Event, EventType
|
||||
from .command import MoveCommand, Player
|
||||
|
||||
class GameController(Observable):
|
||||
def __init__(self, maze):
|
||||
super().__init__()
|
||||
self.maze = maze
|
||||
self.player = Player(maze.start)
|
||||
self.command_history = []
|
||||
|
||||
def move(self, direction):
|
||||
"""Перемещает игрока в направлении"""
|
||||
cmd = MoveCommand(self.player, direction, self.maze, self)
|
||||
if cmd.execute():
|
||||
self.command_history.append(cmd)
|
||||
# Правильный вызов notify с одним аргументом Event
|
||||
self.notify(Event(EventType.PLAYER_MOVED, self.player.current_cell))
|
||||
return True
|
||||
return False
|
||||
|
||||
def undo(self):
|
||||
"""Отменяет последнее действие"""
|
||||
if self.command_history:
|
||||
cmd = self.command_history.pop()
|
||||
cmd.undo()
|
||||
self.notify(Event(EventType.UNDO, None))
|
||||
return True
|
||||
return False
|
||||
|
||||
def reset(self):
|
||||
"""Сбрасывает игру"""
|
||||
self.player.reset()
|
||||
self.command_history.clear()
|
||||
self.notify(Event(EventType.PLAYER_MOVED, self.player.current_cell))
|
||||
|
||||
def get_player_position(self):
|
||||
"""Возвращает позицию игрока"""
|
||||
return self.player.current_cell
|
||||
39
ivantsovma/maze/visualization/observer.py
Normal file
39
ivantsovma/maze/visualization/observer.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
from enum import Enum, auto
|
||||
|
||||
class EventType(Enum):
|
||||
MAZE_LOADED = auto()
|
||||
PATH_FOUND = auto()
|
||||
PATH_NOT_FOUND = auto()
|
||||
PLAYER_MOVED = auto()
|
||||
SEARCH_START = auto()
|
||||
SEARCH_END = auto()
|
||||
ERROR = auto()
|
||||
UNDO = auto()
|
||||
|
||||
class Event:
|
||||
def __init__(self, event_type: EventType, data: Any = None):
|
||||
self.event_type = event_type
|
||||
self.data = data
|
||||
|
||||
class Observer(ABC):
|
||||
@abstractmethod
|
||||
def update(self, event: Event):
|
||||
pass
|
||||
|
||||
class Observable:
|
||||
def __init__(self):
|
||||
self._observers = []
|
||||
|
||||
def attach(self, observer: Observer):
|
||||
if observer not in self._observers:
|
||||
self._observers.append(observer)
|
||||
|
||||
def detach(self, observer: Observer):
|
||||
if observer in self._observers:
|
||||
self._observers.remove(observer)
|
||||
|
||||
def notify(self, event: Event):
|
||||
for observer in self._observers:
|
||||
observer.update(event)
|
||||
136
ivantsovma/structures_data/bst_phonebook.py
Normal file
136
ivantsovma/structures_data/bst_phonebook.py
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import time
|
||||
import csv
|
||||
import random
|
||||
import os
|
||||
|
||||
def create_node(name, phone):
|
||||
"""Создает узел BST"""
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
"""Вставляет запись в BST"""
|
||||
if root is None:
|
||||
return create_node(name, phone)
|
||||
|
||||
current = root
|
||||
while True:
|
||||
if name < current['name']:
|
||||
if current['left'] is None:
|
||||
current['left'] = create_node(name, phone)
|
||||
break
|
||||
current = current['left']
|
||||
elif name > current['name']:
|
||||
if current['right'] is None:
|
||||
current['right'] = create_node(name, phone)
|
||||
break
|
||||
current = current['right']
|
||||
else:
|
||||
current['phone'] = phone
|
||||
break
|
||||
|
||||
return root
|
||||
|
||||
def bst_find(root, name):
|
||||
"""Ищет запись в BST"""
|
||||
current = root
|
||||
while current:
|
||||
if name == current['name']:
|
||||
return current['phone']
|
||||
elif name < current['name']:
|
||||
current = current['left']
|
||||
else:
|
||||
current = current['right']
|
||||
return None
|
||||
|
||||
def bst_find_min(root):
|
||||
"""Находит минимальный узел"""
|
||||
if root is None:
|
||||
return None
|
||||
current = root
|
||||
while current['left']:
|
||||
current = current['left']
|
||||
return current
|
||||
|
||||
def bst_delete(root, name):
|
||||
"""Удаляет запись из BST"""
|
||||
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:
|
||||
if root['left'] is None:
|
||||
return root['right']
|
||||
elif root['right'] is None:
|
||||
return root['left']
|
||||
|
||||
min_node = bst_find_min(root['right'])
|
||||
root['name'] = min_node['name']
|
||||
root['phone'] = min_node['phone']
|
||||
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||
|
||||
return root
|
||||
|
||||
def generate_test_data(n=300):
|
||||
"""Генерирует тестовые данные"""
|
||||
records = [(f"User_{i:05d}", f"123-456-{i%10000:04d}") for i in range(n)]
|
||||
records_shuffled = records.copy()
|
||||
random.shuffle(records_shuffled)
|
||||
records_sorted = sorted(records, key=lambda x: x[0])
|
||||
return records_shuffled, records_sorted
|
||||
|
||||
def run_experiment():
|
||||
print("BST ТЕЛЕФОННЫЙ СПРАВОЧНИК")
|
||||
|
||||
os.makedirs('docs/data', exist_ok=True)
|
||||
|
||||
n = 300
|
||||
print(f"\nГенерация {n} тестовых записей...")
|
||||
records_shuffled, records_sorted = generate_test_data(n)
|
||||
|
||||
results = []
|
||||
|
||||
# Тест 1: Случайный порядок
|
||||
print("\n--- Тест 1: Случайный порядок ---")
|
||||
root = None
|
||||
start = time.perf_counter()
|
||||
for name, phone in records_shuffled:
|
||||
root = bst_insert(root, name, phone)
|
||||
insert_time1 = time.perf_counter() - start
|
||||
print(f"Вставка: {insert_time1:.4f} сек")
|
||||
|
||||
names_to_find = [records_shuffled[i][0] for i in range(30)]
|
||||
start = time.perf_counter()
|
||||
for name in names_to_find:
|
||||
bst_find(root, name)
|
||||
find_time1 = time.perf_counter() - start
|
||||
print(f"Поиск 30 записей: {find_time1:.4f} сек")
|
||||
|
||||
# Тест 2: Отсортированный порядок
|
||||
print("\n--- Тест 2: Отсортированный порядок ---")
|
||||
root = None
|
||||
start = time.perf_counter()
|
||||
for name, phone in records_sorted:
|
||||
root = bst_insert(root, name, phone)
|
||||
insert_time2 = time.perf_counter() - start
|
||||
print(f"Вставка: {insert_time2:.4f} сек")
|
||||
|
||||
# Сохраняем результаты
|
||||
results.append(['BST', 'случайный', insert_time1, find_time1, 0])
|
||||
results.append(['BST', 'отсортированный', insert_time2, 0, 0])
|
||||
|
||||
with open('docs/data/bst_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['Структура', 'Режим', 'Время_вставки', 'Время_поиска', 'Время_удаления'])
|
||||
writer.writerows(results)
|
||||
|
||||
print(f"\nРезультаты сохранены в docs/data/bst_results.csv")
|
||||
print(f"\nСравнение:")
|
||||
print(f"Случайный порядок вставки: {insert_time1:.4f} сек")
|
||||
print(f"Отсортированный порядок вставки: {insert_time2:.4f} сек")
|
||||
print(f"Разница: {insert_time2/insert_time1:.1f} раз")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_experiment()
|
||||
46
ivantsovma/structures_data/compare_structures.py
Normal file
46
ivantsovma/structures_data/compare_structures.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import csv
|
||||
import os
|
||||
|
||||
def read_results(filename):
|
||||
"""Читает результаты из CSV файла"""
|
||||
results = []
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader) # Пропускаем заголовок
|
||||
for row in reader:
|
||||
results.append(row)
|
||||
except:
|
||||
print(f"Не удалось прочитать {filename}")
|
||||
return results
|
||||
|
||||
def main():
|
||||
print("СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
|
||||
|
||||
# Читаем результаты
|
||||
linked_list = read_results('docs/data/linked_list_results.csv')
|
||||
hash_table = read_results('docs/data/hash_table_results.csv')
|
||||
bst = read_results('docs/data/bst_results.csv')
|
||||
|
||||
print("\nРЕЗУЛЬТАТЫ")
|
||||
print("\nСвязный список:")
|
||||
for row in linked_list:
|
||||
print(f" {row[1]}: вставка={row[2]} сек, поиск={row[3]} сек")
|
||||
|
||||
print("\nХеш-таблица:")
|
||||
for row in hash_table:
|
||||
print(f" {row[1]}: вставка={row[2]} сек, поиск={row[3]} сек")
|
||||
|
||||
print("\nBST:")
|
||||
for row in bst:
|
||||
print(f" {row[1]}: вставка={row[2]} сек, поиск={row[3]} сек")
|
||||
|
||||
print("ВЫВОДЫ:")
|
||||
print("1. Хеш-таблица работает быстрее всего для поиска")
|
||||
print("2. BST сильно замедляется на отсортированных данных")
|
||||
print("3. Связный список самый медленный для всех операций")
|
||||
print("4. Для частого поиска лучше использовать хеш-таблицу")
|
||||
print("5. Для отсортированных данных BST неэффективен")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
133
ivantsovma/structures_data/hash_table_phonebook.py
Normal file
133
ivantsovma/structures_data/hash_table_phonebook.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import time
|
||||
import csv
|
||||
import random
|
||||
import os
|
||||
|
||||
def create_node(name, phone):
|
||||
"""Создает узел для бакета"""
|
||||
return {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
"""Вставка в связный список"""
|
||||
new_node = create_node(name, phone)
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
if head['name'] == name:
|
||||
head['phone'] = phone
|
||||
return head
|
||||
|
||||
current = head
|
||||
while current['next']:
|
||||
if current['next']['name'] == name:
|
||||
current['next']['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
current['next'] = new_node
|
||||
return head
|
||||
|
||||
def ll_find(head, name):
|
||||
"""Поиск в связном списке"""
|
||||
current = head
|
||||
while current:
|
||||
if current['name'] == name:
|
||||
return current['phone']
|
||||
current = current['next']
|
||||
return None
|
||||
|
||||
def ll_delete(head, name):
|
||||
"""Удаление из связного списка"""
|
||||
if head is None:
|
||||
return None
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
current = head
|
||||
while current['next']:
|
||||
if current['next']['name'] == name:
|
||||
current['next'] = current['next']['next']
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
return head
|
||||
|
||||
def hash_function(name, table_size):
|
||||
"""Простая хеш-функция"""
|
||||
return sum(ord(c) for c in name) % table_size
|
||||
|
||||
def ht_insert(table, name, phone):
|
||||
"""Вставка в хеш-таблицу"""
|
||||
index = hash_function(name, len(table))
|
||||
table[index] = ll_insert(table[index], name, phone)
|
||||
|
||||
def ht_find(table, name):
|
||||
"""Поиск в хеш-таблице"""
|
||||
index = hash_function(name, len(table))
|
||||
return ll_find(table[index], name)
|
||||
|
||||
def ht_delete(table, name):
|
||||
"""Удаление из хеш-таблицы"""
|
||||
index = hash_function(name, len(table))
|
||||
table[index] = ll_delete(table[index], name)
|
||||
|
||||
def generate_test_data(n=500):
|
||||
"""Генерирует тестовые данные"""
|
||||
records = [(f"User_{i:05d}", f"123-456-{i%10000:04d}") for i in range(n)]
|
||||
random.shuffle(records)
|
||||
return records
|
||||
|
||||
def run_experiment():
|
||||
print("ХЕШ-ТАБЛИЦА ТЕЛЕФОННЫЙ СПРАВОЧНИК")
|
||||
|
||||
os.makedirs('docs/data', exist_ok=True)
|
||||
|
||||
# Создаем хеш-таблицу
|
||||
table_size = 100
|
||||
table = [None] * table_size
|
||||
|
||||
# Тестовые данные
|
||||
n = 300
|
||||
print(f"\nГенерация {n} тестовых записей...")
|
||||
records = generate_test_data(n)
|
||||
|
||||
results = []
|
||||
|
||||
# Вставка
|
||||
print("\n--- Тестирование ---")
|
||||
start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
ht_insert(table, name, phone)
|
||||
insert_time = time.perf_counter() - start
|
||||
print(f"Вставка: {insert_time:.4f} сек")
|
||||
|
||||
# Поиск
|
||||
names_to_find = [records[i][0] for i in range(50)]
|
||||
start = time.perf_counter()
|
||||
for name in names_to_find:
|
||||
ht_find(table, name)
|
||||
find_time = time.perf_counter() - start
|
||||
print(f"Поиск 50 записей: {find_time:.4f} сек")
|
||||
|
||||
# Удаление
|
||||
names_to_delete = names_to_find[:25]
|
||||
start = time.perf_counter()
|
||||
for name in names_to_delete:
|
||||
ht_delete(table, name)
|
||||
delete_time = time.perf_counter() - start
|
||||
print(f"Удаление 25 записей: {delete_time:.4f} сек")
|
||||
|
||||
results.append(['HashTable', 'случайный', insert_time, find_time, delete_time])
|
||||
|
||||
# Сохраняем результаты
|
||||
with open('docs/data/hash_table_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['Структура', 'Режим', 'Время_вставки', 'Время_поиска', 'Время_удаления'])
|
||||
writer.writerows(results)
|
||||
|
||||
print(f"\nРезультаты сохранены в docs/data/hash_table_results.csv")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_experiment()
|
||||
122
ivantsovma/structures_data/linked_list_phonebook.py
Normal file
122
ivantsovma/structures_data/linked_list_phonebook.py
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import time
|
||||
import csv
|
||||
import random
|
||||
import os
|
||||
|
||||
def create_node(name, phone):
|
||||
"""Создает новый узел списка"""
|
||||
return {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
"""Вставляет или обновляет запись"""
|
||||
new_node = create_node(name, phone)
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
current = head
|
||||
prev = None
|
||||
|
||||
while current:
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
return head
|
||||
prev = current
|
||||
current = current['next']
|
||||
|
||||
prev['next'] = new_node
|
||||
return head
|
||||
|
||||
def ll_find(head, name):
|
||||
"""Ищет запись по имени"""
|
||||
current = head
|
||||
while current:
|
||||
if current['name'] == name:
|
||||
return current['phone']
|
||||
current = current['next']
|
||||
return None
|
||||
|
||||
def ll_delete(head, name):
|
||||
"""Удаляет запись"""
|
||||
if head is None:
|
||||
return None
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
current = head
|
||||
while current['next']:
|
||||
if current['next']['name'] == name:
|
||||
current['next'] = current['next']['next']
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
return head
|
||||
|
||||
def ll_list_all(head):
|
||||
"""Собирает все записи"""
|
||||
records = []
|
||||
current = head
|
||||
while current:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
return sorted(records, key=lambda x: x[0])
|
||||
|
||||
def generate_test_data(n=500):
|
||||
"""Генерирует тестовые данные"""
|
||||
records = [(f"User_{i:05d}", f"123-456-{i%10000:04d}") for i in range(n)]
|
||||
records_shuffled = records.copy()
|
||||
random.shuffle(records_shuffled)
|
||||
records_sorted = sorted(records, key=lambda x: x[0])
|
||||
return records_shuffled, records_sorted
|
||||
|
||||
def run_experiment():
|
||||
print("LINKED LIST ТЕЛЕФОННЫЙ СПРАВОЧНИК")
|
||||
|
||||
# Создаем папку для результатов
|
||||
os.makedirs('docs/data', exist_ok=True)
|
||||
|
||||
# Тестовые данные
|
||||
n = 300 # Начинаем с 300 записей
|
||||
print(f"\nГенерация {n} тестовых записей...")
|
||||
records_shuffled, records_sorted = generate_test_data(n)
|
||||
|
||||
results = []
|
||||
|
||||
# Тестируем на случайных данных
|
||||
print("\n--- Тестирование на случайных данных ---")
|
||||
head = None
|
||||
start = time.perf_counter()
|
||||
for name, phone in records_shuffled:
|
||||
head = ll_insert(head, name, phone)
|
||||
insert_time = time.perf_counter() - start
|
||||
print(f"Вставка: {insert_time:.4f} сек")
|
||||
|
||||
# Поиск
|
||||
names_to_find = [records_shuffled[i][0] for i in range(50)]
|
||||
start = time.perf_counter()
|
||||
for name in names_to_find:
|
||||
ll_find(head, name)
|
||||
find_time = time.perf_counter() - start
|
||||
print(f"Поиск 50 записей: {find_time:.4f} сек")
|
||||
|
||||
# Удаление
|
||||
names_to_delete = names_to_find[:25]
|
||||
start = time.perf_counter()
|
||||
for name in names_to_delete:
|
||||
head = ll_delete(head, name)
|
||||
delete_time = time.perf_counter() - start
|
||||
print(f"Удаление 25 записей: {delete_time:.4f} сек")
|
||||
|
||||
results.append(['LinkedList', 'случайный', insert_time, find_time, delete_time])
|
||||
|
||||
# Сохраняем результаты
|
||||
with open('docs/data/linked_list_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['Структура', 'Режим', 'Время_вставки', 'Время_поиска', 'Время_удаления'])
|
||||
writer.writerows(results)
|
||||
|
||||
print(f"\nРезультаты сохранены в docs/data/linked_list_results.csv")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_experiment()
|
||||
Loading…
Reference in New Issue
Block a user