fixed compiler errors and updated read me
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Andre Henriques 2024-01-20 20:26:41 +00:00
parent 2d20363870
commit e3b2e60ddc
3 changed files with 142 additions and 124 deletions

View File

@ -134,6 +134,11 @@ The server will answer with one of the following:
There is two ways of interacting with the server via the API or the `Client` Module There is two ways of interacting with the server via the API or the `Client` Module
## Notes
By default all the logs are disabled to enable all the logs inside the paxos file there is a module variable called `@min_print_level`,
by setting it to 0 it enables all the logs
## API ## API
Start a new iex session and compilie `paxos.ex` and `server.ex`: Start a new iex session and compilie `paxos.ex` and `server.ex`:

View File

@ -21,10 +21,8 @@ defmodule Utils do
""" """
@min_print_level 3 @min_print_level 3
@doc """ # This function works similiary with unicast but it allows both for a pid or an atom
This function works similiary with unicast but it allows both for a pid or an atom # to be given as the first parameter
to be given as the first parameter
"""
def safecast(p, m) when p == nil, do: IO.puts('Trying to safecast #{m} with p as nil') def safecast(p, m) when p == nil, do: IO.puts('Trying to safecast #{m} with p as nil')
def safecast(p, m) when is_pid(p), do: send(p, m) def safecast(p, m) when is_pid(p), do: send(p, m)
def safecast(p, m) do def safecast(p, m) do
@ -132,9 +130,7 @@ defmodule Paxos do
create_log 0 create_log 0
@doc """ # This macro allows the state.instmap to be updated very easily
This macro allows the state.instmap to be updated very easily
"""
defmacrop set_instmap(do: expr) do defmacrop set_instmap(do: expr) do
quote do quote do
var!(map) = var!(state).instmap[var!(inst)] var!(map) = var!(state).instmap[var!(inst)]
@ -171,9 +167,7 @@ defmodule Paxos do
run(state) run(state)
end end
@doc """ # Guarantees that a specific state exists for a specific instance
Guarantees that a specific state exists for a specific instance
"""
defp has_or_create(state, inst, value \\ nil, pid_to_inform \\ nil, action \\ nil) do defp has_or_create(state, inst, value \\ nil, pid_to_inform \\ nil, action \\ nil) do
or_state state.instmap[inst] == nil do or_state state.instmap[inst] == nil do
instmap = instmap =
@ -197,11 +191,9 @@ defmodule Paxos do
end end
end end
@doc """ # Checks if an instance has finished or if it was aborted.
Checks if an instance has finished or if it was aborted. # If the optional parameter ignore_aborted was set to true makes this function only check
If the optional parameter ignore_aborted was set to true makes this function only check # if the the instance has finished
if the the instance has finished
"""
defp has_finished(state, inst, ignore_aborted \\ false) do defp has_finished(state, inst, ignore_aborted \\ false) do
cond do cond do
Map.has_key?(state.decided, inst) -> true Map.has_key?(state.decided, inst) -> true
@ -211,21 +203,19 @@ defmodule Paxos do
end end
end end
@doc """ # This is the run/recieve function
This is the run/recieve function # All the messages that are handled by this function are:
All the messages that are handled by this function are: # {:ele_trust, proc} ->
{:ele_trust, proc} -> # {:propose, inst, value, pid_to_inform, action} ->
{:propose, inst, value, pid_to_inform, action} -> # {:rb_deliver, proc, {:other_propose, inst, value}} ->
{:rb_deliver, proc, {:other_propose, inst, value}} -> # {:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} -> # {:nack, inst, ballot} ->
{:nack, inst, ballot} -> # {:rb_deliver, _proc, {:abort, inst, ballot}} ->
{:rb_deliver, _proc, {:abort, inst, ballot}} -> # {:prepared, inst, ballot, accepted_ballot, accepted_value} ->
{:prepared, inst, ballot, accepted_ballot, accepted_value} -> # {:rb_deliver, proc, {:accept, inst, ballot, value}} ->
{:rb_deliver, proc, {:accept, inst, ballot, value}} -> # {:accepted, inst, ballot} ->
{:accepted, inst, ballot} -> # {:get_value, inst, pid_to_inform} ->
{:get_value, inst, pid_to_inform} -> # {:rb_deliver, _, {:decide, inst, value}} ->
{:rb_deliver, _, {:decide, inst, value}} ->
"""
runfn do runfn do
# Handles leader elector # Handles leader elector
{:ele_trust, proc} -> {:ele_trust, proc} ->
@ -253,6 +243,8 @@ defmodule Paxos do
%{map| ballot: Ballot.inc(map.ballot)} %{map| ballot: Ballot.inc(map.ballot)}
end end
state
not Map.has_key?(state.instmap, inst) -> not Map.has_key?(state.instmap, inst) ->
EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value}) EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value})
state = has_or_create(state, inst, value, pid_to_inform, action) state = has_or_create(state, inst, value, pid_to_inform, action)
@ -277,7 +269,7 @@ defmodule Paxos do
end end
# Handles the sharing of a proposal value to other processes # Handles the sharing of a proposal value to other processes
{:rb_deliver, proc, {:other_propose, inst, value}} -> {:rb_deliver, _, {:other_propose, inst, value}} ->
cond do cond do
has_finished(state, inst, true) -> has_finished(state, inst, true) ->
EagerReliableBroadcast.broadcast( EagerReliableBroadcast.broadcast(
@ -310,6 +302,7 @@ defmodule Paxos do
set_instmap do set_instmap do
%{ map | ballot: ballot } %{ map | ballot: ballot }
end end
state
Ballot.compare(ballot, &>/2, state.instmap[inst].ballot) -> Ballot.compare(ballot, &>/2, state.instmap[inst].ballot) ->
safecast(proc, safecast(proc,
@ -320,6 +313,7 @@ defmodule Paxos do
set_instmap do set_instmap do
%{ map | ballot: ballot } %{ map | ballot: ballot }
end end
state
true -> true ->
safecast(proc, {:nack, inst, ballot}) safecast(proc, {:nack, inst, ballot})
@ -349,12 +343,13 @@ defmodule Paxos do
} }
end end
state
true -> true ->
state state
end end
# Handles an abort # Handles an abort
{:rb_deliver, _proc, {:abort, inst, ballot}} -> {:rb_deliver, _proc, {:abort, inst, _}} ->
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
state state
@ -418,6 +413,7 @@ defmodule Paxos do
accepted_ballot: ballot accepted_ballot: ballot
} }
end end
state
else else
log("#{state.name} -> #{proc} nack") log("#{state.name} -> #{proc} nack")
safecast(proc, {:nack, inst, ballot}) safecast(proc, {:nack, inst, ballot})
@ -471,10 +467,8 @@ defmodule Paxos do
end end
end end
@doc """ # Does the logic to decide when to send the prepare messages
Does the logic to decide when to send the prepare messages # Also sets the state nessesary to run the proposal
Also sets the state nessesary to run the proposal
"""
defp prepare(state, _) when state.leader != state.name, do: state defp prepare(state, _) when state.leader != state.name, do: state
defp prepare(state, inst) do defp prepare(state, inst) do
cond do cond do
@ -505,15 +499,14 @@ defmodule Paxos do
has_sent_accept: false has_sent_accept: false
} }
end end
state
end end
end end
@doc """ # Processes the prepared messages and when a quorum is met the accept messages are send
Processes the prepared messages and when a quorum is met the accept messages are send
"""
defp prepared(state, _) when state.leader != state.name, do: state defp prepared(state, _) when state.leader != state.name, do: state
defp prepared(state, inst) do defp prepared(state, inst) do
if length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and or_state length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and
not state.instmap[inst].has_sent_accept do not state.instmap[inst].has_sent_accept do
{_, a_val} = {_, a_val} =
Enum.reduce(state.instmap[inst].prepared_values, {Ballot.init(state.name, 0), nil}, fn {bal, val}, Enum.reduce(state.instmap[inst].prepared_values, {Ballot.init(state.name, 0), nil}, fn {bal, val},
@ -550,14 +543,11 @@ defmodule Paxos do
has_sent_accept: true has_sent_accept: true
} }
end end
else
state state
end end
end end
@doc """ # Processes the accepted messages and when a qurum is met decide on the value
Processes the accepted messages and when a qurum is met decide on the value
"""
defp accepted(state, _) when state.leader != state.name, do: state defp accepted(state, _) when state.leader != state.name, do: state
defp accepted(state, inst) do defp accepted(state, inst) do
or_state state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do or_state state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
@ -598,9 +588,7 @@ defmodule Paxos do
propose_loop(inst, t) propose_loop(inst, t)
end end
@doc """ # Waits the right message from the paxos replica
Waits the right message from the paxos replica
"""
defp propose_loop(inst, t) do defp propose_loop(inst, t) do
receive do receive do
{:timeout, ^inst} -> {:timeout} {:timeout, ^inst} -> {:timeout}
@ -622,9 +610,7 @@ defmodule Paxos do
get_decision_loop(inst, t) get_decision_loop(inst, t)
end end
@doc """ # Sends waits for the right message from the paxos replica
Sends waits for the right message from the paxos replica
"""
defp get_decision_loop(inst, t) do defp get_decision_loop(inst, t) do
receive do receive do
{:get_value_res, ^inst, v} -> {:get_value_res, ^inst, v} ->
@ -661,9 +647,7 @@ defmodule Ballot do
} }
end end
@doc """ # Compare the name of 2 processes and select the lowest one
Compare the name of 2 processes and select the lowest one
"""
defp lexicographical_compare(a, b) do defp lexicographical_compare(a, b) do
cond do cond do
a == b -> 0 a == b -> 0
@ -672,9 +656,7 @@ defmodule Ballot do
end end
end end
@doc """ # Callculate the difference between w ballots
Callculate the difference between w ballots
"""
defp diff({name1, number1}, {name2, number2}) do defp diff({name1, number1}, {name2, number2}) do
diff = number1 - number2 diff = number1 - number2
if diff == 0 do if diff == 0 do
@ -701,9 +683,7 @@ defmodule EagerReliableBroadcast do
require Utils require Utils
import Utils import Utils
@doc """ # Removes _br from the name of a process
Removes _br from the name of a process
"""
defp get_non_rb_name(name) do defp get_non_rb_name(name) do
String.to_atom(String.replace(Atom.to_string(name), "_rb", "")) String.to_atom(String.replace(Atom.to_string(name), "_rb", ""))
end end

