Battleship (Computer game)

From Bauman National Library
This page was last modified on 8 June 2016, at 11:49.

This article is about implementation of networking in a cross-platform multiplayer game Battleship. Used languages and frameworks: C++, Qt.

Annotation

This work describes using of TCP-protocol and C++ programming language with the GUI library Qt in the context of the development of cross-platform network game "Battleship". The program consists of server and client parts. Multi-threaded server is implemented using low-level system calls, is responsible for the interaction of the players, the exchange of messages between them, as well as the recovery of a gaming session started after a crash. The client program has an intuitive graphical interface with the elements of realistic graphics display the field players. The project is designed and implemented communication protocol server and client, including both the transmission of commands of the game, and arbitrary messages in a chat format. During the development of the program we have also been studied different design patterns for the combination of different strategies automatic placement ships. This article will describe the functionality of the program and implementation of networking in it.

Application description

Client

When using the "Battleship" the user may not even know of the existence of the server that provides the interface clients. The program can play a very ordinary man, far from the software and network technologies. The client application consists of opponents playing fields, menus, and buttons. First, the player puts ships in a special dialog box, available on the "Arrange ships." It can then either connect to the game server by specifying the address and port, as well as a nickname - the name under which the player in question will be visible to other players; or start playing with the computer, selecting the level of difficulty. On the complexity of the arrangement affects the tactics of ships and the presence in the game algorithm neural network model to determine the point of impact machines. If the user selects the network version of the game, then it is necessary to choose a rival. For this display has a function of available players - the button "Free players." Select a specific opponent in the list, the user can invite him to play with him in "Battleship". At the same time the second player prompts you to play, to which he could respond as positively, then the game starts and negatively. The first player who suggested the game. When you hover the mouse on the enemy, the cursor changes it for the shot.
Screenshot of client-app
In case if the ship hit or killed, the player is given the right to the next turn. If the shot was unsuccessful, the right course is transferred to the enemy, and the cursor moves back to its normal state. When all the player's ship sunk, he recognized the loser, and a message is displayed. During the game, players can communicate with each other in a chat format. All parameters of the window (width, height, location, stage combat) and the latest data entered (username, IP address, port number, the level of difficulty) are recorded in the registry and are automatically restored the next time you run the application. Thanks to the cross-platform Qt library version 5.3 the program can be compiled on all modern operating systems. Client has been tested on the operating system Windows XP, 7, 8, 8.1, 10 and OpenSuse Linux 11.4.

To achieve realistic graphics use the combined approach is a combination of three-dimensional graphics of the standard two-dimensional and OpenGl toolkit QPainter. Three-dimensional modeling of surface water applies to complex tasks of computer graphics, so in this paper has been used publicly available OpenGl-implementation of the sea surface. With QPainter can draw two-dimensional graphics primitives using a three-dimensional image as a background. The effect of the waves is achieved by applying multiple textures and noise, the position of the ridge varies linearly timer every 25 ms.

Server

Screenshot server
Server for net services of BattleShip game is a console application. When you start the server the user tells it to the port number on which it will work, then it may already be taking customers. Under each new customer came allocated own thread on which it will continue to operate - thus the server is multithreaded. All events on the server are logged in a file and displayed on the console.

Implementation of networking functions

Including the necessary libraries for cross-platform using

The code server is responsible for setting customers, will be discussed in the listings below. Since the aim is to create a cross-platform job server, and network subsystems APIs in Windows operating systems and Unix a little different, this difference can be eliminated by using conditional compilation using preprocessor directives. Connecting libraries will be as follows:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <map>
#include <iostream>
// # define WIN32 // Uncomment when compiling for [[Windows]]
#ifndef WIN32

// headers for Unix
#include <sys / stat.h>
#include <sys / types.h>
#include <sys / socket.h>
#include <sys / wait.h>
#include <arpa / inet.h>
#include <resolv.h>
#include <dlfcn.h>

