Рубрики
Без рубрики

Ножницы для бумаги с использованием модульной арифметики

Забудьте о бесконечных заявлениях! Пришло время решить ножницы для бумаги с использованием модульной арифметики в Python. Tagged с Python, начинающими, учебником, программированием.

Недавно студенты из моего курса Java выполняли задание, похожее на ножницы для рок -бумаги, когда один из них придумал умный вопрос: можем ли мы сравнить каждый выбор численно? Подумав, я понял, что мы можем полностью внедрить ножницы для рок -бумаги, используя модульную арифметику.

Правила ножниц рок -бумаги

Прежде чем мы вступим в решение, я считаю, что всегда полезно установить требования. В частности, что такое ножницы для рок -бумаги и как мы можем моделировать их в компьютерной игре?

Просто чтобы мы ясны, Rock Paper Scissors-это стратегическая игра один на один, где люди одновременно выбирают камень, бумагу или ножницы. Победитель определяется на основе отношений между различными вариантами: Paper Beats Rock, Rock Beats Ncissors и ножницы бьют бумагу.

Чтобы моделировать этот тип игры в программе, нам придется установить некоторые основные правила. Вместо камня, бумаги и ножниц мы будем использовать числа 1, 2 и 3. Это позволяет нам избежать проблем ввода пользователя. Например, кто хочет справиться со всеми вариациями струн (то есть рок, рок, рок и т. Д.)?

Кроме того, мы настроим программу, чтобы пользователи могли воспроизводить компьютер. Для этого у нас будет программа генерировать случайное число от 1 до 3, запрашивая число от 1 до 3 у пользователя. Если пользователь входит в неверный номер, он проиграет автоматически.

Имея эти правила, мы должны иметь возможность реализовать относительно тривиальное решение для ножниц для каменных бумаг.

Рок -ножницы для ножниц

Конечно, по моему опыту, реализация ножниц рок -бумаги на самом деле не так просто. На самом деле, я видел, как многие студенты пытаются написать программу по ряду причин.

В некоторых случаях студенты только что выучили управление потоком , поэтому они изо всех сил пытаются установить свои условия. В других случаях им трудно упростить решение, поэтому повсюду есть случаи, поэтому я стараюсь избегать обработки ввода пользователя, если это возможно.

В последнем случае я считаю, что многие студенты хотят явно охватить все возможные случаи:

  1. Бумага против бумаги
  2. Бумага против рока
  3. Бумага против ножниц
  4. Рок против рока
  5. Рок против бумаги
  6. Рок против ножниц
  7. Ножницы против ножниц
  8. Ножницы против бумаги
  9. Ножницы против рока
  10. Плохой ввод

Очень быстро студенты поймут, что выписывать 10, если заявления болезненны. К пятому они начнут задаваться вопросом, делают ли они что -то не так. В этот момент они обычно пересматривают свою стратегию, сокращают свои тестовые случаи и, возможно, зажигают свою страсть к информатике. В конце концов, я всегда считаю, что уменьшающая сложность является действительно забавной и полезной задачей. Конечно, не все похожи на меня.

Во всяком случае, многие студенты заметят, что в решении выше есть некоторые избыточные случаи. В частности, они могут обнаружить, что они могут уменьшить все три случая связывания до одного случая (т.е. Выбор ). К сожалению, это уменьшает только десять случаев до восьми.

Итак, есть ли лучший способ? Ну, по словам одного из моих учеников, но мы еще не там! Вместо этого я хочу потратить некоторое время на код.

Rock Paper Scissors Solutions

Когда я впервые решил эту проблему, я был Написание экземпляра Junit Test , чтобы я мог автоматизировать оценку студентов. Как видите, я пошел по маршруту восьми корпусов сверху, но я использовал перечисления для целей ясности кода. Зачем называть Рок как 1, когда я мог бы назвать его тем, что это такое, Игра. РОК ?

Подумав об этой проблеме, я уверен, что есть бесконечное количество более чистых решений. Например, если бы я вернулся и переработал свой код, я бы, вероятно, заполнил какую -то таблицу поиска. Таким образом, я мог бы использовать выбор пользователя и выбор компьютера в качестве индексов в матрице. Вместо того, чтобы писать восемь операторов, мне просто нужно было получить результат из таблицы.

Тем не менее, я хочу потратить некоторое время, чтобы пройти через некоторые типичные решения. Для моего собственного здравомыслия все решения будут в Python, а не на Java, но идея будет такой же.

Общий шаблон

Все следующие решения поделится одним и тем же кодом. Чтобы избежать копирования шаблона, мы рассмотрим все это сейчас:

import random
import sys

