defmodule ServerMacros do @moduledoc """ This module defines some helper macros that are used within the server macro """ @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 {: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 defp unquote(function_name)(v, t) do var!(v) = v receive do unquote(ast3) after t -> :timeout end end end 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) {: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 @moduledoc """ Contains the to run the server Code """ require ServerMacros import ServerMacros require Utils import Utils create_log 2 @doc """ Contains the start code for the server """ def start(name, participants) do 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 = %{ name: name, procs: participants, games: %{}, paxos: paxos, instance: 0, } run(state) end 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} -> 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 -> 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 -> 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 = 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}} -> 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 @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 -> 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 @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) 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) Enum.random(mn..mx) 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) 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 # @doc """ Checks if a game has been finished """ defp is_finished(state, game) do case state.games[game] do {:finished, _} -> true _ -> false 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 state = apply_game(state, v) qurey_status(state) end end @doc """ Sets the modified flag """ defp set_modifed(state, game, val \\ false) 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 # # Apply Game States # defp 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 defp simplify_game_state_pluses([], indexed_game_state), do: {false, indexed_game_state} defp 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) :b -> 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 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 _ -> false end end defp 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, max(n, a) + 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 defp 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 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) defp remove_merged([n | tl], rec, add), do: remove_merged(tl, rec ++ [n], add) defp remove_merged(list), do: remove_merged(list, [], true) defp simplify_game_state(game_state) do indexed_game_state = game_state |> reindex(false) {repeat, indexed_game_state} = indexed_game_state |> Enum.filter(fn x -> case x do {:+, _} -> true {:b, _} -> true _ -> false end end) |> simplify_game_state_pluses(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 defp 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 game_state = if game.hand[player_name] == :- do List.delete_at(game_state, pos_move) else {b, e} = Enum.split(game_state, pos_move) 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, modified: true } 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 end end defp 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 defp 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_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} 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, :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} 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 defmodule Client do import Utils require Utils 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 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 ++ [String.pad_leading("#{i}", 3, " "), v] end) end 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 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 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 -> " 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 -> " xxx \n" |> String.replace("xxx", Enum.at(game_state, 17)) end IO.write("#{res}") printpt(game_state, hand, i + 1) end end