Рефакторинг игры

Наша игра почти закончена (по крайней мере, ее часть). Но мы можем улучшить модуль игры и сделать это вместе.

Давайте начнем…

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

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

Математический подход

Функция mod дает остаток при делении одного целого числа на другое. И это поможет нам получить циклическую зависимость между тремя вариантами: Камень, Бумага и Ножницы.

r = a mod b
r is the remainder when a is divided by b
Вход в полноэкранный режим Выход из полноэкранного режима

Итак, смотрим в наш код, а точнее на атрибуты модуля:

  @stone 1
  @paper 2
  @scissor 3
Вход в полноэкранный режим Выход из полноэкранного режима

Я увидел совет, который может помочь нам сделать вычисления более эффективными:

(first_player_choice - second_player_choice) % 3
Вход в полноэкранный режим Выйти из полноэкранного режима

Рефакторинг игры

Добавление функции для вычисления результата игры game_calc:

defmodule Game do
  @moduledoc """
  Documentation for `Game`.
  """

  @stone 1
  @paper 2
  @scissor 3

  def play(first_player_choice, second_player_choice) do
    result(first_player_choice, second_player_choice)
  end

  defp result(first_player_choice, second_player_choice) do
    cond do
      first_player_choice == second_player_choice ->
        {:ok, "Draw!"}

      first_player_choice == @scissor && second_player_choice == @paper ->
        {:ok, "First player win!!!"}

      first_player_choice == @paper && second_player_choice == @stone ->
        {:ok, "First player win!!!"}

      first_player_choice == @stone && second_player_choice == @scissor ->
        {:ok, "First player win!!!"}

      first_player_choice == @paper && second_player_choice == @scissor ->
        {:ok, "Second player win!!!"}

      first_player_choice == @stone && second_player_choice == @paper ->
        {:ok, "Second player win!!!"}

      first_player_choice == @scissor && second_player_choice == @stone ->
        {:ok, "Second player win!!!"}
    end
  end

  defp game_calc(first_player_item, second_player_item) do
    rem(first_player_item - second_player_item, 3)
  end
end
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем упростить результат функции:

defmodule Game do
  @moduledoc """
  Documentation for `Game`.
  """

  @stone 1
  @paper 2
  @scissor 3

  def play(first_player_choice, second_player_choice) do
    result(first_player_choice, second_player_choice)
  end

  defp result(first_player_choice, second_player_choice) do
    game_calc_result = game_calc(first_player_choice, second_player_choice)

    case game_calc_result do
      0 -> {:ok, "Draw!"}
      1 -> {:ok, "First player win!!!"}
      _ -> {:ok, "Second player win!!!"}
    end
  end

  defp game_calc(first_player_item, second_player_item) do
    rem(first_player_item - second_player_item, 3)
  end
end
Войти в полноэкранный режим Выход из полноэкранного режима

Запуск тестов:

mix test
Войти в полноэкранный режим Выход из полноэкранного режима

Что-то не так. При выполнении тестов мы получили три предупреждения и одно сообщение об ошибке.

Compiling 1 file (.ex)
warning: module attribute @scissor was set but never used
  lib/game.ex:8

warning: module attribute @paper was set but never used
  lib/game.ex:7

warning: module attribute @stone was set but never used
  lib/game.ex:6

..

  1) test Game.play/2 when first player wins when first player chooses stone and second player chooses scissors (GameTest)
     test/game_test.exs:55
     Assertion with == failed
     code:  assert match == "First player win!!!"
     left:  "Second player win!!!"
     right: "First player win!!!"
     stacktrace:
       test/game_test.exs:61: (test)

......

Finished in 0.05 seconds (0.00s async, 0.05s sync)
9 tests, 1 failure

Randomized with seed 811857
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы устранить предупреждения, нам нужно удалить атрибуты модуля:

