Diego Novais

Posted on

# Refactoring the Game

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).

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.

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

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

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

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

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

## Refactoring the Game

Adding the function to calculate the result of the game `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
``````

And then now, we can simplify the function result:

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

``````mix test
``````

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

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

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

``````mix test
``````

We'll see only the tests failure:

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

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

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

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

``````mix test
``````

All the tests pass with success \o/:

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