#else
// headers for Windows
#define HAVE_STRUCT_TIMESPEC 1
#include <winsock2.h>

#endif

#include <sys / time.h>
#include <errno.h>
#include <string.h>
#include <string>
#include <pthread.h>
</ syntaxhighlight> </ code>

=== Basic runtime cycle and main() function ===
The following code describes the function '' main () ''.
Lines 3-9 are responsible for initialization, and lines 39-41 for the liberation of occupied network resources, the network subsystem in systems Windows. Lines 10-20 correspond to the default settings create a socket on the server side. Infinite loop recorded in the 21-38 is responsible for handling customers. Upon receipt of a connection request from any client creates a separate thread that will serve him (32-36). As a library for the creation and management of threads to use the library '' PThreads ''. Of particular note is the line 36. It is locked mutex. The parameter client ID '' client '' is passed to the streaming function '' Child '' as a pointer. This situation can occur: for some reason does not start streaming feature, but this time took accept a connection request from a new customer. In this case, the identifier is lost previous client. To eliminate this effect, it introduced a critical section, which starts with the creation and flow until the stream function does not save the identity of his client in a safe place, such as in your own stack variable. With this approach, the streaming function will always get the correct value of the customer number. The parameter passed to the function in the streaming client ID on which the server communicates with the client. In this function, organized analysis coming from the client messages and commands, a list of which is presented in the table below. If the message from the client does not begin with the team, then it is treated as a simple exchange of messages between users and sent to players in the chat window. Most server [[software]] when working with clients operates a static buffer to save the command received from the user. The disadvantage of this approach is erroneous data processing in the event of longer commands that are often used by hackers to disrupt the server. In this connection, in the realized active server uses a dynamic memory and a control output buffers abroad. The maximum buffer size is 1024 bytes.
<syntaxhighlight lang=cpp>
int main(void)
{
#ifdef WIN32
WSADATA wsadata;
    if(WSAStartup(MAKEWORD(1,1),&amp;wsadata) == SOCKET_ERROR){
        printf("Error creating socket.");
        return1;
    }
#endif
    int sd;
    struct sockaddr_inaddr;
    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    PANIC("Socket");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(MY_PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sd,(structsockaddr) &amp;addr, sizeof(addr)) != 0)
        PANIC("Bind");
    if(listen(sd,20)!=0)
        20PANIC("Listen");
    while(1)
    {
        int client,addr_size=sizeof(addr);
        client=
#ifdef WIN32
        accept(sd,(structsockaddr)&amp;addr,&amp;addr_size);
#else
        accept(sd,(structsockaddr)&amp;addr,(socklen_t)&amp;addr_size);
#endif
        if(client>0)
        {
            pthread_tthread;
            pthread_attr_tattr;
            pthread_attr_init(&amp;attr);
            pthread_attr_setdetachstate(&amp;attr, PTHREAD_CREATE_DETACHED);
            pthread_mutex_lock(&amp;mutex);
            pthread_create(&amp;thread, &amp;attr, Child, &amp;client);
        }
    }
#ifdef WIN32
    WSACleanup();
#endif
    return0;
}

Threaded function of communication with the client

The realization of the threaded function of communication with individual customer Child () is implemented like (parameter is a customer ID):

