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!
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
Top comments (0)