# Create number to choice mapping
mapping = {
  1: "Rock",
  2: "Paper",
  3: "Scissors"
}

# Generate computer choice
pc_choice = random.randint(1, 3)
pc_choice_output = "I chose %s." % mapping[pc_choice]

# Request user choice
try:
  user_choice = int(input("Choose Rock (1), Paper (2), or Scissors (3): "))
  user_choice_output = "You chose %s." % mapping[user_choice]
except (ValueError, KeyError):
  print(pc_choice_output)
  print("You chose nothing.")
  print("You lose by default.")
  sys.exit(0)

# Share choices
print(pc_choice_output)
print(user_choice_output)

# Setup results
i_win = "%s beats %s - I win!" % (mapping[pc_choice], mapping[user_choice])
u_win = "%s beats %s - you win!" % (mapping[user_choice], mapping[pc_choice])
tie = "Tie!"

В этом фрагменте кода мы начнем с импорта случайный Библиотека, которую мы используем для создания случайного выбора компьютерного игрока (подробнее об этом позже). В дополнение к случайной библиотеке, мы также импортируем Sys Библиотека, которую мы будем использовать для выхода на плохой ввод:

import random
import sys

После этого мы создаем число для отображения строки, которое отображает наш числовой выбор с струнами, которые они представляют – рок, бумага и ножницы:

mapping = {
  1: "Rock",
  2: "Paper",
  3: "Scissors"
}

Затем мы генерируем выбор компьютера, используя случайную библиотеку, на которую мы ссылались ранее. Пока мы там, мы создаем строку, которая укажет, какой выбор компьютер сделал для пользователя:

pc_choice = random.randint(1, 3)
pc_choice_output = "I chose %s." % mapping[pc_choice]

После этого мы приходим к блоку Try/кроме блока, который мы используем для некоторой грубой проверки данных. В частности, мы хотим убедиться, что пользователь вводит допустимый номер. Если пользователь входит в число за пределами нашего ожидаемого диапазона или вообще не вводит число, мы хотели бы определить это. Если мы это сделаем, мы распечатаем хороший диалог, приводящий к потере пользователя, которая завершает программу:

try:
  user_choice = int(input("Choose Rock (1), Paper (2), or Scissors (3): "))
  user_choice_output = "You chose %s." % mapping[user_choice]
except (ValueError, KeyError):
  print(pc_choice_output)
  print("You chose nothing.")
  print("You lose by default.")
  sys.exit(0)

Как только у обоих игроков есть достоверные варианты, мы можем распечатать их для пользователя:

print(pc_choice_output)
print(user_choice_output)

Наконец, мы установили некоторые строки результатов, которые мы заполним позже:

i_win = "%s beats %s - I win!" % (mapping[pc_choice], mapping[user_choice])
u_win = "%s beats %s - you win!" % (mapping[user_choice], mapping[pc_choice])
tie = "Tie!"

Если мы запустим решение до этого момента, мы могли бы увидеть что -то вроде следующего:

Choose Rock (1), Paper (2), or Scissors (3): 2
I chose Rock.
You chose Paper.

Между тем, неудачное исполнение может выглядеть примерно как следующее:

Choose Rock (1), Paper (2), or Scissors (3): 5
I chose Paper.
You chose nothing.
You lose by default.

На этом этапе мы можем копаться в логике побед/поражения.

10-й казевый бегемот

Если бы мы хотели смоделировать все десять случаев в Python, мы могли бы сделать это, используя следующие девять операторов (о плохом вводе уже позаботился):

# Share winner
if pc_choice == 1 and user_choice == 1: # Rock vs. Rock
  print(tie)
elif pc_choice == 2 and user_choice == 2: # Paper vs. Paper
  print(tie)
elif pc_choice == 3 and user_choice == 3: # Scissors vs. Scissors
  print(tie)
elif pc_choice == 1 and user_choice == 2: # Rock vs. Paper
  print(u_win)
elif pc_choice == 1 and user_choice == 3: # Rock vs. Scissors
  print(i_win)
elif pc_choice == 2 and user_choice == 1: # Paper vs. Rock
  print(i_win)
elif pc_choice == 2 and user_choice == 3: # Paper vs. Scissors
  print(u_win)
elif pc_choice == 3 and user_choice == 1: # Scissors vs. Rock
  print(u_win)
else: # Scissors vs. Paper
  print(i_win)

Конечно, такое решение оставляет желать лучшего. Например, существует огромное количество дублированного кода. Следующие строки появляются три раза в решении:

print(u_win)
print(i_win)
print(tie)

Кроме того, это немного трудно помнить о картировании, когда мы делаем наши сравнения. В результате я вкладываю дополнительные комментарии к ясности. Тем не менее, код было бы намного приятнее говорить за себя.

