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