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