Current section
Files
Jump to
Current section
Files
lib/cube.ex
defmodule Hextille.Cube do
alias Hextille.Cube, as: Cube
@moduledoc """
Cube module that represents hexagon tiles using Cube coordinates.
Instead of names x, y, z this module uses names q, r, s.
Cube coordinates have a constraint `q + r + s = 0`,
even with floating point cube coordinates. This has to be always respected.
Axes are aligned in the following order:
```
-r
+s .^. +q
.´ `.
| |
`. .´
-q `.´ -s
+r
```
"""
defstruct q: 0, r: 0, s: 0
@doc """
Creates a Cube or throws `ArgumentError` if the given arguments don't
satisfy constraint `q + r + s = 0`. This function should be the preferred way
to create new Hexagons when using this module.
## Examples:
iex> Cube.create!(1, -2, 1)
%Cube{q: 1, r: -2, s: 1}
iex> Cube.create!(4, -2, 1)
** (ArgumentError) Invalid coordinates, constraint q + r + s = 0
"""
def create!(q, r, s) do
if q + r + s == 0 do
%Cube{q: q, r: r, s: s}
else
raise ArgumentError, message: "Invalid coordinates, constraint q + r + s = 0"
end
end
@doc """
Addition of cube coordinates.
Returns a new Cube with added coordinates.
## Examples:
iex> a = %Cube{q: 1, r: -2, s: 1}
iex> b = %Cube{q: 3, r: -2, s: -1}
iex> Cube.add(a, b)
%Cube{q: 4, r: -4, s: 0}
"""
def add(%Cube{} = a, %Cube{} = b) do
%Cube{
q: a.q + b.q,
r: a.r + b.r,
s: a.s + b.s
}
end
@doc """
Subtraction of cube coordinates.
Returns a new Cube with subtracted coordinates.
## Examples:
iex> a = %Cube{q: 1, r: -2, s: 1}
iex> b = %Cube{q: 3, r: -2, s: -1}
iex> Cube.subtract(a, b)
%Cube{q: -2, r: 0, s: 2}
"""
def subtract(%Cube{} = a, %Cube{} = b) do
%Cube{
q: a.q - b.q,
r: a.r - b.r,
s: a.s - b.s
}
end
@doc """
Scale cube coordinates with given multiplier `k`.
Returns a new scaled Cube.
## Examples:
iex> a = %Cube{q: 1, r: -2, s: 1}
iex> Cube.scale(a, 3)
%Cube{q: 3, r: -6, s: 3}
"""
def scale(%Cube{} = h, k) when is_integer(k) do
%Cube{
q: h.q * k,
r: h.r * k,
s: h.s * k
}
end
@doc ~S"""
Rotate cube coordinates 60° to given direction `:left` or `:right`.
Returns a new Cube at rotated position.
Rotations visualized
```
.^. .^. .^.
.´2 0`.´ `.´0 2`.
| | | |
.^. -2 .^. .^. -2 .^.
.´ `.´ `.´ `.´ `.
| | | | |
`. .^. .^. .^. .^.
`.´ `.´s q`.´ `.´-2 2`.
| | | | |
`. .^. r .^. .^. 0 .´
`.´ `.´ `.´ `.´
| | | |
`. .^. .^. .´
`.´ `.´ `.´
```
## Examples:
iex> a = %Cube{q: 2, r: -2, s: 0}
iex> Cube.rotate(a, :left)
%Cube{q: 0, r: -2, s: 2}
iex> Cube.rotate(a, :right)
%Cube{q: 2, r: 0, s: -2}
"""
def rotate(%Cube{} = h, direction) do
case direction do
:left -> %Cube{q: -h.s, r: -h.q, s: -h.r}
:right -> %Cube{q: -h.r, r: -h.s, s: -h.q}
end
end
@doc ~S"""
Returns a new Cube representing the `direction` as a vector.
Directions are enumerated in following order:
```
:north_west :north_east
.^.
.´s q`.
:west | | :east
`. r .´
`.´
:south_west :south_east
```
## Examples:
iex> Cube.directions(:north_east)
%Cube{q: 1, r: -1, s: 0}
iex> Cube.directions(:east)
%Cube{q: 1, r: 0, s: -1}
iex> Cube.directions(:south_east)
%Cube{q: 0, r: 1, s: -1}
iex> Cube.directions(:south_west)
%Cube{q: -1, r: 1, s: 0}
iex> Cube.directions(:west)
%Cube{q: -1, r: 0, s: 1}
iex> Cube.directions(:north_west)
%Cube{q: 0, r: -1, s: 1}
"""
def directions(direction) do
case direction do
:north_east -> %Cube{q: 1, r: -1, s: 0}
:east -> %Cube{q: 1, r: 0, s: -1}
:south_east -> %Cube{q: 0, r: 1, s: -1}
:south_west -> %Cube{q: -1, r: 1, s: 0}
:west -> %Cube{q: -1, r: 0, s: 1}
:north_west -> %Cube{q: 0, r: -1, s: 1}
end
end
@doc ~S"""
Finds the neighbouring tile of hexagon `a` at the given `direction`.
Returns a new Cube representing the neighbour Cube.
Directions are enumerated in following order:
```
:north_west :north_east
.^.
.´s q`.
:west | | :east
`. r .´
`.´
:south_west :south_east
```
Neighbours visualized
```
.^. .^.
.´1 1`.´0 2`.
| | |
.^. -2 .^. -2 .^.
.´1 0`.´0 1`.´-1 2`.
| | | |
`. -1 .^. -1 .^. -1 .´
`.´0 0`.´-1 1`.´
| | |
`. 0 .^. 0 .´
`.´ `.´
```
## Examples:
iex> a = %Cube{q: 1, r: -1, s: 0}
iex> Cube.neighbour(a, :north_east)
%Cube{q: 2, r: -2, s: 0}
iex> Cube.neighbour(a, :north_west)
%Cube{q: 1, r: -2, s: 1}
"""
def neighbour(%Cube{} = h, direction) do
Cube.add(h, Cube.directions(direction))
end
@doc ~S"""
Returns the distance of hexagon `a` from origo as an integer.
## Examples:
iex> a = %Cube{q: 1, r: -2, s: 1}
iex> Cube.length(a)
2
iex> b = %Cube{q: -2, r: -3, s: 5}
iex> Cube.length(b)
5
"""
def length(%Cube{} = h) do
div((abs(h.q) + abs(h.r) + abs(h.s)), 2)
end
@doc ~S"""
Calculates the distance between hexagons `a` and `b`.
Return value is an integer value.
## Examples:
iex> a = %Cube{q: 1, r: -2, s: 1}
iex> b = %Cube{q: -2, r: -3, s: 5}
iex> Cube.distance(a, b)
4
"""
def distance(%Cube{} = a, %Cube{} = b) do
Cube.length(Cube.subtract(a, b))
end
@doc ~S"""
Rounds up an hexagon with float values to integer coordinates.
## Examples:
iex> a = %Cube{q: 1.5, r: -2.25, s: 0.75}
iex> Cube.round_hex(a)
%Cube{q: 1, r: -2, s: 1}
iex> b = %Cube{q: 1.2, r: 2.5, s: -3.7}
iex> Cube.round_hex(b)
%Cube{q: 1, r: 3, s: -4}
"""
def round_hex(%Cube{} = h) do
q = round(h.q)
r = round(h.r)
s = round(h.s)
q_diff = abs(q - h.q)
r_diff = abs(r - h.r)
s_diff = abs(s - h.s)
cond do
(q_diff > r_diff && q_diff > s_diff) ->
%Cube{q: -r - s, r: r, s: s}
(r_diff > s_diff) ->
%Cube{q: q, r: -q - s, s: s}
true ->
%Cube{q: q, r: r, s: -q - r}
end
end
end