Камень, ножницы, бумага на Python за один вечер превращается из трёх условий в настоящую игру: с вводом, случайным ходом компьютера, счётом и серией до трёх побед. Соберём проект по слоям, чтобы каждая новая часть оставалась понятной, а ошибки можно было найти за минуту.
Нужен только Python 3. Дополнительные библиотеки не устанавливаем: функцию choice() берём из стандартного модуля random. Сначала запустим один раунд, затем вынесем правила в функцию и добавим игровой цикл.
Игрок вводит слово, компьютер случайно выбирает один из трёх вариантов.
Функция возвращает победителя раунда, не изменяя общий счёт.
Цикл хранит очки и заканчивается, когда кто-то набрал три победы.
Как представить ходы и правила игры
Самая полезная привычка в небольшом проекте: сначала описать данные. Ходы удобно хранить в списке moves, потому что random.choice(moves) выбирает готовый элемент. Победные пары запишем в множество кортежей. Оно буквально отвечает на вопрос: входит ли пара (игрок, компьютер) в список победных?
| Ход игрока | Побеждает | Пара в коде |
|---|---|---|
| камень | ножницы | ('камень', 'ножницы') |
| ножницы | бумагу | ('ножницы', 'бумага') |
| бумага | камень | ('бумага', 'камень') |
Хранение правил в парах помогает отделить факты игры от управляющего кода. Условие не обязано знать, почему камень побеждает ножницы: оно лишь проверяет наличие пары. Такой подход особенно заметен при расширении проекта. Если добавить два новых жеста, длинная цепочка if/elif быстро разрастётся, а набор победных пар останется обычным перечнем, который можно просмотреть и проверить отдельно.

У каждой фигуры ровно одна победная и одна проигрышная пара, поэтому правила удобно хранить как данные.
winning_pairs, проверка победы занимает одно условие. Добавить новый вариант тоже проще: меняются данные, а не весь алгоритм.Пишем один раунд и проверяем ввод
Метод strip() удаляет случайные пробелы по краям, а lower() приводит буквы к нижнему регистру. Благодаря этому ввод Камень превращается в ожидаемую строку камень. До сравнения ходов обязательно проверяем, что слово вообще есть в списке.
from random import choice
moves = ['камень', 'ножницы', 'бумага']
winning_pairs = {
('камень', 'ножницы'),
('ножницы', 'бумага'),
('бумага', 'камень'),
}
player = input('Ваш ход: ').strip().lower()
if player not in moves:
print('Введите: камень, ножницы или бумага')
else:
computer = choice(moves)
print(f'Компьютер выбрал: {computer}')
if player == computer:
print('Ничья')
elif (player, computer) in winning_pairs:
print('Вы победили')
else:
print('Компьютер победил')- Запустите файл несколько раз и получите победу, поражение и ничью.
- Введите
КАМЕНЬзаглавными буквами: ход должен распознаться. - Введите
огонь: программа должна показать подсказку, а не выбрать победителя.
Здесь есть ввод, нормализация строки, случайный выбор и ветвление. Но после результата процесс завершается, а неправильный ввод приходится исправлять новым запуском. Следующий шаг решает обе проблемы.
Добавляем функцию, счёт и серию до трёх побед
Отделим вычисление результата от общения с игроком. Функция round_winner() получает два хода и возвращает строку player, computer или draw. Она ничего не печатает и не знает про счёт, поэтому её легко тестировать отдельно.
from random import choice
MOVES = ['камень', 'ножницы', 'бумага']
WINNING_PAIRS = {
('камень', 'ножницы'),
('ножницы', 'бумага'),
('бумага', 'камень'),
}
def round_winner(player, computer):
if player == computer:
return 'draw'
if (player, computer) in WINNING_PAIRS:
return 'player'
return 'computer'
player_score = 0
computer_score = 0
while player_score < 3 and computer_score < 3:
player = input('Ваш ход: ').strip().lower()
if player not in MOVES:
print('Допустимые ходы: камень, ножницы, бумага')
continue
computer = choice(MOVES)
winner = round_winner(player, computer)
if winner == 'player':
player_score += 1
message = 'Раунд ваш'
elif winner == 'computer':
computer_score += 1
message = 'Раунд компьютера'
else:
message = 'Ничья, очки не меняются'
print(f'{message}. Компьютер: {computer}')
print(f'Счёт {player_score}:{computer_score}\n')
if player_score == 3:
print('Вы выиграли серию!')
else:
print('Компьютер выиграл серию.')
Команда continue возвращает выполнение к началу цикла и не даёт неверному вводу превратиться в сыгранный раунд.
WINS_TO_FINISH. Тогда серия до пяти побед не потребует искать одинаковые числа по всему файлу.Обратите внимание на состояние программы между раундами. Список ходов и правила не меняются, а два счётчика сохраняют накопленный результат. Переменные player, computer, winner и message относятся только к текущему проходу цикла. Такое разделение помогает отлаживать игру: если неверен победитель, проверяйте функцию правил; если победитель верный, но число очков не меняется, смотрите ветку обновления счёта.
Разбираем частые ошибки и улучшаем проект
Вызов choice() стоит до цикла. Перенесите его внутрь while, чтобы новый ход создавался в каждом раунде.
Счёт меняется до проверки равенства. Сначала получите результат, затем увеличивайте только счёт победителя.
Проверяйте ввод внутри цикла и применяйте continue. Новый раунд начнётся без изменения очков.
Проверьте порядок элементов в кортеже: сначала ход игрока, затем ход компьютера.
| Улучшение | Что потренирует | Минимальное изменение |
|---|---|---|
Команда выход | Досрочное завершение цикла | Проверка перед выбором компьютера |
| Серия до N побед | Константы и условия | WINS_TO_FINISH = 5 |
| История раундов | Списки и словари | Добавлять результат через append() |
| Режим ящерица и Спок | Моделирование правил | Расширить список и множество пар |

