defmodule ServerMacros do def create_create_loop(name, do: match_exp, else: process_exp) do function_name = :"#{name}_loop" ast1 = quote do {:timeout} -> :timeout end ast2 = quote do value -> # TODO check spelling log("Disreguarding: #{inspect(value)}") # Process.send_after(self(), value, t + 2000) unquote(function_name)(v, t) end ast3 = ast1 ++ match_exp ++ ast2 quote do def unquote(function_name)(v, t) do var!(v) = v unquote(process_exp) receive do unquote(ast3) after t -> :timeout end end 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 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(state, 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 new_hand = get_hand_for_game_state(state.games[game_id].game_state) 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) 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 state end end def get_hand_for_game_state(game_state) do 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 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 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) 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[game] | modified: val})} end end # # Apply Game States # def get_index(indexed_game_state, spos, index) do index = spos + index len = length(indexed_game_state) cond do index < 0 -> len - 1 index >= len -> rem(index, len) true -> index end end def simplify_game_state_pluses([], indexed_game_state), do: {false, indexed_game_state} def simplify_game_state_pluses([{:+, 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) 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 def is_merged(item) do case item do {:merged, _} -> true _ -> false end end def expand_merge(indexed_game_state) do {{: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 def reindex(list, flat \\ true) do list = if flat do list |> Enum.map(fn {n, _} -> n end) else list end [list, 0..(length(list) - 1)] |> Enum.zip() end def remove_merged([], rec, _), do: rec def 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 def remove_merged([n | tl], rec, add), do: remove_merged(tl, rec ++ [n], add) def remove_merged(list) do log("#{inspect(list)}") remove_merged(list, [], true) end def simplify_game_state(game_state) do 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 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 = simplify_game_state(game_state) hand = Map.put(game.hand, player_name, new_hand) game = %{game| hand: hand, game_state: game_state } 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 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, 10000) 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_does_not_exist} -> log("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, 10000) end create_loop :make_move do {:make_move, ^v, :game_does_not_exist} -> log("Got game does not exist") {:not_exists} {:make_move, ^v, :not_playing} -> log("Not Playing in that game") {:not_playing} {:make_move, ^v, :game_finished, score} -> log("Game finsihed, #{score}") {:game_finished, score} {: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, game_state, hand} -> log("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, 10000) 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