В любом случае, это совершенно достоверное решение, но я думаю, что мы можем добиться большего.

8-й устойчивый

Как упоминалось ранее, мы можем уменьшить десять случаев вниз до восьми, консолидируя все сценарии галстуков:

# Share winner
if pc_choice == user_choice: # Same choice
  print(tie)
elif pc_choice == 1 and user_choice == 2: # Rock vs. Paper
  print(u_win)
elif pc_choice == 1 and user_choice == 3: # Rock vs. Scissors
  print(i_win)
elif pc_choice == 2 and user_choice == 1: # Paper vs. Rock
  print(i_win)
elif pc_choice == 2 and user_choice == 3: # Paper vs. Scissors
  print(u_win)
elif pc_choice == 3 and user_choice == 1: # Scissors vs. Rock
  print(u_win)
else: # Scissors vs. Paper
  print(i_win)

В случае Tie мы знаем, что пользователь и компьютер сделали тот же выбор, поэтому мы можем сравнить их значения напрямую. В результате мы можем быстро обрезать два случая сверху.

К сожалению, у нас все еще есть немного дублированного кода, но похудение этих случаев намного сложнее. Хотя мы могли бы захотеть объединить все случаи, когда компьютер выигрывает, не совсем ясно, как мы это сделаем.

Аналогичным образом, мы можем заметить, что некоторые из этих случаев являются только обратными друг другу (то есть скала против бумаги и бумаги против скалы). Может быть, есть какой -то способ объединить эти случаи, но это не ясно.

Гнездовая кукла

Один из способов, которым мы могли бы попытаться уменьшить наш дублированный код, – это представить некоторые вложенные заявления:

# Share winner
if pc_choice == user_choice:
  print(tie)
elif pc_choice == 1: # Rock
  if user_choice == 2: # Paper
    print(u_win)
  else: # Scissors
    print(i_win)
elif pc_choice == 2: # Paper
  if user_choice == 1: # Rock
    print(i_win)
  else: # Scissors
    print(u_win)
else: # Scissors
  if user_choice == 1: # Rock
    print(u_win)
  else: # Paper
    print(i_win)

К сожалению, это решение на самом деле не уменьшает наш код вообще. В некотором смысле это на самом деле более запутанно. Есть ли что -нибудь, что мы можем сделать, чтобы немного сократить код? Я рад, что ты спросил!

Модульный арифметический минималистский

Когда я впервые придумал это решение, это было результатом вопроса студента о сравнении двух вариантов непосредственно с использованием реляционных операторов (>, <, == и т. Д.). И если мы думаем об этом, это имеет большой смысл:

  • Камень
  • Бумага
  • Ножницы
  • Рок> ножницы
  • Рок <бумага
  • Бумага> Рок
  • Бумага <ножницы
  • Ножницы> бумага
  • Ножницы <рок

По какой -то причине эти отношения кажутся действительно интуитивными. В конце концов, ножницы сильнее бумаги, но слабее скалы. Таким образом, имеет смысл думать о них как о математических количествах.

Проблема в том, что цифры не отображают это циклическое свойство, которое делают рок, бумага и ножницы. Конечно, три больше двух, но один не больше трех. Так что же нам делать?

Оказывается, есть математический оператор, который может просто спасти день под названием Modulo. Для наших целей оператор модуля позволит нам установить эту циклическую связь между тремя вариантами. Взглянем:

# Share results
if pc_choice == user_choice:
  print(tie)
elif (user_choice + 1) % 3 == pc_choice % 3:
  print(i_win)
else:
  print(u_win)

Как это для резкого сокращения случаев? Здесь мы перешли от худшего сценария из десяти случаев до четырех (включая плохой ввод), но как это работает?

Оказывается, мы должны быть осторожны с картированием нашего выбора. В нашем случае победа происходит в одном направлении в цикле, когда проигрыш происходит в другом направлении. Другими словами, три удара два, два удара один, и один бьет три:

Чтобы захватить эту циклическую связь, мы используем следующее условие:

(user_choice + 1) % 3 == pc_choice % 3

Левая половина этого выражения вычисляет следующий выбор в цикле. Если пользователь выбирает Rock, выражение будет оцениваться до двух, потому что (1 + 1) % 3 составляет два.

Если следующий выбор в цикле также станет выбором компьютера, мы знаем, что пользователь проиграл. Аналогичным образом, если следующий выбор в цикле не является выбором компьютера, мы знаем, что мы, должно быть, выиграли (при условии, что мы уже протестировали на галстук).

