2024-01-16 19:59:01 +00:00
defmodule ServerMacros do
2024-01-19 18:23:08 +00:00
@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
2024-01-16 19:59:01 +00:00
function_name = :" #{ name } _loop "
ast1 = quote do
{ :timeout } -> :timeout
end
ast2 = quote do
value ->
2024-01-17 19:05:18 +00:00
# TODO check spelling
log ( " Disreguarding: #{ inspect ( value ) } " )
# Process.send_after(self(), value, t + 2000)
2024-01-16 22:06:10 +00:00
unquote ( function_name ) ( v , t )
2024-01-16 19:59:01 +00:00
end
ast3 = ast1 ++ match_exp ++ ast2
2024-01-16 20:42:57 +00:00
2024-01-16 23:46:30 +00:00
quote do
2024-01-18 00:29:02 +00:00
defp unquote ( function_name ) ( v , t ) do
2024-01-16 23:46:30 +00:00
var! ( v ) = v
receive do
unquote ( ast3 )
after
2024-01-17 19:05:18 +00:00
t -> :timeout
2024-01-16 19:59:01 +00:00
end
end
end
end
2024-01-19 18:23:08 +00:00
@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
"""
2024-01-16 19:59:01 +00:00
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
2024-01-19 18:23:08 +00:00
@moduledoc """
Contains the to run the server Code
"""
2024-01-16 19:59:01 +00:00
require ServerMacros
import ServerMacros
require Utils
import Utils
2024-01-18 00:29:02 +00:00
create_log 2
2024-01-19 18:23:08 +00:00
@doc """
Contains the start code for the server
"""
2024-01-16 19:59:01 +00:00
def start ( name , participants ) do
2024-01-19 18:23:08 +00:00
log ( " #{ name } Starting server " )
2024-01-16 19:59:01 +00:00
pid = spawn ( Server , :init , [ name , participants ] )
register_name ( name , pid , false )
end
2024-01-19 18:23:08 +00:00
@doc """
Initializes the state and starts the paxos inspect and then it calls the run fn
"""
2024-01-16 19:59:01 +00:00
def init ( name , participants ) do
2024-01-16 20:42:57 +00:00
paxos = Paxos . start ( alter_name ( name , " _paxos " ) , Enum . map ( participants , fn name -> alter_name ( name , " _paxos " ) end ) , true )
2024-01-16 19:59:01 +00:00
state = %{
name : name ,
procs : participants ,
games : %{ } ,
paxos : paxos ,
instance : 0 ,
}
run ( state )
end
runfn do
{ :start_game , participants , pid_to_inform } ->
2024-01-18 23:13:34 +00:00
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
2024-01-16 22:06:10 +00:00
{ :get_game_state , game_id , pid_to_inform } ->
2024-01-18 22:28:10 +00:00
state = get_game_state ( state , game_id , pid_to_inform )
set_modifed ( state , game_id )
2024-01-16 23:46:30 +00:00
{ :make_move , game_id , move , pid_to_inform } ->
2024-01-17 10:58:37 +00:00
try_to_play_checks ( state , game_id , move , pid_to_inform )
2024-01-16 23:46:30 +00:00
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# Checks if the user can play the move before starting the requesting the user to play
2024-01-18 00:29:02 +00:00
defp try_to_play_checks ( state , game_id , move , pid_to_inform , repeat \\ false ) do
2024-01-16 23:46:30 +00:00
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
2024-01-17 19:05:18 +00:00
is_finished ( state , game_id ) ->
2024-01-16 23:46:30 +00:00
{ _ , score } = state . games [ game_id ]
safecast ( pid_to_inform , { :make_move , game_id , :game_finished , score } )
state
2024-01-16 23:50:32 +00:00
game . modified ->
safecast ( pid_to_inform , { :make_move , game_id , :player_moved_before , game . game_state , game . hand } )
set_modifed ( state , game_id )
2024-01-16 23:46:30 +00:00
true ->
2024-01-18 19:40:04 +00:00
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
2024-01-16 23:46:30 +00:00
end
end
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# Tries to propose to paxos the game action
2024-01-18 00:29:02 +00:00
defp try_to_play ( state , game_id , move , pid_to_inform ) do
2024-01-16 23:46:30 +00:00
name = state . name
2024-01-18 22:28:10 +00:00
new_hand = if state . games [ game_id ] . hand [ state . name ] == :- do
2024-01-18 23:13:34 +00:00
Enum . at ( state . games [ game_id ] . game_state , move )
2024-01-18 22:28:10 +00:00
else
get_hand_for_game_state ( state . games [ game_id ] . game_state )
end
2024-01-17 10:58:37 +00:00
try_propose { :make_move , game_id , name , move , new_hand }
2024-01-16 23:46:30 +00:00
do
2024-01-17 10:58:37 +00:00
{ :decision , { :make_move , ^ game_id , ^ name , ^ move , ^ new_hand } } ->
state = apply_game ( state , { :make_move , game_id , name , move , new_hand } )
2024-01-16 23:46:30 +00:00
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 )
2024-01-17 10:58:37 +00:00
{ :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 } )
2024-01-16 23:46:30 +00:00
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
2024-01-16 22:06:10 +00:00
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# Get the most recent game_state and return it to the player
2024-01-18 00:29:02 +00:00
defp get_game_state ( state , game_id , pid_to_inform , repeat \\ false ) do
2024-01-16 22:06:10 +00:00
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 )
2024-01-17 19:05:18 +00:00
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
2024-01-16 22:06:10 +00:00
state
end
2024-01-16 19:59:01 +00:00
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# This generates a new hand based on the current game state
2024-01-18 00:29:02 +00:00
defp get_hand_for_game_state ( game_state ) do
2024-01-17 19:05:18 +00:00
r1 = Enum . random ( 0 . . 100 )
2024-01-18 22:28:10 +00:00
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 )
2024-01-17 19:05:18 +00:00
end
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# This tries to create a game by sending the create message to paxos
2024-01-18 00:29:02 +00:00
defp try_to_create_game ( state , participants ) do
2024-01-16 19:59:01 +00:00
game_ids = Map . keys ( state . games )
2024-01-16 20:42:57 +00:00
latest = Enum . at ( Enum . sort ( game_ids ) , length ( game_ids ) - 1 )
new_game_id = if latest do latest else 0 end + 1
2024-01-16 19:59:01 +00:00
2024-01-17 19:05:18 +00:00
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 )
2024-01-16 19:59:01 +00:00
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
#
2024-01-16 22:06:10 +00:00
# Utils
2024-01-16 19:59:01 +00:00
#
2024-01-20 20:26:41 +00:00
# Checks if a game has been finished
2024-01-18 00:29:02 +00:00
defp is_finished ( state , game ) do
2024-01-16 23:46:30 +00:00
case state . games [ game ] do
{ :finished , _ } -> true
_ -> false
end
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# Gets up to the most recent instance
2024-01-18 00:29:02 +00:00
defp qurey_status ( state ) do
2024-01-16 22:06:10 +00:00
v = Paxos . get_decision ( state . paxos , state . instance , 100 )
or_state v != nil do
state = apply_game ( state , v )
qurey_status ( state )
end
end
2024-01-19 18:23:08 +00:00
2024-01-20 20:26:41 +00:00
# Sets the modified flag
2024-01-18 00:29:02 +00:00
defp set_modifed ( state , game , val \\ false ) do
2024-01-18 22:28:10 +00:00
or_state not is_finished ( state , game ) and state . games [ game ] != :not_playing_in_game and state . games [ game ] != nil do
2024-01-17 19:05:18 +00:00
%{ state | games : Map . put ( state . games , game , %{ state . games [ game ] | modified : val } ) }
2024-01-16 23:46:30 +00:00
end
end
2024-01-16 22:06:10 +00:00
#
# Apply Game States
#
2024-01-16 23:46:30 +00:00
2024-01-20 20:26:41 +00:00
# Calculates the index based on the incoming index and a move
2024-01-18 00:29:02 +00:00
defp get_index ( indexed_game_state , spos , index ) do
2024-01-17 19:05:18 +00:00
index = spos + index
len = length ( indexed_game_state )
cond do
index < 0 ->
len - 1
index >= len ->
rem ( index , len )
true ->
index
end
end
2024-01-20 20:26:41 +00:00
# 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}]
2024-01-18 00:29:02 +00:00
defp simplify_game_state_pluses ( [ ] , indexed_game_state ) , do : { false , indexed_game_state }
defp simplify_game_state_pluses ( [ { :+ , i } | tl ] , indexed_game_state ) do
2024-01-17 19:05:18 +00:00
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 )
2024-01-18 22:28:10 +00:00
:b -> simplify_game_state_pluses ( tl , indexed_game_state )
2024-01-17 19:05:18 +00:00
n ->
list =
indexed_game_state |>
Enum . map ( fn { x , ti } -> if ti == i , do : { { :merged , n + 1 } , i } , else : { x , ti } end ) |>
2024-01-20 20:26:41 +00:00
Enum . filter ( fn { _ , ti } -> cond do
2024-01-17 19:05:18 +00:00
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
2024-01-18 22:28:10 +00:00
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 ) |>
2024-01-20 20:26:41 +00:00
Enum . filter ( fn { _ , ti } -> cond do
2024-01-18 22:28:10 +00:00
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
2024-01-20 20:26:41 +00:00
# Check if the item in an game is the substate merged
2024-01-18 00:29:02 +00:00
defp is_merged ( item ) do
2024-01-17 19:05:18 +00:00
case item do
{ :merged , _ } -> true
_ -> false
end
end
2024-01-20 20:26:41 +00:00
# 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}]
2024-01-18 00:29:02 +00:00
defp expand_merge ( indexed_game_state ) do
2024-01-17 19:05:18 +00:00
{ { :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 |>
2024-01-18 19:40:04 +00:00
Enum . map ( fn { x , ti } -> if ti == i , do : { { :merged , max ( n , a ) + 1 } , i } , else : { x , ti } end ) |>
2024-01-20 20:26:41 +00:00
Enum . filter ( fn { _ , ti } -> cond do
2024-01-17 19:05:18 +00:00
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
2024-01-20 20:26:41 +00:00
# This funcion removes previous indexes and reindexs the state
2024-01-18 00:29:02 +00:00
defp reindex ( list , flat \\ true ) do
2024-01-17 19:05:18 +00:00
list = if flat do
list |> Enum . map ( fn { n , _ } -> n end )
else
list
end
[ list , 0 . . ( length ( list ) - 1 ) ] |> Enum . zip ( )
end
2024-01-20 20:26:41 +00:00
# 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
2024-01-18 00:29:02 +00:00
defp simplify_game_state ( game_state ) do
2024-01-17 19:05:18 +00:00
indexed_game_state =
game_state |>
reindex ( false )
{ repeat , indexed_game_state } =
indexed_game_state |>
Enum . filter ( fn x -> case x do
{ :+ , _ } -> true
2024-01-18 22:28:10 +00:00
{ :b , _ } -> true
2024-01-17 19:05:18 +00:00
_ -> 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
2024-01-17 10:58:37 +00:00
end
2024-01-20 20:26:41 +00:00
# This function tries to apply the make_move command
2024-01-18 00:29:02 +00:00
defp apply_game ( state , { :make_move , game_id , player_name , pos_move , new_hand } ) do
2024-01-17 10:58:37 +00:00
game = state . games [ game_id ]
case game do
{ :finished , _ } ->
2024-01-17 19:05:18 +00:00
raise " Game already finished "
2024-01-17 10:58:37 +00:00
:not_playing_in_game ->
%{ state | instance : state . instance + 1 }
game ->
game_state = game . game_state
2024-01-18 22:28:10 +00:00
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
2024-01-17 19:05:18 +00:00
game_state = simplify_game_state ( game_state )
2024-01-17 10:58:37 +00:00
hand = Map . put ( game . hand , player_name , new_hand )
2024-01-18 19:40:04 +00:00
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
}
2024-01-17 19:05:18 +00:00
else
%{ state | games : Map . put ( state . games , game_id , game ) , instance : state . instance + 1 }
end
2024-01-17 10:58:37 +00:00
end
2024-01-16 23:46:30 +00:00
end
2024-01-20 20:26:41 +00:00
# This function tries to apply the start_game command
2024-01-18 00:29:02 +00:00
defp apply_game ( state , { :start_game , game_id , participants , new_game_state , hand } ) do
2024-01-16 22:06:10 +00:00
cond do
state . games [ game_id ] ->
2024-01-17 19:05:18 +00:00
raise " Game Already Exists "
2024-01-16 22:06:10 +00:00
state . name in participants ->
%{ state |
games : Map . put ( state . games , game_id , %{
game_state : new_game_state ,
participants : participants ,
2024-01-16 23:46:30 +00:00
hand : hand ,
modified : true ,
2024-01-16 22:06:10 +00:00
} ) ,
instance : state . instance + 1
}
true ->
%{ state |
games : Map . put ( state . games , game_id , :not_playing_in_game ) ,
instance : state . instance + 1 ,
}
2024-01-16 19:59:01 +00:00
end
end
2024-01-18 00:29:02 +00:00
defp apply_game ( _ , _ ) , do : raise " Do not know how to apply game state "
2024-01-16 19:59:01 +00:00
############
# Interface
############
2024-01-20 20:26:41 +00:00
# handles responses from the start game request
2024-01-16 19:59:01 +00:00
create_loop :start_game do
2024-01-16 20:42:57 +00:00
{ :start_game_ans , game_id } ->
2024-01-16 19:59:01 +00:00
log ( " Started a game #{ game_id } " )
2024-01-16 20:42:57 +00:00
{ :start_game , game_id }
end
2024-01-20 20:26:41 +00:00
@doc """
Requests the server to start a game
"""
2024-01-16 19:59:01 +00:00
def start_game ( name , participants ) do
safecast ( name , { :start_game , participants , self ( ) } )
2024-01-17 19:05:18 +00:00
start_game_loop ( nil , 10000 )
2024-01-16 22:06:10 +00:00
end
2024-01-20 20:26:41 +00:00
# handles responses from the get game state request
2024-01-16 22:06:10 +00:00
create_loop :get_game_state do
2024-01-17 19:05:18 +00:00
{ :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 }
2024-01-18 19:40:04 +00:00
{ :game_state , ^ v , game_state , hand } ->
log ( " Got game state, #{ inspect ( game_state ) } , hand: #{ inspect ( hand ) } " )
{ :state , game_state , hand }
2024-01-16 22:06:10 +00:00
{ :game_state , ^ v , :game_does_not_exist } ->
2024-01-17 19:05:18 +00:00
log ( " Got game does not exist " )
2024-01-16 23:46:30 +00:00
{ :not_exists }
2024-01-16 19:59:01 +00:00
end
2024-01-20 20:26:41 +00:00
@doc """
Requests the server to get the game state
"""
2024-01-16 22:06:10 +00:00
def get_game_state ( name , game_id ) do
safecast ( name , { :get_game_state , game_id , self ( ) } )
2024-01-17 19:05:18 +00:00
get_game_state_loop ( game_id , 10000 )
2024-01-16 22:06:10 +00:00
end
2024-01-20 20:26:41 +00:00
# handles responses from the make move request
2024-01-16 23:46:30 +00:00
create_loop :make_move do
{ :make_move , ^ v , :game_does_not_exist } ->
2024-01-17 19:05:18 +00:00
log ( " Got game does not exist " )
2024-01-16 23:46:30 +00:00
{ :not_exists }
{ :make_move , ^ v , :not_playing } ->
2024-01-17 19:05:18 +00:00
log ( " Not Playing in that game " )
2024-01-16 23:46:30 +00:00
{ :not_playing }
{ :make_move , ^ v , :game_finished , score } ->
2024-01-17 19:05:18 +00:00
log ( " Game finsihed, #{ score } " )
2024-01-16 23:46:30 +00:00
{ :game_finished , score }
{ :make_move , ^ v , :player_moved_before , game_state , hand } ->
2024-01-17 19:05:18 +00:00
log ( " Player moved_before, #{ inspect ( game_state ) } #{ inspect ( hand ) } " )
2024-01-16 23:46:30 +00:00
{ :player_moved_before , game_state , hand }
2024-01-18 19:40:04 +00:00
{ :make_move , ^ v , :invalid_move } ->
log ( " Invalid Move " )
{ :invalid_move }
2024-01-16 23:46:30 +00:00
{ :make_move , ^ v , game_state , hand } ->
2024-01-17 19:05:18 +00:00
log ( " Got game state, #{ inspect ( game_state ) } , hand: #{ inspect ( hand ) } " )
2024-01-16 23:46:30 +00:00
{ :state , game_state , hand }
end
2024-01-20 20:26:41 +00:00
@doc """
Requests to make a move
"""
2024-01-16 23:46:30 +00:00
def make_move ( name , game_id , move ) do
safecast ( name , { :make_move , game_id , move , self ( ) } )
2024-01-17 19:05:18 +00:00
make_move_loop ( game_id , 10000 )
2024-01-16 23:46:30 +00:00
end
2024-01-16 19:59:01 +00:00
############
# Debug
############
2024-01-20 20:26:41 +00:00
@doc """
Quicky creates some servers it ' s useful in testing
"""
2024-01-16 19:59:01 +00:00
def spinup ( number_of_participants ) do
2024-01-20 20:26:41 +00:00
procs = Enum . to_list ( 1 . . number_of_participants ) |> Enum . map ( fn n -> :" p #{ n } " end )
2024-01-16 19:59:01 +00:00
Enum . map ( procs , fn proc -> Server . start ( proc , procs ) end )
end
2024-01-20 20:26:41 +00:00
@doc """
Quicky kills some servers it ' s useful in testing
"""
2024-01-16 19:59:01 +00:00
def kill ( pids ) do
pids |> Enum . map ( fn m -> Process . exit ( m , :kill ) end )
end
end
2024-01-18 00:29:02 +00:00
defmodule Client do
2024-01-20 20:26:41 +00:00
@moduledoc """
This module handles displaying the game state in a nice to understand
way
"""
2024-01-18 00:29:02 +00:00
import Utils
require Utils
create_log 3
2024-01-20 20:26:41 +00:00
@doc """
This function plays the displays the game then waits for user input and displays the next state of the game
"""
2024-01-18 00:29:02 +00:00
def play_game ( process , game_id ) do
2024-01-18 19:40:04 +00:00
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 ( ) |>
2024-01-18 22:28:10 +00:00
printpt ( to_name ( hand ) )
2024-01-18 19:40:04 +00:00
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 " )
2024-01-18 22:28:10 +00:00
{ :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 " )
2024-01-18 19:40:04 +00:00
{ :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
2024-01-20 20:26:41 +00:00
@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
"""
2024-01-18 19:40:04 +00:00
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 " )
2024-01-18 22:28:10 +00:00
{ :not_exists } -> control_game ( process , game_id )
{ :not_playing } -> control_game ( process , game_id )
{ :game_finished , _ } -> control_game ( process , game_id )
2024-01-18 19:40:04 +00:00
{ :player_moved_before , _ , _ } ->
log ( " Player Moved before you did please check the map and try again " )
2024-01-18 22:28:10 +00:00
control_game ( process , game_id )
2024-01-18 19:40:04 +00:00
{ :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
2024-01-20 20:26:41 +00:00
@doc """
This funcion tries to display the most recent version of the game
Use control_game to control game
"""
2024-01-18 22:28:10 +00:00
def display_game ( process , game_id , temp_game \\ [ ] , temp_hand \\ [ ] ) do
2024-01-18 19:40:04 +00:00
game = Server . get_game_state ( process , game_id )
2024-01-20 20:26:41 +00:00
case game do
2024-01-18 19:40:04 +00:00
: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 } ->
2024-01-18 22:28:10 +00:00
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
2024-01-18 22:53:12 +00:00
Process . sleep ( 1000 )
2024-01-18 22:28:10 +00:00
display_game ( process , game_id , temp_game , temp_hand )
end
2024-01-18 19:40:04 +00:00
end
2024-01-18 00:29:02 +00:00
end
2024-01-20 20:26:41 +00:00
# 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
2024-01-18 19:40:04 +00:00
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 ( )
2024-01-18 22:28:10 +00:00
color <> String . pad_leading ( " #{ letter } " , 3 , " " ) <> IO.ANSI . reset ( )
2024-01-18 00:29:02 +00:00
end
2024-01-20 20:26:41 +00:00
# Adds the index as an indicator and pads the index
defp interpolate ( list ) do
2024-01-18 00:29:02 +00:00
[ list , 0 . . length ( list ) - 1 ] |>
Enum . zip ( ) |>
2024-01-18 22:28:10 +00:00
Enum . reduce ( [ ] , fn { v , i } , acc -> acc ++ [ String . pad_leading ( " #{ i } " , 3 , " " ) , v ] end )
2024-01-18 19:40:04 +00:00
end
2024-01-20 20:26:41 +00:00
# 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 :
2024-01-18 19:40:04 +00:00
grow_empty_list ( tl , i - 1 , acc ++ [ h ++ [ " " ] ] )
2024-01-20 20:26:41 +00:00
# This takes the list that was generated in the grow_empty_list function and merges it between every other item
defp fill ( list ) do
2024-01-18 19:40:04 +00:00
to_fill = 32 - length ( list )
to_add = grow_empty_list ( Enum . map ( list , fn _ -> [ ] end ) , to_fill , [ ] )
fill ( list , to_add , [ ] )
end
2024-01-20 20:26:41 +00:00
defp fill ( [ ] , _ , acc ) , do : acc
defp fill ( [ hd | tail ] , [ add_hd | add_tail ] , acc ) do
2024-01-18 19:40:04 +00:00
fill ( tail , add_tail , acc ++ [ hd ] ++ add_hd )
end
2024-01-20 20:26:41 +00:00
# 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
2024-01-18 19:40:04 +00:00
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 ) )
2024-01-18 00:29:02 +00:00
end
2024-01-18 19:40:04 +00:00
IO . write ( " #{ res } " )
printpt ( game_state , hand , i + 1 )
2024-01-18 00:29:02 +00:00
end
end