Packages
islands_score
0.1.49
0.1.51
0.1.50
0.1.49
0.1.48
0.1.47
0.1.46
0.1.45
0.1.44
0.1.43
0.1.42
0.1.41
0.1.40
0.1.38
0.1.37
0.1.36
0.1.35
0.1.34
0.1.33
0.1.32
0.1.31
0.1.30
0.1.29
0.1.28
0.1.27
0.1.26
0.1.25
0.1.24
0.1.23
0.1.22
0.1.21
0.1.20
0.1.19
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
A score struct and functions for the Game of Islands.
Current section
Files
Jump to
Current section
Files
lib/islands/score.ex
# ┌───────────────────────────────────────────────────────────────────────┐
# │ Inspired by the book "Functional Web Development" by Lance Halvorsen. │
# └───────────────────────────────────────────────────────────────────────┘
defmodule Islands.Score do
@moduledoc """
A score struct and functions for the _Game of Islands_.
The score struct contains the fields:
- `name`
- `gender`
- `hits`
- `misses`
- `forested_types`
representing the properties of a score in the _Game of Islands_.
##### Inspired by the book [Functional Web Development](https://pragprog.com/titles/lhelph/functional-web-development-with-elixir-otp-and-phoenix/) by Lance Halvorsen.
"""
alias __MODULE__
alias IO.ANSI.Plus, as: ANSI
alias Islands.Client.IslandType
alias Islands.{Board, Game, Island, Player, PlayerID}
@island_type_codes ["a", "d", "l", "s", "q"]
@player_ids [:player1, :player2]
@score_width 21
@sp ANSI.cursor_right()
@sp_gender 2
@symbols [f: "♀", m: "♂"]
@derive JSON.Encoder
@enforce_keys [:name, :gender, :hits, :misses, :forested_types]
defstruct [:name, :gender, :hits, :misses, :forested_types]
@typedoc "A score struct for the Game of Islands"
@type t :: %Score{
name: Player.name(),
gender: Player.gender(),
hits: non_neg_integer,
misses: non_neg_integer,
forested_types: [Island.type()]
}
@doc """
Creates a score struct from a player's board struct.
"""
@spec board_score(Game.t(), PlayerID.t()) :: t
def board_score(%Game{} = game, player_id) when player_id in @player_ids do
player = game[player_id]
board = player.board
new(player, board)
end
@doc """
Creates a score struct from an opponent's board struct.
"""
@spec guesses_score(Game.t(), PlayerID.t()) :: t
def guesses_score(%Game{} = game, player_id) when player_id in @player_ids do
opponent = game[Game.opponent_id(player_id)]
board = opponent.board
new(opponent, board)
end
@doc """
Prints `score` formatted with embedded ANSI escapes.
"""
@spec format(t, keyword) :: :ok
def format(%Score{} = score, options) do
{up, right} = {options[:up], options[:right]}
[
[cursor_up(up), ANSI.cursor_right(right), player(score)],
["\n", ANSI.cursor_right(right), top_score(score)],
["\n", ANSI.cursor_right(right), bottom_score(score)]
]
|> ANSI.puts()
end
## Private functions
@spec new(Player.t(), Board.t()) :: t
defp new(player, board) do
%Score{
name: player.name,
gender: player.gender,
hits: Board.hits(board),
misses: Board.misses(board),
forested_types: Board.forested_types(board)
}
end
@spec cursor_up(non_neg_integer) :: String.t()
defp cursor_up(up) when up > 0, do: ANSI.cursor_up(up)
defp cursor_up(_up), do: ""
@spec player(t) :: ANSI.ansilist()
defp player(%Score{name: name, gender: gender}) do
name = String.slice(name, 0, @score_width - @sp_gender)
span = div(@score_width + String.length(name) + @sp_gender, 2) - @sp_gender
# The "visible" width of `player/1` (ignoring ANSI escapes) is 11 to 21...
[
[:chartreuse_yellow, String.pad_leading(name, span)],
[:reset, @sp, :spring_green, "#{@symbols[gender]}"]
]
end
@spec top_score(t) :: ANSI.ansilist()
defp top_score(%Score{hits: hits, misses: misses}) do
# The "visible" width of `top_score/1` (ignoring ANSI escapes) is 21...
[
[:chartreuse_yellow, "hits: "],
[:spring_green, String.pad_leading("#{hits}", 2)],
[:chartreuse_yellow, " misses: "],
[:spring_green, String.pad_leading("#{misses}", 2)]
]
end
@spec bottom_score(t) :: ANSI.ansilist()
defp bottom_score(score) do
# The "visible" width of `bottom_score/1` (ignoring ANSI escapes) is 20...
[
[:reset, :spring_green, :underline, "forested"],
[:reset, @sp, :chartreuse_yellow, "➔", forested_codes(score)]
]
end
@spec forested_codes(t) :: ANSI.ansilist()
defp forested_codes(%Score{forested_types: forested_types}) do
for code <- @island_type_codes do
[attr(IslandType.new(code) in forested_types), code]
end
end
@spec attr(boolean) :: ANSI.ansilist()
defp attr(_forested? = true), do: [:reset, @sp, :spring_green, :underline]
defp attr(_forested?), do: [:reset, @sp, :chartreuse_yellow]
end