Skip to content

Instantly share code, notes, and snippets.

@mbuffa
Created October 26, 2023 12:52
Show Gist options
  • Save mbuffa/d5df6e2fc5c70f7c3a292af68f38be24 to your computer and use it in GitHub Desktop.
Save mbuffa/d5df6e2fc5c70f7c3a292af68f38be24 to your computer and use it in GitHub Desktop.
World of Warcraft Trade
# This is a possible implementation for a trade in copper, silver and gold
# coins, as it happens in a few MMORPGs, such as World of Warcraft.
# A simpler and smarter approach would be to handle it as a single integer,
# and let the UI display the amount in the different coins. This is just
# an exercise for fun.
# Run with `elixir pay.ex`
defmodule Trade do
defmodule Amount do
defstruct gold: 0, silver: 0, copper: 0
def new(copper, silver, gold)
def new(_, 100, _), do: {:error, :silver_too_big}
def new(100, _, _), do: {:error, :copper_too_big}
def new(copper, silver, gold) do
{:ok, %Amount{copper: copper, silver: silver, gold: gold}}
end
end
def pay(cost, purse)
def pay(
%Trade.Amount{copper: cost_in_copper, silver: cost_in_silver, gold: cost_in_gold},
%Trade.Amount{copper: owned_copper, silver: owned_silver, gold: owned_gold}
) do
owned = owned_copper + owned_silver * 100 + owned_gold * 10000
cost = cost_in_copper + cost_in_silver * 100 + cost_in_gold * 10000
if owned < cost do
{:error, "Not enough money"}
else
remaining = cost - owned
remaining_gold = div(remaining, 10000)
remaining_silver =
remaining
|> div(100)
|> Kernel.-(remaining_gold * 100)
remaining_copper =
remaining
|> Kernel.-(remaining_gold * 10000)
|> Kernel.-(remaining_silver * 100)
{:ok, %Trade.Amount{
gold: abs(remaining_gold),
silver: abs(remaining_silver),
copper: abs(remaining_copper)
}}
end
end
end
ExUnit.start()
defmodule Test do
use ExUnit.Case
test "not enough money" do
{:ok, cost} = Trade.Amount.new(1, 0, 0)
{:ok, owned} = Trade.Amount.new(0, 0, 0)
assert Trade.pay(cost, owned) == {:error, "Not enough money"}
{:ok, cost} = Trade.Amount.new(0, 1, 0)
{:ok, owned} = Trade.Amount.new(0, 0, 0)
assert Trade.pay(cost, owned) == {:error, "Not enough money"}
{:ok, cost} = Trade.Amount.new(0, 0, 1)
{:ok, owned} = Trade.Amount.new(0, 0, 0)
assert Trade.pay(cost, owned) == {:error, "Not enough money"}
{:ok, cost} = Trade.Amount.new(4, 2, 1)
{:ok, owned} = Trade.Amount.new(3, 2, 1)
assert Trade.pay(cost, owned) == {:error, "Not enough money"}
end
test "enough money, exact amount" do
{:ok, cost} = Trade.Amount.new(0, 0, 1)
{:ok, owned} = Trade.Amount.new(0, 0, 1)
assert Trade.pay(cost, owned) == {:ok, %Trade.Amount{gold: 0, silver: 0, copper: 0}}
end
test "enough money, with remainders" do
{:ok, cost} = Trade.Amount.new(0, 50, 0)
{:ok, owned} = Trade.Amount.new(0, 0, 1)
assert Trade.pay(cost, owned) == {:ok, %Trade.Amount{gold: 0, silver: 50, copper: 0}}
{:ok, cost} = Trade.Amount.new(23, 0, 0)
{:ok, owned} = Trade.Amount.new(0, 0, 1)
assert Trade.pay(cost, owned) == {:ok, %Trade.Amount{gold: 0, silver: 99, copper: 77}}
{:ok, cost} = Trade.Amount.new(23, 1, 0)
{:ok, owned} = Trade.Amount.new(0, 0, 1)
assert Trade.pay(cost, owned) == {:ok, %Trade.Amount{gold: 0, silver: 98, copper: 77}}
end
test "new/3" do
assert Trade.Amount.new(100, 99, 23) == {:error, :copper_too_big}
assert Trade.Amount.new(100, 100, 23) == {:error, :silver_too_big}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment