Compare commits

...

10 Commits

Author SHA1 Message Date
0a9f086d5d fixed ci
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 18:50:22 +00:00
b337101566 fixed ci
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is failing
2024-01-19 18:46:24 +00:00
c33b7edef6 added ci to generate submission files
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-19 18:45:43 +00:00
8c674626f8 added some docs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 18:23:08 +00:00
a22005c868 added some comments
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-19 14:32:55 +00:00
cc7dc7cea8 disabled logs
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-18 23:13:58 +00:00
b4d0527b41 Fixed the funk
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-18 23:13:34 +00:00
b015d8cabd fixed spaming the server
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-18 22:53:12 +00:00
ee9f5e1187 Fixed the funk
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-18 22:28:10 +00:00
1756972e8c Made game interactive 2024-01-18 19:40:04 +00:00
5 changed files with 799 additions and 233 deletions

View File

@@ -10,6 +10,18 @@ steps:
- marp presentation.md -o index.html - marp presentation.md -o index.html
- cp * /home/andr3/services/notes/presentation - 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: trigger:
branch: branch:
- main - main

1
README.md Normal file
View File

@@ -0,0 +1 @@
# TODO

View File

@@ -1,7 +1,30 @@
defmodule Utils do #
# 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
#
@min_print_level 1 defmodule Utils do
@moduledoc """
This module contains some helpful functions are used throughout all modules
"""
@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 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
@@ -11,9 +34,19 @@ defmodule Utils do
end end
end end
@doc """
This function creates a new name for processes
"""
def alter_name(name, part), do: :"#{name}#{part}" 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)) 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 def register_name(name, pid, link \\ true) do
case :global.re_register_name(name, pid) do case :global.re_register_name(name, pid) do
:yes -> :yes ->
@@ -29,7 +62,10 @@ defmodule Utils do
:error :error
end end
end end
@doc """
This macro defines a new log function for a specific log level
"""
defmacro create_log(level) do defmacro create_log(level) do
quote do quote do
def log(msg) do def log(msg) do
@@ -37,13 +73,26 @@ defmodule Utils do
end end
end end
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 def _log(msg, level) do
if (@min_print_level <= level) do if (@min_print_level <= level) do
IO.puts(msg) IO.puts(msg)
end end
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 defmacro or_state(val, do: expr) do
quote do quote do
case unquote(val) do case unquote(val) do
@@ -52,7 +101,10 @@ defmodule Utils do
end end
end end
end end
@doc """
This macro defines a macro to simplify the process of creating run functions
"""
defmacro runfn(do: expr) do defmacro runfn(do: expr) do
quote do quote do
def run(s) do def run(s) do
@@ -65,37 +117,45 @@ defmodule Utils do
end end
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 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 require Utils
import Utils import Utils
create_log 0 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 quote do
var!(map) = var!(state).instmap[var!(inst)] var!(map) = var!(state).instmap[var!(inst)]
new_instmap = Map.put(var!(state).instmap, var!(inst), unquote(expr)) new_instmap = Map.put(var!(state).instmap, var!(inst), unquote(expr))
var!(state) = %{var!(state) | instmap: new_instmap } var!(state) = %{var!(state) | instmap: new_instmap }
end end
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 def start(name, processes, link \\ false) do
log("Starting paxos for #{name}") log("Starting paxos for #{name}")
pid = spawn(Paxos, :init, [name, processes]) pid = spawn(Paxos, :init, [name, processes])
register_name(name, pid, link) register_name(name, pid, link)
end end
# Init event must be the first @doc """
# one after the component is created Inicializes the state starts the eager reliable broadcast, and starts the eventual leader eletector
"""
def init(name, processes) do def init(name, processes) do
EventualLeaderElector.start(name, processes) EventualLeaderElector.start(name, processes)
EagerReliableBroadcast.start(name, processes) EagerReliableBroadcast.start(name, processes)
@@ -110,9 +170,11 @@ defmodule Paxos do
run(state) run(state)
end end
# Guarantees that a specific state exists @doc """
def has_or_create(state, inst, value \\ nil, pid_to_inform \\ nil, action \\ nil) do 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 or_state state.instmap[inst] == nil do
instmap = instmap =
Map.put(state.instmap, inst, %{ Map.put(state.instmap, inst, %{
@@ -134,8 +196,13 @@ defmodule Paxos do
%{state | instmap: instmap} %{state | instmap: instmap}
end end
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 cond do
Map.has_key?(state.decided, inst) -> true Map.has_key?(state.decided, inst) -> true
ignore_aborted -> false ignore_aborted -> false
@@ -143,15 +210,32 @@ defmodule Paxos do
true -> false true -> false
end end
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 runfn do
# Handles leader elector
{:ele_trust, proc} -> {:ele_trust, proc} ->
log("#{state.name} - #{proc} is leader") log("#{state.name} - #{proc} is leader")
Enum.reduce(Map.keys(state.instmap), %{state | leader: proc}, fn inst, st -> Enum.reduce(Map.keys(state.instmap), %{state | leader: proc}, fn inst, st ->
prepare(st, inst) prepare(st, inst)
end) end)
# Handles a proposal from the parent process
{:propose, inst, value, pid_to_inform, action} -> {:propose, inst, value, pid_to_inform, action} ->
log("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}") log("#{state.name} - Propose #{inspect(inst)} with action #{inspect(action)}")
@@ -191,7 +275,8 @@ defmodule Paxos do
EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value}) EagerReliableBroadcast.broadcast(state.name, {:other_propose, inst, value})
prepare(state, inst) prepare(state, inst)
end end
# Handles the sharing of a proposal value to other processes
{:rb_deliver, proc, {:other_propose, inst, value}} -> {:rb_deliver, proc, {:other_propose, inst, value}} ->
cond do cond do
has_finished(state, inst, true) -> has_finished(state, inst, true) ->
@@ -208,7 +293,8 @@ defmodule Paxos do
end end
prepare(state, inst) prepare(state, inst)
end end
# Handles a prepare request from the leader
{:rb_deliver, proc, {:prepare, proc, inst, ballot}} -> {:rb_deliver, proc, {:prepare, proc, inst, ballot}} ->
log("#{state.name} - prepare from #{proc}") log("#{state.name} - prepare from #{proc}")
@@ -239,7 +325,8 @@ defmodule Paxos do
safecast(proc, {:nack, inst, ballot}) safecast(proc, {:nack, inst, ballot})
state state
end end
# Handles a nack
{:nack, inst, ballot} -> {:nack, inst, ballot} ->
log("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}") log("#{state.name} - nack #{inspect(inst)} #{inspect(ballot)}")
@@ -266,6 +353,7 @@ defmodule Paxos do
state state
end end
# Handles an abort
{:rb_deliver, _proc, {:abort, inst, ballot}} -> {:rb_deliver, _proc, {:abort, inst, ballot}} ->
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@@ -281,6 +369,8 @@ defmodule Paxos do
state state
end end
# Handles a prepared
# Sends out accept when a quorum is met
{:prepared, inst, ballot, accepted_ballot, accepted_value} -> {:prepared, inst, ballot, accepted_ballot, accepted_value} ->
log( log(
"#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}" "#{state.name} - prepared #{inspect(inst)} #{inspect(ballot)} #{inspect(accepted_ballot)} #{inspect(accepted_value)}"
@@ -307,6 +397,7 @@ defmodule Paxos do
state state
end end
# Handles a accept
{:rb_deliver, proc, {:accept, inst, ballot, value}} -> {:rb_deliver, proc, {:accept, inst, ballot, value}} ->
cond do cond do
has_finished(state, inst) -> has_finished(state, inst) ->
@@ -334,6 +425,8 @@ defmodule Paxos do
end end
end end
# Handles a accept
# Sends out accept when a decide when a quoeom is met
{:accepted, inst, ballot} -> {:accepted, inst, ballot} ->
log("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}") log("#{state.name} - accepted #{inspect(inst)} #{inspect(ballot)}")
@@ -351,14 +444,17 @@ defmodule Paxos do
true -> true ->
state state
end end
# handles a parent request to get a value
{:get_value, inst, pid_to_inform} -> {:get_value, inst, pid_to_inform} ->
# log("#{state.name} get_value") # log("#{state.name} get_value")
if has_finished(state, inst, true) do if has_finished(state, inst, true) do
safecast(pid_to_inform, {:get_value_res, inst, state.decided[inst]}) safecast(pid_to_inform, {:get_value_res, inst, state.decided[inst]})
end end
state 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}} -> {:rb_deliver, _, {:decide, inst, value}} ->
log("#{state.name} - decided #{inspect(inst)} #{inspect(value)}") log("#{state.name} - decided #{inspect(inst)} #{inspect(value)}")
@@ -375,13 +471,12 @@ defmodule Paxos do
end end
end end
# @doc """
# Puts process in the preapre state Does the logic to decide when to send the prepare messages
# Also sets the state nessesary to run the proposal
"""
def prepare(state, _) when state.leader != state.name, do: state defp prepare(state, _) when state.leader != state.name, do: state
defp prepare(state, inst) do
def prepare(state, inst) do
cond do cond do
state.instmap[inst] == nil -> state.instmap[inst] == nil ->
state state
@@ -413,12 +508,11 @@ defmodule Paxos do
end end
end end
# @doc """
# Process the prepared responses Processes the prepared messages and when a quorum is met the accept messages are send
# """
def prepared(state, _) when state.leader != state.name, do: state defp prepared(state, _) when state.leader != state.name, do: state
defp prepared(state, inst) do
def prepared(state, inst) do
if length(state.instmap[inst].prepared_values) >= floor(length(state.processes) / 2) + 1 and if 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} =
@@ -461,13 +555,12 @@ defmodule Paxos do
end end
end end
# @doc """
# Process the accepted responses Processes the accepted messages and when a qurum is met decide on the value
# """
def accepted(state, _) when state.leader != state.name, do: state defp accepted(state, _) when state.leader != state.name, do: state
defp accepted(state, inst) do
def accepted(state, inst) do or_state state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
if state.instmap[inst].accepted >= floor(length(state.processes) / 2) + 1 do
value = state.instmap[inst].ballot_value value = state.instmap[inst].ballot_value
if state.instmap[inst].action == :kill_before_decision do if state.instmap[inst].action == :kill_before_decision do
@@ -489,69 +582,76 @@ defmodule Paxos do
| decided: Map.put(state.decided, inst, value), | decided: Map.put(state.decided, inst, value),
instmap: Map.delete(state.instmap, inst) instmap: Map.delete(state.instmap, inst)
} }
else
state
end end
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 def propose(pid, inst, value, t, action \\ nil) do
send(pid, {:propose, inst, value, self(), action}) send(pid, {:propose, inst, value, self(), action})
propose_loop({inst, t}) propose_loop(inst, t)
end end
def propose_loop(input) do @doc """
{_, t} = input Waits the right message from the paxos replica
"""
defp propose_loop(inst, t) do
receive do receive do
{:timeout, inst} -> {:timeout, ^inst} -> {:timeout}
check_and_apply({:timeout}, inst, input, &propose_loop/1) {:abort, ^inst} -> {:abort}
{:decision, ^inst, d} -> {:decision, d}
{:abort, inst} ->
check_and_apply({:abort}, inst, input, &propose_loop/1)
{:decision, inst, d} ->
check_and_apply({:decision, d}, inst, input, &propose_loop/1)
x -> x ->
Process.send_after(self(), x, 500) Process.send_after(self(), x, 500)
propose_loop(input) propose_loop(inst, t)
after after
t -> {:timeout} t -> {:timeout}
end end
end end
@doc """
Sends the :get_value message to the paxos replica
"""
def get_decision(pid, inst, t) do def get_decision(pid, inst, t) do
send(pid, {:get_value, inst, self()}) send(pid, {:get_value, inst, self()})
get_decision_loop({inst, t}) get_decision_loop(inst, t)
end end
def get_decision_loop(input) do @doc """
{_, t} = input Sends waits for the right message from the paxos replica
"""
defp get_decision_loop(inst, t) do
receive do receive do
{:get_value_res, inst, v} -> {:get_value_res, ^inst, v} ->
check_and_apply(v, inst, input, &get_decision_loop/1) v
x -> x ->
Process.send_after(self(), x, 500) Process.send_after(self(), x, 500)
get_decision_loop(input) get_decision_loop(inst, t)
after after
t -> nil t -> nil
end end
end end
def check_and_apply(v, inst, input, fun) do
{inInst, _} = input
if inst == inInst do
v
else
fun.(input)
end
end
end end
defmodule Ballot do defmodule Ballot do
def init(name, number \\ 0), do: {name, number} @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 def inc(b, name \\ nil) do
{old_name, number} = b {old_name, number} = b
@@ -560,7 +660,10 @@ defmodule Ballot do
number + 1 number + 1
} }
end end
@doc """
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
@@ -569,7 +672,9 @@ defmodule Ballot do
end end
end end
@doc """
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
@@ -578,25 +683,42 @@ defmodule Ballot do
diff diff
end end
end end
@doc """
Compare 2 ballots
"""
def compare(b1, operator, b2), do: operator.(diff(b1, b2), 0) def compare(b1, operator, b2), do: operator.(diff(b1, b2), 0)
end end
defmodule EagerReliableBroadcast do 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 require Utils
import 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", "")) String.to_atom(String.replace(Atom.to_string(name), "_rb", ""))
end 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 def start(name, processes) do
pid = spawn(EagerReliableBroadcast, :init, [name, processes]) pid = spawn(EagerReliableBroadcast, :init, [name, processes])
register_name(alter_name(name, "_rb"), pid) register_name(alter_name(name, "_rb"), pid)
end end
# Init event must be the first @doc """
# one after the component is created Sets state and calls the run function
"""
def init(parent, processes) do def init(parent, processes) do
state = %{ state = %{
name: alter_name(parent, "_rb"), name: alter_name(parent, "_rb"),
@@ -639,17 +761,24 @@ defmodule EagerReliableBroadcast do
############# #############
# Interface # # 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}) def broadcast(name, m), do: safecast(alter_name(name, "_rb"), {:broadcast, m})
end end
#
# Emits {:ele_trust, proc }
#
defmodule EventualLeaderElector do defmodule EventualLeaderElector do
@moduledoc """
This modules implents eventual leader elector
emits {:ele_leader, proc}
"""
require Utils require Utils
import 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 def start(name, processes) do
new_name = alter_name(name, "_ele") new_name = alter_name(name, "_ele")
pid = spawn(EventualLeaderElector, :init, [new_name, name, processes]) pid = spawn(EventualLeaderElector, :init, [new_name, name, processes])
@@ -657,8 +786,9 @@ defmodule EventualLeaderElector do
register_name(new_name, pid) register_name(new_name, pid)
end end
# Init event must be the first @doc """
# one after the component is created Initializes state and starts the run function
"""
def init(name, parent, processes) do def init(name, parent, processes) do
processes = Enum.map(processes, fn name -> alter_name(name, "_ele") end) processes = Enum.map(processes, fn name -> alter_name(name, "_ele") end)
@@ -674,7 +804,10 @@ defmodule EventualLeaderElector do
run(request_heartbeats(state)) run(request_heartbeats(state))
end 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 def request_heartbeats(state) do
state = %{state | heard_back: MapSet.new(), seq: state.seq + 1} state = %{state | heard_back: MapSet.new(), seq: state.seq + 1}
beb_broadcast({:heartbeat_request, state.name, state.seq}, state.processes) beb_broadcast({:heartbeat_request, state.name, state.seq}, state.processes)
@@ -684,15 +817,18 @@ defmodule EventualLeaderElector do
end end
runfn do runfn do
# Answer to a heartbeat_request
{:heartbeat_request, name, seq} -> {:heartbeat_request, name, seq} ->
safecast(name, {:heartbeat, state.parent, seq}) safecast(name, {:heartbeat, state.parent, seq})
state state
# Update heard_back with this name
{:heartbeat, name, seq} -> {:heartbeat, name, seq} ->
or_state seq == state.seq do or_state seq == state.seq do
%{state | heard_back: MapSet.put(state.heard_back, name)} %{state | heard_back: MapSet.put(state.heard_back, name)}
end end
# One time out check the heard_back and send if a new leader is elected inform the parent
{:timeout} -> {:timeout} ->
state = or_state MapSet.size(state.heard_back) >= floor(length(state.processes)/2) + 1 do 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) to_trust = Enum.at(Enum.sort(MapSet.to_list(state.heard_back)), 0)

View File

@@ -1,6 +1,13 @@
defmodule ServerMacros do defmodule ServerMacros do
@moduledoc """
def create_create_loop(name, do: match_exp, else: process_exp) do This module defines some helper macros that are used within the server macro
"""
@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" function_name = :"#{name}_loop"
ast1 = quote do ast1 = quote do
@@ -19,7 +26,6 @@ defmodule ServerMacros do
quote do quote do
defp unquote(function_name)(v, t) do defp unquote(function_name)(v, t) do
var!(v) = v var!(v) = v
unquote(process_exp)
receive do receive do
unquote(ast3) unquote(ast3)
after after
@@ -28,19 +34,11 @@ defmodule ServerMacros do
end end
end end
end end
def create_create_loop(name, do: exp, else: else_exp) do @doc """
create_create_loop(name, do: exp, else: else_exp) this function tries to propose and on failure it calls the else block and
end on success tries to match with the expressions on the do block
"""
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
defmacro try_propose(val, do: ready, else: recal_do) do defmacro try_propose(val, do: ready, else: recal_do) do
ast1 = quote do ast1 = quote do
{:timeout} -> unquote(recal_do) {:timeout} -> unquote(recal_do)
@@ -68,20 +66,29 @@ defmodule ServerMacros do
end end
defmodule Server do defmodule Server do
@moduledoc """
Contains the to run the server Code
"""
require ServerMacros require ServerMacros
import ServerMacros import ServerMacros
require Utils require Utils
import Utils import Utils
create_log 2 create_log 2
@doc """
Contains the start code for the server
"""
def start(name, participants) do def start(name, participants) do
log("starting server") log("#{name} Starting server")
pid = spawn(Server, :init, [name, participants]) pid = spawn(Server, :init, [name, participants])
register_name(name, pid, false) register_name(name, pid, false)
end end
@doc """
Initializes the state and starts the paxos inspect and then it calls the run fn
"""
def init(name, participants) do def init(name, participants) do
paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true) paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true)
state = %{ state = %{
@@ -97,19 +104,26 @@ defmodule Server do
runfn do runfn do
{:start_game, participants, pid_to_inform} -> {:start_game, participants, pid_to_inform} ->
{state, game_id} = try_to_create_game(state, participants) or_state is_list(participants) do
state = set_modifed(state, game_id) {state, game_id} = try_to_create_game(state, participants)
safecast(pid_to_inform, {:start_game_ans, game_id}) state = set_modifed(state, game_id)
state safecast(pid_to_inform, {:start_game_ans, game_id})
state
end
{:get_game_state, game_id, pid_to_inform} -> {: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} -> {:make_move, game_id, move, pid_to_inform} ->
try_to_play_checks(state, game_id, move, pid_to_inform) try_to_play_checks(state, game_id, move, pid_to_inform)
end 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 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 ->
@@ -137,14 +151,32 @@ defmodule Server do
safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand}) safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand})
set_modifed(state, game_id) set_modifed(state, game_id)
true -> 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 end
end end
@doc """
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
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} try_propose {:make_move, game_id, name, move, new_hand}
do do
{:decision, {:make_move, ^game_id, ^name, ^move, ^new_hand}} -> {:decision, {:make_move, ^game_id, ^name, ^move, ^new_hand}} ->
@@ -176,7 +208,10 @@ defmodule Server do
try_to_play(state, game_id, move, pid_to_inform) try_to_play(state, game_id, move, pid_to_inform)
end end
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 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 ->
@@ -204,19 +239,27 @@ defmodule Server do
state state
end end
end end
@doc """
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)
if r1 <= 10 do cond do
:+ r1 <= 1 -> :b
else r1 <= 5 -> :-
mx = game_state |> Enum.filter(fn m -> m != :+ end) |> Enum.max() r1 <= 20 -> :+
mn = max(mx - 20, 1) true ->
mx = max(mx - 2, 4) mx = game_state |> Enum.filter(fn m -> m != :+ end) |> Enum.max()
Enum.random(mn..mx) mn = max(mx - 20, 1)
mx = max(mx - 2, 4)
Enum.random(mn..mx)
end end
end end
@doc """
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)
@@ -238,14 +281,19 @@ defmodule Server do
# #
# Utils # Utils
# #
@doc """
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
_ -> false _ -> false
end end
end end
@doc """
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
@@ -253,9 +301,12 @@ defmodule Server do
qurey_status(state) qurey_status(state)
end end
end end
@doc """
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) 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})}
end end
end end
@@ -289,6 +340,7 @@ defmodule Server do
if b == a do if b == a do
case b do case b do
:+ -> simplify_game_state_pluses(tl, indexed_game_state) :+ -> simplify_game_state_pluses(tl, indexed_game_state)
:b -> simplify_game_state_pluses(tl, indexed_game_state)
n -> n ->
list = list =
indexed_game_state |> indexed_game_state |>
@@ -310,6 +362,33 @@ defmodule Server do
end end
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 defp is_merged(item) do
case item do case item do
{:merged, _} -> true {:merged, _} -> true
@@ -332,7 +411,7 @@ defmodule Server do
{:merged, _} -> indexed_game_state {:merged, _} -> indexed_game_state
_ -> _ ->
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 Enum.filter(fn {x, ti} -> cond do
b_i == ti -> false b_i == ti -> false
a_i == ti -> false a_i == ti -> false
@@ -362,24 +441,16 @@ defmodule Server do
defp remove_merged([], rec, _), do: rec defp remove_merged([], rec, _), do: rec
defp remove_merged([{:merged, n} | tl], rec, add) do defp remove_merged([{:merged, n} | tl], rec, add),
if add do do: if add, do: remove_merged(tl, rec ++ [n], false),
remove_merged(tl, rec ++ [n], false) else: remove_merged(tl, rec, false)
else
remove_merged(tl, rec, false)
end
end
defp remove_merged([n | tl], rec, add), do: defp remove_merged([n | tl], rec, add), do:
remove_merged(tl, rec ++ [n], add) remove_merged(tl, rec ++ [n], add)
defp remove_merged(list) do defp remove_merged(list), do: remove_merged(list, [], true)
log("#{inspect(list)}")
remove_merged(list, [], true)
end
defp simplify_game_state(game_state) do defp simplify_game_state(game_state) do
log("game_state: #{inspect(game_state)}")
indexed_game_state = indexed_game_state =
game_state |> game_state |>
reindex(false) reindex(false)
@@ -388,13 +459,12 @@ defmodule Server do
indexed_game_state |> indexed_game_state |>
Enum.filter(fn x -> case x do Enum.filter(fn x -> case x do
{:+, _} -> true {:+, _} -> true
{:b, _} -> true
_ -> false _ -> false
end end
end) |> end) |>
simplify_game_state_pluses(indexed_game_state) simplify_game_state_pluses(indexed_game_state)
log("game_state2: #{inspect(indexed_game_state)}")
if repeat do if repeat do
indexed_game_state |> indexed_game_state |>
Enum.map(fn {v, _} -> v end) |> Enum.map(fn {v, _} -> v end) |>
@@ -414,14 +484,23 @@ defmodule Server do
%{state | instance: state.instance + 1 } %{state | instance: state.instance + 1 }
game -> game ->
game_state = game.game_state game_state = game.game_state
{b, e} = Enum.split(game_state, pos_move) game_state = if game.hand[player_name] == :- do
game_state = b ++ [ game.hand[player_name] ] ++ e 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) game_state = simplify_game_state(game_state)
hand = Map.put(game.hand, player_name, new_hand) 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 if length(game.game_state) > 16 do
%{state| games: Map.put(state.games, game_id, {:finished, Enum.sum(game.game_state)}), instance: state.instance + 1} %{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 else
%{state| games: Map.put(state.games, game_id, game), instance: state.instance + 1} %{state| games: Map.put(state.games, game_id, game), instance: state.instance + 1}
end end
@@ -468,18 +547,15 @@ defmodule Server do
end end
create_loop :get_game_state do 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} -> {:game_state, ^v, :not_playing} ->
log("Not Playing in that game") log("Not Playing in that game")
{:not_playing} {:not_playing}
{:game_state, ^v, :game_finished, score} -> {:game_state, ^v, :game_finished, score} ->
log("Game finsihed, #{score}") log("Game finsihed, #{score}")
{:game_finished, 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} -> {:game_state, ^v, :game_does_not_exist} ->
log("Got game does not exist") log("Got game does not exist")
{:not_exists} {:not_exists}
@@ -503,6 +579,9 @@ defmodule Server do
{:make_move, ^v, :player_moved_before, game_state, hand} -> {:make_move, ^v, :player_moved_before, game_state, hand} ->
log("Player moved_before, #{inspect(game_state)} #{inspect(hand)}") log("Player moved_before, #{inspect(game_state)} #{inspect(hand)}")
{:player_moved_before, game_state, hand} {:player_moved_before, game_state, hand}
{:make_move, ^v, :invalid_move} ->
log("Invalid Move")
{:invalid_move}
{:make_move, ^v, game_state, hand} -> {:make_move, ^v, game_state, hand} ->
log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}") log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
{:state, game_state, hand} {:state, game_state, hand}
@@ -534,87 +613,234 @@ defmodule Client do
create_log 3 create_log 3
def play_game(process, game_id) do 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 end
def to_name(list) when is_list(list), do: list |> Enum.map(fn x -> to_name(x) 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(atom) when is_atom(atom), do: atom
def to_name(i) do 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" ] |> 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)
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 end
def interpolate(list) do def interpolate(list) do
[list, 0..length(list) - 1] |> [list, 0..length(list) - 1] |>
Enum.zip() |> Enum.zip() |>
Enum.reduce([], fn {v, i}, acc -> acc ++ [i, v] end) |> Enum.reduce([], fn {v, i}, acc -> acc ++ [String.pad_leading("#{i}", 3, " "), v] end)
Enum.map(fn x -> String.pad_leading("#{x}", 2, " ") end)
end end
def printpt(game_state) do def grow_empty_list(t, i, acc) when i == 0, do: t ++ acc
rad = 8; def grow_empty_list([], i, acc), do: grow_empty_list(acc, i, [])
printpt1(game_state, 0, rad) 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 end
defp count_char(str, char) do def fill([], _, acc), do: acc
String.split(str, "") |> Enum.reduce(0, fn x, acc -> if x == char do acc + 1 else acc end end) def fill([hd | tail], [add_hd | add_tail], acc) do
fill(tail, add_tail ,acc ++ [hd] ++ add_hd)
end end
def printpt1(_, i, rad) when i > rad * 2, do: nil def printpt(game_state, hand), do: printpt(game_state, hand, 0)
def printpt1(game_state, i, rad) do def printpt(_, _, i) when i > 16, do: nil
res = printpt2(game_state, i, -5, rad, "") def printpt(game_state, hand, i) do
res = case i do
" xxx \n" 0 ->
" xxx yyy \n" " xxx \n" |>
" xxx yyy \n" String.replace("xxx", Enum.at(game_state, 1))
" xxx yyy \n" 1 ->
" xxx yyy \n" " xxx yyy \n" |>
" xxx yyy \n" String.replace("xxx", Enum.at(game_state, 0)) |>
" xxx yyy \n" String.replace("yyy", Enum.at(game_state, 2))
" xxx yyy \n" 2 ->
" xxx yyy \n" " xxx yyy \n" |>
" xxx yyy \n" String.replace("xxx", Enum.at(game_state, 31)) |>
" xxx yyy \n" String.replace("yyy", Enum.at(game_state, 3))
" xxx yyy \n" 3 ->
" xxx yyy \n" " xxx yyy \n" |>
" xxx yyy \n" String.replace("xxx", Enum.at(game_state, 30)) |>
" xxx yyy \n" String.replace("yyy", Enum.at(game_state, 4))
" xxx \n" 4 ->
" xxx yyy \n" |>
String.replace("xxx", Enum.at(game_state, 29)) |>
String.replace("yyy", Enum.at(game_state, 5))
5 ->
if i != 15 do " xxx yyy \n" |>
res = case i do String.replace("xxx", Enum.at(game_state, 28)) |>
0 -> String.replace("yyy", Enum.at(game_state, 6))
x = count_char(res, "x") 6 ->
log("#{x}") " xxx yyy \n" |>
#TODO String.replace("xxx", Enum.at(game_state, 27)) |>
res String.replace("yyy", Enum.at(game_state, 7))
16 -> 7 ->
#TODO " xxx yyy \n" |>
res String.replace("xxx", Enum.at(game_state, 26)) |>
v -> String.replace("yyy", Enum.at(game_state, 8))
#TODO 8 ->
res " xxx zzz yyy \n" |>
end String.replace("xxx", Enum.at(game_state, 25)) |>
IO.write(" #{res} \n") 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 end
printpt1(game_state, i + 1, rad)
end
def printpt2(_, _, j, rad, res) when j > rad * 4 + 10, do: res IO.write("#{res}")
def printpt2(game_state, i, j, rad, res) do printpt(game_state, hand, i + 1)
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)
end end
end end

191
notes Normal file
View 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 "
" # "