This repository has been archived on 2024-01-29. You can view files and clone it, but cannot push or open issues or pull requests.
distributed_system_coursework/lib/server.ex

621 lines
18 KiB
Elixir
Raw Normal View History

2024-01-16 19:59:01 +00:00
defmodule ServerMacros do
2024-01-17 19:05:18 +00:00
def create_create_loop(name, do: match_exp, else: process_exp) do
2024-01-16 19:59:01 +00:00
function_name = :"#{name}_loop"
ast1 = quote do
{:timeout} -> :timeout
end
ast2 = quote do
value ->
2024-01-17 19:05:18 +00:00
# TODO check spelling
log("Disreguarding: #{inspect(value)}")
# Process.send_after(self(), value, t + 2000)
2024-01-16 22:06:10 +00:00
unquote(function_name)(v, t)
2024-01-16 19:59:01 +00:00
end
ast3 = ast1 ++ match_exp ++ ast2
2024-01-16 20:42:57 +00:00
2024-01-16 23:46:30 +00:00
quote do
2024-01-18 00:29:02 +00:00
defp unquote(function_name)(v, t) do
2024-01-16 23:46:30 +00:00
var!(v) = v
unquote(process_exp)
receive do
unquote(ast3)
after
2024-01-17 19:05:18 +00:00
t -> :timeout
2024-01-16 19:59:01 +00:00
end
end
end
end
2024-01-16 20:42:57 +00:00
def create_create_loop(name, do: exp, else: else_exp) do
2024-01-17 19:05:18 +00:00
create_create_loop(name, do: exp, else: else_exp)
2024-01-16 20:42:57 +00:00
end
2024-01-16 19:59:01 +00:00
def create_create_loop(name, do: exp) do
2024-01-17 19:05:18 +00:00
create_create_loop(name, do: exp, else: nil)
2024-01-16 19:59:01 +00:00
end
defmacro create_loop(name, clauses) do
create_create_loop(name, clauses)
end
defmacro try_propose(val, do: ready, else: recal_do) do
ast1 = quote do
{:timeout} -> unquote(recal_do)
{:abort} -> unquote(recal_do)
end
ast2 = quote do
{:decision, v} ->
var!(state) = apply_game(var!(state), v)
unquote(recal_do)
v ->
raise "Unknown message on try_propose #{inspect(v)}"
end
ast3 = ast1 ++ ready ++ ast2
quote do
v = Paxos.propose(var!(state).paxos, var!(state).instance, unquote(val), 1000)
case v do
unquote(ast3)
end
end
end
end
defmodule Server do
require ServerMacros
import ServerMacros
require Utils
import Utils
2024-01-18 00:29:02 +00:00
create_log 2
2024-01-16 19:59:01 +00:00
def start(name, participants) do
log("starting server")
pid = spawn(Server, :init, [name, participants])
register_name(name, pid, false)
end
def init(name, participants) do
2024-01-16 20:42:57 +00:00
paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true)
2024-01-16 19:59:01 +00:00
state = %{
name: name,
procs: participants,
games: %{},
paxos: paxos,
instance: 0,
}
run(state)
end
runfn do
{:start_game, participants, pid_to_inform} ->
{state, game_id} = try_to_create_game(state, participants)
2024-01-16 23:46:30 +00:00
state = set_modifed(state, game_id)
2024-01-16 19:59:01 +00:00
safecast(pid_to_inform, {:start_game_ans, game_id})
state
2024-01-16 22:06:10 +00:00
{:get_game_state, game_id, pid_to_inform} ->
get_game_state(state, game_id, pid_to_inform)
2024-01-16 23:46:30 +00:00
{:make_move, game_id, move, pid_to_inform} ->
2024-01-17 10:58:37 +00:00
try_to_play_checks(state, game_id, move, pid_to_inform)
2024-01-16 23:46:30 +00:00
end
2024-01-18 00:29:02 +00:00
defp try_to_play_checks(state, game_id, move, pid_to_inform, repeat \\ false) do
2024-01-16 23:46:30 +00:00
cond do
state.games[game_id] == :not_playing_in_game ->
safecast(pid_to_inform, {:make_move, game_id, :not_playing})
state
state.games[game_id] == nil ->
if repeat do
safecast(pid_to_inform, {:make_move, game_id, :game_does_not_exist})
state
else
state = qurey_status(state)
try_to_play_checks(state, game_id, move, pid_to_inform, true)
end
true ->
state = qurey_status(state)
game = state.games[game_id]
cond do
2024-01-17 19:05:18 +00:00
is_finished(state, game_id) ->
2024-01-16 23:46:30 +00:00
{_, score} = state.games[game_id]
safecast(pid_to_inform, {:make_move, game_id, :game_finished, score})
state
2024-01-16 23:50:32 +00:00
game.modified ->
safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand})
set_modifed(state, game_id)
2024-01-16 23:46:30 +00:00
true ->
try_to_play(state, game_id, move, pid_to_inform)
end
end
end
2024-01-18 00:29:02 +00:00
defp try_to_play(state, game_id, move, pid_to_inform) do
2024-01-16 23:46:30 +00:00
name = state.name
2024-01-17 19:05:18 +00:00
new_hand = get_hand_for_game_state(state.games[game_id].game_state)
2024-01-17 10:58:37 +00:00
try_propose {:make_move, game_id, name, move, new_hand}
2024-01-16 23:46:30 +00:00
do
2024-01-17 10:58:37 +00:00
{:decision, {:make_move, ^game_id, ^name, ^move, ^new_hand}} ->
state = apply_game(state, {:make_move, game_id, name, move, new_hand})
2024-01-16 23:46:30 +00:00
if is_finished(state, game_id) do
{_, score} = state.games[game_id]
safecast(pid_to_inform, {:make_move, game_id, :game_finished, score})
else
game = state.games[game_id]
safecast(pid_to_inform, {:make_move, game_id, game.game_state, game.hand[state.name]})
end
set_modifed(state, game_id)
2024-01-17 10:58:37 +00:00
{:decision, {:make_move, ^game_id, new_name, new_move, new_new_hand}} ->
state = apply_game(state, {:make_move, game_id, new_name, new_move, new_new_hand})
2024-01-16 23:46:30 +00:00
if is_finished(state, game_id) do
{_, score} = state.games[game_id]
safecast(pid_to_inform, {:make_move, game_id, :game_finished, score})
else
game = state.games[game_id]
safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand})
end
set_modifed(state, game_id)
else
try_to_play(state, game_id, move, pid_to_inform)
end
2024-01-16 22:06:10 +00:00
end
2024-01-18 00:29:02 +00:00
defp get_game_state(state, game_id, pid_to_inform, repeat \\ false) do
2024-01-16 22:06:10 +00:00
cond do
state.games[game_id] == :not_playing_in_game ->
safecast(pid_to_inform, {:game_state, game_id, :not_playing})
state
state.games[game_id] == nil ->
if repeat do
safecast(pid_to_inform, {:game_state, game_id, :game_does_not_exist})
state
else
state = qurey_status(state)
get_game_state(state, game_id, pid_to_inform, true)
end
true ->
state = qurey_status(state)
2024-01-17 19:05:18 +00:00
if is_finished(state, game_id) do
{_, score} = state.games[game_id]
safecast(pid_to_inform, {:game_state, game_id, :game_finished, score})
else
game = state.games[game_id]
safecast(pid_to_inform, {:game_state, game_id, game.game_state, game.hand[state.name]})
end
2024-01-16 22:06:10 +00:00
state
end
2024-01-16 19:59:01 +00:00
end
2024-01-18 00:29:02 +00:00
defp get_hand_for_game_state(game_state) do
2024-01-17 19:05:18 +00:00
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)
end
end
2024-01-18 00:29:02 +00:00
defp try_to_create_game(state, participants) do
2024-01-16 19:59:01 +00:00
game_ids = Map.keys(state.games)
2024-01-16 20:42:57 +00:00
latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1)
new_game_id = if latest do latest else 0 end + 1
2024-01-16 19:59:01 +00:00
2024-01-17 19:05:18 +00:00
new_game_state = Enum.to_list(0..Enum.random(3..8)) |> Enum.map(fn _ -> Enum.random(1..4) end)
hand = Enum.reduce(participants, %{}, fn p, acc -> Map.put(acc, p, get_hand_for_game_state(new_game_state)) end)
2024-01-16 19:59:01 +00:00
try_propose {:start_game, new_game_id, participants, new_game_state, hand}
do
{:decision, {:start_game, ^new_game_id, ^participants, ^new_game_state, ^hand}} ->
state = apply_game(state, {:start_game, new_game_id, participants, new_game_state, hand})
{state, new_game_id}
else
try_to_create_game(state, participants)
end
end
#
2024-01-16 22:06:10 +00:00
# Utils
2024-01-16 19:59:01 +00:00
#
2024-01-16 22:06:10 +00:00
2024-01-18 00:29:02 +00:00
defp is_finished(state, game) do
2024-01-16 23:46:30 +00:00
case state.games[game] do
{:finished, _} -> true
_ -> false
end
end
2024-01-18 00:29:02 +00:00
defp qurey_status(state) do
2024-01-16 22:06:10 +00:00
v = Paxos.get_decision(state.paxos, state.instance, 100)
or_state v != nil do
state = apply_game(state, v)
qurey_status(state)
end
end
2024-01-16 19:59:01 +00:00
2024-01-18 00:29:02 +00:00
defp set_modifed(state, game, val \\ false) do
2024-01-16 23:46:30 +00:00
or_state not is_finished(state, game) do
2024-01-17 19:05:18 +00:00
%{state | games: Map.put(state.games, game, %{state.games[game] | modified: val})}
2024-01-16 23:46:30 +00:00
end
end
2024-01-16 22:06:10 +00:00
#
# Apply Game States
#
2024-01-16 23:46:30 +00:00
2024-01-18 00:29:02 +00:00
defp get_index(indexed_game_state, spos, index) do
2024-01-17 19:05:18 +00:00
index = spos + index
len = length(indexed_game_state)
cond do
index < 0 ->
len - 1
index >= len ->
rem(index, len)
true ->
index
end
end
2024-01-18 00:29:02 +00:00
defp simplify_game_state_pluses([], indexed_game_state), do: {false, indexed_game_state}
defp simplify_game_state_pluses([{:+, i} | tl], indexed_game_state) do
2024-01-17 19:05:18 +00:00
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)
if b == a do
case b do
:+ -> simplify_game_state_pluses(tl, indexed_game_state)
n ->
list =
indexed_game_state |>
Enum.map(fn {x, ti} -> if ti == i, do: {{:merged, n + 1}, 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)}
end
else
simplify_game_state_pluses(tl, indexed_game_state)
end
else
simplify_game_state_pluses(tl, indexed_game_state)
end
end
2024-01-18 00:29:02 +00:00
defp is_merged(item) do
2024-01-17 19:05:18 +00:00
case item do
{:merged, _} -> true
_ -> false
end
end
2024-01-18 00:29:02 +00:00
defp expand_merge(indexed_game_state) do
2024-01-17 19:05:18 +00:00
{{:merged, n}, i} = indexed_game_state |> Enum.find(fn {x, _} -> is_merged(x) end)
b_i = get_index(indexed_game_state, i, - 1)
a_i = get_index(indexed_game_state, i, + 1)
if b_i != a_i do
{b, b_i} = Enum.at(indexed_game_state, b_i)
{a, a_i} = Enum.at(indexed_game_state, a_i)
if a == b do
case b do
:+ -> indexed_game_state
{: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.filter(fn {x, ti} -> cond do
b_i == ti -> false
a_i == ti -> false
true -> true
end
end) |>
reindex() |>
expand_merge()
end
else
indexed_game_state
end
else
indexed_game_state
end
end
2024-01-18 00:29:02 +00:00
defp reindex(list, flat \\ true) do
2024-01-17 19:05:18 +00:00
list = if flat do
list |> Enum.map(fn {n, _} -> n end)
else
list
end
[list, 0..(length(list) - 1)] |> Enum.zip()
end
2024-01-18 00:29:02 +00:00
defp remove_merged([], rec, _), do: rec
2024-01-17 19:05:18 +00:00
2024-01-18 00:29:02 +00:00
defp remove_merged([{:merged, n} | tl], rec, add) do
2024-01-17 19:05:18 +00:00
if add do
remove_merged(tl, rec ++ [n], false)
else
remove_merged(tl, rec, false)
end
end
2024-01-18 00:29:02 +00:00
defp remove_merged([n | tl], rec, add), do:
2024-01-17 19:05:18 +00:00
remove_merged(tl, rec ++ [n], add)
2024-01-18 00:29:02 +00:00
defp remove_merged(list) do
2024-01-17 19:05:18 +00:00
log("#{inspect(list)}")
remove_merged(list, [], true)
end
2024-01-18 00:29:02 +00:00
defp simplify_game_state(game_state) do
2024-01-17 19:05:18 +00:00
log("game_state: #{inspect(game_state)}")
indexed_game_state =
game_state |>
reindex(false)
{repeat, indexed_game_state} =
indexed_game_state |>
Enum.filter(fn x -> case x do
{:+, _} -> 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) |>
remove_merged() |>
simplify_game_state()
else
indexed_game_state |> Enum.map(fn {v, _} -> v end)
end
2024-01-17 10:58:37 +00:00
end
2024-01-18 00:29:02 +00:00
defp apply_game(state, {:make_move, game_id, player_name, pos_move, new_hand}) do
2024-01-17 10:58:37 +00:00
game = state.games[game_id]
case game do
{:finished, _} ->
2024-01-17 19:05:18 +00:00
raise "Game already finished"
2024-01-17 10:58:37 +00:00
:not_playing_in_game ->
%{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
2024-01-17 19:05:18 +00:00
game_state = simplify_game_state(game_state)
2024-01-17 10:58:37 +00:00
hand = Map.put(game.hand, player_name, new_hand)
game = %{game| hand: hand, game_state: game_state }
2024-01-17 19:05:18 +00:00
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}
else
%{state| games: Map.put(state.games, game_id, game), instance: state.instance + 1}
end
2024-01-17 10:58:37 +00:00
end
2024-01-16 23:46:30 +00:00
end
2024-01-18 00:29:02 +00:00
defp apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do
2024-01-16 22:06:10 +00:00
cond do
state.games[game_id] ->
2024-01-17 19:05:18 +00:00
raise "Game Already Exists"
2024-01-16 22:06:10 +00:00
state.name in participants ->
%{state |
games: Map.put(state.games, game_id, %{
game_state: new_game_state,
participants: participants,
2024-01-16 23:46:30 +00:00
hand: hand,
modified: true,
2024-01-16 22:06:10 +00:00
}),
instance: state.instance + 1
}
true ->
%{state |
games: Map.put(state.games, game_id, :not_playing_in_game),
instance: state.instance + 1,
}
2024-01-16 19:59:01 +00:00
end
end
2024-01-18 00:29:02 +00:00
defp apply_game(_, _), do: raise "Do not know how to apply game state"
2024-01-16 19:59:01 +00:00
############
# Interface
############
create_loop :start_game do
2024-01-16 20:42:57 +00:00
{:start_game_ans, game_id} ->
2024-01-16 19:59:01 +00:00
log("Started a game #{game_id}")
2024-01-16 20:42:57 +00:00
{:start_game, game_id}
end
2024-01-16 19:59:01 +00:00
def start_game(name, participants) do
safecast(name, {:start_game, participants, self()})
2024-01-17 19:05:18 +00:00
start_game_loop(nil, 10000)
2024-01-16 22:06:10 +00:00
end
create_loop :get_game_state do
{:game_state, ^v, :not_playing} ->
2024-01-17 19:05:18 +00:00
log("Not Playing in that game")
2024-01-16 23:46:30 +00:00
{:not_playing}
2024-01-16 22:06:10 +00:00
{:game_state, ^v, game_state, hand} ->
2024-01-17 19:05:18 +00:00
log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
2024-01-16 23:46:30 +00:00
{:state, game_state, hand}
2024-01-17 19:05:18 +00:00
{: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}
2024-01-16 22:06:10 +00:00
{:game_state, ^v, :game_does_not_exist} ->
2024-01-17 19:05:18 +00:00
log("Got game does not exist")
2024-01-16 23:46:30 +00:00
{:not_exists}
2024-01-16 19:59:01 +00:00
end
2024-01-16 22:06:10 +00:00
def get_game_state(name, game_id) do
safecast(name, {:get_game_state, game_id, self()})
2024-01-17 19:05:18 +00:00
get_game_state_loop(game_id, 10000)
2024-01-16 22:06:10 +00:00
end
2024-01-16 23:46:30 +00:00
create_loop :make_move do
{:make_move, ^v, :game_does_not_exist} ->
2024-01-17 19:05:18 +00:00
log("Got game does not exist")
2024-01-16 23:46:30 +00:00
{:not_exists}
{:make_move, ^v, :not_playing} ->
2024-01-17 19:05:18 +00:00
log("Not Playing in that game")
2024-01-16 23:46:30 +00:00
{:not_playing}
{:make_move, ^v, :game_finished, score} ->
2024-01-17 19:05:18 +00:00
log("Game finsihed, #{score}")
2024-01-16 23:46:30 +00:00
{:game_finished, score}
{:make_move, ^v, :player_moved_before, game_state, hand} ->
2024-01-17 19:05:18 +00:00
log("Player moved_before, #{inspect(game_state)} #{inspect(hand)}")
2024-01-16 23:46:30 +00:00
{:player_moved_before, game_state, hand}
{:make_move, ^v, game_state, hand} ->
2024-01-17 19:05:18 +00:00
log("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
2024-01-16 23:46:30 +00:00
{:state, game_state, hand}
end
def make_move(name, game_id, move) do
safecast(name, {:make_move, game_id, move, self()})
2024-01-17 19:05:18 +00:00
make_move_loop(game_id, 10000)
2024-01-16 23:46:30 +00:00
end
2024-01-16 19:59:01 +00:00
############
# Debug
############
def spinup(number_of_participants) do
procs = Enum.to_list(0..number_of_participants) |> Enum.map(fn n -> :"p#{n}" end)
Enum.map(procs, fn proc -> Server.start(proc, procs) end)
end
def kill (pids) do
pids |> Enum.map(fn m -> Process.exit(m, :kill) end)
end
end
2024-01-18 00:29:02 +00:00
defmodule Client do
import Utils
require Utils
create_log 3
def play_game(process, game_id) do
end
def to_name(list) when is_list(list), do: list |> Enum.map(fn x -> to_name(x) end)
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)
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)
end
def printpt(game_state) do
rad = 8;
printpt1(game_state, 0, rad)
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)
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")
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)
end
end