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
- 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

1
README.md Normal file
View File

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

View File

@@ -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)

View File

@@ -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} ->
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 ->
@@ -136,15 +150,33 @@ defmodule Server do
game.modified ->
safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand})
set_modifed(state, game_id)
true ->
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,11 +240,16 @@ 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
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)
@@ -217,6 +257,9 @@ defmodule Server do
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
game_state = if game.hand[player_name] == :- do
List.delete_at(game_state, pos_move)
else
{b, e} = Enum.split(game_state, pos_move)
game_state = b ++ [ game.hand[player_name] ] ++ e
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
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 ->
x = count_char(res, "x")
log("#{x}")
#TODO
res
" 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 ->
#TODO
res
v ->
#TODO
res
end
IO.write(" #{res} \n")
end
printpt1(game_state, i + 1, rad)
" xxx \n" |>
String.replace("xxx", Enum.at(game_state, 17))
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
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 "
" # "