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 e3b2e60ddc
All checks were successful
continuous-integration/drone/push Build is passing
fixed compiler errors and updated read me
2024-01-20 20:26:41 +00:00

880 lines
29 KiB
Elixir

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
# 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
# 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
# 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
# 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
# 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
#
# Checks if a game has been finished
defp is_finished(state, game) do
case state.games[game] do
{:finished, _} -> true
_ -> false
end
end
# 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
# 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
#
# Calculates the index based on the incoming index and a move
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
# This funcion looks at pluses and blackholes and chekes if they can be merged
# This funcion will pick a state like this [ 1, 2, :+, 2, 1 ] and create a new state [ 1, {:merged, 3} , 1] and then call expand merge which will expand the merge
# Note by this point the state is indexed so it looks like [{1, 0}, {:+, 1}, {1, 2}]
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 {_, 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 {_, 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
# Check if the item in an game is the substate merged
defp is_merged(item) do
case item do
{:merged, _} -> true
_ -> false
end
end
# This funcion expands merges
# With a state like this [1, {:merged, 3}, 1] it will create a game state like this [ {:merged, 4 } ]
# Note by this point the state is indexed so it looks like [{1, 0}, {:+, 1}, {1, 2}]
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 {_, 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
# This funcion removes previous indexes and reindexs the state
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
# Removes all merged from the array
defp remove_merged(l), do: l |> Enum.map(fn x -> case x do
{:merged, n} -> n
x -> x
end end)
# This funcion recieves the game state after the move was done and tries to simplify the game
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
# This function tries to apply the make_move command
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
# This function tries to apply the start_game command
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
############
# handles responses from the start game request
create_loop :start_game do
{:start_game_ans, game_id} ->
log("Started a game #{game_id}")
{:start_game, game_id}
end
@doc """
Requests the server to start a game
"""
def start_game(name, participants) do
safecast(name, {:start_game, participants, self()})
start_game_loop(nil, 10000)
end
# handles responses from the get game state request
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
@doc """
Requests the server to get the game state
"""
def get_game_state(name, game_id) do
safecast(name, {:get_game_state, game_id, self()})
get_game_state_loop(game_id, 10000)
end
# handles responses from the make move request
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
@doc """
Requests to make a move
"""
def make_move(name, game_id, move) do
safecast(name, {:make_move, game_id, move, self()})
make_move_loop(game_id, 10000)
end
############
# Debug
############
@doc """
Quicky creates some servers it's useful in testing
"""
def spinup(number_of_participants) do
procs = Enum.to_list(1..number_of_participants) |> Enum.map(fn n -> :"p#{n}" end)
Enum.map(procs, fn proc -> Server.start(proc, procs) end)
end
@doc """
Quicky kills some servers it's useful in testing
"""
def kill (pids) do
pids |> Enum.map(fn m -> Process.exit(m, :kill) end)
end
end
defmodule Client do
@moduledoc """
This module handles displaying the game state in a nice to understand
way
"""
import Utils
require Utils
create_log 3
@doc """
This function plays the displays the game then waits for user input and displays the next state of the game
"""
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
@doc """
This funcion waits for user input and then commands the server to act
Use the display_game funcion in a seperate terminal to see the game
"""
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
@doc """
This funcion tries to display the most recent version of the game
Use control_game to control game
"""
def display_game(process, game_id, temp_game \\ [], temp_hand \\ []) do
game = Server.get_game_state(process, game_id)
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
# This funcion recieves some objects and transforms them into user friendly text
defp to_name(list) when is_list(list), do: list |> Enum.map(fn x -> to_name(x) end)
defp to_name(atom) when atom == :+, do: IO.ANSI.color_background(9) <> IO.ANSI.color(15) <> " + " <> IO.ANSI.reset()
defp to_name(atom) when atom == :-, do: IO.ANSI.color_background(21) <> IO.ANSI.color(15) <> " - " <> IO.ANSI.reset()
defp to_name(atom) when atom == :b, do: IO.ANSI.color_background(232) <> IO.ANSI.color(15) <> " b " <> IO.ANSI.reset()
defp to_name(atom) when is_atom(atom), do: atom
defp 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
# Adds the index as an indicator and pads the index
defp 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
# This grows a list with between spaces every item of the interpolated list
# This allows the items to take as puch space in the possilble
defp grow_empty_list(t, i, acc) when i == 0, do: t ++ acc
defp grow_empty_list([], i, acc), do: grow_empty_list(acc, i, [])
defp grow_empty_list([h | tl], i, acc), do:
grow_empty_list(tl, i - 1, acc ++ [ h ++ [" "] ])
# This takes the list that was generated in the grow_empty_list function and merges it between every other item
defp 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
defp fill([], _, acc), do: acc
defp fill([hd | tail], [add_hd | add_tail], acc) do
fill(tail, add_tail ,acc ++ [hd] ++ add_hd)
end
# This functions prints the circle
defp printpt(game_state, hand), do: printpt(game_state, hand, 0)
defp printpt(_, _, i) when i > 16, do: nil
defp 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