View File

@ -121,9 +121,7 @@ defmodule Server do
end end
@doc """ # Checks if the user can play the move before starting the requesting the user to play
Checks if the user can play the move before starting the requesting the user to play
"""
defp try_to_play_checks(state, game_id, move, pid_to_inform, repeat \\ false) do defp try_to_play_checks(state, game_id, move, pid_to_inform, repeat \\ false) do
cond do cond do
state.games[game_id] == :not_playing_in_game -> state.games[game_id] == :not_playing_in_game ->
@ -165,9 +163,7 @@ defmodule Server do
end end
end end
@doc """ # Tries to propose to paxos the game action
Tries to propose to paxos the game action
"""
defp try_to_play(state, game_id, move, pid_to_inform) do defp try_to_play(state, game_id, move, pid_to_inform) do
name = state.name name = state.name
@ -209,9 +205,7 @@ defmodule Server do
end end
end end
@doc """ # Get the most recent game_state and return it to the player
Get the most recent game_state and return it to the player
"""
defp get_game_state(state, game_id, pid_to_inform, repeat \\ false) do defp get_game_state(state, game_id, pid_to_inform, repeat \\ false) do
cond do cond do
state.games[game_id] == :not_playing_in_game -> state.games[game_id] == :not_playing_in_game ->
@ -240,9 +234,7 @@ defmodule Server do
end end
end end
@doc """ # This generates a new hand based on the current game state
This generates a new hand based on the current game state
"""
defp get_hand_for_game_state(game_state) do defp get_hand_for_game_state(game_state) do
r1 = Enum.random(0..100) r1 = Enum.random(0..100)
cond do cond do
@ -257,9 +249,7 @@ defmodule Server do
end end
end end
@doc """ # This tries to create a game by sending the create message to paxos
This tries to create a game by sending the create message to paxos
"""
defp try_to_create_game(state, participants) do defp try_to_create_game(state, participants) do
game_ids = Map.keys(state.games) game_ids = Map.keys(state.games)
latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1) latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1)
@ -281,9 +271,8 @@ defmodule Server do
# #
# Utils # Utils
# #
@doc """
Checks if a game has been finished # Checks if a game has been finished
"""
defp is_finished(state, game) do defp is_finished(state, game) do
case state.games[game] do case state.games[game] do
{:finished, _} -> true {:finished, _} -> true
@ -291,9 +280,7 @@ defmodule Server do
end end
end end
@doc """ # Gets up to the most recent instance
Gets up to the most recent instance
"""
defp qurey_status(state) do defp qurey_status(state) do
v = Paxos.get_decision(state.paxos, state.instance, 100) v = Paxos.get_decision(state.paxos, state.instance, 100)
or_state v != nil do or_state v != nil do
@ -302,9 +289,7 @@ defmodule Server do
end end
end end
@doc """ # Sets the modified flag
Sets the modified flag
"""
defp set_modifed(state, game, val \\ false) do defp set_modifed(state, game, val \\ false) do
or_state not is_finished(state, game) and state.games[game] != :not_playing_in_game and state.games[game] != nil do or_state not is_finished(state, game) and state.games[game] != :not_playing_in_game and state.games[game] != nil do
%{state | games: Map.put(state.games, game, %{state.games[game] | modified: val})} %{state | games: Map.put(state.games, game, %{state.games[game] | modified: val})}
@ -315,6 +300,7 @@ defmodule Server do
# Apply Game States # Apply Game States
# #
# Calculates the index based on the incoming index and a move
defp get_index(indexed_game_state, spos, index) do defp get_index(indexed_game_state, spos, index) do
index = spos + index index = spos + index
len = length(indexed_game_state) len = length(indexed_game_state)
@ -328,6 +314,9 @@ defmodule Server do
end end
end end
# This funcion looks at pluses and blackholes and chekes if they can be merged
# This funcion will pick a state like this [ 1, 2, :+, 2, 1 ] and create a new state [ 1, {:merged, 3} , 1] and then call expand merge which will expand the merge
# Note by this point the state is indexed so it looks like [{1, 0}, {:+, 1}, {1, 2}]
defp simplify_game_state_pluses([], indexed_game_state), do: {false, indexed_game_state} defp simplify_game_state_pluses([], indexed_game_state), do: {false, indexed_game_state}
defp simplify_game_state_pluses([{:+, i} | tl], indexed_game_state) do defp simplify_game_state_pluses([{:+, i} | tl], indexed_game_state) do
before_i = get_index(indexed_game_state, i, -1) before_i = get_index(indexed_game_state, i, -1)
@ -345,7 +334,7 @@ defmodule Server do
list = list =
indexed_game_state |> indexed_game_state |>
Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, n + 1}, i}, else: {x, ti} end) |> Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, n + 1}, i}, else: {x, ti} end) |>
Enum.filter(fn {x, ti} -> cond do Enum.filter(fn {_, ti} -> cond do
b_i == ti -> false b_i == ti -> false
a_i == ti -> false a_i == ti -> false
true -> true true -> true
@ -376,7 +365,7 @@ defmodule Server do
list = list =
indexed_game_state |> indexed_game_state |>
Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, trunc(:math.floor((a + b) / 2))}, i}, else: {x, ti} end) |> Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, trunc(:math.floor((a + b) / 2))}, i}, else: {x, ti} end) |>
Enum.filter(fn {x, ti} -> cond do Enum.filter(fn {_, ti} -> cond do
b_i == ti -> false b_i == ti -> false
a_i == ti -> false a_i == ti -> false
true -> true true -> true
@ -389,6 +378,7 @@ defmodule Server do
end end
end end
# Check if the item in an game is the substate merged
defp is_merged(item) do defp is_merged(item) do
case item do case item do
{:merged, _} -> true {:merged, _} -> true
@ -396,6 +386,9 @@ defmodule Server do
end end
end end
# This funcion expands merges
# With a state like this [1, {:merged, 3}, 1] it will create a game state like this [ {:merged, 4 } ]
# Note by this point the state is indexed so it looks like [{1, 0}, {:+, 1}, {1, 2}]
defp expand_merge(indexed_game_state) do defp expand_merge(indexed_game_state) do
{{:merged, n}, i} = indexed_game_state |> Enum.find(fn {x, _} -> is_merged(x) end) {{:merged, n}, i} = indexed_game_state |> Enum.find(fn {x, _} -> is_merged(x) end)
@ -412,7 +405,7 @@ defmodule Server do
_ -> _ ->
indexed_game_state |> indexed_game_state |>
Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, max(n, a) + 1}, i}, else: {x, ti} end) |> Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, max(n, a) + 1}, i}, else: {x, ti} end) |>
Enum.filter(fn {x, ti} -> cond do Enum.filter(fn {_, ti} -> cond do
b_i == ti -> false b_i == ti -> false
a_i == ti -> false a_i == ti -> false
true -> true true -> true
@ -429,6 +422,7 @@ defmodule Server do
end end
end end
# This funcion removes previous indexes and reindexs the state
defp reindex(list, flat \\ true) do defp reindex(list, flat \\ true) do
list = if flat do list = if flat do
list |> Enum.map(fn {n, _} -> n end) list |> Enum.map(fn {n, _} -> n end)
@ -439,17 +433,13 @@ defmodule Server do
[list, 0..(length(list) - 1)] |> Enum.zip() [list, 0..(length(list) - 1)] |> Enum.zip()
end end
defp remove_merged([], rec, _), do: rec # Removes all merged from the array
defp remove_merged(l), do: l |> Enum.map(fn x -> case x do
defp remove_merged([{:merged, n} | tl], rec, add), {:merged, n} -> n
do: if add, do: remove_merged(tl, rec ++ [n], false), x -> x
else: remove_merged(tl, rec, false) end end)
defp remove_merged([n | tl], rec, add), do:
remove_merged(tl, rec ++ [n], add)
defp remove_merged(list), do: remove_merged(list, [], true)
# This funcion recieves the game state after the move was done and tries to simplify the game
defp simplify_game_state(game_state) do defp simplify_game_state(game_state) do
indexed_game_state = indexed_game_state =
game_state |> game_state |>
@ -475,6 +465,7 @@ defmodule Server do
end end
end end
# This function tries to apply the make_move command
defp apply_game(state, {:make_move, game_id, player_name, pos_move, new_hand}) do defp apply_game(state, {:make_move, game_id, player_name, pos_move, new_hand}) do
game = state.games[game_id] game = state.games[game_id]
case game do case game do
@ -507,6 +498,7 @@ defmodule Server do
end end
end end
# This function tries to apply the start_game command
defp apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do defp apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do
cond do cond do
state.games[game_id] -> state.games[game_id] ->
@ -535,17 +527,22 @@ defmodule Server do
# Interface # Interface
############ ############
# handles responses from the start game request
create_loop :start_game do create_loop :start_game do
{:start_game_ans, game_id} -> {:start_game_ans, game_id} ->
log("Started a game #{game_id}") log("Started a game #{game_id}")
{:start_game, game_id} {:start_game, game_id}
end end
@doc """
Requests the server to start a game
"""
def start_game(name, participants) do def start_game(name, participants) do
safecast(name, {:start_game, participants, self()}) safecast(name, {:start_game, participants, self()})
start_game_loop(nil, 10000) start_game_loop(nil, 10000)
end end
# handles responses from the get game state request
create_loop :get_game_state do create_loop :get_game_state do
{:game_state, ^v, :not_playing} -> {:game_state, ^v, :not_playing} ->
log("Not Playing in that game") log("Not Playing in that game")
@ -561,11 +558,15 @@ defmodule Server do
{:not_exists} {:not_exists}
end end
@doc """
Requests the server to get the game state
"""
def get_game_state(name, game_id) do def get_game_state(name, game_id) do
safecast(name, {:get_game_state, game_id, self()}) safecast(name, {:get_game_state, game_id, self()})
get_game_state_loop(game_id, 10000) get_game_state_loop(game_id, 10000)
end end
# handles responses from the make move request
create_loop :make_move do create_loop :make_move do
{:make_move, ^v, :game_does_not_exist} -> {:make_move, ^v, :game_does_not_exist} ->
log("Got game does not exist") log("Got game does not exist")
@ -587,6 +588,9 @@ defmodule Server do
{:state, game_state, hand} {:state, game_state, hand}
end end
@doc """
Requests to make a move
"""
def make_move(name, game_id, move) do def make_move(name, game_id, move) do
safecast(name, {:make_move, game_id, move, self()}) safecast(name, {:make_move, game_id, move, self()})
make_move_loop(game_id, 10000) make_move_loop(game_id, 10000)
@ -596,22 +600,35 @@ defmodule Server do
# Debug # Debug
############ ############
@doc """
Quicky creates some servers it's useful in testing
"""
def spinup(number_of_participants) do def spinup(number_of_participants) do
procs = Enum.to_list(0..number_of_participants) |> Enum.map(fn n -> :"p#{n}" end) procs = Enum.to_list(1..number_of_participants) |> Enum.map(fn n -> :"p#{n}" end)
Enum.map(procs, fn proc -> Server.start(proc, procs) end) Enum.map(procs, fn proc -> Server.start(proc, procs) end)
end end
@doc """
Quicky kills some servers it's useful in testing
"""
def kill (pids) do def kill (pids) do
pids |> Enum.map(fn m -> Process.exit(m, :kill) end) pids |> Enum.map(fn m -> Process.exit(m, :kill) end)
end end
end end
defmodule Client do defmodule Client do
@moduledoc """
This module handles displaying the game state in a nice to understand
way
"""
import Utils import Utils
require Utils require Utils
create_log 3 create_log 3
@doc """
This function plays the displays the game then waits for user input and displays the next state of the game
"""
def play_game(process, game_id) do def play_game(process, game_id) do
game = Server.get_game_state(process, game_id) game = Server.get_game_state(process, game_id)
@ -675,6 +692,11 @@ defmodule Client do
end end
@doc """
This funcion waits for user input and then commands the server to act
Use the display_game funcion in a seperate terminal to see the game
"""
def control_game(process, game_id) do def control_game(process, game_id) do
to_play = IO.gets("Type the number you want to play or q to exit: ") to_play = IO.gets("Type the number you want to play or q to exit: ")
to_play = to_play |> String.trim("\"") |> String.trim() to_play = to_play |> String.trim("\"") |> String.trim()
@ -707,10 +729,15 @@ defmodule Client do
end end
@doc """
This funcion tries to display the most recent version of the game
Use control_game to control game
"""
def display_game(process, game_id, temp_game \\ [], temp_hand \\ []) do def display_game(process, game_id, temp_game \\ [], temp_hand \\ []) do
game = Server.get_game_state(process, game_id) game = Server.get_game_state(process, game_id)
cont = case game do case game do
:timeout -> log("Could to not comunicate with the server") :timeout -> log("Could to not comunicate with the server")
{:not_exists} -> log("Game does not exist") {:not_exists} -> log("Game does not exist")
{:not_playing} -> log("Not Playing in that game") {:not_playing} -> log("Not Playing in that game")
@ -733,43 +760,49 @@ defmodule Client do
end end
end end
def to_name(list) when is_list(list), do: list |> Enum.map(fn x -> to_name(x) end) # This funcion recieves some objects and transforms them into user friendly text
def to_name(atom) when atom == :+, do: IO.ANSI.color_background(9) <> IO.ANSI.color(15) <> " + " <> IO.ANSI.reset() defp to_name(list) when is_list(list), do: list |> Enum.map(fn x -> to_name(x) end)
def to_name(atom) when atom == :-, do: IO.ANSI.color_background(21) <> IO.ANSI.color(15) <> " - " <> IO.ANSI.reset() defp to_name(atom) when atom == :+, do: IO.ANSI.color_background(9) <> IO.ANSI.color(15) <> " + " <> IO.ANSI.reset()
def to_name(atom) when atom == :b, do: IO.ANSI.color_background(232) <> IO.ANSI.color(15) <> " b " <> IO.ANSI.reset() defp to_name(atom) when atom == :-, do: IO.ANSI.color_background(21) <> IO.ANSI.color(15) <> " - " <> IO.ANSI.reset()
def to_name(atom) when is_atom(atom), do: atom defp to_name(atom) when atom == :b, do: IO.ANSI.color_background(232) <> IO.ANSI.color(15) <> " b " <> IO.ANSI.reset()
def to_name(i) do defp to_name(atom) when is_atom(atom), do: atom
defp to_name(i) do
letter = [ "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og" ] |> Enum.at(i - 1) letter = [ "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og" ] |> Enum.at(i - 1)
color = [46,45,138,19,11,140,8,47,57,159,72,48,55,35,251,188,107,110,21,11,156,134,128,179,140,234,14,90,206,7,249,209,253,123,192,165,234,136,198,208,43,34,215,127,23,250,177,237,124,202,229, color = [46,45,138,19,11,140,8,47,57,159,72,48,55,35,251,188,107,110,21,11,156,134,128,179,140,234,14,90,206,7,249,209,253,123,192,165,234,136,198,208,43,34,215,127,23,250,177,237,124,202,229,
63,206,220,224,109,202,113,253,7,243,26,160,65,39,112,57,75, 252,82,213,186,68,243,134,100,226,48,90,134,208,102,25,106,72, 242,26,59,166,26,187,54,194,165,97,219,186,130,7,154,233,85, 130,67,43,200,90,60,148,49,161,110,247,116,223,159,132,132] |> Enum.at(i - 1) |> IO.ANSI.color() 63,206,220,224,109,202,113,253,7,243,26,160,65,39,112,57,75, 252,82,213,186,68,243,134,100,226,48,90,134,208,102,25,106,72, 242,26,59,166,26,187,54,194,165,97,219,186,130,7,154,233,85, 130,67,43,200,90,60,148,49,161,110,247,116,223,159,132,132] |> Enum.at(i - 1) |> IO.ANSI.color()
color <> String.pad_leading("#{letter}", 3, " ") <> IO.ANSI.reset() color <> String.pad_leading("#{letter}", 3, " ") <> IO.ANSI.reset()
end end
def interpolate(list) do # Adds the index as an indicator and pads the index
defp interpolate(list) do
[list, 0..length(list) - 1] |> [list, 0..length(list) - 1] |>
Enum.zip() |> Enum.zip() |>
Enum.reduce([], fn {v, i}, acc -> acc ++ [String.pad_leading("#{i}", 3, " "), v] end) Enum.reduce([], fn {v, i}, acc -> acc ++ [String.pad_leading("#{i}", 3, " "), v] end)
end end
def grow_empty_list(t, i, acc) when i == 0, do: t ++ acc # This grows a list with between spaces every item of the interpolated list
def grow_empty_list([], i, acc), do: grow_empty_list(acc, i, []) # This allows the items to take as puch space in the possilble
def grow_empty_list([h | tl], i, acc), do: defp grow_empty_list(t, i, acc) when i == 0, do: t ++ acc
defp grow_empty_list([], i, acc), do: grow_empty_list(acc, i, [])
defp grow_empty_list([h | tl], i, acc), do:
grow_empty_list(tl, i - 1, acc ++ [ h ++ [" "] ]) grow_empty_list(tl, i - 1, acc ++ [ h ++ [" "] ])
def fill(list) do # This takes the list that was generated in the grow_empty_list function and merges it between every other item
defp fill(list) do
to_fill = 32 - length(list) to_fill = 32 - length(list)
to_add = grow_empty_list(Enum.map(list, fn _ -> [] end), to_fill, []) to_add = grow_empty_list(Enum.map(list, fn _ -> [] end), to_fill, [])
fill(list, to_add, []) fill(list, to_add, [])
end end
def fill([], _, acc), do: acc defp fill([], _, acc), do: acc
def fill([hd | tail], [add_hd | add_tail], acc) do defp fill([hd | tail], [add_hd | add_tail], acc) do
fill(tail, add_tail ,acc ++ [hd] ++ add_hd) fill(tail, add_tail ,acc ++ [hd] ++ add_hd)
end end
def printpt(game_state, hand), do: printpt(game_state, hand, 0) # This functions prints the circle
def printpt(_, _, i) when i > 16, do: nil defp printpt(game_state, hand), do: printpt(game_state, hand, 0)
def printpt(game_state, hand, i) do defp printpt(_, _, i) when i > 16, do: nil
defp printpt(game_state, hand, i) do
res = case i do res = case i do
0 -> 0 ->
" xxx \n" |> " xxx \n" |>