Создаём консольную игру «Камень-ножницы-бумага»
Нам нужно создать консольную игру, в которой игрок будет выбирать свой ход из 3 вариантов (камень, ножницы, бумага) путём ввода в консоль одной из 3 цифр: 1, 2 или 3. Вымышленный соперник будет выбирать свой вариант случайно из таких же трёх вариантов.
Игра идёт до тех пор, пока у игрока есть жизни. Если атака успешна или соперник умер, должны быть начислены очки в
зависимости от выбранного уровня сложности. Если игрок умер, очки должны быть записаны в файл с результатами.
main.py— основной скрипт, отвечает за меню и за запуск игрыtests.py— файл с тестами для проверки работоспособности классов и методов- Папка
game/— со всеми основными файлами игры. Все остальные файлы должны быть в этой папке. game/__init__.py— пустой файл, необходим для корректного импорта модулей из папкиgame/models.py— файл с описанием моделей, которые есть в игре (игрок и соперник)game/settings.py— файл со всеми константами, которые могут нам понадобитьсяgame/exceptions.py— файл с необходимыми нам исключениямиgame/game.py— файл с основной логикой игрыgame/score.py— файл с логикой сохранения очков игры
Должен содержать два класса:
PlayerEnemy
Атрибуты:
name— имя игрока, задаётся пользователем через консольlives— количество жизней, берётся из константы изsettings.pyscore— очки игрока, изначально 0
Методы:
__init__— для инициализации игрока, принимает только имя, назначает имя, количество жизней и очковselect_attack— метод для ввода атаки игроком. Вводим до тех пор, пока пользователь не введёт валидное значение (1, 2, 3), использует константы из файлаsettings.pydecrease_lives— метод, который будет вызываться, если игрок проиграл «бой», уменьшает жизни на 1. Если жизни закончились, вызывает исключениеGameOverиз файлаexceptions.pyadd_score— метод для начисления очков игроку
Атрибуты:
lives— количество жизней, изначально зависит от уровня соперника и уровня сложности, уменьшается на 1, когда соперник проигрывает «бой»level— уровень соперника, будет увеличиваться с каждым новым соперником. Изначально 1
Методы:
__init__— для инициализации соперника, принимает только уровень и сложность, чтобы вычислить количество жизней, назначает количество жизней и уровеньselect_attack— метод для случайного выбора атаки (1, 2, 3), использует константы из файлаsettings.pydecrease_lives— уменьшает жизни при проигрыше «боя», вызывает исключениеEnemyDownиз файлаexceptions.py, если у соперника закончились жизни
Содержит класс игры Game
Атрибуты:
player— объект игрокаenemy— объект соперника, при убийстве будет создан новый, с более высоким уровнемmode— уровень сложности, normal или hard, содержит либо 1, либо 2, которые определены константами
Методы:
__init__— принимает объект игрока и уровень сложности, создаёт первого соперникаcreate_enemy— метод для создания нового соперникаplay— метод запуска игры. Запускает бесконечный цикл, в одной итерации которого происходит «бой». Для этого вызывает два метода:fightиhandle_fight_result. Отслеживает, не произошло ли одно из исключений при вызове второго метода —GameOverилиEnemyDown. При первом завершает игру и вызывает метод для записи очков, при втором создаёт нового, более сильного соперникаfight— метод запрашивает у пользователя и соперника атаки, из констант получает результат боя (-1, 0, 1)handle_fight_result— принимает результат боя и в зависимости от результата отнимает жизни либо у игрока, либо у соперникаsave_score— вызывает сохранение очков при помощи класса из файлаscore.py
Классы:
GameOver— когда у игрока заканчиваются жизниEnemyDown— когда у соперника заканчиваются жизни
Содержит все константы.
Примерное содержание:
MODE_NORMAL = 'Normal'
MODE_HARD = 'Hard'
MODES = {'1': MODE_NORMAL,
'2': MODE_HARD}
PLAYER_LIVES = 2
POINTS_FOR_FIGHT = 1
POINTS_FOR_KILLING = 5
MAX_RECORDS_NUMBER = 5
HARD_MODE_MULTIPLIER = 2
SCORE_FILE = 'scores.txt'
PAPER = 'Paper'
STONE = 'Stone'
SCISSORS = 'Scissors'
WIN = 1
DRAW = 0
LOSE = -1
ALLOWED_ATTACKS = {
'1': PAPER,
'2': STONE,
'3': SCISSORS
}
ATTACK_PAIRS_OUTCOME = {
(PAPER, PAPER): DRAW,
(PAPER, STONE): WIN,
(PAPER, SCISSORS): LOSE,
(STONE, PAPER): LOSE,
(STONE, STONE): DRAW,
(STONE, SCISSORS): WIN,
(SCISSORS, PAPER): WIN,
(SCISSORS, STONE): LOSE,
(SCISSORS, SCISSORS): DRAW
}Классы:
ScoreHandler— класс для обработки очковGameRecord— класс, содержащий записи об игрокахPlayerRecord— класс для хранения записи об одном игроке
Атрибуты:
game_record— объект классаGameRecord, туда мы будем считывать сохранённые очки и записывать таблицу с новымиfile_name— имя файла, откуда и куда мы записываем очки
Методы:
__init__— принимает только имя файла и сохраняет его. Вызывает метод для чтения файлаread— метод, который будет читать файл и каждую его строку сохранять вPlayerRecord, которые будут сохраняться вGameRecordsave— метод, который нужен, чтобы записать новые результаты в файл (предварительно отсортировать и обрезать, если нужно)display— метод для отображения очков
Атрибуты:
records— список объектов типаPlayerRecord
Методы:
__init__— создаёт объект с пустым списком записейadd_record— метод для добавления записи об одном игроке. Должен проверять, нет ли у нас уже такого игрока, и если есть, то перезаписывать его результат. Тот же самый игрок проверяется по имени и уровню сложности (игрок может быть представлен в таблице два раза на разном уровне сложности). Можно использовать magic-метод__eq__для поиска черезinprepare_records— метод для сортировки существующих результатов и обрезки до максимального количества, указанного в настройках
Атрибуты:
name— имя игрокаmode— уровень сложностиscore— количество очков
Методы:
__init__— для создания объекта принимает все три параметра__gt__— чтобы можно было отсортировать записи по очкам__str__— для удобного вывода данных
Содержит функции:
main— для запуска всего кода. Внутри этой функции должен быть запущен процесс выбора из трёх пунктов: запуск игры, посмотреть очки и выйти из игры (1, 2, 3)play_game— вызывается, если игрок выбрал начать игру. В этой функции будет запущен процесс создания игрока, создание объекта игры и запуск самой игрыcreate_player— спросить у игрока имя и сложность, создать объект игрока с указанным именем и передать объект игрока и сложность в класс игрыshow_scores— показать очки, используя классScoreHandlerexit_game— выйти из игры (неexit, чтобы не конфликтовать со встроенной функцией)
Файл с модульными тестами. Используйте unittest для тестирования классов и методов.
Player:
- Создание игрока с корректным именем и начальными значениями (
lives,score) - Метод
add_scoreкорректно увеличивает очки - Метод
decrease_livesуменьшает жизни на 1 - Метод
decrease_livesвызываетGameOver, когда жизни заканчиваются
Enemy:
- Создание соперника с корректным количеством жизней в зависимости от уровня и сложности
- Метод
select_attackвозвращает одно из допустимых значений - Метод
decrease_livesвызываетEnemyDown, когда жизни заканчиваются
Game:
- Метод
fightвозвращает корректный результат (-1, 0, 1) для разных комбинаций атак - Метод
create_enemyсоздаёт соперника с правильным уровнем
PlayerRecord:
- Метод
__gt__корректно сравнивает записи по очкам - Метод
__eq__корректно сравнивает записи по имени и режиму
GameRecord:
- Метод
add_recordдобавляет новую запись - Метод
add_recordперезаписывает существующую запись при совпадении имени и режима - Метод
prepare_recordsсортирует записи по убыванию очков и обрезает доMAX_RECORDS_NUMBER
ScoreHandler:
- Метод
readкорректно читает файл и создаёт объектыPlayerRecord - Метод
saveкорректно записывает данные в файл
Подсказка: для тестирования методов, которые используют
input(), можно использоватьunittest.mock.patch.
| Name | Mode | Score |
|---|---|---|
| Vlad | Normal | 14 |
| Test | Hard | 12 |
| Jack | Normal | 12 |
| Vlad | Hard | 10 |
| Jack | Hard | 9 |