Refactoring the Game
Tags: elixir, algorithms, webdev, tutorial
Published: 2022-09-01 05:15:49

---
series: Rock, Paper, and Scissors with Elixir
---

Our **game** is almost finished (_at least part of it_). But we can improve the module game and do it together.

**Let's start...**

I found modular arithmetic, looking for something with a mathematical approach to solving the logic of our game (if you are interested in the subject, take a look [here](https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/what-is-modular-arithmetic)).

Now we'll use modular arithmetic to add a few math to our code. And make the code brighter and cleaner.

## The mathematical approach

The mod function provides the remainder when one integer is divided by another. And it will help us to a cyclical relationship between the three choices: Rock, Paper, and Scissors.

```Elixir
r = a mod b
r is the remainder when a is divided by b
```

So, looking into our code, more specifically on module attributes:

```Elixir
  @stone 1
  @paper 2
  @scissor 3
```

I saw a tip that can help us to make calculus more efficiently:

```Elixir
(first_player_choice - second_player_choice) % 3
```

## Refactoring the Game

Adding the function to calculate the result of the game `game_calc`:

```Elixir
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
```

And then now, we can simplify the function result:

```Elixir
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
```

### Running the tests:

```Bash
mix test
```

Something is wrong. We got three warnings and one failure message when we ran the tests.

```Bash
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
```

To solve the warning messages, we need to remove the module attributes:

```Elixir
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
```

And now, if we rerun the tests:

```Bash
mix test
```

We'll see only the tests failure:

```Bash
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
```

### Understanding the failure message

The failure is because we pass to kernel function rem/2 a dividend negative in our formula. And according to the documentation, this kernel function uses truncated division, which means that the result will always have the sign of the dividend.

When first player wins when first player chooses stone and second player chooses scissors, the result is `-2`:

```Elixir
# 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
```

### Solving the failure message

According to the documentation, the function `Integer.mod/2` Computes the modulo remainder of an integer division.

**It's important to know:** `Integer.mod/2` uses floored division, which means that the result will always have the sign of the divisor.

So, when first player wins when first player chooses stone and second player chooses scissors, the result is `1`:

```Elixir
# 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
```

So, To solve the failure message, we need to remove the rem/2 function and add the Integer.mod/2:

```Elixir
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
```

And now, finally, if we rerun the tests:

```Bash
mix test
```

All the tests pass with success \o/:

```Bash
Compiling 1 file (.ex)
.........

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

Randomized with seed 992719
```

It's **time to celebrate**, the game **Rock, Paper, and Scissors** is **"done"**!

**Repository of the project:** https://github.com/dnovais/rock_paper_scissor_elixir

See you soon!

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

---

#### Sources and references
- 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