Добавляйте по одной возможности и после каждого шага снова проходите три базовых исхода.
Как протестировать игру и что изучать дальше
Случайность мешает воспроизводить конкретный раунд, поэтому саму функцию правил тестируем фиксированными значениями. Семь коротких проверок закрывают три победы игрока, три поражения и ничью. Если позже изменятся сообщения или интерфейс, алгоритм правил всё равно останется проверенным.
assert round_winner('камень', 'ножницы') == 'player'
assert round_winner('ножницы', 'бумага') == 'player'
assert round_winner('бумага', 'камень') == 'player'
assert round_winner('ножницы', 'камень') == 'computer'
assert round_winner('бумага', 'ножницы') == 'computer'
assert round_winner('камень', 'бумага') == 'computer'
assert round_winner('камень', 'камень') == 'draw'Если ни один assert не вывел сообщение, все проверки прошли. Намеренно сломайте одну победную пару и запустите файл снова: Python покажет AssertionError. Это полезный эксперимент, потому что демонстрирует назначение теста. Он не доказывает, что в программе вообще нет ошибок, но быстро замечает повторное появление уже проверенной проблемы после следующего изменения кода.
Почему используется random.choice, а не randint?
choice сразу возвращает элемент списка. С randint пришлось бы сначала получить индекс и отдельно обратиться к списку.
Зачем функция возвращает строку, а не печатает результат?
Возвращаемое значение можно проверить тестом, использовать для счёта или показать в другом интерфейсе.
Почему winning_pairs является множеством?
Нам важна быстрая проверка принадлежности пары, а не порядок элементов. Множество хорошо выражает эту задачу.
Как сделать игру нечувствительной к регистру?
Применить lower к введённой строке, а все допустимые варианты хранить в нижнем регистре.
Можно ли сделать графическую версию?
Да. Сначала сохраните функцию правил, а ввод и print замените кнопками и текстовыми элементами выбранной GUI-библиотеки.
Закрепите функции, списки и циклы в курсе Python. Затем выберите ещё один проект из подборки 10 мини-проектов на Python или попробуйте собрать версию прямо на смартфоне по статье Python на Android.
Главный результат этого проекта не игра, а архитектура: данные правил отдельно, вычисление отдельно, интерфейс отдельно. Этот принцип переносится на викторины, карточные игры и небольших ботов.
