A game based on the generation of a map where multiple connected players can compete with each other for the search for hidden objects.
The map is generated randomly from the server.
It consists of:
- Walls: obstacles where it is not possible to position.
- Hidden Objects: objects to be found.
The player who has found more objects is proclaimed a winner.
When a server starts, a map is generated.
When a player enters the gameplay part-time. In this period more players can enter up to a maximum limit calculated by the size of the generated maps. Each player selects the box from where to leave and when ready he can begin searching for hidden objects.
At the end of time, the game ends. Players are brought back to the lobby and the server generates a new map.
When you start the client, you will see the menu in the picture, where you can:
- Log in [Accedi]
- Register as a new user [Registrati]
- Exit the client program [Exit]
If you do not have an account, you must go to the section [Registrati] to create a new user. The user will enter a nickname/username and password. This information will be sent to the server that controls the file containing all registered accounts.
If there is no user already registered with the nickname received, the server will return a confirmation message to the client. Conversely, you will be prompted for the user to try to enter a different nickname. Once the account is created through the function [Accedi] you will be prompted for entering the data (nickname/password) previously used for registration.
The operation is the same. The client will send the data for access to the server, the latter will check in his/her file that there is a user with nicknames/passwords equal to those sent. If this search leads to a match then a confirmation message is sent to the client accessing the second menu.
Conversely, at the occurrence of:
- Nickname and/or password do not match.
- The user is not registered with that nickname/password.
- User already logged in with those login data.
An error message will be returned and after three failures attempts, the client returns to the main menu screen.
After accessing a second menu, the player can use all the main features of the game.
The selection includes:
- Access to the game [Gioca]
- View connected users [Utenti collegati]
- View the remaining objects [Vedi oggetti rimanenti]
- View the objects found [Vedi oggetti trovati]
- Go back to the previous menu [Torna indietro]
It's the heart of the program. The player displays the current map generated by the Server and sent to the Client. The map has hidden objects and obstacles (these are visible from the symbol |M|.
The player is asked to specify a location from which to search for objects.
Opportunity controls allow no player to place themselves in a gaming cell if:
- There is another player.
- There is a wall.
- Coordinates for positioning do not exist.
To avoid various players' misfortunes, each opponent will have a different color (brown) from the current player (green). Also, all objects found will be highlighted in the map ( blue color with symbol |o|). Since each map is randomized to size, each player can participate in a default number of players each time. If the maximum number is reached, the player trying to enter an appropriate message will inform him of the situation.
To avoid stalling situations, a special wall-generation system has been defined which avoids inserting them into the edges of the map.
Opportunity information will be present on the game map to inform you about the possible actions you can take.
The legend will always be accessible to every map update and the player can at any time access information such as:
- User list with the related objects found [L]
- Number of remaining items [O]
- Get out of the game [.]
Also, as shown in the picture, there is a timer showing the elapsed time of the current match.
To prevent each user from navigating by pressing the appropriate key and then sending to confirm, a system call ("/ bin / stty raw") was used to allow the game to move within the game map quickly and realistic. Each player can use the WASD keys or directional arrows to navigate without any problems and without using the sender to confirm the choice.
In addition, when the match ends while the user is entering the coordinates, a message is sent to the position submission that informs the user of the termination of the current match. After the message, it is returned to the game menu.
If the player decides to leave the game before the end of time, or if all of the objects are exhausted, he may fall back on finding all the objects previously taken.
The time available for each game is set at fifteen seconds.
Connected users are shown with the related objects found.
The user list is structured as a Search Binary Tree (ABR) where each user is entered in alphabetical order in the appropriate structure. This choice facilitates and improves the search for a particular user or eliminates it.
The number of objects hidden in the game map is displayed and ready to be found.
Simple and functional, the objects in the map are defined as global variable. Each time a user finds an object, it is decremented and initialized each time a new game session is generated. As with the walls, the number of objects present in a game is accurately chosen depending on the size of the map itself.
All the objects found by every single user in the game will be shown. If the selection of the objects found is made in the game menu and not in the game, only the total number of objects found will be shown.
Once started, specifying its port, the server does not need any intervention from the game administrators or hosts.
As shown in the figure, the only output of the server guarantees the performer the proper startup of the server itself, showing the port to be communicated to the Clients.
In conclusion, the Server does not output any output to STDOUT, excluding the case above and appropriate errors sent to STDERR output; and receives no input from STDIN.
If the server is started by specifying a port in the range of ports reserved for root jobs (0-1023), a message will alert the performer and the server will require the correct port entry.
The server is of type competing multi-thread. It accepts a new connection, and as soon as the object that represents the socket is obtained, it creates a new thread to delegate the task of responding to the connection client.
After creating the 'slave' thread, the main thread returns to wait for additional connections.
At the first start, the first map is created and printed in the "log.txt" file. Each time the game ends, the server keeps track of the newly created map.
The file “log.txt” represents a concrete area of information. The server keeps track of the file not only of the user interactions, but also the threads of each user's threads.
Inside, you can find information about:
- Used door.
- Map information (such as a number of objects).
- Complete map printing with appropriate indicators to show the location of each object.
- Information on the correct login or not of a user.
- The starting position of a user on the map.
- Information about moving each user.
- A player's exit from the game.
- More ...
Server client communication has been defined and built to ensure perfect synchronization between the two. Certain requests that are sent by the client are preceded by the sending of a character used to select the required operation. An additional check will be performed by the server by sending a confirmation character to the client. The latter can represent both normal control and certify the correct selection of the operation required by the server, and a special character to select a part of code to run to the client.
For example:
Client:
if( write(fdsocket,nickname,strlen(nickname)) < 0 ) errori_generali(5);
if( read(fdsocket,&flag_user,1) <= 0 ) errori_generali(6);
if( flag_user == 'e' )
- The first write will send a nickname to the server
- The client will wait through the read control character sent by the server
- In this case, reading the control character involves performing a specific action, in which case the nickname is incorrect
Server:
if( (numero_lettere_lette=read(fd2,appoggio,MAXSIZE)) < 0 ) errori_generali(8);
appoggio[numero_lettere_lette] = '\0';
flag_user = controllo_utente(appoggio,1,file);
if( flag_user == -1 )
{
contatore++;
if( write(fd2,"e",1) < 0 ) errori_generali(9);
- The first read will read the nickname of the player sent by the client
- After appropriate checks, the server decides, for example, to send "e" as a control letter through a write
- In conclusion the character "e" represents both a control character and selection of different code parts depending on the type of character sent.
A further example:
Client:
if( write(fd,"o",1) < 0 )
An appropriate case selection 1 leads to writing the number 1 to the server by writing
Server:
switch(flag_menu)
{
case '1':
if((file=open("database.txt",O_RDWR | O_CREAT, S_IRWXU))<0) errori_generali(7);
The read server will read the read character, in this case 1, and in a similar way to the client, through a case will select the appropriate procedure to be executed, in this case the login.
More info:
- If the server receives a SIGPIPE signal or termination character from the client, the client thread is deleted, while the root remains running to wait or serve the other clients already connected.
- During a game, to avoid that two or more clients try to access the various map boxes at the same time, mutexes on reading and writing for the various global variables were used. When a game ends, the last thread that is accessing the global flag of the map changes the variable so that each client at the next "read" receives a message informing the user about the termination of the match.
- Each thread stores the ID of the current match at birth, so that each interaction can check that the current game map has not been re-created; if the ID is different, the user is informed by the client and the ID is updated.
- If a client is terminated via CTRL-C, the server will receive a SIGPIPE signal and remove the player that has just closed the game if the player is in the game.
- If you end the server using the CTRL-C or CTRL-Z command, a message is printed on STDERR and the server ends.
By using any command interpreter, you can start running both client and server programs. If you are having trouble executing these, you can check the correctness of the commands below.
- Locate the root folder and open it
- Right-click anywhere and click on "open a terminal here". Eventually you can reach the folder by specifying its path.
- Verify that you have the right execution permissions ('man chmod' for info).
- Write on the terminal:
gcc -pthread main.c -o Server Then write: ./Server <porta>
Remember that <porta> must be replaced with the port number you want to use. Inserting an invalid port will be notified.
- Locate the root folder and open it.
- Right-click anywhere and click on "open a terminal here". Eventually you can reach the folder by specifying its path.
- Verify that you have the right execution permissions ('man chmod' for info) .
- Write on the terminal:
gcc main.c errori.c -o Client
- Then write:
./Client <Porta> <Indirizzo IP>
Porta and Indirizzo IP must be replaced with the current server values to which you want to connect.
Note: When compiling the server and client, you can view any warnings. They are not a problem for the proper use and operation of both programs.
Warnings are generated by the use of the "perror.h" library and the particular sequence that allows you to print colored characters.
In case clients and servers run on the same machine, then <Indirizzo IP>\ will be 127.0.0.1
The family of protocols used is AF_INET (Internet Address Family). By using these protocols, communication does not only occur locally, but also through the Internet. The message transmission type is SOCK_STREAM. Through this type of choice, the transmission is streamlined, sequential and reliable.
/* Address composition */
struct sockaddr_in my_addr;
/* porta = port number; fd and fd2 = Connections of the server and the client respectively */
int porta, fd, fd2;
/* Assignment of the family of protocols */
sin.sin_family = AF_INET;
/* Creating the TCP socket */
socket(AF_INET, SOCK_STREAM, 0);
/* Host TO Network Short --- conversion to numeric format for the network */
my_addr.sin_port = htons(porta);
/* Host TO Network Short --- conversion to numeric format for the network */
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/* Association of the TCP socket to the address */
bind(fd, (struct sockaddr *) &my_addr, sizeof(my_addr)) != 0 )
/* The server listens for client connection requests */
listen(fd,5)
/* Accept the client connection */
accept(fd, NULL, NULL);
/* Close the connection */
close(fd);
/* Creating new thread */
pthread_create(&tid,NULL,funzione_principale,(void *)&fd2)
/* Closing the thread */
pthread_exit ();
/* File descriptor for the logging file and to access the database.txt file */
int fd_log, file;
/* Open <File> in <Mode> mode with <Permess> permissions. Returns -1 if an error occurs, zero otherwise */
file = open (“<File>”,<Mode>,<Permessi>);
/* Reading the <File> file and saving it in “string”. Stores a maximum of 1 character. Returns -1 if an error occurs, a number greater than zero otherwise */
read (file,string,1);
/* Writing to file <File> of the contents of the string "string". Returns -1 if an error occurs, a number greater than zero otherwise */
write (file,string,strlen(string));
/* Close the descriptor file */
close (file);
/* Handler <function> for the <signal> signal */
signal (<segnale>,<funzione>);
Signals used:
- SIGPIPE: signal launched when a process is unexpectedly terminated; the signal is sent through the pipe.
- SIGINT: signal caused by CTRL-C pressure
/* Creation of a new mutex */
pthread_mutex_t a;
/* Mutex initialization */
pthread_mutex_init(&a, NULL);
/* Traffic light block. Critical zone start */
pthread_mutex_lock(&a);
/* Release of the mutex. End of critical zone */
pthread_mutex_unlock(&a);