bread-and-potions
This commit is contained in:
parent
af3b5c40f8
commit
eab9b54f20
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"angelikatyborska"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"neenjaw"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/rpg.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/rpg_test.exs"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language_versions": ">=1.10",
|
||||||
|
"blurb": "Learn about protocols by developing your own role-playing video game."
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"bread-and-potions","id":"67297c81721e4ff98b3b7d962328ddfd","url":"https://exercism.org/tracks/elixir/exercises/bread-and-potions","handle":"negrienko","is_requester":true,"auto_approve":false}
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
numbers-*.tar
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test
|
||||||
|
```
|
||||||
|
|
||||||
|
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||||
|
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||||
|
|
||||||
|
## Pending tests
|
||||||
|
|
||||||
|
In test suites of practice exercises, all but the first test have been tagged to be skipped.
|
||||||
|
|
||||||
|
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# @tag :pending
|
||||||
|
test "shouting" do
|
||||||
|
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test --include pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# ExUnit.configure(exclude: :pending, trace: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful `mix test` options
|
||||||
|
|
||||||
|
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||||
|
* `--failed` - runs only tests that failed the last time they ran
|
||||||
|
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||||
|
is reached
|
||||||
|
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||||
|
in the same order they were defined in
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit lib/rpg.ex` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||||
|
- The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir)
|
||||||
|
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Hints
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- Read about protocols in the [official Getting Started guide][getting-started-protocols] or on [elixirschool.com][elixir-school-protocols].
|
||||||
|
|
||||||
|
## 1. Define edibility
|
||||||
|
|
||||||
|
- There is a [special macro][kernel-defprotocol] for defining protocols.
|
||||||
|
- Protocols consist of one or more function headers.
|
||||||
|
- A function header is like a function definition, but without the `do` block. It defines the function name and its arguments, but not what it does.
|
||||||
|
|
||||||
|
## 2. Make loaves of bread edible
|
||||||
|
|
||||||
|
- There is a [special macro][kernel-defimpl] for implementing a protocol for a given data type.
|
||||||
|
- Implementing a protocol means writing a function definition for each function header from the protocol.
|
||||||
|
|
||||||
|
## 3. Make mana potions edible
|
||||||
|
|
||||||
|
- There is a [special macro][kernel-defimpl] for implementing a protocol for a given data type.
|
||||||
|
- Implementing a protocol means writing a function definition for each function header from the protocol.
|
||||||
|
|
||||||
|
## 4. Make poisons edible
|
||||||
|
|
||||||
|
- There is a [special macro][kernel-defimpl] for implementing a protocol for a given data type.
|
||||||
|
- Implementing a protocol means writing a function definition for each function header from the protocol.
|
||||||
|
|
||||||
|
[getting-started-protocols]: https://hexdocs.pm/elixir/protocols.html
|
||||||
|
[elixir-school-protocols]: https://elixirschool.com/en/lessons/advanced/protocols/
|
||||||
|
[kernel-defprotocol]: https://hexdocs.pm/elixir/Kernel.html#defprotocol/2
|
||||||
|
[kernel-defimpl]: https://hexdocs.pm/elixir/Kernel.html#defimpl/3
|
|
@ -0,0 +1,82 @@
|
||||||
|
# Bread And Potions
|
||||||
|
|
||||||
|
Welcome to Bread And Potions on Exercism's Elixir Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Protocols
|
||||||
|
|
||||||
|
Protocols are a mechanism to achieve polymorphism in Elixir when you want behavior to vary depending on the data type.
|
||||||
|
|
||||||
|
Protocols are defined using `defprotocol` and contain one or more function headers.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defprotocol Reversible do
|
||||||
|
def reverse(term)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Protocols can be implemented using `defimpl`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defimpl Reversible, for: List do
|
||||||
|
def reverse(term) do
|
||||||
|
Enum.reverse(term)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
A protocol can be implemented for any existing Elixir data type or for a struct.
|
||||||
|
|
||||||
|
When a protocol function is invoked, the appropriate implementation gets automatically chosen based on the type of the first argument.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You're developing your own role-playing video game. In your game, there are _characters_ and _items_. One of the many actions that you can do with an item is to make a character eat it.
|
||||||
|
|
||||||
|
Not all items are edible, and not all edible items have the same effects on the character. Some items, when eaten, turn into a different item (e.g. if you eat an apple, you are left with an apple core).
|
||||||
|
|
||||||
|
To allow for all that flexibility, you decided to create an `Edible` protocol that some of the items can implement.
|
||||||
|
|
||||||
|
## 1. Define edibility
|
||||||
|
|
||||||
|
Create the `RPG.Edible` protocol. The protocol has one function - `eat`. The `eat` function accepts an item and a character and returns a by-product and a character.
|
||||||
|
|
||||||
|
## 2. Make loaves of bread edible
|
||||||
|
|
||||||
|
Implement the `RPG.Edible` protocol for the `RPG.LoafOfBread` item. When eaten, a loaf of bread gives the character 5 health points and has no by-product.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
RPG.Edible.eat(%RPG.LoafOfBread{}, %RPG.Character{health: 31})
|
||||||
|
# => {nil, %RPG.Character{health: 36, mana: 0}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Make mana potions edible
|
||||||
|
|
||||||
|
Implement the `RPG.Edible` protocol for the `RPG.ManaPotion` item. When eaten, a mana potion gives the character as many mana points as the potion's strength, and produces an empty bottle.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
RPG.Edible.eat(%RPG.ManaPotion{strength: 13}, %RPG.Character{mana: 50})
|
||||||
|
# => {%RPG.EmptyBottle{}, %RPG.Character{health: 100, mana: 63}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Make poisons edible
|
||||||
|
|
||||||
|
Implement the `RPG.Edible` protocol for the `RPG.Poison` item. When eaten, a poison takes away all the health points from the character, and produces an empty bottle.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
RPG.Edible.eat(%RPG.Poison{}, %RPG.Character{health: 3000})
|
||||||
|
# => {%RPG.EmptyBottle{}, %RPG.Character{health: 0, mana: 0}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @angelikatyborska
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @neenjaw
|
|
@ -0,0 +1,58 @@
|
||||||
|
defmodule RPG do
|
||||||
|
defmodule Character do
|
||||||
|
defstruct health: 100, mana: 0
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule LoafOfBread do
|
||||||
|
defstruct []
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ManaPotion do
|
||||||
|
defstruct strength: 10
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule Poison do
|
||||||
|
defstruct []
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule EmptyBottle do
|
||||||
|
defstruct []
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add code to define the protocol and its implementations below here...
|
||||||
|
defprotocol Edible do
|
||||||
|
@moduledoc """
|
||||||
|
Protocol definition for interaction between Characters and Items
|
||||||
|
"""
|
||||||
|
@type item() :: LoafOfBread.t() | ManaPotion.t() | Poison.t() | EmptyBottle.t()
|
||||||
|
@spec eat(item(), Character.t()) :: {nil | item(), Character.t()}
|
||||||
|
def eat(item, character)
|
||||||
|
end
|
||||||
|
|
||||||
|
defimpl Edible, for: LoafOfBread do
|
||||||
|
@moduledoc """
|
||||||
|
Edible implimentation for LoafOfBread
|
||||||
|
"""
|
||||||
|
@spec eat(LoafOfBread.t(), Character.t()) :: {nil, Character.t()}
|
||||||
|
def eat(%LoafOfBread{}, %Character{health: health} = character),
|
||||||
|
do: {nil, %{character | health: health + 5}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defimpl Edible, for: ManaPotion do
|
||||||
|
@moduledoc """
|
||||||
|
Edible implimentation for ManaPotion
|
||||||
|
"""
|
||||||
|
@spec eat(ManaPotion.t(), Character.t()) :: {EmptyBottle.t(), Character.t()}
|
||||||
|
def eat(%ManaPotion{strength: strength}, %Character{mana: mana} = character),
|
||||||
|
do: {%EmptyBottle{}, %{character | mana: mana + strength}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defimpl Edible, for: Poison do
|
||||||
|
@moduledoc """
|
||||||
|
Edible implimentation for Poison
|
||||||
|
"""
|
||||||
|
@spec eat(Poison.t(), Character.t()) :: {EmptyBottle.t(), Character.t()}
|
||||||
|
def eat(%Poison{}, %Character{} = character),
|
||||||
|
do: {%EmptyBottle{}, %{character | health: 0}}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule RPG.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :bread_and_potions,
|
||||||
|
version: "0.1.0",
|
||||||
|
# elixir: "~> 1.10",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help deps" to learn about dependencies.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,133 @@
|
||||||
|
defmodule RPGTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
alias RPG.{Edible, Character, LoafOfBread, ManaPotion, Poison, EmptyBottle}
|
||||||
|
|
||||||
|
defmodule NewItem do
|
||||||
|
defstruct []
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Edible" do
|
||||||
|
@tag task_id: 1
|
||||||
|
test "is a protocol" do
|
||||||
|
assert Edible.__protocol__(:functions) == [eat: 2]
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "cannot eat a completely new item" do
|
||||||
|
assert_raise Protocol.UndefinedError, fn ->
|
||||||
|
Edible.eat(%NewItem{}, %Character{})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "LoafOfBread" do
|
||||||
|
@tag task_id: 2
|
||||||
|
test "implements the Edible protocol" do
|
||||||
|
{:consolidated, modules} = Edible.__protocol__(:impls)
|
||||||
|
assert LoafOfBread in modules
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "eating it increases health" do
|
||||||
|
character = %Character{health: 50}
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%LoafOfBread{}, character)
|
||||||
|
assert character.health == 55
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "eating it has no byproduct" do
|
||||||
|
character = %Character{}
|
||||||
|
{byproduct, %Character{}} = Edible.eat(%LoafOfBread{}, character)
|
||||||
|
assert byproduct == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 2
|
||||||
|
test "eating it does not affect mana" do
|
||||||
|
character = %Character{mana: 77}
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%LoafOfBread{}, character)
|
||||||
|
assert character.mana == 77
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "ManaPotion" do
|
||||||
|
@tag task_id: 3
|
||||||
|
test "implements the Edible protocol" do
|
||||||
|
{:consolidated, modules} = Edible.__protocol__(:impls)
|
||||||
|
assert ManaPotion in modules
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "eating it increases mana" do
|
||||||
|
character = %Character{mana: 10}
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%ManaPotion{strength: 6}, character)
|
||||||
|
assert character.mana == 16
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%ManaPotion{strength: 9}, character)
|
||||||
|
assert character.mana == 25
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "eating it produces an empty bottle" do
|
||||||
|
character = %Character{}
|
||||||
|
{byproduct, %Character{}} = Edible.eat(%ManaPotion{}, character)
|
||||||
|
assert byproduct == %EmptyBottle{}
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 3
|
||||||
|
test "eating it does not affect health" do
|
||||||
|
character = %Character{health: 4}
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%ManaPotion{strength: 6}, character)
|
||||||
|
assert character.health == 4
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Poison" do
|
||||||
|
@tag task_id: 4
|
||||||
|
test "implements the Edible protocol" do
|
||||||
|
{:consolidated, modules} = Edible.__protocol__(:impls)
|
||||||
|
assert Poison in modules
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "eating it brings health down to 0" do
|
||||||
|
character = %Character{health: 120}
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%Poison{}, character)
|
||||||
|
assert character.health == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "eating it produces an empty bottle" do
|
||||||
|
character = %Character{}
|
||||||
|
{byproduct, %Character{}} = Edible.eat(%Poison{}, character)
|
||||||
|
assert byproduct == %EmptyBottle{}
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "eating it does not affect mana" do
|
||||||
|
character = %Character{mana: 99}
|
||||||
|
{_byproduct, %Character{} = character} = Edible.eat(%Poison{}, character)
|
||||||
|
assert character.mana == 99
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "eating many things one after another" do
|
||||||
|
items = [
|
||||||
|
%LoafOfBread{},
|
||||||
|
%ManaPotion{strength: 10},
|
||||||
|
%ManaPotion{strength: 2},
|
||||||
|
%LoafOfBread{}
|
||||||
|
]
|
||||||
|
|
||||||
|
character = %Character{health: 100, mana: 100}
|
||||||
|
|
||||||
|
character =
|
||||||
|
Enum.reduce(items, character, fn item, character ->
|
||||||
|
{_, character} = Edible.eat(item, character)
|
||||||
|
character
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert character.health == 110
|
||||||
|
assert character.mana == 112
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
Loading…
Reference in New Issue