Quickpong - development of a network game based on Twisted framework

Published on May 20, 2013

Quickpong - development of a network game based on Twisted framework

    Developed and launched on the quickpong.com domain an online version of the game Pong. In the game (by design), only the multiplayer mode is implemented, that is, the game is not against artificial intelligence, but against another person.

    The game consists of a client-server application, the server-side framework, written in Python Twisted , the client - at fleshovom framework, FlashPunk .

    This is my first experience developing an asynchronous network application that can handle thousands of concurrent connections. Next, I will talk about how this program works, what problems I had to face during development, what ideas I wanted to implement and what ultimately remained unrealized. Perhaps my experience will be useful to someone.

    Two words about the gameplay


    On the playing field, left and right are 2 boards that can move vertically. Each of the boards is controlled by a person. Between the boards, a ball moves, bouncing off the walls of the playing field and boards. The player’s task is to prevent the ball from falling outside the wall near which its board is located.

    Selection of Technologies Used


    When mastering new technologies and tools, the availability of competent detailed documentation plays a huge role. When choosing a server framework, I looked at Python's Twisted, Tornado, and Node.js.

    Based on the fact that I had no experience developing such applications at all, my choice fell on Twisted. A very detailed introductory course has been written for him , explaining the basics of developing asynchronous applications regardless of any framework or programming language, and also, of course, telling about using Twisted itself. I strongly recommend this course to anyone who wants to understand the basics of developing asynchronous applications. Using the link above you can find the translation of this course into Russian.

    In this project, the main interest for me was the development of the server side, so for the client development I decided not to bother much and make it on a flash, which I already had experience with using the FlashPunk framework.

    Client-server interaction algorithm


    To the choice of the algorithm of interaction between the client and the server, I approached not so thoughtfully as the choice of frameworks. As a result, I implemented the working version of the algorithm only the third time, and I invented the first two versions of the algorithm myself and, after two failures, I found the third version on the Internet.

    The main difficulty in such applications is the synchronization of clients; one cannot allow one client to see one picture at a time, and another client to see another.

    In addition, I would like to avoid fraud on the part of clients: if desired and skill, the client swf can be modified and can transmit cheating data to the server.

    Thus, in the first version of the algorithm, I decided to act like this:
    1. each client himself calculates the state of his game world (the position of the ball, his board and the opponent's board),
    2. each client 20 times per second transmits to the server information about the change in the position of its board and information about the parameters of the ball (coordinates, vector, speed) in its game world,
    3. the server sends each player information about the position of the opponent’s board,
    4. the server itself calculates the current state of the game world and compares it with the data received from clients (point 2). If there is a desync between the states calculated by the client and the server, then the server forcibly returns the clients to the state that it considers correct.
    The algorithm turned out to be inoperative. He could be working only if all 3 participants in the data exchange would have the same state. In practice, in 100% of cases there was a desync and it was impossible to play.

    In the second version of the algorithm, I decided to get rid of one of the links - from calculating the state of the game world on the server:
    1. each client as before calculated the coordinates of the ball and the boards and transmitted data about its state to the server,
    2. the server compared the states of the clients and if they differed, then forcibly returned one of the clients to the state of the second.
    This approach also turned out to be inoperative. Even the clients running on two identical machines calculated the state of the game world a little differently and, despite the fact that now I compared 2 states rather than 3, as in the previous case, the state of one of the clients was constantly changing forcibly, it looked like noticeable “jumps” of the ball and play was impossible.

    It became clear that, firstly, the state of the game world should be calculated only in one place, on the server, and secondly, before continuing with experiments, to avoid such a waste of time, I should study the theory and experience of other developers.

    Googling, I found such interesting links here:

    The algorithm described in the last link was taken by me for this project. Its essence is as follows:
    1. all calculations are carried out only on the server. Clients send changes of their state to the delta server, in my case, this is a change in the position of the board relative to the previous data transfer.
    2. The server calculates the positions of the boards (suppressing possible cheating on the client side) and the ball and transfers the generated data frame to the clients.
    3. Clients render the received data frames, in my case, with a delay of 3 frames. This is an important point. Data from the server to the client arrives unevenly, for example, due to network problems, the client can receive data about the new ball position not after the set 50 ms, but after 60-70 ms. That is, it may happen that the last data frame is already rendered, and a new one has not yet arrived. In this situation, the client will not have data to draw the ball and, in my case, the ball will simply stop waiting for a new portion of data to be received. In order to prevent such situations, the client draws the data late for 3 data frames, which gives some margin, thanks to which even if the data from the server is not received on time, the client will have something to render. The unpleasant side of this algorithm is a noticeable delay between the user’s action (pressing buttons on the keyboard) and the display of changes on the screen. There are methods to eliminate this delay, but specifically in this case I decided not to complicate the client.


    Implementation


    I posted the source of the server and client on Github:

    This diagram: quickpong.com/images/quickpong.png shows the logic of the client and server interaction.

    From the point of view of the server implementation, everything is quite transparent. At port 10080, an event loop starts, in which the server factory, the QuickpongServerFactory class, runs . When the factory is initialized, an instance of the Quickpong class is created , which contains all the logic for the interaction of the server with the client.

    When a new client connects, the factory calls the buildProtocol method and creates an instance of the QuickpongProtocol class for each joined client, the created object is transferred to Quickpong. Thus, the Quickpong class object has access to all the clients that have joined and can do the necessary work with them: pair them, calculate the state of the game world, etc.

    The object of the QuickpongProtocol class contains only methods for receiving and transmitting data from / to the client.

    With the implementation of the client, everything is also simple, the only interesting point was the following. Using FlashPunk I can set the picture refresh rate (FPS), while FlashPunk can guarantee that it will draw N frames per second, but cannot guarantee that every frame will be drawn in 1 / N of a second. That is, with FPS 50, in the ideal case, each frame should be drawn in 20 ms, in the real case, one frame can be drawn in 15 ms and the other in 25 ms. If the ball moves at a constant speed, for example, 10 pixels per second, and the rendering of each data frame coincides with the rendering of the FlashPunk frame, then the ball will move unevenly, jerkily, since in one case it will move 10 pixels in 15 ms, and in another for 25.

    This feature had to be taken into account in the client and before rendering the frame, I check how much time has passed since the previous frame was drawn, based on this I determine whether to render the data frame in full or in part.

    Testing and monitoring


    The most interesting question for me is how many online players can support this server? For the test, I wrote in python a small client that emulated human actions.

    The test was conducted in a virtual machine, under which 1 Intel® Xeon® CPU E31275 @ 3.40GHz core was allocated.

    Using the same Twisted, I hung up a web server on port 10082, which displays a comma-separated number of online users and the number of active games. Based on this information, as well as using the python library psutil and the rrdtool + py-rrdtool bundle, I wrote scripts that display information about the current number of online users and resources consumed in a digestible form: quickpong.com/stats.html(pictures are updated once a minute).

    With 5,000 (5 thousand) players, the program eats about 100 MB of RAM, loads the CPU on average by 30-40%.

    Roadmap


    Ideas that have remained unrealized:
    • html5 client
    • complication of the game process due to a change in the angle of bounce from the board, depending on the point of contact of the ball and the board,
    • introduction of the game mode in four, one board on each side of the playing field :)),
    • lag compensation algorithm implementation
    • fix minor bugs.

    At the moment, I have lost interest in the development of this project, maybe my achievements will seem to someone informative or interesting, for this I posted all the sources on the Github: