Andre Henriques
e3b2e60ddc
All checks were successful
continuous-integration/drone/push Build is passing
|
||
---|---|---|
lib | ||
presentation | ||
.drone.yml | ||
.formatter.exs | ||
.gitignore | ||
mix.exs | ||
notes | ||
README.md |
Application Coperative "Atomas" Game
The application that this project implements is a coperative version of the game Atomas. Atomas is a simple single player mobile phone game which allows players to combine atomns to make new attoms with heigher atomic numbers.
The Server application inplements a coperative, and simplified version of this game that allows players to play a game of Atomas in the same board at the same time.
The Server uses Paxos to guanratee that the only user can play at the time and that a player can not make a move on a diferent game state than another player.
Ther server.ex
file both provides some Server
Module that runs this service and a Client
Module that can be used to display some the game state in the terminal with nicer representation.
API
The server provides the following API:
{:start_game, participants, pid_to_inform}
{:get_game_state, game_id, pid_to_inform}
{:make_move, game_id, move, pid_to_inform}
Important Concepts
The game_id
is a number and is an identifier for the game, this can be a number because paxos guanratees that the there can not be two games with same number, as the server would have learned about that decision and whould have not incremented the number.
The game_state
is a representation of a game state that corresponds to an array of numbers, and some special atoms, where the number represents the atomic number of an atom the max length of this array is 16 when array gets bigger then that the game is over.
Start Game
The :start_game
message causes the server to create a new game.
The :start_game
message takes as arguments participants
and pid_to_inform
.
participants
which is a list of names of the servers that are participating in the game.pid_to_inform
which is a pid of the process that needs to be informed of the success or failure of this action.
For example to request the start of a game a user might do somethig like:
send(server_pid, {:start_game, [:p0, :p1], self()})
# use recieve do to wait for the message
# Or, assuming the server was started with the name :p0
Server.start_game(:p0, [:p0, :p1])
The server will answer with:
{:start_game_ans, game_id}
This request will always create a new game (with the exception of crashes).
Get Game State
The :get_game_state
message causes the server to return the current state of a game.
The :get_game_state
message takes as arguments game_id
, and pid_to_inform
.
game_id
which is the game_id returned by the:start_game
requestpid_to_inform
which is a pid of the process that needs to be informed of the success or failure of this action.
For example to request the current state of a agame:
send(server_pid, {:get_game_state, 1, self()})
# use recieve do to wait for the message
# Or, assuming the server was started with the name :p0
Server.get_game_state(:p0, 1)
The server will answer with one of the following:
{:game_state, game_id, :not_playing} # When the player is not playing in the game
{:game_state, game_id, :game_does_not_exist} # When the game was never created
{:game_state, game_id, :game_finished, score} # When the game is already over
{:game_state, game_id, game_state, hand} # When the player is in the game and the game has not finished
# Returns the current game state of the game,
# and the current "atom" on the hand of the player
Make a move
The :make_move
message causes the server to try to play the move that the player is requesting.
The :make_move
message takes as arguments game_id
, move
, and pid_to_inform
.
game_id
which is the game_id returned by the:start_game
requestmove
which is the position in the game state array the play wants to insert their "atom"pid_to_inform
which is a pid of the process that needs to be informed of the success or failure of this action.
For example to request the current state of a agame:
send(server_pid, {:make_move, 1, 3, self()})
# use recieve do to wait for the message
# Or, assuming the server was started with the name :p0
Server.make_move(:p0, 1, 3)
The server will answer with one of the following:
{:make_move, game_id, :not_playing} # When the player is not playing in the game
{:make_move, game_id, :game_does_not_exist} # When the game was never created
{:make_move, game_id, :game_finished, score} # When the game is already over
{:make_move, game_id, :invalid_move} # When the move requested by the player is an invalid move
{:make_move, game_id, :player_moved_before, game_state, hand} # When the player is in the game and the game has not finished,
# but another player has played before this request was male
# Returns the current game state of the game,
# and the current "atom" on the hand of the player
{:make_move, game_id, game_state, hand} # When the player is in the game and the game has not finished
# and the move is valid
# Returns the current game state of the game,
# and the current "atom" on the hand of the player
Properties
- If a game exists then there was a process that create it.
- Eventually all processes would will be in the same game state
- A player can not make a move in a game state that is not the last
- If two players make a move at the same time for the same game only one of the moves happens and the other player eventually gets notified
Assumptions
- Processes won't be behave in a byzantine way
- Only a minority of processes can fail
- Will be run in a partial synchronous system
Usage instructions
There is two ways of interacting with the server via the API or the Client
Module
Notes
By default all the logs are disabled to enable all the logs inside the paxos file there is a module variable called @min_print_level
,
by setting it to 0 it enables all the logs
API
Start a new iex session and compilie paxos.ex
and server.ex
:
c "paxos.ex"; c "server.ex"
** Paxos needs to be compiled before the server as the server requires macros written in the paxos file **
Now start 3 instances of the server:
procs = Enum.to_list(1..3) |> Enum.map(fn x -> :"p#{x}" end)
pids = procs |> Enum.map(fn x -> Server.start(x, procs) end)
After the 3 instances have been started you can use Server helper functions to communicate with the Server:
Server.start_game(:p1, [:p1, :p2])
Server.get_game_state(:p1, 1)
Server.make_move(:p1, 1, 0)
Using the Client
Open 2 different terminals. One terminal will be the display game and the other will be used to control the game
In the first terminal start an iex session with a session name:
iex --sname c1
In the second terminal start an iex session with a session name:
iex --sname c2
On the first terminal connect to the second terminal:
# In c1
Node.connect(:"c2@<computer_name>")
On both terminals compile paxos and server:
# In both run
c "paxos.ex"; c "server.ex"
On the first terminal start the servers, create a game and enter display mode:
# In c1
procs = Enum.to_list(1..3) |> Enum.map(fn x -> :"p#{x}" end)
pids = procs |> Enum.map(fn x -> Server.start(x, procs) end)
Server.start_game(:p1, [:p1, :p2])
Client.display_game(:p1, 1)
You now should see something like:
0 H
1
Be
H
5 2
Be
Be Li
4 3
Be
The Center "Atom" is your hand, while the Letters arround are the game_state or board, the number represents where you can put the "Atom" in your hand
On the second terminal start enter control mode:
# In c2
Client.control_game(:p1, 1)
You should see:
Type the number you want to play or q to exit:
Keep your mouse in the second terminal, and insert the number, where you want to make your move, and hit enter!
The map should update on the top screen and now you can play the game!