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 defp 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 2 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} -> 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 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 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 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 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 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 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 # defp is_finished(state, game) do case state.games[game] do {:finished, _} -> true _ -> false end end 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 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