С помощью этого решения нам больше не придется иметь дело со всем этим дубликатом кода. У нас есть один случай для плохого ввода, один случай для связей, один случай для побед и один случай для потерь.

Простая модификация

После того, как я придумал модульное решение, я понял, что еще есть способы упростить решение. В частности, это было бы полезно для Начните отображение с нуля Анкет

Одним из сбоев, с которыми я столкнулся в предыдущем решении, был, когда пользователь выбрал бумагу. В результате выражение (user_choice + 1) % 3 оценит ноль, который не является одним из наших выборов. Чтобы компенсировать, решение должно также оценить модулю выбора компьютера. Следующее полное решение устраняет эту проблему:

import random
import sys

# Create number to choice mapping
mapping = {
  0: "Rock",
  1: "Paper",
  2: "Scissors"
}

# Generate computer choice
pc_choice = random.randint(0, 2)
pc_choice_output = "I chose %s." % mapping[pc_choice]

# Request user choice
try:
  user_choice = int(input("Choose Rock (0), Paper (1), or Scissors (2): "))
  user_choice_output = "You chose %s." % mapping[user_choice]
except (ValueError, KeyError):
  print(pc_choice_output)
  print("You chose nothing.")
  print("You lose by default.")
  sys.exit(0)

# Share choices
print(pc_choice_output)
print(user_choice_output)

# Setup results
i_win = "%s beats %s - I win!" % (mapping[pc_choice], mapping[user_choice])
u_win = "%s beats %s - you win!" % (mapping[user_choice], mapping[pc_choice])
tie = "Tie!"

# Share winner
if pc_choice == user_choice:
  print(tie)
elif (user_choice + 1) % 3 == pc_choice:
  print(i_win)
else:
  print(u_win)

Вот и все! Мы создали игру ножниц с ножницами командной строки, используя модульную арифметику в 40 строках кода.

Сила модульной арифметики

После написания этой статьи я пошел в Интернет, чтобы посмотреть, сделал ли кто -нибудь подобное раньше, и, к сожалению, я не первый, к сожалению. С другой стороны, есть много информации о том, почему это решение работает.

Оказывается, существует другое выражение, которое лучше отражает отношения между различными вариантами:

(user_choice - pc_choice) % 3

Из этого выражения у нас есть три случая:

  • 0 (галстук)
  • 1 (Пользователь выигрывает)
  • 2 (компьютер выигрывает)

И мы можем даже расширить это решение на произвольное количество вариантов. Одна игра, которую я видел, упоминалась несколько раз, – это каменная бумажная ящерица Спока. В этом случае мы будем кодировать пять вариантов, используя значения Zero – четыре и определили победителей, используя следующее выражение:

(user_choice - pc_choice) % 5

Из этого выражения у нас все еще есть три случая:

  • 0 (галстук)
  • 1, 2 (пользователь выигрывает)
  • 3, 4 (компьютер выигрывает)

Другими словами, первая половина разницы приводит к победе для пользователя, в то время как вторая половина разницы приводит к потере для пользователя.

Как мы видим, такого рода решения масштабируются к произвольному количеству вариантов, что является тревожным эффективным. Вместо того, чтобы записывать квадрат количества вариантов в качестве случаев, нам нужно справиться только с теми же тремя сценариями. Как это для силы модульной арифметики?

Поделитесь своими историями

Несмотря на то, что я не первым решал ножницы для рок -бумаги, используя модульную арифметику, я действительно подумал, что это круто и интересно, и я надеюсь, что вы тоже это сделали.

Если вы знаете какие -либо сложные проблемы, которые имеют такое элегантное решение, дайте нам знать в комментариях. Я был бы заинтересован в изучении возможных шаблонов в этих типах проблемы. Возможно, есть какая -то техника для сокращения ветвления, которую мы могли бы выявить из нескольких примеров.

Во всяком случае, еще раз спасибо за то, что нашли время, чтобы прочитать одну из моих статей. Если вам понравилось то, что вы читали, подумайте о передаче его другу. Слово устья может помочь мне помочь.

Пока вы здесь, я полагаю, что сейчас самое время упомянуть, что у меня есть веб -сайт под названием Renegade Coder Анкет Большая часть кодирующего контента вы можете найти здесь, на dev.to , но у меня также есть некоторый контент только для участников, который охватывает такие темы, как мое путешествие к доктору наук, мой опыт преподавания, мой опыт в отрасли и мою личную жизнь. Если что -то из этого звучит интересно, или вы просто хотели бы получить мою информационную рассылку, не стесняйтесь Зарегистрируйтесь Анкет

До скорого!

Оригинал: “https://dev.to/renegadecoder94/rock-paper-scissors-using-modular-arithmetic-2j4p”