void * Child(void * arg)
{
    int client = * (int *) arg;
    qDebug("New client!");
    printf("New client! \ n");
    map <int, string> :: iterator it;

    char buffer[CNT]; // buffer for reading a read
    char data[NS]; // common message buffer

    char * i = data; //Start
    char * e = data + NS; // end of the buffer for a message

    ssize_t rcount; // actual number of bytes processed
    uint32_t mescnt = 0; // size of the message block

    while ((rcount = recv (client, buffer, CNT, 0)) > 0) // Read <= CNT bytes from the socket and write in the buffer.
        // Actual number of bytes read in rcount.
    {

        char * p = buffer;
        for (int q = 0; q < rcount &amp;&amp; i < e; q++) // copy the newly received bytes into a temporary buffer, check array bounds!
            *i++ = *p++;

        if (mescnt == 0) // block size is zero
        {
            if (rcount> = sizeof (uint32_t)) // message header handed
            {
                uint32_t mc = *(uint32_t *) data; // network order
                mescnt = ntohl(mc); // convert directly
            }
        }
        if (mescnt> 0) // accept and understand the rest of Posts
        {
            // because unknown in advance, as it was reported,
            // start an infinite loop
            char * s = data;
            while (true)
            {
                char * real_data = s + sizeof (uint32_t);

                // how many bytes it already?
                int already_read = i - real_data;
                if (already_read == mescnt) // it is the entire message
                {
                    analyze(real_data, mescnt, client);
                    // analysis of a series of messages is finished
                    mescnt = 0;
                    i = data;
                    break;
                }
                else // came a part of the message, or several messages
                {
                    if (already_read <mescnt) // it is smaller than it is necessary, read on
                        break;

                    // it is more than necessary
                    analyze(real_data, mescnt, client);
                    s = real_data + mescnt;

                    if (i - s <sizeof (uint32_t)) // buffer size does not fit
                    {
                        int q = 0;
                        for (; s < i; ++q)
                            data [q] = *s++;

                        i = data + q;
                        mescnt = 0;
                        break;
                    }

                    // header size fit

                    uint32_t mc = *(uint32_t *) s; // network order
                    mescnt = ntohl(mc); // convert directly

                    // we close the cycle
                }
            }
        }

    }

    // The channel error, close client connection,
    // remove the connection from the client cards. Since this is the general operation
    // data, then frame the design of the critical section.

    close(client);

    string user = clients[client];

    pthread_mutex_lock(&amp; ​​mutex);
    clients.erase(client);
    pthread_mutex_unlock (&amp; ​​mutex);

    for (it = clients.begin (); it! = clients.end (); it++)
    {
        string st = user + "closed the session.";
        writeToClient ((* it).first, st.c_str (), st.size());

    }
    qCritical ("Client died!");
    printf ("Client died! \n");
}

Sending messages to the client

How we can see the previous function uses the function writeToClient() , which sends the message to the client. The parameters passed to her customer ID, just the message itself and the number of characters. This function returns a Boolean value success or failure of the operation. Its implementation is presented below:

bool writeToClient(int client, const char * mes, const int mc)
{

    int len ​​= sizeof(uint32_t) + mc;
    uint32_t mescnt = htonl (mc);

    char * message = (char *) malloc(len);
    char * p = message;
    char * n = (char *) &amp; mescnt;

    for (int i = 0; i < sizeof(uint32_t); ++i)
        *p++ = *n++;

    for (int i = 0; i <mc; i ++)
        *p++ = *mes++;

    ssize_t rcount = send (client, message, len, 0);
    write(1, message, len);
    free(message);

    printf ("LEN% d \n", len);
    qDebug ("LEN% d", len);
    return rcount == len;
}

Requests analyzing

In addition to the above functions in the streaming function Child() function is still used analyze() . We need it in order to analyze the requests from the client and manage their treatment. Its parameters are the message length and the number of the customer. The message from the client can start with one of the following values ​​that defined in our protocol:

Command Description
LOGIN User registration in the system. The parameter passed to the username (nickname).
LIST The request to display a list of all the registered users on the system.
SEND The team send a message. The first parameter specifies the name of the user and then transmitted to indicate a line of text.
WELCOME The request at the beginning of the game by the user is passed as a parameter.
ACCEPT Approval of the request at the beginning of the game.
REJECT Cancellation of the proposed game.

Lets implement this function as follows:

