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
Andre Henriques 25c0503504
All checks were successful
continuous-integration/drone/push Build is passing
more worke on the server
2024-01-17 10:58:37 +00:00

380 lines
11 KiB
Elixir

defmodule ServerMacros do
def create_create_loop(name, do: match_exp, else: process_exp, after: after_exp) do
function_name = :"#{name}_loop"
ast1 = quote do
{:timeout} -> :timeout
end
ast2 = quote do
value ->
log("Got unexpected value: #{inspect(value)}")
Process.send_after(self(), value, t + 2000)
unquote(function_name)(v, t)
end
ast3 = ast1 ++ match_exp ++ ast2
after_exp = if after_exp == nil do
quote do
t -> :timeout
end
else
after_exp
end
quote do
def unquote(function_name)(v, t) do
var!(v) = v
unquote(process_exp)
receive do
unquote(ast3)
after
unquote(after_exp)
end
end
end
end
def create_create_loop(name, do: exp, else: else_exp) do
create_create_loop(name, do: exp, else: else_exp, after: nil)
end
def create_create_loop(name, do: exp, after: after_exp) do
create_create_loop(name, do: exp, else: nil, after: after_exp)
end
def create_create_loop(name, do: exp) do
create_create_loop(name, do: exp, else: nil, after: nil)
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
create_log 3
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
paxos = Paxos.start(alter_name(name, "_paxos"), Enum.map(participants, fn name -> alter_name(name, "_paxos") end), true)
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)
state = set_modifed(state, game_id)
safecast(pid_to_inform, {:start_game_ans, game_id})
state
{:get_game_state, game_id, pid_to_inform} ->
get_game_state(state, game_id, pid_to_inform)
{:make_move, game_id, move, pid_to_inform} ->
try_to_play_checks(state, game_id, move, pid_to_inform)
end
def try_to_play_checks(state, game_id, move, pid_to_inform, repeat \\ false) do
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
is_finished(game, game_id) ->
{_, score} = state.games[game_id]
safecast(pid_to_inform, {:make_move, game_id, :game_finished, score})
state
game.modified ->
safecast(pid_to_inform, {:make_move, game_id, :player_moved_before, game.game_state, game.hand})
set_modifed(state, game_id)
true ->
try_to_play(state, game_id, move, pid_to_inform)
end
end
end
def try_to_play(state, game_id, move, pid_to_inform) do
name = state.name
# TODO create new hand
new_hand = 2
try_propose {:make_move, game_id, name, move, new_hand}
do
{:decision, {:make_move, ^game_id, ^name, ^move, ^new_hand}} ->
state = apply_game(state, {:make_move, game_id, name, move, new_hand})
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)
{: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})
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
end
def get_game_state(state, game_id, pid_to_inform, repeat \\ false) do
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)
safecast(pid_to_inform, {:game_state, game_id, state.games[game_id].game_state, state.games[game_id].hand[state.name]})
state
end
end
def try_to_create_game(state, participants) do
game_ids = Map.keys(state.games)
latest = Enum.at(Enum.sort(game_ids), length(game_ids) - 1)
new_game_id = if latest do latest else 0 end + 1
# TODO: randomize game state
new_game_state = [1, 1]
# TODO: randomize Initial hand value
hand = Enum.reduce(participants, %{}, fn p, acc -> Map.put(acc, p, 1) end)
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
#
# Utils
#
def is_finished(state, game) do
case state.games[game] do
{:finished, _} -> true
_ -> false
end
end
def qurey_status(state) do
v = Paxos.get_decision(state.paxos, state.instance, 100)
or_state v != nil do
state = apply_game(state, v)
qurey_status(state)
end
end
def set_modifed(state, game, val \\ false) do
or_state not is_finished(state, game) do
%{state | games: Map.put(state.games, game, %{state.games | modified: val})}
end
end
#
# Apply Game States
#
def simplify_game_state(game_state) do
# TODO actualy do this
game_state
end
def apply_game(state, {:make_move, game_id, player_name, pos_move, new_hand}) do
game = state.games[game_id]
case game do
{:finished, _} ->
raise :game_already_finished
: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
game_state = simplefy_game_state(game_state)
hand = Map.put(game.hand, player_name, new_hand)
game = %{game| hand: hand, game_state: game_state }
# TODO decide if it's ending state
%{state| games: Map.put(state.games, game_id, game)}
end
end
def apply_game(state, {:start_game, game_id, participants, new_game_state, hand}) do
cond do
state.games[game_id] ->
raise :game_already_exists
state.name in participants ->
%{state |
games: Map.put(state.games, game_id, %{
game_state: new_game_state,
participants: participants,
hand: hand,
modified: true,
}),
instance: state.instance + 1
}
true ->
%{state |
games: Map.put(state.games, game_id, :not_playing_in_game),
instance: state.instance + 1,
}
end
end
def apply_game(_, _), do: raise :do_not_know_how_to_apply_game_state
############
# Interface
############
create_loop :start_game do
{:start_game_ans, game_id} ->
log("Started a game #{game_id}")
{:start_game, game_id}
end
def start_game(name, participants) do
safecast(name, {:start_game, participants, self()})
start_game_loop(nil, 1000)
end
create_loop :get_game_state do
{:game_state, ^v, :not_playing} ->
IO.puts("Not Playing in that game")
{:not_playing}
{:game_state, ^v, game_state, hand} ->
IO.puts("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
{:state, game_state, hand}
{:game_state, ^v, :game_does_not_exist} ->
IO.puts("Got game does not exist")
{:not_exists}
end
def get_game_state(name, game_id) do
safecast(name, {:get_game_state, game_id, self()})
get_game_state_loop(game_id, 1000)
end
create_loop :make_move do
{:make_move, ^v, :game_does_not_exist} ->
IO.puts("Got game does not exist")
{:not_exists}
{:make_move, ^v, :not_playing} ->
IO.puts("Not Playing in that game")
{:not_playing}
{:make_move, ^v, :game_finished, score} ->
IO.puts("Game finsihed, #{score}")
{:game_finished, score}
{:make_move, ^v, :player_moved_before, game_state, hand} ->
IO.puts("Player moved_before, #{inspect(game_state)} #{inspect(hand)}")
{:player_moved_before, game_state, hand}
{:make_move, ^v, game_state, hand} ->
IO.puts("Got game state, #{inspect(game_state)}, hand: #{inspect(hand)}")
{:state, game_state, hand}
end
def make_move(name, game_id, move) do
safecast(name, {:make_move, game_id, move, self()})
make_move_loop(game_id, 1000)
end
############
# 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