defmodule Game do
  @moduledoc """
  Documentation for `Game`.
  """

  def play(first_player_choice, second_player_choice) do
    result(first_player_choice, second_player_choice)
  end

  defp result(first_player_choice, second_player_choice) do
    game_calc_result = game_calc(first_player_choice, second_player_choice)

    case game_calc_result do
      0 -> {:ok, "Draw!"}
      1 -> {:ok, "First player win!!!"}
      _ -> {:ok, "Second player win!!!"}
    end
  end

  defp game_calc(first_player_item, second_player_item) do
    rem(first_player_item - second_player_item, 3)
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

А теперь, если мы повторно запустим тесты:

mix test
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы увидим только сбой тестов:

Compiling 1 file (.ex)
....

  1) test Game.play/2 when first player wins when first player chooses stone and second player chooses scissors (GameTest)
     test/game_test.exs:55
     Assertion with == failed
     code:  assert match == "First player win!!!"
     left:  "Second player win!!!"
     right: "First player win!!!"
     stacktrace:
       test/game_test.exs:61: (test)

....

Finished in 0.04 seconds (0.00s async, 0.04s sync)
9 tests, 1 failure

Randomized with seed 730068
Войти в полноэкранный режим Выход из полноэкранного режима

Понимание сообщения о сбое

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

Когда первый игрок выигрывает, когда первый игрок выбирает камень, а второй игрок выбирает ножницы, результат будет -2:

# stone = 1
# paper = 2
# scissor = 3

# R = (first_player_choice - second_player_choice) % 3
# R = (stone - scissors) % 3
# R = (1 - 3) % 3

# In elixir using rem/2

rem(1-3, 3)
> -2
Войти в полноэкранный режим Выход из полноэкранного режима

Решение проблемы с сообщением о сбое

Согласно документации, функция Integer.mod/2 вычисляет остаток от деления целого числа по модулю.

Это важно знать: Integer.mod/2 использует деление с полом, что означает, что результат всегда будет иметь знак делителя.

Так, если первый игрок выигрывает, когда первый игрок выбирает камень, а второй игрок выбирает ножницы, результат будет 1:

# stone = 1
# paper = 2
# scissor = 3

# R = (first_player_choice - second_player_choice) % 3
# R = (stone - scissors) % 3
# R = (1 - 3) % 3

# In elixir using rem/2

Integer.mod(1-3, 3)
> 1
Войти в полноэкранный режим Выход из полноэкранного режима

Итак, чтобы решить проблему с сообщением о сбое, нам нужно удалить функцию rem/2 и добавить Integer.mod/2:

defmodule Game do
  @moduledoc """
  Documentation for `Game`.
  """

  def play(first_player_choice, second_player_choice) do
    result(first_player_choice, second_player_choice)
  end

  defp result(first_player_choice, second_player_choice) do
    game_calc_result = game_calc(first_player_choice, second_player_choice)

    case game_calc_result do
      0 -> {:ok, "Draw!"}
      1 -> {:ok, "First player win!!!"}
      _ -> {:ok, "Second player win!!!"}
    end
  end

  defp game_calc(first_player_item, second_player_item) do
    Integer.mod(first_player_item - second_player_item, 3)
  end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

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

mix test
Войти в полноэкранный режим Выйти из полноэкранного режима

Все тесты пройдены успешно o/:

Compiling 1 file (.ex)
.........

Finished in 0.04 seconds (0.00s async, 0.04s sync)
9 tests, 0 failures

Randomized with seed 992719
Войти в полноэкранный режим Выход из полноэкранного режима

Пришло время праздновать, игра «Камень, бумага и ножницы» «сделана»!

Репозиторий проекта: https://github.com/dnovais/rock_paper_scissor_elixir

До скорой встречи!

Контакты

Email: contato@diegonovais.com.br
Linkedin: https://www.linkedin.com/in/diegonovais/
Twitter: https://twitter.com/diegonovaistech


Источники и ссылки

  • https://hexdocs.pm/elixir/1.12/Integer.html#mod/2
  • https://www.cs.drexel.edu/~jpopyack/Courses/CSP/Fa18/notes/CS150_RockPaperScissors_Revisited.pdf
  • https://www.cin.ufpe.br/~gdcc/matdis/aulas/aritmeticaModular.pdf
  • https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/what-is-modular-arithmetic

Оцените статью
devanswers.ru
Добавить комментарий