void analyze (char * mes, int size, int client)
{
    mes += sizeof (uint32_t);
    size -= sizeof (uint32_t);

    map <int, string> :: iterator it; // to access the data card

    // first word of the command, and then depending on the command data

    char * cmd = mes;
    char * p = mes;
    char * e = mes + size;

    while (p <e &amp;&amp; * p! = '') p++;
    if (p == e) return;
    * p = '\ 0';

    if (! strcmp (cmd, "LOGIN"))
    {
        char * n = ++p;
        while (p <e &amp;&amp; * p! = '') p++;
        * p = '\ 0';

        for (it = clients.begin (); it! = clients.end (); it++)
        {
            string st = string (n) + "is OnLine!";
            writeToClient ((* it).first, st.c_str(), st.size());
        }

        pthread_mutex_lock (&amp; ​​mutex);
        clients [client] = n;
        pthread_mutex_unlock (&amp; ​​mutex);

        // send a greeting to the user
        const char * welcome = "Welcome to the Sea Batle server!";
        writeToClient (client, welcome, strlen (welcome));

    }
    else if (! strcmp (cmd, "LIST"))
    {
    size_t mlen = 0;
    string users = "LIST \ n";
    for (it = clients.begin(); it! = clients.end(); it ++)
        if ((* it).first! = client)
            users + = (* it) .second + "\ n";

    writeToClient (client, users.c_str (), users.size());
    }
    else if (!strcmp (cmd, "SEND"))
    {
        char * n = ++p;
        while (p <e &amp;&amp; * p! = '') p++;
        * p = '\ 0';

        bool ok = false;
        for (it = clients.begin (); it! = clients.end (); it++)
        {
            if ((* it).second == n)
            {
                p++;
                writeToClient ((* it).first, p, e-p);
                ok = true;
            }
        }
    if (!ok)
        {
            const char * out = "The user is not connected to the server.";
            writeToClient (client, out, strlen (out));
        }
    }
    else if (!strcmp (cmd, "WELCOME") || !strcmp (cmd, "ACCEPT") || !strcmp (cmd, "REJECT"))
    {
        char * n = ++p;
        while (p <e &amp;&amp; * p! = '') p++;
        * p = '\0';

        bool ok = false;
        int ic = -1;

        for (it = clients.begin(); it! = clients.end (); it ++)
        {
            if ((* it).second == n)
            {
                p++;
        string st = string (cmd) + "" + clients [client];
                writeToClient ((* it) .first, st.c_str (), st.size());
                ic = (* it).first;
                ok = true;
            }
        }
    if (!ok)
        {
            const char * out = "The user is not connected to the server.";
            writeToClient (client, out, strlen(out));
        }
        else
        {
            if (!strcmp (cmd, "ACCEPT"))
            {
                long int next = getNext();
                char s[255];
                sprintf(s, "GAMEID% ld", next);
                writeToClient(client, s, strlen (s));
                writeToClient(ic, s, strlen (s));
            }
        }
    }
}

Logging

It is also a good style to implement the function for logging. Implementing of it can be different, but we have chosen to use the following:

void PANIC(const char * msg)
{
     perror(msg);
// Exit (-1);
}

In the end, we got a number of features that are sufficient for the organization of networking for our game. For convenience, they are summarized in the table:

The function name Return Value Options Appointment
PANIC Error Message Displays a fatal error message
writeToClient The success of the operation Customer number, the message, the number of characters It sends a message to the client
analyze The message length, the number of customer Recognizes a message from a client and responds according to the protocol
Child Customer number Streaming feature, which is working with individual clients

This project on OpenSource

Full source code is placed in the public repository on GitHub licensed under Apache license, where it is available for review and use to everyone.

Compiling of the program

Server

For server compiling fits any C ++ compiler for the target platform. For example, g++ or msvc.

Client

To compile the client user need framework Qt at least version 5.3, as well as compiled libraries jpeglib for 3D-graphics.

Links

  1. repository on GitHub
  2. Sockets tutorial
  3. g++