Compare commits
10 Commits
03af54b32f
...
latest
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a9f086d5d | |||
| b337101566 | |||
| c33b7edef6 | |||
| 8c674626f8 | |||
| a22005c868 | |||
| cc7dc7cea8 | |||
| b4d0527b41 | |||
| b015d8cabd | |||
| ee9f5e1187 | |||
| 1756972e8c |
12
.drone.yml
12
.drone.yml
@@ -10,6 +10,18 @@ steps:
|
||||
- marp presentation.md -o index.html
|
||||
- cp * /home/andr3/services/notes/presentation
|
||||
|
||||
- name: Create release
|
||||
environment:
|
||||
TOKEN:
|
||||
from_secret: token
|
||||
commands:
|
||||
- cp lib/server.ex .
|
||||
- zip server.zip server.ex README.md
|
||||
- tea login add --url https://git.andr3h3nriqu3s.com --token "$TOKEN"
|
||||
- tea r rm -y latest || echo "Release not found"
|
||||
- tea r c --title "Latest" --asset "README.md" --asset "lib/paxos.ex" --asset "lib/server.ex" --asset "server.zip" latest
|
||||
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
||||
286
lib/paxos.ex
286
lib/paxos.ex
@@ -1,7 +1,30 @@
|
||||
#
|
||||
# This files contains some modules nessesary for paxos to work
|
||||
#
|
||||
# Utils: General Utilities
|
||||
# Paxos: Paxos logic
|
||||
# Ballot: Contain the logic to handle the ballot numbers
|
||||
# EventualLeaderElector: Leader elector code
|
||||
# EagerReliableBroadcast: Handles eager reliable broadcast
|
||||
#
|
||||
|
||||
defmodule Utils do
|
||||
@moduledoc """
|
||||
This module contains some helpful functions are used throughout all modules
|
||||
"""
|
||||
|
||||
@min_print_level 1
|
||||
@doc """
|
||||
Sets the min log level
|
||||
0 - show all
|
||||
1 - ignore paxos
|
||||
2 - ignore server
|
||||
"""
|
||||
@min_print_level 3
|
||||
|
||||
@doc """
|
||||
This function works similiary with unicast but it allows both for a pid or an atom
|
||||
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 is_pid(p), do: send(p, m)
|
||||
def safecast(p, m) do
|
||||
@@ -11,9 +34,19 @@ defmodule Utils do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function creates a new name for processes
|
||||
"""
|
||||
def alter_name(name, part), do: :"#{name}#{part}"
|
||||
|
||||
@doc """
|
||||
This function sends a message to a list of porcesses
|
||||
"""
|
||||
def beb_broadcast(m, dest), do: for(p <- dest, do: safecast(p, m))
|
||||
|
||||
@doc """
|
||||
This function register a pid in the global name space
|
||||
"""
|
||||
def register_name(name, pid, link \\ true) do
|
||||
case :global.re_register_name(name, pid) do
|
||||
:yes ->
|
||||
@@ -30,6 +63,9 @@ defmodule Utils do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This macro defines a new log function for a specific log level
|
||||
"""
|
||||
defmacro create_log(level) do
|
||||
quote do
|
||||
def log(msg) do
|
||||
@@ -38,12 +74,25 @@ defmodule Utils do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This function is used bu the create_log macro.
|
||||
|
||||
This function only prints logs if the level definied in the create_log macro
|
||||
"""
|
||||
def _log(msg, level) do
|
||||
if (@min_print_level <= level) do
|
||||
IO.puts(msg)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This macro defines a an or_state function that acts like this:
|
||||
if val do
|
||||
expr
|
||||
else
|
||||
state
|
||||
end
|
||||
"""
|
||||
defmacro or_state(val, do: expr) do
|
||||
quote do
|
||||
case unquote(val) do
|
||||
@@ -53,6 +102,9 @@ defmodule Utils do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This macro defines a macro to simplify the process of creating run functions
|
||||
"""
|
||||
defmacro runfn(do: expr) do
|
||||
quote do
|
||||
def run(s) do
|
||||
@@ -65,20 +117,25 @@ defmodule Utils do
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
# Possible actions
|
||||
# :kill_before_decision
|
||||
# :increase_ballot_number - this makes it so that it does not propose but jump simply increases the number of the current ballot
|
||||
# this is usefull when forcing a nack
|
||||
#
|
||||
defmodule Paxos do
|
||||
@moduledoc """
|
||||
This module contains all the logic for Paxos
|
||||
|
||||
To start a paxos instance run Paxos.start
|
||||
|
||||
To propose a a value to paxos run Paxos.propose
|
||||
|
||||
To get a previous decision please run Paxos.get_decision
|
||||
"""
|
||||
require Utils
|
||||
import Utils
|
||||
|
||||
create_log 0
|
||||
|
||||
defmacro set_instmap(do: expr) do
|
||||
@doc """
|
||||
This macro allows the state.instmap to be updated very easily
|
||||
"""
|
||||
defmacrop set_instmap(do: expr) do
|
||||
quote do
|
||||
var!(map) = var!(state).instmap[var!(inst)]
|
||||
new_instmap = Map.put(var!(state).instmap, var!(inst), unquote(expr))
|
||||
@@ -86,7 +143,9 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
# Starts the Paxos replica with a specific name and some processes
|
||||
@doc """
|
||||
Starts the Paxos replica with a specific name and some processes
|
||||
"""
|
||||
def start(name, processes, link \\ false) do
|
||||
log("Starting paxos for #{name}")
|
||||
|
||||
@@ -94,8 +153,9 @@ defmodule Paxos do
|
||||
register_name(name, pid, link)
|
||||
end
|
||||
|
||||
# Init event must be the first
|
||||
# one after the component is created
|
||||
@doc """
|
||||
Inicializes the state starts the eager reliable broadcast, and starts the eventual leader eletector
|
||||
"""
|
||||
def init(name, processes) do
|
||||
EventualLeaderElector.start(name, processes)
|
||||
EagerReliableBroadcast.start(name, processes)
|
||||
@@ -111,8 +171,10 @@ defmodule Paxos do
|
||||
run(state)
|
||||
end
|
||||
|
||||
# Guarantees that a specific state exists
|
||||
def has_or_create(state, inst, value \\ nil, pid_to_inform \\ nil, action \\ nil) do
|
||||
@doc """
|
||||
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
|
||||
or_state state.instmap[inst] == nil do
|
||||
instmap =
|
||||
Map.put(state.instmap, inst, %{
|
||||
@@ -135,7 +197,12 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
def has_finished(state, inst, ignore_aborted \\ false) do
|
||||
@doc """
|
||||
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 the instance has finished
|
||||
"""
|
||||
defp has_finished(state, inst, ignore_aborted \\ false) do
|
||||
cond do
|
||||
Map.has_key?(state.decided, inst) -> true
|
||||
ignore_aborted -> false
|
||||
@@ -144,7 +211,23 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This is the run/recieve function
|
||||
All the messages that are handled by this function are:
|
||||
{:ele_trust, proc} ->
|
||||
{:propose, inst, value, pid_to_inform, action} ->
|
||||
{:rb_deliver, proc, {:other_propose, inst, value}} ->
|
||||
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
|
||||
{:nack, inst, ballot} ->
|
||||
{:rb_deliver, _proc, {:abort, inst, ballot}} ->
|
||||
{:prepared, inst, ballot, accepted_ballot, accepted_value} ->
|
||||
{:rb_deliver, proc, {:accept, inst, ballot, value}} ->
|
||||
{:accepted, inst, ballot} ->
|
||||
{:get_value, inst, pid_to_inform} ->
|
||||
{:rb_deliver, _, {:decide, inst, value}} ->
|
||||
"""
|
||||
runfn do
|
||||
# Handles leader elector
|
||||
{:ele_trust, proc} ->
|
||||
log("#{state.name} - #{proc} is leader")
|
||||
|
||||
@@ -152,6 +235,7 @@ defmodule Paxos do
|
||||
prepare(st, inst)
|
||||
end)
|
||||
|
||||
# Handles a proposal from the parent process
|
||||
{:propose, inst, value, pid_to_inform, action} ->
|
||||
log("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}")
|
||||
|
||||
@@ -192,6 +276,7 @@ defmodule Paxos do
|
||||
prepare(state, inst)
|
||||
end
|
||||
|
||||
# Handles the sharing of a proposal value to other processes
|
||||
{:rb_deliver, proc, {:other_propose, inst, value}} ->
|
||||
cond do
|
||||
has_finished(state, inst, true) ->
|
||||
@@ -209,6 +294,7 @@ defmodule Paxos do
|
||||
prepare(state, inst)
|
||||
end
|
||||
|
||||
# Handles a prepare request from the leader
|
||||
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
|
||||
log("#{state.name} - prepare from #{proc}")
|
||||
|
||||
@@ -240,6 +326,7 @@ defmodule Paxos do
|
||||
state
|
||||
end
|
||||
|
||||
# Handles a nack
|
||||
{:nack, inst, ballot} ->
|
||||
log("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}")
|
||||
|
||||
@@ -266,6 +353,7 @@ defmodule Paxos do
|
||||
state
|
||||
end
|
||||
|
||||
# Handles an abort
|
||||
{:rb_deliver, _proc, {:abort, inst, ballot}} ->
|
||||
cond do
|
||||
has_finished(state, inst) ->
|
||||
@@ -281,6 +369,8 @@ defmodule Paxos do
|
||||
state
|
||||
end
|
||||
|
||||
# Handles a prepared
|
||||
# Sends out accept when a quorum is met
|
||||
{:prepared, inst, ballot, accepted_ballot, accepted_value} ->
|
||||
log(
|
||||
"#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}"
|
||||
@@ -307,6 +397,7 @@ defmodule Paxos do
|
||||
state
|
||||
end
|
||||
|
||||
# Handles a accept
|
||||
{:rb_deliver, proc, {:accept, inst, ballot, value}} ->
|
||||
cond do
|
||||
has_finished(state, inst) ->
|
||||
@@ -334,6 +425,8 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
# Handles a accept
|
||||
# Sends out accept when a decide when a quoeom is met
|
||||
{:accepted, inst, ballot} ->
|
||||
log("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}")
|
||||
|
||||
@@ -352,6 +445,7 @@ defmodule Paxos do
|
||||
state
|
||||
end
|
||||
|
||||
# handles a parent request to get a value
|
||||
{:get_value, inst, pid_to_inform} ->
|
||||
# log("#{state.name} get_value")
|
||||
if has_finished(state, inst, true) do
|
||||
@@ -359,6 +453,8 @@ defmodule Paxos do
|
||||
end
|
||||
state
|
||||
|
||||
# Adds a decision made by a leader to the list of decisions
|
||||
# and informs the parent of the decision
|
||||
{:rb_deliver, _, {:decide, inst, value}} ->
|
||||
log("#{state.name} - decided #{inspect(inst)} #{inspect(value)}")
|
||||
|
||||
@@ -375,13 +471,12 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Puts process in the preapre state
|
||||
#
|
||||
|
||||
def prepare(state, _) when state.leader != state.name, do: state
|
||||
|
||||
def prepare(state, inst) do
|
||||
@doc """
|
||||
Does the logic to decide when to send the prepare messages
|
||||
Also sets the state nessesary to run the proposal
|
||||
"""
|
||||
defp prepare(state, _) when state.leader != state.name, do: state
|
||||
defp prepare(state, inst) do
|
||||
cond do
|
||||
state.instmap[inst] == nil ->
|
||||
state
|
||||
@@ -413,12 +508,11 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Process the prepared responses
|
||||
#
|
||||
def prepared(state, _) when state.leader != state.name, do: state
|
||||
|
||||
def prepared(state, inst) do
|
||||
@doc """
|
||||
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, inst) do
|
||||
if length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and
|
||||
not state.instmap[inst].has_sent_accept do
|
||||
{_, a_val} =
|
||||
@@ -461,13 +555,12 @@ defmodule Paxos do
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Process the accepted responses
|
||||
#
|
||||
def accepted(state, _) when state.leader != state.name, do: state
|
||||
|
||||
def accepted(state, inst) do
|
||||
if state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
|
||||
@doc """
|
||||
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, inst) do
|
||||
or_state state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
|
||||
value = state.instmap[inst].ballot_value
|
||||
|
||||
if state.instmap[inst].action == :kill_before_decision do
|
||||
@@ -489,69 +582,76 @@ defmodule Paxos do
|
||||
| decided: Map.put(state.decided, inst, value),
|
||||
instmap: Map.delete(state.instmap, inst)
|
||||
}
|
||||
else
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
#######################
|
||||
# Interface #
|
||||
#######################
|
||||
|
||||
@doc """
|
||||
Send the propose message to the paxos replica and waits for a response from the correct instance
|
||||
"""
|
||||
def propose(pid, inst, value, t, action \\ nil) do
|
||||
send(pid, {:propose, inst, value, self(), action})
|
||||
|
||||
propose_loop({inst, t})
|
||||
propose_loop(inst, t)
|
||||
end
|
||||
|
||||
def propose_loop(input) do
|
||||
{_, t} = input
|
||||
@doc """
|
||||
Waits the right message from the paxos replica
|
||||
"""
|
||||
defp propose_loop(inst, t) do
|
||||
receive do
|
||||
{:timeout, inst} ->
|
||||
check_and_apply({:timeout}, inst, input, &propose_loop/1)
|
||||
|
||||
{:abort, inst} ->
|
||||
check_and_apply({:abort}, inst, input, &propose_loop/1)
|
||||
|
||||
{:decision, inst, d} ->
|
||||
check_and_apply({:decision, d}, inst, input, &propose_loop/1)
|
||||
|
||||
{:timeout, ^inst} -> {:timeout}
|
||||
{:abort, ^inst} -> {:abort}
|
||||
{:decision, ^inst, d} -> {:decision, d}
|
||||
x ->
|
||||
Process.send_after(self(), x, 500)
|
||||
propose_loop(input)
|
||||
propose_loop(inst, t)
|
||||
after
|
||||
t -> {:timeout}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sends the :get_value message to the paxos replica
|
||||
"""
|
||||
def get_decision(pid, inst, t) do
|
||||
send(pid, {:get_value, inst, self()})
|
||||
get_decision_loop({inst, t})
|
||||
get_decision_loop(inst, t)
|
||||
end
|
||||
|
||||
def get_decision_loop(input) do
|
||||
{_, t} = input
|
||||
@doc """
|
||||
Sends waits for the right message from the paxos replica
|
||||
"""
|
||||
defp get_decision_loop(inst, t) do
|
||||
receive do
|
||||
{:get_value_res, inst, v} ->
|
||||
check_and_apply(v, inst, input, &get_decision_loop/1)
|
||||
{:get_value_res, ^inst, v} ->
|
||||
v
|
||||
|
||||
x ->
|
||||
Process.send_after(self(), x, 500)
|
||||
get_decision_loop(input)
|
||||
get_decision_loop(inst, t)
|
||||
after
|
||||
t -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def check_and_apply(v, inst, input, fun) do
|
||||
{inInst, _} = input
|
||||
if inst == inInst do
|
||||
v
|
||||
else
|
||||
fun.(input)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Ballot do
|
||||
@moduledoc """
|
||||
A set of a helper functions to manage ballots
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Create a new ballot
|
||||
"""
|
||||
def init(name, number \\ 0), do: {name, number}
|
||||
|
||||
@doc """
|
||||
Increase the ballot number
|
||||
"""
|
||||
def inc(b, name \\ nil) do
|
||||
{old_name, number} = b
|
||||
|
||||
@@ -561,6 +661,9 @@ defmodule Ballot do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compare the name of 2 processes and select the lowest one
|
||||
"""
|
||||
defp lexicographical_compare(a, b) do
|
||||
cond do
|
||||
a == b -> 0
|
||||
@@ -569,7 +672,9 @@ defmodule Ballot do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Callculate the difference between w ballots
|
||||
"""
|
||||
defp diff({name1, number1}, {name2, number2}) do
|
||||
diff = number1 - number2
|
||||
if diff == 0 do
|
||||
@@ -579,24 +684,41 @@ defmodule Ballot do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compare 2 ballots
|
||||
"""
|
||||
def compare(b1, operator, b2), do: operator.(diff(b1, b2), 0)
|
||||
end
|
||||
|
||||
defmodule EagerReliableBroadcast do
|
||||
@moduledoc """
|
||||
This modules implents eager reiable broadcast with the optimization of removing the from the deliver list after hearing
|
||||
the same number of echos as there is processes
|
||||
|
||||
emits {:rb_deliver, proc, message}
|
||||
"""
|
||||
|
||||
require Utils
|
||||
import Utils
|
||||
|
||||
def get_non_rb_name(name) do
|
||||
@doc """
|
||||
Removes _br from the name of a process
|
||||
"""
|
||||
defp get_non_rb_name(name) do
|
||||
String.to_atom(String.replace(Atom.to_string(name), "_rb", ""))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Starts the Eager reliable process and registers it with a name and links the process with the parent process
|
||||
"""
|
||||
def start(name, processes) do
|
||||
pid = spawn(EagerReliableBroadcast, :init, [name, processes])
|
||||
register_name(alter_name(name, "_rb"), pid)
|
||||
end
|
||||
|
||||
# Init event must be the first
|
||||
# one after the component is created
|
||||
@doc """
|
||||
Sets state and calls the run function
|
||||
"""
|
||||
def init(parent, processes) do
|
||||
state = %{
|
||||
name: alter_name(parent, "_rb"),
|
||||
@@ -639,17 +761,24 @@ defmodule EagerReliableBroadcast do
|
||||
#############
|
||||
# Interface #
|
||||
#############
|
||||
@doc """
|
||||
Sends a a broadcast request to the eager reliable broadcast replica
|
||||
"""
|
||||
def broadcast(name, m), do: safecast(alter_name(name, "_rb"), {:broadcast, m})
|
||||
end
|
||||
|
||||
#
|
||||
# Emits {:ele_trust, proc }
|
||||
#
|
||||
|
||||
defmodule EventualLeaderElector do
|
||||
@moduledoc """
|
||||
This modules implents eventual leader elector
|
||||
|
||||
emits {:ele_leader, proc}
|
||||
"""
|
||||
require Utils
|
||||
import Utils
|
||||
|
||||
@doc """
|
||||
Initializes the leader elector process and registers is under name_ele and links it with the parent process
|
||||
"""
|
||||
def start(name, processes) do
|
||||
new_name = alter_name(name, "_ele")
|
||||
pid = spawn(EventualLeaderElector, :init, [new_name, name, processes])
|
||||
@@ -657,8 +786,9 @@ defmodule EventualLeaderElector do
|
||||
register_name(new_name, pid)
|
||||
end
|
||||
|
||||
# Init event must be the first
|
||||
# one after the component is created
|
||||
@doc """
|
||||
Initializes state and starts the run function
|
||||
"""
|
||||
def init(name, parent, processes) do
|
||||
processes = Enum.map(processes, fn name -> alter_name(name, "_ele") end)
|
||||
|
||||
@@ -675,6 +805,9 @@ defmodule EventualLeaderElector do
|
||||
run(request_heartbeats(state))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Clears heard_back and updates the sequence number and then sends a request for heartbeats to all proccesses
|
||||
"""
|
||||
def request_heartbeats(state) do
|
||||
state = %{state | heard_back: MapSet.new(), seq: state.seq + 1}
|
||||
beb_broadcast({:heartbeat_request, state.name, state.seq}, state.processes)
|
||||
@@ -684,15 +817,18 @@ defmodule EventualLeaderElector do
|
||||
end
|
||||
|
||||
runfn do
|
||||
# Answer to a heartbeat_request
|
||||
{:heartbeat_request, name, seq} ->
|
||||
safecast(name, {:heartbeat, state.parent, seq})
|
||||
state
|
||||
|
||||
# Update heard_back with this name
|
||||
{:heartbeat, name, seq} ->
|
||||
or_state seq == state.seq do
|
||||
%{state | heard_back: MapSet.put(state.heard_back, name)}
|
||||
end
|
||||
|
||||
# One time out check the heard_back and send if a new leader is elected inform the parent
|
||||
{:timeout} ->
|
||||
state = or_state MapSet.size(state.heard_back) >= floor(length(state.processes)/2) + 1 do
|
||||
to_trust = Enum.at(Enum.sort(MapSet.to_list(state.heard_back)), 0)
|
||||
|
||||
466
lib/server.ex
466
lib/server.ex
@@ -1,6 +1,13 @@
|
||||
defmodule ServerMacros do
|
||||
@moduledoc """
|
||||
This module defines some helper macros that are used within the server macro
|
||||
"""
|
||||
|
||||
def create_create_loop(name, do: match_exp, else: process_exp) do
|
||||
@doc """
|
||||
This macro creates a wait loop to wait for the messages that are receive match what is inside the
|
||||
loop
|
||||
"""
|
||||
defmacro create_loop(name, do: match_exp) do
|
||||
function_name = :"#{name}_loop"
|
||||
|
||||
ast1 = quote do
|
||||
@@ -19,7 +26,6 @@ defmodule ServerMacros do
|
||||
quote do
|
||||
defp unquote(function_name)(v, t) do
|
||||
var!(v) = v
|
||||
unquote(process_exp)
|
||||
receive do
|
||||
unquote(ast3)
|
||||
after
|
||||
@@ -29,18 +35,10 @@ defmodule ServerMacros do
|
||||
end
|
||||
end
|
||||
|
||||
def create_create_loop(name, do: exp, else: else_exp) do
|
||||
create_create_loop(name, do: exp, else: else_exp)
|
||||
end
|
||||
|
||||
def create_create_loop(name, do: exp) do
|
||||
create_create_loop(name, do: exp, else: nil)
|
||||
end
|
||||
|
||||
defmacro create_loop(name, clauses) do
|
||||
create_create_loop(name, clauses)
|
||||
end
|
||||
|
||||
@doc """
|
||||
this function tries to propose and on failure it calls the else block and
|
||||
on success tries to match with the expressions on the do block
|
||||
"""
|
||||
defmacro try_propose(val, do: ready, else: recal_do) do
|
||||
ast1 = quote do
|
||||
{:timeout} -> unquote(recal_do)
|
||||
@@ -68,6 +66,9 @@ defmodule ServerMacros do
|
||||
end
|
||||
|
||||
defmodule Server do
|
||||
@moduledoc """
|
||||
Contains the to run the server Code
|
||||
"""
|
||||
require ServerMacros
|
||||
import ServerMacros
|
||||
require Utils
|
||||
@@ -75,13 +76,19 @@ defmodule Server do
|
||||
|
||||
create_log 2
|
||||
|
||||
@doc """
|
||||
Contains the start code for the server
|
||||
"""
|
||||
def start(name, participants) do
|
||||
log("starting server")
|
||||
log("#{name} Starting server")
|
||||
|
||||
pid = spawn(Server, :init, [name, participants])
|
||||
register_name(name, pid, false)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Initializes the state and starts the paxos inspect and then it calls the run fn
|
||||
"""
|
||||
def init(name, participants) do
|
||||
paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true)
|
||||
state = %{
|
||||
@@ -97,19 +104,26 @@ defmodule Server do
|
||||
|
||||
runfn do
|
||||
{:start_game, participants, pid_to_inform} ->
|
||||
{state, game_id} = try_to_create_game(state, participants)
|
||||
state = set_modifed(state, game_id)
|
||||
safecast(pid_to_inform, {:start_game_ans, game_id})
|
||||
state
|
||||
or_state is_list(participants) do
|
||||
{state, game_id} = try_to_create_game(state, participants)
|
||||
state = set_modifed(state, game_id)
|
||||
safecast(pid_to_inform, {:start_game_ans, game_id})
|
||||
state
|
||||
end
|
||||
|
||||
{:get_game_state, game_id, pid_to_inform} ->
|
||||
get_game_state(state, game_id, pid_to_inform)
|
||||
state = get_game_state(state, game_id, pid_to_inform)
|
||||
set_modifed(state, game_id)
|
||||
|
||||
|
||||
{:make_move, game_id, move, pid_to_inform} ->
|
||||
try_to_play_checks(state, game_id, move, pid_to_inform)
|
||||
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
cond do
|
||||
state.games[game_id] == :not_playing_in_game ->
|
||||
@@ -137,14 +151,32 @@ defmodule Server do
|
||||
safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand})
|
||||
set_modifed(state, game_id)
|
||||
true ->
|
||||
try_to_play(state, game_id, move, pid_to_inform)
|
||||
cond do
|
||||
not is_number(move) ->
|
||||
safecast(pid_to_inform, {:make_move, game_id, :invalid_move})
|
||||
state
|
||||
move >= length(game.game_state) ->
|
||||
safecast(pid_to_inform, {:make_move, game_id, :invalid_move})
|
||||
state
|
||||
true ->
|
||||
try_to_play(state, game_id, move, pid_to_inform)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Tries to propose to paxos the game action
|
||||
"""
|
||||
defp try_to_play(state, game_id, move, pid_to_inform) do
|
||||
name = state.name
|
||||
new_hand = get_hand_for_game_state(state.games[game_id].game_state)
|
||||
|
||||
new_hand = if state.games[game_id].hand[state.name] == :- do
|
||||
Enum.at(state.games[game_id].game_state, move)
|
||||
else
|
||||
get_hand_for_game_state(state.games[game_id].game_state)
|
||||
end
|
||||
|
||||
try_propose {:make_move, game_id, name, move, new_hand}
|
||||
do
|
||||
{:decision, {:make_move, ^game_id, ^name, ^move, ^new_hand}} ->
|
||||
@@ -177,6 +209,9 @@ defmodule Server do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
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
|
||||
cond do
|
||||
state.games[game_id] == :not_playing_in_game ->
|
||||
@@ -205,18 +240,26 @@ defmodule Server do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This generates a new hand based on the current game state
|
||||
"""
|
||||
defp get_hand_for_game_state(game_state) do
|
||||
r1 = Enum.random(0..100)
|
||||
if r1 <= 10 do
|
||||
:+
|
||||
else
|
||||
mx = game_state |> Enum.filter(fn m -> m != :+ end) |> Enum.max()
|
||||
mn = max(mx - 20, 1)
|
||||
mx = max(mx - 2, 4)
|
||||
Enum.random(mn..mx)
|
||||
cond do
|
||||
r1 <= 1 -> :b
|
||||
r1 <= 5 -> :-
|
||||
r1 <= 20 -> :+
|
||||
true ->
|
||||
mx = game_state |> Enum.filter(fn m -> m != :+ end) |> Enum.max()
|
||||
mn = max(mx - 20, 1)
|
||||
mx = max(mx - 2, 4)
|
||||
Enum.random(mn..mx)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This tries to create a game by sending the create message to paxos
|
||||
"""
|
||||
defp try_to_create_game(state, participants) do
|
||||
game_ids = Map.keys(state.games)
|
||||
latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1)
|
||||
@@ -238,7 +281,9 @@ defmodule Server do
|
||||
#
|
||||
# Utils
|
||||
#
|
||||
|
||||
@doc """
|
||||
Checks if a game has been finished
|
||||
"""
|
||||
defp is_finished(state, game) do
|
||||
case state.games[game] do
|
||||
{:finished, _} -> true
|
||||
@@ -246,6 +291,9 @@ defmodule Server do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets up to the most recent instance
|
||||
"""
|
||||
defp qurey_status(state) do
|
||||
v = Paxos.get_decision(state.paxos, state.instance, 100)
|
||||
or_state v != nil do
|
||||
@@ -254,8 +302,11 @@ defmodule Server do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets the modified flag
|
||||
"""
|
||||
defp set_modifed(state, game, val \\ false) do
|
||||
or_state not is_finished(state, game) 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})}
|
||||
end
|
||||
end
|
||||
@@ -289,6 +340,7 @@ defmodule Server do
|
||||
if b == a do
|
||||
case b do
|
||||
:+ -> simplify_game_state_pluses(tl, indexed_game_state)
|
||||
:b -> simplify_game_state_pluses(tl, indexed_game_state)
|
||||
n ->
|
||||
list =
|
||||
indexed_game_state |>
|
||||
@@ -310,6 +362,33 @@ defmodule Server do
|
||||
end
|
||||
end
|
||||
|
||||
defp simplify_game_state_pluses([{:b, i} | tl], indexed_game_state) do
|
||||
before_i = get_index(indexed_game_state, i, -1)
|
||||
after_i = get_index(indexed_game_state, i, 1)
|
||||
|
||||
if before_i != after_i do
|
||||
{b, b_i} = Enum.at(indexed_game_state, before_i)
|
||||
{a, a_i} = Enum.at(indexed_game_state, after_i)
|
||||
|
||||
a = if is_atom(a) do 1 else a end
|
||||
b = if is_atom(b) do 1 else b end
|
||||
|
||||
list =
|
||||
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.filter(fn {x, ti} -> cond do
|
||||
b_i == ti -> false
|
||||
a_i == ti -> false
|
||||
true -> true
|
||||
end
|
||||
end) |>
|
||||
reindex()
|
||||
{true, expand_merge(list)}
|
||||
else
|
||||
simplify_game_state_pluses(tl, indexed_game_state)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_merged(item) do
|
||||
case item do
|
||||
{:merged, _} -> true
|
||||
@@ -332,7 +411,7 @@ defmodule Server do
|
||||
{:merged, _} -> 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, max(n, a) + 1}, i}, else: {x, ti} end) |>
|
||||
Enum.filter(fn {x, ti} -> cond do
|
||||
b_i == ti -> false
|
||||
a_i == ti -> false
|
||||
@@ -362,24 +441,16 @@ defmodule Server do
|
||||
|
||||
defp remove_merged([], rec, _), do: rec
|
||||
|
||||
defp remove_merged([{:merged, n} | tl], rec, add) do
|
||||
if add do
|
||||
remove_merged(tl, rec ++ [n], false)
|
||||
else
|
||||
remove_merged(tl, rec, false)
|
||||
end
|
||||
end
|
||||
defp remove_merged([{:merged, n} | tl], rec, add),
|
||||
do: if add, do: remove_merged(tl, rec ++ [n], false),
|
||||
else: remove_merged(tl, rec, false)
|
||||
|
||||
defp remove_merged([n | tl], rec, add), do:
|
||||
remove_merged(tl, rec ++ [n], add)
|
||||
|
||||
defp remove_merged(list) do
|
||||
log("#{inspect(list)}")
|
||||
remove_merged(list, [], true)
|
||||
end
|
||||
defp remove_merged(list), do: remove_merged(list, [], true)
|
||||
|
||||
defp simplify_game_state(game_state) do
|
||||
log("game_state: #{inspect(game_state)}")
|
||||
indexed_game_state =
|
||||
game_state |>
|
||||
reindex(false)
|
||||
@@ -388,13 +459,12 @@ defmodule Server do
|
||||
indexed_game_state |>
|
||||
Enum.filter(fn x -> case x do
|
||||
{:+, _} -> true
|
||||
{:b, _} -> true
|
||||
_ -> false
|
||||
end
|
||||
end) |>
|
||||
simplify_game_state_pluses(indexed_game_state)
|
||||
|
||||
log("game_state2: #{inspect(indexed_game_state)}")
|
||||
|
||||
if repeat do
|
||||
indexed_game_state |>
|
||||
Enum.map(fn {v, _} -> v end) |>
|
||||
@@ -414,14 +484,23 @@ defmodule Server do
|
||||
%{state | instance: state.instance + 1 }
|
||||
game ->
|
||||
game_state = game.game_state
|
||||
{b, e} = Enum.split(game_state, pos_move)
|
||||
game_state = b ++ [ game.hand[player_name] ] ++ e
|
||||
game_state = if game.hand[player_name] == :- do
|
||||
List.delete_at(game_state, pos_move)
|
||||
else
|
||||
{b, e} = Enum.split(game_state, pos_move)
|
||||
b ++ [ game.hand[player_name] ] ++ e
|
||||
end
|
||||
game_state = simplify_game_state(game_state)
|
||||
hand = Map.put(game.hand, player_name, new_hand)
|
||||
game = %{game| hand: hand, game_state: game_state }
|
||||
game = %{game| hand: hand, game_state: game_state, modified: true }
|
||||
|
||||
if length(game.game_state) > 15 do
|
||||
%{state| games: Map.put(state.games, game_id, {:finished, Enum.sum(game.game_state)}), instance: state.instance + 1}
|
||||
if length(game.game_state) > 16 do
|
||||
%{state| games: Map.put(state.games, game_id, {
|
||||
:finished,
|
||||
game.game_state |>
|
||||
Enum.filter(fn x -> not is_atom(x) end) |>
|
||||
Enum.sum()}), instance: state.instance + 1
|
||||
}
|
||||
else
|
||||
%{state| games: Map.put(state.games, game_id, game), instance: state.instance + 1}
|
||||
end
|
||||
@@ -468,18 +547,15 @@ defmodule Server do
|
||||
end
|
||||
|
||||
create_loop :get_game_state do
|
||||
{:game_state, ^v, :not_playing} ->
|
||||
log("Not Playing in that game")
|
||||
{:not_playing}
|
||||
{:game_state, ^v, game_state, hand} ->
|
||||
log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
|
||||
{:state, game_state, hand}
|
||||
{:game_state, ^v, :not_playing} ->
|
||||
log("Not Playing in that game")
|
||||
{:not_playing}
|
||||
{:game_state, ^v, :game_finished, score} ->
|
||||
log("Game finsihed, #{score}")
|
||||
{:game_finished, score}
|
||||
{:game_state, ^v, game_state, hand} ->
|
||||
log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
|
||||
{:state, game_state, hand}
|
||||
{:game_state, ^v, :game_does_not_exist} ->
|
||||
log("Got game does not exist")
|
||||
{:not_exists}
|
||||
@@ -503,6 +579,9 @@ defmodule Server do
|
||||
{:make_move, ^v, :player_moved_before, game_state, hand} ->
|
||||
log("Player moved_before, #{inspect(game_state)} #{inspect(hand)}")
|
||||
{:player_moved_before, game_state, hand}
|
||||
{:make_move, ^v, :invalid_move} ->
|
||||
log("Invalid Move")
|
||||
{:invalid_move}
|
||||
{:make_move, ^v, game_state, hand} ->
|
||||
log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
|
||||
{:state, game_state, hand}
|
||||
@@ -534,87 +613,234 @@ defmodule Client do
|
||||
create_log 3
|
||||
|
||||
def play_game(process, game_id) do
|
||||
game = Server.get_game_state(process, game_id)
|
||||
|
||||
cont = case game do
|
||||
:timeout ->
|
||||
log("Could to not comunicate with the server")
|
||||
true
|
||||
{:not_exists} ->
|
||||
log("Game does not exist")
|
||||
true
|
||||
{:not_playing} ->
|
||||
log("Not Playing in that game")
|
||||
true
|
||||
{:game_finished, score} ->
|
||||
log("Game finsihed, #{score}")
|
||||
true
|
||||
{:game_does_not_exist} ->
|
||||
log("Got game does not exist")
|
||||
true
|
||||
{:state, game_state, hand} ->
|
||||
game_state |>
|
||||
to_name() |>
|
||||
interpolate() |>
|
||||
fill() |>
|
||||
printpt(to_name(hand))
|
||||
false
|
||||
end
|
||||
|
||||
if not cont do
|
||||
to_play = IO.gets("Type the number you want to play or q to exit: ")
|
||||
to_play = to_play |> String.trim("\"") |> String.trim()
|
||||
|
||||
case to_play do
|
||||
"q" ->
|
||||
log("Bye Bye")
|
||||
v ->
|
||||
try do
|
||||
{n, _} = Integer.parse(v)
|
||||
|
||||
res = Server.make_move(process, game_id, n)
|
||||
case res do
|
||||
:timeout ->
|
||||
log("Could to not comunicate with the server")
|
||||
{:not_exists} -> log("Game does not exist")
|
||||
{:not_playing} -> log("Not Playing in that game")
|
||||
{:game_finished, score} -> log("Game finsihed, #{score}")
|
||||
{:game_does_not_exist} -> log("Got game does not exist")
|
||||
{:player_moved_before, _, _} ->
|
||||
log("Player Moved before you did please check the map and try again")
|
||||
play_game(process, game_id)
|
||||
{:invalid_move} -> raise "Invalid Move"
|
||||
{:state, _, _} -> play_game(process, game_id)
|
||||
end
|
||||
rescue
|
||||
_ ->
|
||||
log("Please provide a valid number")
|
||||
play_game(process, game_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def control_game(process, game_id) do
|
||||
to_play = IO.gets("Type the number you want to play or q to exit: ")
|
||||
to_play = to_play |> String.trim("\"") |> String.trim()
|
||||
|
||||
case to_play do
|
||||
"q" ->
|
||||
log("Bye Bye")
|
||||
v ->
|
||||
try do
|
||||
{n, _} = Integer.parse(v)
|
||||
|
||||
res = Server.make_move(process, game_id, n)
|
||||
case res do
|
||||
:timeout -> log("Could to not comunicate with the server")
|
||||
{:not_exists} -> control_game(process, game_id)
|
||||
{:not_playing} -> control_game(process, game_id)
|
||||
{:game_finished, _} -> control_game(process, game_id)
|
||||
{:player_moved_before, _, _} ->
|
||||
log("Player Moved before you did please check the map and try again")
|
||||
control_game(process, game_id)
|
||||
{:invalid_move} -> raise "Invalid Move"
|
||||
{:state, _, _} -> control_game(process, game_id)
|
||||
end
|
||||
rescue
|
||||
_ ->
|
||||
log("Please provide a valid number")
|
||||
control_game(process, game_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def display_game(process, game_id, temp_game \\ [], temp_hand \\ []) do
|
||||
game = Server.get_game_state(process, game_id)
|
||||
|
||||
cont = case game do
|
||||
:timeout -> log("Could to not comunicate with the server")
|
||||
{:not_exists} -> log("Game does not exist")
|
||||
{:not_playing} -> log("Not Playing in that game")
|
||||
{:game_finished, score} -> log("Game finsihed, #{score}")
|
||||
{:game_does_not_exist} -> log("Got game does not exist")
|
||||
{:state, game_state, hand} ->
|
||||
if temp_game != game_state or temp_hand != hand do
|
||||
# IO.ANSI.clear()
|
||||
game_state |>
|
||||
to_name() |>
|
||||
interpolate() |>
|
||||
fill() |>
|
||||
printpt(to_name(hand))
|
||||
Process.sleep(1000)
|
||||
display_game(process, game_id, game_state, hand)
|
||||
else
|
||||
Process.sleep(1000)
|
||||
display_game(process, game_id, temp_game, temp_hand)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def 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(9) <> IO.ANSI.color(15) <> " + " <> IO.ANSI.reset()
|
||||
def to_name(atom) when atom == :-, do: IO.ANSI.color_background(21) <> 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()
|
||||
def to_name(atom) when is_atom(atom), do: atom
|
||||
def to_name(i) do
|
||||
[ "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,
|
||||
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()
|
||||
end
|
||||
|
||||
def interpolate(list) do
|
||||
[list, 0..length(list) - 1] |>
|
||||
Enum.zip() |>
|
||||
Enum.reduce([], fn {v, i}, acc -> acc ++ [i, v] end) |>
|
||||
Enum.map(fn x -> String.pad_leading("#{x}", 2, " ") end)
|
||||
Enum.reduce([], fn {v, i}, acc -> acc ++ [String.pad_leading("#{i}", 3, " "), v] end)
|
||||
end
|
||||
|
||||
def printpt(game_state) do
|
||||
rad = 8;
|
||||
printpt1(game_state, 0, rad)
|
||||
def grow_empty_list(t, i, acc) when i == 0, do: t ++ acc
|
||||
def grow_empty_list([], i, acc), do: grow_empty_list(acc, i, [])
|
||||
def grow_empty_list([h | tl], i, acc), do:
|
||||
grow_empty_list(tl, i - 1, acc ++ [ h ++ [" "] ])
|
||||
|
||||
def fill(list) do
|
||||
to_fill = 32 - length(list)
|
||||
to_add = grow_empty_list(Enum.map(list, fn _ -> [] end), to_fill, [])
|
||||
fill(list, to_add, [])
|
||||
end
|
||||
|
||||
defp count_char(str, char) do
|
||||
String.split(str, "") |> Enum.reduce(0, fn x, acc -> if x == char do acc + 1 else acc end end)
|
||||
def fill([], _, acc), do: acc
|
||||
def fill([hd | tail], [add_hd | add_tail], acc) do
|
||||
fill(tail, add_tail ,acc ++ [hd] ++ add_hd)
|
||||
end
|
||||
|
||||
def printpt1(_, i, rad) when i > rad * 2, do: nil
|
||||
def printpt1(game_state, i, rad) do
|
||||
res = printpt2(game_state, i, -5, rad, "")
|
||||
|
||||
" xxx \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx yyy \n"
|
||||
" xxx \n"
|
||||
|
||||
|
||||
|
||||
|
||||
if i != 15 do
|
||||
res = case i do
|
||||
0 ->
|
||||
x = count_char(res, "x")
|
||||
log("#{x}")
|
||||
#TODO
|
||||
res
|
||||
16 ->
|
||||
#TODO
|
||||
res
|
||||
v ->
|
||||
#TODO
|
||||
res
|
||||
end
|
||||
IO.write(" #{res} \n")
|
||||
def printpt(game_state, hand), do: printpt(game_state, hand, 0)
|
||||
def printpt(_, _, i) when i > 16, do: nil
|
||||
def printpt(game_state, hand, i) do
|
||||
res = case i do
|
||||
0 ->
|
||||
" xxx \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 1))
|
||||
1 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 0)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 2))
|
||||
2 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 31)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 3))
|
||||
3 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 30)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 4))
|
||||
4 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 29)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 5))
|
||||
5 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 28)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 6))
|
||||
6 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 27)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 7))
|
||||
7 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 26)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 8))
|
||||
8 ->
|
||||
" xxx zzz yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 25)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 9)) |>
|
||||
String.replace("zzz", hand)
|
||||
9 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 24)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 10))
|
||||
10 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 23)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 11))
|
||||
11 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 22)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 12))
|
||||
12 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 21)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 13))
|
||||
13 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 20)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 14))
|
||||
14 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 19)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 15))
|
||||
15 ->
|
||||
" xxx yyy \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 18)) |>
|
||||
String.replace("yyy", Enum.at(game_state, 16))
|
||||
16 ->
|
||||
" xxx \n" |>
|
||||
String.replace("xxx", Enum.at(game_state, 17))
|
||||
end
|
||||
printpt1(game_state, i + 1, rad)
|
||||
end
|
||||
|
||||
def printpt2(_, _, j, rad, res) when j > rad * 4 + 10, do: res
|
||||
def printpt2(game_state, i, j, rad, res) do
|
||||
dist = :math.sqrt((i - rad)*(i - rad) + (j / 2 - rad)*(j / 2 - rad));
|
||||
v = if (dist > rad - 1 and dist < rad + 1) do
|
||||
case i do
|
||||
0 -> "x"
|
||||
16 -> "x"
|
||||
_ -> if j < rad * 2 do "x" else "y" end
|
||||
end
|
||||
else
|
||||
" "
|
||||
end
|
||||
printpt2(game_state, i, j + 1, rad, res <> v)
|
||||
IO.write("#{res}")
|
||||
printpt(game_state, hand, i + 1)
|
||||
end
|
||||
end
|
||||
|
||||
191
notes
Normal file
191
notes
Normal file
@@ -0,0 +1,191 @@
|
||||
procs = Enum.to_list(1..3) |> Enum.map(fn m -> :"p#{m}")
|
||||
pids = Enum.map(procs, fn p -> ShopServer.start(p, procs))
|
||||
pids |> Enum.map(fn p -> Process.exit(p, :kill) end)
|
||||
|
||||
|
||||
|
||||
max = 100
|
||||
|
||||
1..max |> Enum.to_list() |> Enum.reduce(%{}, fn n, acc -> Map.put(acc, n, :not_reseverd) end) |>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
" # "
|
||||
" #1# "
|
||||
" # "
|
||||
" # # "
|
||||
" #0# #2# "
|
||||
" # # "
|
||||
" # "
|
||||
" #4# "
|
||||
" # "
|
||||
|
||||
|
||||
" # "
|
||||
" #1# "
|
||||
" # "
|
||||
" # # "
|
||||
" #0# #2# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #7# #3# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #6# #4# "
|
||||
" # # "
|
||||
" # "
|
||||
" #5# "
|
||||
" # "
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
" # "
|
||||
" #1# "
|
||||
" # "
|
||||
" # # "
|
||||
" #0# #2# "
|
||||
" # # "
|
||||
" #11 #3# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #10 #4# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #9# #5# "
|
||||
" # # "
|
||||
" #8# #6# "
|
||||
" # # "
|
||||
" # "
|
||||
" #7# "
|
||||
" # "
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
" # "
|
||||
" #1# "
|
||||
" # "
|
||||
" # # "
|
||||
" #0# #2# "
|
||||
" #15 #3# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #14 #4# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #13 #5# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #12 #6# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #11 #7# "
|
||||
" #10 #8# "
|
||||
" # # "
|
||||
" # "
|
||||
" #9# "
|
||||
" # "
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
" # "
|
||||
" #1# "
|
||||
" # "
|
||||
" # # "
|
||||
" #0# #2# "
|
||||
" #19 #3# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #18 #4# "
|
||||
" #17 #5# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #16 #6# "
|
||||
" # # "
|
||||
" #15 #7# "
|
||||
" # # "
|
||||
" #14 #8# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #13 #9# "
|
||||
" #12 #10 "
|
||||
" # # "
|
||||
" # "
|
||||
" #11 "
|
||||
" # "
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
" # "
|
||||
" #1# "
|
||||
" # "
|
||||
" # # "
|
||||
" #0# #2# "
|
||||
" #23 #3# "
|
||||
" # # "
|
||||
" #22 #4# "
|
||||
" #21 #5# "
|
||||
" # # "
|
||||
" #20 #6# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #19 #7# "
|
||||
" # # "
|
||||
" # # "
|
||||
" #18 #8# "
|
||||
" # # "
|
||||
" #17 #9# "
|
||||
" #16 #10 "
|
||||
" # # "
|
||||
" #15 #11 "
|
||||
" #14 #12 "
|
||||
" # # "
|
||||
" # "
|
||||
" #13 "
|
||||
" # "
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user